Compare commits

..

2 Commits

Author SHA1 Message Date
Shelley Vohr
bc070f6707 chore: throw in utility net before app is ready 2024-03-23 21:21:45 +01:00
Shelley Vohr
63411ec513 fix: UtilityProcess.fork before app ready 2024-03-22 14:11:27 +01:00
76 changed files with 515 additions and 1480 deletions

View File

@@ -38,8 +38,6 @@ jobs:
- run: npm install mdast-util-from-markdown@2.0.0 unist-util-select@5.1.0
- name: Add labels
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
env:
ISSUE_BODY: ${{ github.event.issue.body }}
with:
github-token: ${{ steps.generate-token.outputs.token }}
script: |
@@ -49,7 +47,7 @@ jobs:
const [ owner, repo ] = '${{ github.repository }}'.split('/');
const issue_number = ${{ github.event.issue.number }};
const tree = fromMarkdown(process.env.ISSUE_BODY);
const tree = fromMarkdown(`${{ github.event.issue.body }}`);
const labels = [];

View File

@@ -1,17 +1,3 @@
{
"extends": "@electron/lint-roller/configs/markdownlint.json",
"no-angle-brackets": true,
"no-inline-html": {
"allowed_elements": [
"br",
"details",
"img",
"li",
"summary",
"ul",
"unknown",
"Tabs",
"TabItem",
]
}
"extends": "@electron/lint-roller/configs/markdownlint.json"
}

View File

