Compare commits

..

25 Commits

Author SHA1 Message Date
Shelley Vohr
96c81bd6a7 refactor: pull printing utils to new file 2024-04-09 19:37:47 +02:00
Shelley Vohr
920f909e3e refactor: clean up printing.patch 2024-04-09 16:16:44 +02:00
Jeremy Rose
76f7bbb0a8 fix: move BrowserWindow's WebContentsView to be a child of rootview (#41256) 2024-04-08 10:30:23 -07:00
Bruno Pitrus
22c149812c build: add missing header for content::SyntheticGestureTarget (#41789)
IWYU: add missing header for `content::SyntheticGestureTarget`

GNU libstdc++ does not allow using std::unique_ptr on incomplete types,
leading to a compile error.
2024-04-08 18:17:00 +02:00
David Sanders
42164d7081 build: add Markdown lint check for unescaped angle brackets (#41753) 2024-04-04 14:50:35 -04:00
Calvin
3eb94b72ba feat: Options parameter for Session.clearData API (#41355)
* feat: Options parameter for `Session.clearData` API

* Consolidate & curate data type categories

* Update docs for better typing

* off-by-one typo

* refactor to use `std::shared_ptr` instead of `base::RefCounted`

* fix compile errors

* std::enable_shared_from_this didn't work 🤷

* Refine docs with defaults
2024-04-01 12:09:01 -04:00
Keeley Hammond
752f2eb124 build: add GH Actions to release-build script (#41639)
* build: add GH Actions to release-build script

* Update script/release/ci-release-build.js

---------

Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2024-04-01 10:02:26 -04:00
Shelley Vohr
beafbfd511 build: combine ImportModuleDynamically patches (#41712)
build: combine ImportModuleDynamically patches
2024-03-29 13:34:56 +01:00
dependabot[bot]
d54645e554 build(deps-dev): bump express from 4.18.2 to 4.19.2 (#41716)
Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-28 16:12:14 -07:00
Shelley Vohr
0bf53a3876 fix: Storage.{get|set|clear}Cookies via CDP not working (#41718) 2024-03-28 16:09:27 -07:00
Shelley Vohr
62d4b21819 test: disable flaky <webview>.capturePage() specs (#41713)
test: disable flaky <webview>.capturePage() specs
2024-03-28 22:37:14 +01:00
Shelley Vohr
61457c9498 feat(serial): allow Bluetooth ports to be requested by service class ID (#41638)
* feat(serial): allow Bluetooth ports to be requested by service class ID

* fix: bluetooth dependency
2024-03-28 18:23:13 +01:00
Jeremy Rose
c6102b9278 docs: add missing headers option to ClientRequest options (#41723) 2024-03-28 09:38:16 -07:00
daihere1993
72c2b9e862 fix: recognize 'undefined' header value in ClientRequest (#41615)
Co-authored-by: zowu <luke.wu@nokia-sbell.com>
2024-03-27 16:46:07 -07:00
Shelley Vohr
08241669bc test: add tests for Storage Access API (#41698) 2024-03-27 19:52:24 +01:00
Samuel Attard
6e36153799 build: fix potential source of errors in issue workflow (#41715) 2024-03-27 09:50:55 -07:00
taoky
4f76fff978 fix: don't do self-destroy in LibnotifyNotification::Dismiss() (#41691)
Callers of Notification::Dismiss() assume that the notification
instance is not deleted after the call, but this was not the case
for LibnotifyNotification:
- Destroy() would get `this` deleted.
- notify_notification_close() in portal environment triggers
LibnotifyNotification::OnNotificationClosed(), and finally calls
Destroy()

This patch removes all Destroy() in Dismiss(), and adds a boolean
to tell whether notify_notification_close() is running, to avoid crash
under portal environment.

Fixes #40461.
2024-03-27 10:53:23 +01:00
Alice Zhao
c82ec0c72b test: remove hardcoded url (#41706) 2024-03-27 10:53:02 +01:00
Alice Zhao
c57ce31e84 test: fix flaky tests in webContents.navigationHistory (#41705)
test: fix flaky tests by replacing real urls with data urls
2024-03-27 13:49:11 +09:00
Shelley Vohr
32b44aa5c8 fix: crash on extension unload when script validation finishes (#41686)
https://chromium-review.googlesource.com/c/chromium/src/+/5225796
2024-03-26 14:32:06 +01:00
Shelley Vohr
7032c0d03c test: add test and api_feature definition for chrome.scripting.globalParams (#41685)
chore: add test and api_feature for chrome.scripting.globalParams
2024-03-26 12:33:47 +01:00
Erick Zhao
b9c4b27781 docs: backslash escape parametrized TypeScript types (#41575)
* docs: backslash escape parametrized TypeScript types

* missing instances
2024-03-25 11:19:44 +01:00
Shelley Vohr
8c427253b3 refactor: update gin_helper/function_template (#41534)
* refactor: update gin_helper/function_template

* fix: crash in Node.js Worker threads
2024-03-25 10:01:54 +01:00
Shelley Vohr
ed28ead8ac fix: WTF-8 decoding issue in node:fs (#41673)
fix: WTF-8 decoding issue in node:fs
2024-03-24 20:39:37 +01:00
Piotr Płaczek
90a7e5acae fix: normalize path before calling showItemInFolder and openPath (#41642)
* fix: normalize path before calling ShowItemInFolder

* fix: normalize path before calling OpenPath
2024-03-22 10:28:44 -04:00
83 changed files with 1715 additions and 743 deletions

View File

@@ -38,6 +38,8 @@ 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: |
@@ -47,7 +49,7 @@ jobs:
const [ owner, repo ] = '${{ github.repository }}'.split('/');
const issue_number = ${{ github.event.issue.number }};
const tree = fromMarkdown(`${{ github.event.issue.body }}`);
const tree = fromMarkdown(process.env.ISSUE_BODY);
const labels = [];

View File

@@ -1,3 +1,17 @@
{
"extends": "@electron/lint-roller/configs/markdownlint.json"
"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",
]
}
}

View File

@@ -475,6 +475,7 @@ 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",
@@ -699,6 +700,8 @@ source_set("electron_lib") {
sources += [
"shell/browser/printing/print_view_manager_electron.cc",
"shell/browser/printing/print_view_manager_electron.h",
"shell/browser/printing/printing_utils.cc",
"shell/browser/printing/printing_utils.h",
"shell/renderer/printing/print_render_frame_helper_delegate.cc",
"shell/renderer/printing/print_render_frame_helper_delegate.h",
]

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,6 +17,8 @@ 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)
@@ -158,7 +160,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&#62; | any&#62;
* `listener` Function\<Promise\<any\> | any\>
* `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&#62; | any&#62;
* `listener` Function\<Promise\<any\> | any\>
* `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,6 +814,8 @@ 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
@@ -862,6 +864,8 @@ 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.
@@ -1216,7 +1220,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
@@ -1424,23 +1428,34 @@ 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()`
#### `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.
Returns `Promise<void>` - resolves when all data has been cleared.
This method clears many different types of data, inlcuding:
* Cache
* Cookies
* Downloads
* IndexedDB
* Local Storage
* Service Workers
* And more...
Clears various different types of data.
This method clears more types of data and is more thourough than the
`clearStorageData` method, however it is currently less configurable than that
method.
`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`.
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 <=6, this would return a `FileList` with a `File` object for:
In Electron &lt;=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 <code>electron/main</code>.
main process modules from `electron/main`.
```js
const { app, BrowserWindow } = require('electron/main')

View File

@@ -209,6 +209,22 @@ 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 };
@@ -275,12 +291,7 @@ function parseOptions (optionsIn: ClientRequestConstructorOptions | string): Nod
};
const headers: Record<string, string | string[]> = options.headers || {};
for (const [name, value] of Object.entries(headers)) {
if (!isValidHeaderName(name)) {
throw new Error(`Invalid header name: '${name}'`);
}
if (!isValidHeaderValue(value.toString())) {
throw new Error(`Invalid value for header '${name}': '${value}'`);
}
validateHeader(name, value);
const key = name.toLowerCase();
urlLoaderOptions.headers[key] = { name, value };
}
@@ -351,21 +362,10 @@ 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');
}
if (!isValidHeaderName(name)) {
throw new Error(`Invalid header name: '${name}'`);
}
if (!isValidHeaderValue(value.toString())) {
throw new Error(`Invalid value for header '${name}': '${value}'`);
}
validateHeader(name, value);
const key = name.toLowerCase();
this._urlLoaderOptions.headers[key] = { name, value };

View File

@@ -1,20 +1,11 @@
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.9.0",
"@electron/lint-roller": "^1.12.1",
"@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.16.4",
"express": "^4.19.2",
"folder-hash": "^2.1.1",
"fs-extra": "^9.0.1",
"got": "^11.8.5",

View File

@@ -34,64 +34,22 @@ index 69e759c3b34692beac06ceeddf4b3f1637fb7788..42d0da7f936626aa8bb90057723deeaa
}
diff --git a/chrome/browser/printing/print_job.cc b/chrome/browser/printing/print_job.cc
index 0d8a53178e8d509a573ffdaadf2c4cec670522ac..8e7f9b92beb317888a5a3acb3bc2fa29f283ff03 100644
index 0d8a53178e8d509a573ffdaadf2c4cec670522ac..b6fa337b2f5f445f38ef521613702af320df82cb 100644
--- a/chrome/browser/printing/print_job.cc
+++ b/chrome/browser/printing/print_job.cc
@@ -96,6 +96,7 @@ bool PrintWithReducedRasterization(PrefService* prefs) {
return base::FeatureList::IsEnabled(features::kPrintWithReducedRasterization);
}
+#if 0
PrefService* GetPrefsForWebContents(content::WebContents* web_contents) {
@@ -100,9 +100,7 @@ PrefService* GetPrefsForWebContents(content::WebContents* web_contents) {
// TODO(thestig): Figure out why crbug.com/1083911 occurred, which is likely
// because `web_contents` was null. As a result, this section has many more
@@ -110,6 +111,7 @@ content::WebContents* GetWebContents(content::GlobalRenderFrameHostId rfh_id) {
auto* rfh = content::RenderFrameHost::FromID(rfh_id);
return rfh ? content::WebContents::FromRenderFrameHost(rfh) : nullptr;
// pointer checks to avoid crashing.
- content::BrowserContext* context =
- web_contents ? web_contents->GetBrowserContext() : nullptr;
- return context ? Profile::FromBrowserContext(context)->GetPrefs() : nullptr;
+ return nullptr;
}
+#endif
#endif // BUILDFLAG(IS_WIN)
@@ -150,10 +152,8 @@ void PrintJob::Initialize(std::unique_ptr<PrinterQuery> query,
#if BUILDFLAG(IS_WIN)
pdf_page_mapping_ = PageNumber::GetPages(settings->ranges(), page_count);
- PrefService* prefs = GetPrefsForWebContents(GetWebContents(rfh_id_));
- if (prefs && prefs->IsManagedPreference(prefs::kPdfUseSkiaRendererEnabled)) {
- use_skia_ = prefs->GetBoolean(prefs::kPdfUseSkiaRendererEnabled);
- }
+ // TODO(codebytere): should we enable this later?
+ use_skia_ = false;
#endif
auto new_doc = base::MakeRefCounted<PrintedDocument>(std::move(settings),
@@ -404,8 +404,10 @@ void PrintJob::StartPdfToEmfConversion(
const PrintSettings& settings = document()->settings();
+#if 0
PrefService* prefs = GetPrefsForWebContents(GetWebContents(rfh_id_));
- bool print_with_reduced_rasterization = PrintWithReducedRasterization(prefs);
+#endif
+ bool print_with_reduced_rasterization = PrintWithReducedRasterization(nullptr);
using RenderMode = PdfRenderSettings::Mode;
RenderMode mode = print_with_reduced_rasterization
@@ -497,8 +499,10 @@ void PrintJob::StartPdfToPostScriptConversion(
if (ps_level2) {
mode = PdfRenderSettings::Mode::POSTSCRIPT_LEVEL2;
} else {
+#if 0
PrefService* prefs = GetPrefsForWebContents(GetWebContents(rfh_id_));
- mode = PrintWithPostScriptType42Fonts(prefs)
+#endif
+ mode = PrintWithPostScriptType42Fonts(nullptr)
? PdfRenderSettings::Mode::POSTSCRIPT_LEVEL3_WITH_TYPE42_FONTS
: PdfRenderSettings::Mode::POSTSCRIPT_LEVEL3;
}
content::WebContents* GetWebContents(content::GlobalRenderFrameHostId rfh_id) {
diff --git a/chrome/browser/printing/print_view_manager_base.cc b/chrome/browser/printing/print_view_manager_base.cc
index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09b1e78450 100644
index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..168b7b773996a621fbbebea08933317b895705a2 100644
--- a/chrome/browser/printing/print_view_manager_base.cc
+++ b/chrome/browser/printing/print_view_manager_base.cc
@@ -23,7 +23,9 @@
@@ -179,13 +137,12 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
}
PrintViewManagerBase::~PrintViewManagerBase() {
@@ -202,12 +228,17 @@ void PrintViewManagerBase::DisableThirdPartyBlocking() {
@@ -202,12 +228,16 @@ void PrintViewManagerBase::DisableThirdPartyBlocking() {
}
#endif // BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
-bool PrintViewManagerBase::PrintNow(content::RenderFrameHost* rfh) {
+bool PrintViewManagerBase::PrintNow(content::RenderFrameHost* rfh,
+ bool silent,
+ base::Value::Dict settings,
+ CompletionCallback callback) {
if (!StartPrintCommon(rfh)) {
@@ -195,11 +152,11 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
- GetPrintRenderFrame(rfh)->PrintRequestedPages();
+ callback_ = std::move(callback);
+
+ GetPrintRenderFrame(rfh)->PrintRequestedPages(silent, std::move(settings));
+ GetPrintRenderFrame(rfh)->PrintRequestedPages(std::move(settings));
for (auto& observer : GetTestObservers()) {
observer.OnPrintNow(rfh);
@@ -336,12 +367,13 @@ void PrintViewManagerBase::OnDidUpdatePrintableArea(
@@ -336,12 +366,13 @@ void PrintViewManagerBase::OnDidUpdatePrintableArea(
}
PRINTER_LOG(EVENT) << "Paper printable area updated for vendor id "
<< print_settings->requested_media().vendor_id;
@@ -214,7 +171,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
base::Value::Dict job_settings,
std::unique_ptr<PrintSettings> print_settings,
UpdatePrintSettingsCallback callback) {
@@ -349,7 +381,8 @@ void PrintViewManagerBase::CompleteUpdatePrintSettings(
@@ -349,7 +380,8 @@ void PrintViewManagerBase::CompleteUpdatePrintSettings(
settings->pages = GetPageRangesFromJobSettings(job_settings);
settings->params = mojom::PrintParams::New();
RenderParamsFromPrintSettings(*print_settings, settings->params.get());
@@ -224,7 +181,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
if (!PrintMsgPrintParamsIsValid(*settings->params)) {
mojom::PrinterType printer_type = static_cast<mojom::PrinterType>(
*job_settings.FindInt(kSettingPrinterType));
@@ -361,6 +394,10 @@ void PrintViewManagerBase::CompleteUpdatePrintSettings(
@@ -361,6 +393,10 @@ void PrintViewManagerBase::CompleteUpdatePrintSettings(
return;
}
@@ -235,7 +192,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
set_cookie(settings->params->document_cookie);
std::move(callback).Run(std::move(settings));
}
@@ -647,11 +684,12 @@ void PrintViewManagerBase::DidPrintDocument(
@@ -647,11 +683,12 @@ void PrintViewManagerBase::DidPrintDocument(
void PrintViewManagerBase::GetDefaultPrintSettings(
GetDefaultPrintSettingsCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -249,7 +206,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
content::RenderFrameHost* render_frame_host = GetCurrentTargetFrame();
if (base::FeatureList::IsEnabled(kCheckPrintRfhIsActive) &&
!render_frame_host->IsActive()) {
@@ -710,10 +748,12 @@ void PrintViewManagerBase::UpdatePrintSettings(
@@ -710,10 +747,12 @@ void PrintViewManagerBase::UpdatePrintSettings(
base::Value::Dict job_settings,
UpdatePrintSettingsCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -262,7 +219,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
std::optional<int> printer_type_value =
job_settings.FindInt(kSettingPrinterType);
@@ -724,6 +764,7 @@ void PrintViewManagerBase::UpdatePrintSettings(
@@ -724,6 +763,7 @@ void PrintViewManagerBase::UpdatePrintSettings(
mojom::PrinterType printer_type =
static_cast<mojom::PrinterType>(*printer_type_value);
@@ -270,7 +227,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
if (printer_type != mojom::PrinterType::kExtension &&
printer_type != mojom::PrinterType::kPdf &&
printer_type != mojom::PrinterType::kLocal) {
@@ -743,6 +784,7 @@ void PrintViewManagerBase::UpdatePrintSettings(
@@ -743,6 +783,7 @@ void PrintViewManagerBase::UpdatePrintSettings(
if (value > 0)
job_settings.Set(kSettingRasterizePdfDpi, value);
}
@@ -278,7 +235,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
std::unique_ptr<PrintSettings> print_settings =
PrintSettingsFromJobSettings(job_settings);
@@ -762,7 +804,21 @@ void PrintViewManagerBase::UpdatePrintSettings(
@@ -762,7 +803,21 @@ void PrintViewManagerBase::UpdatePrintSettings(
}
}
@@ -301,7 +258,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
// TODO(crbug.com/1424368): Remove this if the printable areas can be made
// fully available from `PrintBackend::GetPrinterSemanticCapsAndDefaults()`
// for in-browser queries.
@@ -784,8 +840,6 @@ void PrintViewManagerBase::UpdatePrintSettings(
@@ -784,8 +839,6 @@ void PrintViewManagerBase::UpdatePrintSettings(
}
#endif
@@ -310,7 +267,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
}
void PrintViewManagerBase::SetAccessibilityTree(
@@ -801,7 +855,7 @@ void PrintViewManagerBase::SetAccessibilityTree(
@@ -801,7 +854,7 @@ void PrintViewManagerBase::SetAccessibilityTree(
void PrintViewManagerBase::IsPrintingEnabled(
IsPrintingEnabledCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -319,7 +276,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
}
void PrintViewManagerBase::ScriptedPrint(mojom::ScriptedPrintParamsPtr params,
@@ -852,6 +906,7 @@ void PrintViewManagerBase::PrintingFailed(int32_t cookie,
@@ -852,6 +905,7 @@ void PrintViewManagerBase::PrintingFailed(int32_t cookie,
PrintManager::PrintingFailed(cookie, reason);
@@ -327,7 +284,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
// `PrintingFailed()` can occur because asynchronous compositing results
// don't complete until after a print job has already failed and been
// destroyed. In such cases the error notification to the user will
@@ -861,7 +916,7 @@ void PrintViewManagerBase::PrintingFailed(int32_t cookie,
@@ -861,7 +915,7 @@ void PrintViewManagerBase::PrintingFailed(int32_t cookie,
print_job_->document()->cookie() == cookie) {
ShowPrintErrorDialogForGenericError();
}
@@ -336,7 +293,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
ReleasePrinterQuery();
}
@@ -873,15 +928,24 @@ void PrintViewManagerBase::RemoveTestObserver(TestObserver& observer) {
@@ -873,15 +927,24 @@ void PrintViewManagerBase::RemoveTestObserver(TestObserver& observer) {
test_observers_.RemoveObserver(&observer);
}
@@ -361,7 +318,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
}
void PrintViewManagerBase::RenderFrameDeleted(
@@ -933,7 +997,12 @@ void PrintViewManagerBase::OnJobDone() {
@@ -933,7 +996,12 @@ void PrintViewManagerBase::OnJobDone() {
// Printing is done, we don't need it anymore.
// print_job_->is_job_pending() may still be true, depending on the order
// of object registration.
@@ -375,7 +332,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
ReleasePrintJob();
}
@@ -942,9 +1011,10 @@ void PrintViewManagerBase::OnCanceling() {
@@ -942,9 +1010,10 @@ void PrintViewManagerBase::OnCanceling() {
}
void PrintViewManagerBase::OnFailed() {
@@ -387,7 +344,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
TerminatePrintJob(true);
}
@@ -954,7 +1024,7 @@ bool PrintViewManagerBase::RenderAllMissingPagesNow() {
@@ -954,7 +1023,7 @@ bool PrintViewManagerBase::RenderAllMissingPagesNow() {
// Is the document already complete?
if (print_job_->document() && print_job_->document()->IsComplete()) {
@@ -396,7 +353,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
return true;
}
@@ -1007,7 +1077,10 @@ bool PrintViewManagerBase::SetupNewPrintJob(
@@ -1007,7 +1076,10 @@ bool PrintViewManagerBase::SetupNewPrintJob(
// Disconnect the current `print_job_`.
auto weak_this = weak_ptr_factory_.GetWeakPtr();
@@ -408,7 +365,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
if (!weak_this)
return false;
@@ -1027,7 +1100,7 @@ bool PrintViewManagerBase::SetupNewPrintJob(
@@ -1027,7 +1099,7 @@ bool PrintViewManagerBase::SetupNewPrintJob(
#endif
print_job_->AddObserver(*this);
@@ -417,7 +374,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
return true;
}
@@ -1095,6 +1168,11 @@ void PrintViewManagerBase::ReleasePrintJob() {
@@ -1095,6 +1167,11 @@ void PrintViewManagerBase::ReleasePrintJob() {
}
#endif
@@ -429,7 +386,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
if (!print_job_)
return;
@@ -1102,7 +1180,7 @@ void PrintViewManagerBase::ReleasePrintJob() {
@@ -1102,7 +1179,7 @@ void PrintViewManagerBase::ReleasePrintJob() {
// printing_rfh_ should only ever point to a RenderFrameHost with a live
// RenderFrame.
DCHECK(rfh->IsRenderFrameLive());
@@ -438,7 +395,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
}
print_job_->RemoveObserver(*this);
@@ -1144,7 +1222,7 @@ bool PrintViewManagerBase::RunInnerMessageLoop() {
@@ -1144,7 +1221,7 @@ bool PrintViewManagerBase::RunInnerMessageLoop() {
}
bool PrintViewManagerBase::OpportunisticallyCreatePrintJob(int cookie) {
@@ -447,7 +404,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
return true;
if (!cookie) {
@@ -1298,6 +1376,8 @@ void PrintViewManagerBase::CompleteScriptedPrint(
@@ -1298,6 +1375,8 @@ void PrintViewManagerBase::CompleteScriptedPrint(
auto callback_wrapper = base::BindOnce(
&PrintViewManagerBase::ScriptedPrintReply, weak_ptr_factory_.GetWeakPtr(),
std::move(callback), render_process_host->GetID());
@@ -456,7 +413,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
#if BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
DisableThirdPartyBlocking();
#endif
@@ -1312,7 +1392,7 @@ void PrintViewManagerBase::CompleteScriptedPrint(
@@ -1312,7 +1391,7 @@ void PrintViewManagerBase::CompleteScriptedPrint(
params->expected_pages_count, params->has_selection, params->margin_type,
params->is_scripted, !render_process_host->IsPdf(),
base::BindOnce(&OnDidScriptedPrint, queue_, std::move(printer_query),
@@ -466,7 +423,7 @@ index 301a4a47fb4dfb6007c2214b04ffdff744cc3043..427a045c310109475b83a229637f4d09
#if BUILDFLAG(ENABLE_PRINT_CONTENT_ANALYSIS)
diff --git a/chrome/browser/printing/print_view_manager_base.h b/chrome/browser/printing/print_view_manager_base.h
index 24a2bc8b26462e6cafbcd5eb89e838b73266ec70..402aab3b14e6987bde0e22e6c82ac438d465330a 100644
index 24a2bc8b26462e6cafbcd5eb89e838b73266ec70..0399e0bafa51d7b02d4dd05e6df8b48fc51d7457 100644
--- a/chrome/browser/printing/print_view_manager_base.h
+++ b/chrome/browser/printing/print_view_manager_base.h
@@ -49,6 +49,8 @@ class PrinterQuery;
@@ -478,19 +435,18 @@ index 24a2bc8b26462e6cafbcd5eb89e838b73266ec70..402aab3b14e6987bde0e22e6c82ac438
// Base class for managing the print commands for a WebContents.
class PrintViewManagerBase : public PrintManager, public PrintJob::Observer {
public:
@@ -82,7 +84,10 @@ class PrintViewManagerBase : public PrintManager, public PrintJob::Observer {
@@ -82,7 +84,9 @@ class PrintViewManagerBase : public PrintManager, public PrintJob::Observer {
// Prints the current document immediately. Since the rendering is
// asynchronous, the actual printing will not be completed on the return of
// this function. Returns false if printing is impossible at the moment.
- virtual bool PrintNow(content::RenderFrameHost* rfh);
+ virtual bool PrintNow(content::RenderFrameHost* rfh,
+ bool silent = true,
+ base::Value::Dict settings = {},
+ CompletionCallback callback = {});
// Like PrintNow(), but for the node under the context menu, instead of the
// entire frame.
@@ -136,8 +141,10 @@ class PrintViewManagerBase : public PrintManager, public PrintJob::Observer {
@@ -136,8 +140,10 @@ class PrintViewManagerBase : public PrintManager, public PrintJob::Observer {
void IsPrintingEnabled(IsPrintingEnabledCallback callback) override;
void ScriptedPrint(mojom::ScriptedPrintParamsPtr params,
ScriptedPrintCallback callback) override;
@@ -501,7 +457,7 @@ index 24a2bc8b26462e6cafbcd5eb89e838b73266ec70..402aab3b14e6987bde0e22e6c82ac438
// Adds and removes observers for `PrintViewManagerBase` events. The order in
// which notifications are sent to observers is undefined. Observers must be
@@ -145,6 +152,14 @@ class PrintViewManagerBase : public PrintManager, public PrintJob::Observer {
@@ -145,6 +151,14 @@ class PrintViewManagerBase : public PrintManager, public PrintJob::Observer {
void AddTestObserver(TestObserver& observer);
void RemoveTestObserver(TestObserver& observer);
@@ -516,7 +472,7 @@ index 24a2bc8b26462e6cafbcd5eb89e838b73266ec70..402aab3b14e6987bde0e22e6c82ac438
protected:
explicit PrintViewManagerBase(content::WebContents* web_contents);
@@ -268,6 +283,7 @@ class PrintViewManagerBase : public PrintManager, public PrintJob::Observer {
@@ -268,6 +282,7 @@ class PrintViewManagerBase : public PrintManager, public PrintJob::Observer {
bool success);
#endif
void CompleteUpdatePrintSettings(
@@ -524,7 +480,7 @@ index 24a2bc8b26462e6cafbcd5eb89e838b73266ec70..402aab3b14e6987bde0e22e6c82ac438
base::Value::Dict job_settings,
std::unique_ptr<PrintSettings> print_settings,
UpdatePrintSettingsCallback callback);
@@ -362,8 +378,11 @@ class PrintViewManagerBase : public PrintManager, public PrintJob::Observer {
@@ -362,8 +377,11 @@ class PrintViewManagerBase : public PrintManager, public PrintJob::Observer {
// The current RFH that is printing with a system printing dialog.
raw_ptr<content::RenderFrameHost> printing_rfh_ = nullptr;
@@ -565,32 +521,6 @@ index 418f53b0db3a2b2624298fca5628aacdd2a99c94..4a110fda692e16ec887cb90c6a2fa611
}
#if BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/browser/ui/webui/print_preview/fake_print_render_frame.cc b/chrome/browser/ui/webui/print_preview/fake_print_render_frame.cc
index ac54519f611f719470c4b7a139a58f489884ede2..89055b6df74389560b6c5884a511897a36425b7b 100644
--- a/chrome/browser/ui/webui/print_preview/fake_print_render_frame.cc
+++ b/chrome/browser/ui/webui/print_preview/fake_print_render_frame.cc
@@ -21,7 +21,7 @@ FakePrintRenderFrame::FakePrintRenderFrame(
FakePrintRenderFrame::~FakePrintRenderFrame() = default;
-void FakePrintRenderFrame::PrintRequestedPages() {}
+void FakePrintRenderFrame::PrintRequestedPages(bool /*silent*/, ::base::Value::Dict /*settings*/) {}
void FakePrintRenderFrame::PrintWithParams(mojom::PrintPagesParamsPtr params,
PrintWithParamsCallback callback) {
diff --git a/chrome/browser/ui/webui/print_preview/fake_print_render_frame.h b/chrome/browser/ui/webui/print_preview/fake_print_render_frame.h
index 1e8f7bedaf679ee19788bf181b33e5d574d1f863..8be77b57580621bf659a6a2a63a2be94a2301c70 100644
--- a/chrome/browser/ui/webui/print_preview/fake_print_render_frame.h
+++ b/chrome/browser/ui/webui/print_preview/fake_print_render_frame.h
@@ -25,7 +25,7 @@ class FakePrintRenderFrame : public mojom::PrintRenderFrame {
private:
// printing::mojom::PrintRenderFrame:
- void PrintRequestedPages() override;
+ void PrintRequestedPages(bool silent, ::base::Value::Dict settings) override;
void PrintWithParams(mojom::PrintPagesParamsPtr params,
PrintWithParamsCallback callback) override;
void PrintForSystemDialog() override;
diff --git a/components/printing/browser/print_manager.cc b/components/printing/browser/print_manager.cc
index 21c81377d32ae8d4185598a7eba88ed1d2063ef0..0767f4e9369e926b1cea99178c1a1975941f1765 100644
--- a/components/printing/browser/print_manager.cc
@@ -617,7 +547,7 @@ index ca71560874a0189068dd11fbc039f5673bf6bd96..a8551d95e64da2afbc1685b2df8f1fc3
mojom::PrintFailureReason reason) override;
diff --git a/components/printing/common/print.mojom b/components/printing/common/print.mojom
index 4ecdb28904fac480cf102fffdff24ae008ac88cf..e8150c95fafd83d7e2fe1f472a35acec3ca6e391 100644
index 4ecdb28904fac480cf102fffdff24ae008ac88cf..0d7d20c1a7920b1961585b345d30d9969411ca9e 100644
--- a/components/printing/common/print.mojom
+++ b/components/printing/common/print.mojom
@@ -302,7 +302,7 @@ union PrintWithParamsResult {
@@ -625,7 +555,7 @@ index 4ecdb28904fac480cf102fffdff24ae008ac88cf..e8150c95fafd83d7e2fe1f472a35acec
// Tells the RenderFrame to switch the CSS to print media type, render every
// requested page, and then switch back the CSS to display media type.
- PrintRequestedPages();
+ PrintRequestedPages(bool silent, mojo_base.mojom.DictionaryValue settings);
+ PrintRequestedPages(mojo_base.mojom.DictionaryValue settings);
// Requests the frame to be printed with specified parameters. This is used
// to programmatically produce PDF by request from the browser (e.g. over
@@ -640,7 +570,7 @@ index 4ecdb28904fac480cf102fffdff24ae008ac88cf..e8150c95fafd83d7e2fe1f472a35acec
PrintingFailed(int32 cookie, PrintFailureReason reason);
diff --git a/components/printing/renderer/print_render_frame_helper.cc b/components/printing/renderer/print_render_frame_helper.cc
index a0ec654b13f82d9c5ff4eb838585485a59c45924..e24f2e209e547685daf18570c0a8a95306c72e2d 100644
index a0ec654b13f82d9c5ff4eb838585485a59c45924..01381687ee4e4c4096c0407ffc211c3e0bc57369 100644
--- a/components/printing/renderer/print_render_frame_helper.cc
+++ b/components/printing/renderer/print_render_frame_helper.cc
@@ -47,6 +47,7 @@
@@ -664,7 +594,7 @@ index a0ec654b13f82d9c5ff4eb838585485a59c45924..e24f2e209e547685daf18570c0a8a953
- Print(web_frame, blink::WebNode(), PrintRequestType::kScripted);
+ Print(web_frame, blink::WebNode(), PrintRequestType::kScripted,
+ false /* silent */, base::Value::Dict() /* new_settings */);
+ base::Value::Dict() /* new_settings */);
if (!weak_this) {
return;
}
@@ -673,7 +603,7 @@ index a0ec654b13f82d9c5ff4eb838585485a59c45924..e24f2e209e547685daf18570c0a8a953
}
-void PrintRenderFrameHelper::PrintRequestedPages() {
+void PrintRenderFrameHelper::PrintRequestedPages(bool silent, base::Value::Dict settings) {
+void PrintRenderFrameHelper::PrintRequestedPages(base::Value::Dict settings) {
ScopedIPC scoped_ipc(weak_ptr_factory_.GetWeakPtr());
if (ipc_nesting_level_ > kAllowedIpcDepthForPrint)
return;
@@ -682,21 +612,20 @@ index a0ec654b13f82d9c5ff4eb838585485a59c45924..e24f2e209e547685daf18570c0a8a953
auto plugin = delegate_->GetPdfElement(frame);
- Print(frame, plugin, PrintRequestType::kRegular);
+ Print(frame, plugin, PrintRequestType::kRegular, silent, std::move(settings));
+ Print(frame, plugin, PrintRequestType::kRegular, std::move(settings));
if (render_frame_gone_) {
return;
@@ -1371,7 +1372,8 @@ void PrintRenderFrameHelper::PrintForSystemDialog() {
@@ -1371,7 +1372,7 @@ void PrintRenderFrameHelper::PrintForSystemDialog() {
}
Print(frame, print_preview_context_.source_node(),
- PrintRequestType::kRegular);
+ PrintRequestType::kRegular, false,
+ base::Value::Dict());
+ PrintRequestType::kRegular, base::Value::Dict());
if (render_frame_gone_) {
return;
}
@@ -1434,6 +1436,8 @@ void PrintRenderFrameHelper::PrintPreview(base::Value::Dict settings) {
@@ -1434,6 +1435,8 @@ void PrintRenderFrameHelper::PrintPreview(base::Value::Dict settings) {
if (ipc_nesting_level_ > kAllowedIpcDepthForPrint)
return;
@@ -705,28 +634,27 @@ index a0ec654b13f82d9c5ff4eb838585485a59c45924..e24f2e209e547685daf18570c0a8a953
print_preview_context_.OnPrintPreview();
#if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -2011,7 +2015,8 @@ void PrintRenderFrameHelper::PrintNode(const blink::WebNode& node) {
@@ -2011,7 +2014,7 @@ void PrintRenderFrameHelper::PrintNode(const blink::WebNode& node) {
}
Print(duplicate_node.GetDocument().GetFrame(), duplicate_node,
- PrintRequestType::kRegular);
+ PrintRequestType::kRegular, false /* silent */,
+ base::Value::Dict() /* new_settings */);
+ PrintRequestType::kRegular, base::Value::Dict());
// Check if `this` is still valid.
if (!weak_this) {
return;
@@ -2027,17 +2032,19 @@ void PrintRenderFrameHelper::PrintNode(const blink::WebNode& node) {
@@ -2027,17 +2030,19 @@ void PrintRenderFrameHelper::PrintNode(const blink::WebNode& node) {
void PrintRenderFrameHelper::Print(blink::WebLocalFrame* frame,
const blink::WebNode& node,
- PrintRequestType print_request_type) {
+ PrintRequestType print_request_type,
+ bool silent,
+ base::Value::Dict settings) {
// If still not finished with earlier print request simply ignore.
if (prep_frame_view_)
return;
+ bool silent = settings.FindBool("silent").value_or(false);
FrameReference frame_ref(frame);
- if (!InitPrintSettings(frame, node)) {
@@ -738,7 +666,7 @@ index a0ec654b13f82d9c5ff4eb838585485a59c45924..e24f2e209e547685daf18570c0a8a953
DidFinishPrinting(PrintingResult::kFailPrintInit);
return;
}
@@ -2058,8 +2065,15 @@ void PrintRenderFrameHelper::Print(blink::WebLocalFrame* frame,
@@ -2058,8 +2063,15 @@ void PrintRenderFrameHelper::Print(blink::WebLocalFrame* frame,
print_pages_params_->params->print_scaling_option;
auto self = weak_ptr_factory_.GetWeakPtr();
@@ -755,7 +683,7 @@ index a0ec654b13f82d9c5ff4eb838585485a59c45924..e24f2e209e547685daf18570c0a8a953
// Check if `this` is still valid.
if (!self)
return;
@@ -2317,25 +2331,33 @@ void PrintRenderFrameHelper::IPCProcessed() {
@@ -2317,25 +2329,33 @@ void PrintRenderFrameHelper::IPCProcessed() {
}
bool PrintRenderFrameHelper::InitPrintSettings(blink::WebLocalFrame* frame,
@@ -796,7 +724,7 @@ index a0ec654b13f82d9c5ff4eb838585485a59c45924..e24f2e209e547685daf18570c0a8a953
}
diff --git a/components/printing/renderer/print_render_frame_helper.h b/components/printing/renderer/print_render_frame_helper.h
index b0ac94751454bd16b4e9bfdc071e2623813133ec..271bd9949a802a370b3619340f3364df14c7fe4a 100644
index b0ac94751454bd16b4e9bfdc071e2623813133ec..74809543b8c1030494638a58c6d27ee69e2867b2 100644
--- a/components/printing/renderer/print_render_frame_helper.h
+++ b/components/printing/renderer/print_render_frame_helper.h
@@ -245,7 +245,7 @@ class PrintRenderFrameHelper
@@ -804,22 +732,21 @@ index b0ac94751454bd16b4e9bfdc071e2623813133ec..271bd9949a802a370b3619340f3364df
// printing::mojom::PrintRenderFrame:
- void PrintRequestedPages() override;
+ void PrintRequestedPages(bool silent, base::Value::Dict settings) override;
+ void PrintRequestedPages(base::Value::Dict settings) override;
void PrintWithParams(mojom::PrintPagesParamsPtr params,
PrintWithParamsCallback callback) override;
#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
@@ -312,7 +312,9 @@ class PrintRenderFrameHelper
@@ -312,7 +312,8 @@ class PrintRenderFrameHelper
// WARNING: |this| may be gone after this method returns.
void Print(blink::WebLocalFrame* frame,
const blink::WebNode& node,
- PrintRequestType print_request_type);
+ PrintRequestType print_request_type,
+ bool silent,
+ base::Value::Dict settings);
// Notification when printing is done - signal tear-down/free resources.
void DidFinishPrinting(PrintingResult result);
@@ -322,7 +324,8 @@ class PrintRenderFrameHelper
@@ -322,7 +323,8 @@ class PrintRenderFrameHelper
// Initialize print page settings with default settings.
// Used only for native printing workflow.
bool InitPrintSettings(blink::WebLocalFrame* frame,

View File

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

View File

@@ -1,26 +0,0 @@
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

@@ -0,0 +1,30 @@
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,9 +3,18 @@ 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',
@@ -23,6 +32,14 @@ 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 }) {
@@ -53,6 +70,65 @@ 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 = {
@@ -167,6 +243,59 @@ 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,
@@ -194,18 +323,6 @@ 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 = {
@@ -252,6 +369,18 @@ 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}.`);
@@ -265,6 +394,19 @@ 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) {
@@ -272,6 +414,10 @@ function runRelease (targetBranch, options) {
buildCircleCI(targetBranch, options);
break;
}
case 'GitHubActions': {
buildGHActions(targetBranch, options);
break;
}
case 'AppVeyor': {
buildAppVeyor(targetBranch, options);
break;
@@ -284,6 +430,8 @@ 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.`);
}
@@ -297,7 +445,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]
Usage: ci-release-build.js [--job=CI_JOB_NAME] [--arch=INDIVIDUAL_ARCH] [--ci=CircleCI|AppVeyor|GitHubActions]
[--ghRelease] [--circleBuildNum=xxx] [--appveyorJobId=xxx] [--commit=sha] TARGET_BRANCH
`);
process.exit(0);

View File

@@ -77,6 +77,7 @@ BrowserWindow::BrowserWindow(gin::Arguments* args,
WebContentsView::Create(isolate, web_preferences);
DCHECK(web_contents_view.get());
window_->AddDraggableRegionProvider(web_contents_view.get());
web_contents_view_.Reset(isolate, web_contents_view.ToV8());
// Save a reference of the WebContents.
gin::Handle<WebContents> web_contents =
@@ -92,7 +93,14 @@ BrowserWindow::BrowserWindow(gin::Arguments* args,
InitWithArgs(args);
// Install the content view after BaseWindow's JS code is initialized.
SetContentView(gin::CreateHandle<View>(isolate, web_contents_view.get()));
// The WebContentsView is added a sibling of BaseWindow's contentView (before
// it in the paint order) so that any views added to BrowserWindow's
// contentView will be painted on top of the BrowserWindow's WebContentsView.
// See https://github.com/electron/electron/pull/41256.
// Note that |GetContentsView|, confusingly, does not refer to the same thing
// as |BaseWindow::GetContentView|.
window()->GetContentsView()->AddChildViewAt(web_contents_view->view(), 0);
window()->GetContentsView()->DeprecatedLayoutImmediately();
// Init window after everything has been setup.
window()->InitFromOptions(options);

View File

@@ -95,6 +95,7 @@ class BrowserWindow : public BaseWindow,
base::CancelableRepeatingClosure window_unresponsive_closure_;
v8::Global<v8::Value> web_contents_;
v8::Global<v8::Value> web_contents_view_;
base::WeakPtr<api::WebContents> api_web_contents_;
base::WeakPtrFactory<BrowserWindow> weak_factory_{this};

View File

@@ -12,6 +12,7 @@
#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"
@@ -33,12 +34,14 @@
#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"
@@ -86,6 +89,7 @@
#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"
@@ -106,6 +110,8 @@
#endif
using content::BrowserThread;
using content::BrowsingDataFilterBuilder;
using content::BrowsingDataRemover;
using content::StoragePartition;
namespace {
@@ -117,25 +123,23 @@ 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 (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;
if (Lookup.contains(type))
storage_mask |= Lookup.at(type);
}
return storage_mask;
}
@@ -152,38 +156,202 @@ uint32_t GetQuotaMask(const std::vector<std::string>& quota_types) {
return quota_mask;
}
constexpr content::BrowsingDataRemover::DataType kClearDataTypeAll = ~0ULL;
constexpr content::BrowsingDataRemover::OriginType kClearOriginTypeAll =
content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB |
content::BrowsingDataRemover::ORIGIN_TYPE_PROTECTED_WEB;
constexpr BrowsingDataRemover::DataType kClearDataTypeAll = ~0ULL;
constexpr BrowsingDataRemover::OriginType kClearOriginTypeAll =
BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB |
BrowsingDataRemover::ORIGIN_TYPE_PROTECTED_WEB;
// 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:
ClearDataObserver(gin_helper::Promise<void> promise,
content::BrowsingDataRemover* remover)
: promise_(std::move(promise)) {
observation_.Observe(remover);
}
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},
});
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));
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);
}
delete this;
}
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 {
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)));
// 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));
}
// 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);
}
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,
@@ -1138,17 +1306,85 @@ v8::Local<v8::Promise> Session::ClearCodeCaches(
return handle;
}
v8::Local<v8::Promise> Session::ClearData(gin::Arguments* args) {
v8::Local<v8::Value> Session::ClearData(gin_helper::ErrorThrower thrower,
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();
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);
BrowsingDataRemover* remover = browser_context_->GetBrowsingDataRemover();
ClearDataTask::Run(remover, std::move(promise), data_type_mask,
std::move(origins), filter_mode, origin_matching_mode);
return promise_handle;
}

View File

@@ -147,7 +147,8 @@ 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::Promise> ClearData(gin::Arguments* args);
v8::Local<v8::Value> ClearData(gin_helper::ErrorThrower thrower,
gin::Arguments* args);
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
base::Value GetSpellCheckerLanguages();
void SetSpellCheckerLanguages(gin_helper::ErrorThrower thrower,

View File

@@ -194,6 +194,22 @@ 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));
}
@@ -234,30 +250,6 @@ 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_);
@@ -439,9 +431,6 @@ 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,7 +48,6 @@ class UtilityProcessWrapper
static raw_ptr<UtilityProcessWrapper> FromProcessId(base::ProcessId pid);
void Shutdown(int exit_code);
void EnsureNetworkServiceInitialized();
// gin::Wrappable
static gin::WrapperInfo kWrapperInfo;
@@ -85,7 +84,6 @@ 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

@@ -76,7 +76,6 @@
#include "mojo/public/cpp/system/platform_handle.h"
#include "ppapi/buildflags/buildflags.h"
#include "printing/buildflags/buildflags.h"
#include "printing/print_job_constants.h"
#include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "shell/browser/api/electron_api_browser_window.h"
@@ -173,10 +172,10 @@
#include "components/printing/browser/print_manager_utils.h"
#include "components/printing/browser/print_to_pdf/pdf_print_result.h"
#include "components/printing/browser/print_to_pdf/pdf_print_utils.h"
#include "printing/backend/print_backend.h" // nogncheck
#include "printing/mojom/print.mojom.h" // nogncheck
#include "printing/mojom/print.mojom.h" // nogncheck
#include "printing/page_range.h"
#include "shell/browser/printing/print_view_manager_electron.h"
#include "shell/browser/printing/printing_utils.h"
#if BUILDFLAG(IS_WIN)
#include "printing/backend/win_helper.h"
@@ -530,96 +529,6 @@ std::optional<base::TimeDelta> GetCursorBlinkInterval() {
return std::nullopt;
}
#if BUILDFLAG(ENABLE_PRINTING)
// This will return false if no printer with the provided device_name can be
// found on the network. We need to check this because Chromium does not do
// sanity checking of device_name validity and so will crash on invalid names.
bool IsDeviceNameValid(const std::u16string& device_name) {
#if BUILDFLAG(IS_MAC)
base::apple::ScopedCFTypeRef<CFStringRef> new_printer_id(
base::SysUTF16ToCFStringRef(device_name));
PMPrinter new_printer = PMPrinterCreateFromPrinterID(new_printer_id.get());
bool printer_exists = new_printer != nullptr;
PMRelease(new_printer);
return printer_exists;
#else
scoped_refptr<printing::PrintBackend> print_backend =
printing::PrintBackend::CreateInstance(
g_browser_process->GetApplicationLocale());
return print_backend->IsValidPrinter(base::UTF16ToUTF8(device_name));
#endif
}
// This function returns a validated device name.
// If the user passed one to webContents.print(), we check that it's valid and
// return it or fail if the network doesn't recognize it. If the user didn't
// pass a device name, we first try to return the system default printer. If one
// isn't set, then pull all the printers and use the first one or fail if none
// exist.
std::pair<std::string, std::u16string> GetDeviceNameToUse(
const std::u16string& device_name) {
#if BUILDFLAG(IS_WIN)
// Blocking is needed here because Windows printer drivers are oftentimes
// not thread-safe and have to be accessed on the UI thread.
ScopedAllowBlockingForElectron allow_blocking;
#endif
if (!device_name.empty()) {
if (!IsDeviceNameValid(device_name))
return std::make_pair("Invalid deviceName provided", std::u16string());
return std::make_pair(std::string(), device_name);
}
scoped_refptr<printing::PrintBackend> print_backend =
printing::PrintBackend::CreateInstance(
g_browser_process->GetApplicationLocale());
std::string printer_name;
printing::mojom::ResultCode code =
print_backend->GetDefaultPrinterName(printer_name);
// We don't want to return if this fails since some devices won't have a
// default printer.
if (code != printing::mojom::ResultCode::kSuccess)
LOG(ERROR) << "Failed to get default printer name";
if (printer_name.empty()) {
printing::PrinterList printers;
if (print_backend->EnumeratePrinters(printers) !=
printing::mojom::ResultCode::kSuccess)
return std::make_pair("Failed to enumerate printers", std::u16string());
if (printers.empty())
return std::make_pair("No printers available on the network",
std::u16string());
printer_name = printers.front().printer_name;
}
return std::make_pair(std::string(), base::UTF8ToUTF16(printer_name));
}
// Copied from
// chrome/browser/ui/webui/print_preview/local_printer_handler_default.cc:L36-L54
scoped_refptr<base::TaskRunner> CreatePrinterHandlerTaskRunner() {
// USER_VISIBLE because the result is displayed in the print preview dialog.
#if !BUILDFLAG(IS_WIN)
static constexpr base::TaskTraits kTraits = {
base::MayBlock(), base::TaskPriority::USER_VISIBLE};
#endif
#if defined(USE_CUPS)
// CUPS is thread safe.
return base::ThreadPool::CreateTaskRunner(kTraits);
#elif BUILDFLAG(IS_WIN)
// Windows drivers are likely not thread-safe and need to be accessed on the
// UI thread.
return content::GetUIThreadTaskRunner({base::TaskPriority::USER_VISIBLE});
#else
// Be conservative on unsupported platforms.
return base::ThreadPool::CreateSingleThreadTaskRunner(kTraits);
#endif
}
#endif
struct UserDataLink : public base::SupportsUserData::Data {
explicit UserDataLink(base::WeakPtr<WebContents> contents)
: web_contents(contents) {}
@@ -2952,7 +2861,6 @@ bool WebContents::IsCurrentlyAudible() {
void WebContents::OnGetDeviceNameToUse(
base::Value::Dict print_settings,
printing::CompletionCallback print_callback,
bool silent,
// <error, device_name>
std::pair<std::string, std::u16string> info) {
// The content::WebContents might be already deleted at this point, and the
@@ -2972,6 +2880,12 @@ void WebContents::OnGetDeviceNameToUse(
// If the user has passed a deviceName use it, otherwise use default printer.
print_settings.Set(printing::kSettingDeviceName, info.second);
if (!print_settings.FindInt(printing::kSettingDpiHorizontal)) {
gfx::Size dpi = GetDefaultPrinterDPI(info.second);
print_settings.Set(printing::kSettingDpiHorizontal, dpi.width());
print_settings.Set(printing::kSettingDpiVertical, dpi.height());
}
auto* print_view_manager =
PrintViewManagerElectron::FromWebContents(web_contents());
if (!print_view_manager)
@@ -2982,7 +2896,7 @@ void WebContents::OnGetDeviceNameToUse(
? focused_frame
: web_contents()->GetPrimaryMainFrame();
print_view_manager->PrintNow(rfh, silent, std::move(print_settings),
print_view_manager->PrintNow(rfh, std::move(print_settings),
std::move(print_callback));
}
@@ -3003,9 +2917,10 @@ void WebContents::Print(gin::Arguments* args) {
return;
}
// Set optional silent printing
// Set optional silent printing.
bool silent = false;
options.Get("silent", &silent);
settings.Set("silent", silent);
bool print_background = false;
options.Get("printBackground", &print_background);
@@ -3133,7 +3048,6 @@ void WebContents::Print(gin::Arguments* args) {
// Set custom dots per inch (dpi)
gin_helper::Dictionary dpi_settings;
int dpi = 72;
if (options.Get("dpi", &dpi_settings)) {
int horizontal = 72;
dpi_settings.Get("horizontal", &horizontal);
@@ -3141,16 +3055,13 @@ void WebContents::Print(gin::Arguments* args) {
int vertical = 72;
dpi_settings.Get("vertical", &vertical);
settings.Set(printing::kSettingDpiVertical, vertical);
} else {
settings.Set(printing::kSettingDpiHorizontal, dpi);
settings.Set(printing::kSettingDpiVertical, dpi);
}
print_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE, base::BindOnce(&GetDeviceNameToUse, device_name),
base::BindOnce(&WebContents::OnGetDeviceNameToUse,
weak_factory_.GetWeakPtr(), std::move(settings),
std::move(callback), silent));
std::move(callback)));
}
// Partially duplicated and modified from

View File

@@ -236,7 +236,6 @@ class WebContents : public ExclusiveAccessContext,
#if BUILDFLAG(ENABLE_PRINTING)
void OnGetDeviceNameToUse(base::Value::Dict print_settings,
printing::CompletionCallback print_callback,
bool silent,
// <error, device_name>
std::pair<std::string, std::u16string> info);
void Print(gin::Arguments* args);

View File

@@ -42,6 +42,7 @@ void FrameSubscriber::AttachToHost(content::RenderWidgetHost* host) {
// Create and configure the video capturer.
gfx::Size size = GetRenderViewSize();
DCHECK(!size.IsEmpty());
video_capturer_ = host_->GetView()->CreateVideoCapturer();
video_capturer_->SetResolutionConstraints(size, size, true);
video_capturer_->SetAutoThrottlingEnabled(false);

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::HasInstance())
if (!SystemNetworkContextManager::GetInstance())
SystemNetworkContextManager::CreateInstance(local_state_.get());
}

View File

@@ -999,10 +999,6 @@ 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,6 +22,7 @@
#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"
@@ -1061,6 +1062,17 @@ 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 =
@@ -1306,6 +1318,17 @@ 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

@@ -469,12 +469,12 @@ void NativeWindowViews::SetGTKDarkThemeEnabled(bool use_dark_theme) {
void NativeWindowViews::SetContentView(views::View* view) {
if (content_view()) {
root_view_.RemoveChildView(content_view());
root_view_.GetMainView()->RemoveChildView(content_view());
}
set_content_view(view);
focused_view_ = view;
root_view_.AddChildView(content_view());
root_view_.DeprecatedLayoutImmediately();
root_view_.GetMainView()->AddChildView(content_view());
root_view_.GetMainView()->DeprecatedLayoutImmediately();
}
void NativeWindowViews::Close() {
@@ -1664,7 +1664,7 @@ std::u16string NativeWindowViews::GetWindowTitle() const {
}
views::View* NativeWindowViews::GetContentsView() {
return &root_view_;
return root_view_.GetMainView();
}
bool NativeWindowViews::ShouldDescendIntoChildForEventHandling(
@@ -1674,7 +1674,7 @@ bool NativeWindowViews::ShouldDescendIntoChildForEventHandling(
}
views::ClientView* NativeWindowViews::CreateClientView(views::Widget* widget) {
return new NativeWindowClientView{widget, GetContentsView(), this};
return new NativeWindowClientView{widget, &root_view_, this};
}
std::unique_ptr<views::NonClientFrameView>

View File

@@ -214,21 +214,8 @@ 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;
}
@@ -236,7 +223,6 @@ 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,16 +41,11 @@ 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. If it has not been
// created yet, NetworkService is called, which will cause the
// SystemNetworkContextManager to be created.
// Gets the global SystemNetworkContextManager instance.
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();
NotificationDismissed(!on_dismissing_);
}
void LibnotifyNotification::OnNotificationView(NotifyNotification* notification,

View File

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

View File

@@ -25,6 +25,7 @@
#include "content/browser/renderer_host/render_widget_host_delegate.h" // nogncheck
#include "content/browser/renderer_host/render_widget_host_owner_delegate.h" // nogncheck
#include "content/common/input/synthetic_gesture.h" // nogncheck
#include "content/common/input/synthetic_gesture_target.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/context_factory.h"

View File

@@ -0,0 +1,126 @@
// Copyright (c) 2024 Microsoft, GmbH.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/printing/printing_utils.h"
#include "base/apple/scoped_typeref.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/browser_process.h"
#include "electron/buildflags/buildflags.h"
#include "printing/backend/print_backend.h" // nogncheck
#include "printing/units.h"
#if BUILDFLAG(IS_MAC)
#include <ApplicationServices/ApplicationServices.h>
#elif BUILDFLAG(LINUX)
#include <gtk/gtk.h>
#endif
namespace electron {
gfx::Size GetDefaultPrinterDPI(const std::u16string& device_name) {
#if BUILDFLAG(IS_MAC)
return gfx::Size(printing::kDefaultMacDpi, printing::kDefaultMacDpi);
#elif BUILDFLAG(IS_WIN)
HDC hdc =
CreateDC(L"WINSPOOL", base::as_wcstr(device_name), nullptr, nullptr);
if (hdc == nullptr)
return gfx::Size(kDefaultPdfDpi, kDefaultPdfDpi);
int dpi_x = GetDeviceCaps(hdc, LOGPIXELSX);
int dpi_y = GetDeviceCaps(hdc, LOGPIXELSY);
DeleteDC(hdc);
return gfx::Size(dpi_x, dpi_y);
#else
GtkPrintSettings* print_settings = gtk_print_settings_new();
int dpi = gtk_print_settings_get_resolution(print_settings);
g_object_unref(print_settings);
return gfx::Size(dpi, dpi);
#endif
}
bool IsDeviceNameValid(const std::u16string& device_name) {
#if BUILDFLAG(IS_MAC)
base::apple::ScopedCFTypeRef<CFStringRef> new_printer_id(
base::SysUTF16ToCFStringRef(device_name));
PMPrinter new_printer = PMPrinterCreateFromPrinterID(new_printer_id.get());
bool printer_exists = new_printer != nullptr;
PMRelease(new_printer);
return printer_exists;
#else
scoped_refptr<printing::PrintBackend> print_backend =
printing::PrintBackend::CreateInstance(
g_browser_process->GetApplicationLocale());
return print_backend->IsValidPrinter(base::UTF16ToUTF8(device_name));
#endif
}
std::pair<std::string, std::u16string> GetDeviceNameToUse(
const std::u16string& device_name) {
#if BUILDFLAG(IS_WIN)
// Blocking is needed here because Windows printer drivers are oftentimes
// not thread-safe and have to be accessed on the UI thread.
ScopedAllowBlockingForElectron allow_blocking;
#endif
if (!device_name.empty()) {
if (!IsDeviceNameValid(device_name))
return std::make_pair("Invalid deviceName provided", std::u16string());
return std::make_pair(std::string(), device_name);
}
scoped_refptr<printing::PrintBackend> print_backend =
printing::PrintBackend::CreateInstance(
g_browser_process->GetApplicationLocale());
std::string printer_name;
printing::mojom::ResultCode code =
print_backend->GetDefaultPrinterName(printer_name);
// We don't want to return if this fails since some devices won't have a
// default printer.
if (code != printing::mojom::ResultCode::kSuccess)
LOG(ERROR) << "Failed to get default printer name";
if (printer_name.empty()) {
printing::PrinterList printers;
if (print_backend->EnumeratePrinters(printers) !=
printing::mojom::ResultCode::kSuccess)
return std::make_pair("Failed to enumerate printers", std::u16string());
if (printers.empty())
return std::make_pair("No printers available on the network",
std::u16string());
printer_name = printers.front().printer_name;
}
return std::make_pair(std::string(), base::UTF8ToUTF16(printer_name));
}
// Copied from
// chrome/browser/ui/webui/print_preview/local_printer_handler_default.cc:L36-L54
scoped_refptr<base::TaskRunner> CreatePrinterHandlerTaskRunner() {
// USER_VISIBLE because the result is displayed in the print preview dialog.
#if !BUILDFLAG(IS_WIN)
static constexpr base::TaskTraits kTraits = {
base::MayBlock(), base::TaskPriority::USER_VISIBLE};
#endif
#if defined(USE_CUPS)
// CUPS is thread safe.
return base::ThreadPool::CreateTaskRunner(kTraits);
#elif BUILDFLAG(IS_WIN)
// Windows drivers are likely not thread-safe and need to be accessed on the
// UI thread.
return content::GetUIThreadTaskRunner({base::TaskPriority::USER_VISIBLE});
#else
// Be conservative on unsupported platforms.
return base::ThreadPool::CreateSingleThreadTaskRunner(kTraits);
#endif
}
} // namespace electron

View File

@@ -0,0 +1,41 @@
// Copyright (c) 2024 Microsoft, GmbH.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_PRINTING_PRINTING_UTILS_H_
#define ELECTRON_SHELL_BROWSER_PRINTING_PRINTING_UTILS_H_
#include <string>
#include "base/memory/scoped_refptr.h"
#include "base/task/task_runner.h"
namespace gfx {
class Size;
}
namespace electron {
// This function returns the per-platform default printer's DPI.
gfx::Size GetDefaultPrinterDPI(const std::u16string& device_name);
// This will return false if no printer with the provided device_name can be
// found on the network. We need to check this because Chromium does not do
// sanity checking of device_name validity and so will crash on invalid names.
bool IsDeviceNameValid(const std::u16string& device_name);
// This function returns a validated device name.
// If the user passed one to webContents.print(), we check that it's valid and
// return it or fail if the network doesn't recognize it. If the user didn't
// pass a device name, we first try to return the system default printer. If one
// isn't set, then pull all the printers and use the first one or fail if none
// exist.
std::pair<std::string, std::u16string> GetDeviceNameToUse(
const std::u16string& device_name);
// This function creates a task runner for use with printing tasks.
scoped_refptr<base::TaskRunner> CreatePrinterHandlerTaskRunner();
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_PRINTING_PRINTING_UTILS_H_

View File

@@ -35,7 +35,9 @@ std::unique_ptr<content::SerialChooser> ElectronSerialDelegate::RunChooser(
if (controller) {
DeleteControllerForFrame(frame);
}
AddControllerForFrame(frame, std::move(filters), std::move(callback));
AddControllerForFrame(frame, std::move(filters),
std::move(allowed_bluetooth_service_class_ids),
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
@@ -106,12 +108,14 @@ 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(callback), web_contents,
weak_factory_.GetWeakPtr());
render_frame_host, std::move(filters),
std::move(allowed_bluetooth_service_class_ids), 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,6 +67,7 @@ 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,6 +11,9 @@
#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"
@@ -58,14 +61,57 @@ 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()) {
@@ -93,10 +139,11 @@ api::Session* SerialChooserController::GetSession() {
void SerialChooserController::OnPortAdded(
const device::mojom::SerialPortInfo& port) {
if (!FilterMatchesAny(port))
if (!DisplayDevice(port))
return;
ports_.push_back(port.Clone());
api::Session* session = GetSession();
if (session) {
session->Emit("serial-port-added", port.Clone(), web_contents());
@@ -105,9 +152,8 @@ void SerialChooserController::OnPortAdded(
void SerialChooserController::OnPortRemoved(
const device::mojom::SerialPortInfo& port) {
const auto it = std::find_if(
ports_.begin(), ports_.end(),
[&port](const auto& ptr) { return ptr->token == port.token; });
const auto it = base::ranges::find(ports_, port.token,
&device::mojom::SerialPortInfo::token);
if (it != ports_.end()) {
api::Session* session = GetSession();
if (session) {
@@ -152,7 +198,7 @@ void SerialChooserController::OnGetDevices(
});
for (auto& port : ports) {
if (FilterMatchesAny(*port))
if (DisplayDevice(*port))
ports_.push_back(std::move(port));
}
@@ -169,21 +215,17 @@ void SerialChooserController::OnGetDevices(
}
}
bool SerialChooserController::FilterMatchesAny(
bool SerialChooserController::DisplayDevice(
const device::mojom::SerialPortInfo& port) const {
if (filters_.empty())
return true;
if (filters_.empty()) {
return BluetoothPortIsAllowed(allowed_bluetooth_service_class_ids_, port);
}
for (const auto& filter : filters_) {
if (filter->has_vendor_id &&
(!port.has_vendor_id || filter->vendor_id != port.vendor_id)) {
continue;
if (FilterMatchesPort(*filter, port) &&
BluetoothPortIsAllowed(allowed_bluetooth_service_class_ids_, port)) {
return true;
}
if (filter->has_product_id &&
(!port.has_product_id || filter->product_id != port.product_id)) {
continue;
}
return true;
}
return false;

View File

@@ -34,6 +34,7 @@ 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);
@@ -55,11 +56,12 @@ class SerialChooserController final : public SerialChooserContext::PortObserver,
private:
api::Session* GetSession();
void OnGetDevices(std::vector<device::mojom::SerialPortInfoPtr> ports);
bool FilterMatchesAny(const device::mojom::SerialPortInfo& port) const;
bool DisplayDevice(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,6 +28,7 @@
#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"
@@ -139,4 +140,8 @@ bool DevToolsManagerDelegate::HasBundledFrontendResources() {
return true;
}
content::BrowserContext* DevToolsManagerDelegate::GetDefaultBrowserContext() {
return ElectronBrowserContext::From("", false);
}
} // namespace electron

View File

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

View File

@@ -9,18 +9,12 @@
#include "content/public/common/input/native_web_keyboard_event.h"
#include "shell/browser/native_window.h"
#include "shell/browser/ui/views/menu_bar.h"
#include "ui/views/layout/box_layout.h"
namespace electron {
namespace {
// The menu bar height in pixels.
#if BUILDFLAG(IS_WIN)
const int kMenuBarHeight = 20;
#else
const int kMenuBarHeight = 25;
#endif
bool IsAltKey(const content::NativeWebKeyboardEvent& event) {
return event.windows_key_code == ui::VKEY_MENU;
}
@@ -41,6 +35,12 @@ RootView::RootView(NativeWindow* window)
: window_(window),
last_focused_view_tracker_(std::make_unique<views::ViewTracker>()) {
set_owned_by_client();
views::BoxLayout* layout =
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
main_view_ = AddChildView(std::make_unique<views::View>());
main_view_->SetUseDefaultFillLayout(true);
layout->SetFlexForView(main_view_, 1);
}
RootView::~RootView() = default;
@@ -77,7 +77,7 @@ bool RootView::HasMenu() const {
}
int RootView::GetMenuBarHeight() const {
return kMenuBarHeight;
return menu_bar_ ? menu_bar_->GetPreferredSize().height() : 0;
}
void RootView::SetAutoHideMenuBar(bool auto_hide) {
@@ -90,10 +90,8 @@ void RootView::SetMenuBarVisibility(bool visible) {
menu_bar_visible_ = visible;
if (visible) {
DCHECK_EQ(children().size(), 1ul);
AddChildView(menu_bar_.get());
AddChildViewAt(menu_bar_.get(), 0);
} else {
DCHECK_EQ(children().size(), 2ul);
RemoveChildView(menu_bar_.get());
}
@@ -166,21 +164,6 @@ void RootView::ResetAltState() {
menu_bar_alt_pressed_ = false;
}
void RootView::Layout(PassKey) {
if (!window_->content_view()) // Not ready yet.
return;
const auto menu_bar_bounds =
menu_bar_visible_ ? gfx::Rect(0, 0, size().width(), kMenuBarHeight)
: gfx::Rect();
if (menu_bar_)
menu_bar_->SetBoundsRect(menu_bar_bounds);
window_->content_view()->SetBoundsRect(
gfx::Rect(0, menu_bar_visible_ ? menu_bar_bounds.bottom() : 0,
size().width(), size().height() - menu_bar_bounds.height()));
}
gfx::Size RootView::GetMinimumSize() const {
return window_->GetMinimumSize();
}

View File

@@ -46,8 +46,9 @@ class RootView : public views::View {
void RegisterAcceleratorsWithFocusManager(ElectronMenuModel* menu_model);
void UnregisterAcceleratorsWithFocusManager();
views::View* GetMainView() { return main_view_; }
// views::View:
void Layout(PassKey) override;
gfx::Size GetMinimumSize() const override;
gfx::Size GetMaximumSize() const override;
bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
@@ -62,6 +63,9 @@ class RootView : public views::View {
bool menu_bar_visible_ = false;
bool menu_bar_alt_pressed_ = false;
// Main view area.
raw_ptr<views::View> main_view_;
// Map from accelerator to menu item's command id.
accelerator_util::AcceleratorTable accelerator_table_;

View File

@@ -39,6 +39,14 @@
"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"],
@@ -68,5 +76,10 @@
"scripting": {
"dependencies": ["permission:scripting"],
"contexts": ["blessed_extension"]
},
"scripting.globalParams": {
"channel": "trunk",
"dependencies": ["permission:scripting"],
"contexts": ["content_script"]
}
}

View File

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

View File

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

View File

@@ -4,10 +4,34 @@
#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)) {
: v8_ref_(isolate, v8::External::New(isolate, this)),
dispose_observer_(gin::PerIsolateData::From(isolate), this) {
v8_ref_.SetWeak(this, &CallbackHolderBase::FirstWeakCallback,
v8::WeakCallbackType::kParameter);
}
@@ -33,4 +57,28 @@ 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,6 +12,7 @@
#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"
@@ -25,8 +26,9 @@
namespace gin_helper {
enum CreateFunctionTemplateFlags {
HolderIsFirstArgument = 1 << 0,
struct InvokerOptions {
bool holder_is_first_argument = false;
const char* holder_type = nullptr; // Null if unknown or not applicable.
};
template <typename T>
@@ -43,64 +45,91 @@ 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.
// 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.
// 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,
const base::RepeatingCallback<Sig>& callback,
int flags)
: CallbackHolderBase(isolate), callback(callback), flags(flags) {}
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;
base::RepeatingCallback<Sig> callback;
int flags = 0;
InvokerOptions invoker_options;
private:
virtual ~CallbackHolder() = default;
~CallbackHolder() override = default;
};
template <typename T>
bool GetNextArgument(gin::Arguments* args,
int create_flags,
const InvokerOptions& invoker_options,
bool is_first,
T* result) {
if (is_first && (create_flags & HolderIsFirstArgument) != 0) {
if (is_first && invoker_options.holder_is_first_argument) {
return args->GetHolder(result);
} else {
return args->GetNext(result);
}
}
// Support std::optional as output, which would be empty and do not throw error
// when conversion to T fails.
// Electron-specific GetNextArgument that supports std::optional.
template <typename T>
bool GetNextArgument(gin::Arguments* args,
int create_flags,
const InvokerOptions& invoker_options,
bool is_first,
std::optional<T>* result) {
T converted;
@@ -110,10 +139,36 @@ 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,
int create_flags,
const InvokerOptions& invoker_options,
bool is_first,
gin::Arguments* result) {
*result = *args;
return true;
}
inline bool GetNextArgument(gin::Arguments* args,
const InvokerOptions& invoker_options,
bool is_first,
gin::Arguments** result) {
*result = args;
@@ -122,71 +177,65 @@ 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,
int create_flags,
const InvokerOptions& invoker_options,
bool is_first,
v8::Isolate** result) {
*result = args->isolate();
return true;
}
// 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...>;
};
// Throws an error indicating conversion failure.
void ThrowConversionError(gin::Arguments* args,
const InvokerOptions& invoker_options,
size_t index);
// Class template for extracting and storing single argument for callback
// at position |index|.
template <size_t index, typename ArgType>
template <size_t index, typename ArgType, typename = void>
struct ArgumentHolder {
using ArgLocalType = typename CallbackParamTraits<ArgType>::LocalType;
ArgLocalType value;
bool ok = false;
ArgumentHolder(gin::Arguments* args, int create_flags) {
ArgumentHolder(gin::Arguments* args, const InvokerOptions& invoker_options) {
v8::Local<v8::Object> holder;
if (index == 0 && (create_flags & HolderIsFirstArgument) &&
if (index == 0 && invoker_options.holder_is_first_argument &&
args->GetHolder(&holder) &&
gin_helper::Destroyable::IsDestroyed(holder)) {
args->ThrowTypeError("Object has been destroyed");
return;
}
ok = GetNextArgument(args, create_flags, index == 0, &value);
ok = GetNextArgument(args, invoker_options, index == 0, &value);
if (!ok) {
// 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();
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);
}
}
};
@@ -194,24 +243,19 @@ 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<IndicesHolder<indices...>, ArgTypes...>
class Invoker<std::index_sequence<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, 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;
}
Invoker(gin::Arguments* args, const InvokerOptions& invoker_options)
: ArgumentHolder<indices, ArgTypes>(args, invoker_options)...,
args_(args) {}
bool IsOK() { return And(ArgumentHolder<indices, ArgTypes>::ok...); }
@@ -252,46 +296,89 @@ struct Dispatcher {};
template <typename ReturnType, typename... ArgTypes>
struct Dispatcher<ReturnType(ArgTypes...)> {
static void DispatchToCallback(
const v8::FunctionCallbackInfo<v8::Value>& info) {
gin::Arguments args(info);
static void DispatchToCallbackImpl(gin::Arguments* args) {
v8::Local<v8::External> v8_holder;
args.GetData(&v8_holder);
CHECK(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 = typename IndicesGenerator<sizeof...(ArgTypes)>::type;
Invoker<Indices, ArgTypes...> invoker(&args, holder->flags);
using Indices = std::index_sequence_for<ArgTypes...>;
Invoker<Indices, ArgTypes...> invoker(args, holder->invoker_options);
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.
// 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.
//
// 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,
const base::RepeatingCallback<Sig> callback,
int callback_flags = 0) {
base::RepeatingCallback<Sig> callback,
InvokerOptions invoker_options = {}) {
typedef CallbackHolder<Sig> HolderT;
HolderT* holder = new HolderT(isolate, callback, callback_flags);
HolderT* holder =
new HolderT(isolate, std::move(callback), std::move(invoker_options));
return v8::FunctionTemplate::New(isolate,
&Dispatcher<Sig>::DispatchToCallback,
gin::ConvertToV8<v8::Local<v8::External>>(
isolate, holder->GetHandle(isolate)));
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))};
}
// Base template - used only for non-member function pointers. Other types
@@ -326,9 +413,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) {
int flags = HolderIsFirstArgument;
InvokerOptions invoker_options = {.holder_is_first_argument = true};
return gin_helper::CreateFunctionTemplate(
isolate, base::BindRepeating(callback), flags);
isolate, base::BindRepeating(callback), std::move(invoker_options));
}
};

View File

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

View File

@@ -68,19 +68,16 @@ 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,11 +57,8 @@ class NodeService : public node::mojom::NodeService {
NodeService(const NodeService&) = delete;
NodeService& operator=(const NodeService&) = delete;
// mojom::NodeService:
// mojom::NodeService implementation:
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,10 +15,11 @@ 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

@@ -31,7 +31,7 @@ const isScaleFactorRounding = () => {
const expectBoundsEqual = (actual: any, expected: any) => {
if (!isScaleFactorRounding()) {
expect(expected).to.deep.equal(actual);
expect(actual).to.deep.equal(expected);
} else if (Array.isArray(actual)) {
expect(actual[0]).to.be.closeTo(expected[0], 1);
expect(actual[1]).to.be.closeTo(expected[1], 1);

View File

@@ -177,6 +177,38 @@ 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

@@ -616,6 +616,25 @@ 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,12 +935,11 @@ describe('net module', () => {
response.end();
});
const serverUrl = url.parse(serverUrlUnparsed);
const options = {
const urlRequest = net.request({
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 localstorage data', async () => {
it('clears all data when no options supplied', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } });
await w.loadFile(path.join(fixtures, 'api', 'localstorage.html'));
@@ -1623,7 +1623,8 @@ describe('session module', () => {
expect(await w.webContents.executeJavaScript('localStorage.length')).to.equal(0);
});
it('clears localstorage data when called twice in parallel', async () => {
it('clears all data when no options supplied, called twice in parallel', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } });
await w.loadFile(path.join(fixtures, 'api', 'localstorage.html'));
@@ -1638,5 +1639,92 @@ 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,6 +548,9 @@ 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 });
@@ -559,44 +562,32 @@ describe('webContents module', () => {
expect(result).to.deep.equal({ url: '', title: '' });
});
it('should fetch navigation entry given a valid index', async () => {
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' });
});
await w.loadURL(urlPage1);
const result = w.webContents.navigationHistory.getEntryAtIndex(0);
expect(result).to.deep.equal({ url: urlPage1, title: 'Page 1' });
});
it('should return null given an invalid index larger than history length', async () => {
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();
});
await w.loadURL(urlPage1);
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('https://www.google.com');
w.webContents.on('did-finish-load', async () => {
const result = w.webContents.navigationHistory.getEntryAtIndex(-1);
expect(result).to.be.null();
});
await w.loadURL(urlPage1);
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('https://www.google.com');
w.webContents.on('did-finish-load', async () => {
expect(w.webContents.navigationHistory.getActiveIndex()).to.equal(0);
});
await w.loadURL(urlPage1);
expect(w.webContents.navigationHistory.getActiveIndex()).to.equal(0);
});
it('should return valid active index after a multiple page visits', async () => {
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;
await w.loadURL(urlPage1);
await w.loadURL(urlPage2);
await w.loadURL(urlPage3);
expect(w.webContents.navigationHistory.getActiveIndex()).to.equal(2);
});
@@ -608,20 +599,14 @@ describe('webContents module', () => {
describe('navigationHistory.length() API', () => {
it('should return valid history length after a single page visit', async () => {
await w.loadURL('https://www.google.com');
w.webContents.on('did-finish-load', async () => {
expect(w.webContents.navigationHistory.length()).to.equal(1);
});
await w.loadURL(urlPage1);
expect(w.webContents.navigationHistory.length()).to.equal(1);
});
it('should return valid history length after a multiple page visits', async () => {
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;
await w.loadURL(urlPage1);
await w.loadURL(urlPage2);
await w.loadURL(urlPage3);
expect(w.webContents.navigationHistory.length()).to.equal(3);
});

View File

@@ -1383,6 +1383,138 @@ 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,6 +1285,16 @@ 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

@@ -0,0 +1,50 @@
<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,6 +20,27 @@ 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,6 +19,11 @@ 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

@@ -14,8 +14,6 @@ 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,
@@ -2107,9 +2105,8 @@ describe('<webview> tag', function () {
}
});
// 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()', () => {
// FIXME: This test is flaking constantly on Linux and macOS.
xdescribe('<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.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==
"@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==
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.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==
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==
dependencies:
bytes "3.1.2"
content-type "~1.0.4"
content-type "~1.0.5"
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
@@ -1613,7 +1613,7 @@ body-parser@1.20.1:
iconv-lite "0.4.24"
on-finished "2.4.1"
qs "6.11.0"
raw-body "2.5.1"
raw-body "2.5.2"
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:
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==
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==
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.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
cookie@0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
core-util-is@~1.0.0:
version "1.0.2"
@@ -2071,14 +2071,7 @@ debug@^3.1.0, debug@^3.2.7:
dependencies:
ms "^2.1.1"
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:
debug@^4.0.0, 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==
@@ -2808,17 +2801,17 @@ execa@^4.0.1:
signal-exit "^3.0.2"
strip-final-newline "^2.0.0"
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==
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==
dependencies:
accepts "~1.3.8"
array-flatten "1.1.1"
body-parser "1.20.1"
body-parser "1.20.2"
content-disposition "0.5.4"
content-type "~1.0.4"
cookie "0.5.0"
cookie "0.6.0"
cookie-signature "1.0.6"
debug "2.6.9"
depd "2.0.0"
@@ -5243,10 +5236,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.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==
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==
dependencies:
bytes "3.1.2"
http-errors "2.0.0"