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:
Samuel AttardandClaude authored and GitHub committed 2026-09-21 00:18:37 -07:00
1 parent 5eefaea339
commit ad3a896a52
52 files changed
+580 -490

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
{
"$schema": "../node_modules/oxfmt/configuration_schema.json",
"semi": false,
"singleQuote": true,
"printWidth": 120,
"trailingComma": "none",
"ignorePatterns": ["fiddles"]
}
+35 -37
View File
@@ -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
// ...
+6 -4
View File
@@ -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'
})
```
+4 -7
View File
@@ -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) {
+7 -7
View File
@@ -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'
+8 -10
View File
@@ -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
View File
@@ -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
View File
@@ -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
+27 -21
View File
@@ -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
View File
@@ -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
View File
@@ -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()
+2 -4
View File
@@ -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
View File
@@ -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')
}
})
+7 -5
View File
@@ -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) => {
+8 -5
View File
@@ -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.
+1 -1
View File
@@ -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
View File
@@ -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)
})
})
```
+11 -8
View File
@@ -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
View File
@@ -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" */
}
}
```
+1 -1
View File
@@ -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
View File
@@ -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
+3 -3
View File
@@ -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') }
];
+1 -1
View File
@@ -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 }]
```
+3 -1
View File
@@ -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
+25 -44
View File
@@ -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)
+20 -16
View File
@@ -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()
})
```
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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
})
}
+1 -1
View File
@@ -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;
+2 -5
View File
@@ -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
+5 -5
View File
@@ -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')
+7 -5
View File
@@ -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
+5 -5
View File
@@ -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 {
+3 -1
View File
@@ -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
])
+8 -3
View File
@@ -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...')
}
})
```
+5 -9
View File
@@ -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) }
+2 -2
View File
@@ -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')
}
+1 -1
View File
@@ -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()
+2 -4
View File
@@ -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:
+5 -5
View File
@@ -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
View File
@@ -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.
+1 -2
View File
@@ -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`
+6 -4
View File
@@ -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
+4 -4
View File
@@ -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)
})
```
+2 -3
View File
@@ -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) => {
+2 -2
View File
@@ -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,
+8 -3
View File
@@ -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
View File
@@ -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",