@@ -475,7 +475,6 @@ source_set("electron_lib") {
"//net:extras",
"//net:net_resources",
"//printing/buildflags",
"//services/device/public/cpp/bluetooth:bluetooth",
"//services/device/public/cpp/geolocation",
"//services/device/public/cpp/hid",
"//services/device/public/mojom",

View File

@@ -32,7 +32,7 @@ In most cases, you should do everything in the `ready` event handler.
Returns:
* `event` Event
* `launchInfo` Record\<string, any\> | [NotificationResponse](structures/notification-response.md) _macOS_
* `launchInfo` Record<string, any> | [NotificationResponse](structures/notification-response.md) _macOS_
Emitted once, when Electron has finished initializing. On macOS, `launchInfo`
holds the `userInfo` of the [`NSUserNotification`](https://developer.apple.com/documentation/foundation/nsusernotification)
@@ -970,7 +970,7 @@ app.setJumpList([
### `app.requestSingleInstanceLock([additionalData])`
* `additionalData` Record\<any, any\> (optional) - A JSON object containing additional data to send to the first instance.
* `additionalData` Record<any, any> (optional) - A JSON object containing additional data to send to the first instance.
Returns `boolean`

View File

@@ -103,7 +103,7 @@ The `autoUpdater` object has the following methods:
* `options` Object
* `url` string
* `headers` Record\<string, string\> (optional) _macOS_ - HTTP request headers.
* `headers` Record<string, string> (optional) _macOS_ - HTTP request headers.
* `serverType` string (optional) _macOS_ - Can be `json` or `default`, see the [Squirrel.Mac][squirrel-mac]
README for more information.

View File

@@ -656,7 +656,7 @@ Closes the currently open [Quick Look][quick-look] panel.
#### `win.setBounds(bounds[, animate])`
* `bounds` Partial\<[Rectangle](structures/rectangle.md)\>
* `bounds` Partial<[Rectangle](structures/rectangle.md)>
* `animate` boolean (optional) _macOS_
Resizes and moves the window to the supplied bounds. Any properties that are not supplied will default to their current values.

View File

@@ -779,7 +779,7 @@ Closes the currently open [Quick Look][quick-look] panel.
#### `win.setBounds(bounds[, animate])`
* `bounds` Partial\<[Rectangle](structures/rectangle.md)\>
* `bounds` Partial<[Rectangle](structures/rectangle.md)>
* `animate` boolean (optional) _macOS_
Resizes and moves the window to the supplied bounds. Any properties that are not supplied will default to their current values.
@@ -1215,7 +1215,7 @@ win.loadURL('http://localhost:8000/post', {
* `filePath` string
* `options` Object (optional)
* `query` Record\<string, string\> (optional) - Passed to `url.format()`.
* `query` Record<string, string> (optional) - Passed to `url.format()`.
* `search` string (optional) - Passed to `url.format()`.
* `hash` string (optional) - Passed to `url.format()`.

View File

@@ -17,8 +17,6 @@ following properties:
method.
* `url` string (optional) - The request URL. Must be provided in the absolute
form with the protocol scheme specified as http or https.
* `headers` Record\<string, string | string[]\> (optional) - Headers to be sent
with the request.
* `session` Session (optional) - The [`Session`](session.md) instance with
which the request is associated.
* `partition` string (optional) - The name of the [`partition`](session.md)
@@ -160,7 +158,7 @@ Returns:
* `statusCode` Integer
* `method` string
* `redirectUrl` string
* `responseHeaders` Record\<string, string[]\>
* `responseHeaders` Record<string, string[]>
Emitted when the server returns a redirect response (e.g. 301 Moved
Permanently). Calling [`request.followRedirect`](#requestfollowredirect) will

View File

@@ -59,14 +59,14 @@ The `crashReporter` module has the following methods:
number of crashes uploaded to 1/hour. Default is `false`.
* `compress` boolean (optional) - If true, crash reports will be compressed
and uploaded with `Content-Encoding: gzip`. Default is `true`.
* `extra` Record\<string, string\> (optional) - Extra string key/value
* `extra` Record<string, string> (optional) - Extra string key/value
annotations that will be sent along with crash reports that are generated
in the main process. Only string values are supported. Crashes generated in
child processes will not contain these extra
parameters to crash reports generated from child processes, call
[`addExtraParameter`](#crashreporteraddextraparameterkey-value) from the
child process.
* `globalExtra` Record\<string, string\> (optional) - Extra string key/value
* `globalExtra` Record<string, string> (optional) - Extra string key/value
annotations that will be sent along with any crash reports generated in any
process. These annotations cannot be changed once the crash reporter has
been started. If a key is present in both the global extra parameters and

View File

@@ -72,7 +72,7 @@ Removes listeners of the specified `channel`.
### `ipcMain.handle(channel, listener)`
* `channel` string
* `listener` Function\<Promise\<any\> | any\>
* `listener` Function<Promise\<any&#62; | any&#62;
* `event` [IpcMainInvokeEvent][ipc-main-invoke-event]
* `...args` any[]
@@ -109,7 +109,7 @@ provided to the renderer process. Please refer to
### `ipcMain.handleOnce(channel, listener)`
* `channel` string
* `listener` Function\<Promise\<any\> | any\>
* `listener` Function<Promise\<any&#62; | any&#62;
* `event` [IpcMainInvokeEvent][ipc-main-invoke-event]
* `...args` any[]

View File

@@ -111,7 +111,7 @@ expect streaming responses.
* `scheme` string - scheme to handle, for example `https` or `my-app`. This is
the bit before the `:` in a URL.
* `handler` Function\<[GlobalResponse](https://nodejs.org/api/globals.html#response) | Promise\<GlobalResponse\>\>
* `handler` Function<[GlobalResponse](https://nodejs.org/api/globals.html#response) | Promise<GlobalResponse>>
* `request` [GlobalRequest](https://nodejs.org/api/globals.html#request)
Register a protocol handler for `scheme`. Requests made to URLs with this

View File

@@ -27,7 +27,7 @@ The `pushNotification` module emits the following events:
Returns:
* `event` Event
* `userInfo` Record\<String, any\>
* `userInfo` Record<String, any>
Emitted when the app receives a remote notification while running.
See: https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428430-application?language=objc

View File

@@ -814,8 +814,6 @@ win.webContents.session.setCertificateVerifyProc((request, callback) => {
* `keyboardLock` - Request capture of keypresses for any or all of the keys on the physical keyboard via the [Keyboard Lock API](https://developer.mozilla.org/en-US/docs/Web/API/Keyboard/lock). These requests always appear to originate from the main frame.
* `openExternal` - Request to open links in external applications.
* `speaker-selection` - Request to enumerate and select audio output devices via the [speaker-selection permissions policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy/speaker-selection).
* `storage-access` - Allows content loaded in a third-party context to request access to third-party cookies using the [Storage Access API](https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API).
* `top-level-storage-access` - Allow top-level sites to request third-party cookie access on behalf of embedded content originating from another site in the same related website set using the [Storage Access API](https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API).
* `window-management` - Request access to enumerate screens using the [`getScreenDetails`](https://developer.chrome.com/en/articles/multi-screen-window-placement/) API.
* `unknown` - An unrecognized permission request.
* `callback` Function
@@ -864,8 +862,6 @@ session.fromPartition('some-partition').setPermissionRequestHandler((webContents
* `openExternal` - Open links in external applications.
* `pointerLock` - Directly interpret mouse movements as an input method via the [Pointer Lock API](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_Lock_API). These requests always appear to originate from the main frame.
* `serial` - Read from and write to serial devices with the [Web Serial API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API).
* `storage-access` - Allows content loaded in a third-party context to request access to third-party cookies using the [Storage Access API](https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API).
* `top-level-storage-access` - Allow top-level sites to request third-party cookie access on behalf of embedded content originating from another site in the same related website set using the [Storage Access API](https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API).
* `usb` - Expose non-standard Universal Serial Bus (USB) compatible devices services to the web with the [WebUSB API](https://developer.mozilla.org/en-US/docs/Web/API/WebUSB_API).
* `requestingOrigin` string - The origin URL of the permission check
* `details` Object - Some properties are only available on certain permission types.
@@ -1220,7 +1216,7 @@ Returns `Promise<Buffer>` - resolves with blob data.
* `url` string
* `options` Object (optional)
* `headers` Record\<string, string\> (optional) - HTTP request headers.
* `headers` Record<string, string> (optional) - HTTP request headers.
Initiates a download of the resource at `url`.
The API will generate a [DownloadItem](download-item.md) that can be accessed
@@ -1428,34 +1424,23 @@ is emitted.
Returns `string | null` - The absolute file system path where data for this
session is persisted on disk. For in memory sessions this returns `null`.
#### `ses.clearData([options])`
* `options` Object (optional)
* `dataTypes` String[] (optional) - The types of data to clear. By default, this will clear all types of data.
* `backgroundFetch` - Background Fetch
* `cache` - Cache
* `cookies` - Cookies
* `downloads` - Downloads
* `fileSystems` - File Systems
* `indexedDB` - IndexedDB
* `localStorage` - Local Storage
* `serviceWorkers` - Service Workers
* `webSQL` - WebSQL
* `origins` String[] (optional) - Clear data for only these origins. Cannot be used with `excludeOrigins`.
* `excludeOrigins` String[] (optional) - Clear data for all origins except these ones. Cannot be used with `origins`.
* `avoidClosingConnections` boolean (optional) - Skips deleting cookies that would close current network connections. (Default: `false`)
* `originMatchingMode` String (optional) - The behavior for matching data to origins.
* `third-parties-included` (default) - Storage is matched on origin in first-party contexts and top-level-site in third-party contexts.
* `origin-in-all-contexts` - Storage is matched on origin only in all contexts.
#### `ses.clearData()`
Returns `Promise<void>` - resolves when all data has been cleared.
Clears various different types of data.
This method clears many different types of data, inlcuding:
* Cache
* Cookies
* Downloads
* IndexedDB
* Local Storage
* Service Workers
* And more...
This method clears more types of data and is more thourough than the
`clearStorageData` method.
**Note:** Cookies are stored at a broader scope than origins. When removing cookies and filtering by `origins` (or `excludeOrigins`), the cookies will be removed at the [registrable domain](https://url.spec.whatwg.org/#host-registrable-domain) level. For example, clearing cookies for the origin `https://really.specific.origin.example.com/` will end up clearing all cookies for `example.com`. Clearing cookies for the origin `https://my.website.example.co.uk/` will end up clearing all cookies for `example.co.uk`.
`clearStorageData` method, however it is currently less configurable than that
method.
For more information, refer to Chromium's [`BrowsingDataRemover` interface](https://source.chromium.org/chromium/chromium/src/+/main:content/public/browser/browsing_data_remover.h).

View File

@@ -1,4 +1,4 @@
# FilePathWithHeaders Object
* `path` string - The path to the file to send.
* `headers` Record\<string, string\> (optional) - Additional headers to be sent.
* `headers` Record<string, string> (optional) - Additional headers to be sent.

View File

@@ -3,5 +3,5 @@
* `actionIdentifier` string - The identifier string of the action that the user selected.
* `date` number - The delivery date of the notification.
* `identifier` string - The unique identifier for this notification request.
* `userInfo` Record\<string, any\> - A dictionary of custom information associated with the notification.
* `userInfo` Record<string, any> - A dictionary of custom information associated with the notification.
* `userText` string (optional) - The text entered or chosen by the user.

View File

@@ -4,4 +4,4 @@
* `referrer` string
* `method` string
* `uploadData` [UploadData[]](upload-data.md) (optional)
* `headers` Record\<string, string\>
* `headers` Record<string, string>

View File

@@ -10,7 +10,7 @@
`"text/html"`. Setting `mimeType` would implicitly set the `content-type`
header in response, but if `content-type` is already set in `headers`, the
`mimeType` would be ignored.
* `headers` Record\<string, string | string[]\> (optional) - An object containing the response headers. The
* `headers` Record<string, string | string[]> (optional) - An object containing the response headers. The
keys must be string, and values must be either string or Array of string.
* `data` (Buffer | string | ReadableStream) (optional) - The response body. When
returning stream as response, this is a Node.js readable stream representing

View File

@@ -19,7 +19,7 @@
include in the trace. If not specified, trace all processes.
* `histogram_names` string[] (optional) - a list of [histogram][] names to report
with the trace.
* `memory_dump_config` Record\<string, any\> (optional) - if the
* `memory_dump_config` Record<string, any> (optional) - if the
`disabled-by-default-memory-infra` category is enabled, this contains
optional additional configuration for data collection. See the [Chromium
memory-infra docs][memory-infra docs] for more information.

View File

@@ -36,7 +36,7 @@ Returns `boolean` - Whether the Swipe between pages setting is on.
### `systemPreferences.postNotification(event, userInfo[, deliverImmediately])` _macOS_
* `event` string
* `userInfo` Record\<string, any\>
* `userInfo` Record<string, any>
* `deliverImmediately` boolean (optional) - `true` to post notifications immediately even when the subscribing app is inactive.
Posts `event` as native notifications of macOS. The `userInfo` is an Object
@@ -45,7 +45,7 @@ that contains the user information dictionary sent along with the notification.
### `systemPreferences.postLocalNotification(event, userInfo)` _macOS_
* `event` string
* `userInfo` Record\<string, any\>
* `userInfo` Record<string, any>
Posts `event` as native notifications of macOS. The `userInfo` is an Object
that contains the user information dictionary sent along with the notification.
@@ -53,7 +53,7 @@ that contains the user information dictionary sent along with the notification.
### `systemPreferences.postWorkspaceNotification(event, userInfo)` _macOS_
* `event` string
* `userInfo` Record\<string, any\>
* `userInfo` Record<string, any>
Posts `event` as native notifications of macOS. The `userInfo` is an Object
that contains the user information dictionary sent along with the notification.
@@ -63,7 +63,7 @@ that contains the user information dictionary sent along with the notification.
* `event` string | null
* `callback` Function
* `event` string
* `userInfo` Record\<string, unknown\>
* `userInfo` Record<string, unknown>
* `object` string
Returns `number` - The ID of this subscription
@@ -92,7 +92,7 @@ If `event` is null, the `NSDistributedNotificationCenter` doesnt use it as cr
* `event` string | null
* `callback` Function
* `event` string
* `userInfo` Record\<string, unknown\>
* `userInfo` Record<string, unknown>
* `object` string
Returns `number` - The ID of this subscription
@@ -107,7 +107,7 @@ If `event` is null, the `NSNotificationCenter` doesnt use it as criteria for
* `event` string | null
* `callback` Function
* `event` string
* `userInfo` Record\<string, unknown\>
* `userInfo` Record<string, unknown>
* `object` string
Returns `number` - The ID of this subscription
@@ -137,7 +137,7 @@ Same as `unsubscribeNotification`, but removes the subscriber from `NSWorkspace.
### `systemPreferences.registerDefaults(defaults)` _macOS_
* `defaults` Record\<string, string | boolean | number\> - a dictionary of (`key: value`) user defaults
* `defaults` Record<string, string | boolean | number> - a dictionary of (`key: value`) user defaults
Add the specified defaults to your application's `NSUserDefaults`.

View File

@@ -237,7 +237,7 @@ See [`window.open()`](window-open.md) for more details and how to use this in co
Returns:
* `details` Event\<\>
* `details` Event<>
* `url` string - The URL the frame is navigating to.
* `isSameDocument` boolean - This event does not fire for same document navigations using window.history api and reference fragment navigations.
This property is always set to `false` for this event.
@@ -270,7 +270,7 @@ Calling `event.preventDefault()` will prevent the navigation.
Returns:
* `details` Event\<\>
* `details` Event<>
* `url` string - The URL the frame is navigating to.
* `isSameDocument` boolean - This event does not fire for same document navigations using window.history api and reference fragment navigations.
This property is always set to `false` for this event.
@@ -300,7 +300,7 @@ Calling `event.preventDefault()` will prevent the navigation.
Returns:
* `details` Event\<\>
* `details` Event<>
* `url` string - The URL the frame is navigating to.
* `isSameDocument` boolean - Whether the navigation happened without changing
document. Examples of same document navigations are reference fragment
@@ -324,7 +324,7 @@ Emitted when any frame (including main) starts navigating.
Returns:
* `details` Event\<\>
* `details` Event<>
* `url` string - The URL the frame is navigating to.
* `isSameDocument` boolean - Whether the navigation happened without changing
document. Examples of same document navigations are reference fragment
@@ -355,7 +355,7 @@ redirect).
Returns:
* `details` Event\<\>
* `details` Event<>
* `url` string - The URL the frame is navigating to.
* `isSameDocument` boolean - Whether the navigation happened without changing
document. Examples of same document navigations are reference fragment
@@ -683,7 +683,7 @@ Emitted when media is paused or done playing.
Returns:
* `event` Event\<\>
* `event` Event<>
* `audible` boolean - True if one or more frames or child `webContents` are emitting audio.
Emitted when media becomes audible or inaudible.
@@ -900,7 +900,7 @@ Returns:
* `webPreferences` [WebPreferences](structures/web-preferences.md) - The web preferences that will be used by the guest
page. This object can be modified to adjust the preferences for the guest
page.
* `params` Record\<string, string\> - The other `<webview>` parameters such as the `src` URL.
* `params` Record<string, string> - The other `<webview>` parameters such as the `src` URL.
This object can be modified to adjust the parameters of the guest page.
Emitted when a `<webview>`'s web contents is being attached to this web
@@ -1020,7 +1020,7 @@ win.webContents.loadURL('https://github.com', options)
* `filePath` string
* `options` Object (optional)
* `query` Record\<string, string\> (optional) - Passed to `url.format()`.
* `query` Record<string, string> (optional) - Passed to `url.format()`.
* `search` string (optional) - Passed to `url.format()`.
* `hash` string (optional) - Passed to `url.format()`.
@@ -1051,7 +1051,7 @@ win.loadFile('src/index.html')
* `url` string
* `options` Object (optional)
* `headers` Record\<string, string\> (optional) - HTTP request headers.
* `headers` Record<string, string> (optional) - HTTP request headers.
Initiates a download of the resource at `url` without navigating. The
`will-download` event of `session` will be triggered.
@@ -1288,7 +1288,7 @@ Ignore application menu shortcuts while this web contents is focused.
#### `contents.setWindowOpenHandler(handler)`
* `handler` Function\<[WindowOpenHandlerResponse](structures/window-open-handler-response.md)\>
* `handler` Function<[WindowOpenHandlerResponse](structures/window-open-handler-response.md)>
* `details` Object
* `url` string - The _resolved_ version of the URL passed to `window.open()`. e.g. opening a window with `window.open('foo')` will yield something like `https://the-origin/the/current/path/foo`.
* `frameName` string - Name of the window provided in `window.open()`
@@ -1583,7 +1583,7 @@ Returns `Promise<PrinterInfo[]>` - Resolves with a [`PrinterInfo[]`](structures/
* `from` number - Index of the first page to print (0-based).
* `to` number - Index of the last page to print (inclusive) (0-based).
* `duplexMode` string (optional) - Set the duplex mode of the printed web page. Can be `simplex`, `shortEdge`, or `longEdge`.
* `dpi` Record\<string, number\> (optional)
* `dpi` Record<string, number> (optional)
* `horizontal` number (optional) - The horizontal dpi.
* `vertical` number (optional) - The vertical dpi.
* `header` string (optional) - string to be printed as page header.

View File

@@ -99,11 +99,11 @@ Some examples of valid `urls`:
* `referrer` string
* `timestamp` Double
* `uploadData` [UploadData[]](structures/upload-data.md) (optional)
* `requestHeaders` Record\<string, string\>
* `requestHeaders` Record<string, string>
* `callback` Function
* `beforeSendResponse` Object
* `cancel` boolean (optional)
* `requestHeaders` Record\<string, string | string[]\> (optional) - When provided, request will be made
* `requestHeaders` Record<string, string | string[]> (optional) - When provided, request will be made
with these headers.
The `listener` will be called with `listener(details, callback)` before sending
@@ -126,7 +126,7 @@ The `callback` has to be called with a `response` object.
* `resourceType` string - Can be `mainFrame`, `subFrame`, `stylesheet`, `script`, `image`, `font`, `object`, `xhr`, `ping`, `cspReport`, `media`, `webSocket` or `other`.
* `referrer` string
* `timestamp` Double
* `requestHeaders` Record\<string, string\>
* `requestHeaders` Record<string, string>
The `listener` will be called with `listener(details)` just before a request is
going to be sent to the server, modifications of previous `onBeforeSendHeaders`
@@ -148,11 +148,11 @@ response are visible by the time this listener is fired.
* `timestamp` Double
* `statusLine` string
* `statusCode` Integer
* `responseHeaders` Record\<string, string[]\> (optional)
* `responseHeaders` Record<string, string[]> (optional)
* `callback` Function
* `headersReceivedResponse` Object
* `cancel` boolean (optional)
* `responseHeaders` Record\<string, string | string[]\> (optional) - When provided, the server is assumed
* `responseHeaders` Record<string, string | string[]> (optional) - When provided, the server is assumed
to have responded with these headers.
* `statusLine` string (optional) - Should be provided when overriding
`responseHeaders` to change header status otherwise original response
@@ -177,7 +177,7 @@ The `callback` has to be called with a `response` object.
* `resourceType` string - Can be `mainFrame`, `subFrame`, `stylesheet`, `script`, `image`, `font`, `object`, `xhr`, `ping`, `cspReport`, `media`, `webSocket` or `other`.
* `referrer` string
* `timestamp` Double
* `responseHeaders` Record\<string, string[]\> (optional)
* `responseHeaders` Record<string, string[]> (optional)
* `fromCache` boolean - Indicates whether the response was fetched from disk
cache.
* `statusCode` Integer
@@ -207,7 +207,7 @@ and response headers are available.
* `ip` string (optional) - The server IP address that the request was
actually sent to.
* `fromCache` boolean
* `responseHeaders` Record\<string, string[]\> (optional)
* `responseHeaders` Record<string, string[]> (optional)
The `listener` will be called with `listener(details)` when a server initiated
redirect is about to occur.
@@ -226,7 +226,7 @@ redirect is about to occur.
* `resourceType` string - Can be `mainFrame`, `subFrame`, `stylesheet`, `script`, `image`, `font`, `object`, `xhr`, `ping`, `cspReport`, `media`, `webSocket` or `other`.
* `referrer` string
* `timestamp` Double
* `responseHeaders` Record\<string, string[]\> (optional)
* `responseHeaders` Record<string, string[]> (optional)
* `fromCache` boolean
* `statusCode` Integer
* `statusLine` string

View File

@@ -287,7 +287,7 @@ e.g. the `http://` or `file://`.
* `url` string
* `options` Object (optional)
* `headers` Record\<string, string\> (optional) - HTTP request headers.
* `headers` Record<string, string> (optional) - HTTP request headers.
Initiates a download of the resource at `url` without navigating.
@@ -580,7 +580,7 @@ Stops any `findInPage` request for the `webview` with the provided `action`.
* `from` number - Index of the first page to print (0-based).
* `to` number - Index of the last page to print (inclusive) (0-based).
* `duplexMode` string (optional) - Set the duplex mode of the printed web page. Can be `simplex`, `shortEdge`, or `longEdge`.
* `dpi` Record\<string, number\> (optional)
* `dpi` Record<string, number> (optional)
* `horizontal` number (optional) - The horizontal dpi.
* `vertical` number (optional) - The vertical dpi.
* `header` string (optional) - string to be printed as page header.

View File

@@ -1686,7 +1686,7 @@ folder
└── file3
```
In Electron &lt;=6, this would return a `FileList` with a `File` object for:
In Electron <=6, this would return a `FileList` with a `File` object for:
```console
path/to/folder

View File

@@ -234,7 +234,7 @@ Notification) whereas camelCase modules are not instantiable (e.g. app, ipcRende
<details><summary>Typed import aliases</summary>
For better type checking when writing TypeScript code, you can choose to import
main process modules from `electron/main`.
main process modules from <code>electron/main</code>.
```js
const { app, BrowserWindow } = require('electron/main')

View File

@@ -209,22 +209,6 @@ type ExtraURLLoaderOptions = {
headers: Record<string, { name: string, value: string | string[] }>;
allowNonHttpProtocols: boolean;
}
function validateHeader (name: any, value: any): void {
if (typeof name !== 'string') {
throw new TypeError('`name` should be a string in setHeader(name, value)');
}
if (value == null) {
throw new Error('`value` required in setHeader("' + name + '", value)');
}
if (!isValidHeaderName(name)) {
throw new Error(`Invalid header name: '${name}'`);
}
if (!isValidHeaderValue(value.toString())) {
throw new Error(`Invalid value for header '${name}': '${value}'`);
}
}
function parseOptions (optionsIn: ClientRequestConstructorOptions | string): NodeJS.CreateURLLoaderOptions & ExtraURLLoaderOptions {
// eslint-disable-next-line node/no-deprecated-api
const options: any = typeof optionsIn === 'string' ? url.parse(optionsIn) : { ...optionsIn };
@@ -291,7 +275,12 @@ function parseOptions (optionsIn: ClientRequestConstructorOptions | string): Nod
};
const headers: Record<string, string | string[]> = options.headers || {};
for (const [name, value] of Object.entries(headers)) {
validateHeader(name, value);
if (!isValidHeaderName(name)) {
throw new Error(`Invalid header name: '${name}'`);
}
if (!isValidHeaderValue(value.toString())) {
throw new Error(`Invalid value for header '${name}': '${value}'`);
}
const key = name.toLowerCase();
urlLoaderOptions.headers[key] = { name, value };
}
@@ -362,10 +351,21 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
}
setHeader (name: string, value: string) {
if (typeof name !== 'string') {
throw new TypeError('`name` should be a string in setHeader(name, value)');
}
if (value == null) {
throw new Error('`value` required in setHeader("' + name + '", value)');
}
if (this._started || this._firstWrite) {
throw new Error('Can\'t set headers after they are sent');
}
validateHeader(name, value);
if (!isValidHeaderName(name)) {
throw new Error(`Invalid header name: '${name}'`);
}
if (!isValidHeaderValue(value.toString())) {
throw new Error(`Invalid value for header '${name}': '${value}'`);
}
const key = name.toLowerCase();
this._urlLoaderOptions.headers[key] = { name, value };

View File

@@ -1,11 +1,20 @@
import { app } from 'electron/main';
import { IncomingMessage } from 'electron/utility';
import type { ClientRequestConstructorOptions } from 'electron/utility';
import { ClientRequest } from '@electron/internal/common/api/net-client-request';
import { fetchWithSession } from '@electron/internal/browser/api/net-fetch';
const { isOnline, resolveHost } = process._linkedBinding('electron_common_net');
const { _ensureNetworkServiceInitialized } = process._linkedBinding('electron_browser_utility_process');
if (app.isReady()) {
_ensureNetworkServiceInitialized();
}
export function request (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
if (!app.isReady()) {
throw new Error('net module can only be used after app is ready');
}
return new ClientRequest(options, callback);
}

View File

@@ -9,7 +9,7 @@
"@electron/docs-parser": "^1.2.0",
"@electron/fiddle-core": "^1.0.4",
"@electron/github-app-auth": "^2.0.0",
"@electron/lint-roller": "^1.12.1",
"@electron/lint-roller": "^1.9.0",
"@electron/typescript-definitions": "^8.15.2",
"@octokit/rest": "^19.0.7",
"@primer/octicons": "^10.0.0",
@@ -49,7 +49,7 @@
"eslint-plugin-standard": "^4.0.1",
"eslint-plugin-unicorn": "^46.0.1",
"events": "^3.2.0",
"express": "^4.19.2",
"express": "^4.16.4",
"folder-hash": "^2.1.1",
"fs-extra": "^9.0.1",
"got": "^11.8.5",

View File

@@ -33,6 +33,7 @@ fix_assert_module_in_the_renderer_process.patch
fix_add_trusted_space_and_trusted_lo_space_to_the_v8_heap.patch
win_process_avoid_assert_after_spawning_store_app_4152.patch
chore_remove_use_of_deprecated_kmaxlength.patch
fix_missing_include_for_node_extern.patch
feat_optionally_prevent_calling_v8_enablewebassemblytraphandler.patch
build_only_create_cppgc_heap_on_non-32_bit_platforms.patch
fix_-wshadow_error_in_uvwasi_c.patch
@@ -46,4 +47,3 @@ build_ensure_v8_pointer_compression_sandbox_is_enabled_on_64bit.patch
fix_revert_src_lb_reducing_c_calls_of_esm_legacy_main_resolve.patch
src_preload_function_for_environment.patch
deprecate_vector_v8_local_in_v8.patch
fs_fix_wtf-8_decoding_issue.patch

View File

@@ -87,18 +87,10 @@ index 895ff3a5948add3513700ecc2f32fce4c2fbe4eb..3182a5e4aad2ba0be2b6769edb696b81
MaybeLocal<Value> ModuleWrap::SyntheticModuleEvaluationStepsCallback(
diff --git a/src/module_wrap.h b/src/module_wrap.h
index e17048357feca2419087621ed280de30882a90bc..63682be31ce00a3bf7b9be909cac4b7f9ec02a8e 100644
index e17048357feca2419087621ed280de30882a90bc..061117dc3182d63e35d7e99ffd95801f96356322 100644
--- a/src/module_wrap.h
+++ b/src/module_wrap.h
@@ -7,6 +7,7 @@
#include <string>
#include <vector>
#include "base_object.h"
+#include "node.h"
namespace node {
@@ -31,7 +32,14 @@ enum HostDefinedOptions : int {
@@ -31,7 +31,14 @@ enum HostDefinedOptions : int {
kLength = 9,
};
@@ -114,7 +106,7 @@ index e17048357feca2419087621ed280de30882a90bc..63682be31ce00a3bf7b9be909cac4b7f
public:
enum InternalFields {
kModuleSlot = BaseObject::kInternalFieldCount,
@@ -68,6 +76,8 @@ class ModuleWrap : public BaseObject {
@@ -68,6 +75,8 @@ class ModuleWrap : public BaseObject {
return true;
}
@@ -123,7 +115,7 @@ index e17048357feca2419087621ed280de30882a90bc..63682be31ce00a3bf7b9be909cac4b7f
private:
ModuleWrap(Realm* realm,
v8::Local<v8::Object> object,
@@ -102,7 +112,6 @@ class ModuleWrap : public BaseObject {
@@ -102,7 +111,6 @@ class ModuleWrap : public BaseObject {
v8::Local<v8::String> specifier,
v8::Local<v8::FixedArray> import_attributes,
v8::Local<v8::Module> referrer);

View File

@@ -0,0 +1,26 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shelley Vohr <shelley.vohr@gmail.com>
Date: Wed, 15 Nov 2023 12:25:39 +0100
Subject: fix: missing include for NODE_EXTERN
At some point it seems that node.h was removed from the include chain,
causing the following error:
../../third_party/electron_node/src/module_wrap.h:33:1: error: unknown type name 'NODE_EXTERN'
33 | NODE_EXTERN v8::MaybeLocal<v8::Promise> ImportModuleDynamically(
| ^
This should be upstreamed.
diff --git a/src/module_wrap.h b/src/module_wrap.h
index 061117dc3182d63e35d7e99ffd95801f96356322..63682be31ce00a3bf7b9be909cac4b7f9ec02a8e 100644
--- a/src/module_wrap.h
+++ b/src/module_wrap.h
@@ -7,6 +7,7 @@
#include <string>
#include <vector>
#include "base_object.h"
+#include "node.h"
namespace node {

View File

@@ -1,30 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Richard Lau <rlau@redhat.com>
Date: Fri, 1 Mar 2024 19:15:40 +0000
Subject: fs: fix WTF-8 decoding issue
Cherry-pick of libuv/libuv@d09441c
Refs: https://github.com/libuv/libuv/pull/2970
Fixes: https://github.com/nodejs/node/issues/48673
We forgot to mask off the high bits from the first byte, so we ended up
always failing the subsequent range check.
diff --git a/deps/uv/src/win/fs.c b/deps/uv/src/win/fs.c
index fc209c54f470edaa031009979061cff071cbf66d..4fc13b04bdae5bd9e2627027a10c534c2d9675dc 100644
--- a/deps/uv/src/win/fs.c
+++ b/deps/uv/src/win/fs.c
@@ -176,9 +176,11 @@ static int32_t fs__decode_wtf8_char(const char** input) {
if ((b4 & 0xC0) != 0x80)
return -1; /* invalid: not a continuation byte */
code_point = (code_point << 6) | (b4 & 0x3F);
- if (b1 <= 0xF4)
+ if (b1 <= 0xF4) {
+ code_point &= 0x1FFFFF;
if (code_point <= 0x10FFFF)
return code_point; /* four-byte character */
+ }
/* code point too large */
return -1;

View File

@@ -3,18 +3,9 @@ if (!process.env.CI) require('dotenv-safe').load();
const assert = require('node:assert');
const got = require('got');
const { Octokit } = require('@octokit/rest');
const octokit = new Octokit({
auth: process.env.ELECTRON_GITHUB_TOKEN
});
const BUILD_APPVEYOR_URL = 'https://ci.appveyor.com/api/builds';
const CIRCLECI_PIPELINE_URL = 'https://circleci.com/api/v2/project/gh/electron/electron/pipeline';
const GH_ACTIONS_PIPELINE_URL = 'https://github.com/electron/electron/actions';
const GH_ACTIONS_API_URL = '/repos/electron/electron/actions';
const CIRCLECI_WAIT_TIME = process.env.CIRCLECI_WAIT_TIME || 30000;
const GH_ACTIONS_WAIT_TIME = process.env.GH_ACTIONS_WAIT_TIME || 30000;
const appVeyorJobs = {
'electron-x64': 'electron-x64-release',
@@ -32,14 +23,6 @@ const circleCIPublishIndividualArches = {
'linux-publish': ['arm', 'arm64', 'x64']
};
const ghActionsPublishWorkflows = [
'macos-publish'
];
const ghActionsPublishIndividualArches = {
'macos-publish': ['osx-x64', 'mas-x64', 'osx-arm64', 'mas-arm64']
};
let jobRequestedCount = 0;
async function makeRequest ({ auth, username, password, url, headers, body, method }) {
@@ -70,65 +53,6 @@ async function makeRequest ({ auth, username, password, url, headers, body, meth
return JSON.parse(response.body);
}
async function githubActionsCall (targetBranch, workflowName, options) {
console.log(`Triggering GitHub Actions to run build job: ${workflowName} on branch: ${targetBranch} with release flag.`);
const buildRequest = {
branch: targetBranch,
parameters: {}
};
if (options.ghRelease) {
buildRequest.parameters['upload-to-storage'] = '0';
} else {
buildRequest.parameters['upload-to-storage'] = '1';
}
buildRequest.parameters[`run-${workflowName}`] = true;
if (options.arch) {
const validArches = ghActionsPublishIndividualArches[workflowName];
assert(validArches.includes(options.arch), `Unknown GitHub Actions architecture "${options.arch}". Valid values are ${JSON.stringify(validArches)}`);
buildRequest.parameters['macos-publish-arch-limit'] = options.arch;
}
jobRequestedCount++;
try {
const commits = await octokit.repos.listCommits({
owner: 'electron',
repo: 'electron',
sha: targetBranch,
per_page: 5
});
if (!commits.data.length) {
console.error('Could not fetch most recent commits for GitHub Actions, returning early');
}
await octokit.request(`POST ${GH_ACTIONS_API_URL}/workflows/${workflowName}.yml/dispatches`, {
ref: buildRequest.branch,
inputs: {
...buildRequest.parameters
},
headers: {
'X-GitHub-Api-Version': '2022-11-28'
}
});
const runNumber = await getGitHubActionsRun(workflowName, commits.data[0].sha);
if (runNumber === -1) {
return;
}
console.log(`GitHub Actions release build pipeline ${runNumber} for ${workflowName} triggered.`);
const runUrl = `${GH_ACTIONS_PIPELINE_URL}/runs/${runNumber}`;
if (options.runningPublishWorkflows) {
console.log(`GitHub Actions release workflow request for ${workflowName} successful. Check ${runUrl} for status.`);
} else {
console.log(`GitHub Actions release build workflow running at ${GH_ACTIONS_PIPELINE_URL}/runs/${runNumber} for ${workflowName}.`);
console.log(`GitHub Actions release build request for ${workflowName} successful. Check ${runUrl} for status.`);
}
} catch (err) {
console.log('Error calling GitHub Actions: ', err);
}
}
async function circleCIcall (targetBranch, workflowName, options) {
console.log(`Triggering CircleCI to run build job: ${workflowName} on branch: ${targetBranch} with release flag.`);
const buildRequest = {
@@ -243,59 +167,6 @@ async function getCircleCIJobNumber (workflowId) {
return jobNumber;
}
async function getGitHubActionsRun (workflowId, headCommit) {
let runNumber = 0;
let actionRun;
while (runNumber === 0) {
const actionsRuns = await octokit.request(`GET ${GH_ACTIONS_API_URL}/workflows/${workflowId}.yml/runs`, {
headers: {
'X-GitHub-Api-Version': '2022-11-28'
}
});
if (!actionsRuns.data.workflow_runs.length) {
console.log(`No current workflow_runs found for ${workflowId}, response was: ${actionsRuns.data.workflow_runs}`);
runNumber = -1;
break;
}
for (const run of actionsRuns.data.workflow_runs) {
if (run.head_sha === headCommit) {
console.log(`GitHub Actions run ${run.html_url} found for ${headCommit}, waiting on status.`);
actionRun = run;
break;
}
}
if (actionRun) {
switch (actionRun.status) {
case 'in_progress':
case 'pending':
case 'queued':
case 'requested':
case 'waiting': {
if (actionRun.id && !isNaN(actionRun.id)) {
console.log(`GitHub Actions run ${actionRun.status} for ${actionRun.html_url}.`);
runNumber = actionRun.id;
}
break;
}
case 'action_required':
case 'cancelled':
case 'failure':
case 'skipped':
case 'timed_out':
case 'failed': {
console.log(`Error workflow run returned a status of ${actionRun.status} for ${actionRun.html_url}`);
runNumber = -1;
break;
}
}
await new Promise(resolve => setTimeout(resolve, GH_ACTIONS_WAIT_TIME));
}
}
return runNumber;
}
async function circleCIRequest (url, method, requestBody) {
const requestOpts = {
username: process.env.CIRCLE_TOKEN,
@@ -323,6 +194,18 @@ async function circleCIRequest (url, method, requestBody) {
});
}
function buildAppVeyor (targetBranch, options) {
const validJobs = Object.keys(appVeyorJobs);
if (options.job) {
assert(validJobs.includes(options.job), `Unknown AppVeyor CI job name: ${options.job}. Valid values are: ${validJobs}.`);
callAppVeyor(targetBranch, options.job, options);
} else {
for (const job of validJobs) {
callAppVeyor(targetBranch, job, options);
}
}
}
async function callAppVeyor (targetBranch, job, options) {
console.log(`Triggering AppVeyor to run build job: ${job} on branch: ${targetBranch} with release flag.`);
const environmentVariables = {
@@ -369,18 +252,6 @@ async function callAppVeyor (targetBranch, job, options) {
}
}
function buildAppVeyor (targetBranch, options) {
const validJobs = Object.keys(appVeyorJobs);
if (options.job) {
assert(validJobs.includes(options.job), `Unknown AppVeyor CI job name: ${options.job}. Valid values are: ${validJobs}.`);
callAppVeyor(targetBranch, options.job, options);
} else {
for (const job of validJobs) {
callAppVeyor(targetBranch, job, options);
}
}
}
function buildCircleCI (targetBranch, options) {
if (options.job) {
assert(circleCIPublishWorkflows.includes(options.job), `Unknown CircleCI workflow name: ${options.job}. Valid values are: ${circleCIPublishWorkflows}.`);
@@ -394,19 +265,6 @@ function buildCircleCI (targetBranch, options) {
}
}
function buildGHActions (targetBranch, options) {
if (options.job) {
assert(ghActionsPublishWorkflows.includes(options.job), `Unknown GitHub Actions workflow name: ${options.job}. Valid values are: ${ghActionsPublishWorkflows}.`);
githubActionsCall(targetBranch, options.job, options);
} else {
assert(!options.arch, 'Cannot provide a single architecture while building all workflows, please specify a single workflow via --workflow');
options.runningPublishWorkflows = true;
for (const job of ghActionsPublishWorkflows) {
githubActionsCall(targetBranch, job, options);
}
}
}
function runRelease (targetBranch, options) {
if (options.ci) {
switch (options.ci) {
@@ -414,10 +272,6 @@ function runRelease (targetBranch, options) {
buildCircleCI(targetBranch, options);
break;
}
case 'GitHubActions': {
buildGHActions(targetBranch, options);
break;
}
case 'AppVeyor': {
buildAppVeyor(targetBranch, options);
break;
@@ -430,8 +284,6 @@ function runRelease (targetBranch, options) {
} else {
buildCircleCI(targetBranch, options);
buildAppVeyor(targetBranch, options);
// TODO(vertedinde): Enable GH Actions in defaults when ready
// buildGHActions(targetBranch, options);
}
console.log(`${jobRequestedCount} jobs were requested.`);
}
@@ -445,7 +297,7 @@ if (require.main === module) {
const targetBranch = args._[0];
if (args._.length < 1) {
console.log(`Trigger CI to build release builds of electron.
Usage: ci-release-build.js [--job=CI_JOB_NAME] [--arch=INDIVIDUAL_ARCH] [--ci=CircleCI|AppVeyor|GitHubActions]
Usage: ci-release-build.js [--job=CI_JOB_NAME] [--arch=INDIVIDUAL_ARCH] [--ci=CircleCI|AppVeyor]
[--ghRelease] [--circleBuildNum=xxx] [--appveyorJobId=xxx] [--commit=sha] TARGET_BRANCH
`);
process.exit(0);

View File

@@ -12,7 +12,6 @@
#include <vector>
#include "base/command_line.h"
#include "base/containers/fixed_flat_map.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
@@ -34,14 +33,12 @@
#include "content/browser/code_cache/generated_code_cache_context.h" // nogncheck
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/browsing_data_filter_builder.h"
#include "content/public/browser/browsing_data_remover.h"
#include "content/public/browser/download_item_utils.h"
#include "content/public/browser/download_manager_delegate.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/storage_partition.h"
#include "gin/arguments.h"
#include "gin/converter.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "net/base/completion_repeating_callback.h"
@@ -89,7 +86,6 @@
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/origin.h"
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
#include "extensions/browser/extension_registry.h"
@@ -110,8 +106,6 @@
#endif
using content::BrowserThread;
using content::BrowsingDataFilterBuilder;
using content::BrowsingDataRemover;
using content::StoragePartition;
namespace {
@@ -123,23 +117,25 @@ struct ClearStorageDataOptions {
};
uint32_t GetStorageMask(const std::vector<std::string>& storage_types) {
static constexpr auto Lookup =
base::MakeFixedFlatMap<std::string_view, uint32_t>(
{{"cookies", StoragePartition::REMOVE_DATA_MASK_COOKIES},
{"filesystem", StoragePartition::REMOVE_DATA_MASK_FILE_SYSTEMS},
{"indexdb", StoragePartition::REMOVE_DATA_MASK_INDEXEDDB},
{"localstorage", StoragePartition::REMOVE_DATA_MASK_LOCAL_STORAGE},
{"shadercache", StoragePartition::REMOVE_DATA_MASK_SHADER_CACHE},
{"websql", StoragePartition::REMOVE_DATA_MASK_WEBSQL},
{"serviceworkers",
StoragePartition::REMOVE_DATA_MASK_SERVICE_WORKERS},
{"cachestorage", StoragePartition::REMOVE_DATA_MASK_CACHE_STORAGE}});
uint32_t storage_mask = 0;
for (const auto& it : storage_types) {
auto type = base::ToLowerASCII(it);
if (Lookup.contains(type))
storage_mask |= Lookup.at(type);
if (type == "cookies")
storage_mask |= StoragePartition::REMOVE_DATA_MASK_COOKIES;
else if (type == "filesystem")
storage_mask |= StoragePartition::REMOVE_DATA_MASK_FILE_SYSTEMS;
else if (type == "indexdb")
storage_mask |= StoragePartition::REMOVE_DATA_MASK_INDEXEDDB;
else if (type == "localstorage")
storage_mask |= StoragePartition::REMOVE_DATA_MASK_LOCAL_STORAGE;
else if (type == "shadercache")
storage_mask |= StoragePartition::REMOVE_DATA_MASK_SHADER_CACHE;
else if (type == "websql")
storage_mask |= StoragePartition::REMOVE_DATA_MASK_WEBSQL;
else if (type == "serviceworkers")
storage_mask |= StoragePartition::REMOVE_DATA_MASK_SERVICE_WORKERS;
else if (type == "cachestorage")
storage_mask |= StoragePartition::REMOVE_DATA_MASK_CACHE_STORAGE;
}
return storage_mask;
}
@@ -156,202 +152,38 @@ uint32_t GetQuotaMask(const std::vector<std::string>& quota_types) {
return quota_mask;
}
constexpr BrowsingDataRemover::DataType kClearDataTypeAll = ~0ULL;
constexpr BrowsingDataRemover::OriginType kClearOriginTypeAll =
BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB |
BrowsingDataRemover::ORIGIN_TYPE_PROTECTED_WEB;
constexpr content::BrowsingDataRemover::DataType kClearDataTypeAll = ~0ULL;
constexpr content::BrowsingDataRemover::OriginType kClearOriginTypeAll =
content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB |
content::BrowsingDataRemover::ORIGIN_TYPE_PROTECTED_WEB;
constexpr auto kDataTypeLookup =
base::MakeFixedFlatMap<std::string_view, BrowsingDataRemover::DataType>({
{"backgroundFetch", BrowsingDataRemover::DATA_TYPE_BACKGROUND_FETCH},
{"cache", BrowsingDataRemover::DATA_TYPE_CACHE |
BrowsingDataRemover::DATA_TYPE_CACHE_STORAGE},
{"cookies", BrowsingDataRemover::DATA_TYPE_COOKIES},
{"downloads", BrowsingDataRemover::DATA_TYPE_DOWNLOADS},
{"fileSystems", BrowsingDataRemover::DATA_TYPE_FILE_SYSTEMS},
{"indexedDB", BrowsingDataRemover::DATA_TYPE_INDEXED_DB},
{"localStorage", BrowsingDataRemover::DATA_TYPE_LOCAL_STORAGE},
{"serviceWorkers", BrowsingDataRemover::DATA_TYPE_SERVICE_WORKERS},
{"webSQL", BrowsingDataRemover::DATA_TYPE_WEB_SQL},
});
BrowsingDataRemover::DataType GetDataTypeMask(
const std::vector<std::string>& data_types) {
BrowsingDataRemover::DataType mask = 0u;
for (const auto& type : data_types) {
if (kDataTypeLookup.contains(type)) {
mask |= kDataTypeLookup.at(type);
}
}
return mask;
}
std::vector<std::string> GetDataTypesFromMask(
BrowsingDataRemover::DataType mask) {
std::vector<std::string> results;
for (const auto [type, flag] : kDataTypeLookup) {
if (mask & flag) {
results.emplace_back(type);
}
}
return results;
}
// Represents a task to clear browsing data for the `clearData` API method.
//
// This type manages its own lifetime, deleting itself once the task finishes
// completely.
class ClearDataTask {
// Observes the BrowsingDataRemover that backs the `clearData` method and
// fulfills that API's promise once it's done. This type manages its own
// lifetime, deleting itself once it's done.
class ClearDataObserver : public content::BrowsingDataRemover::Observer {
public:
// Starts running a task. This function will return before the task is
// finished, but will resolve or reject the |promise| when it finishes.
static void Run(
BrowsingDataRemover* remover,
gin_helper::Promise<void> promise,
BrowsingDataRemover::DataType data_type_mask,
std::vector<url::Origin> origins,
BrowsingDataFilterBuilder::Mode filter_mode,
BrowsingDataFilterBuilder::OriginMatchingMode origin_matching_mode) {
std::shared_ptr<ClearDataTask> task(new ClearDataTask(std::move(promise)));
ClearDataObserver(gin_helper::Promise<void> promise,
content::BrowsingDataRemover* remover)
: promise_(std::move(promise)) {
observation_.Observe(remover);
}
// This method counts as an operation. This is important so we can call
// `OnOperationFinished` at the end of this method as a fallback if all the
// other operations finished while this method was still executing
task->operations_running_ = 1;
// Cookies are scoped more broadly than other types of data, so if we are
// filtering then we need to do it at the registrable domain level
if (!origins.empty() &&
data_type_mask & BrowsingDataRemover::DATA_TYPE_COOKIES) {
data_type_mask &= ~BrowsingDataRemover::DATA_TYPE_COOKIES;
auto cookies_filter_builder =
BrowsingDataFilterBuilder::Create(filter_mode);
for (const url::Origin& origin : origins) {
std::string domain = GetDomainAndRegistry(
origin,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
if (domain.empty()) {
domain = origin.host();
}
cookies_filter_builder->AddRegisterableDomain(domain);
}
StartOperation(task, remover, BrowsingDataRemover::DATA_TYPE_COOKIES,
std::move(cookies_filter_builder));
void OnBrowsingDataRemoverDone(
content::BrowsingDataRemover::DataType failed_data_types) override {
if (failed_data_types == 0ULL) {
promise_.Resolve();
} else {
promise_.RejectWithErrorMessage(base::StringPrintf(
"Failed to clear browsing data (%" PRIu64 ")", failed_data_types));
}
// If cookies aren't the only data type and weren't handled above, then we
// can start an operation that is scoped to origins
if (data_type_mask) {
auto filter_builder =
BrowsingDataFilterBuilder::Create(filter_mode, origin_matching_mode);
for (auto const& origin : origins) {
filter_builder->AddOrigin(origin);
}
StartOperation(task, remover, data_type_mask, std::move(filter_builder));
}
// This static method counts as an operation.
task->OnOperationFinished(std::nullopt);
delete this;
}
private:
// An individiual |content::BrowsingDataRemover::Remove...| operation as part
// of a full |ClearDataTask|. This class manages its own lifetime, cleaning
// itself up after the operation completes and notifies the task of the
// result.
class ClearDataOperation : public BrowsingDataRemover::Observer {
public:
static void Run(std::shared_ptr<ClearDataTask> task,
BrowsingDataRemover* remover,
BrowsingDataRemover::DataType data_type_mask,
std::unique_ptr<BrowsingDataFilterBuilder> filter_builder) {
auto* operation = new ClearDataOperation(task, remover);
remover->RemoveWithFilterAndReply(base::Time::Min(), base::Time::Max(),
data_type_mask, kClearOriginTypeAll,
std::move(filter_builder), operation);
}
// BrowsingDataRemover::Observer:
void OnBrowsingDataRemoverDone(
BrowsingDataRemover::DataType failed_data_types) override {
task_->OnOperationFinished(failed_data_types);
delete this;
}
private:
ClearDataOperation(std::shared_ptr<ClearDataTask> task,
BrowsingDataRemover* remover)
: task_(task) {
observation_.Observe(remover);
}
std::shared_ptr<ClearDataTask> task_;
base::ScopedObservation<BrowsingDataRemover, BrowsingDataRemover::Observer>
observation_{this};
};
explicit ClearDataTask(gin_helper::Promise<void> promise)
: promise_(std::move(promise)) {}
static void StartOperation(
std::shared_ptr<ClearDataTask> task,
BrowsingDataRemover* remover,
BrowsingDataRemover::DataType data_type_mask,
std::unique_ptr<BrowsingDataFilterBuilder> filter_builder) {
// Track this operation
task->operations_running_ += 1;
ClearDataOperation::Run(task, remover, data_type_mask,
std::move(filter_builder));
}
void OnOperationFinished(
std::optional<BrowsingDataRemover::DataType> failed_data_types) {
DCHECK_GT(operations_running_, 0);
operations_running_ -= 1;
if (failed_data_types.has_value()) {
failed_data_types_ |= failed_data_types.value();
}
// If this is the last operation, then the task is finished
if (operations_running_ == 0) {
OnTaskFinished();
}
}
void OnTaskFinished() {
if (failed_data_types_ == 0ULL) {
promise_.Resolve();
} else {
v8::Isolate* isolate = promise_.isolate();
v8::Local<v8::Value> failed_data_types_array =
gin::ConvertToV8(isolate, GetDataTypesFromMask(failed_data_types_));
// Create a rich error object with extra detail about what data types
// failed
auto error = v8::Exception::Error(
gin::StringToV8(isolate, "Failed to clear data"));
error.As<v8::Object>()
->Set(promise_.GetContext(),
gin::StringToV8(isolate, "failedDataTypes"),
failed_data_types_array)
.Check();
promise_.Reject(error);
}
}
int operations_running_ = 0;
BrowsingDataRemover::DataType failed_data_types_ = 0ULL;
gin_helper::Promise<void> promise_;
base::ScopedObservation<content::BrowsingDataRemover,
content::BrowsingDataRemover::Observer>
observation_{this};
};
base::Value::Dict createProxyConfig(ProxyPrefs::ProxyMode proxy_mode,
@@ -1306,85 +1138,17 @@ v8::Local<v8::Promise> Session::ClearCodeCaches(
return handle;
}
v8::Local<v8::Value> Session::ClearData(gin_helper::ErrorThrower thrower,
gin::Arguments* args) {
v8::Local<v8::Promise> Session::ClearData(gin::Arguments* args) {
auto* isolate = JavascriptEnvironment::GetIsolate();
BrowsingDataRemover::DataType data_type_mask = kClearDataTypeAll;
std::vector<url::Origin> origins;
BrowsingDataFilterBuilder::OriginMatchingMode origin_matching_mode =
BrowsingDataFilterBuilder::OriginMatchingMode::kThirdPartiesIncluded;
BrowsingDataFilterBuilder::Mode filter_mode =
BrowsingDataFilterBuilder::Mode::kPreserve;
if (gin_helper::Dictionary options; args->GetNext(&options)) {
if (std::vector<std::string> data_types;
options.Get("dataTypes", &data_types)) {
data_type_mask = GetDataTypeMask(data_types);
}
if (bool avoid_closing_connections;
options.Get("avoidClosingConnections", &avoid_closing_connections) &&
avoid_closing_connections) {
data_type_mask |=
BrowsingDataRemover::DATA_TYPE_AVOID_CLOSING_CONNECTIONS;
}
std::vector<GURL> origin_urls;
{
bool has_origins_key = options.Get("origins", &origin_urls);
std::vector<GURL> exclude_origin_urls;
bool has_exclude_origins_key =
options.Get("excludeOrigins", &exclude_origin_urls);
if (has_origins_key && has_exclude_origins_key) {
thrower.ThrowError(
"Cannot provide both 'origins' and 'excludeOrigins'");
return v8::Undefined(isolate);
}
if (has_origins_key) {
filter_mode = BrowsingDataFilterBuilder::Mode::kDelete;
} else if (has_exclude_origins_key) {
origin_urls = std::move(exclude_origin_urls);
}
}
if (!origin_urls.empty()) {
origins.reserve(origin_urls.size());
for (const GURL& origin_url : origin_urls) {
auto origin = url::Origin::Create(origin_url);
// Opaque origins cannot be used with this API
if (origin.opaque()) {
thrower.ThrowError(
base::StringPrintf("Invalid origin: '%s'",
origin_url.possibly_invalid_spec().c_str()));
return v8::Undefined(isolate);
}
origins.push_back(std::move(origin));
}
}
if (std::string origin_matching_mode_string;
options.Get("originMatchingMode", &origin_matching_mode_string)) {
if (origin_matching_mode_string == "third-parties-included") {
origin_matching_mode = BrowsingDataFilterBuilder::OriginMatchingMode::
kThirdPartiesIncluded;
} else if (origin_matching_mode_string == "origin-in-all-contexts") {
origin_matching_mode =
BrowsingDataFilterBuilder::OriginMatchingMode::kOriginInAllContexts;
}
}
}
gin_helper::Promise<void> promise(isolate);
v8::Local<v8::Promise> promise_handle = promise.GetHandle();
BrowsingDataRemover* remover = browser_context_->GetBrowsingDataRemover();
ClearDataTask::Run(remover, std::move(promise), data_type_mask,
std::move(origins), filter_mode, origin_matching_mode);
content::BrowsingDataRemover* remover =
browser_context_->GetBrowsingDataRemover();
auto* observer = new ClearDataObserver(std::move(promise), remover);
remover->RemoveAndReply(base::Time::Min(), base::Time::Max(),
kClearDataTypeAll, kClearOriginTypeAll, observer);
return promise_handle;
}

View File

@@ -147,8 +147,7 @@ class Session : public gin::Wrappable<Session>,
v8::Local<v8::Value> GetPath(v8::Isolate* isolate);
void SetCodeCachePath(gin::Arguments* args);
v8::Local<v8::Promise> ClearCodeCaches(const gin_helper::Dictionary& options);
v8::Local<v8::Value> ClearData(gin_helper::ErrorThrower thrower,
gin::Arguments* args);
v8::Local<v8::Promise> ClearData(gin::Arguments* args);
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
base::Value GetSpellCheckerLanguages();
void SetSpellCheckerLanguages(gin_helper::ErrorThrower thrower,

View File

@@ -194,22 +194,6 @@ UtilityProcessWrapper::UtilityProcessWrapper(
connector_->set_connection_error_handler(base::BindOnce(
&UtilityProcessWrapper::CloseConnectorPort, weak_factory_.GetWeakPtr()));
mojo::PendingRemote<network::mojom::URLLoaderFactory> url_loader_factory;
network::mojom::URLLoaderFactoryParamsPtr loader_params =
network::mojom::URLLoaderFactoryParams::New();
loader_params->process_id = pid_;
loader_params->is_orb_enabled = false;
loader_params->is_trusted = true;
network::mojom::NetworkContext* network_context =
g_browser_process->system_network_context_manager()->GetContext();
network_context->CreateURLLoaderFactory(
url_loader_factory.InitWithNewPipeAndPassReceiver(),
std::move(loader_params));
params->url_loader_factory = std::move(url_loader_factory);
mojo::PendingRemote<network::mojom::HostResolver> host_resolver;
network_context->CreateHostResolver(
{}, host_resolver.InitWithNewPipeAndPassReceiver());
params->host_resolver = std::move(host_resolver);
node_service_remote_->Initialize(std::move(params));
}
@@ -250,6 +234,30 @@ void UtilityProcessWrapper::CloseConnectorPort() {
}
}
void UtilityProcessWrapper::EnsureNetworkServiceInitialized() {
if (network_context_initialized_)
return;
network_context_initialized_ = true;
mojo::PendingRemote<network::mojom::URLLoaderFactory> url_loader_factory;
network::mojom::URLLoaderFactoryParamsPtr loader_params =
network::mojom::URLLoaderFactoryParams::New();
loader_params->process_id = pid_;
loader_params->is_orb_enabled = false;
loader_params->is_trusted = true;
network::mojom::NetworkContext* network_context =
g_browser_process->system_network_context_manager()->GetContext();
network_context->CreateURLLoaderFactory(
url_loader_factory.InitWithNewPipeAndPassReceiver(),
std::move(loader_params));
mojo::PendingRemote<network::mojom::HostResolver> host_resolver;
network_context->CreateHostResolver(
{}, host_resolver.InitWithNewPipeAndPassReceiver());
node_service_remote_->InitializeNetworkService(std::move(url_loader_factory),
std::move(host_resolver));
}
void UtilityProcessWrapper::Shutdown(int exit_code) {
if (pid_ != base::kNullProcessId)
GetAllUtilityProcessWrappers().Remove(pid_);
@@ -431,6 +439,9 @@ void Initialize(v8::Local<v8::Object> exports,
v8::Isolate* isolate = context->GetIsolate();
gin_helper::Dictionary dict(isolate, exports);
dict.SetMethod("_fork", &electron::api::UtilityProcessWrapper::Create);
dict.SetMethod(
"_ensureNetworkServiceInitialized",
&electron::api::UtilityProcessWrapper::EnsureNetworkServiceInitialized);
}
} // namespace

View File

@@ -48,6 +48,7 @@ class UtilityProcessWrapper
static raw_ptr<UtilityProcessWrapper> FromProcessId(base::ProcessId pid);
void Shutdown(int exit_code);
void EnsureNetworkServiceInitialized();
// gin::Wrappable
static gin::WrapperInfo kWrapperInfo;
@@ -84,6 +85,7 @@ class UtilityProcessWrapper
int stdout_read_fd_ = -1;
int stderr_read_fd_ = -1;
bool connector_closed_ = false;
bool network_context_initialized_ = false;
std::unique_ptr<mojo::Connector> connector_;
blink::MessagePortDescriptor host_port_;
mojo::Remote<node::mojom::NodeService> node_service_remote_;

View File

@@ -1753,6 +1753,23 @@ void WebContents::RenderFrameHostChanged(content::RenderFrameHost* old_host,
if (new_host)
new_host->GetRenderWidgetHost()->AddInputEventObserver(this);
}
// During cross-origin navigation, a FrameTreeNode will swap out its RFH.
// If an instance of WebFrameMain exists, it will need to have its RFH
// swapped as well.
//
// |old_host| can be a nullptr so we use |new_host| for looking up the
// WebFrameMain instance.
auto* web_frame = WebFrameMain::FromRenderFrameHost(new_host);
if (web_frame) {
web_frame->UpdateRenderFrameHost(new_host);
}
}
void WebContents::FrameDeleted(int frame_tree_node_id) {
auto* web_frame = WebFrameMain::FromFrameTreeNodeId(frame_tree_node_id);
if (web_frame)
web_frame->Destroyed();
}
void WebContents::RenderViewDeleted(content::RenderViewHost* render_view_host) {

View File

@@ -628,6 +628,7 @@ class WebContents : public ExclusiveAccessContext,
void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
void RenderFrameHostChanged(content::RenderFrameHost* old_host,
content::RenderFrameHost* new_host) override;
void FrameDeleted(int frame_tree_node_id) override;
void RenderViewDeleted(content::RenderViewHost*) override;
void PrimaryMainFrameRenderProcessGone(
base::TerminationStatus status) override;

View File

@@ -55,32 +55,41 @@ struct Converter<blink::mojom::PageVisibilityState> {
namespace electron::api {
typedef std::unordered_map<blink::LocalFrameToken,
WebFrameMain*,
blink::LocalFrameToken::Hasher>
WebFrameMainIdMap;
typedef std::unordered_map<int, WebFrameMain*> WebFrameMainIdMap;
WebFrameMainIdMap& GetWebFrameMainMap() {
static base::NoDestructor<WebFrameMainIdMap> instance;
return *instance;
}
// static
WebFrameMain* WebFrameMain::FromFrameTreeNodeId(int frame_tree_node_id) {
WebFrameMainIdMap& frame_map = GetWebFrameMainMap();
auto iter = frame_map.find(frame_tree_node_id);
auto* web_frame = iter == frame_map.end() ? nullptr : iter->second;
return web_frame;
}
// static
WebFrameMain* WebFrameMain::FromRenderFrameHost(content::RenderFrameHost* rfh) {
if (!rfh)
return nullptr;
WebFrameMainIdMap& frame_map = GetWebFrameMainMap();
auto iter = frame_map.find(rfh->GetGlobalFrameToken().frame_token);
auto* web_frame = iter == frame_map.end() ? nullptr : iter->second;
return web_frame;
// TODO(codebytere): remove after refactoring away from FrameTreeNodeId as map
// key.
auto* ftn =
static_cast<content::RenderFrameHostImpl*>(rfh)->frame_tree_node();
if (!ftn)
return nullptr;
return FromFrameTreeNodeId(rfh->GetFrameTreeNodeId());
}
gin::WrapperInfo WebFrameMain::kWrapperInfo = {gin::kEmbedderNativeGin};
WebFrameMain::WebFrameMain(content::RenderFrameHost* rfh)
: rfh_token_(rfh->GetGlobalFrameToken()), render_frame_(rfh) {
GetWebFrameMainMap().emplace(rfh_token_.frame_token, this);
: frame_tree_node_id_(rfh->GetFrameTreeNodeId()), render_frame_(rfh) {
GetWebFrameMainMap().emplace(frame_tree_node_id_, this);
}
WebFrameMain::~WebFrameMain() {
@@ -89,7 +98,7 @@ WebFrameMain::~WebFrameMain() {
void WebFrameMain::Destroyed() {
MarkRenderFrameDisposed();
GetWebFrameMainMap().erase(rfh_token_.frame_token);
GetWebFrameMainMap().erase(frame_tree_node_id_);
Unpin();
}
@@ -261,7 +270,7 @@ void WebFrameMain::PostMessage(v8::Isolate* isolate,
}
int WebFrameMain::FrameTreeNodeID() const {
return 0;
return frame_tree_node_id_;
}
std::string WebFrameMain::Name() const {

View File

@@ -12,7 +12,6 @@
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/process/process.h"
#include "content/public/browser/global_routing_id.h"
#include "gin/handle.h"
#include "gin/wrappable.h"
#include "mojo/public/cpp/bindings/remote.h"
@@ -50,6 +49,7 @@ class WebFrameMain : public gin::Wrappable<WebFrameMain>,
static gin::Handle<WebFrameMain> FromOrNull(
v8::Isolate* isolate,
content::RenderFrameHost* render_frame_host);
static WebFrameMain* FromFrameTreeNodeId(int frame_tree_node_id);
static WebFrameMain* FromRenderFrameHost(
content::RenderFrameHost* render_frame_host);
@@ -125,7 +125,7 @@ class WebFrameMain : public gin::Wrappable<WebFrameMain>,
mojo::Remote<mojom::ElectronRenderer> renderer_api_;
mojo::PendingReceiver<mojom::ElectronRenderer> pending_receiver_;
content::GlobalRenderFrameHostToken rfh_token_;
int frame_tree_node_id_;
raw_ptr<content::RenderFrameHost> render_frame_ = nullptr;

View File

@@ -126,7 +126,7 @@ void BrowserProcessImpl::PreCreateThreads() {
// Must be created before the IOThread.
// Once IOThread class is no longer needed,
// this can be created on first use.
if (!SystemNetworkContextManager::GetInstance())
if (!SystemNetworkContextManager::HasInstance())
SystemNetworkContextManager::CreateInstance(local_state_.get());
}

View File

@@ -999,6 +999,10 @@ void ElectronBrowserClient::OnNetworkServiceCreated(
if (!g_browser_process)
return;
if (!SystemNetworkContextManager::HasInstance())
SystemNetworkContextManager::CreateInstance(
g_browser_process->local_state());
g_browser_process->system_network_context_manager()->OnNetworkServiceCreated(
network_service);
}

View File

@@ -22,7 +22,6 @@
#include "extensions/browser/api/scripting/scripting_utils.h"
#include "extensions/browser/extension_api_frame_id_map.h"
#include "extensions/browser/extension_file_task_runner.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_user_script_loader.h"
#include "extensions/browser/extension_util.h"
@@ -1062,17 +1061,6 @@ void ScriptingRegisterContentScriptsFunction::OnContentScriptFilesValidated(
return;
}
// We cannot proceed if the extension is uninstalled or unloaded in the middle
// of validating its script files.
ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
if (!extension() ||
!registry->enabled_extensions().Contains(extension_id())) {
// Note: a Respond() is not needed if the system is shutting down or if the
// extension is no longer enabled.
Release(); // Matches the `AddRef()` in `Run()`.
return;
}
auto error = std::move(result.second);
auto scripts = std::move(result.first);
ExtensionUserScriptLoader* loader =
@@ -1318,17 +1306,6 @@ void ScriptingUpdateContentScriptsFunction::OnContentScriptFilesValidated(
return;
}
// We cannot proceed if the extension is uninstalled or unloaded in the middle
// of validating its script files.
ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
if (!extension() ||
!registry->enabled_extensions().Contains(extension_id())) {
// Note: a Respond() is not needed if the system is shutting down or if the
// extension is no longer enabled.
Release(); // Matches the `AddRef()` in `Run()`.
return;
}
auto error = std::move(result.second);
auto scripts = std::move(result.first);
ExtensionUserScriptLoader* loader =

View File

@@ -214,8 +214,21 @@ SystemNetworkContextManager* SystemNetworkContextManager::CreateInstance(
return g_system_network_context_manager;
}
// static
bool SystemNetworkContextManager::HasInstance() {
return !!g_system_network_context_manager;
}
// static
SystemNetworkContextManager* SystemNetworkContextManager::GetInstance() {
if (!g_system_network_context_manager) {
// Initialize the network service, which will trigger
// ElectronBrowserClient::OnNetworkServiceCreated(), which calls
// CreateInstance() to initialize |g_system_network_context_manager|.
content::GetNetworkService();
DCHECK(g_system_network_context_manager);
}
return g_system_network_context_manager;
}
@@ -223,6 +236,7 @@ SystemNetworkContextManager* SystemNetworkContextManager::GetInstance() {
void SystemNetworkContextManager::DeleteInstance() {
DCHECK(g_system_network_context_manager);
delete g_system_network_context_manager;
g_system_network_context_manager = nullptr;
}
// c.f.

View File

@@ -41,11 +41,16 @@ class SystemNetworkContextManager {
SystemNetworkContextManager& operator=(const SystemNetworkContextManager&) =
delete;
// Checks if the global SystemNetworkContextManager has been created.
static bool HasInstance();
// Creates the global instance of SystemNetworkContextManager. If an
// instance already exists, this will cause a DCHECK failure.
static SystemNetworkContextManager* CreateInstance(PrefService* pref_service);
// Gets the global SystemNetworkContextManager instance.
// Gets the global SystemNetworkContextManager instance. If it has not been
// created yet, NetworkService is called, which will cause the
// SystemNetworkContextManager to be created.
static SystemNetworkContextManager* GetInstance();
// Destroys the global SystemNetworkContextManager instance.

View File

@@ -159,21 +159,21 @@ void LibnotifyNotification::Show(const NotificationOptions& options) {
void LibnotifyNotification::Dismiss() {
if (!notification_) {
Destroy();
return;
}
GError* error = nullptr;
on_dismissing_ = true;
libnotify_loader_.notify_notification_close(notification_, &error);
if (error) {
log_and_clear_error(error, "notify_notification_close");
Destroy();
}
on_dismissing_ = false;
}
void LibnotifyNotification::OnNotificationClosed(
NotifyNotification* notification) {
NotificationDismissed(!on_dismissing_);
NotificationDismissed();
}
void LibnotifyNotification::OnNotificationView(NotifyNotification* notification,

View File

@@ -33,7 +33,6 @@ class LibnotifyNotification : public Notification {
RAW_PTR_EXCLUSION NotifyNotification* notification_ = nullptr;
ScopedGSignal signal_;
bool on_dismissing_ = false;
};
} // namespace electron

View File

@@ -35,9 +35,7 @@ std::unique_ptr<content::SerialChooser> ElectronSerialDelegate::RunChooser(
if (controller) {
DeleteControllerForFrame(frame);
}
AddControllerForFrame(frame, std::move(filters),
std::move(allowed_bluetooth_service_class_ids),
std::move(callback));
AddControllerForFrame(frame, std::move(filters), std::move(callback));
// Return a nullptr because the return value isn't used for anything, eg
// there is no mechanism to cancel navigator.serial.requestPort(). The return
@@ -108,14 +106,12 @@ SerialChooserController* ElectronSerialDelegate::ControllerForFrame(
SerialChooserController* ElectronSerialDelegate::AddControllerForFrame(
content::RenderFrameHost* render_frame_host,
std::vector<blink::mojom::SerialPortFilterPtr> filters,
std::vector<device::BluetoothUUID> allowed_bluetooth_service_class_ids,
content::SerialChooser::Callback callback) {
auto* web_contents =
content::WebContents::FromRenderFrameHost(render_frame_host);
auto controller = std::make_unique<SerialChooserController>(
render_frame_host, std::move(filters),
std::move(allowed_bluetooth_service_class_ids), std::move(callback),
web_contents, weak_factory_.GetWeakPtr());
render_frame_host, std::move(filters), std::move(callback), web_contents,
weak_factory_.GetWeakPtr());
controller_map_.insert(
std::make_pair(render_frame_host, std::move(controller)));
return ControllerForFrame(render_frame_host);

View File

@@ -67,7 +67,6 @@ class ElectronSerialDelegate : public content::SerialDelegate,
SerialChooserController* AddControllerForFrame(
content::RenderFrameHost* render_frame_host,
std::vector<blink::mojom::SerialPortFilterPtr> filters,
std::vector<device::BluetoothUUID> allowed_bluetooth_service_class_ids,
content::SerialChooser::Callback callback);
base::ScopedObservation<SerialChooserContext,

View File

@@ -11,9 +11,6 @@
#include "base/functional/bind.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
#include "services/device/public/cpp/bluetooth/bluetooth_utils.h"
#include "services/device/public/mojom/serial.mojom.h"
#include "shell/browser/api/electron_api_session.h"
#include "shell/browser/serial/serial_chooser_context.h"
#include "shell/browser/serial/serial_chooser_context_factory.h"
@@ -61,57 +58,14 @@ struct Converter<device::mojom::SerialPortInfoPtr> {
namespace electron {
namespace {
using ::device::mojom::SerialPortType;
bool FilterMatchesPort(const blink::mojom::SerialPortFilter& filter,
const device::mojom::SerialPortInfo& port) {
if (filter.bluetooth_service_class_id) {
if (!port.bluetooth_service_class_id) {
return false;
}
return device::BluetoothUUID(*port.bluetooth_service_class_id) ==
device::BluetoothUUID(*filter.bluetooth_service_class_id);
}
if (!filter.has_vendor_id) {
return true;
}
if (!port.has_vendor_id || port.vendor_id != filter.vendor_id) {
return false;
}
if (!filter.has_product_id) {
return true;
}
return port.has_product_id && port.product_id == filter.product_id;
}
bool BluetoothPortIsAllowed(
const std::vector<::device::BluetoothUUID>& allowed_ids,
const device::mojom::SerialPortInfo& port) {
if (!port.bluetooth_service_class_id) {
return true;
}
// Serial Port Profile is allowed by default.
if (*port.bluetooth_service_class_id == device::GetSerialPortProfileUUID()) {
return true;
}
return base::Contains(allowed_ids, port.bluetooth_service_class_id.value());
}
} // namespace
SerialChooserController::SerialChooserController(
content::RenderFrameHost* render_frame_host,
std::vector<blink::mojom::SerialPortFilterPtr> filters,
std::vector<::device::BluetoothUUID> allowed_bluetooth_service_class_ids,
content::SerialChooser::Callback callback,
content::WebContents* web_contents,
base::WeakPtr<ElectronSerialDelegate> serial_delegate)
: WebContentsObserver(web_contents),
filters_(std::move(filters)),
allowed_bluetooth_service_class_ids_(
std::move(allowed_bluetooth_service_class_ids)),
callback_(std::move(callback)),
serial_delegate_(serial_delegate),
render_frame_host_id_(render_frame_host->GetGlobalId()) {
@@ -139,11 +93,10 @@ api::Session* SerialChooserController::GetSession() {
void SerialChooserController::OnPortAdded(
const device::mojom::SerialPortInfo& port) {
if (!DisplayDevice(port))
if (!FilterMatchesAny(port))
return;
ports_.push_back(port.Clone());
api::Session* session = GetSession();
if (session) {
session->Emit("serial-port-added", port.Clone(), web_contents());
@@ -152,8 +105,9 @@ void SerialChooserController::OnPortAdded(
void SerialChooserController::OnPortRemoved(
const device::mojom::SerialPortInfo& port) {
const auto it = base::ranges::find(ports_, port.token,
&device::mojom::SerialPortInfo::token);
const auto it = std::find_if(
ports_.begin(), ports_.end(),
[&port](const auto& ptr) { return ptr->token == port.token; });
if (it != ports_.end()) {
api::Session* session = GetSession();
if (session) {
@@ -198,7 +152,7 @@ void SerialChooserController::OnGetDevices(
});
for (auto& port : ports) {
if (DisplayDevice(*port))
if (FilterMatchesAny(*port))
ports_.push_back(std::move(port));
}
@@ -215,17 +169,21 @@ void SerialChooserController::OnGetDevices(
}
}
bool SerialChooserController::DisplayDevice(
bool SerialChooserController::FilterMatchesAny(
const device::mojom::SerialPortInfo& port) const {
if (filters_.empty()) {
return BluetoothPortIsAllowed(allowed_bluetooth_service_class_ids_, port);
}
if (filters_.empty())
return true;
for (const auto& filter : filters_) {
if (FilterMatchesPort(*filter, port) &&
BluetoothPortIsAllowed(allowed_bluetooth_service_class_ids_, port)) {
return true;
if (filter->has_vendor_id &&
(!port.has_vendor_id || filter->vendor_id != port.vendor_id)) {
continue;
}
if (filter->has_product_id &&
(!port.has_product_id || filter->product_id != port.product_id)) {
continue;
}
return true;
}
return false;

View File

@@ -34,7 +34,6 @@ class SerialChooserController final : public SerialChooserContext::PortObserver,
SerialChooserController(
content::RenderFrameHost* render_frame_host,
std::vector<blink::mojom::SerialPortFilterPtr> filters,
std::vector<::device::BluetoothUUID> allowed_bluetooth_service_class_ids,
content::SerialChooser::Callback callback,
content::WebContents* web_contents,
base::WeakPtr<ElectronSerialDelegate> serial_delegate);
@@ -56,12 +55,11 @@ class SerialChooserController final : public SerialChooserContext::PortObserver,
private:
api::Session* GetSession();
void OnGetDevices(std::vector<device::mojom::SerialPortInfoPtr> ports);
bool DisplayDevice(const device::mojom::SerialPortInfo& port) const;
bool FilterMatchesAny(const device::mojom::SerialPortInfo& port) const;
void RunCallback(device::mojom::SerialPortInfoPtr port);
void OnDeviceChosen(const std::string& port_id);
std::vector<blink::mojom::SerialPortFilterPtr> filters_;
std::vector<::device::BluetoothUUID> allowed_bluetooth_service_class_ids_;
content::SerialChooser::Callback callback_;
url::Origin origin_;

View File

@@ -28,7 +28,6 @@
#include "net/socket/stream_socket.h"
#include "net/socket/tcp_server_socket.h"
#include "shell/browser/browser.h"
#include "shell/browser/electron_browser_context.h"
#include "shell/common/electron_paths.h"
#include "third_party/inspector_protocol/crdtp/dispatch.h"
#include "ui/base/resource/resource_bundle.h"
@@ -140,8 +139,4 @@ bool DevToolsManagerDelegate::HasBundledFrontendResources() {
return true;
}
content::BrowserContext* DevToolsManagerDelegate::GetDefaultBrowserContext() {
return ElectronBrowserContext::From("", false);
}
} // namespace electron

View File

@@ -10,10 +10,6 @@
#include "base/compiler_specific.h"
#include "content/public/browser/devtools_manager_delegate.h"
namespace content {
class BrowserContext;
}
namespace electron {
class DevToolsManagerDelegate : public content::DevToolsManagerDelegate {
@@ -37,7 +33,6 @@ class DevToolsManagerDelegate : public content::DevToolsManagerDelegate {
TargetType target_type) override;
std::string GetDiscoveryPageHTML() override;
bool HasBundledFrontendResources() override;
content::BrowserContext* GetDefaultBrowserContext() override;
};
} // namespace electron

View File

@@ -39,14 +39,6 @@
"contexts": ["blessed_extension", "unblessed_extension", "content_script"],
"max_manifest_version": 2
},
"extension.lastError": {
"contexts": [
"blessed_extension",
"unblessed_extension",
"content_script"
],
"max_manifest_version": 2
},
"i18n": {
"channel": "stable",
"extension_types": ["extension"],
@@ -76,10 +68,5 @@
"scripting": {
"dependencies": ["permission:scripting"],
"contexts": ["blessed_extension"]
},
"scripting.globalParams": {
"channel": "trunk",
"dependencies": ["permission:scripting"],
"contexts": ["content_script"]
}
}

View File

@@ -132,9 +132,8 @@ template <typename ReturnType, typename... ArgTypes>
struct NativeFunctionInvoker<ReturnType(ArgTypes...)> {
static void Go(base::RepeatingCallback<ReturnType(ArgTypes...)> val,
gin::Arguments* args) {
using Indices = std::index_sequence_for<ArgTypes...>;
Invoker<Indices, ArgTypes...> invoker(args,
{.holder_is_first_argument = false});
using Indices = typename IndicesGenerator<sizeof...(ArgTypes)>::type;
Invoker<Indices, ArgTypes...> invoker(args, 0);
if (invoker.IsOK())
invoker.DispatchToCallback(val);
}

View File

@@ -26,8 +26,7 @@ inline WrappableBase* InvokeFactory(
gin::Arguments* args,
const base::RepeatingCallback<WrappableBase*(P1)>& callback) {
typename CallbackParamTraits<P1>::LocalType a1;
if (!gin_helper::GetNextArgument(args, {.holder_is_first_argument = true}, 0,
&a1))
if (!gin_helper::GetNextArgument(args, 0, true, &a1))
return nullptr;
return callback.Run(a1);
}
@@ -38,10 +37,8 @@ inline WrappableBase* InvokeFactory(
const base::RepeatingCallback<WrappableBase*(P1, P2)>& callback) {
typename CallbackParamTraits<P1>::LocalType a1;
typename CallbackParamTraits<P2>::LocalType a2;
if (!gin_helper::GetNextArgument(args, {.holder_is_first_argument = true}, 0,
&a1) ||
!gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
&a2))
if (!gin_helper::GetNextArgument(args, 0, true, &a1) ||
!gin_helper::GetNextArgument(args, 0, false, &a2))
return nullptr;
return callback.Run(a1, a2);
}
@@ -53,12 +50,9 @@ inline WrappableBase* InvokeFactory(
typename CallbackParamTraits<P1>::LocalType a1;
typename CallbackParamTraits<P2>::LocalType a2;
typename CallbackParamTraits<P3>::LocalType a3;
if (!gin_helper::GetNextArgument(args, {.holder_is_first_argument = true}, 0,
&a1) ||
!gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
&a2) ||
!gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
&a3))
if (!gin_helper::GetNextArgument(args, 0, true, &a1) ||
!gin_helper::GetNextArgument(args, 0, false, &a2) ||
!gin_helper::GetNextArgument(args, 0, false, &a3))
return nullptr;
return callback.Run(a1, a2, a3);
}
@@ -71,14 +65,10 @@ inline WrappableBase* InvokeFactory(
typename CallbackParamTraits<P2>::LocalType a2;
typename CallbackParamTraits<P3>::LocalType a3;
typename CallbackParamTraits<P4>::LocalType a4;
if (!gin_helper::GetNextArgument(args, {.holder_is_first_argument = true}, 0,
&a1) ||
!gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
&a2) ||
!gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
&a3) ||
!gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
&a4))
if (!gin_helper::GetNextArgument(args, 0, true, &a1) ||
!gin_helper::GetNextArgument(args, 0, false, &a2) ||
!gin_helper::GetNextArgument(args, 0, false, &a3) ||
!gin_helper::GetNextArgument(args, 0, false, &a4))
return nullptr;
return callback.Run(a1, a2, a3, a4);
}
@@ -93,16 +83,11 @@ inline WrappableBase* InvokeFactory(
typename CallbackParamTraits<P3>::LocalType a3;
typename CallbackParamTraits<P4>::LocalType a4;
typename CallbackParamTraits<P5>::LocalType a5;
if (!gin_helper::GetNextArgument(args, {.holder_is_first_argument = true}, 0,
&a1) ||
!gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
&a2) ||
!gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
&a3) ||
!gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
&a4) ||
!gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
&a5))
if (!gin_helper::GetNextArgument(args, 0, true, &a1) ||
!gin_helper::GetNextArgument(args, 0, false, &a2) ||
!gin_helper::GetNextArgument(args, 0, false, &a3) ||
!gin_helper::GetNextArgument(args, 0, false, &a4) ||
!gin_helper::GetNextArgument(args, 0, false, &a5))
return nullptr;
return callback.Run(a1, a2, a3, a4, a5);
}
@@ -123,18 +108,12 @@ inline WrappableBase* InvokeFactory(
typename CallbackParamTraits<P4>::LocalType a4;
typename CallbackParamTraits<P5>::LocalType a5;
typename CallbackParamTraits<P6>::LocalType a6;
if (!gin_helper::GetNextArgument(args, {.holder_is_first_argument = true}, 0,
&a1) ||
!gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
&a2) ||
!gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
&a3) ||
!gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
&a4) ||
!gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
&a5) ||
!gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
&a6))
if (!gin_helper::GetNextArgument(args, 0, true, &a1) ||
!gin_helper::GetNextArgument(args, 0, false, &a2) ||
!gin_helper::GetNextArgument(args, 0, false, &a3) ||
!gin_helper::GetNextArgument(args, 0, false, &a4) ||
!gin_helper::GetNextArgument(args, 0, false, &a5) ||
!gin_helper::GetNextArgument(args, 0, false, &a6))
return nullptr;
return callback.Run(a1, a2, a3, a4, a5, a6);
}

View File

@@ -4,34 +4,10 @@
#include "shell/common/gin_helper/function_template.h"
#include "base/observer_list.h"
#include "base/strings/strcat.h"
namespace gin_helper {
CallbackHolderBase::DisposeObserver::DisposeObserver(
gin::PerIsolateData* per_isolate_data,
CallbackHolderBase* holder)
: per_isolate_data_(per_isolate_data), holder_(*holder) {
if (per_isolate_data_)
per_isolate_data_->AddDisposeObserver(this);
}
CallbackHolderBase::DisposeObserver::~DisposeObserver() {
if (per_isolate_data_)
per_isolate_data_->RemoveDisposeObserver(this);
}
void CallbackHolderBase::DisposeObserver::OnBeforeDispose(
v8::Isolate* isolate) {
holder_->v8_ref_.Reset();
}
void CallbackHolderBase::DisposeObserver::OnDisposed() {
// The holder contains the observer, so the observer is destroyed here also.
delete &holder_.get();
}
CallbackHolderBase::CallbackHolderBase(v8::Isolate* isolate)
: v8_ref_(isolate, v8::External::New(isolate, this)),
dispose_observer_(gin::PerIsolateData::From(isolate), this) {
: v8_ref_(isolate, v8::External::New(isolate, this)) {
v8_ref_.SetWeak(this, &CallbackHolderBase::FirstWeakCallback,
v8::WeakCallbackType::kParameter);
}
@@ -57,28 +33,4 @@ void CallbackHolderBase::SecondWeakCallback(
delete data.GetParameter();
}
void ThrowConversionError(gin::Arguments* args,
const InvokerOptions& invoker_options,
size_t index) {
if (index == 0 && invoker_options.holder_is_first_argument) {
// Failed to get the appropriate `this` object. This can happen if a
// method is invoked using Function.prototype.[call|apply] and passed an
// invalid (or null) `this` argument.
std::string error =
invoker_options.holder_type
? base::StrCat({"Illegal invocation: Function must be "
"called on an object of type ",
invoker_options.holder_type})
: "Illegal invocation";
args->ThrowTypeError(error);
} else {
// Otherwise, this failed parsing on a different argument.
// Arguments::ThrowError() will try to include appropriate information.
// Ideally we would include the expected c++ type in the error message
// here, too (which we can access via typeid(ArgType).name()), however we
// compile with no-rtti, which disables typeid.
args->ThrowError();
}
}
} // namespace gin_helper

View File

@@ -12,7 +12,6 @@
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "gin/arguments.h"
#include "gin/per_isolate_data.h"
#include "shell/common/gin_helper/arguments.h"
#include "shell/common/gin_helper/destroyable.h"
#include "shell/common/gin_helper/error_thrower.h"
@@ -26,9 +25,8 @@
namespace gin_helper {
struct InvokerOptions {
bool holder_is_first_argument = false;
const char* holder_type = nullptr; // Null if unknown or not applicable.
enum CreateFunctionTemplateFlags {
HolderIsFirstArgument = 1 << 0,
};
template <typename T>
@@ -45,91 +43,64 @@ struct CallbackParamTraits<const T*> {
};
// CallbackHolder and CallbackHolderBase are used to pass a
// base::RepeatingCallback from CreateFunctionTemplate through v8 (via
// v8::FunctionTemplate) to DispatchToCallback, where it is invoked.
// CallbackHolder will clean up the callback in two different scenarios:
// - If the garbage collector finds that it's garbage and collects it. (But note
// that even _if_ we become garbage, we might never get collected!)
// - If the isolate gets disposed.
//
// TODO(crbug.com/1285119): When gin::Wrappable gets migrated over to using
// cppgc, this class should also be considered for migration.
// base::RepeatingCallback from
// CreateFunctionTemplate through v8 (via v8::FunctionTemplate) to
// DispatchToCallback, where it is invoked.
// This simple base class is used so that we can share a single object template
// among every CallbackHolder instance.
class CallbackHolderBase {
public:
v8::Local<v8::External> GetHandle(v8::Isolate* isolate);
// disable copy
CallbackHolderBase(const CallbackHolderBase&) = delete;
CallbackHolderBase& operator=(const CallbackHolderBase&) = delete;
v8::Local<v8::External> GetHandle(v8::Isolate* isolate);
protected:
explicit CallbackHolderBase(v8::Isolate* isolate);
virtual ~CallbackHolderBase();
private:
class DisposeObserver : gin::PerIsolateData::DisposeObserver {
public:
DisposeObserver(gin::PerIsolateData* per_isolate_data,
CallbackHolderBase* holder);
~DisposeObserver() override;
void OnBeforeDispose(v8::Isolate* isolate) override;
void OnDisposed() override;
private:
// Unlike in Chromium, it's possible for PerIsolateData to be null
// for a given isolate - e.g. in a Node.js Worker. Thus this
// needs to be a raw_ptr instead of a raw_ref.
const raw_ptr<gin::PerIsolateData> per_isolate_data_;
const raw_ref<CallbackHolderBase> holder_;
};
static void FirstWeakCallback(
const v8::WeakCallbackInfo<CallbackHolderBase>& data);
static void SecondWeakCallback(
const v8::WeakCallbackInfo<CallbackHolderBase>& data);
v8::Global<v8::External> v8_ref_;
DisposeObserver dispose_observer_;
};
template <typename Sig>
class CallbackHolder : public CallbackHolderBase {
public:
CallbackHolder(v8::Isolate* isolate,
base::RepeatingCallback<Sig> callback,
InvokerOptions invoker_options)
: CallbackHolderBase(isolate),
callback(std::move(callback)),
invoker_options(std::move(invoker_options)) {}
CallbackHolder(const CallbackHolder&) = delete;
CallbackHolder& operator=(const CallbackHolder&) = delete;
const base::RepeatingCallback<Sig>& callback,
int flags)
: CallbackHolderBase(isolate), callback(callback), flags(flags) {}
base::RepeatingCallback<Sig> callback;
InvokerOptions invoker_options;
int flags = 0;
private:
~CallbackHolder() override = default;
virtual ~CallbackHolder() = default;
};
template <typename T>
bool GetNextArgument(gin::Arguments* args,
const InvokerOptions& invoker_options,
int create_flags,
bool is_first,
T* result) {
if (is_first && invoker_options.holder_is_first_argument) {
if (is_first && (create_flags & HolderIsFirstArgument) != 0) {
return args->GetHolder(result);
} else {
return args->GetNext(result);
}
}
// Electron-specific GetNextArgument that supports std::optional.
// Support std::optional as output, which would be empty and do not throw error
// when conversion to T fails.
template <typename T>
bool GetNextArgument(gin::Arguments* args,
const InvokerOptions& invoker_options,
int create_flags,
bool is_first,
std::optional<T>* result) {
T converted;
@@ -139,36 +110,10 @@ bool GetNextArgument(gin::Arguments* args,
return true;
}
// Electron-specific GetNextArgument that supports ErrorThrower.
inline bool GetNextArgument(gin::Arguments* args,
const InvokerOptions& invoker_options,
bool is_first,
ErrorThrower* result) {
*result = ErrorThrower(args->isolate());
return true;
}
// Electron-specific GetNextArgument that supports the gin_helper::Arguments.
inline bool GetNextArgument(gin::Arguments* args,
const InvokerOptions& invoker_options,
bool is_first,
gin_helper::Arguments** result) {
*result = static_cast<gin_helper::Arguments*>(args);
return true;
}
// For advanced use cases, we allow callers to request the unparsed Arguments
// object and poke around in it directly.
inline bool GetNextArgument(gin::Arguments* args,
const InvokerOptions& invoker_options,
bool is_first,
gin::Arguments* result) {
*result = *args;
return true;
}
inline bool GetNextArgument(gin::Arguments* args,
const InvokerOptions& invoker_options,
int create_flags,
bool is_first,
gin::Arguments** result) {
*result = args;
@@ -177,65 +122,71 @@ inline bool GetNextArgument(gin::Arguments* args,
// It's common for clients to just need the isolate, so we make that easy.
inline bool GetNextArgument(gin::Arguments* args,
const InvokerOptions& invoker_options,
int create_flags,
bool is_first,
v8::Isolate** result) {
*result = args->isolate();
return true;
}
// Throws an error indicating conversion failure.
void ThrowConversionError(gin::Arguments* args,
const InvokerOptions& invoker_options,
size_t index);
// Allow clients to pass a util::Error to throw errors if they
// don't need the full gin::Arguments
inline bool GetNextArgument(gin::Arguments* args,
int create_flags,
bool is_first,
ErrorThrower* result) {
*result = ErrorThrower(args->isolate());
return true;
}
// Supports the gin_helper::Arguments.
inline bool GetNextArgument(gin::Arguments* args,
int create_flags,
bool is_first,
gin_helper::Arguments** result) {
*result = static_cast<gin_helper::Arguments*>(args);
return true;
}
// Classes for generating and storing an argument pack of integer indices
// (based on well-known "indices trick", see: http://goo.gl/bKKojn):
template <size_t... indices>
struct IndicesHolder {};
template <size_t requested_index, size_t... indices>
struct IndicesGenerator {
using type = typename IndicesGenerator<requested_index - 1,
requested_index - 1,
indices...>::type;
};
template <size_t... indices>
struct IndicesGenerator<0, indices...> {
using type = IndicesHolder<indices...>;
};
// Class template for extracting and storing single argument for callback
// at position |index|.
template <size_t index, typename ArgType, typename = void>
template <size_t index, typename ArgType>
struct ArgumentHolder {
using ArgLocalType = typename CallbackParamTraits<ArgType>::LocalType;
ArgLocalType value;
bool ok = false;
ArgumentHolder(gin::Arguments* args, const InvokerOptions& invoker_options) {
ArgumentHolder(gin::Arguments* args, int create_flags) {
v8::Local<v8::Object> holder;
if (index == 0 && invoker_options.holder_is_first_argument &&
if (index == 0 && (create_flags & HolderIsFirstArgument) &&
args->GetHolder(&holder) &&
gin_helper::Destroyable::IsDestroyed(holder)) {
args->ThrowTypeError("Object has been destroyed");
return;
}
ok = GetNextArgument(args, invoker_options, index == 0, &value);
ok = GetNextArgument(args, create_flags, index == 0, &value);
if (!ok) {
ThrowConversionError(args, invoker_options, index);
}
}
};
// This is required for types such as v8::LocalVector<T>, which don't have
// a default constructor. To create an element of such a type, the isolate
// has to be provided.
template <size_t index, typename ArgType>
struct ArgumentHolder<
index,
ArgType,
std::enable_if_t<!std::is_default_constructible_v<
typename CallbackParamTraits<ArgType>::LocalType> &&
std::is_constructible_v<
typename CallbackParamTraits<ArgType>::LocalType,
v8::Isolate*>>> {
using ArgLocalType = typename CallbackParamTraits<ArgType>::LocalType;
ArgLocalType value;
bool ok;
ArgumentHolder(gin::Arguments* args, const InvokerOptions& invoker_options)
: value(args->isolate()),
ok(GetNextArgument(args, invoker_options, index == 0, &value)) {
if (!ok) {
ThrowConversionError(args, invoker_options, index);
// Ideally we would include the expected c++ type in the error
// message which we can access via typeid(ArgType).name()
// however we compile with no-rtti, which disables typeid.
args->ThrowError();
}
}
};
@@ -243,19 +194,24 @@ struct ArgumentHolder<
// Class template for converting arguments from JavaScript to C++ and running
// the callback with them.
template <typename IndicesType, typename... ArgTypes>
class Invoker;
class Invoker {};
template <size_t... indices, typename... ArgTypes>
class Invoker<std::index_sequence<indices...>, ArgTypes...>
class Invoker<IndicesHolder<indices...>, ArgTypes...>
: public ArgumentHolder<indices, ArgTypes>... {
public:
// Invoker<> inherits from ArgumentHolder<> for each argument.
// C++ has always been strict about the class initialization order,
// so it is guaranteed ArgumentHolders will be initialized (and thus, will
// extract arguments from Arguments) in the right order.
Invoker(gin::Arguments* args, const InvokerOptions& invoker_options)
: ArgumentHolder<indices, ArgTypes>(args, invoker_options)...,
args_(args) {}
Invoker(gin::Arguments* args, int create_flags)
: ArgumentHolder<indices, ArgTypes>(args, create_flags)..., args_(args) {
// GCC thinks that create_flags is going unused, even though the
// expansion above clearly makes use of it. Per jyasskin@, casting
// to void is the commonly accepted way to convince the compiler
// that you're actually using a parameter/variable.
(void)create_flags;
}
bool IsOK() { return And(ArgumentHolder<indices, ArgTypes>::ok...); }
@@ -296,89 +252,46 @@ struct Dispatcher {};
template <typename ReturnType, typename... ArgTypes>
struct Dispatcher<ReturnType(ArgTypes...)> {
static void DispatchToCallbackImpl(gin::Arguments* args) {
static void DispatchToCallback(
const v8::FunctionCallbackInfo<v8::Value>& info) {
gin::Arguments args(info);
v8::Local<v8::External> v8_holder;
CHECK(args->GetData(&v8_holder));
args.GetData(&v8_holder);
CallbackHolderBase* holder_base =
reinterpret_cast<CallbackHolderBase*>(v8_holder->Value());
typedef CallbackHolder<ReturnType(ArgTypes...)> HolderT;
HolderT* holder = static_cast<HolderT*>(holder_base);
using Indices = std::index_sequence_for<ArgTypes...>;
Invoker<Indices, ArgTypes...> invoker(args, holder->invoker_options);
using Indices = typename IndicesGenerator<sizeof...(ArgTypes)>::type;
Invoker<Indices, ArgTypes...> invoker(&args, holder->flags);
if (invoker.IsOK())
invoker.DispatchToCallback(holder->callback);
}
static void DispatchToCallback(
const v8::FunctionCallbackInfo<v8::Value>& info) {
gin::Arguments args(info);
DispatchToCallbackImpl(&args);
}
static void DispatchToCallbackForProperty(
v8::Local<v8::Name>,
const v8::PropertyCallbackInfo<v8::Value>& info) {
gin::Arguments args(info);
DispatchToCallbackImpl(&args);
}
};
// CreateFunctionTemplate creates a v8::FunctionTemplate that will create
// JavaScript functions that execute a provided C++ function or
// base::RepeatingCallback. JavaScript arguments are automatically converted via
// gin::Converter, as is the return value of the C++ function, if any.
// |invoker_options| contains additional parameters. If it contains a
// holder_type, it will be used to provide a useful conversion error if the
// holder is the first argument. If not provided, a generic invocation error
// will be used.
// base::RepeatingCallback.
// JavaScript arguments are automatically converted via gin::Converter, as is
// the return value of the C++ function, if any.
//
// NOTE: V8 caches FunctionTemplates for a lifetime of a web page for its own
// internal reasons, thus it is generally a good idea to cache the template
// returned by this function. Otherwise, repeated method invocations from JS
// will create substantial memory leaks. See http://crbug.com/463487.
//
// The callback will be destroyed if either the function template gets garbage
// collected or _after_ the isolate is disposed. Garbage collection can never be
// relied upon. As such, any destructors for objects bound to the callback must
// not depend on the isolate being alive at the point they are called. The order
// in which callbacks are destroyed is not guaranteed.
template <typename Sig>
v8::Local<v8::FunctionTemplate> CreateFunctionTemplate(
v8::Isolate* isolate,
base::RepeatingCallback<Sig> callback,
InvokerOptions invoker_options = {}) {
const base::RepeatingCallback<Sig> callback,
int callback_flags = 0) {
typedef CallbackHolder<Sig> HolderT;
HolderT* holder =
new HolderT(isolate, std::move(callback), std::move(invoker_options));
HolderT* holder = new HolderT(isolate, callback, callback_flags);
v8::Local<v8::FunctionTemplate> tmpl = v8::FunctionTemplate::New(
isolate, &Dispatcher<Sig>::DispatchToCallback,
gin::ConvertToV8<v8::Local<v8::External>>(isolate,
holder->GetHandle(isolate)),
v8::Local<v8::Signature>(), 0, v8::ConstructorBehavior::kAllow);
return tmpl;
}
// CreateDataPropertyCallback creates a v8::AccessorNameGetterCallback and
// corresponding data value that will hold and execute the provided
// base::RepeatingCallback, using automatic conversions similar to
// |CreateFunctionTemplate|.
//
// It is expected that these will be passed to v8::Template::SetLazyDataProperty
// or another similar function.
template <typename Sig>
std::pair<v8::AccessorNameGetterCallback, v8::Local<v8::Value>>
CreateDataPropertyCallback(v8::Isolate* isolate,
base::RepeatingCallback<Sig> callback,
InvokerOptions invoker_options = {}) {
typedef CallbackHolder<Sig> HolderT;
HolderT* holder =
new HolderT(isolate, std::move(callback), std::move(invoker_options));
return {&Dispatcher<Sig>::DispatchToCallbackForProperty,
gin::ConvertToV8<v8::Local<v8::External>>(
isolate, holder->GetHandle(isolate))};
return v8::FunctionTemplate::New(isolate,
&Dispatcher<Sig>::DispatchToCallback,
gin::ConvertToV8<v8::Local<v8::External>>(
isolate, holder->GetHandle(isolate)));
}
// Base template - used only for non-member function pointers. Other types
@@ -413,9 +326,9 @@ struct CallbackTraits<
typename std::enable_if<std::is_member_function_pointer<T>::value>::type> {
static v8::Local<v8::FunctionTemplate> CreateTemplate(v8::Isolate* isolate,
T callback) {
InvokerOptions invoker_options = {.holder_is_first_argument = true};
int flags = HolderIsFirstArgument;
return gin_helper::CreateFunctionTemplate(
isolate, base::BindRepeating(callback), std::move(invoker_options));
isolate, base::BindRepeating(callback), flags);
}
};

View File

@@ -336,8 +336,7 @@ void ShowItemInFolder(const base::FilePath& full_path) {
base::ThreadPool::CreateCOMSTATaskRunner(
{base::MayBlock(), base::TaskPriority::USER_BLOCKING})
->PostTask(FROM_HERE,
base::BindOnce(&ShowItemInFolderOnWorkerThread,
full_path.NormalizePathSeparators()));
base::BindOnce(&ShowItemInFolderOnWorkerThread, full_path));
}
void OpenPath(const base::FilePath& full_path, OpenCallback callback) {
@@ -345,11 +344,9 @@ void OpenPath(const base::FilePath& full_path, OpenCallback callback) {
base::ThreadPool::CreateCOMSTATaskRunner(
{base::MayBlock(), base::TaskPriority::USER_BLOCKING})
->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&OpenPathOnThread,
full_path.NormalizePathSeparators()),
std::move(callback));
->PostTaskAndReplyWithResult(FROM_HERE,
base::BindOnce(&OpenPathOnThread, full_path),
std::move(callback));
}
void OpenExternal(const GURL& url,

View File

@@ -68,16 +68,19 @@ NodeService::~NodeService() {
}
}
void NodeService::InitializeNetworkService(
mojo::PendingRemote<network::mojom::URLLoaderFactory> url_loader_factory,
mojo::PendingRemote<network::mojom::HostResolver> host_resolver) {
URLLoaderBundle::GetInstance()->SetURLLoaderFactory(
std::move(url_loader_factory), mojo::Remote(std::move(host_resolver)));
}
void NodeService::Initialize(node::mojom::NodeServiceParamsPtr params) {
if (NodeBindings::IsInitialized())
return;
ParentPort::GetInstance()->Initialize(std::move(params->port));
URLLoaderBundle::GetInstance()->SetURLLoaderFactory(
std::move(params->url_loader_factory),
mojo::Remote(std::move(params->host_resolver)));
js_env_ = std::make_unique<JavascriptEnvironment>(node_bindings_->uv_loop());
v8::HandleScope scope(js_env_->isolate());

View File

@@ -57,8 +57,11 @@ class NodeService : public node::mojom::NodeService {
NodeService(const NodeService&) = delete;
NodeService& operator=(const NodeService&) = delete;
// mojom::NodeService implementation:
// mojom::NodeService:
void Initialize(node::mojom::NodeServiceParamsPtr params) override;
void InitializeNetworkService(
mojo::PendingRemote<network::mojom::URLLoaderFactory> url_loader_factory,
mojo::PendingRemote<network::mojom::HostResolver> host_resolver) override;
private:
// This needs to be initialized first so that it can be destroyed last

View File

@@ -15,11 +15,10 @@ struct NodeServiceParams {
array<string> args;
array<string> exec_args;
blink.mojom.MessagePortDescriptor port;
pending_remote<network.mojom.URLLoaderFactory> url_loader_factory;
pending_remote<network.mojom.HostResolver> host_resolver;
};
[ServiceSandbox=sandbox.mojom.Sandbox.kNoSandbox]
interface NodeService {
Initialize(NodeServiceParams params);
InitializeNetworkService(pending_remote<network.mojom.URLLoaderFactory> url_loader_factory, pending_remote<network.mojom.HostResolver> host_resolver);
};

View File

@@ -177,38 +177,6 @@ describe('debugger module', () => {
await loadingFinished;
});
it('can get and set cookies using the Storage API', async () => {
await w.webContents.loadURL('about:blank');
w.webContents.debugger.attach('1.1');
await w.webContents.debugger.sendCommand('Storage.clearCookies', {});
await w.webContents.debugger.sendCommand('Storage.setCookies', {
cookies: [
{
name: 'cookieOne',
value: 'cookieValueOne',
url: 'https://cookieone.com'
},
{
name: 'cookieTwo',
value: 'cookieValueTwo',
url: 'https://cookietwo.com'
}
]
});
const { cookies } = await w.webContents.debugger.sendCommand('Storage.getCookies', {});
expect(cookies).to.have.lengthOf(2);
const cookieOne = cookies.find((cookie: any) => cookie.name === 'cookieOne');
expect(cookieOne.domain).to.equal('cookieone.com');
expect(cookieOne.value).to.equal('cookieValueOne');
const cookieTwo = cookies.find((cookie: any) => cookie.name === 'cookieTwo');
expect(cookieTwo.domain).to.equal('cookietwo.com');
expect(cookieTwo.value).to.equal('cookieValueTwo');
});
it('uses empty sessionId by default', async () => {
w.webContents.loadURL('about:blank');
w.webContents.debugger.attach();

View File

@@ -198,21 +198,6 @@ describe('ipc module', () => {
expect(received).to.have.lengthOf(1000);
expect(received).to.deep.equal([...received].sort((a, b) => a - b));
});
it('handles navigation', async () => {
const received = new Set<string>();
ipcMain.on('test', (e) => { received.add(e.senderFrame.url); });
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
// This is testing a race condition between navigation and IPC.
// It's not quite deterministic so repeat a few times to get good signal.
for (let i = 0; i < 10; i++) {
w.loadFile(path.resolve(fixturesPath, 'pages', 'ipc-after-navigate.html'));
await once(w.webContents, 'did-navigate');
const expectedUrl = w.getURL();
await once(w.webContents, 'did-stop-loading');
expect([...received.values()]).to.deep.equal([expectedUrl]);
}
});
});
describe('MessagePort', () => {

View File

@@ -616,25 +616,6 @@ describe('net module (session)', () => {
});
}).to.throw('`partition` should be a string');
});
it('should throw if given a header value that is empty(null/undefined)', () => {
const emptyHeaderValues = [null, undefined];
const errorMsg = '`value` required in setHeader("foo", value)';
for (const emptyValue of emptyHeaderValues) {
expect(() => {
net.request({
url: 'https://foo',
headers: { foo: emptyValue as any }
} as any);
}).to.throw(errorMsg);
const request = net.request({ url: 'https://foo' });
expect(() => {
request.setHeader('foo', emptyValue as any);
}).to.throw(errorMsg);
}
});
});
describe('net.fetch', () => {

View File

@@ -935,11 +935,12 @@ describe('net module', () => {
response.end();
});
const serverUrl = url.parse(serverUrlUnparsed);
const urlRequest = net.request({
const options = {
port: serverUrl.port ? parseInt(serverUrl.port, 10) : undefined,
hostname: '127.0.0.1',
headers: { [customHeaderName]: customHeaderValue }
});
};
const urlRequest = net.request(options);
const response = await getResponse(urlRequest);
expect(response.statusCode).to.be.equal(200);
await collectStreamBody(response);

View File

@@ -1613,7 +1613,7 @@ describe('session module', () => {
// NOTE: This API clears more than localStorage, but localStorage is a
// convenient test target for this API
it('clears all data when no options supplied', async () => {
it('clears localstorage data', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } });
await w.loadFile(path.join(fixtures, 'api', 'localstorage.html'));
@@ -1623,8 +1623,7 @@ describe('session module', () => {
expect(await w.webContents.executeJavaScript('localStorage.length')).to.equal(0);
});
it('clears all data when no options supplied, called twice in parallel', async () => {
it('clears localstorage data when called twice in parallel', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } });
await w.loadFile(path.join(fixtures, 'api', 'localstorage.html'));
@@ -1639,92 +1638,5 @@ describe('session module', () => {
// Await the first promise so it doesn't creep into another test
await clearDataPromise;
});
it('only clears specified data categories', async () => {
const w = new BrowserWindow({
show: false,
webPreferences: { nodeIntegration: true, contextIsolation: false }
});
await w.loadFile(
path.join(fixtures, 'api', 'localstorage-and-indexeddb.html')
);
const { webContents } = w;
const { session } = webContents;
await once(ipcMain, 'indexeddb-ready');
async function queryData (channel: string): Promise<string> {
const event = once(ipcMain, `result-${channel}`);
webContents.send(`get-${channel}`);
return (await event)[1];
}
// Data is in localStorage
await expect(queryData('localstorage')).to.eventually.equal('hello localstorage');
// Data is in indexedDB
await expect(queryData('indexeddb')).to.eventually.equal('hello indexeddb');
// Clear only indexedDB, not localStorage
await session.clearData({ dataTypes: ['indexedDB'] });
// The localStorage data should still be there
await expect(queryData('localstorage')).to.eventually.equal('hello localstorage');
// The indexedDB data should be gone
await expect(queryData('indexeddb')).to.eventually.be.undefined();
});
it('only clears the specified origins', async () => {
const w = new BrowserWindow({ show: false });
await w.loadURL('about:blank');
const { session } = w.webContents;
const { cookies } = session;
await Promise.all([
cookies.set({
url: 'https://example.com/',
name: 'testdotcom',
value: 'testdotcom'
}),
cookies.set({
url: 'https://example.org/',
name: 'testdotorg',
value: 'testdotorg'
})
]);
await session.clearData({ origins: ['https://example.com'] });
expect((await cookies.get({ url: 'https://example.com/', name: 'testdotcom' })).length).to.equal(0);
expect((await cookies.get({ url: 'https://example.org/', name: 'testdotorg' })).length).to.be.greaterThan(0);
});
it('clears all except the specified origins', async () => {
const w = new BrowserWindow({ show: false });
await w.loadURL('about:blank');
const { session } = w.webContents;
const { cookies } = session;
await Promise.all([
cookies.set({
url: 'https://example.com/',
name: 'testdotcom',
value: 'testdotcom'
}),
cookies.set({
url: 'https://example.org/',
name: 'testdotorg',
value: 'testdotorg'
})
]);
await session.clearData({ excludeOrigins: ['https://example.com'] });
expect((await cookies.get({ url: 'https://example.com/', name: 'testdotcom' })).length).to.be.greaterThan(0);
expect((await cookies.get({ url: 'https://example.org/', name: 'testdotorg' })).length).to.equal(0);
});
});
});

View File

@@ -548,9 +548,6 @@ describe('webContents module', () => {
describe('navigationHistory', () => {
let w: BrowserWindow;
const urlPage1 = 'data:text/html,<html><head><script>document.title = "Page 1";</script></head><body></body></html>';
const urlPage2 = 'data:text/html,<html><head><script>document.title = "Page 2";</script></head><body></body></html>';
const urlPage3 = 'data:text/html,<html><head><script>document.title = "Page 3";</script></head><body></body></html>';
beforeEach(async () => {
w = new BrowserWindow({ show: false });
@@ -562,32 +559,44 @@ describe('webContents module', () => {
expect(result).to.deep.equal({ url: '', title: '' });
});
it('should fetch navigation entry given a valid index', async () => {
await w.loadURL(urlPage1);
const result = w.webContents.navigationHistory.getEntryAtIndex(0);
expect(result).to.deep.equal({ url: urlPage1, title: 'Page 1' });
await w.loadURL('https://www.google.com');
w.webContents.on('did-finish-load', async () => {
const result = w.webContents.navigationHistory.getEntryAtIndex(0);
expect(result).to.deep.equal({ url: 'https://www.google.com/', title: 'Google' });
});
});
it('should return null given an invalid index larger than history length', async () => {
await w.loadURL(urlPage1);
const result = w.webContents.navigationHistory.getEntryAtIndex(5);
expect(result).to.be.null();
await w.loadURL('https://www.google.com');
w.webContents.on('did-finish-load', async () => {
const result = w.webContents.navigationHistory.getEntryAtIndex(5);
expect(result).to.be.null();
});
});
it('should return null given an invalid negative index', async () => {
await w.loadURL(urlPage1);
const result = w.webContents.navigationHistory.getEntryAtIndex(-1);
expect(result).to.be.null();
await w.loadURL('https://www.google.com');
w.webContents.on('did-finish-load', async () => {
const result = w.webContents.navigationHistory.getEntryAtIndex(-1);
expect(result).to.be.null();
});
});
});
describe('navigationHistory.getActiveIndex() API', () => {
it('should return valid active index after a single page visit', async () => {
await w.loadURL(urlPage1);
expect(w.webContents.navigationHistory.getActiveIndex()).to.equal(0);
await w.loadURL('https://www.google.com');
w.webContents.on('did-finish-load', async () => {
expect(w.webContents.navigationHistory.getActiveIndex()).to.equal(0);
});
});
it('should return valid active index after a multiple page visits', async () => {
await w.loadURL(urlPage1);
await w.loadURL(urlPage2);
await w.loadURL(urlPage3);
const loadPromise = once(w.webContents, 'did-finish-load');
await w.loadURL('https://www.github.com');
await loadPromise;
await w.loadURL('https://www.google.com');
await loadPromise;
await w.loadURL('about:blank');
await loadPromise;
expect(w.webContents.navigationHistory.getActiveIndex()).to.equal(2);
});
@@ -599,14 +608,20 @@ describe('webContents module', () => {
describe('navigationHistory.length() API', () => {
it('should return valid history length after a single page visit', async () => {
await w.loadURL(urlPage1);
expect(w.webContents.navigationHistory.length()).to.equal(1);
await w.loadURL('https://www.google.com');
w.webContents.on('did-finish-load', async () => {
expect(w.webContents.navigationHistory.length()).to.equal(1);
});
});
it('should return valid history length after a multiple page visits', async () => {
await w.loadURL(urlPage1);
await w.loadURL(urlPage2);
await w.loadURL(urlPage3);
const loadPromise = once(w.webContents, 'did-finish-load');
await w.loadURL('https://www.github.com');
await loadPromise;
await w.loadURL('https://www.google.com');
await loadPromise;
await w.loadURL('about:blank');
await loadPromise;
expect(w.webContents.navigationHistory.length()).to.equal(3);
});

View File

@@ -1383,138 +1383,6 @@ describe('chromium features', () => {
});
});
describe('Storage Access API', () => {
afterEach(closeAllWindows);
afterEach(() => {
session.defaultSession.setPermissionCheckHandler(null);
session.defaultSession.setPermissionRequestHandler(null);
});
it('can determine if a permission is granted for "storage-access"', async () => {
session.defaultSession.setPermissionCheckHandler(
(_wc, permission) => permission === 'storage-access'
);
const w = new BrowserWindow({ show: false });
await w.loadFile(path.join(fixturesPath, 'pages', 'a.html'));
const permission = await w.webContents.executeJavaScript(`
navigator.permissions.query({ name: 'storage-access' })
.then(permission => permission.state).catch(err => err.message);
`, true);
expect(permission).to.eq('granted');
});
it('can determine if a permission is denied for "storage-access"', async () => {
session.defaultSession.setPermissionCheckHandler(
(_wc, permission) => permission !== 'storage-access'
);
const w = new BrowserWindow({ show: false });
await w.loadFile(path.join(fixturesPath, 'pages', 'a.html'));
const permission = await w.webContents.executeJavaScript(`
navigator.permissions.query({ name: 'storage-access' })
.then(permission => permission.state).catch(err => err.message);
`, true);
expect(permission).to.eq('denied');
});
it('can determine if a permission is granted for "top-level-storage-access"', async () => {
session.defaultSession.setPermissionCheckHandler(
(_wc, permission) => permission === 'top-level-storage-access'
);
const w = new BrowserWindow({ show: false });
await w.loadFile(path.join(fixturesPath, 'pages', 'a.html'));
const permission = await w.webContents.executeJavaScript(`
navigator.permissions.query({
name: 'top-level-storage-access',
requestedOrigin: "https://www.example.com",
}).then(permission => permission.state).catch(err => err.message);
`, true);
expect(permission).to.eq('granted');
});
it('can determine if a permission is denied for "top-level-storage-access"', async () => {
session.defaultSession.setPermissionCheckHandler(
(_wc, permission) => permission !== 'top-level-storage-access'
);
const w = new BrowserWindow({ show: false });
await w.loadFile(path.join(fixturesPath, 'pages', 'a.html'));
const permission = await w.webContents.executeJavaScript(`
navigator.permissions.query({
name: 'top-level-storage-access',
requestedOrigin: "https://www.example.com",
}).then(permission => permission.state).catch(err => err.message);
`, true);
expect(permission).to.eq('denied');
});
it('can grant a permission request for "top-level-storage-access"', async () => {
session.defaultSession.setPermissionRequestHandler(
(_wc, permission, callback) => {
callback(permission === 'top-level-storage-access');
}
);
const w = new BrowserWindow({ show: false });
await w.loadFile(path.join(fixturesPath, 'pages', 'button.html'));
// requestStorageAccessFor returns a Promise that fulfills with undefined
// if the access to third-party cookies was granted and rejects if access was denied.
const permission = await w.webContents.executeJavaScript(`
new Promise((resolve, reject) => {
const button = document.getElementById('button');
button.addEventListener("click", () => {
document.requestStorageAccessFor('https://myfakesite').then(
(res) => { resolve('granted') },
(err) => { resolve('denied') },
);
});
button.click();
});
`, true);
expect(permission).to.eq('granted');
});
it('can deny a permission request for "top-level-storage-access"', async () => {
session.defaultSession.setPermissionRequestHandler(
(_wc, permission, callback) => {
callback(permission !== 'top-level-storage-access');
}
);
const w = new BrowserWindow({ show: false });
await w.loadFile(path.join(fixturesPath, 'pages', 'button.html'));
// requestStorageAccessFor returns a Promise that fulfills with undefined
// if the access to third-party cookies was granted and rejects if access was denied.
const permission = await w.webContents.executeJavaScript(`
new Promise((resolve, reject) => {
const button = document.getElementById('button');
button.addEventListener("click", () => {
document.requestStorageAccessFor('https://myfakesite').then(
(res) => { resolve('granted') },
(err) => { resolve('denied') },
);
});
button.click();
});
`, true);
expect(permission).to.eq('denied');
});
});
describe('IdleDetection', () => {
afterEach(closeAllWindows);
afterEach(() => {

View File

@@ -1285,16 +1285,6 @@ describe('chrome extensions', () => {
});
});
it('globalParams', async () => {
await w.loadURL(url);
const message = { method: 'globalParams' };
w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
const [,, responseString] = await once(w.webContents, 'console-message');
const response = JSON.parse(responseString);
expect(response).to.deep.equal({ changed: true });
});
it('insertCSS', async () => {
await w.loadURL(url);

View File

@@ -1,50 +0,0 @@
<html>
<body>
<script type="text/javascript" charset="utf-8">
const {ipcRenderer} = require('electron');
window.localStorage.setItem('test', 'hello localstorage');
ipcRenderer.on('get-localstorage', () => {
const result = window.localStorage.getItem('test');
ipcRenderer.send('result-localstorage', result);
});
const deleteRequest = window.indexedDB.deleteDatabase('testdb');
deleteRequest.onerror = deleteRequest.onsuccess = (event) => {
const openRequest = window.indexedDB.open('testdb');
openRequest.onupgradeneeded = (event) => {
const db = event.target.result;
db.onerror = (event) => {
console.error(event);
};
const objectStore = db.createObjectStore('testdata');
objectStore.createIndex('test', '');
};
openRequest.onsuccess = (event) => {
const db = event.target.result;
const addRequest = db.transaction("testdata", "readwrite").objectStore("testdata").add("hello indexeddb", 'test');
addRequest.onsuccess = () => {
ipcRenderer.send('indexeddb-ready');
};
};
};
ipcRenderer.on('get-indexeddb', () => {
const openRequest = window.indexedDB.open('testdb');
openRequest.onerror = (event) => {
console.error(event);
};
openRequest.onsuccess = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('testdata')) {
ipcRenderer.send('result-indexeddb', undefined);
return;
}
const getRequest = db.transaction('testdata', 'readonly').objectStore('testdata').get('test');
getRequest.onsuccess = (event) => {
ipcRenderer.send('result-indexeddb', event.target.result);
};
};
});
</script>
</body>
</html>

View File

@@ -20,27 +20,6 @@ const handleRequest = async (request, sender, sendResponse) => {
break;
}
case 'globalParams' : {
await chrome.scripting.executeScript({
target: { tabId },
func: () => {
chrome.scripting.globalParams.changed = true;
},
world: 'ISOLATED'
});
const results = await chrome.scripting.executeScript({
target: { tabId },
func: () => JSON.stringify(chrome.scripting.globalParams),
world: 'ISOLATED'
});
const result = JSON.parse(results[0].result);
sendResponse(result);
break;
}
case 'registerContentScripts': {
await chrome.scripting.registerContentScripts([{
id: 'session-script',

View File

@@ -19,11 +19,6 @@ const map = {
chrome.runtime.sendMessage({ method: 'insertCSS' }, response => {
console.log(JSON.stringify(response));
});
},
globalParams () {
chrome.runtime.sendMessage({ method: 'globalParams' }, response => {
console.log(JSON.stringify(response));
});
}
};

View File

@@ -1,9 +0,0 @@
<script>
const { ipcRenderer } = require('electron')
window.onload = () => {
setInterval(() => {
ipcRenderer.send('test')
}, 0);
window.location.replace("https://example.com");
}
</script>

View File

@@ -14,6 +14,8 @@ import { HexColors, ScreenCapture } from './lib/screen-helpers';
declare let WebView: any;
const features = process._linkedBinding('electron_common_features');
const isMacArm64 = (process.platform === 'darwin' && process.arch === 'arm64');
async function loadWebView (w: WebContents, attributes: Record<string, string>, opts?: {openDevTools?: boolean}): Promise<void> {
const { openDevTools } = {
openDevTools: false,
@@ -2105,8 +2107,9 @@ describe('<webview> tag', function () {
}
});
// FIXME: This test is flaking constantly on Linux and macOS.
xdescribe('<webview>.capturePage()', () => {
// TODO(miniak): figure out why this is failing on windows
// TODO(vertedinde): figure out why this is failing on mac arm64
ifdescribe(process.platform !== 'win32' && !isMacArm64)('<webview>.capturePage()', () => {
it('returns a Promise with a NativeImage', async function () {
this.retries(5);

View File

@@ -199,10 +199,10 @@
"@octokit/auth-app" "^4.0.13"
"@octokit/rest" "^19.0.11"
"@electron/lint-roller@^1.12.1":
version "1.12.1"
resolved "https://registry.yarnpkg.com/@electron/lint-roller/-/lint-roller-1.12.1.tgz#3152b9a68815b2ab51cc5a4d462ae6769d5052ce"
integrity sha512-TGgVcHUAooM9/dV3iJTxhmKl35x/gzStsClz2/LWtBQZ59cRK+0YwWF5HWhtydGFIpOLEQGzCvUrty5zZLyd4w==
"@electron/lint-roller@^1.9.0":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@electron/lint-roller/-/lint-roller-1.11.1.tgz#bf4ab114e8cb3a77e2c634807d4af3d40c3ba863"
integrity sha512-LfErOK5MnSmoSyVN5OnHWsQ8p5It8JBE0AmRYCx65WS4BZudnYeRcohlRSOSi+GGqldujdKHyEo+fdY5lREL/Q==
dependencies:
"@dsanders11/vscode-markdown-languageservice" "^0.3.0"
balanced-match "^2.0.0"
@@ -1599,13 +1599,13 @@ binary-extensions@^2.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9"
integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==
body-parser@1.20.2:
version "1.20.2"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
body-parser@1.20.1:
version "1.20.1"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==
dependencies:
bytes "3.1.2"
content-type "~1.0.5"
content-type "~1.0.4"
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
@@ -1613,7 +1613,7 @@ body-parser@1.20.2:
iconv-lite "0.4.24"
on-finished "2.4.1"
qs "6.11.0"
raw-body "2.5.2"
raw-body "2.5.1"
type-is "~1.6.18"
unpipe "1.0.0"
@@ -2017,20 +2017,20 @@ content-disposition@0.5.4:
dependencies:
safe-buffer "5.2.1"
content-type@~1.0.4, content-type@~1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
content-type@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
cookie@0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
cookie@0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
core-util-is@~1.0.0:
version "1.0.2"
@@ -2071,7 +2071,14 @@ debug@^3.1.0, debug@^3.2.7:
dependencies:
ms "^2.1.1"
debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4:
debug@^4.0.0:
version "4.3.2"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
dependencies:
ms "2.1.2"
debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@@ -2801,17 +2808,17 @@ execa@^4.0.1:
signal-exit "^3.0.2"
strip-final-newline "^2.0.0"
express@^4.19.2:
version "4.19.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465"
integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==
express@^4.16.4:
version "4.18.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59"
integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==
dependencies:
accepts "~1.3.8"
array-flatten "1.1.1"
body-parser "1.20.2"
body-parser "1.20.1"
content-disposition "0.5.4"
content-type "~1.0.4"
cookie "0.6.0"
cookie "0.5.0"
cookie-signature "1.0.6"
debug "2.6.9"
depd "2.0.0"
@@ -5236,10 +5243,10 @@ range-parser@~1.2.1:
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
raw-body@2.5.2:
version "2.5.2"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a"
integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==
raw-body@2.5.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857"
integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==
dependencies:
bytes "3.1.2"
http-errors "2.0.0"