mirror of
https://github.com/electron/electron.git
synced 2026-10-28 11:44:46 +08:00
build: check the formatting of docs code blocks with oxfmt (#54140)
* build: check the formatting of docs code blocks with oxfmt lint-roller 4 splits the old `lint-roller-markdown-standard` into an oxlint half and an oxfmt half. This wires up the second one: `lint:fmt-in-markdown` runs `lint-roller-markdown-oxfmt` over docs/ as part of `lint:docs`, using docs/.oxfmtrc.json, which matches the repo's .oxfmtrc.json except that docs snippets keep their semicolon-free style. `--fix` writes the formatted blocks back, and a block can opt out with `@noformat` in its info string. * docs: format code blocks with oxfmt Result of `lint-roller-markdown-oxfmt --fix` with docs/.oxfmtrc.json, plus the @ts-expect-error line anchors (and one Docusaurus highlight marker) that moved with the reformat. The Babel output sample in the ESM guide and the two excerpts of repo source in creating-api.md are marked @noformat, and the contentTracing example passes an async callback instead of wrapping an IIFE so that it does not need a leading semicolon. --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
5eefaea339
commit
ad3a896a52
52 files changed
+580
-490
No files matched your search
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "../node_modules/oxfmt/configuration_schema.json",
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"trailingComma": "none",
|
||||
"ignorePatterns": ["fiddles"]
|
||||
}
|
||||
+35
-37
@@ -1021,7 +1021,8 @@ app.setJumpList([
|
||||
{ type: 'file', path: 'C:\\Projects\\project2.proj' }
|
||||
]
|
||||
},
|
||||
{ // has a name so `type` is assumed to be "custom"
|
||||
{
|
||||
// has a name so `type` is assumed to be "custom"
|
||||
name: 'Tools',
|
||||
items: [
|
||||
{
|
||||
@@ -1045,7 +1046,8 @@ app.setJumpList([
|
||||
]
|
||||
},
|
||||
{ type: 'frequent' },
|
||||
{ // has no name and no type so `type` is assumed to be "tasks"
|
||||
{
|
||||
// has no name and no type so `type` is assumed to be "tasks"
|
||||
items: [
|
||||
{
|
||||
type: 'task',
|
||||
@@ -1268,9 +1270,7 @@ const { app } = require('electron')
|
||||
app.whenReady().then(() => {
|
||||
app.configureHostResolver({
|
||||
secureDnsMode: 'secure',
|
||||
secureDnsServers: [
|
||||
'https://cloudflare-dns.com/dns-query'
|
||||
]
|
||||
secureDnsServers: ['https://cloudflare-dns.com/dns-query']
|
||||
})
|
||||
})
|
||||
```
|
||||
@@ -1481,26 +1481,26 @@ For `infoType` equal to `basic`:
|
||||
|
||||
```json5
|
||||
{
|
||||
auxAttributes:
|
||||
{
|
||||
amdSwitchable: true,
|
||||
canSupportThreadedTextureMailbox: false,
|
||||
directComposition: false,
|
||||
directRendering: true,
|
||||
glResetNotificationStrategy: 0,
|
||||
inProcessGpu: true,
|
||||
initializationTime: 0,
|
||||
jpegDecodeAcceleratorSupported: false,
|
||||
optimus: false,
|
||||
passthroughCmdDecoder: false,
|
||||
sandboxed: false,
|
||||
softwareRendering: false,
|
||||
supportsOverlays: false,
|
||||
videoDecodeAcceleratorFlags: 0
|
||||
},
|
||||
gpuDevice:
|
||||
[{ active: true, deviceId: 26657, vendorId: 4098 },
|
||||
{ active: false, deviceId: 3366, vendorId: 32902 }],
|
||||
auxAttributes: {
|
||||
amdSwitchable: true,
|
||||
canSupportThreadedTextureMailbox: false,
|
||||
directComposition: false,
|
||||
directRendering: true,
|
||||
glResetNotificationStrategy: 0,
|
||||
inProcessGpu: true,
|
||||
initializationTime: 0,
|
||||
jpegDecodeAcceleratorSupported: false,
|
||||
optimus: false,
|
||||
passthroughCmdDecoder: false,
|
||||
sandboxed: false,
|
||||
softwareRendering: false,
|
||||
supportsOverlays: false,
|
||||
videoDecodeAcceleratorFlags: 0
|
||||
},
|
||||
gpuDevice: [
|
||||
{ active: true, deviceId: 26657, vendorId: 4098 },
|
||||
{ active: false, deviceId: 3366, vendorId: 32902 }
|
||||
],
|
||||
machineModelName: 'MacBookPro',
|
||||
machineModelVersion: '11.5'
|
||||
}
|
||||
@@ -1703,11 +1703,7 @@ const { app } = require('electron')
|
||||
|
||||
app.whenReady().then(() => {
|
||||
// Enable a subset of features:
|
||||
app.setAccessibilitySupportFeatures([
|
||||
'screenReader',
|
||||
'pdfPrinting',
|
||||
'webContents'
|
||||
])
|
||||
app.setAccessibilitySupportFeatures(['screenReader', 'pdfPrinting', 'webContents'])
|
||||
|
||||
// Other logic
|
||||
|
||||
@@ -1813,12 +1809,14 @@ const { app, dialog } = require('electron')
|
||||
app.moveToApplicationsFolder({
|
||||
conflictHandler: (conflictType) => {
|
||||
if (conflictType === 'exists') {
|
||||
return dialog.showMessageBoxSync({
|
||||
type: 'question',
|
||||
buttons: ['Halt Move', 'Continue Move'],
|
||||
defaultId: 0,
|
||||
message: 'An app of this name already exists'
|
||||
}) === 1
|
||||
return (
|
||||
dialog.showMessageBoxSync({
|
||||
type: 'question',
|
||||
buttons: ['Halt Move', 'Continue Move'],
|
||||
defaultId: 0,
|
||||
message: 'An app of this name already exists'
|
||||
}) === 1
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1880,7 +1878,7 @@ The handler is called when a password is needed to unlock a client certificate f
|
||||
```js
|
||||
const { app } = require('electron')
|
||||
|
||||
async function passwordPromptUI (text) {
|
||||
async function passwordPromptUI(text) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// display UI to prompt user for password
|
||||
// ...
|
||||
|
||||
@@ -1317,10 +1317,12 @@ const { BrowserWindow } = require('electron')
|
||||
const win = new BrowserWindow()
|
||||
|
||||
win.loadURL('http://localhost:8000/post', {
|
||||
postData: [{
|
||||
type: 'rawData',
|
||||
bytes: Buffer.from('hello=world')
|
||||
}],
|
||||
postData: [
|
||||
{
|
||||
type: 'rawData',
|
||||
bytes: Buffer.from('hello=world')
|
||||
}
|
||||
],
|
||||
extraHeaders: 'Content-Type: application/x-www-form-urlencoded'
|
||||
})
|
||||
```
|
||||
|
||||
@@ -103,15 +103,12 @@ const { pathToFileURL } = require('node:url')
|
||||
// manager.
|
||||
clipboard.write([
|
||||
new ClipboardItem({
|
||||
'text/uri-list': [
|
||||
pathToFileURL('/path/to/first.txt').href,
|
||||
pathToFileURL('/path/to/second.txt').href
|
||||
].join('\r\n')
|
||||
'text/uri-list': [pathToFileURL('/path/to/first.txt').href, pathToFileURL('/path/to/second.txt').href].join('\r\n')
|
||||
})
|
||||
])
|
||||
|
||||
// Read the files currently on the clipboard.
|
||||
async function readFiles () {
|
||||
async function readFiles() {
|
||||
const [item] = await clipboard.read()
|
||||
if (item.types.includes('text/uri-list')) {
|
||||
const blob = await item.getType('text/uri-list')
|
||||
@@ -152,7 +149,7 @@ Rejects when `type` is not present in
|
||||
```js
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
async function dumpClipboard () {
|
||||
async function dumpClipboard() {
|
||||
const items = await clipboard.read()
|
||||
for (const item of items) {
|
||||
for (const type of item.types) {
|
||||
@@ -174,7 +171,7 @@ Rejects when a bookmark is not available in the clipboard.
|
||||
```js
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
async function dumpClipboard () {
|
||||
async function dumpClipboard() {
|
||||
const bookmarkType = 'electron application/bookmark'
|
||||
const items = await clipboard.read()
|
||||
for (const item of items) {
|
||||
|
||||
@@ -52,7 +52,7 @@ that follow the W3C [web custom format proposal](https://github.com/w3c/editing/
|
||||
```js
|
||||
const { clipboard, ClipboardItem } = require('electron')
|
||||
|
||||
async function writeClipboard () {
|
||||
async function writeClipboard() {
|
||||
await clipboard.write([
|
||||
new ClipboardItem({
|
||||
'web application/x.my-app-clip': new Blob(['arbitrary payload'])
|
||||
@@ -81,7 +81,7 @@ clipboard. The two clipboards are independent: writing via
|
||||
```js
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
async function run () {
|
||||
async function run() {
|
||||
await clipboard.selection.writeText('Example string')
|
||||
console.log(await clipboard.selection.readText())
|
||||
}
|
||||
@@ -103,7 +103,7 @@ API.
|
||||
```js
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
async function readText () {
|
||||
async function readText() {
|
||||
await clipboard.writeText('hello i am a bit of text!')
|
||||
const text = await clipboard.readText()
|
||||
console.log(text)
|
||||
@@ -125,7 +125,7 @@ API.
|
||||
```js
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
async function writeClipboardText () {
|
||||
async function writeClipboardText() {
|
||||
await clipboard.writeText('hello i am a bit of text!')
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ contents.
|
||||
```js
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
async function dumpClipboard () {
|
||||
async function dumpClipboard() {
|
||||
const items = await clipboard.read()
|
||||
for (const item of items) {
|
||||
for (const type of item.types) {
|
||||
@@ -169,7 +169,7 @@ const { clipboard, ClipboardItem, nativeImage } = require('electron')
|
||||
|
||||
const png = nativeImage.createFromPath('/path/to/icon.png').toPNG()
|
||||
|
||||
async function writeClipboard () {
|
||||
async function writeClipboard() {
|
||||
await clipboard.write([
|
||||
new ClipboardItem({
|
||||
'text/plain': 'hello',
|
||||
@@ -198,7 +198,7 @@ custom format (`electron application/osclipboard;format="public/utf8-plain-text"
|
||||
```js
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
async function check () {
|
||||
async function check() {
|
||||
const hasFormat = await clipboard.has('text/html')
|
||||
console.log(hasFormat)
|
||||
// 'true' or 'false'
|
||||
|
||||
@@ -14,16 +14,14 @@ This module does not include a web interface. To view recorded traces, use
|
||||
```js
|
||||
const { app, contentTracing } = require('electron')
|
||||
|
||||
app.whenReady().then(() => {
|
||||
(async () => {
|
||||
await contentTracing.startRecording({
|
||||
included_categories: ['*']
|
||||
})
|
||||
console.log('Tracing started')
|
||||
await new Promise(resolve => setTimeout(resolve, 5000))
|
||||
const path = await contentTracing.stopRecording()
|
||||
console.log('Tracing data recorded to ' + path)
|
||||
})()
|
||||
app.whenReady().then(async () => {
|
||||
await contentTracing.startRecording({
|
||||
included_categories: ['*']
|
||||
})
|
||||
console.log('Tracing started')
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000))
|
||||
const path = await contentTracing.stopRecording()
|
||||
console.log('Tracing data recorded to ' + path)
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
+25
-33
@@ -19,12 +19,9 @@ An example of exposing an API to a renderer from an isolated preload script is g
|
||||
// Preload (Isolated World)
|
||||
const { contextBridge, ipcRenderer } = require('electron')
|
||||
|
||||
contextBridge.exposeInMainWorld(
|
||||
'electron',
|
||||
{
|
||||
doThing: () => ipcRenderer.send('do-a-thing')
|
||||
}
|
||||
)
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
doThing: () => ipcRenderer.send('do-a-thing')
|
||||
})
|
||||
```
|
||||
|
||||
```js @ts-nocheck
|
||||
@@ -90,27 +87,24 @@ An example of a complex API is shown below:
|
||||
```js
|
||||
const { contextBridge, ipcRenderer } = require('electron')
|
||||
|
||||
contextBridge.exposeInMainWorld(
|
||||
'electron',
|
||||
{
|
||||
doThing: () => ipcRenderer.send('do-a-thing'),
|
||||
myPromises: [Promise.resolve(), Promise.reject(new Error('whoops'))],
|
||||
anAsyncFunction: async () => 123,
|
||||
data: {
|
||||
myFlags: ['a', 'b', 'c'],
|
||||
bootTime: 1234
|
||||
},
|
||||
nestedAPI: {
|
||||
evenDeeper: {
|
||||
youCanDoThisAsMuchAsYouWant: {
|
||||
fn: () => ({
|
||||
returnData: 123
|
||||
})
|
||||
}
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
doThing: () => ipcRenderer.send('do-a-thing'),
|
||||
myPromises: [Promise.resolve(), Promise.reject(new Error('whoops'))],
|
||||
anAsyncFunction: async () => 123,
|
||||
data: {
|
||||
myFlags: ['a', 'b', 'c'],
|
||||
bootTime: 1234
|
||||
},
|
||||
nestedAPI: {
|
||||
evenDeeper: {
|
||||
youCanDoThisAsMuchAsYouWant: {
|
||||
fn: () => ({
|
||||
returnData: 123
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
An example of `exposeInIsolatedWorld` is shown below:
|
||||
@@ -118,13 +112,9 @@ An example of `exposeInIsolatedWorld` is shown below:
|
||||
```js
|
||||
const { contextBridge, ipcRenderer } = require('electron')
|
||||
|
||||
contextBridge.exposeInIsolatedWorld(
|
||||
1004,
|
||||
'electron',
|
||||
{
|
||||
doThing: () => ipcRenderer.send('do-a-thing')
|
||||
}
|
||||
)
|
||||
contextBridge.exposeInIsolatedWorld(1004, 'electron', {
|
||||
doThing: () => ipcRenderer.send('do-a-thing')
|
||||
})
|
||||
```
|
||||
|
||||
```js @ts-nocheck
|
||||
@@ -178,7 +168,9 @@ contextBridge.exposeInMainWorld('electron', {
|
||||
|
||||
```js @ts-nocheck
|
||||
// Renderer (Main World)
|
||||
window.electron.onMyEventName(data => { /* ... */ })
|
||||
window.electron.onMyEventName((data) => {
|
||||
/* ... */
|
||||
})
|
||||
```
|
||||
|
||||
### Exposing Node Global Symbols
|
||||
@@ -194,7 +186,7 @@ const { contextBridge } = require('electron')
|
||||
const crypto = require('node:crypto')
|
||||
|
||||
contextBridge.exposeInMainWorld('nodeCrypto', {
|
||||
sha256sum (data) {
|
||||
sha256sum(data) {
|
||||
const hash = crypto.createHash('sha256')
|
||||
hash.update(data)
|
||||
return hash.digest('hex')
|
||||
|
||||
+14
-8
@@ -14,30 +14,36 @@ For example:
|
||||
const { session } = require('electron')
|
||||
|
||||
// Query all cookies.
|
||||
session.defaultSession.cookies.get({})
|
||||
session.defaultSession.cookies
|
||||
.get({})
|
||||
.then((cookies) => {
|
||||
console.log(cookies)
|
||||
}).catch((error) => {
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
// Query all cookies associated with a specific url.
|
||||
session.defaultSession.cookies.get({ url: 'https://www.github.com' })
|
||||
session.defaultSession.cookies
|
||||
.get({ url: 'https://www.github.com' })
|
||||
.then((cookies) => {
|
||||
console.log(cookies)
|
||||
}).catch((error) => {
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
// Set a cookie with the given cookie data;
|
||||
// may overwrite equivalent cookies if they exist.
|
||||
const cookie = { url: 'https://www.github.com', name: 'dummy_name', value: 'dummy' }
|
||||
session.defaultSession.cookies.set(cookie)
|
||||
.then(() => {
|
||||
session.defaultSession.cookies.set(cookie).then(
|
||||
() => {
|
||||
// success
|
||||
}, (error) => {
|
||||
},
|
||||
(error) => {
|
||||
console.error(error)
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Instance Events
|
||||
|
||||
@@ -15,16 +15,19 @@ const { app, BrowserWindow, desktopCapturer, session } = require('electron')
|
||||
app.whenReady().then(() => {
|
||||
const mainWindow = new BrowserWindow()
|
||||
|
||||
session.defaultSession.setDisplayMediaRequestHandler((request, callback) => {
|
||||
desktopCapturer.getSources({ types: ['screen'] }).then((sources) => {
|
||||
// Grant access to the first screen found.
|
||||
callback({ video: sources[0], audio: 'loopback' })
|
||||
})
|
||||
// If true, use the system picker if available.
|
||||
// Note: this is currently experimental. If the system picker
|
||||
// is available, it will be used and the media request handler
|
||||
// will not be invoked.
|
||||
}, { useSystemPicker: true })
|
||||
session.defaultSession.setDisplayMediaRequestHandler(
|
||||
(request, callback) => {
|
||||
desktopCapturer.getSources({ types: ['screen'] }).then((sources) => {
|
||||
// Grant access to the first screen found.
|
||||
callback({ video: sources[0], audio: 'loopback' })
|
||||
})
|
||||
// If true, use the system picker if available.
|
||||
// Note: this is currently experimental. If the system picker
|
||||
// is available, it will be used and the media request handler
|
||||
// will not be invoked.
|
||||
},
|
||||
{ useSystemPicker: true }
|
||||
)
|
||||
|
||||
mainWindow.loadFile('index.html')
|
||||
})
|
||||
@@ -37,17 +40,20 @@ const stopButton = document.getElementById('stopButton')
|
||||
const video = document.querySelector('video')
|
||||
|
||||
startButton.addEventListener('click', () => {
|
||||
navigator.mediaDevices.getDisplayMedia({
|
||||
audio: true,
|
||||
video: {
|
||||
width: 320,
|
||||
height: 240,
|
||||
frameRate: 30
|
||||
}
|
||||
}).then(stream => {
|
||||
video.srcObject = stream
|
||||
video.onloadedmetadata = (e) => video.play()
|
||||
}).catch(e => console.log(e))
|
||||
navigator.mediaDevices
|
||||
.getDisplayMedia({
|
||||
audio: true,
|
||||
video: {
|
||||
width: 320,
|
||||
height: 240,
|
||||
frameRate: 30
|
||||
}
|
||||
})
|
||||
.then((stream) => {
|
||||
video.srcObject = stream
|
||||
video.onloadedmetadata = (e) => video.play()
|
||||
})
|
||||
.catch((e) => console.log(e))
|
||||
})
|
||||
|
||||
stopButton.addEventListener('click', () => {
|
||||
|
||||
+11
-8
@@ -171,14 +171,17 @@ The `extensions` array should contain extensions without wildcards or dots (e.g.
|
||||
> shown.
|
||||
|
||||
```js @ts-type={mainWindow:Electron.BaseWindow}
|
||||
dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openFile', 'openDirectory']
|
||||
}).then(result => {
|
||||
console.log(result.canceled)
|
||||
console.log(result.filePaths)
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
dialog
|
||||
.showOpenDialog(mainWindow, {
|
||||
properties: ['openFile', 'openDirectory']
|
||||
})
|
||||
.then((result) => {
|
||||
console.log(result.canceled)
|
||||
console.log(result.filePaths)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ Node.js's HTTP stack.
|
||||
Example:
|
||||
|
||||
```js
|
||||
async function example () {
|
||||
async function example() {
|
||||
const response = await net.fetch('https://my.app')
|
||||
if (response.ok) {
|
||||
const body = await response.json()
|
||||
|
||||
@@ -88,9 +88,7 @@ Policy:
|
||||
```js
|
||||
const { protocol } = require('electron')
|
||||
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{ scheme: 'foo', privileges: { bypassCSP: true } }
|
||||
])
|
||||
protocol.registerSchemesAsPrivileged([{ scheme: 'foo', privileges: { bypassCSP: true } }])
|
||||
```
|
||||
|
||||
A standard scheme adheres to what RFC 3986 calls [generic URI syntax](https://tools.ietf.org/html/rfc3986#section-3).
|
||||
@@ -352,7 +350,7 @@ const { protocol } = require('electron')
|
||||
|
||||
const { PassThrough } = require('node:stream')
|
||||
|
||||
function createStream (text) {
|
||||
function createStream(text) {
|
||||
const rv = new PassThrough() // PassThrough is also a Readable stream
|
||||
rv.push(text)
|
||||
rv.push(null)
|
||||
|
||||
+50
-31
@@ -185,7 +185,7 @@ Returns:
|
||||
```js
|
||||
const { app, dialog, BrowserWindow, session } = require('electron')
|
||||
|
||||
async function createWindow () {
|
||||
async function createWindow() {
|
||||
const mainWindow = new BrowserWindow()
|
||||
|
||||
await mainWindow.loadURL('https://buzzfeed.com')
|
||||
@@ -208,14 +208,16 @@ async function createWindow () {
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.webContents.executeJavaScript(`
|
||||
mainWindow.webContents.executeJavaScript(
|
||||
`
|
||||
window.showDirectoryPicker({
|
||||
id: 'electron-demo',
|
||||
mode: 'readwrite',
|
||||
startIn: 'downloads',
|
||||
}).catch(e => {
|
||||
console.log(e)
|
||||
})`, true
|
||||
})`,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
@@ -341,9 +343,12 @@ app.whenReady().then(() => {
|
||||
|
||||
// Search through the list of devices that have previously been granted permission
|
||||
return grantedDevices.some((grantedDevice) => {
|
||||
return grantedDevice.vendorId === details.device.vendorId &&
|
||||
grantedDevice.productId === details.device.productId &&
|
||||
grantedDevice.serialNumber && grantedDevice.serialNumber === details.device.serialNumber
|
||||
return (
|
||||
grantedDevice.vendorId === details.device.vendorId &&
|
||||
grantedDevice.productId === details.device.productId &&
|
||||
grantedDevice.serialNumber &&
|
||||
grantedDevice.serialNumber === details.device.serialNumber
|
||||
)
|
||||
})
|
||||
}
|
||||
return false
|
||||
@@ -452,9 +457,12 @@ app.whenReady().then(() => {
|
||||
|
||||
// Search through the list of devices that have previously been granted permission
|
||||
return grantedDevices.some((grantedDevice) => {
|
||||
return grantedDevice.vendorId === details.device.vendorId &&
|
||||
grantedDevice.productId === details.device.productId &&
|
||||
grantedDevice.serialNumber && grantedDevice.serialNumber === details.device.serialNumber
|
||||
return (
|
||||
grantedDevice.vendorId === details.device.vendorId &&
|
||||
grantedDevice.productId === details.device.productId &&
|
||||
grantedDevice.serialNumber &&
|
||||
grantedDevice.serialNumber === details.device.serialNumber
|
||||
)
|
||||
})
|
||||
}
|
||||
return false
|
||||
@@ -594,9 +602,12 @@ app.whenReady().then(() => {
|
||||
|
||||
// Search through the list of devices that have previously been granted permission
|
||||
return grantedDevices.some((grantedDevice) => {
|
||||
return grantedDevice.vendorId === details.device.vendorId &&
|
||||
grantedDevice.productId === details.device.productId &&
|
||||
grantedDevice.serialNumber && grantedDevice.serialNumber === details.device.serialNumber
|
||||
return (
|
||||
grantedDevice.vendorId === details.device.vendorId &&
|
||||
grantedDevice.productId === details.device.productId &&
|
||||
grantedDevice.serialNumber &&
|
||||
grantedDevice.serialNumber === details.device.serialNumber
|
||||
)
|
||||
})
|
||||
}
|
||||
return false
|
||||
@@ -921,7 +932,7 @@ Node.js's HTTP stack.
|
||||
Example:
|
||||
|
||||
```js
|
||||
async function example () {
|
||||
async function example() {
|
||||
const response = await net.fetch('https://my.app')
|
||||
if (response.ok) {
|
||||
const body = await response.json()
|
||||
@@ -1236,16 +1247,19 @@ is set to `true`, the handler will not be invoked.
|
||||
```js
|
||||
const { session, desktopCapturer } = require('electron')
|
||||
|
||||
session.defaultSession.setDisplayMediaRequestHandler((request, callback) => {
|
||||
desktopCapturer.getSources({ types: ['screen'] }).then((sources) => {
|
||||
// Grant access to the first screen found.
|
||||
callback({ video: sources[0] })
|
||||
})
|
||||
// Use the system picker if available.
|
||||
// Note: this is currently experimental. If the system picker
|
||||
// is available, it will be used and the media request handler
|
||||
// will not be invoked.
|
||||
}, { useSystemPicker: true })
|
||||
session.defaultSession.setDisplayMediaRequestHandler(
|
||||
(request, callback) => {
|
||||
desktopCapturer.getSources({ types: ['screen'] }).then((sources) => {
|
||||
// Grant access to the first screen found.
|
||||
callback({ video: sources[0] })
|
||||
})
|
||||
// Use the system picker if available.
|
||||
// Note: this is currently experimental. If the system picker
|
||||
// is available, it will be used and the media request handler
|
||||
// will not be invoked.
|
||||
},
|
||||
{ useSystemPicker: true }
|
||||
)
|
||||
```
|
||||
|
||||
Passing a [WebFrameMain](web-frame-main.md) object as a video or audio stream
|
||||
@@ -1320,9 +1334,12 @@ app.whenReady().then(() => {
|
||||
|
||||
// Search through the list of devices that have previously been granted permission
|
||||
return grantedDevices.some((grantedDevice) => {
|
||||
return grantedDevice.vendorId === details.device.vendorId &&
|
||||
grantedDevice.productId === details.device.productId &&
|
||||
grantedDevice.serialNumber && grantedDevice.serialNumber === details.device.serialNumber
|
||||
return (
|
||||
grantedDevice.vendorId === details.device.vendorId &&
|
||||
grantedDevice.productId === details.device.productId &&
|
||||
grantedDevice.serialNumber &&
|
||||
grantedDevice.serialNumber === details.device.serialNumber
|
||||
)
|
||||
})
|
||||
} else if (details.deviceType === 'serial') {
|
||||
if (details.device.vendorId === 123 && details.device.productId === 345) {
|
||||
@@ -1431,7 +1448,7 @@ const { app, BrowserWindow, session } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
|
||||
function createWindow () {
|
||||
function createWindow() {
|
||||
let bluetoothPinCallback = null
|
||||
|
||||
const mainWindow = new BrowserWindow({
|
||||
@@ -1911,10 +1928,12 @@ const path = require('node:path')
|
||||
|
||||
app.whenReady().then(() => {
|
||||
const protocol = session.fromPartition('some-partition').protocol
|
||||
if (!protocol.registerFileProtocol('atom', (request, callback) => {
|
||||
const url = request.url.substr(7)
|
||||
callback({ path: path.normalize(path.join(__dirname, url)) })
|
||||
})) {
|
||||
if (
|
||||
!protocol.registerFileProtocol('atom', (request, callback) => {
|
||||
const url = request.url.substr(7)
|
||||
callback({ path: path.normalize(path.join(__dirname, url)) })
|
||||
})
|
||||
) {
|
||||
console.error('Failed to register protocol')
|
||||
}
|
||||
})
|
||||
|
||||
@@ -32,11 +32,13 @@ app.whenReady().then(() => {
|
||||
const items = ['One', 'Two', 'Three']
|
||||
const n = new Notification({
|
||||
title: 'Choose an option',
|
||||
actions: [{
|
||||
type: 'selection',
|
||||
text: 'Apply',
|
||||
items
|
||||
}]
|
||||
actions: [
|
||||
{
|
||||
type: 'selection',
|
||||
text: 'Apply',
|
||||
items
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
n.on('action', (e) => {
|
||||
|
||||
@@ -320,11 +320,14 @@ Returns `Promise<void>` - resolves if the user has successfully authenticated wi
|
||||
```js
|
||||
const { systemPreferences } = require('electron')
|
||||
|
||||
systemPreferences.promptTouchID('To get consent for a Security-Gated Thing').then(success => {
|
||||
console.log('You have successfully authenticated with Touch ID!')
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
systemPreferences
|
||||
.promptTouchID('To get consent for a Security-Gated Thing')
|
||||
.then((success) => {
|
||||
console.log('You have successfully authenticated with Touch ID!')
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
```
|
||||
|
||||
This API itself will not protect your user data; rather, it is a mechanism to allow you to do so. Native apps will need to set [Access Control Constants](https://developer.apple.com/documentation/security/secaccesscontrolcreateflags?language=objc) like [`kSecAccessControlUserPresence`](https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/ksecaccesscontroluserpresence?language=objc) on their keychain entry so that reading it would auto-prompt for Touch ID biometric consent. This could be done with [`node-keytar`](https://github.com/atom/node-keytar), such that one would store an encryption key with `node-keytar` and only fetch it if `promptTouchID()` resolves.
|
||||
|
||||
@@ -115,7 +115,7 @@ const spin = new TouchBarButton({
|
||||
const spinReels = () => {
|
||||
updateReels()
|
||||
|
||||
if ((Date.now() - startTime) >= spinLength) {
|
||||
if (Date.now() - startTime >= spinLength) {
|
||||
finishSpin()
|
||||
} else {
|
||||
// Slow down a bit on each spin
|
||||
|
||||
+29
-20
@@ -94,7 +94,7 @@ When communicating with the [Chrome DevTools Protocol](https://chromedevtools.gi
|
||||
it can be useful to lookup a WebContents instance based on its assigned TargetID.
|
||||
|
||||
```js
|
||||
async function lookupTargetId (browserWindow) {
|
||||
async function lookupTargetId(browserWindow) {
|
||||
const wc = browserWindow.webContents
|
||||
await wc.debugger.attach('1.3')
|
||||
const { targetInfo } = await wc.debugger.sendCommand('Target.getTargetInfo')
|
||||
@@ -472,7 +472,7 @@ win.webContents.on('will-prevent-unload', (event) => {
|
||||
defaultId: 0,
|
||||
cancelId: 1
|
||||
})
|
||||
const leave = (choice === 0)
|
||||
const leave = choice === 0
|
||||
if (leave) {
|
||||
event.preventDefault()
|
||||
}
|
||||
@@ -972,7 +972,7 @@ win.webContents.on('paint', async (e, dirty, image) => {
|
||||
if (e.texture) {
|
||||
// By managing lifecycle yourself, you can handle the event in async handler or pass the `e.texture.textureInfo`
|
||||
// to other processes (not `e.texture`, the `e.texture.release` function is not passable through IPC).
|
||||
await new Promise(resolve => setTimeout(resolve, 50))
|
||||
await new Promise((resolve) => setTimeout(resolve, 50))
|
||||
|
||||
// You can send the native texture handle to native code for importing into your rendering pipeline.
|
||||
// Read more at https://github.com/electron/electron/blob/main/shell/browser/osr/README.md
|
||||
@@ -1448,7 +1448,8 @@ Code execution will be suspended until web page stop loading.
|
||||
```js
|
||||
const win = new BrowserWindow()
|
||||
|
||||
win.webContents.executeJavaScript('fetch("https://jsonplaceholder.typicode.com/users/1").then(resp => resp.json())', true)
|
||||
win.webContents
|
||||
.executeJavaScript('fetch("https://jsonplaceholder.typicode.com/users/1").then(resp => resp.json())', true)
|
||||
.then((result) => {
|
||||
console.log(result) // Will be the JSON object from the fetch call
|
||||
})
|
||||
@@ -1902,10 +1903,12 @@ const win = new BrowserWindow()
|
||||
const options = {
|
||||
silent: true,
|
||||
deviceName: 'My-Printer',
|
||||
pageRanges: [{
|
||||
from: 0,
|
||||
to: 1
|
||||
}]
|
||||
pageRanges: [
|
||||
{
|
||||
from: 0,
|
||||
to: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
win.webContents.print(options, (success, errorType) => {
|
||||
if (!success) console.log(errorType)
|
||||
@@ -1938,14 +1941,17 @@ app.whenReady().then(() => {
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
// Use default printing options
|
||||
const pdfPath = path.join(os.homedir(), 'Desktop', 'temp.pdf')
|
||||
win.webContents.printToPDF({}).then(data => {
|
||||
fs.writeFile(pdfPath, data, (error) => {
|
||||
if (error) throw error
|
||||
console.log(`Wrote PDF successfully to ${pdfPath}`)
|
||||
win.webContents
|
||||
.printToPDF({})
|
||||
.then((data) => {
|
||||
fs.writeFile(pdfPath, data, (error) => {
|
||||
if (error) throw error
|
||||
console.log(`Wrote PDF successfully to ${pdfPath}`)
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(`Failed to write PDF to ${pdfPath}: `, error)
|
||||
})
|
||||
}).catch(error => {
|
||||
console.log(`Failed to write PDF to ${pdfPath}: `, error)
|
||||
})
|
||||
})
|
||||
})
|
||||
```
|
||||
@@ -2258,11 +2264,14 @@ const win = new BrowserWindow()
|
||||
win.loadURL('https://github.com')
|
||||
|
||||
win.webContents.on('did-finish-load', async () => {
|
||||
win.webContents.savePage('/tmp/test.html', 'HTMLComplete').then(() => {
|
||||
console.log('Page was saved successfully.')
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
win.webContents
|
||||
.savePage('/tmp/test.html', 'HTMLComplete')
|
||||
.then(() => {
|
||||
console.log('Page was saved successfully.')
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ of [`WebContents`](web-contents.md).
|
||||
```js
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
async function main () {
|
||||
async function main() {
|
||||
const win = new BrowserWindow({ width: 800, height: 600 })
|
||||
await win.loadURL('https://reddit.com')
|
||||
|
||||
@@ -226,14 +226,17 @@ app.whenReady().then(() => {
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
const pdfPath = path.join(os.homedir(), 'Desktop', 'iframe.pdf')
|
||||
const iframe = win.webContents.mainFrame.frames[0]
|
||||
iframe.printToPDF({}).then(data => {
|
||||
fs.writeFile(pdfPath, data, (error) => {
|
||||
if (error) throw error
|
||||
console.log(`Wrote PDF successfully to ${pdfPath}`)
|
||||
iframe
|
||||
.printToPDF({})
|
||||
.then((data) => {
|
||||
fs.writeFile(pdfPath, data, (error) => {
|
||||
if (error) throw error
|
||||
console.log(`Wrote PDF successfully to ${pdfPath}`)
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(`Failed to write PDF to ${pdfPath}: `, error)
|
||||
})
|
||||
}).catch(error => {
|
||||
console.log(`Failed to write PDF to ${pdfPath}: `, error)
|
||||
})
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
+14
-6
@@ -111,9 +111,9 @@ const { webFrame } = require('electron')
|
||||
const spellChecker = require('spellchecker')
|
||||
|
||||
webFrame.setSpellCheckProvider('en-US', {
|
||||
spellCheck (words, callback) {
|
||||
spellCheck(words, callback) {
|
||||
setTimeout(() => {
|
||||
const misspelled = words.filter(x => spellchecker.isMisspelled(x))
|
||||
const misspelled = words.filter((x) => spellchecker.isMisspelled(x))
|
||||
callback(misspelled)
|
||||
}, 0)
|
||||
}
|
||||
@@ -249,10 +249,18 @@ This will generate:
|
||||
size: 2549,
|
||||
liveSize: 2542
|
||||
},
|
||||
cssStyleSheets: { /* same with "images" */ },
|
||||
xslStyleSheets: { /* same with "images" */ },
|
||||
fonts: { /* same with "images" */ },
|
||||
other: { /* same with "images" */ }
|
||||
cssStyleSheets: {
|
||||
/* same with "images" */
|
||||
},
|
||||
xslStyleSheets: {
|
||||
/* same with "images" */
|
||||
},
|
||||
fonts: {
|
||||
/* same with "images" */
|
||||
},
|
||||
other: {
|
||||
/* same with "images" */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ electronApi.doSomethingWithFile(file)
|
||||
const { contextBridge, webUtils } = require('electron')
|
||||
|
||||
contextBridge.exposeInMainWorld('electronApi', {
|
||||
doSomethingWithFile (file) {
|
||||
doSomethingWithFile(file) {
|
||||
const path = webUtils.getPathForFile(file)
|
||||
// Do something with the path, e.g., send it over IPC to the main process.
|
||||
// It's best not to expose the full file path to the web content if possible.
|
||||
|
||||
+158
-111
@@ -412,7 +412,7 @@ now reached through a new `clipboard.selection` sub-namespace.
|
||||
```js
|
||||
const { clipboard, ClipboardItem } = require('electron')
|
||||
|
||||
function getClipboardToUse (clipboardType) {
|
||||
function getClipboardToUse(clipboardType) {
|
||||
if (clipboardType === 'selection') {
|
||||
return clipboard.selection
|
||||
} else {
|
||||
@@ -420,10 +420,10 @@ function getClipboardToUse (clipboardType) {
|
||||
}
|
||||
}
|
||||
|
||||
async function readClipboard (format, clipboardType) {
|
||||
async function readClipboard(format, clipboardType) {
|
||||
const clipboardToUse = getClipboardToUse(clipboardType)
|
||||
const clipboardItems = await clipboardToUse.read()
|
||||
const foundItem = clipboardItems.find(clipboardItem => {
|
||||
const foundItem = clipboardItems.find((clipboardItem) => {
|
||||
return clipboardItem.types.includes(format)
|
||||
})
|
||||
if (foundItem) {
|
||||
@@ -433,7 +433,7 @@ async function readClipboard (format, clipboardType) {
|
||||
}
|
||||
}
|
||||
|
||||
async function writeClipboard (format, text, clipboardType) {
|
||||
async function writeClipboard(format, text, clipboardType) {
|
||||
const clipboardToUse = getClipboardToUse(clipboardType)
|
||||
return clipboardToUse.write([
|
||||
new ClipboardItem({
|
||||
@@ -442,10 +442,10 @@ async function writeClipboard (format, text, clipboardType) {
|
||||
])
|
||||
}
|
||||
|
||||
async function readBuffer (format, clipboardType) {
|
||||
async function readBuffer(format, clipboardType) {
|
||||
const clipboardToUse = getClipboardToUse(clipboardType)
|
||||
const clipboardItems = await clipboardToUse.read()
|
||||
const foundItem = clipboardItems.find(clipboardItem => {
|
||||
const foundItem = clipboardItems.find((clipboardItem) => {
|
||||
return clipboardItem.types.includes(format)
|
||||
})
|
||||
if (foundItem) {
|
||||
@@ -455,7 +455,7 @@ async function readBuffer (format, clipboardType) {
|
||||
}
|
||||
}
|
||||
|
||||
async function writeBuffer (format, buffer, clipboardType) {
|
||||
async function writeBuffer(format, buffer, clipboardType) {
|
||||
const clipboardToUse = getClipboardToUse(clipboardType)
|
||||
return clipboardToUse.write([
|
||||
new ClipboardItem({
|
||||
@@ -464,7 +464,7 @@ async function writeBuffer (format, buffer, clipboardType) {
|
||||
])
|
||||
}
|
||||
|
||||
async function availableFormats (clipboardType) {
|
||||
async function availableFormats(clipboardType) {
|
||||
const clipboardToUse = getClipboardToUse(clipboardType)
|
||||
const clipboardItems = await clipboardToUse.read()
|
||||
const clipboardFormats = []
|
||||
@@ -478,7 +478,7 @@ async function availableFormats (clipboardType) {
|
||||
return clipboardFormats
|
||||
}
|
||||
|
||||
async function has (format, clipboardType, isRawFormat) {
|
||||
async function has(format, clipboardType, isRawFormat) {
|
||||
const clipboardToUse = getClipboardToUse(clipboardType)
|
||||
let mimeType = format
|
||||
if (isRawFormat) {
|
||||
@@ -488,10 +488,10 @@ async function has (format, clipboardType, isRawFormat) {
|
||||
}
|
||||
|
||||
const BOOKMARK_MIME_TYPE = 'electron application/bookmark'
|
||||
async function readBookmark (clipboardType) {
|
||||
async function readBookmark(clipboardType) {
|
||||
const clipboardToUse = getClipboardToUse(clipboardType)
|
||||
const clipboardItems = await clipboardToUse.read()
|
||||
const foundItem = clipboardItems.find(clipboardItem => {
|
||||
const foundItem = clipboardItems.find((clipboardItem) => {
|
||||
return clipboardItem.types.includes(BOOKMARK_MIME_TYPE)
|
||||
})
|
||||
if (foundItem) {
|
||||
@@ -501,7 +501,7 @@ async function readBookmark (clipboardType) {
|
||||
}
|
||||
}
|
||||
|
||||
async function writeBookmark (title, url, clipboardType) {
|
||||
async function writeBookmark(title, url, clipboardType) {
|
||||
const clipboardToUse = getClipboardToUse(clipboardType)
|
||||
return clipboardToUse.write([
|
||||
new ClipboardItem({
|
||||
@@ -511,41 +511,39 @@ async function writeBookmark (title, url, clipboardType) {
|
||||
}
|
||||
|
||||
const FIND_TEXT_MIME_TYPE = 'electron application/findtext'
|
||||
async function readFindText () {
|
||||
async function readFindText() {
|
||||
return readClipboard(FIND_TEXT_MIME_TYPE)
|
||||
}
|
||||
|
||||
async function writeFindText (text) {
|
||||
async function writeFindText(text) {
|
||||
return writeClipboard(FIND_TEXT_MIME_TYPE, text)
|
||||
}
|
||||
|
||||
const HTML_MIME_TYPE = 'text/html'
|
||||
async function readHTML (clipboardType) {
|
||||
async function readHTML(clipboardType) {
|
||||
return readClipboard(HTML_MIME_TYPE, clipboardType)
|
||||
}
|
||||
|
||||
async function writeHTML (markup, clipboardType) {
|
||||
async function writeHTML(markup, clipboardType) {
|
||||
return writeClipboard(HTML_MIME_TYPE, markup, clipboardType)
|
||||
}
|
||||
|
||||
const PNG_MIME_TYPE = 'image/png'
|
||||
const JPEG_MIME_TYPE = 'image/jpeg'
|
||||
async function readImage (clipboardType) {
|
||||
async function readImage(clipboardType) {
|
||||
const clipboardToUse = getClipboardToUse(clipboardType)
|
||||
const clipboardItems = await clipboardToUse.read()
|
||||
// Look for PNG first
|
||||
let foundItem = clipboardItems.find(clipboardItem => {
|
||||
let foundItem = clipboardItems.find((clipboardItem) => {
|
||||
return clipboardItem.types.includes(PNG_MIME_TYPE)
|
||||
})
|
||||
if (!foundItem) {
|
||||
foundItem = clipboardItems.find(clipboardItem => {
|
||||
foundItem = clipboardItems.find((clipboardItem) => {
|
||||
return clipboardItem.types.includes(JPEG_MIME_TYPE)
|
||||
})
|
||||
}
|
||||
if (foundItem) {
|
||||
const mimeType = foundItem.types.includes(PNG_MIME_TYPE)
|
||||
? PNG_MIME_TYPE
|
||||
: JPEG_MIME_TYPE
|
||||
const mimeType = foundItem.types.includes(PNG_MIME_TYPE) ? PNG_MIME_TYPE : JPEG_MIME_TYPE
|
||||
// getType() resolves to a Blob; convert it to a Buffer for nativeImage.
|
||||
const blob = await foundItem.getType(mimeType)
|
||||
const buffer = Buffer.from(await blob.arrayBuffer())
|
||||
@@ -553,7 +551,7 @@ async function readImage (clipboardType) {
|
||||
}
|
||||
}
|
||||
|
||||
async function writeImage (image, clipboardType) {
|
||||
async function writeImage(image, clipboardType) {
|
||||
const clipboardToUse = getClipboardToUse(clipboardType)
|
||||
return clipboardToUse.write([
|
||||
new ClipboardItem({
|
||||
@@ -563,11 +561,11 @@ async function writeImage (image, clipboardType) {
|
||||
}
|
||||
|
||||
const RTF_MIME_TYPE = 'text/rtf'
|
||||
async function readRTF (clipboardType) {
|
||||
async function readRTF(clipboardType) {
|
||||
return readClipboard(RTF_MIME_TYPE, clipboardType)
|
||||
}
|
||||
|
||||
async function writeRTF (text, clipboardType) {
|
||||
async function writeRTF(text, clipboardType) {
|
||||
return writeClipboard(RTF_MIME_TYPE, text, clipboardType)
|
||||
}
|
||||
```
|
||||
@@ -809,10 +807,7 @@ Electron 45 and no longer has any effect):
|
||||
|
||||
```js
|
||||
// main.js (right beneath your require/import statements)
|
||||
app.commandLine.appendSwitch(
|
||||
'disable-features',
|
||||
'MacCatapLoopbackAudioForScreenShare'
|
||||
)
|
||||
app.commandLine.appendSwitch('disable-features', 'MacCatapLoopbackAudioForScreenShare')
|
||||
```
|
||||
|
||||
### Behavior Changed: shared texture OSR `paint` event data structure
|
||||
@@ -1131,7 +1126,9 @@ protocol.registerFileProtocol('other', () => {
|
||||
})
|
||||
|
||||
const mainWindow = new BrowserWindow()
|
||||
mainWindow.loadURL('data:text/html,<script src="loaded-from-dataurl.js"></script>', { baseURLForDataURL: 'other://C:\\myapp' })
|
||||
mainWindow.loadURL('data:text/html,<script src="loaded-from-dataurl.js"></script>', {
|
||||
baseURLForDataURL: 'other://C:\\myapp'
|
||||
})
|
||||
mainWindow.loadURL('other://C:\\myapp\\index.html')
|
||||
|
||||
// Replace with
|
||||
@@ -1209,7 +1206,7 @@ electron.showFilePath(file)
|
||||
const { contextBridge, webUtils } = require('electron')
|
||||
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
showFilePath (file) {
|
||||
showFilePath(file) {
|
||||
// It's best not to expose the full file path to the web content if
|
||||
// possible.
|
||||
const path = webUtils.getPathForFile(file)
|
||||
@@ -1348,10 +1345,14 @@ Use the new `render-process-gone` event instead.
|
||||
|
||||
```js
|
||||
// Removed
|
||||
app.on('renderer-process-crashed', (event, webContents, killed) => { /* ... */ })
|
||||
app.on('renderer-process-crashed', (event, webContents, killed) => {
|
||||
/* ... */
|
||||
})
|
||||
|
||||
// Replace with
|
||||
app.on('render-process-gone', (event, webContents, details) => { /* ... */ })
|
||||
app.on('render-process-gone', (event, webContents, details) => {
|
||||
/* ... */
|
||||
})
|
||||
```
|
||||
|
||||
### Removed: `crashed` event on `WebContents` and `<webview>`
|
||||
@@ -1361,12 +1362,20 @@ Use the new `render-process-gone` event instead.
|
||||
|
||||
```js
|
||||
// Removed
|
||||
win.webContents.on('crashed', (event, killed) => { /* ... */ })
|
||||
webview.addEventListener('crashed', (event) => { /* ... */ })
|
||||
win.webContents.on('crashed', (event, killed) => {
|
||||
/* ... */
|
||||
})
|
||||
webview.addEventListener('crashed', (event) => {
|
||||
/* ... */
|
||||
})
|
||||
|
||||
// Replace with
|
||||
win.webContents.on('render-process-gone', (event, details) => { /* ... */ })
|
||||
webview.addEventListener('render-process-gone', (event) => { /* ... */ })
|
||||
win.webContents.on('render-process-gone', (event, details) => {
|
||||
/* ... */
|
||||
})
|
||||
webview.addEventListener('render-process-gone', (event) => {
|
||||
/* ... */
|
||||
})
|
||||
```
|
||||
|
||||
### Removed: `gpu-process-crashed` event on `app`
|
||||
@@ -1376,10 +1385,14 @@ Use the new `child-process-gone` event instead.
|
||||
|
||||
```js
|
||||
// Removed
|
||||
app.on('gpu-process-crashed', (event, killed) => { /* ... */ })
|
||||
app.on('gpu-process-crashed', (event, killed) => {
|
||||
/* ... */
|
||||
})
|
||||
|
||||
// Replace with
|
||||
app.on('child-process-gone', (event, details) => { /* ... */ })
|
||||
app.on('child-process-gone', (event, details) => {
|
||||
/* ... */
|
||||
})
|
||||
```
|
||||
|
||||
## Breaking API Changes (28.0)
|
||||
@@ -1452,10 +1465,14 @@ Use the new `render-process-gone` event instead.
|
||||
|
||||
```js
|
||||
// Deprecated
|
||||
app.on('renderer-process-crashed', (event, webContents, killed) => { /* ... */ })
|
||||
app.on('renderer-process-crashed', (event, webContents, killed) => {
|
||||
/* ... */
|
||||
})
|
||||
|
||||
// Replace with
|
||||
app.on('render-process-gone', (event, webContents, details) => { /* ... */ })
|
||||
app.on('render-process-gone', (event, webContents, details) => {
|
||||
/* ... */
|
||||
})
|
||||
```
|
||||
|
||||
### Deprecated: `params.inputFormType` property on `context-menu` on `WebContents`
|
||||
@@ -1471,12 +1488,20 @@ Use the new `render-process-gone` event instead.
|
||||
|
||||
```js
|
||||
// Deprecated
|
||||
win.webContents.on('crashed', (event, killed) => { /* ... */ })
|
||||
webview.addEventListener('crashed', (event) => { /* ... */ })
|
||||
win.webContents.on('crashed', (event, killed) => {
|
||||
/* ... */
|
||||
})
|
||||
webview.addEventListener('crashed', (event) => {
|
||||
/* ... */
|
||||
})
|
||||
|
||||
// Replace with
|
||||
win.webContents.on('render-process-gone', (event, details) => { /* ... */ })
|
||||
webview.addEventListener('render-process-gone', (event) => { /* ... */ })
|
||||
win.webContents.on('render-process-gone', (event, details) => {
|
||||
/* ... */
|
||||
})
|
||||
webview.addEventListener('render-process-gone', (event) => {
|
||||
/* ... */
|
||||
})
|
||||
```
|
||||
|
||||
### Deprecated: `gpu-process-crashed` event on `app`
|
||||
@@ -1486,10 +1511,14 @@ Use the new `child-process-gone` event instead.
|
||||
|
||||
```js
|
||||
// Deprecated
|
||||
app.on('gpu-process-crashed', (event, killed) => { /* ... */ })
|
||||
app.on('gpu-process-crashed', (event, killed) => {
|
||||
/* ... */
|
||||
})
|
||||
|
||||
// Replace with
|
||||
app.on('child-process-gone', (event, details) => { /* ... */ })
|
||||
app.on('child-process-gone', (event, details) => {
|
||||
/* ... */
|
||||
})
|
||||
```
|
||||
|
||||
## Breaking API Changes (27.0)
|
||||
@@ -1518,11 +1547,17 @@ Use the new `updated` event on the `nativeTheme` module instead.
|
||||
|
||||
```js
|
||||
// Removed
|
||||
systemPreferences.on('inverted-color-scheme-changed', () => { /* ... */ })
|
||||
systemPreferences.on('high-contrast-color-scheme-changed', () => { /* ... */ })
|
||||
systemPreferences.on('inverted-color-scheme-changed', () => {
|
||||
/* ... */
|
||||
})
|
||||
systemPreferences.on('high-contrast-color-scheme-changed', () => {
|
||||
/* ... */
|
||||
})
|
||||
|
||||
// Replace with
|
||||
nativeTheme.on('updated', () => { /* ... */ })
|
||||
nativeTheme.on('updated', () => {
|
||||
/* ... */
|
||||
})
|
||||
```
|
||||
|
||||
### Removed: Some `window.setVibrancy` options on macOS
|
||||
@@ -1743,13 +1778,13 @@ const imagePath = path.join('path', 'to', 'capybara.png')
|
||||
|
||||
// Scaling up a smaller image.
|
||||
const upSize = { width: 256, height: 256 }
|
||||
nativeImage.createThumbnailFromPath(imagePath, upSize).then(result => {
|
||||
nativeImage.createThumbnailFromPath(imagePath, upSize).then((result) => {
|
||||
console.log(result.getSize()) // { width: 256, height: 256 }
|
||||
})
|
||||
|
||||
// Scaling down a larger image.
|
||||
const downSize = { width: 64, height: 64 }
|
||||
nativeImage.createThumbnailFromPath(imagePath, downSize).then(result => {
|
||||
nativeImage.createThumbnailFromPath(imagePath, downSize).then((result) => {
|
||||
console.log(result.getSize()) // { width: 64, height: 64 }
|
||||
})
|
||||
```
|
||||
@@ -1760,7 +1795,7 @@ Previous Behavior (on Windows):
|
||||
// a 128x128 image
|
||||
const imagePath = path.join('path', 'to', 'capybara.png')
|
||||
const size = { width: 256, height: 256 }
|
||||
nativeImage.createThumbnailFromPath(imagePath, size).then(result => {
|
||||
nativeImage.createThumbnailFromPath(imagePath, size).then((result) => {
|
||||
console.log(result.getSize()) // { width: 128, height: 128 }
|
||||
})
|
||||
```
|
||||
@@ -1815,13 +1850,13 @@ const w = new BrowserWindow({ show: false })
|
||||
|
||||
// Removed in Electron 23
|
||||
w.webContents.incrementCapturerCount()
|
||||
w.capturePage().then(image => {
|
||||
w.capturePage().then((image) => {
|
||||
console.log(image.toDataURL())
|
||||
w.webContents.decrementCapturerCount()
|
||||
})
|
||||
|
||||
// Replace with
|
||||
w.capturePage().then(image => {
|
||||
w.capturePage().then((image) => {
|
||||
console.log(image.toDataURL())
|
||||
})
|
||||
```
|
||||
@@ -1836,13 +1871,13 @@ const w = new BrowserWindow({ show: false })
|
||||
|
||||
// Removed in Electron 23
|
||||
w.webContents.incrementCapturerCount()
|
||||
w.capturePage().then(image => {
|
||||
w.capturePage().then((image) => {
|
||||
console.log(image.toDataURL())
|
||||
w.webContents.decrementCapturerCount()
|
||||
})
|
||||
|
||||
// Replace with
|
||||
w.capturePage().then(image => {
|
||||
w.capturePage().then((image) => {
|
||||
console.log(image.toDataURL())
|
||||
})
|
||||
```
|
||||
@@ -1859,13 +1894,13 @@ const w = new BrowserWindow({ show: false })
|
||||
|
||||
// Removed in Electron 23
|
||||
w.webContents.incrementCapturerCount()
|
||||
w.capturePage().then(image => {
|
||||
w.capturePage().then((image) => {
|
||||
console.log(image.toDataURL())
|
||||
w.webContents.decrementCapturerCount()
|
||||
})
|
||||
|
||||
// Replace with
|
||||
w.capturePage().then(image => {
|
||||
w.capturePage().then((image) => {
|
||||
console.log(image.toDataURL())
|
||||
})
|
||||
```
|
||||
@@ -1880,13 +1915,13 @@ const w = new BrowserWindow({ show: false })
|
||||
|
||||
// Removed in Electron 23
|
||||
w.webContents.incrementCapturerCount()
|
||||
w.capturePage().then(image => {
|
||||
w.capturePage().then((image) => {
|
||||
console.log(image.toDataURL())
|
||||
w.webContents.decrementCapturerCount()
|
||||
})
|
||||
|
||||
// Replace with
|
||||
w.capturePage().then(image => {
|
||||
w.capturePage().then((image) => {
|
||||
console.log(image.toDataURL())
|
||||
})
|
||||
```
|
||||
@@ -2002,30 +2037,33 @@ address changes upstream that made our previous implementation untenable and rif
|
||||
// Main process
|
||||
const { webContents } = require('electron')
|
||||
|
||||
webContents.printToPDF({
|
||||
landscape: true,
|
||||
displayHeaderFooter: true,
|
||||
printBackground: true,
|
||||
scale: 2,
|
||||
pageSize: 'Ledger',
|
||||
margins: {
|
||||
top: 2,
|
||||
bottom: 2,
|
||||
left: 2,
|
||||
right: 2
|
||||
},
|
||||
pageRanges: '1-5, 8, 11-13',
|
||||
headerTemplate: '<h1>Title</h1>',
|
||||
footerTemplate: '<div><span class="pageNumber"></span></div>',
|
||||
preferCSSPageSize: true
|
||||
}).then(data => {
|
||||
fs.writeFile(pdfPath, data, (error) => {
|
||||
if (error) throw error
|
||||
console.log(`Wrote PDF successfully to ${pdfPath}`)
|
||||
webContents
|
||||
.printToPDF({
|
||||
landscape: true,
|
||||
displayHeaderFooter: true,
|
||||
printBackground: true,
|
||||
scale: 2,
|
||||
pageSize: 'Ledger',
|
||||
margins: {
|
||||
top: 2,
|
||||
bottom: 2,
|
||||
left: 2,
|
||||
right: 2
|
||||
},
|
||||
pageRanges: '1-5, 8, 11-13',
|
||||
headerTemplate: '<h1>Title</h1>',
|
||||
footerTemplate: '<div><span class="pageNumber"></span></div>',
|
||||
preferCSSPageSize: true
|
||||
})
|
||||
.then((data) => {
|
||||
fs.writeFile(pdfPath, data, (error) => {
|
||||
if (error) throw error
|
||||
console.log(`Wrote PDF successfully to ${pdfPath}`)
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(`Failed to write PDF to ${pdfPath}: `, error)
|
||||
})
|
||||
}).catch(error => {
|
||||
console.log(`Failed to write PDF to ${pdfPath}: `, error)
|
||||
})
|
||||
```
|
||||
|
||||
## Breaking API Changes (20.0)
|
||||
@@ -2097,10 +2135,7 @@ If you need this functionality, it can be replaced as follows:
|
||||
// Main process
|
||||
const { ipcMain, desktopCapturer } = require('electron')
|
||||
|
||||
ipcMain.handle(
|
||||
'DESKTOP_CAPTURER_GET_SOURCES',
|
||||
(event, opts) => desktopCapturer.getSources(opts)
|
||||
)
|
||||
ipcMain.handle('DESKTOP_CAPTURER_GET_SOURCES', (event, opts) => desktopCapturer.getSources(opts))
|
||||
```
|
||||
|
||||
```js
|
||||
@@ -2582,7 +2617,9 @@ The APIs are now synchronous and the optional callback is no longer needed.
|
||||
|
||||
```js
|
||||
// Deprecated
|
||||
protocol.unregisterProtocol(scheme, () => { /* ... */ })
|
||||
protocol.unregisterProtocol(scheme, () => {
|
||||
/* ... */
|
||||
})
|
||||
// Replace with
|
||||
protocol.unregisterProtocol(scheme)
|
||||
```
|
||||
@@ -2611,7 +2648,9 @@ The APIs are now synchronous and the optional callback is no longer needed.
|
||||
|
||||
```js
|
||||
// Deprecated
|
||||
protocol.registerFileProtocol(scheme, handler, () => { /* ... */ })
|
||||
protocol.registerFileProtocol(scheme, handler, () => {
|
||||
/* ... */
|
||||
})
|
||||
// Replace with
|
||||
protocol.registerFileProtocol(scheme, handler)
|
||||
```
|
||||
@@ -2626,7 +2665,9 @@ and `protocol.isProtocolIntercepted` instead.
|
||||
|
||||
```js
|
||||
// Deprecated
|
||||
protocol.isProtocolHandled(scheme).then(() => { /* ... */ })
|
||||
protocol.isProtocolHandled(scheme).then(() => {
|
||||
/* ... */
|
||||
})
|
||||
// Replace with
|
||||
const isRegistered = protocol.isProtocolRegistered(scheme)
|
||||
const isIntercepted = protocol.isProtocolIntercepted(scheme)
|
||||
@@ -2843,11 +2884,17 @@ Use the new `updated` event on the `nativeTheme` module instead.
|
||||
|
||||
```js
|
||||
// Deprecated
|
||||
systemPreferences.on('inverted-color-scheme-changed', () => { /* ... */ })
|
||||
systemPreferences.on('high-contrast-color-scheme-changed', () => { /* ... */ })
|
||||
systemPreferences.on('inverted-color-scheme-changed', () => {
|
||||
/* ... */
|
||||
})
|
||||
systemPreferences.on('high-contrast-color-scheme-changed', () => {
|
||||
/* ... */
|
||||
})
|
||||
|
||||
// Replace with
|
||||
nativeTheme.on('updated', () => { /* ... */ })
|
||||
nativeTheme.on('updated', () => {
|
||||
/* ... */
|
||||
})
|
||||
```
|
||||
|
||||
### Deprecated: methods in `systemPreferences`
|
||||
@@ -2930,13 +2977,11 @@ webFrame.setIsolatedWorldContentSecurityPolicy(worldId, csp)
|
||||
webFrame.setIsolatedWorldHumanReadableName(worldId, name)
|
||||
webFrame.setIsolatedWorldSecurityOrigin(worldId, securityOrigin)
|
||||
// Replace with
|
||||
webFrame.setIsolatedWorldInfo(
|
||||
worldId,
|
||||
{
|
||||
securityOrigin: 'some_origin',
|
||||
name: 'human_readable_name',
|
||||
csp: 'content_security_policy'
|
||||
})
|
||||
webFrame.setIsolatedWorldInfo(worldId, {
|
||||
securityOrigin: 'some_origin',
|
||||
name: 'human_readable_name',
|
||||
csp: 'content_security_policy'
|
||||
})
|
||||
```
|
||||
|
||||
### Removed: `marked` property on `getBlinkMemoryInfo`
|
||||
@@ -3150,13 +3195,11 @@ webFrame.setIsolatedWorldContentSecurityPolicy(worldId, csp)
|
||||
webFrame.setIsolatedWorldHumanReadableName(worldId, name)
|
||||
webFrame.setIsolatedWorldSecurityOrigin(worldId, securityOrigin)
|
||||
// Replace with
|
||||
webFrame.setIsolatedWorldInfo(
|
||||
worldId,
|
||||
{
|
||||
securityOrigin: 'some_origin',
|
||||
name: 'human_readable_name',
|
||||
csp: 'content_security_policy'
|
||||
})
|
||||
webFrame.setIsolatedWorldInfo(worldId, {
|
||||
securityOrigin: 'some_origin',
|
||||
name: 'human_readable_name',
|
||||
csp: 'content_security_policy'
|
||||
})
|
||||
```
|
||||
|
||||
### API Changed: `webFrame.setSpellCheckProvider` now takes an asynchronous callback
|
||||
@@ -3173,7 +3216,7 @@ webFrame.setSpellCheckProvider('en-US', true, {
|
||||
// Replace with
|
||||
webFrame.setSpellCheckProvider('en-US', {
|
||||
spellCheck: (words, callback) => {
|
||||
callback(words.filter(text => spellchecker.isMisspelled(text)))
|
||||
callback(words.filter((text) => spellchecker.isMisspelled(text)))
|
||||
}
|
||||
})
|
||||
```
|
||||
@@ -3425,8 +3468,12 @@ webview.setAttribute('guestinstance', instanceId)
|
||||
// There is no replacement for this API
|
||||
|
||||
// Keyboard listeners no longer work on webview tag
|
||||
webview.onkeydown = () => { /* handler */ }
|
||||
webview.onkeyup = () => { /* handler */ }
|
||||
webview.onkeydown = () => {
|
||||
/* handler */
|
||||
}
|
||||
webview.onkeyup = () => {
|
||||
/* handler */
|
||||
}
|
||||
```
|
||||
|
||||
### Node Headers URL
|
||||
|
||||
@@ -129,9 +129,9 @@ void Initialize(v8::Local<v8::Object> exports,
|
||||
|
||||
In the [`typings/internal-ambient.d.ts`](../../typings/internal-ambient.d.ts) file, we need to append a new property onto the `Process` interface like so:
|
||||
|
||||
```ts title='typings/internal-ambient.d.ts' @ts-nocheck
|
||||
```ts title='typings/internal-ambient.d.ts' @ts-nocheck @noformat
|
||||
interface Process {
|
||||
_linkedBinding(name: 'electron_browser_{api_name}'): Electron.ApiName;
|
||||
_linkedBinding(name: 'electron_browser_{api_name}'): Electron.ApiName;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -165,7 +165,7 @@ An example of the contents of this file can be found [here](../../lib/browser/ap
|
||||
|
||||
Add your module to the module list found at `"lib/browser/api/module-list.ts"` like so:
|
||||
|
||||
```ts title='lib/browser/api/module-list.ts' @ts-nocheck
|
||||
```ts title='lib/browser/api/module-list.ts' @ts-nocheck @noformat
|
||||
export const browserModuleList: ElectronInternal.ModuleEntry[] = [
|
||||
{ name: 'apiName', loader: () => require('./api-name') }
|
||||
];
|
||||
|
||||
@@ -94,7 +94,7 @@ const display1 = virtualDisplay.create({ x: 0, y: 0, width: 1920, height: 1080 }
|
||||
const display2 = virtualDisplay.create({ x: 500, y: 0, width: 1920, height: 1080 })
|
||||
|
||||
// macOS automatically repositions display2 to x: 1920 to prevent overlap
|
||||
const actualBounds = screen.getAllDisplays().map(d => d.bounds)
|
||||
const actualBounds = screen.getAllDisplays().map((d) => d.bounds)
|
||||
// Result: [{ x: 0, y: 0, width: 1920, height: 1080 }, { x: 1920, y: 0, width: 1920, height: 1080 }]
|
||||
```
|
||||
|
||||
|
||||
@@ -55,7 +55,9 @@ There are a few style guidelines that aren't covered by the linter rules:
|
||||
purposes.
|
||||
* No nesting lists more than 2 levels (due to the markdown renderer).
|
||||
* All `js` and `javascript` code blocks are linted with
|
||||
[oxlint](https://oxc.rs/docs/guide/usage/linter) as part of `npm run lint:docs`.
|
||||
[oxlint](https://oxc.rs/docs/guide/usage/linter) and formatted with
|
||||
[oxfmt](https://oxc.rs/docs/guide/usage/formatter) as part of `npm run lint:docs`;
|
||||
`npm run lint:fmt-in-markdown -- --fix` reformats them.
|
||||
* For unordered lists, use asterisks instead of dashes.
|
||||
|
||||
## Picking words
|
||||
|
||||
@@ -22,7 +22,7 @@ When building an application menu in Electron, each top-level array menu item **
|
||||
Electron will set a default menu for your app if this API is never called. Below is an example of
|
||||
that default menu being created manually using shorthand [`MenuItem` roles](./menus.md#roles).
|
||||
|
||||
```js title='Manually creating the default menu' @ts-expect-error=[107]
|
||||
```js title='Manually creating the default menu' @ts-expect-error=[94]
|
||||
const { shell } = require('electron/common')
|
||||
const { app, Menu } = require('electron/main')
|
||||
|
||||
@@ -30,27 +30,27 @@ const isMac = process.platform === 'darwin'
|
||||
const template = [
|
||||
// { role: 'appMenu' }
|
||||
...(isMac
|
||||
? [{
|
||||
label: app.name,
|
||||
submenu: [
|
||||
{ role: 'about' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'services' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'hide' },
|
||||
{ role: 'hideOthers' },
|
||||
{ role: 'unhide' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'quit' }
|
||||
]
|
||||
}]
|
||||
? [
|
||||
{
|
||||
label: app.name,
|
||||
submenu: [
|
||||
{ role: 'about' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'services' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'hide' },
|
||||
{ role: 'hideOthers' },
|
||||
{ role: 'unhide' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'quit' }
|
||||
]
|
||||
}
|
||||
]
|
||||
: []),
|
||||
// { role: 'fileMenu' }
|
||||
{
|
||||
label: 'File',
|
||||
submenu: [
|
||||
isMac ? { role: 'close' } : { role: 'quit' }
|
||||
]
|
||||
submenu: [isMac ? { role: 'close' } : { role: 'quit' }]
|
||||
},
|
||||
// { role: 'editMenu' }
|
||||
{
|
||||
@@ -70,17 +70,10 @@ const template = [
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Speech',
|
||||
submenu: [
|
||||
{ role: 'startSpeaking' },
|
||||
{ role: 'stopSpeaking' }
|
||||
]
|
||||
submenu: [{ role: 'startSpeaking' }, { role: 'stopSpeaking' }]
|
||||
}
|
||||
]
|
||||
: [
|
||||
{ role: 'delete' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'selectAll' }
|
||||
])
|
||||
: [{ role: 'delete' }, { type: 'separator' }, { role: 'selectAll' }])
|
||||
]
|
||||
},
|
||||
// { role: 'viewMenu' }
|
||||
@@ -105,15 +98,8 @@ const template = [
|
||||
{ role: 'minimize' },
|
||||
{ role: 'zoom' },
|
||||
...(isMac
|
||||
? [
|
||||
{ type: 'separator' },
|
||||
{ role: 'front' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'window' }
|
||||
]
|
||||
: [
|
||||
{ role: 'close' }
|
||||
])
|
||||
? [{ type: 'separator' }, { role: 'front' }, { type: 'separator' }, { role: 'window' }]
|
||||
: [{ role: 'close' }])
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -148,14 +134,12 @@ Menu.setApplicationMenu(menu)
|
||||
Defining each submenu explicitly can get very verbose. If you want to re-use default submenus
|
||||
in your app, you can use various submenu-related roles provided by Electron.
|
||||
|
||||
```js title='Using default roles for each submenu' @ts-expect-error=[26]
|
||||
```js title='Using default roles for each submenu' @ts-expect-error=[24]
|
||||
const { shell } = require('electron/common')
|
||||
const { app, Menu } = require('electron/main')
|
||||
|
||||
const template = [
|
||||
...(process.platform === 'darwin'
|
||||
? [{ role: 'appMenu' }]
|
||||
: []),
|
||||
...(process.platform === 'darwin' ? [{ role: 'appMenu' }] : []),
|
||||
{ role: 'fileMenu' },
|
||||
{ role: 'editMenu' },
|
||||
{ role: 'viewMenu' },
|
||||
@@ -194,10 +178,7 @@ const win = new BrowserWindow()
|
||||
const menu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'my custom menu',
|
||||
submenu: [
|
||||
{ role: 'copy' },
|
||||
{ role: 'paste' }
|
||||
]
|
||||
submenu: [{ role: 'copy' }, { role: 'paste' }]
|
||||
}
|
||||
])
|
||||
win.setMenu(menu)
|
||||
|
||||
@@ -42,16 +42,18 @@ After running the configuration wizard, your `wdio.conf.js` should include rough
|
||||
export const config = {
|
||||
// ...
|
||||
services: ['electron'],
|
||||
capabilities: [{
|
||||
browserName: 'electron',
|
||||
'wdio:electronServiceOptions': {
|
||||
// WebdriverIO can automatically find your bundled application
|
||||
// if you use Electron Forge or electron-builder, otherwise you
|
||||
// can define it here, e.g.:
|
||||
// appBinaryPath: './path/to/bundled/application.exe',
|
||||
appArgs: ['foo', 'bar=baz']
|
||||
capabilities: [
|
||||
{
|
||||
browserName: 'electron',
|
||||
'wdio:electronServiceOptions': {
|
||||
// WebdriverIO can automatically find your bundled application
|
||||
// if you use Electron Forge or electron-builder, otherwise you
|
||||
// can define it here, e.g.:
|
||||
// appBinaryPath: './path/to/bundled/application.exe',
|
||||
appArgs: ['foo', 'bar=baz']
|
||||
}
|
||||
}
|
||||
}]
|
||||
]
|
||||
// ...
|
||||
}
|
||||
```
|
||||
@@ -300,7 +302,9 @@ const electronPath = require('electron')
|
||||
const childProcess = require('node:child_process')
|
||||
|
||||
// spawn the process
|
||||
const env = { /* ... */ }
|
||||
const env = {
|
||||
/* ... */
|
||||
}
|
||||
const stdio = ['inherit', 'inherit', 'inherit', 'ipc']
|
||||
const appProcess = childProcess.spawn(electronPath, ['./app'], { stdio, env })
|
||||
|
||||
@@ -334,7 +338,7 @@ a `TestDriver` class:
|
||||
|
||||
```js title='testDriver.js' @ts-nocheck
|
||||
class TestDriver {
|
||||
constructor ({ path, args, env }) {
|
||||
constructor({ path, args, env }) {
|
||||
this.rpcCalls = []
|
||||
|
||||
// start child process
|
||||
@@ -362,14 +366,14 @@ class TestDriver {
|
||||
|
||||
// simple RPC call
|
||||
// to use: driver.rpc('method', 1, 2, 3).then(...)
|
||||
async rpc (cmd, ...args) {
|
||||
async rpc(cmd, ...args) {
|
||||
// send rpc request
|
||||
const msgId = this.rpcCalls.length
|
||||
this.process.send({ msgId, cmd, args })
|
||||
return new Promise((resolve, reject) => this.rpcCalls.push({ resolve, reject }))
|
||||
}
|
||||
|
||||
stop () {
|
||||
stop() {
|
||||
this.process.kill()
|
||||
}
|
||||
}
|
||||
@@ -381,7 +385,7 @@ In your app code, you can then write a simple handler to receive RPC calls:
|
||||
|
||||
```js title='main.js'
|
||||
const METHODS = {
|
||||
isReady () {
|
||||
isReady() {
|
||||
// do any setup needed
|
||||
return true
|
||||
}
|
||||
@@ -428,10 +432,10 @@ const app = new TestDriver({
|
||||
NODE_ENV: 'test'
|
||||
}
|
||||
})
|
||||
test.before(async t => {
|
||||
test.before(async (t) => {
|
||||
await app.isReady
|
||||
})
|
||||
test.after.always('cleanup', async t => {
|
||||
test.after.always('cleanup', async (t) => {
|
||||
await app.stop()
|
||||
})
|
||||
```
|
||||
|
||||
@@ -86,7 +86,7 @@ You can create a `interface.d.ts` declaration file and globally augment the `Win
|
||||
|
||||
```ts title='interface.d.ts' @ts-noisolate
|
||||
export interface IElectronAPI {
|
||||
loadPreferences: () => Promise<void>,
|
||||
loadPreferences: () => Promise<void>
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -61,7 +61,7 @@ win.webContents.on('context-menu', (_event, params) => {
|
||||
// Whether the context is editable.
|
||||
if (params.isEditable) {
|
||||
menu.popup({
|
||||
// highlight-next-line
|
||||
// highlight-next-line
|
||||
frame: params.frame
|
||||
})
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ The `@babel/plugin-transform-modules-commonjs` plugin will transform
|
||||
ESM imports down to `require` calls. The exact syntax will depend on the
|
||||
[`importInterop` setting](https://babeljs.io/docs/babel-plugin-transform-modules-commonjs#importinterop).
|
||||
|
||||
```js @nolint @ts-nocheck title='@babel/plugin-transform-modules-commonjs'
|
||||
```js @nolint @noformat @ts-nocheck title='@babel/plugin-transform-modules-commonjs'
|
||||
import foo from "foo";
|
||||
import { bar } from "bar";
|
||||
foo;
|
||||
|
||||
@@ -76,7 +76,6 @@ inAppPurchase.on('transactions-updated', (event, transactions) => {
|
||||
}
|
||||
|
||||
case 'failed':
|
||||
|
||||
console.log(`Failed to purchase ${payment.productIdentifier}.`)
|
||||
|
||||
// Finish the transaction.
|
||||
@@ -84,12 +83,10 @@ inAppPurchase.on('transactions-updated', (event, transactions) => {
|
||||
|
||||
break
|
||||
case 'restored':
|
||||
|
||||
console.log(`The purchase of ${payment.productIdentifier} has been restored.`)
|
||||
|
||||
break
|
||||
case 'deferred':
|
||||
|
||||
console.log(`The purchase of ${payment.productIdentifier} has been deferred.`)
|
||||
|
||||
break
|
||||
@@ -105,7 +102,7 @@ if (!inAppPurchase.canMakePayments()) {
|
||||
}
|
||||
|
||||
// Retrieve and display the product descriptions.
|
||||
inAppPurchase.getProducts(PRODUCT_IDS).then(products => {
|
||||
inAppPurchase.getProducts(PRODUCT_IDS).then((products) => {
|
||||
// Check the parameters.
|
||||
if (!Array.isArray(products) || products.length <= 0) {
|
||||
console.log('Unable to retrieve the product information.')
|
||||
@@ -122,7 +119,7 @@ inAppPurchase.getProducts(PRODUCT_IDS).then(products => {
|
||||
const selectedQuantity = 1
|
||||
|
||||
// Purchase the selected product.
|
||||
inAppPurchase.purchaseProduct(selectedProduct.productIdentifier, selectedQuantity).then(isProductValid => {
|
||||
inAppPurchase.purchaseProduct(selectedProduct.productIdentifier, selectedQuantity).then((isProductValid) => {
|
||||
if (!isProductValid) {
|
||||
console.log('The product is not valid.')
|
||||
return
|
||||
|
||||
@@ -57,13 +57,13 @@ const path = require('node:path')
|
||||
|
||||
// ...
|
||||
|
||||
function handleSetTitle (event, title) {
|
||||
function handleSetTitle(event, title) {
|
||||
const webContents = event.sender
|
||||
const win = BrowserWindow.fromWebContents(webContents)
|
||||
win.setTitle(title)
|
||||
}
|
||||
|
||||
function createWindow () {
|
||||
function createWindow() {
|
||||
const mainWindow = new BrowserWindow({
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
@@ -189,14 +189,14 @@ const path = require('node:path')
|
||||
|
||||
// ...
|
||||
|
||||
async function handleFileOpen () {
|
||||
async function handleFileOpen() {
|
||||
const { canceled, filePaths } = await dialog.showOpenDialog({})
|
||||
if (!canceled) {
|
||||
return filePaths[0]
|
||||
}
|
||||
}
|
||||
|
||||
function createWindow () {
|
||||
function createWindow() {
|
||||
const mainWindow = new BrowserWindow({
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
@@ -384,7 +384,7 @@ const { app, BrowserWindow, Menu, ipcMain } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
|
||||
function createWindow () {
|
||||
function createWindow() {
|
||||
const mainWindow = new BrowserWindow({
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
|
||||
@@ -108,11 +108,13 @@ if (process.platform === 'darwin') {
|
||||
}
|
||||
|
||||
// highlight-start
|
||||
const submenu = Menu.buildFromTemplate([{
|
||||
label: 'Open a Dialog',
|
||||
click: () => dialog.showMessageBox({ message: 'Hello World!' }),
|
||||
accelerator: 'CommandOrControl+Alt+R'
|
||||
}])
|
||||
const submenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'Open a Dialog',
|
||||
click: () => dialog.showMessageBox({ message: 'Hello World!' }),
|
||||
accelerator: 'CommandOrControl+Alt+R'
|
||||
}
|
||||
])
|
||||
menu.append(new MenuItem({ label: 'Custom Menu', submenu }))
|
||||
// highlight-end
|
||||
|
||||
|
||||
@@ -193,9 +193,9 @@ packager({
|
||||
schemes: ['electron-fiddle']
|
||||
}
|
||||
]
|
||||
|
||||
}).then(paths => console.log(`SUCCESS: Created ${paths.join(', ')}`))
|
||||
.catch(err => console.error(`ERROR: ${err.message}`))
|
||||
})
|
||||
.then((paths) => console.log(`SUCCESS: Created ${paths.join(', ')}`))
|
||||
.catch((err) => console.error(`ERROR: ${err.message}`))
|
||||
```
|
||||
|
||||
If you're using Electron Packager's CLI, use the `--protocol` and `--protocol-name` flags. For
|
||||
|
||||
@@ -61,7 +61,7 @@ localAIHandler.setPromptAPIHandler((details) => {
|
||||
// details.securityOrigin — origin of the calling page
|
||||
|
||||
return class MyLanguageModel extends LanguageModelUtility {
|
||||
static async create (options) {
|
||||
static async create(options) {
|
||||
// options.signal - AbortSignal to cancel the creation of the model
|
||||
// options.initialPrompts - initial prompts to pass to the language model
|
||||
|
||||
@@ -71,26 +71,26 @@ localAIHandler.setPromptAPIHandler((details) => {
|
||||
})
|
||||
}
|
||||
|
||||
static async availability () {
|
||||
static async availability() {
|
||||
// Return 'available', 'downloadable', 'downloading', or 'unavailable'
|
||||
return 'available'
|
||||
}
|
||||
|
||||
async prompt (input) {
|
||||
async prompt(input) {
|
||||
// input is a LanguageModelMessage[]
|
||||
// Return a string response from your model, or a ReadableStream
|
||||
// to return a streaming response.
|
||||
return 'This is a response from your local LLM!'
|
||||
}
|
||||
|
||||
async clone () {
|
||||
async clone() {
|
||||
return new MyLanguageModel({
|
||||
contextUsage: this.contextUsage,
|
||||
contextWindow: this.contextWindow
|
||||
})
|
||||
}
|
||||
|
||||
destroy () {
|
||||
destroy() {
|
||||
// Clean up model resources
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ With `@electron/osx-sign`, you can set custom entitlements per file as such:
|
||||
```js @ts-nocheck
|
||||
const { signAsync } = require('@electron/osx-sign')
|
||||
|
||||
function getEntitlementsForFile (filePath) {
|
||||
function getEntitlementsForFile(filePath) {
|
||||
if (filePath.startsWith('my-path-1')) {
|
||||
return './my-path-1.plist'
|
||||
} else {
|
||||
|
||||
@@ -49,7 +49,9 @@ app.whenReady().then(() => {
|
||||
const dockMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'New Window',
|
||||
click: () => { const win = new BrowserWindow() }
|
||||
click: () => {
|
||||
const win = new BrowserWindow()
|
||||
}
|
||||
}
|
||||
// add more menu options to the array
|
||||
])
|
||||
|
||||
@@ -306,8 +306,11 @@ To add visual aid to your menus, you can use the `icon` property to assign image
|
||||
const { nativeImage } = require('electron/common')
|
||||
const { MenuItem } = require('electron/main')
|
||||
|
||||
// highlight-next-line
|
||||
const green = nativeImage.createFromDataURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACOSURBVHgBpZLRDYAgEEOrEzgCozCCGzkCbKArOIlugJvgoRAUNcLRpvGH19TkgFQWkqIohhK8UEaKwKcsOg/+WR1vX+AlA74u6q4FqgCOSzwsGHCwbKliAF89Cv89tWmOT4VaVMoVbOBrdQUz+FrD6XItzh4LzYB1HFJ9yrEkZ4l+wvcid9pTssh4UKbPd+4vED2Nd54iAAAAAElFTkSuQmCC')
|
||||
// highlight-start
|
||||
const green = nativeImage.createFromDataURL(
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACOSURBVHgBpZLRDYAgEEOrEzgCozCCGzkCbKArOIlugJvgoRAUNcLRpvGH19TkgFQWkqIohhK8UEaKwKcsOg/+WR1vX+AlA74u6q4FqgCOSzwsGHCwbKliAF89Cv89tWmOT4VaVMoVbOBrdQUz+FrD6XItzh4LzYB1HFJ9yrEkZ4l+wvcid9pTssh4UKbPd+4vED2Nd54iAAAAAElFTkSuQmCC'
|
||||
)
|
||||
// highlight-end
|
||||
|
||||
const item = new MenuItem({
|
||||
label: 'Green Circle',
|
||||
@@ -328,7 +331,9 @@ const item = new MenuItem({
|
||||
label: 'Log Message',
|
||||
// highlight-next-line
|
||||
sublabel: 'This will use the console.log utility',
|
||||
click: () => { console.log('Logging via menu...') }
|
||||
click: () => {
|
||||
console.log('Logging via menu...')
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -129,11 +129,11 @@ listeners.
|
||||
```js title='preloadMain.js and preloadSecondary.js (Preload scripts)' @ts-window-type={electronMessagePort:MessagePort}
|
||||
const { ipcRenderer } = require('electron')
|
||||
|
||||
ipcRenderer.on('port', e => {
|
||||
ipcRenderer.on('port', (e) => {
|
||||
// port received, make it globally available.
|
||||
window.electronMessagePort = e.ports[0]
|
||||
|
||||
window.electronMessagePort.onmessage = messageEvent => {
|
||||
window.electronMessagePort.onmessage = (messageEvent) => {
|
||||
// handle message
|
||||
}
|
||||
})
|
||||
@@ -245,18 +245,14 @@ Electron's built-in IPC methods only support two modes: fire-and-forget
|
||||
can implement a "response stream", where a single request responds with a
|
||||
stream of data.
|
||||
|
||||
```js title='renderer.js (Renderer Process)' @ts-expect-error=[18]
|
||||
```js title='renderer.js (Renderer Process)' @ts-expect-error=[14]
|
||||
const makeStreamingRequest = (element, callback) => {
|
||||
// MessageChannels are lightweight--it's cheap to create a new one for each
|
||||
// request.
|
||||
const { port1, port2 } = new MessageChannel()
|
||||
|
||||
// We send one end of the port to the main process ...
|
||||
ipcRenderer.postMessage(
|
||||
'give-me-a-stream',
|
||||
{ element, count: 10 },
|
||||
[port2]
|
||||
)
|
||||
ipcRenderer.postMessage('give-me-a-stream', { element, count: 10 }, [port2])
|
||||
|
||||
// ... and we hang on to the other end. The main process will send messages
|
||||
// to its end of the port, and close it when it's finished.
|
||||
@@ -343,7 +339,7 @@ const { ipcRenderer } = require('electron')
|
||||
// We need to wait until the main world is ready to receive the message before
|
||||
// sending the port. We create this promise in the preload so it's guaranteed
|
||||
// to register the onload listener before the load event is fired.
|
||||
const windowLoaded = new Promise(resolve => {
|
||||
const windowLoaded = new Promise((resolve) => {
|
||||
window.onload = resolve
|
||||
})
|
||||
|
||||
|
||||
@@ -1105,7 +1105,7 @@ You're so close! We now have working Objective-C and thread-safe ways to expose
|
||||
const EventEmitter = require('node:events')
|
||||
|
||||
class ObjcMacosAddon extends EventEmitter {
|
||||
constructor () {
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
@@ -1120,19 +1120,19 @@ class ObjcMacosAddon extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
helloWorld (input = '') {
|
||||
helloWorld(input = '') {
|
||||
return this.addon.helloWorld(input)
|
||||
}
|
||||
|
||||
helloGui () {
|
||||
helloGui() {
|
||||
this.addon.helloGui()
|
||||
}
|
||||
|
||||
destroy () {
|
||||
destroy() {
|
||||
this.addon.destroy()
|
||||
}
|
||||
|
||||
parse (payload) {
|
||||
parse(payload) {
|
||||
const parsed = JSON.parse(payload)
|
||||
|
||||
return { ...parsed, date: new Date(parsed.date) }
|
||||
|
||||
@@ -1118,7 +1118,7 @@ You're so close! We now have working Objective-C, Swift, and thread-safe ways to
|
||||
const EventEmitter = require('node:events')
|
||||
|
||||
class SwiftAddon extends EventEmitter {
|
||||
constructor () {
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
@@ -1141,19 +1141,19 @@ class SwiftAddon extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
helloWorld (input = '') {
|
||||
helloWorld(input = '') {
|
||||
return this.addon.helloWorld(input)
|
||||
}
|
||||
|
||||
helloGui () {
|
||||
helloGui() {
|
||||
this.addon.helloGui()
|
||||
}
|
||||
|
||||
destroy () {
|
||||
destroy() {
|
||||
this.addon.destroy()
|
||||
}
|
||||
|
||||
parse (payload) {
|
||||
parse(payload) {
|
||||
const parsed = JSON.parse(payload)
|
||||
|
||||
return { ...parsed, date: new Date(parsed.date) }
|
||||
|
||||
@@ -279,7 +279,7 @@ const native = bindings('my_addon')
|
||||
|
||||
// Create a nice JavaScript wrapper
|
||||
class MyNativeAddon extends EventEmitter {
|
||||
constructor () {
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
// Create an instance of our C++ class
|
||||
@@ -287,7 +287,7 @@ class MyNativeAddon extends EventEmitter {
|
||||
}
|
||||
|
||||
// Wrap the C++ method with a nicer JavaScript API
|
||||
helloWorld (input = '') {
|
||||
helloWorld(input = '') {
|
||||
if (typeof input !== 'string') {
|
||||
throw new TypeError('Input must be a string')
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ A common flow is that you want to restore the history of a webContents - for ins
|
||||
const firstWindow = new BrowserWindow()
|
||||
|
||||
// Later, you want a second window to have the same history and navigation position
|
||||
async function restore () {
|
||||
async function restore() {
|
||||
const entries = firstWindow.webContents.navigationHistory.getAllEntries()
|
||||
const index = firstWindow.webContents.navigationHistory.getActiveIndex()
|
||||
|
||||
|
||||
@@ -71,12 +71,10 @@ Notifications can be displayed directly from the renderer process with the
|
||||
|
||||
```js title='Renderer Process'
|
||||
const NOTIFICATION_TITLE = 'Title'
|
||||
const NOTIFICATION_BODY =
|
||||
'Notification from the Renderer process. Click to log to console.'
|
||||
const NOTIFICATION_BODY = 'Notification from the Renderer process. Click to log to console.'
|
||||
const CLICK_MESSAGE = 'Notification clicked'
|
||||
|
||||
new Notification(NOTIFICATION_TITLE, { body: NOTIFICATION_BODY }).onclick =
|
||||
() => console.log(CLICK_MESSAGE)
|
||||
new Notification(NOTIFICATION_TITLE, { body: NOTIFICATION_BODY }).onclick = () => console.log(CLICK_MESSAGE)
|
||||
```
|
||||
|
||||
Here's a full example that you can open with Electron Fiddle:
|
||||
|
||||
@@ -179,11 +179,11 @@ const fs = require('node:fs')
|
||||
const fooParser = require('foo-parser')
|
||||
|
||||
class Parser {
|
||||
constructor () {
|
||||
constructor() {
|
||||
this.files = fs.readdirSync('.')
|
||||
}
|
||||
|
||||
getParsedFiles () {
|
||||
getParsedFiles() {
|
||||
return fooParser.parse(this.files)
|
||||
}
|
||||
}
|
||||
@@ -202,16 +202,16 @@ do this work a little later, when `getParsedFiles()` is actually called?
|
||||
const fs = require('node:fs')
|
||||
|
||||
class Parser {
|
||||
async getFiles () {
|
||||
async getFiles() {
|
||||
// Touch the disk as soon as `getFiles` is called, not sooner.
|
||||
// Also, ensure that we're not blocking other operations by using
|
||||
// the asynchronous version.
|
||||
this.files = this.files || await fs.promises.readdir('.')
|
||||
this.files = this.files || (await fs.promises.readdir('.'))
|
||||
|
||||
return this.files
|
||||
}
|
||||
|
||||
async getParsedFiles () {
|
||||
async getParsedFiles() {
|
||||
// Our fictitious foo-parser is a big and expensive module to load, so
|
||||
// defer that work until we actually need to parse files.
|
||||
// Since `require()` comes with a module cache, the `require()` call
|
||||
|
||||
+14
-16
@@ -291,22 +291,20 @@ const { session } = require('electron')
|
||||
|
||||
const { URL } = require('node:url')
|
||||
|
||||
session
|
||||
.defaultSession
|
||||
.setPermissionRequestHandler((webContents, permission, callback) => {
|
||||
const parsedUrl = new URL(webContents.getURL())
|
||||
session.defaultSession.setPermissionRequestHandler((webContents, permission, callback) => {
|
||||
const parsedUrl = new URL(webContents.getURL())
|
||||
|
||||
if (permission === 'notifications') {
|
||||
// Approves the permissions request
|
||||
callback(true)
|
||||
}
|
||||
if (permission === 'notifications') {
|
||||
// Approves the permissions request
|
||||
callback(true)
|
||||
}
|
||||
|
||||
// Verify URL
|
||||
if (parsedUrl.protocol !== 'https:' || parsedUrl.host !== 'example.com') {
|
||||
// Denies the permissions request
|
||||
return callback(false)
|
||||
}
|
||||
})
|
||||
// Verify URL
|
||||
if (parsedUrl.protocol !== 'https:' || parsedUrl.host !== 'example.com') {
|
||||
// Denies the permissions request
|
||||
return callback(false)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Note: `session.defaultSession` is only available after `app.whenReady` is called.
|
||||
@@ -395,7 +393,7 @@ session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
||||
callback({
|
||||
responseHeaders: {
|
||||
...details.responseHeaders,
|
||||
'Content-Security-Policy': ['default-src \'none\'']
|
||||
'Content-Security-Policy': ["default-src 'none'"]
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -770,7 +768,7 @@ ipcMain.handle('get-secrets', (e) => {
|
||||
return getSecrets()
|
||||
})
|
||||
|
||||
function validateSender (frame) {
|
||||
function validateSender(frame) {
|
||||
// Validate the frame's origin against an allowlist. Use the origin, not the
|
||||
// URL: about:blank, blob: and sandboxed documents have URLs that do not
|
||||
// identify who controls them, and the frame may be null if it has gone away.
|
||||
|
||||
@@ -75,8 +75,7 @@ programmatically. For more information, see the [Snapcraft API docs][snapcraft-s
|
||||
```js @ts-nocheck
|
||||
const snap = require('electron-installer-snap')
|
||||
|
||||
snap(options)
|
||||
.then(snapPath => console.log(`Created snap at ${snapPath}!`))
|
||||
snap(options).then((snapPath) => console.log(`Created snap at ${snapPath}!`))
|
||||
```
|
||||
|
||||
## Using `snapcraft` with `@electron/packager`
|
||||
|
||||
@@ -43,10 +43,12 @@ myWindow.webContents.on('context-menu', (event, params) => {
|
||||
|
||||
// Add each spelling suggestion
|
||||
for (const suggestion of params.dictionarySuggestions) {
|
||||
menu.append(new MenuItem({
|
||||
label: suggestion,
|
||||
click: () => myWindow.webContents.replaceMisspelling(suggestion)
|
||||
}))
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: suggestion,
|
||||
click: () => myWindow.webContents.replaceMisspelling(suggestion)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// Allow users to add the misspelled word to the dictionary
|
||||
|
||||
@@ -59,14 +59,14 @@ const { app, Tray, Menu } = require('electron/main')
|
||||
let tray
|
||||
|
||||
// 16x16 red circle data URL
|
||||
const icon = nativeImage.createFromDataURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACTSURBVHgBpZKBCYAgEEV/TeAIjuIIbdQIuUGt0CS1gW1iZ2jIVaTnhw+Cvs8/OYDJA4Y8kR3ZR2/kmazxJbpUEfQ/Dm/UG7wVwHkjlQdMFfDdJMFaACebnjJGyDWgcnZu1/lrCrl6NCoEHJBrDwEr5NrT6ko/UV8xdLAC2N49mlc5CylpYh8wCwqrvbBGLoKGvz8Bfq0QPWEUo/EAAAAASUVORK5CYII=')
|
||||
const icon = nativeImage.createFromDataURL(
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACTSURBVHgBpZKBCYAgEEV/TeAIjuIIbdQIuUGt0CS1gW1iZ2jIVaTnhw+Cvs8/OYDJA4Y8kR3ZR2/kmazxJbpUEfQ/Dm/UG7wVwHkjlQdMFfDdJMFaACebnjJGyDWgcnZu1/lrCrl6NCoEHJBrDwEr5NrT6ko/UV8xdLAC2N49mlc5CylpYh8wCwqrvbBGLoKGvz8Bfq0QPWEUo/EAAAAASUVORK5CYII='
|
||||
)
|
||||
|
||||
// The Tray can only be instantiated after the 'ready' event is fired
|
||||
app.whenReady().then(() => {
|
||||
tray = new Tray(icon)
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{ role: 'quit' }
|
||||
])
|
||||
const contextMenu = Menu.buildFromTemplate([{ role: 'quit' }])
|
||||
tray.setContextMenu(contextMenu)
|
||||
})
|
||||
```
|
||||
|
||||
@@ -221,15 +221,14 @@ Now that you've configured the basic update mechanism for your application, you
|
||||
need to ensure that the user will get notified when there's an update. This
|
||||
can be achieved using the [autoUpdater API events](../api/auto-updater.md#events):
|
||||
|
||||
```js title="main.js" @ts-expect-error=[11]
|
||||
```js title="main.js" @ts-expect-error=[10]
|
||||
autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => {
|
||||
const dialogOpts = {
|
||||
type: 'info',
|
||||
buttons: ['Restart', 'Later'],
|
||||
title: 'Application Update',
|
||||
message: process.platform === 'win32' ? releaseNotes : releaseName,
|
||||
detail:
|
||||
'A new version has been downloaded. Restart the application to apply the updates.'
|
||||
detail: 'A new version has been downloaded. Restart the application to apply the updates.'
|
||||
}
|
||||
|
||||
dialog.showMessageBox(dialogOpts).then((returnValue) => {
|
||||
|
||||
@@ -15,7 +15,7 @@ To enable Window State Persistence, set `windowStatePersistence: true` in your w
|
||||
```js
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
function createWindow () {
|
||||
function createWindow() {
|
||||
const win = new BrowserWindow({
|
||||
name: 'main-window',
|
||||
width: 800,
|
||||
@@ -46,7 +46,7 @@ You can control which aspects of the window state are persisted by passing an ob
|
||||
```js
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
function createWindow () {
|
||||
function createWindow() {
|
||||
const win = new BrowserWindow({
|
||||
name: 'main-window',
|
||||
width: 800,
|
||||
|
||||
@@ -135,12 +135,17 @@ win.setThumbarButtons([
|
||||
{
|
||||
tooltip: 'button1',
|
||||
icon: nativeImage.createFromPath(path.join(__dirname, 'button1.png')),
|
||||
click () { console.log('button1 clicked') }
|
||||
}, {
|
||||
click() {
|
||||
console.log('button1 clicked')
|
||||
}
|
||||
},
|
||||
{
|
||||
tooltip: 'button2',
|
||||
icon: nativeImage.createFromPath(path.join(__dirname, 'button2.png')),
|
||||
flags: ['enabled', 'dismissonclick'],
|
||||
click () { console.log('button2 clicked.') }
|
||||
click() {
|
||||
console.log('button2 clicked.')
|
||||
}
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
+2
-1
@@ -50,12 +50,13 @@
|
||||
"lint:objc": "node ./script/lint.js --objc",
|
||||
"lint:py": "node ./script/lint.js --py",
|
||||
"lint:gn": "node ./script/lint.js --gn",
|
||||
"lint:docs": "npm run lint:js-in-markdown && npm run create-typescript-definitions && npm run lint:ts-check-js-in-markdown && npm run lint:docs-fiddles && npm run lint:docs-relative-links && npm run lint:markdown && npm run lint:api-history",
|
||||
"lint:docs": "npm run lint:js-in-markdown && npm run lint:fmt-in-markdown && npm run create-typescript-definitions && npm run lint:ts-check-js-in-markdown && npm run lint:docs-fiddles && npm run lint:docs-relative-links && npm run lint:markdown && npm run lint:api-history",
|
||||
"lint:docs-fiddles": "oxlint docs/fiddles",
|
||||
"lint:docs-relative-links": "lint-roller-markdown-links --resource-root . --root docs \"**/*.md\"",
|
||||
"lint:markdown": "node ./script/lint.js --md",
|
||||
"lint:ts-check-js-in-markdown": "lint-roller-markdown-ts-check --root docs \"**/*.md\" --ignore \"breaking-changes.md\"",
|
||||
"lint:js-in-markdown": "lint-roller-markdown-oxlint --root docs \"**/*.md\"",
|
||||
"lint:fmt-in-markdown": "lint-roller-markdown-oxfmt --root docs \"**/*.md\" --config docs/.oxfmtrc.json",
|
||||
"lint:api-history": "lint-roller-markdown-api-history --root \"./docs/api/\" --schema \"./docs/api-history.schema.json\" --breaking-changes-file \"./docs/breaking-changes.md\" --check-placement --check-strings \"*.md\"",
|
||||
"lint:chromium-roller": "node ./script/lint-roller-chromium-changes.mjs",
|
||||
"create-api-json": "node script/create-api-json.mjs",
|
||||
|
||||
Reference in new issue
Block a user