Compare commits

...

31 Commits

Author SHA1 Message Date
trop[bot]
0fe0a90341 chore: remove deprecated webContents.getPrinters() (#39735)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: David Sanders <dsanders11@ucsbalum.com>
2023-09-07 10:19:38 -04:00
trop[bot]
f3dcd3b287 fix: restore flag that allowed websockets to not be backgrounded (#39755)
* fix: restore flag that allowed websockets to not be backgrounded

Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>

* chore: update patches

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
2023-09-07 09:16:41 -04:00
trop[bot]
05de1d7906 fix: prevent errors when extensions listen for chrome.tabs events (#39771)
* fix: prevent errors when extensions listen for chrome.tabs events

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

* chore: mark events unsupported in Electron

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-09-07 14:13:35 +02:00
trop[bot]
c14f5369af fix: devtools allow restoring saved dock state on Windows (#39767)
* fix: devtools allow restoring saved dock state on Windows

Co-authored-by: deepak1556 <hop2deep@gmail.com>

* chore: address feedback

Co-authored-by: deepak1556 <hop2deep@gmail.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: deepak1556 <hop2deep@gmail.com>
2023-09-07 11:42:02 +02:00
trop[bot]
3901b38e01 build: update appveyor image to latest version (#39750)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: jkleinsc <jkleinsc@users.noreply.github.com>
2023-09-06 14:55:19 -07:00
trop[bot]
e14b09c6c5 test: fix flaky content tracing test (#39731)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2023-09-06 19:24:04 +02:00
trop[bot]
e4ecade9cb build: fix depot_tools patch application (#39743)
build: fix depot_tools patch application

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-09-06 11:38:30 -04:00
trop[bot]
04fad161e0 docs: use node: imports for node core modules (#39694)
docs: use `node:` imports for node builtin modules

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Erick Zhao <erick@hotmail.ca>
2023-09-04 10:30:07 +02:00
trop[bot]
89f8c0efed docs: fix return typing of ses.getExtension (#39699)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: David Sanders <dsanders11@ucsbalum.com>
2023-09-03 10:42:53 +02:00
trop[bot]
48c537e51e feat: support chrome.scripting extension APIs (#39675)
feat: support chrome.scripting extension APIs

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-08-31 10:39:34 -04:00
trop[bot]
3d3852343c feat: add more extensions manifest key options (#39672)
feat: add more manifest key options

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-08-31 10:38:20 -04:00
trop[bot]
d51a1e4c23 feat: enable WaylandWindowDecorations by default (#39644)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Athul Iddya <athul@iddya.com>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2023-08-31 10:37:00 -04:00
trop[bot]
43d0dcfc27 fix: promise resolved to early when browser initiated in-page navigation v2 (#39680)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Tomasz Malinowski <tomasz@openfin.co>
2023-08-30 13:09:33 -04:00
trop[bot]
056c343c04 docs: fix the minimum supported macOS version to Catalina (#39683)
docs: update the minimum supported macOS version to Catalina

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Milan Burda <milan.burda@gmail.com>
2023-08-29 12:18:20 -04:00
trop[bot]
5707be53af fix: webview exiting fullscreen presentation mode (#39660)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-08-28 11:04:21 +09:00
trop[bot]
e1465f6723 fix: ensure windows respect fullscreenability with different resizability values (#39640)
* fix: ensure child windows respect fullscreenability/resizability when parent is fullscreen

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

* test: add an extra resize test

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-08-25 19:15:42 +02:00
John Kleinschmidt
c406384e8c ci: explicitly use python3 to start goma (#39651)
ci: explicitly use python3 to start goma (#39650)

* ci: explicitly use python3 to start goma

* ci: explicitly use python3 for goma

(cherry picked from commit 83760bd5c6)
2023-08-24 23:18:33 -04:00
trop[bot]
c5ce6de82b fix: assert module in the renderer process (#39623)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-08-24 20:52:18 -04:00
trop[bot]
d89b7f0a4e feat: allow headers to be sent with webContents.downloadURL() (#39560)
feat: allow headers to be sent with webContents.downloadURL()

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-08-24 11:04:25 -04:00
trop[bot]
dfbd4c4335 fix: ensure BrowserView bounds are always relative to window (#39627)
fix: ensure BrowserView bounds are always relative to window

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-08-24 10:46:08 -04:00
trop[bot]
d79189056d docs: mention alternative tooling (#39637)
* docs: mention alternative tooling

Co-authored-by: Erick Zhao <erick@hotmail.ca>

* Update forge-overview.md

Co-authored-by: Erick Zhao <erick@hotmail.ca>

* Update forge-overview.md

Co-authored-by: Erick Zhao <erick@hotmail.ca>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Erick Zhao <erick@hotmail.ca>
2023-08-24 15:43:11 +02:00
trop[bot]
6fd069231f fix: instantiate tab video tracks from BrowserCaptureMediaStreamTrack (#39619)
return BrowserCaptureMediaStreamTrack instead of MediaStreamTrack

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: brhenrique <bruno.d@miro.com>
2023-08-23 22:13:08 +02:00
trop[bot]
56e749782e refactor: node::Environment self-cleanup (#39628)
* chore: savepoint

Co-authored-by: Charles Kerr <charles@charleskerr.com>

* chore: turn raw_ptr tests back off

Co-authored-by: Charles Kerr <charles@charleskerr.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
2023-08-23 12:25:20 -05:00
trop[bot]
864dd4af40 fix: chrome.tabs 'url' and 'title' are privileged information (#39608)
fix: tabs url and title are privileged information

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-08-22 23:02:08 +02:00
trop[bot]
3d31570f8d fix: dangling raw_ptr in ElectronBrowserMainParts dtor (#39593)
* fix: dangling raw_ptr in ElectronBrowserMainParts dtor

Co-authored-by: Charles Kerr <charles@charleskerr.com>

* fixup! fix: dangling raw_ptr in ElectronBrowserMainParts dtor

Browser::WhenReady() holds a reference to JsEnv isolate so must come after

Co-authored-by: Charles Kerr <charles@charleskerr.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
2023-08-21 15:54:31 +02:00
trop[bot]
3411959886 fix: chrome://gpu failing to load (#39583)
fix: chrome://gpu failing to load

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-08-21 15:54:17 +02:00
trop[bot]
b0b1f2c727 fix: use tiled edges to calculate frame inset sizes in Linux (#39570)
Adapt to the window frame size calculation changes in CL 3970920 by
setting the inset sizes to 0 for tiled edges.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Athul Iddya <athul@iddya.com>
2023-08-21 11:42:54 +09:00
trop[bot]
4128e9f0e0 refactor: prefer Sorted variant of MakeFixedFlatSet() (#39564)
perf: prefer Sorted variant of MakeFixedFlatSet()

https://chromium-review.googlesource.com/c/chromium/src/+/4660000
says that the sorted version is simpler at compile time because it
can skip MakeFixedFlatSet()'s compile-time dynamic sorting.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
2023-08-21 10:06:15 +09:00
trop[bot]
0bd5e36411 docs: note macOS bounds Tray offset (#39552)
* docs: note macOS bounds Tray offset

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

* Update docs/api/browser-window.md

Co-authored-by: David Sanders <dsanders11@ucsbalum.com>

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-08-17 13:57:32 +02:00
trop[bot]
c97a4ce691 fix: destruction order of js env fields (#39549)
isolate_ depends on isolate_holder_ and so must be destroyed first.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
2023-08-17 11:03:03 +02:00
trop[bot]
fbb982350b docs: add missing webview render-process-gone event (#39543)
docs: add mising webview 'render-process-gone' event

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Milan Burda <milan.burda@gmail.com>
2023-08-17 10:33:11 +02:00
110 changed files with 3829 additions and 657 deletions

View File

@@ -249,19 +249,19 @@ step-depot-tools-get: &step-depot-tools-get
cd depot_tools
cat > gclient.diff \<< 'EOF'
diff --git a/gclient.py b/gclient.py
index 3a9c5c6..f222043 100755
index c305c248..e6e0fbdc 100755
--- a/gclient.py
+++ b/gclient.py
@@ -712,7 +712,8 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
if dep_type == 'cipd':
cipd_root = self.GetCipdRoot()
- for package in dep_value.get('packages', []):
+ packages = dep_value.get('packages', [])
+ for package in (x for x in packages if "infra/3pp/tools/swift-format" not in x.get('package')):
deps_to_add.append(
CipdDependency(
parent=self,
@@ -735,7 +735,8 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
if dep_type == 'cipd':
cipd_root = self.GetCipdRoot()
- for package in dep_value.get('packages', []):
+ packages = dep_value.get('packages', [])
+ for package in (x for x in packages if "infra/3pp/tools/swift-format" not in x.get('package')):
deps_to_add.append(
CipdDependency(parent=self,
name=name,
EOF
git apply --3way gclient.diff
fi

View File

@@ -38,7 +38,7 @@ For more installation options and troubleshooting tips, see
Each Electron release provides binaries for macOS, Windows, and Linux.
* macOS (High Sierra and up): Electron provides 64-bit Intel and ARM binaries for macOS. Apple Silicon support was added in Electron 11.
* macOS (Catalina and up): Electron provides 64-bit Intel and ARM binaries for macOS. Apple Silicon support was added in Electron 11.
* Windows (Windows 10 and up): Electron provides `ia32` (`x86`), `x64` (`amd64`), and `arm64` binaries for Windows. Windows on ARM support was added in Electron 5.0.8. Support for Windows 7, 8 and 8.1 was [removed in Electron 23, in line with Chromium's Windows deprecation policy](https://www.electronjs.org/blog/windows-7-to-8-1-deprecation-notice).
* Linux: The prebuilt binaries of Electron are built on Ubuntu 20.04. They have also been verified to work on:
* Ubuntu 14.04 and newer
@@ -78,7 +78,7 @@ binary. Use this to spawn Electron from Node scripts:
```javascript
const electron = require('electron')
const proc = require('child_process')
const proc = require('node:child_process')
// will print something similar to /Users/maf/.../Electron
console.log(electron)

View File

@@ -29,7 +29,7 @@
version: 1.0.{build}
build_cloud: electronhq-16-core
image: e-118.0.5949.0
image: e-118.0.5991.0
environment:
GIT_CACHE_PATH: C:\Users\appveyor\libcc_cache
ELECTRON_OUT_DIR: Default
@@ -118,7 +118,7 @@ for:
- ps: .\src\electron\script\start-goma.ps1 -gomaDir $env:LOCAL_GOMA_DIR
- ps: >-
if (Test-Path 'env:RAW_GOMA_AUTH') {
$goma_login = python $env:LOCAL_GOMA_DIR\goma_auth.py info
$goma_login = python3 $env:LOCAL_GOMA_DIR\goma_auth.py info
if ($goma_login -eq 'Login as Fermi Planck') {
Write-warning "Goma authentication is correct";
} else {
@@ -168,7 +168,7 @@ for:
- ninja -C out/Default electron:hunspell_dictionaries_zip
- ninja -C out/Default electron:electron_chromedriver_zip
- ninja -C out/Default third_party/electron_node:headers
- python %LOCAL_GOMA_DIR%\goma_ctl.py stat
- python3 %LOCAL_GOMA_DIR%\goma_ctl.py stat
- ps: >-
Get-CimInstance -Namespace root\cimv2 -Class Win32_product | Select vendor, description, @{l='install_location';e='InstallLocation'}, @{l='install_date';e='InstallDate'}, @{l='install_date_2';e='InstallDate2'}, caption, version, name, @{l='sku_number';e='SKUNumber'} | ConvertTo-Json | Out-File -Encoding utf8 -FilePath .\installed_software.json
- python3 electron/build/profile_toolchain.py --output-json=out/Default/windows_toolchain_profile.json

View File

@@ -29,7 +29,7 @@
version: 1.0.{build}
build_cloud: electronhq-16-core
image: e-118.0.5949.0
image: e-118.0.5991.0
environment:
GIT_CACHE_PATH: C:\Users\appveyor\libcc_cache
ELECTRON_OUT_DIR: Default
@@ -116,7 +116,7 @@ for:
- ps: .\src\electron\script\start-goma.ps1 -gomaDir $env:LOCAL_GOMA_DIR
- ps: >-
if (Test-Path 'env:RAW_GOMA_AUTH') {
$goma_login = python $env:LOCAL_GOMA_DIR\goma_auth.py info
$goma_login = python3 $env:LOCAL_GOMA_DIR\goma_auth.py info
if ($goma_login -eq 'Login as Fermi Planck') {
Write-warning "Goma authentication is correct";
} else {
@@ -166,7 +166,7 @@ for:
- ninja -C out/Default electron:hunspell_dictionaries_zip
- ninja -C out/Default electron:electron_chromedriver_zip
- ninja -C out/Default third_party/electron_node:headers
- python %LOCAL_GOMA_DIR%\goma_ctl.py stat
- python3 %LOCAL_GOMA_DIR%\goma_ctl.py stat
- ps: >-
Get-CimInstance -Namespace root\cimv2 -Class Win32_product | Select vendor, description, @{l='install_location';e='InstallLocation'}, @{l='install_date';e='InstallDate'}, @{l='install_date_2';e='InstallDate2'}, caption, version, name, @{l='sku_number';e='SKUNumber'} | ConvertTo-Json | Out-File -Encoding utf8 -FilePath .\installed_software.json
- python3 electron/build/profile_toolchain.py --output-json=out/Default/windows_toolchain_profile.json

View File

@@ -412,18 +412,7 @@ Returns:
* `event` Event
* `webContents` [WebContents](web-contents.md)
* `details` Object
* `reason` string - The reason the render process is gone. Possible values:
* `clean-exit` - Process exited with an exit code of zero
* `abnormal-exit` - Process exited with a non-zero exit code
* `killed` - Process was sent a SIGTERM or otherwise killed externally
* `crashed` - Process crashed
* `oom` - Process ran out of memory
* `launch-failed` - Process never successfully launched
* `integrity-failure` - Windows code integrity checks failed
* `exitCode` Integer - The exit code of the process, unless `reason` is
`launch-failed`, in which case `exitCode` will be a platform-specific
launch failure error code.
* `details` [RenderProcessGoneDetails](structures/render-process-gone-details.md)
Emitted when the renderer process unexpectedly disappears. This is normally
because it was crashed or killed.
@@ -1344,7 +1333,7 @@ application name. For example:
``` js
const { app } = require('electron')
const path = require('path')
const path = require('node:path')
const appFolder = path.dirname(process.execPath)
const updateExe = path.resolve(appFolder, '..', 'Update.exe')
@@ -1415,7 +1404,7 @@ Returns `Function` - This function **must** be called once you have finished acc
```js
const { app, dialog } = require('electron')
const fs = require('fs')
const fs = require('node:fs')
let filepath
let bookmark

View File

@@ -820,10 +820,14 @@ win.setBounds({ width: 100 })
console.log(win.getBounds())
```
**Note:** On macOS, the y-coordinate value cannot be smaller than the [Tray](tray.md) height. The tray height has changed over time and depends on the operating system, but is between 20-40px. Passing a value lower than the tray height will result in a window that is flush to the tray.
#### `win.getBounds()`
Returns [`Rectangle`](structures/rectangle.md) - The `bounds` of the window as `Object`.
**Note:** On macOS, the y-coordinate value returned will be at minimum the [Tray](tray.md) height. For example, calling `win.setBounds({ x: 25, y: 20, width: 800, height: 600 })` with a tray height of 38 means that `win.getBounds()` will return `{ x: 25, y: 38, width: 800, height: 600 }`.
#### `win.getBackgroundColor()`
Returns `string` - Gets the background color of the window in Hex (`#RRGGBB`) format.
@@ -1207,7 +1211,7 @@ const win = new BrowserWindow()
const url = require('url').format({
protocol: 'file',
slashes: true,
pathname: require('path').join(__dirname, 'index.html')
pathname: require('node:path').join(__dirname, 'index.html')
})
win.loadURL(url)

View File

@@ -147,7 +147,7 @@ Be very cautious about which globals and APIs you expose to untrusted remote con
```javascript
const { contextBridge } = require('electron')
const crypto = require('crypto')
const crypto = require('node:crypto')
contextBridge.exposeInMainWorld('nodeCrypto', {
sha256sum (data) {
const hash = crypto.createHash('sha256')

View File

@@ -40,18 +40,41 @@ We support the following extensions APIs, with some caveats. Other APIs may
additionally be supported, but support for any APIs not listed here is
provisional and may be removed.
### Supported Manifest Keys
- `name`
- `version`
- `author`
- `permissions`
- `content_scripts`
- `default_locale`
- `devtools_page`
- `short_name`
- `host_permissions` (Manifest V3)
- `manifest_version`
- `background` (Manifest V2)
- `minimum_chrome_version`
See [Manifest file format](https://developer.chrome.com/docs/extensions/mv3/manifest/) for more information about the purpose of each possible key.
### `chrome.devtools.inspectedWindow`
All features of this API are supported.
See [official documentation](https://developer.chrome.com/docs/extensions/reference/devtools_inspectedWindow) for more information.
### `chrome.devtools.network`
All features of this API are supported.
See [official documentation](https://developer.chrome.com/docs/extensions/reference/devtools_network) for more information.
### `chrome.devtools.panels`
All features of this API are supported.
See [official documentation](https://developer.chrome.com/docs/extensions/reference/devtools_panels) for more information.
### `chrome.extension`
The following properties of `chrome.extension` are supported:
@@ -63,6 +86,25 @@ The following methods of `chrome.extension` are supported:
- `chrome.extension.getURL`
- `chrome.extension.getBackgroundPage`
See [official documentation](https://developer.chrome.com/docs/extensions/reference/extension) for more information.
### `chrome.management`
The following methods of `chrome.management` are supported:
- `chrome.management.getAll`
- `chrome.management.get`
- `chrome.management.getSelf`
- `chrome.management.getPermissionWarningsById`
- `chrome.management.getPermissionWarningsByManifest`
The following events of `chrome.management` are supported:
- `chrome.management.onEnabled`
- `chrome.management.onDisabled`
See [official documentation](https://developer.chrome.com/docs/extensions/reference/management) for more information.
### `chrome.runtime`
The following properties of `chrome.runtime` are supported:
@@ -89,12 +131,24 @@ The following events of `chrome.runtime` are supported:
- `chrome.runtime.onConnect`
- `chrome.runtime.onMessage`
See [official documentation](https://developer.chrome.com/docs/extensions/reference/runtime) for more information.
### `chrome.scripting`
All features of this API are supported.
See [official documentation](https://developer.chrome.com/docs/extensions/reference/scripting) for more information.
### `chrome.storage`
The following methods of `chrome.storage` are supported:
- `chrome.storage.local`
`chrome.storage.sync` and `chrome.storage.managed` are **not** supported.
See [official documentation](https://developer.chrome.com/docs/extensions/reference/storage) for more information.
### `chrome.tabs`
The following methods of `chrome.tabs` are supported:
@@ -111,23 +165,12 @@ The following methods of `chrome.tabs` are supported:
> tab". Since Electron has no such concept, passing `-1` as a tab ID is not
> supported and will raise an error.
### `chrome.management`
The following methods of `chrome.management` are supported:
- `chrome.management.getAll`
- `chrome.management.get`
- `chrome.management.getSelf`
- `chrome.management.getPermissionWarningsById`
- `chrome.management.getPermissionWarningsByManifest`
The following events of `chrome.management` are supported:
- `chrome.management.onEnabled`
- `chrome.management.onDisabled`
See [official documentation](https://developer.chrome.com/docs/extensions/reference/tabs) for more information.
### `chrome.webRequest`
All features of this API are supported.
> **NOTE:** Electron's [`webRequest`](web-request.md) module takes precedence over `chrome.webRequest` if there are conflicting handlers.
See [official documentation](https://developer.chrome.com/docs/extensions/reference/webRequest) for more information.

View File

@@ -33,7 +33,7 @@ to register it to that session explicitly.
```javascript
const { app, BrowserWindow, net, protocol, session } = require('electron')
const path = require('path')
const path = require('node:path')
const url = require('url')
app.whenReady().then(() => {
@@ -122,7 +122,7 @@ Example:
```js
const { app, net, protocol } = require('electron')
const { join } = require('path')
const { join } = require('node:path')
const { pathToFileURL } = require('url')
protocol.registerSchemesAsPrivileged([

View File

@@ -103,7 +103,7 @@ const { session } = require('electron')
session.defaultSession.on('will-download', (event, item, webContents) => {
event.preventDefault()
require('got')(item.getURL()).then((response) => {
require('fs').writeFileSync('/somewhere', response.body)
require('node:fs').writeFileSync('/somewhere', response.body)
})
})
```
@@ -1193,7 +1193,7 @@ automatically. To clear the handler, call `setBluetoothPairingHandler(null)`.
```javascript
const { app, BrowserWindow, session } = require('electron')
const path = require('path')
const path = require('node:path')
function createWindow () {
let bluetoothPinCallback = null
@@ -1311,7 +1311,7 @@ The API will generate a [DownloadItem](download-item.md) that can be accessed
with the [will-download](#event-will-download) event.
**Note:** This does not perform any security checks that relate to a page's origin,
unlike [`webContents.downloadURL`](web-contents.md#contentsdownloadurlurl).
unlike [`webContents.downloadURL`](web-contents.md#contentsdownloadurlurl-options).
#### `ses.createInterruptedDownload(options)`
@@ -1457,7 +1457,7 @@ extension to be loaded.
```js
const { app, session } = require('electron')
const path = require('path')
const path = require('node:path')
app.whenReady().then(async () => {
await session.defaultSession.loadExtension(
@@ -1491,7 +1491,7 @@ is emitted.
* `extensionId` string - ID of extension to query
Returns `Extension` | `null` - The loaded extension with the given ID.
Returns `Extension | null` - The loaded extension with the given ID.
**Note:** This API cannot be called before the `ready` event of the `app` module
is emitted.
@@ -1544,7 +1544,7 @@ A [`Protocol`](protocol.md) object for this session.
```javascript
const { app, session } = require('electron')
const path = require('path')
const path = require('node:path')
app.whenReady().then(() => {
const protocol = session.fromPartition('some-partition').protocol

View File

@@ -0,0 +1,13 @@
# RenderProcessGoneDetails Object
* `reason` string - The reason the render process is gone. Possible values:
* `clean-exit` - Process exited with an exit code of zero
* `abnormal-exit` - Process exited with a non-zero exit code
* `killed` - Process was sent a SIGTERM or otherwise killed externally
* `crashed` - Process crashed
* `oom` - Process ran out of memory
* `launch-failed` - Process never successfully launched
* `integrity-failure` - Windows code integrity checks failed
* `exitCode` Integer - The exit code of the process, unless `reason` is
`launch-failed`, in which case `exitCode` will be a platform-specific
launch failure error code.

View File

@@ -479,18 +479,7 @@ checking `reason === 'killed'` when you switch to that event.
Returns:
* `event` Event
* `details` Object
* `reason` string - The reason the render process is gone. Possible values:
* `clean-exit` - Process exited with an exit code of zero
* `abnormal-exit` - Process exited with a non-zero exit code
* `killed` - Process was sent a SIGTERM or otherwise killed externally
* `crashed` - Process crashed
* `oom` - Process ran out of memory
* `launch-failed` - Process never successfully launched
* `integrity-failure` - Windows code integrity checks failed
* `exitCode` Integer - The exit code of the process, unless `reason` is
`launch-failed`, in which case `exitCode` will be a platform-specific
launch failure error code.
* `details` [RenderProcessGoneDetails](structures/render-process-gone-details.md)
Emitted when the renderer process unexpectedly disappears. This is normally
because it was crashed or killed.
@@ -1057,9 +1046,11 @@ const win = new BrowserWindow()
win.loadFile('src/index.html')
```
#### `contents.downloadURL(url)`
#### `contents.downloadURL(url[, options])`
* `url` string
* `options` Object (optional)
* `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.
@@ -1545,14 +1536,6 @@ If you would like the page to stay hidden, you should ensure that `stayHidden` i
Returns `boolean` - Whether this page is being captured. It returns true when the capturer count
is large then 0.
#### `contents.getPrinters()` _Deprecated_
Get the system printer list.
Returns [`PrinterInfo[]`](structures/printer-info.md)
**Deprecated:** Should use the new [`contents.getPrintersAsync`](web-contents.md#contentsgetprintersasync) API.
#### `contents.getPrintersAsync()`
Get the system printer list.
@@ -1646,9 +1629,9 @@ An example of `webContents.printToPDF`:
```javascript
const { BrowserWindow } = require('electron')
const fs = require('fs')
const path = require('path')
const os = require('os')
const fs = require('node:fs')
const path = require('node:path')
const os = require('node:os')
const win = new BrowserWindow()
win.loadURL('https://github.com')

View File

@@ -280,9 +280,11 @@ if the page fails to load (see
Loads the `url` in the webview, the `url` must contain the protocol prefix,
e.g. the `http://` or `file://`.
### `<webview>.downloadURL(url)`
### `<webview>.downloadURL(url[, options])`
* `url` string
* `options` Object (optional)
* `headers` Record<string, string> (optional) - HTTP request headers.
Initiates a download of the resource at `url` without navigating.
@@ -983,9 +985,22 @@ ipcRenderer.on('ping', () => {
})
```
### Event: 'crashed'
### Event: 'crashed' _Deprecated_
Fired when the renderer process is crashed.
Fired when the renderer process crashes or is killed.
**Deprecated:** This event is superceded by the `render-process-gone` event
which contains more information about why the render process disappeared. It
isn't always because it crashed.
### Event: 'render-process-gone'
Returns:
* `details` [RenderProcessGoneDetails](structures/render-process-gone-details.md)
Fired when the renderer process unexpectedly disappears. This is normally
because it was crashed or killed.
### Event: 'plugin-crashed'

View File

@@ -45,6 +45,22 @@ systemPreferences.on('high-contrast-color-scheme-changed', () => { /* ... */ })
nativeTheme.on('updated', () => { /* ... */ })
```
### Removed: `webContents.getPrinters`
The `webContents.getPrinters` method has been removed. Use
`webContents.getPrintersAsync` instead.
```js
const w = new BrowserWindow({ show: false })
// Removed
console.log(w.webContents.getPrinters())
// Replace with
w.webContents.getPrintersAsync().then((printers) => {
console.log(printers)
})
```
## Planned Breaking API Changes (26.0)
### Deprecated: `webContents.getPrinters`

View File

@@ -42,14 +42,14 @@ $ asar list /path/to/example.asar
Read a file in the ASAR archive:
```javascript
const fs = require('fs')
const fs = require('node:fs')
fs.readFileSync('/path/to/example.asar/file.txt')
```
List all files under the root of the archive:
```javascript
const fs = require('fs')
const fs = require('node:fs')
fs.readdirSync('/path/to/example.asar')
```
@@ -99,7 +99,7 @@ You can also set `process.noAsar` to `true` to disable the support for `asar` in
the `fs` module:
```javascript
const fs = require('fs')
const fs = require('node:fs')
process.noAsar = true
fs.readFileSync('/path/to/example.asar')
```

View File

@@ -260,7 +260,7 @@ To create a custom driver, we'll use Node.js' [`child_process`](https://nodejs.o
The test suite will spawn the Electron process, then establish a simple messaging protocol:
```js title='testDriver.js' @ts-nocheck
const childProcess = require('child_process')
const childProcess = require('node:child_process')
const electronPath = require('electron')
// spawn the process

View File

@@ -136,7 +136,7 @@ Finally, the `main.js` file represents the main process and contains the actual
```js
const { app, BrowserWindow, ipcMain, nativeTheme } = require('electron')
const path = require('path')
const path = require('node:path')
const createWindow = () => {
const win = new BrowserWindow({

View File

@@ -35,8 +35,8 @@ Using the [React Developer Tools][react-devtools] as an example:
```javascript
const { app, session } = require('electron')
const path = require('path')
const os = require('os')
const path = require('node:path')
const os = require('node:os')
// on macOS
const reactDevToolsPath = path.join(

View File

@@ -4,15 +4,48 @@ Electron Forge is a tool for packaging and publishing Electron applications.
It unifies Electron's build tooling ecosystem into
a single extensible interface so that anyone can jump right into making Electron apps.
<details>
<summary>Alternative tooling</summary>
If you do not want to use Electron Forge for your project, there are other
third-party tools you can use to distribute your app.
These tools are maintained by members of the Electron community,
and do not come with official support from the Electron project.
**Electron Builder**
A "complete solution to package and build a ready-for-distribution Electron app"
that focuses on an integrated experience. [`electron-builder`](https://github.com/electron-userland/electron-builder) adds a single dependency and manages all further requirements internally.
`electron-builder` replaces features and modules used by the Electron
maintainers (such as the auto-updater) with custom ones.
**Hydraulic Conveyor**
A [desktop app deployment tool](https://hydraulic.dev) that supports
cross-building/signing of all packages from any OS without the need for
multi-platform CI, can do synchronous web-style updates on each start
of the app, requires no code changes, can use plain HTTP servers for updates and
which focuses on ease of use. Conveyor replaces the Electron auto-updaters
with Sparkle on macOS, MSIX on Windows, and Linux package repositories.
Conveyor is a commercial tool that is free for open source projects. There's
an example of [how to package GitHub Desktop](https://hydraulic.dev/blog/8-packaging-electron-apps.html)
which can be used for learning.
</details>
## Getting started
The [Electron Forge docs][] contain detailed information on taking your application
from source code to your end users' machines.
This includes:
* Packaging your application [(package)][]
* Generating executables and installers for each OS [(make)][], and,
* Publishing these files to online platforms to download [(publish)][].
- Packaging your application [(package)][]
- Generating executables and installers for each OS [(make)][], and,
- Publishing these files to online platforms to download [(publish)][].
For beginners, we recommend following through Electron's [tutorial][] to develop, build,
package and publish your first Electron app. If you have already developed an app on your machine
@@ -20,11 +53,11 @@ and want to start on packaging and distribution, start from [step 5][] of the tu
## Getting help
* If you need help with developing your app, our [community Discord server][discord] is a great place
to get advice from other Electron app developers.
* If you suspect you're running into a bug with Forge, please check the [GitHub issue tracker][]
to see if any existing issues match your problem. If not, feel free to fill out our bug report
template and submit a new issue.
- If you need help with developing your app, our [community Discord server][discord] is a great place
to get advice from other Electron app developers.
- If you suspect you're running into a bug with Forge, please check the [GitHub issue tracker][]
to see if any existing issues match your problem. If not, feel free to fill out our bug report
template and submit a new issue.
[Electron Forge Docs]: https://www.electronforge.io/
[step 5]: ./tutorial-5-packaging.md

View File

@@ -52,7 +52,7 @@ In the main process, set an IPC listener on the `set-title` channel with the `ip
```javascript {6-10,22} title='main.js (Main Process)'
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const path = require('node:path')
// ...
@@ -183,7 +183,7 @@ provided to the renderer process. Please refer to
```javascript {6-13,25} title='main.js (Main Process)'
const { app, BrowserWindow, dialog, ipcMain } = require('electron')
const path = require('path')
const path = require('node:path')
// ...
@@ -378,7 +378,7 @@ target renderer.
```javascript {11-26} title='main.js (Main Process)'
const { app, BrowserWindow, Menu, ipcMain } = require('electron')
const path = require('path')
const path = require('node:path')
function createWindow () {
const mainWindow = new BrowserWindow({

View File

@@ -27,7 +27,7 @@ control our application lifecycle and create a native browser window.
```javascript
const { app, BrowserWindow, shell } = require('electron')
const path = require('path')
const path = require('node:path')
```
Next, we will proceed to register our application to handle all "`electron-fiddle://`" protocols.

View File

@@ -303,7 +303,7 @@ without having to step through the isolated world.
```js title='main.js (Main Process)'
const { BrowserWindow, app, MessageChannelMain } = require('electron')
const path = require('path')
const path = require('node:path')
app.whenReady().then(async () => {
// Create a BrowserWindow with contextIsolation enabled.

View File

@@ -22,7 +22,7 @@ In `preload.js` use the [`contextBridge`][] to inject a method `window.electron.
```js
const { contextBridge, ipcRenderer } = require('electron')
const path = require('path')
const path = require('node:path')
contextBridge.exposeInMainWorld('electron', {
startDrag: (fileName) => {

View File

@@ -41,7 +41,7 @@ To enable this mode, GPU acceleration has to be disabled by calling the
```javascript fiddle='docs/fiddles/features/offscreen-rendering'
const { app, BrowserWindow } = require('electron')
const fs = require('fs')
const fs = require('node:fs')
app.disableHardwareAcceleration()

View File

@@ -174,7 +174,7 @@ equally fictitious `foo-parser` module. In traditional Node.js development,
you might write code that eagerly loads dependencies:
```js title='parser.js' @ts-expect-error=[2]
const fs = require('fs')
const fs = require('node:fs')
const fooParser = require('foo-parser')
class Parser {
@@ -198,7 +198,7 @@ do this work a little later, when `getParsedFiles()` is actually called?
```js title='parser.js' @ts-expect-error=[20]
// "fs" is likely already being loaded, so the `require()` call is cheap
const fs = require('fs')
const fs = require('node:fs')
class Parser {
async getFiles () {

View File

@@ -292,7 +292,7 @@ to the `webPreferences.preload` option in your existing `BrowserWindow` construc
```js
const { app, BrowserWindow } = require('electron')
// include the Node.js 'path' module at the top of your file
const path = require('path')
const path = require('node:path')
// modify your existing createWindow() function
const createWindow = () => {
@@ -358,7 +358,7 @@ The full code is available below:
// Modules to control application life and create native browser window
const { app, BrowserWindow } = require('electron')
const path = require('path')
const path = require('node:path')
const createWindow = () => {
// Create the browser window.

View File

@@ -26,8 +26,8 @@ the application via JumpList or dock menu, respectively.
```javascript fiddle='docs/fiddles/features/recent-documents'
const { app, BrowserWindow } = require('electron')
const fs = require('fs')
const path = require('path')
const fs = require('node:fs')
const path = require('node:path')
const createWindow = () => {
const win = new BrowserWindow({

View File

@@ -29,7 +29,7 @@ To set the represented file of window, you can use the
```javascript fiddle='docs/fiddles/features/represented-file'
const { app, BrowserWindow } = require('electron')
const os = require('os')
const os = require('node:os')
const createWindow = () => {
const win = new BrowserWindow({

View File

@@ -83,7 +83,7 @@ To attach this script to your renderer process, pass its path to the
```js {2,8-10} title="main.js"
const { app, BrowserWindow } = require('electron')
const path = require('path')
const path = require('node:path')
const createWindow = () => {
const win = new BrowserWindow({
@@ -204,7 +204,7 @@ you send out the `invoke` call from the renderer.
```js {1,15} title="main.js"
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const path = require('node:path')
const createWindow = () => {
const win = new BrowserWindow({

View File

@@ -184,7 +184,7 @@ allowing events such as `mouseleave` to be emitted:
```javascript title='main.js'
const { BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const path = require('node:path')
const win = new BrowserWindow({
webPreferences: {

View File

@@ -126,7 +126,7 @@ following lines:
```javascript
const { BrowserWindow, nativeImage } = require('electron')
const path = require('path')
const path = require('node:path')
const win = new BrowserWindow()

View File

@@ -61,6 +61,7 @@ template("electron_extra_paks") {
"$root_gen_dir/content/browser/tracing/tracing_resources.pak",
"$root_gen_dir/content/browser/webrtc/resources/webrtc_internals_resources.pak",
"$root_gen_dir/content/content_resources.pak",
"$root_gen_dir/content/gpu_resources.pak",
"$root_gen_dir/mojo/public/js/mojo_bindings_resources.pak",
"$root_gen_dir/net/net_resources.pak",
"$root_gen_dir/third_party/blink/public/resources/blink_resources.pak",
@@ -74,6 +75,7 @@ template("electron_extra_paks") {
"//chrome/common:resources",
"//components/resources",
"//content:content_resources",
"//content/browser/resources/gpu:resources",
"//content/browser/resources/media:resources",
"//content/browser/tracing:resources",
"//content/browser/webrtc/resources",

View File

@@ -115,6 +115,7 @@ auto_filenames = {
"docs/api/structures/protocol-response.md",
"docs/api/structures/rectangle.md",
"docs/api/structures/referrer.md",
"docs/api/structures/render-process-gone-details.md",
"docs/api/structures/resolved-endpoint.md",
"docs/api/structures/resolved-host.md",
"docs/api/structures/scrubber-item.md",

View File

@@ -682,6 +682,8 @@ filenames = {
"shell/browser/extensions/api/resources_private/resources_private_api.h",
"shell/browser/extensions/api/runtime/electron_runtime_api_delegate.cc",
"shell/browser/extensions/api/runtime/electron_runtime_api_delegate.h",
"shell/browser/extensions/api/scripting/scripting_api.cc",
"shell/browser/extensions/api/scripting/scripting_api.h",
"shell/browser/extensions/api/streams_private/streams_private_api.cc",
"shell/browser/extensions/api/streams_private/streams_private_api.h",
"shell/browser/extensions/api/tabs/tabs_api.cc",

View File

@@ -386,17 +386,6 @@ WebContents.prototype.print = function (options: ElectronInternal.WebContentsPri
}
};
WebContents.prototype.getPrinters = function () {
// TODO(nornagon): this API has nothing to do with WebContents and should be
// moved.
if (printing.getPrinterList) {
return printing.getPrinterList();
} else {
console.error('Error: Printing feature is disabled.');
return [];
}
};
WebContents.prototype.getPrintersAsync = async function () {
// TODO(nornagon): this API has nothing to do with WebContents and should be
// moved.
@@ -446,6 +435,7 @@ WebContents.prototype.loadURL = function (url, options) {
};
let navigationStarted = false;
let browserInitiatedInPageNavigation = false;
const navigationListener = (event: Electron.Event, url: string, isSameDocument: boolean, isMainFrame: boolean) => {
if (isMainFrame) {
if (navigationStarted && !isSameDocument) {
@@ -460,6 +450,7 @@ WebContents.prototype.loadURL = function (url, options) {
// as the routing does not leave the document
return rejectAndCleanup(-3, 'ERR_ABORTED', url);
}
browserInitiatedInPageNavigation = navigationStarted && isSameDocument;
navigationStarted = true;
}
};
@@ -474,17 +465,22 @@ WebContents.prototype.loadURL = function (url, options) {
// would be more appropriate.
rejectAndCleanup(-2, 'ERR_FAILED', url);
};
const finishListenerWhenUserInitiatedNavigation = () => {
if (!browserInitiatedInPageNavigation) {
finishListener();
}
};
const removeListeners = () => {
this.removeListener('did-finish-load', finishListener);
this.removeListener('did-fail-load', failListener);
this.removeListener('did-navigate-in-page', finishListener);
this.removeListener('did-navigate-in-page', finishListenerWhenUserInitiatedNavigation);
this.removeListener('did-start-navigation', navigationListener);
this.removeListener('did-stop-loading', stopLoadingListener);
this.removeListener('destroyed', stopLoadingListener);
};
this.on('did-finish-load', finishListener);
this.on('did-fail-load', failListener);
this.on('did-navigate-in-page', finishListener);
this.on('did-navigate-in-page', finishListenerWhenUserInitiatedNavigation);
this.on('did-start-navigation', navigationListener);
this.on('did-stop-loading', stopLoadingListener);
this.on('destroyed', stopLoadingListener);

View File

@@ -98,7 +98,6 @@ make_gtk_getlibgtk_public.patch
build_disable_print_content_analysis.patch
custom_protocols_plzserviceworker.patch
feat_filter_out_non-shareable_windows_in_the_current_application_in.patch
fix_allow_guest_webcontents_to_enter_fullscreen.patch
disable_freezing_flags_after_init_in_node.patch
short-circuit_permissions_checks_in_mediastreamdevicescontroller.patch
chore_add_electron_deps_to_gitignores.patch
@@ -129,3 +128,4 @@ fix_harden_blink_scriptstate_maybefrom.patch
chore_add_buildflag_guard_around_new_include.patch
fix_use_delegated_generic_capturer_when_available.patch
build_remove_ent_content_analysis_assert.patch
revert_remove_the_allowaggressivethrottlingwithwebsocket_feature.patch

View File

@@ -1,20 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samuel Attard <sattard@salesforce.com>
Date: Mon, 6 Jun 2022 14:25:15 -0700
Subject: fix: allow guest webcontents to enter fullscreen
This can be upstreamed, a guest webcontents can't technically become the focused webContents. This DCHECK should allow all guest webContents to request fullscreen entrance.
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 8d5a0e6aa6ea3a0965ed6abb76368d1f8e3ea91e..36cd52f18c83adc007d6f896a4136a93f0fa1c83 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -3715,7 +3715,7 @@ void WebContentsImpl::EnterFullscreenMode(
OPTIONAL_TRACE_EVENT0("content", "WebContentsImpl::EnterFullscreenMode");
DCHECK(CanEnterFullscreenMode(requesting_frame, options));
DCHECK(requesting_frame->IsActive());
- DCHECK(ContainsOrIsFocusedWebContents());
+ DCHECK(ContainsOrIsFocusedWebContents() || IsGuest());
// When WebView is the `delegate_` we can end up with VisualProperties changes
// synchronously. Notify the view ahead so it can handle the transition.

View File

@@ -45,10 +45,10 @@ index 1e9dcbb9f0a5e401a50d63be490755bc3eeaa1a3..1e2e0a321df6fe9dea4401ed327c9373
// RenderFrameMetadataProvider::Observer implementation.
void OnRenderFrameMetadataChangedBeforeActivation(
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 36cd52f18c83adc007d6f896a4136a93f0fa1c83..99f6e45fe48449c2dbe88514648a4d907999e282 100644
index 61d985829d54e2f98351f39d1b58e5702f10fa79..6ee703fc50459901068c364a3a0ccd3a60ce28bb 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -8319,7 +8319,7 @@ void WebContentsImpl::OnFocusedElementChangedInFrame(
@@ -8323,7 +8323,7 @@ void WebContentsImpl::OnFocusedElementChangedInFrame(
"WebContentsImpl::OnFocusedElementChangedInFrame",
"render_frame_host", frame);
RenderWidgetHostViewBase* root_view =

View File

@@ -0,0 +1,41 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samuel Attard <samuel.r.attard@gmail.com>
Date: Tue, 5 Sep 2023 13:22:31 -0700
Subject: Revert "Remove the AllowAggressiveThrottlingWithWebSocket feature."
This reverts commit 615c1810a187840ffeb04096087efff86edb37de.
diff --git a/third_party/blink/renderer/modules/websockets/websocket_channel_impl.cc b/third_party/blink/renderer/modules/websockets/websocket_channel_impl.cc
index d566c1a209aa5b39692fe79dd8b5012a5e053574..90e97f739b3e7ede2fa4ceffb1be5130f651f60b 100644
--- a/third_party/blink/renderer/modules/websockets/websocket_channel_impl.cc
+++ b/third_party/blink/renderer/modules/websockets/websocket_channel_impl.cc
@@ -95,6 +95,17 @@ enum WebSocketOpCode {
kOpCodeBinary = 0x2,
};
+// When enabled, a page can be aggressively throttled even if it uses a
+// WebSocket. Aggressive throttling does not affect the execution of WebSocket
+// event handlers, so there is little reason to disable it on pages using a
+// WebSocket.
+//
+// TODO(crbug.com/1121725): Cleanup this feature in June 2021, when it becomes
+// enabled by default on Stable.
+BASE_FEATURE(kAllowAggressiveThrottlingWithWebSocket,
+ "AllowAggressiveThrottlingWithWebSocket",
+ base::FEATURE_ENABLED_BY_DEFAULT);
+
} // namespace
void WebSocketChannelImpl::MessageDataDeleter::operator()(char* p) const {
@@ -285,7 +296,10 @@ bool WebSocketChannelImpl::Connect(const KURL& url, const String& protocol) {
// even if the `WebSocketChannel` is closed.
feature_handle_for_scheduler_ = scheduler->RegisterFeature(
SchedulingPolicy::Feature::kWebSocket,
- SchedulingPolicy{SchedulingPolicy::DisableBackForwardCache()});
+ base::FeatureList::IsEnabled(kAllowAggressiveThrottlingWithWebSocket)
+ ? SchedulingPolicy{SchedulingPolicy::DisableBackForwardCache()}
+ : SchedulingPolicy{SchedulingPolicy::DisableAggressiveThrottling(),
+ SchedulingPolicy::DisableBackForwardCache()});
scheduler->RegisterStickyFeature(
SchedulingPolicy::Feature::kWebSocketSticky,
SchedulingPolicy{SchedulingPolicy::DisableBackForwardCache()});

View File

@@ -8,7 +8,8 @@ the parent frame should also enter fullscreen mode too. Chromium handles
this for iframes, but not for webviews as they are essentially main
frames instead of child frames.
This patch makes webviews propagate the fullscreen state to embedder.
This patch makes webviews propagate the fullscreen state to embedder.It also handles a
DCHECK preventing guest webcontents from becoming the focused webContents.
Note that we also need to manually update embedder's
`api::WebContents::IsFullscreenForTabOrPending` value.
@@ -35,3 +36,67 @@ index 01eb116b51037bff5da7a87d119b78387f5e72c6..7ab5609215ce352b2595b4953e1b9ca0
// Focus the window if another frame may have delegated the capability.
if (had_fullscreen_token && !GetView()->HasFocus())
GetView()->Focus();
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 8d5a0e6aa6ea3a0965ed6abb76368d1f8e3ea91e..61d985829d54e2f98351f39d1b58e5702f10fa79 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -3569,21 +3569,25 @@ KeyboardEventProcessingResult WebContentsImpl::PreHandleKeyboardEvent(
const NativeWebKeyboardEvent& event) {
OPTIONAL_TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("content.verbose"),
"WebContentsImpl::PreHandleKeyboardEvent");
- auto* outermost_contents = GetOutermostWebContents();
- // TODO(wjmaclean): Generalize this to forward all key events to the outermost
- // delegate's handler.
- if (outermost_contents != this && IsFullscreen() &&
- event.windows_key_code == ui::VKEY_ESCAPE) {
- // When an inner WebContents has focus and is fullscreen, redirect <esc>
- // key events to the outermost WebContents so it can be handled by that
- // WebContents' delegate.
- if (outermost_contents->PreHandleKeyboardEvent(event) ==
- KeyboardEventProcessingResult::HANDLED) {
+
+ auto handled = delegate_ ? delegate_->PreHandleKeyboardEvent(this, event)
+ : KeyboardEventProcessingResult::NOT_HANDLED;
+
+ if (IsFullscreen() && event.windows_key_code == ui::VKEY_ESCAPE) {
+ if (handled == KeyboardEventProcessingResult::HANDLED)
return KeyboardEventProcessingResult::HANDLED;
+
+ // When an inner WebContents has focus and is fullscreen, traverse through
+ // containing webcontents to any that may handle the escape key.
+ while (auto* outer_web_contents = GetOuterWebContents()) {
+ auto result = outer_web_contents->PreHandleKeyboardEvent(event);
+ if (result == KeyboardEventProcessingResult::HANDLED) {
+ return KeyboardEventProcessingResult::HANDLED;
+ }
}
}
- return delegate_ ? delegate_->PreHandleKeyboardEvent(this, event)
- : KeyboardEventProcessingResult::NOT_HANDLED;
+
+ return handled;
}
bool WebContentsImpl::HandleMouseEvent(const blink::WebMouseEvent& event) {
@@ -3715,7 +3719,7 @@ void WebContentsImpl::EnterFullscreenMode(
OPTIONAL_TRACE_EVENT0("content", "WebContentsImpl::EnterFullscreenMode");
DCHECK(CanEnterFullscreenMode(requesting_frame, options));
DCHECK(requesting_frame->IsActive());
- DCHECK(ContainsOrIsFocusedWebContents());
+ DCHECK(ContainsOrIsFocusedWebContents() || IsGuest());
// When WebView is the `delegate_` we can end up with VisualProperties changes
// synchronously. Notify the view ahead so it can handle the transition.
diff --git a/third_party/blink/renderer/core/fullscreen/fullscreen.cc b/third_party/blink/renderer/core/fullscreen/fullscreen.cc
index f58ade7ed652ea0d97c123dc78715c049cb5e500..6619207f2f8731a7d8e88d6c1613e77ca4abc558 100644
--- a/third_party/blink/renderer/core/fullscreen/fullscreen.cc
+++ b/third_party/blink/renderer/core/fullscreen/fullscreen.cc
@@ -99,7 +99,7 @@ void FullscreenElementChanged(Document& document,
// is the iframe element for the out-of-process frame that contains the
// fullscreen element. Hence, it must match :-webkit-full-screen-ancestor.
if (new_request_type & FullscreenRequestType::kForCrossProcessDescendant) {
- DCHECK(IsA<HTMLIFrameElement>(new_element));
+ // DCHECK(IsA<HTMLIFrameElement>(new_element));
new_element->SetContainsFullScreenElement(true);
}
new_element->SetContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(

View File

@@ -41,3 +41,4 @@ fix_ftbfs_werror_wextra-semi.patch
fix_isurl_implementation.patch
ci_ensure_node_tests_set_electron_run_as_node.patch
chore_update_fixtures_errors_force_colors_snapshot.patch
fix_assert_module_in_the_renderer_process.patch

View File

@@ -0,0 +1,75 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shelley Vohr <shelley.vohr@gmail.com>
Date: Wed, 16 Aug 2023 19:15:29 +0200
Subject: fix: assert module in the renderer process
When creating a Node.js Environment, embedders have the option to disable Node.js'
default overriding of Error.prepareStackTrace. However, the assert module depends on
a WeakMap that is populated with the error stacktraces in the overridden function.
This adds handling to fall back to the default implementation if Error.prepareStackTrace
if the override has been disabled.
This will be upstreamed.
diff --git a/lib/assert.js b/lib/assert.js
index 04c2dd3bfcfdfbb4b8079c306e1d80aa48027787..34658819d09cc20f372798caec79e19c4a36565d 100644
--- a/lib/assert.js
+++ b/lib/assert.js
@@ -66,6 +66,7 @@ const { inspect } = require('internal/util/inspect');
const { isPromise, isRegExp } = require('internal/util/types');
const { EOL } = require('internal/constants');
const { BuiltinModule } = require('internal/bootstrap/loaders');
+const { getEmbedderOptions } = require('internal/options');
const { isError } = require('internal/util');
const errorCache = new SafeMap();
@@ -293,8 +294,16 @@ function getErrMessage(message, fn) {
ErrorCaptureStackTrace(err, fn);
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = tmpLimit;
- overrideStackTrace.set(err, (_, stack) => stack);
- const call = err.stack[0];
+ let call;
+ if (getEmbedderOptions().hasPrepareStackTraceCallback) {
+ overrideStackTrace.set(err, (_, stack) => stack);
+ call = err.stack[0];
+ } else {
+ const tmpPrepare = Error.prepareStackTrace;
+ Error.prepareStackTrace = (_, stack) => stack;
+ call = err.stack[0];
+ Error.prepareStackTrace = tmpPrepare;
+ }
const filename = call.getFileName();
const line = call.getLineNumber() - 1;
diff --git a/src/api/environment.cc b/src/api/environment.cc
index d5a03d5e10faaa204b3f9f290fed79be824c78b1..c4caef25af670658965fc740ce03c2d2c4ed3e66 100644
--- a/src/api/environment.cc
+++ b/src/api/environment.cc
@@ -263,6 +263,9 @@ void SetIsolateErrorHandlers(v8::Isolate* isolate, const IsolateSettings& s) {
auto* prepare_stack_trace_cb = s.prepare_stack_trace_callback ?
s.prepare_stack_trace_callback : PrepareStackTraceCallback;
isolate->SetPrepareStackTraceCallback(prepare_stack_trace_cb);
+ } else {
+ auto env = Environment::GetCurrent(isolate);
+ env->set_prepare_stack_trace_callback(Local<Function>());
}
}
diff --git a/src/node_options.cc b/src/node_options.cc
index 499d048fcb82e50a302d74e2c7f87f3696103a40..7ad8d80faee840e4dd224d946871b2ff08b0c23c 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -1205,6 +1205,11 @@ void GetEmbedderOptions(const FunctionCallbackInfo<Value>& args) {
Local<Context> context = env->context();
Local<Object> ret = Object::New(isolate);
+ if (ret->Set(context,
+ FIXED_ONE_BYTE_STRING(env->isolate(), "hasPrepareStackTraceCallback"),
+ Boolean::New(isolate, !env->prepare_stack_trace_callback().IsEmpty()))
+ .IsNothing()) return;
+
if (ret->Set(context,
FIXED_ONE_BYTE_STRING(env->isolate(), "shouldNotRegisterESMLoader"),
Boolean::New(isolate, env->should_not_register_esm_loader()))

View File

@@ -1,6 +1,6 @@
param([string]$gomaDir=$PWD)
$cmdPath = Join-Path -Path $gomaDir -ChildPath "goma_ctl.py"
Start-Process -FilePath cmd -ArgumentList "/C", "python", "$cmdPath", "ensure_start"
Start-Process -FilePath cmd -ArgumentList "/C", "python3", "$cmdPath", "ensure_start"
$timedOut = $false; $waitTime = 0; $waitIncrement = 5; $timeout=120;
Do { sleep $waitIncrement; $timedOut = (($waitTime+=$waitIncrement) -gt $timeout); iex "$gomaDir\gomacc.exe port 2" > $null; } Until(($LASTEXITCODE -eq 0) -or $timedOut)
if ($timedOut) {

View File

@@ -54,13 +54,14 @@ namespace {
// See https://nodejs.org/api/cli.html#cli_options
void ExitIfContainsDisallowedFlags(const std::vector<std::string>& argv) {
// Options that are unilaterally disallowed.
static constexpr auto disallowed = base::MakeFixedFlatSet<base::StringPiece>({
"--enable-fips",
"--force-fips",
"--openssl-config",
"--use-bundled-ca",
"--use-openssl-ca",
});
static constexpr auto disallowed =
base::MakeFixedFlatSetSorted<base::StringPiece>({
"--enable-fips",
"--force-fips",
"--openssl-config",
"--use-bundled-ca",
"--use-openssl-ca",
});
for (const auto& arg : argv) {
const auto key = base::StringPiece(arg).substr(0, arg.find('='));

View File

@@ -768,10 +768,20 @@ void BaseWindow::AddBrowserView(gin::Handle<BrowserView> browser_view) {
browser_view->SetOwnerWindow(nullptr);
}
// If the user set the BrowserView's bounds before adding it to the window,
// we need to get those initial bounds *before* adding it to the window
// so bounds isn't returned relative despite not being correctly positioned
// relative to the window.
auto bounds = browser_view->GetBounds();
window_->AddBrowserView(browser_view->view());
window_->AddDraggableRegionProvider(browser_view.get());
browser_view->SetOwnerWindow(this);
browser_views_.emplace_back().Reset(isolate(), browser_view.ToV8());
// Recalibrate bounds relative to the containing window.
if (!bounds.IsEmpty())
browser_view->SetBounds(bounds);
}
}

View File

@@ -66,6 +66,9 @@ class BrowserView : public gin::Wrappable<BrowserView>,
BrowserView(const BrowserView&) = delete;
BrowserView& operator=(const BrowserView&) = delete;
gfx::Rect GetBounds();
void SetBounds(const gfx::Rect& bounds);
protected:
BrowserView(gin::Arguments* args, const gin_helper::Dictionary& options);
~BrowserView() override;
@@ -78,8 +81,6 @@ class BrowserView : public gin::Wrappable<BrowserView>,
private:
void SetAutoResize(AutoResizeFlags flags);
void SetBounds(const gfx::Rect& bounds);
gfx::Rect GetBounds();
void SetBackgroundColor(const std::string& color_name);
v8::Local<v8::Value> GetWebContents(v8::Isolate*);

View File

@@ -41,25 +41,6 @@ struct Converter<printing::PrinterBasicInfo> {
namespace electron::api {
#if BUILDFLAG(ENABLE_PRINTING)
printing::PrinterList GetPrinterList(v8::Isolate* isolate) {
EmitWarning(node::Environment::GetCurrent(isolate),
"Deprecation Warning: getPrinters() is deprecated. "
"Use the asynchronous and non-blocking version, "
"getPrintersAsync(), instead.",
"electron");
printing::PrinterList printers;
auto print_backend = printing::PrintBackend::CreateInstance(
g_browser_process->GetApplicationLocale());
{
ScopedAllowBlockingForElectron allow_blocking;
printing::mojom::ResultCode code =
print_backend->EnumeratePrinters(printers);
if (code != printing::mojom::ResultCode::kSuccess)
LOG(INFO) << "Failed to enumerate printers";
}
return printers;
}
v8::Local<v8::Promise> GetPrinterListAsync(v8::Isolate* isolate) {
gin_helper::Promise<printing::PrinterList> promise(isolate);
v8::Local<v8::Promise> handle = promise.GetHandle();
@@ -92,7 +73,6 @@ v8::Local<v8::Promise> GetPrinterListAsync(v8::Isolate* isolate) {
namespace {
#if BUILDFLAG(ENABLE_PRINTING)
using electron::api::GetPrinterList;
using electron::api::GetPrinterListAsync;
#endif
@@ -103,7 +83,6 @@ void Initialize(v8::Local<v8::Object> exports,
v8::Isolate* isolate = context->GetIsolate();
gin_helper::Dictionary dict(isolate, exports);
#if BUILDFLAG(ENABLE_PRINTING)
dict.SetMethod("getPrinterList", base::BindRepeating(&GetPrinterList));
dict.SetMethod("getPrinterListAsync",
base::BindRepeating(&GetPrinterListAsync));
#endif

View File

@@ -2444,12 +2444,25 @@ void WebContents::ReloadIgnoringCache() {
/* check_for_repost */ true);
}
void WebContents::DownloadURL(const GURL& url) {
auto* browser_context = web_contents()->GetBrowserContext();
auto* download_manager = browser_context->GetDownloadManager();
void WebContents::DownloadURL(const GURL& url, gin::Arguments* args) {
std::map<std::string, std::string> headers;
gin_helper::Dictionary options;
if (args->GetNext(&options)) {
if (options.Has("headers") && !options.Get("headers", &headers)) {
args->ThrowTypeError("Invalid value for headers - must be an object");
return;
}
}
std::unique_ptr<download::DownloadUrlParameters> download_params(
content::DownloadRequestUtils::CreateDownloadForWebContentsMainFrame(
web_contents(), url, MISSING_TRAFFIC_ANNOTATION));
for (const auto& [name, value] : headers) {
download_params->add_request_header(name, value);
}
auto* download_manager =
web_contents()->GetBrowserContext()->GetDownloadManager();
download_manager->DownloadUrl(std::move(download_params));
}
@@ -2640,14 +2653,6 @@ void WebContents::OpenDevTools(gin::Arguments* args) {
state = "detach";
}
#if BUILDFLAG(IS_WIN)
auto* win = static_cast<NativeWindowViews*>(owner_window());
// Force a detached state when WCO is enabled to match Chrome
// behavior and prevent occlusion of DevTools.
if (win && win->IsWindowControlsOverlayEnabled())
state = "detach";
#endif
bool activate = true;
std::string title;
if (args && args->Length() == 1) {

View File

@@ -169,7 +169,7 @@ class WebContents : public ExclusiveAccessContext,
void LoadURL(const GURL& url, const gin_helper::Dictionary& options);
void Reload();
void ReloadIgnoringCache();
void DownloadURL(const GURL& url);
void DownloadURL(const GURL& url, gin::Arguments* args);
GURL GetURL() const;
std::u16string GetTitle() const;
bool IsLoading() const;

View File

@@ -44,13 +44,6 @@ namespace electron {
namespace {
bool IsAppRTL() {
const std::string& locale = g_browser_process->GetApplicationLocale();
base::i18n::TextDirection text_direction =
base::i18n::GetTextDirectionForLocaleInStartUp(locale.c_str());
return text_direction == base::i18n::RIGHT_TO_LEFT;
}
NSString* GetAppPathForProtocol(const GURL& url) {
NSURL* ns_url = [NSURL
URLWithString:base::SysUTF8ToNSString(url.possibly_invalid_spec())];

View File

@@ -580,7 +580,7 @@ void ElectronBrowserContext::DisplayMediaDeviceChosen(
blink::MediaStreamDevice video_device(request.video_type, id, name);
video_device.display_media_info = DesktopMediaIDToDisplayMediaInformation(
nullptr, url::Origin::Create(request.security_origin),
content::DesktopMediaID::Parse(request.requested_video_device_id));
content::DesktopMediaID::Parse(video_device.id));
devices.video_device = video_device;
} else if (result_dict.Get("video", &rfh)) {
auto* web_contents = content::WebContents::FromRenderFrameHost(rfh);
@@ -592,7 +592,7 @@ void ElectronBrowserContext::DisplayMediaDeviceChosen(
base::UTF16ToUTF8(web_contents->GetTitle()));
video_device.display_media_info = DesktopMediaIDToDisplayMediaInformation(
web_contents, url::Origin::Create(request.security_origin),
content::DesktopMediaID::Parse(request.requested_video_device_id));
content::DesktopMediaID::Parse(video_device.id));
devices.video_device = video_device;
} else {
gin_helper::ErrorThrower(args->isolate())

View File

@@ -204,11 +204,11 @@ ElectronBrowserMainParts* ElectronBrowserMainParts::self_ = nullptr;
ElectronBrowserMainParts::ElectronBrowserMainParts()
: fake_browser_process_(std::make_unique<BrowserProcessImpl>()),
browser_(std::make_unique<Browser>()),
node_bindings_(
NodeBindings::Create(NodeBindings::BrowserEnvironment::kBrowser)),
electron_bindings_(
std::make_unique<ElectronBindings>(node_bindings_->uv_loop())) {
node_bindings_{
NodeBindings::Create(NodeBindings::BrowserEnvironment::kBrowser)},
electron_bindings_{
std::make_unique<ElectronBindings>(node_bindings_->uv_loop())},
browser_{std::make_unique<Browser>()} {
DCHECK(!self_) << "Cannot have two ElectronBrowserMainParts";
self_ = this;
}
@@ -266,26 +266,25 @@ void ElectronBrowserMainParts::PostEarlyInitialization() {
node_bindings_->Initialize(js_env_->isolate()->GetCurrentContext());
// Create the global environment.
node::Environment* env = node_bindings_->CreateEnvironment(
node_env_ = node_bindings_->CreateEnvironment(
js_env_->isolate()->GetCurrentContext(), js_env_->platform());
node_env_ = std::make_unique<NodeEnvironment>(env);
env->set_trace_sync_io(env->options()->trace_sync_io);
node_env_->set_trace_sync_io(node_env_->options()->trace_sync_io);
// We do not want to crash the main process on unhandled rejections.
env->options()->unhandled_rejections = "warn-with-error-code";
node_env_->options()->unhandled_rejections = "warn-with-error-code";
// Add Electron extended APIs.
electron_bindings_->BindTo(js_env_->isolate(), env->process_object());
electron_bindings_->BindTo(js_env_->isolate(), node_env_->process_object());
// Create explicit microtasks runner.
js_env_->CreateMicrotasksRunner();
// Wrap the uv loop with global env.
node_bindings_->set_uv_env(env);
node_bindings_->set_uv_env(node_env_.get());
// Load everything.
node_bindings_->LoadEnvironment(env);
node_bindings_->LoadEnvironment(node_env_.get());
// We already initialized the feature list in PreEarlyInitialization(), but
// the user JS script would not have had a chance to alter the command-line
@@ -627,9 +626,9 @@ void ElectronBrowserMainParts::PostMainMessageLoopRun() {
// Destroy node platform after all destructors_ are executed, as they may
// invoke Node/V8 APIs inside them.
node_env_->env()->set_trace_sync_io(false);
node_env_->set_trace_sync_io(false);
js_env_->DestroyMicrotasksRunner();
node::Stop(node_env_->env(), node::StopFlags::kDoNotTerminateIsolate);
node::Stop(node_env_.get(), node::StopFlags::kDoNotTerminateIsolate);
node_env_.reset();
auto default_context_key = ElectronBrowserContext::PartitionKey("", false);

View File

@@ -37,6 +37,10 @@ class Screen;
}
#endif
namespace node {
class Environment;
}
namespace ui {
class LinuxUiGetter;
}
@@ -47,7 +51,6 @@ class Browser;
class ElectronBindings;
class JavascriptEnvironment;
class NodeBindings;
class NodeEnvironment;
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
class ElectronExtensionsClient;
@@ -157,11 +160,20 @@ class ElectronBrowserMainParts : public content::BrowserMainParts {
// Before then, we just exit() without any intermediate steps.
absl::optional<int> exit_code_;
std::unique_ptr<JavascriptEnvironment> js_env_;
std::unique_ptr<Browser> browser_;
std::unique_ptr<NodeBindings> node_bindings_;
// depends-on: node_bindings_
std::unique_ptr<ElectronBindings> electron_bindings_;
std::unique_ptr<NodeEnvironment> node_env_;
// depends-on: node_bindings_
std::unique_ptr<JavascriptEnvironment> js_env_;
// depends-on: js_env_'s isolate
std::shared_ptr<node::Environment> node_env_;
// depends-on: js_env_'s isolate
std::unique_ptr<Browser> browser_;
std::unique_ptr<IconManager> icon_manager_;
std::unique_ptr<base::FieldTrialList> field_trial_list_;

View File

@@ -13,6 +13,7 @@ function_registration("api_registration") {
sources = [
"//electron/shell/common/extensions/api/extension.json",
"//electron/shell/common/extensions/api/resources_private.idl",
"//electron/shell/common/extensions/api/scripting.idl",
"//electron/shell/common/extensions/api/tabs.json",
]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,207 @@
// Copyright 2023 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_EXTENSIONS_API_SCRIPTING_SCRIPTING_API_H_
#define ELECTRON_SHELL_BROWSER_EXTENSIONS_API_SCRIPTING_SCRIPTING_API_H_
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "chrome/common/extensions/api/scripting.h"
#include "extensions/browser/extension_function.h"
#include "extensions/browser/script_executor.h"
#include "extensions/common/mojom/code_injection.mojom.h"
#include "extensions/common/user_script.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace extensions {
// A simple helper struct to represent a read file (either CSS or JS) to be
// injected.
struct InjectedFileSource {
InjectedFileSource(std::string file_name, std::unique_ptr<std::string> data);
InjectedFileSource(InjectedFileSource&&);
~InjectedFileSource();
std::string file_name;
std::unique_ptr<std::string> data;
};
class ScriptingExecuteScriptFunction : public ExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("scripting.executeScript", SCRIPTING_EXECUTESCRIPT)
ScriptingExecuteScriptFunction();
ScriptingExecuteScriptFunction(const ScriptingExecuteScriptFunction&) =
delete;
ScriptingExecuteScriptFunction& operator=(
const ScriptingExecuteScriptFunction&) = delete;
// ExtensionFunction:
ResponseAction Run() override;
private:
~ScriptingExecuteScriptFunction() override;
// Called when the resource files to be injected has been loaded.
void DidLoadResources(std::vector<InjectedFileSource> file_sources,
absl::optional<std::string> load_error);
// Triggers the execution of `sources` in the appropriate context.
// Returns true on success; on failure, populates `error`.
bool Execute(std::vector<mojom::JSSourcePtr> sources, std::string* error);
// Invoked when script execution is complete.
void OnScriptExecuted(std::vector<ScriptExecutor::FrameResult> frame_results);
api::scripting::ScriptInjection injection_;
};
class ScriptingInsertCSSFunction : public ExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("scripting.insertCSS", SCRIPTING_INSERTCSS)
ScriptingInsertCSSFunction();
ScriptingInsertCSSFunction(const ScriptingInsertCSSFunction&) = delete;
ScriptingInsertCSSFunction& operator=(const ScriptingInsertCSSFunction&) =
delete;
// ExtensionFunction:
ResponseAction Run() override;
private:
~ScriptingInsertCSSFunction() override;
// Called when the resource files to be injected has been loaded.
void DidLoadResources(std::vector<InjectedFileSource> file_sources,
absl::optional<std::string> load_error);
// Triggers the execution of `sources` in the appropriate context.
// Returns true on success; on failure, populates `error`.
bool Execute(std::vector<mojom::CSSSourcePtr> sources, std::string* error);
// Called when the CSS insertion is complete.
void OnCSSInserted(std::vector<ScriptExecutor::FrameResult> results);
api::scripting::CSSInjection injection_;
};
class ScriptingRemoveCSSFunction : public ExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("scripting.removeCSS", SCRIPTING_REMOVECSS)
ScriptingRemoveCSSFunction();
ScriptingRemoveCSSFunction(const ScriptingRemoveCSSFunction&) = delete;
ScriptingRemoveCSSFunction& operator=(const ScriptingRemoveCSSFunction&) =
delete;
// ExtensionFunction:
ResponseAction Run() override;
private:
~ScriptingRemoveCSSFunction() override;
// Called when the CSS removal is complete.
void OnCSSRemoved(std::vector<ScriptExecutor::FrameResult> results);
};
using ValidateContentScriptsResult =
std::pair<std::unique_ptr<UserScriptList>, absl::optional<std::string>>;
class ScriptingRegisterContentScriptsFunction : public ExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("scripting.registerContentScripts",
SCRIPTING_REGISTERCONTENTSCRIPTS)
ScriptingRegisterContentScriptsFunction();
ScriptingRegisterContentScriptsFunction(
const ScriptingRegisterContentScriptsFunction&) = delete;
ScriptingRegisterContentScriptsFunction& operator=(
const ScriptingRegisterContentScriptsFunction&) = delete;
// ExtensionFunction:
ResponseAction Run() override;
private:
~ScriptingRegisterContentScriptsFunction() override;
// Called when script files have been checked.
void OnContentScriptFilesValidated(
std::set<std::string> persistent_script_ids,
ValidateContentScriptsResult result);
// Called when content scripts have been registered.
void OnContentScriptsRegistered(const absl::optional<std::string>& error);
};
class ScriptingGetRegisteredContentScriptsFunction : public ExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("scripting.getRegisteredContentScripts",
SCRIPTING_GETREGISTEREDCONTENTSCRIPTS)
ScriptingGetRegisteredContentScriptsFunction();
ScriptingGetRegisteredContentScriptsFunction(
const ScriptingGetRegisteredContentScriptsFunction&) = delete;
ScriptingGetRegisteredContentScriptsFunction& operator=(
const ScriptingGetRegisteredContentScriptsFunction&) = delete;
// ExtensionFunction:
ResponseAction Run() override;
private:
~ScriptingGetRegisteredContentScriptsFunction() override;
};
class ScriptingUnregisterContentScriptsFunction : public ExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("scripting.unregisterContentScripts",
SCRIPTING_UNREGISTERCONTENTSCRIPTS)
ScriptingUnregisterContentScriptsFunction();
ScriptingUnregisterContentScriptsFunction(
const ScriptingUnregisterContentScriptsFunction&) = delete;
ScriptingUnregisterContentScriptsFunction& operator=(
const ScriptingUnregisterContentScriptsFunction&) = delete;
// ExtensionFunction:
ResponseAction Run() override;
private:
~ScriptingUnregisterContentScriptsFunction() override;
// Called when content scripts have been unregistered.
void OnContentScriptsUnregistered(const absl::optional<std::string>& error);
};
class ScriptingUpdateContentScriptsFunction : public ExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("scripting.updateContentScripts",
SCRIPTING_UPDATECONTENTSCRIPTS)
ScriptingUpdateContentScriptsFunction();
ScriptingUpdateContentScriptsFunction(
const ScriptingUpdateContentScriptsFunction&) = delete;
ScriptingUpdateContentScriptsFunction& operator=(
const ScriptingUpdateContentScriptsFunction&) = delete;
// ExtensionFunction:
ResponseAction Run() override;
private:
~ScriptingUpdateContentScriptsFunction() override;
// Called when script files have been checked.
void OnContentScriptFilesValidated(
std::set<std::string> persistent_script_ids,
ValidateContentScriptsResult result);
// Called when content scripts have been updated.
void OnContentScriptsUpdated(const absl::optional<std::string>& error);
};
} // namespace extensions
#endif // ELECTRON_SHELL_BROWSER_EXTENSIONS_API_SCRIPTING_SCRIPTING_API_H_

View File

@@ -301,6 +301,7 @@ ExtensionFunction::ResponseAction TabsQueryFunction::Run() {
tabs::Tab tab;
tab.id = contents->ID();
tab.title = base::UTF16ToUTF8(wc->GetTitle());
tab.url = wc->GetLastCommittedURL().spec();
tab.active = contents->IsFocused();
tab.audible = contents->IsCurrentlyAudible();
@@ -322,12 +323,18 @@ ExtensionFunction::ResponseAction TabsGetFunction::Run() {
return RespondNow(Error("No such tab"));
tabs::Tab tab;
tab.id = tab_id;
// TODO(nornagon): in Chrome, the tab URL is only available to extensions
// that have the "tabs" (or "activeTab") permission. We should do the same
// permission check here.
tab.url = contents->web_contents()->GetLastCommittedURL().spec();
// "title" and "url" properties are considered privileged data and can
// only be checked if the extension has the "tabs" permission or it has
// access to the WebContents's origin.
auto* wc = contents->web_contents();
if (extension()->permissions_data()->HasAPIPermissionForTab(
contents->ID(), mojom::APIPermissionID::kTab) ||
extension()->permissions_data()->HasHostPermission(wc->GetURL())) {
tab.url = wc->GetLastCommittedURL().spec();
tab.title = base::UTF16ToUTF8(wc->GetTitle());
}
tab.active = contents->IsFocused();
@@ -609,10 +616,16 @@ ExtensionFunction::ResponseValue TabsUpdateFunction::GetResult() {
auto* api_web_contents = electron::api::WebContents::From(web_contents_);
tab.id = (api_web_contents ? api_web_contents->ID() : -1);
// TODO(nornagon): in Chrome, the tab URL is only available to extensions
// that have the "tabs" (or "activeTab") permission. We should do the same
// permission check here.
tab.url = web_contents_->GetLastCommittedURL().spec();
// "title" and "url" properties are considered privileged data and can
// only be checked if the extension has the "tabs" permission or it has
// access to the WebContents's origin.
if (extension()->permissions_data()->HasAPIPermissionForTab(
api_web_contents->ID(), mojom::APIPermissionID::kTab) ||
extension()->permissions_data()->HasHostPermission(
web_contents_->GetURL())) {
tab.url = web_contents_->GetLastCommittedURL().spec();
tab.title = base::UTF16ToUTF8(web_contents_->GetTitle());
}
if (api_web_contents)
tab.active = api_web_contents->IsFocused();

View File

@@ -7,6 +7,7 @@
#include "extensions/browser/api/i18n/i18n_api.h"
#include "extensions/browser/extension_function_registry.h"
#include "shell/browser/extensions/api/generated_api_registration.h"
#include "shell/browser/extensions/api/scripting/scripting_api.h"
#include "shell/browser/extensions/api/tabs/tabs_api.h"
namespace extensions {

View File

@@ -104,9 +104,10 @@ gin::IsolateHolder CreateIsolateHolder(v8::Isolate* isolate) {
JavascriptEnvironment::JavascriptEnvironment(uv_loop_t* event_loop,
bool setup_wasm_streaming)
: isolate_(Initialize(event_loop, setup_wasm_streaming)),
isolate_holder_(CreateIsolateHolder(isolate_)),
locker_(isolate_) {
: isolate_holder_{CreateIsolateHolder(
Initialize(event_loop, setup_wasm_streaming))},
isolate_{isolate_holder_.isolate()},
locker_{isolate_} {
isolate_->Enter();
v8::HandleScope scope(isolate_);
@@ -339,12 +340,4 @@ void JavascriptEnvironment::DestroyMicrotasksRunner() {
base::CurrentThread::Get()->RemoveTaskObserver(microtasks_runner_.get());
}
NodeEnvironment::NodeEnvironment(node::Environment* env) : env_(env) {}
NodeEnvironment::~NodeEnvironment() {
auto* isolate_data = env_->isolate_data();
node::FreeEnvironment(env_);
node::FreeIsolateData(isolate_data);
}
} // namespace electron

View File

@@ -43,29 +43,17 @@ class JavascriptEnvironment {
v8::Isolate* Initialize(uv_loop_t* event_loop, bool setup_wasm_streaming);
std::unique_ptr<node::MultiIsolatePlatform> platform_;
raw_ptr<v8::Isolate> isolate_;
gin::IsolateHolder isolate_holder_;
// owned-by: isolate_holder_
const raw_ptr<v8::Isolate> isolate_;
// depends-on: isolate_
v8::Locker locker_;
std::unique_ptr<MicrotasksRunner> microtasks_runner_;
};
// Manage the Node Environment automatically.
class NodeEnvironment {
public:
explicit NodeEnvironment(node::Environment* env);
~NodeEnvironment();
// disable copy
NodeEnvironment(const NodeEnvironment&) = delete;
NodeEnvironment& operator=(const NodeEnvironment&) = delete;
node::Environment* env() { return env_; }
private:
raw_ptr<node::Environment> env_;
};
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_JAVASCRIPT_ENVIRONMENT_H_

View File

@@ -55,25 +55,27 @@ void NativeBrowserViewMac::SetBounds(const gfx::Rect& bounds) {
auto* iwc_view = GetInspectableWebContentsView();
if (!iwc_view)
return;
auto* view = iwc_view->GetNativeView().GetNativeNSView();
auto* superview = view.superview;
const auto superview_height = superview ? superview.frame.size.height : 0;
view.frame =
NSMakeRect(bounds.x(), superview_height - bounds.y() - bounds.height(),
bounds.width(), bounds.height());
const auto superview_height =
view.superview ? view.superview.frame.size.height : 0;
int y_coord = superview_height - bounds.y() - bounds.height();
view.frame = NSMakeRect(bounds.x(), y_coord, bounds.width(), bounds.height());
}
gfx::Rect NativeBrowserViewMac::GetBounds() {
auto* iwc_view = GetInspectableWebContentsView();
if (!iwc_view)
return gfx::Rect();
NSView* view = iwc_view->GetNativeView().GetNativeNSView();
const int superview_height =
(view.superview) ? view.superview.frame.size.height : 0;
return gfx::Rect(
view.frame.origin.x,
superview_height - view.frame.origin.y - view.frame.size.height,
view.frame.size.width, view.frame.size.height);
view.superview ? view.superview.frame.size.height : 0;
int y_coord = superview_height - view.frame.origin.y - view.frame.size.height;
return gfx::Rect(view.frame.origin.x, y_coord, view.frame.size.width,
view.frame.size.height);
}
void NativeBrowserViewMac::SetBackgroundColor(SkColor color) {

View File

@@ -5,6 +5,6 @@
#include "shell/browser/native_window_features.h"
namespace features {
const base::Feature kWaylandWindowDecorations{
"WaylandWindowDecorations", base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kWaylandWindowDecorations{"WaylandWindowDecorations",
base::FEATURE_ENABLED_BY_DEFAULT};
}

View File

@@ -834,23 +834,19 @@ void NativeWindowMac::SetResizable(bool resizable) {
ScopedDisableResize disable_resize;
SetStyleMask(resizable, NSWindowStyleMaskResizable);
bool was_fullscreenable = IsFullScreenable();
// Right now, resizable and fullscreenable are decoupled in
// documentation and on Windows/Linux. Chromium disables
// fullscreenability if resizability is false on macOS as well
// as disabling the maximize traffic light unless the window
// is both resizable and maximizable. To work around this, we want
// to match behavior on other platforms by disabiliting the maximize
// button but keeping fullscreenability enabled.
// TODO(codebytere): refactor this once we have a better solution.
// fullscreen collection behavior as well as the maximize traffic
// light in SetCanResize if resizability is false on macOS unless
// the window is both resizable and maximizable. We want consistent
// cross-platform behavior, so if resizability is disabled we disable
// the maximize button and ensure fullscreenability matches user setting.
SetCanResize(resizable);
if (!resizable) {
SetFullScreenable(true);
[[window_ standardWindowButton:NSWindowZoomButton] setEnabled:false];
} else {
SetFullScreenable(true);
[[window_ standardWindowButton:NSWindowZoomButton]
setEnabled:IsFullScreenable()];
}
SetFullScreenable(was_fullscreenable);
[[window_ standardWindowButton:NSWindowZoomButton]
setEnabled:resizable ? was_fullscreenable : false];
}
bool NativeWindowMac::IsResizable() {

View File

@@ -44,11 +44,13 @@
#include "services/network/public/cpp/simple_url_loader_stream_consumer.h"
#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
#include "shell/browser/api/electron_api_web_contents.h"
#include "shell/browser/native_window_views.h"
#include "shell/browser/net/asar/asar_url_loader_factory.h"
#include "shell/browser/protocol_registry.h"
#include "shell/browser/ui/inspectable_web_contents_delegate.h"
#include "shell/browser/ui/inspectable_web_contents_view.h"
#include "shell/browser/ui/inspectable_web_contents_view_delegate.h"
#include "shell/common/application_info.h"
#include "shell/common/platform_util.h"
#include "third_party/blink/public/common/logging/logging_utils.h"
#include "third_party/blink/public/common/page/page_zoom.h"
@@ -585,6 +587,23 @@ void InspectableWebContents::LoadCompleted() {
prefs.FindString("currentDockState");
base::RemoveChars(*current_dock_state, "\"", &dock_state_);
}
#if BUILDFLAG(IS_WIN)
auto* api_web_contents = api::WebContents::From(GetWebContents());
if (api_web_contents) {
auto* win =
static_cast<NativeWindowViews*>(api_web_contents->owner_window());
// When WCO is enabled, undock the devtools if the current dock
// position overlaps with the position of window controls to avoid
// broken layout.
if (win && win->IsWindowControlsOverlayEnabled()) {
if (IsAppRTL() && dock_state_ == "left") {
dock_state_ = "undocked";
} else if (dock_state_ == "right") {
dock_state_ = "undocked";
}
}
}
#endif
std::u16string javascript = base::UTF8ToUTF16(
"UI.DockController.instance().setDockSide(\"" + dock_state_ + "\");");
GetDevToolsWebContents()->GetPrimaryMainFrame()->ExecuteJavaScript(

View File

@@ -150,7 +150,13 @@ void ClientFrameViewLinux::Init(NativeWindowViews* window,
}
gfx::Insets ClientFrameViewLinux::GetBorderDecorationInsets() const {
return frame_provider_->GetFrameThicknessDip();
const auto insets = frame_provider_->GetFrameThicknessDip();
// We shouldn't draw frame decorations for the tiled edges.
// See https://wayland.app/protocols/xdg-shell#xdg_toplevel:enum:state
return gfx::Insets::TLBR(tiled_edges().top ? 0 : insets.top(),
tiled_edges().left ? 0 : insets.left(),
tiled_edges().bottom ? 0 : insets.bottom(),
tiled_edges().right ? 0 : insets.right());
}
gfx::Insets ClientFrameViewLinux::GetInputInsets() const {

View File

@@ -45,7 +45,7 @@ electron::UsbChooserContext* GetChooserContext(
// These extensions can claim the smart card USB class and automatically gain
// permissions for devices that have an interface with this class.
constexpr auto kSmartCardPrivilegedExtensionIds =
base::MakeFixedFlatSet<base::StringPiece>({
base::MakeFixedFlatSetSorted<base::StringPiece>({
// Smart Card Connector Extension and its Beta version, see
// crbug.com/1233881.
"khpfeaanjngmcnplbdlpegiifgpfgdco",

View File

@@ -4,8 +4,10 @@
#include "shell/common/application_info.h"
#include "base/i18n/rtl.h"
#include "base/no_destructor.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/browser_process.h"
#include "chrome/common/chrome_version.h"
#include "content/public/common/user_agent.h"
#include "electron/electron_version.h"
@@ -47,4 +49,11 @@ std::string GetApplicationUserAgent() {
return content::BuildUserAgentFromProduct(user_agent);
}
bool IsAppRTL() {
const std::string& locale = g_browser_process->GetApplicationLocale();
base::i18n::TextDirection text_direction =
base::i18n::GetTextDirectionForLocaleInStartUp(locale.c_str());
return text_direction == base::i18n::RIGHT_TO_LEFT;
}
} // namespace electron

View File

@@ -25,6 +25,8 @@ std::string GetApplicationVersion();
// Returns the user agent of Electron.
std::string GetApplicationUserAgent();
bool IsAppRTL();
#if BUILDFLAG(IS_WIN)
PCWSTR GetRawAppUserModelID();
bool GetAppUserModelID(ScopedHString* app_id);

View File

@@ -39,6 +39,7 @@ generated_json_strings("generated_api_json_strings") {
sources = [
"extension.json",
"resources_private.idl",
"scripting.idl",
"tabs.json",
]
@@ -59,6 +60,7 @@ generated_json_strings("generated_api_json_strings") {
generated_types("generated_api_types") {
sources = [
"resources_private.idl",
"scripting.idl",
"tabs.json",
]

View File

@@ -51,5 +51,9 @@
"matches": [
"chrome://print/*"
]
}]
}],
"scripting": {
"dependencies": ["permission:scripting"],
"contexts": ["blessed_extension"]
}
}

View File

@@ -7,6 +7,10 @@
// well as feature.h, simple_feature.h, and feature_provider.h.
{
"author": {
"channel": "stable",
"extension_types": "all"
},
"content_scripts": {
"channel": "stable",
"extension_types": ["extension"]
@@ -15,8 +19,17 @@
"channel": "stable",
"extension_types": ["extension"]
},
"host_permissions": {
"channel": "stable",
"extension_types": ["extension"],
"min_manifest_version": 3
},
"minimum_chrome_version": {
"channel": "stable",
"extension_types": ["extension"]
},
"short_name": {
"channel": "stable",
"extension_types": "all"
}
}

View File

@@ -20,5 +20,18 @@
"extension_types": [
"extension"
]
},
"tabs": {
"channel": "stable",
"extension_types": [
"extension"
]
},
"scripting": {
"channel": "stable",
"extension_types": [
"extension"
],
"min_manifest_version": 3
}
}

View File

@@ -0,0 +1,262 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Use the <code>chrome.scripting</code> API to execute script in different
// contexts.
namespace scripting {
callback InjectedFunction = void();
// The origin for a style change.
// See <a href="https://developer.mozilla.org/en-US/docs/Glossary/Style_origin">style origins</a>
// for more info.
enum StyleOrigin {
AUTHOR,
USER
};
// The JavaScript world for a script to execute within.
enum ExecutionWorld {
// Specifies the isolated world, which is the execution environment unique
// to this extension.
ISOLATED,
// Specifies the main world of the DOM, which is the execution environment
// shared with the host page's JavaScript.
MAIN
};
dictionary InjectionTarget {
// The ID of the tab into which to inject.
long tabId;
// The <a href="https://developer.chrome.com/extensions/webNavigation#frame_ids">IDs</a>
// of specific frames to inject into.
long[]? frameIds;
// The <a href="https://developer.chrome.com/extensions/webNavigation#document_ids">IDs</a>
// of specific documentIds to inject into. This must not be set if
// <code>frameIds</code> is set.
DOMString[]? documentIds;
// Whether the script should inject into all frames within the tab. Defaults
// to false.
// This must not be true if <code>frameIds</code> is specified.
boolean? allFrames;
};
dictionary ScriptInjection {
// A JavaScript function to inject. This function will be serialized, and
// then deserialized for injection. This means that any bound parameters
// and execution context will be lost.
// Exactly one of <code>files</code> and <code>func</code> must be
// specified.
[serializableFunction]InjectedFunction? func;
// The arguments to curry into a provided function. This is only valid if
// the <code>func</code> parameter is specified. These arguments must be
// JSON-serializable.
any[]? args;
// We used to call the injected function `function`, but this is
// incompatible with JavaScript's object declaration shorthand (see
// https://crbug.com/1166438). We leave this silently in for backwards
// compatibility.
// TODO(devlin): Remove this in M95.
[nodoc, serializableFunction]InjectedFunction? function;
// The path of the JS or CSS files to inject, relative to the extension's
// root directory.
// Exactly one of <code>files</code> and <code>func</code> must be
// specified.
DOMString[]? files;
// Details specifying the target into which to inject the script.
InjectionTarget target;
// The JavaScript "world" to run the script in. Defaults to
// <code>ISOLATED</code>.
ExecutionWorld? world;
// Whether the injection should be triggered in the target as soon as
// possible. Note that this is not a guarantee that injection will occur
// prior to page load, as the page may have already loaded by the time the
// script reaches the target.
boolean? injectImmediately;
};
dictionary CSSInjection {
// Details specifying the target into which to insert the CSS.
InjectionTarget target;
// A string containing the CSS to inject.
// Exactly one of <code>files</code> and <code>css</code> must be
// specified.
DOMString? css;
// The path of the CSS files to inject, relative to the extension's root
// directory.
// Exactly one of <code>files</code> and <code>css</code> must be
// specified.
DOMString[]? files;
// The style origin for the injection. Defaults to <code>'AUTHOR'</code>.
StyleOrigin? origin;
};
dictionary InjectionResult {
// The result of the script execution.
any? result;
// The frame associated with the injection.
long frameId;
// The document associated with the injection.
DOMString documentId;
};
// Describes a content script to be injected into a web page registered
// through this API.
dictionary RegisteredContentScript {
// The id of the content script, specified in the API call. Must not start
// with a '_' as it's reserved as a prefix for generated script IDs.
DOMString id;
// Specifies which pages this content script will be injected into. See
// <a href="match_patterns">Match Patterns</a> for more details on the
// syntax of these strings. Must be specified for
// $(ref:registerContentScripts).
DOMString[]? matches;
// Excludes pages that this content script would otherwise be injected into.
// See <a href="match_patterns">Match Patterns</a> for more details on the
// syntax of these strings.
DOMString[]? excludeMatches;
// The list of CSS files to be injected into matching pages. These are
// injected in the order they appear in this array, before any DOM is
// constructed or displayed for the page.
DOMString[]? css;
// The list of JavaScript files to be injected into matching pages. These
// are injected in the order they appear in this array.
DOMString[]? js;
// If specified true, it will inject into all frames, even if the frame is
// not the top-most frame in the tab. Each frame is checked independently
// for URL requirements; it will not inject into child frames if the URL
// requirements are not met. Defaults to false, meaning that only the top
// frame is matched.
boolean? allFrames;
// TODO(devlin): Add documentation once the implementation is complete. See
// crbug.com/55084.
[nodoc]
boolean? matchOriginAsFallback;
// Specifies when JavaScript files are injected into the web page. The
// preferred and default value is <code>document_idle</code>.
extensionTypes.RunAt? runAt;
// Specifies if this content script will persist into future sessions. The
// default is true.
boolean? persistAcrossSessions;
// The JavaScript "world" to run the script in. Defaults to
// <code>ISOLATED</code>.
ExecutionWorld? world;
};
// An object used to filter content scripts for
// ${ref:getRegisteredContentScripts}.
dictionary ContentScriptFilter {
// If specified, $(ref:getRegisteredContentScripts) will only return scripts
// with an id specified in this list.
DOMString[]? ids;
};
callback ScriptInjectionCallback = void(InjectionResult[] results);
callback CSSInjectionCallback = void();
callback RegisterContentScriptsCallback = void();
callback GetRegisteredContentScriptsCallback = void(
RegisteredContentScript[] scripts);
callback UnregisterContentScriptsCallback = void();
callback UpdateContentScriptsCallback = void();
interface Properties {
// An object available for content scripts running in isolated worlds to use
// and modify as a JS object. One instance exists per frame and is shared
// between all content scripts for a given extension. This object is
// initialized when the frame is created, before document_start.
// TODO(crbug.com/1054624): Enable this once implementation is complete.
[nodoc, nocompile] static long globalParams();
};
interface Functions {
// Injects a script into a target context. The script will be run at
// <code>document_idle</code>. If the script evaluates to a promise,
// the browser will wait for the promise to settle and return the
// resulting value.
// |injection|: The details of the script which to inject.
// |callback|: Invoked upon completion of the injection. The resulting
// array contains the result of execution for each frame where the
// injection succeeded.
[supportsPromises] static void executeScript(
ScriptInjection injection,
optional ScriptInjectionCallback callback);
// Inserts a CSS stylesheet into a target context.
// If multiple frames are specified, unsuccessful injections are ignored.
// |injection|: The details of the styles to insert.
// |callback|: Invoked upon completion of the insertion.
[supportsPromises] static void insertCSS(
CSSInjection injection,
optional CSSInjectionCallback callback);
// Removes a CSS stylesheet that was previously inserted by this extension
// from a target context.
// |injection|: The details of the styles to remove. Note that the
// <code>css</code>, <code>files</code>, and <code>origin</code> properties
// must exactly match the stylesheet inserted through $(ref:insertCSS).
// Attempting to remove a non-existent stylesheet is a no-op.
// |callback|: A callback to be invoked upon the completion of the removal.
[supportsPromises] static void removeCSS(
CSSInjection injection,
optional CSSInjectionCallback callback);
// Registers one or more content scripts for this extension.
// |scripts|: Contains a list of scripts to be registered. If there are
// errors during script parsing/file validation, or if the IDs specified
// already exist, then no scripts are registered.
// |callback|: A callback to be invoked once scripts have been fully
// registered or if an error has occurred.
[supportsPromises] static void registerContentScripts(
RegisteredContentScript[] scripts,
optional RegisterContentScriptsCallback callback);
// Returns all dynamically registered content scripts for this extension
// that match the given filter.
// |filter|: An object to filter the extension's dynamically registered
// scripts.
[supportsPromises] static void getRegisteredContentScripts(
optional ContentScriptFilter filter,
GetRegisteredContentScriptsCallback callback);
// Unregisters content scripts for this extension.
// |filter|: If specified, only unregisters dynamic content scripts which
// match the filter. Otherwise, all of the extension's dynamic content
// scripts are unregistered.
// |callback|: A callback to be invoked once scripts have been unregistered
// or if an error has occurred.
[supportsPromises] static void unregisterContentScripts(
optional ContentScriptFilter filter,
optional UnregisterContentScriptsCallback callback);
// Updates one or more content scripts for this extension.
// |scripts|: Contains a list of scripts to be updated. A property is only
// updated for the existing script if it is specified in this object. If
// there are errors during script parsing/file validation, or if the IDs
// specified do not correspond to a fully registered script, then no scripts
// are updated.
// |callback|: A callback to be invoked once scripts have been updated or
// if an error has occurred.
[supportsPromises] static void updateContentScripts(
RegisteredContentScript[] scripts,
optional RegisterContentScriptsCallback callback);
};
};

View File

@@ -704,9 +704,362 @@
}
}
],
// Electron does not yet support tab events - we define them here because otherwise extensions crash when
// they try to listen for them.
"events": [
{
"name": "onCreated",
"deprecated": "chrome.tabs.onCreated is not current supported in Electron",
"type": "function",
"description": "Fired when a tab is created. Note that the tab's URL and tab group membership may not be set at the time this event is fired, but you can listen to onUpdated events so as to be notified when a URL is set or the tab is added to a tab group.",
"parameters": [
{
"$ref": "Tab",
"name": "tab",
"description": "Details of the tab that was created."
}
]
},
{
"name": "onUpdated",
"deprecated": "chrome.tabs.onUpdated is not current supported in Electron",
"type": "function",
"description": "Fired when a tab is updated.",
"parameters": [
{
"type": "integer",
"name": "tabId",
"minimum": 0
},
{
"type": "object",
"name": "changeInfo",
"description": "Lists the changes to the state of the tab that was updated.",
"properties": {
"status": {
"$ref": "TabStatus",
"optional": true,
"description": "The tab's loading status."
},
"url": {
"type": "string",
"optional": true,
"description": "The tab's URL if it has changed."
},
"groupId": {
"type": "integer",
"optional": true,
"minimum": -1,
"description": "The tab's new group."
},
"pinned": {
"type": "boolean",
"optional": true,
"description": "The tab's new pinned state."
},
"audible": {
"type": "boolean",
"optional": true,
"description": "The tab's new audible state."
},
"discarded": {
"type": "boolean",
"optional": true,
"description": "The tab's new discarded state."
},
"autoDiscardable": {
"type": "boolean",
"optional": true,
"description": "The tab's new auto-discardable state."
},
"mutedInfo": {
"$ref": "MutedInfo",
"optional": true,
"description": "The tab's new muted state and the reason for the change."
},
"favIconUrl": {
"type": "string",
"optional": true,
"description": "The tab's new favicon URL."
},
"title": {
"type": "string",
"optional": true,
"description": "The tab's new title."
}
}
},
{
"$ref": "Tab",
"name": "tab",
"description": "Gives the state of the tab that was updated."
}
]
},
{
"name": "onMoved",
"deprecated": "chrome.tabs.onMoved is not current supported in Electron",
"type": "function",
"description": "Fired when a tab is moved within a window. Only one move event is fired, representing the tab the user directly moved. Move events are not fired for the other tabs that must move in response to the manually-moved tab. This event is not fired when a tab is moved between windows; for details, see $(ref:tabs.onDetached).",
"parameters": [
{
"type": "integer",
"name": "tabId",
"minimum": 0
},
{
"type": "object",
"name": "moveInfo",
"properties": {
"windowId": {
"type": "integer",
"minimum": 0
},
"fromIndex": {
"type": "integer",
"minimum": 0
},
"toIndex": {
"type": "integer",
"minimum": 0
}
}
}
]
},
{
"name": "onSelectionChanged",
"deprecated": "chrome.tabs.onSelectionChanged is not current supported in Electron",
"type": "function",
"description": "Fires when the selected tab in a window changes.",
"parameters": [
{
"type": "integer",
"name": "tabId",
"minimum": 0,
"description": "The ID of the tab that has become active."
},
{
"type": "object",
"name": "selectInfo",
"properties": {
"windowId": {
"type": "integer",
"minimum": 0,
"description": "The ID of the window the selected tab changed inside of."
}
}
}
]
},
{
"name": "onActiveChanged",
"deprecated": "chrome.tabs.onActiveChanged is not current supported in Electron",
"type": "function",
"description": "Fires when the selected tab in a window changes. Note that the tab's URL may not be set at the time this event fired, but you can listen to $(ref:tabs.onUpdated) events so as to be notified when a URL is set.",
"parameters": [
{
"type": "integer",
"name": "tabId",
"minimum": 0,
"description": "The ID of the tab that has become active."
},
{
"type": "object",
"name": "selectInfo",
"properties": {
"windowId": {
"type": "integer",
"minimum": 0,
"description": "The ID of the window the selected tab changed inside of."
}
}
}
]
},
{
"name": "onActivated",
"deprecated": "chrome.tabs.onActivated is not current supported in Electron",
"type": "function",
"description": "Fires when the active tab in a window changes. Note that the tab's URL may not be set at the time this event fired, but you can listen to onUpdated events so as to be notified when a URL is set.",
"parameters": [
{
"type": "object",
"name": "activeInfo",
"properties": {
"tabId": {
"type": "integer",
"minimum": 0,
"description": "The ID of the tab that has become active."
},
"windowId": {
"type": "integer",
"minimum": 0,
"description": "The ID of the window the active tab changed inside of."
}
}
}
]
},
{
"name": "onHighlightChanged",
"deprecated": "chrome.tabs.onHighlightChanged is not current supported in Electron",
"type": "function",
"description": "Fired when the highlighted or selected tabs in a window changes.",
"parameters": [
{
"type": "object",
"name": "selectInfo",
"properties": {
"windowId": {
"type": "integer",
"minimum": 0,
"description": "The window whose tabs changed."
},
"tabIds": {
"type": "array",
"name": "tabIds",
"items": {
"type": "integer",
"minimum": 0
},
"description": "All highlighted tabs in the window."
}
}
}
]
},
{
"name": "onHighlighted",
"deprecated": "chrome.tabs.onHighlighted is not current supported in Electron",
"type": "function",
"description": "Fired when the highlighted or selected tabs in a window changes.",
"parameters": [
{
"type": "object",
"name": "highlightInfo",
"properties": {
"windowId": {
"type": "integer",
"minimum": 0,
"description": "The window whose tabs changed."
},
"tabIds": {
"type": "array",
"name": "tabIds",
"items": {
"type": "integer",
"minimum": 0
},
"description": "All highlighted tabs in the window."
}
}
}
]
},
{
"name": "onDetached",
"deprecated": "chrome.tabs.onDetached is not current supported in Electron",
"type": "function",
"description": "Fired when a tab is detached from a window; for example, because it was moved between windows.",
"parameters": [
{
"type": "integer",
"name": "tabId",
"minimum": 0
},
{
"type": "object",
"name": "detachInfo",
"properties": {
"oldWindowId": {
"type": "integer",
"minimum": 0
},
"oldPosition": {
"type": "integer",
"minimum": 0
}
}
}
]
},
{
"name": "onAttached",
"deprecated": "chrome.tabs.onAttached is not current supported in Electron",
"type": "function",
"description": "Fired when a tab is attached to a window; for example, because it was moved between windows.",
"parameters": [
{
"type": "integer",
"name": "tabId",
"minimum": 0
},
{
"type": "object",
"name": "attachInfo",
"properties": {
"newWindowId": {
"type": "integer",
"minimum": 0
},
"newPosition": {
"type": "integer",
"minimum": 0
}
}
}
]
},
{
"name": "onRemoved",
"deprecated": "chrome.tabs.onRemoved is not current supported in Electron",
"type": "function",
"description": "Fired when a tab is closed.",
"parameters": [
{
"type": "integer",
"name": "tabId",
"minimum": 0
},
{
"type": "object",
"name": "removeInfo",
"properties": {
"windowId": {
"type": "integer",
"minimum": 0,
"description": "The window whose tab is closed."
},
"isWindowClosing": {
"type": "boolean",
"description": "True when the tab was closed because its parent window was closed."
}
}
}
]
},
{
"name": "onReplaced",
"deprecated": "chrome.tabs.onReplaced is not current supported in Electron",
"type": "function",
"description": "Fired when a tab is replaced with another tab due to prerendering or instant.",
"parameters": [
{
"type": "integer",
"name": "addedTabId",
"minimum": 0
},
{
"type": "integer",
"name": "removedTabId",
"minimum": 0
}
]
},
{
"name": "onZoomChange",
"deprecated": "chrome.tabs.onZoomChange is not current supported in Electron",
"type": "function",
"description": "Fired when a tab is zoomed.",
"parameters": [

View File

@@ -39,6 +39,10 @@ constexpr APIPermissionInfo::InitInfo permissions_to_register[] = {
{mojom::APIPermissionID::kPdfViewerPrivate, "pdfViewerPrivate"},
#endif
{mojom::APIPermissionID::kManagement, "management"},
{mojom::APIPermissionID::kTab, "tabs",
APIPermissionInfo::kFlagRequiresManagementUIWarning},
{mojom::APIPermissionID::kScripting, "scripting",
APIPermissionInfo::kFlagRequiresManagementUIWarning},
};
base::span<const APIPermissionInfo::InitInfo> GetPermissionInfos() {
return base::make_span(permissions_to_register);

View File

@@ -232,7 +232,7 @@ void ErrorMessageListener(v8::Local<v8::Message> message,
// If node CLI inspect support is disabled, allow no debug options.
bool IsAllowedOption(base::StringPiece option) {
static constexpr auto debug_options =
base::MakeFixedFlatSet<base::StringPiece>({
base::MakeFixedFlatSetSorted<base::StringPiece>({
"--debug",
"--debug-brk",
"--debug-port",
@@ -244,13 +244,14 @@ bool IsAllowedOption(base::StringPiece option) {
});
// This should be aligned with what's possible to set via the process object.
static constexpr auto options = base::MakeFixedFlatSet<base::StringPiece>({
"--trace-warnings",
"--trace-deprecation",
"--throw-deprecation",
"--no-deprecation",
"--dns-result-order",
});
static constexpr auto options =
base::MakeFixedFlatSetSorted<base::StringPiece>({
"--dns-result-order",
"--no-deprecation",
"--throw-deprecation",
"--trace-deprecation",
"--trace-warnings",
});
if (debug_options.contains(option))
return electron::fuses::IsNodeCliInspectEnabled();
@@ -262,14 +263,21 @@ bool IsAllowedOption(base::StringPiece option) {
// See https://nodejs.org/api/cli.html#cli_node_options_options
void SetNodeOptions(base::Environment* env) {
// Options that are unilaterally disallowed
static constexpr auto disallowed = base::MakeFixedFlatSet<base::StringPiece>(
{"--enable-fips", "--force-fips", "--openssl-config", "--use-bundled-ca",
"--use-openssl-ca", "--experimental-policy"});
static constexpr auto disallowed =
base::MakeFixedFlatSetSorted<base::StringPiece>({
"--enable-fips",
"--experimental-policy",
"--force-fips",
"--openssl-config",
"--use-bundled-ca",
"--use-openssl-ca",
});
static constexpr auto pkg_opts = base::MakeFixedFlatSet<base::StringPiece>({
"--http-parser",
"--max-http-header-size",
});
static constexpr auto pkg_opts =
base::MakeFixedFlatSetSorted<base::StringPiece>({
"--http-parser",
"--max-http-header-size",
});
if (env->HasVar("NODE_OPTIONS")) {
if (electron::fuses::IsNodeOptionsEnabled()) {
@@ -469,7 +477,7 @@ void NodeBindings::Initialize(v8::Local<v8::Context> context) {
g_is_initialized = true;
}
node::Environment* NodeBindings::CreateEnvironment(
std::shared_ptr<node::Environment> NodeBindings::CreateEnvironment(
v8::Handle<v8::Context> context,
node::MultiIsolatePlatform* platform,
std::vector<std::string> args,
@@ -636,10 +644,25 @@ node::Environment* NodeBindings::CreateEnvironment(
base::PathService::Get(content::CHILD_PROCESS_EXE, &helper_exec_path);
process.Set("helperExecPath", helper_exec_path);
return env;
auto env_deleter = [isolate, isolate_data,
context = v8::Global<v8::Context>{isolate, context}](
node::Environment* nenv) mutable {
// When `isolate_data` was created above, a pointer to it was kept
// in context's embedder_data[kElectronContextEmbedderDataIndex].
// Since we're about to free `isolate_data`, clear that entry
v8::HandleScope handle_scope{isolate};
context.Get(isolate)->SetAlignedPointerInEmbedderData(
kElectronContextEmbedderDataIndex, nullptr);
context.Reset();
node::FreeEnvironment(nenv);
node::FreeIsolateData(isolate_data);
};
return {env, std::move(env_deleter)};
}
node::Environment* NodeBindings::CreateEnvironment(
std::shared_ptr<node::Environment> NodeBindings::CreateEnvironment(
v8::Handle<v8::Context> context,
node::MultiIsolatePlatform* platform) {
#if BUILDFLAG(IS_WIN)

View File

@@ -5,6 +5,7 @@
#ifndef ELECTRON_SHELL_COMMON_NODE_BINDINGS_H_
#define ELECTRON_SHELL_COMMON_NODE_BINDINGS_H_
#include <memory>
#include <string>
#include <type_traits>
#include <vector>
@@ -25,12 +26,6 @@ class SingleThreadTaskRunner;
namespace electron {
// Choose a reasonable unique index that's higher than any Blink uses
// and thus unlikely to collide with an existing index.
static constexpr int kElectronContextEmbedderDataIndex =
static_cast<int>(gin::kPerContextDataStartIndex) +
static_cast<int>(gin::kEmbedderElectron);
// A helper class to manage uv_handle_t types, e.g. uv_async_t.
//
// As per the uv docs: "uv_close() MUST be called on each handle before
@@ -95,12 +90,15 @@ class NodeBindings {
std::vector<std::string> ParseNodeCliFlags();
// Create the environment and load node.js.
node::Environment* CreateEnvironment(v8::Handle<v8::Context> context,
node::MultiIsolatePlatform* platform,
std::vector<std::string> args,
std::vector<std::string> exec_args);
node::Environment* CreateEnvironment(v8::Handle<v8::Context> context,
node::MultiIsolatePlatform* platform);
std::shared_ptr<node::Environment> CreateEnvironment(
v8::Handle<v8::Context> context,
node::MultiIsolatePlatform* platform,
std::vector<std::string> args,
std::vector<std::string> exec_args);
std::shared_ptr<node::Environment> CreateEnvironment(
v8::Handle<v8::Context> context,
node::MultiIsolatePlatform* platform);
// Load node.js in the environment.
void LoadEnvironment(node::Environment* env);
@@ -111,12 +109,6 @@ class NodeBindings {
// Notify embed thread to start polling after environment is loaded.
void StartPolling();
// Clears the PerIsolateData.
void clear_isolate_data(v8::Local<v8::Context> context) {
context->SetAlignedPointerInEmbedderData(kElectronContextEmbedderDataIndex,
nullptr);
}
node::IsolateData* isolate_data(v8::Local<v8::Context> context) const {
if (context->GetNumberOfEmbedderDataFields() <=
kElectronContextEmbedderDataIndex) {
@@ -167,6 +159,12 @@ class NodeBindings {
raw_ptr<uv_loop_t> uv_loop_;
private:
// Choose a reasonable unique index that's higher than any Blink uses
// and thus unlikely to collide with an existing index.
static constexpr int kElectronContextEmbedderDataIndex =
static_cast<int>(gin::kPerContextDataStartIndex) +
static_cast<int>(gin::kEmbedderElectron);
// Thread to poll uv events.
static void EmbedThreadRunner(void* arg);

View File

@@ -88,7 +88,7 @@ void ElectronRendererClient::DidCreateScriptContext(
v8::Maybe<bool> initialized = node::InitializeContext(renderer_context);
CHECK(!initialized.IsNothing() && initialized.FromJust());
node::Environment* env =
std::shared_ptr<node::Environment> env =
node_bindings_->CreateEnvironment(renderer_context, nullptr);
// If we have disabled the site instance overrides we should prevent loading
@@ -106,11 +106,11 @@ void ElectronRendererClient::DidCreateScriptContext(
BindProcess(env->isolate(), &process_dict, render_frame);
// Load everything.
node_bindings_->LoadEnvironment(env);
node_bindings_->LoadEnvironment(env.get());
if (node_bindings_->uv_env() == nullptr) {
// Make uv loop being wrapped by window context.
node_bindings_->set_uv_env(env);
node_bindings_->set_uv_env(env.get());
// Give the node loop a run to make sure everything is ready.
node_bindings_->StartPolling();
@@ -124,7 +124,9 @@ void ElectronRendererClient::WillReleaseScriptContext(
return;
node::Environment* env = node::Environment::GetCurrent(context);
if (environments_.erase(env) == 0)
const auto iter = base::ranges::find_if(
environments_, [env](auto& item) { return env == item.get(); });
if (iter == environments_.end())
return;
gin_helper::EmitEvent(env->isolate(), env->process_object(), "exit");
@@ -143,9 +145,7 @@ void ElectronRendererClient::WillReleaseScriptContext(
DCHECK_EQ(microtask_queue->GetMicrotasksScopeDepth(), 0);
microtask_queue->set_microtasks_policy(v8::MicrotasksPolicy::kExplicit);
node::FreeEnvironment(env);
node::FreeIsolateData(node_bindings_->isolate_data(context));
node_bindings_->clear_isolate_data(context);
environments_.erase(iter);
microtask_queue->set_microtasks_policy(old_policy);
@@ -201,7 +201,11 @@ node::Environment* ElectronRendererClient::GetEnvironment(
auto context =
GetContext(render_frame->GetWebFrame(), v8::Isolate::GetCurrent());
node::Environment* env = node::Environment::GetCurrent(context);
return base::Contains(environments_, env) ? env : nullptr;
return base::Contains(environments_, env,
[](auto const& item) { return item.get(); })
? env
: nullptr;
}
} // namespace electron

View File

@@ -55,7 +55,7 @@ class ElectronRendererClient : public RendererClientBase {
// The node::Environment::GetCurrent API does not return nullptr when it
// is called for a context without node::Environment, so we have to keep
// a book of the environments created.
std::set<node::Environment*> environments_;
std::set<std::shared_ptr<node::Environment>> environments_;
// Getting main script context from web frame would lazily initializes
// its script context. Doing so in a web page without scripts would trigger

View File

@@ -6,7 +6,9 @@
#include <utility>
#include "base/containers/cxx20_erase_set.h"
#include "base/no_destructor.h"
#include "base/ranges/algorithm.h"
#include "base/threading/thread_local.h"
#include "shell/common/api/electron_bindings.h"
#include "shell/common/gin_helper/event_emitter_caller.h"
@@ -61,20 +63,23 @@ void WebWorkerObserver::WorkerScriptReadyForEvaluation(
// Setup node environment for each window.
v8::Maybe<bool> initialized = node::InitializeContext(worker_context);
CHECK(!initialized.IsNothing() && initialized.FromJust());
node::Environment* env =
std::shared_ptr<node::Environment> env =
node_bindings_->CreateEnvironment(worker_context, nullptr);
// Add Electron extended APIs.
electron_bindings_->BindTo(env->isolate(), env->process_object());
// Load everything.
node_bindings_->LoadEnvironment(env);
node_bindings_->LoadEnvironment(env.get());
// Make uv loop being wrapped by window context.
node_bindings_->set_uv_env(env);
node_bindings_->set_uv_env(env.get());
// Give the node loop a run to make sure everything is ready.
node_bindings_->StartPolling();
// Keep the environment alive until we free it in ContextWillDestroy()
environments_.insert(std::move(env));
}
void WebWorkerObserver::ContextWillDestroy(v8::Local<v8::Context> context) {
@@ -91,9 +96,8 @@ void WebWorkerObserver::ContextWillDestroy(v8::Local<v8::Context> context) {
DCHECK_EQ(microtask_queue->GetMicrotasksScopeDepth(), 0);
microtask_queue->set_microtasks_policy(v8::MicrotasksPolicy::kExplicit);
node::FreeEnvironment(env);
node::FreeIsolateData(node_bindings_->isolate_data(context));
node_bindings_->clear_isolate_data(context);
base::EraseIf(environments_,
[env](auto const& item) { return item.get() == env; });
microtask_queue->set_microtasks_policy(old_policy);

View File

@@ -6,9 +6,16 @@
#define ELECTRON_SHELL_RENDERER_WEB_WORKER_OBSERVER_H_
#include <memory>
#include <set>
#include "v8/include/v8.h"
namespace node {
class Environment;
} // namespace node
namespace electron {
class ElectronBindings;
@@ -35,6 +42,7 @@ class WebWorkerObserver {
private:
std::unique_ptr<NodeBindings> node_bindings_;
std::unique_ptr<ElectronBindings> electron_bindings_;
std::set<std::shared_ptr<node::Environment>> environments_;
};
} // namespace electron

View File

@@ -30,9 +30,9 @@ NodeService::NodeService(
NodeService::~NodeService() {
if (!node_env_stopped_) {
node_env_->env()->set_trace_sync_io(false);
node_env_->set_trace_sync_io(false);
js_env_->DestroyMicrotasksRunner();
node::Stop(node_env_->env(), node::StopFlags::kDoNotTerminateIsolate);
node::Stop(node_env_.get(), node::StopFlags::kDoNotTerminateIsolate);
}
}
@@ -57,13 +57,12 @@ void NodeService::Initialize(node::mojom::NodeServiceParamsPtr params) {
#endif
// Create the global environment.
node::Environment* env = node_bindings_->CreateEnvironment(
node_env_ = node_bindings_->CreateEnvironment(
js_env_->isolate()->GetCurrentContext(), js_env_->platform(),
params->args, params->exec_args);
node_env_ = std::make_unique<NodeEnvironment>(env);
node::SetProcessExitHandler(
env, [this](node::Environment* env, int exit_code) {
node_env_.get(), [this](node::Environment* env, int exit_code) {
// Destroy node platform.
env->set_trace_sync_io(false);
js_env_->DestroyMicrotasksRunner();
@@ -72,20 +71,21 @@ void NodeService::Initialize(node::mojom::NodeServiceParamsPtr params) {
receiver_.ResetWithReason(exit_code, "");
});
env->set_trace_sync_io(env->options()->trace_sync_io);
node_env_->set_trace_sync_io(node_env_->options()->trace_sync_io);
// Add Electron extended APIs.
electron_bindings_->BindTo(env->isolate(), env->process_object());
electron_bindings_->BindTo(node_env_->isolate(), node_env_->process_object());
// Add entry script to process object.
gin_helper::Dictionary process(env->isolate(), env->process_object());
gin_helper::Dictionary process(node_env_->isolate(),
node_env_->process_object());
process.SetHidden("_serviceStartupScript", params->script);
// Setup microtask runner.
js_env_->CreateMicrotasksRunner();
// Wrap the uv loop with global env.
node_bindings_->set_uv_env(env);
node_bindings_->set_uv_env(node_env_.get());
// LoadEnvironment should be called after setting up
// JavaScriptEnvironment including the microtask runner
@@ -94,7 +94,7 @@ void NodeService::Initialize(node::mojom::NodeServiceParamsPtr params) {
// the exit handler set above will be triggered and it expects
// both Node Env and JavaScriptEnviroment are setup to perform
// a clean shutdown of this process.
node_bindings_->LoadEnvironment(env);
node_bindings_->LoadEnvironment(node_env_.get());
// Run entry script.
node_bindings_->PrepareEmbedThread();

View File

@@ -11,12 +11,17 @@
#include "mojo/public/cpp/bindings/receiver.h"
#include "shell/services/node/public/mojom/node_service.mojom.h"
namespace node {
class Environment;
} // namespace node
namespace electron {
class ElectronBindings;
class JavascriptEnvironment;
class NodeBindings;
class NodeEnvironment;
class NodeService : public node::mojom::NodeService {
public:
@@ -35,7 +40,7 @@ class NodeService : public node::mojom::NodeService {
std::unique_ptr<JavascriptEnvironment> js_env_;
std::unique_ptr<NodeBindings> node_bindings_;
std::unique_ptr<ElectronBindings> electron_bindings_;
std::unique_ptr<NodeEnvironment> node_env_;
std::shared_ptr<node::Environment> node_env_;
mojo::Receiver<node::mojom::NodeService> receiver_{this};
};

View File

@@ -147,6 +147,41 @@ describe('BrowserView module', () => {
view.setBounds({} as any);
}).to.throw(/conversion failure/);
});
it('can set bounds after view is added to window', () => {
view = new BrowserView();
const bounds = { x: 0, y: 0, width: 50, height: 50 };
w.addBrowserView(view);
view.setBounds(bounds);
expect(view.getBounds()).to.deep.equal(bounds);
});
it('can set bounds before view is added to window', () => {
view = new BrowserView();
const bounds = { x: 0, y: 0, width: 50, height: 50 };
view.setBounds(bounds);
w.addBrowserView(view);
expect(view.getBounds()).to.deep.equal(bounds);
});
it('can update bounds', () => {
view = new BrowserView();
w.addBrowserView(view);
const bounds1 = { x: 0, y: 0, width: 50, height: 50 };
view.setBounds(bounds1);
expect(view.getBounds()).to.deep.equal(bounds1);
const bounds2 = { x: 0, y: 150, width: 50, height: 50 };
view.setBounds(bounds2);
expect(view.getBounds()).to.deep.equal(bounds2);
});
});
describe('BrowserView.getBounds()', () => {
@@ -156,6 +191,16 @@ describe('BrowserView module', () => {
view.setBounds(bounds);
expect(view.getBounds()).to.deep.equal(bounds);
});
it('does not changer after being added to a window', () => {
view = new BrowserView();
const bounds = { x: 10, y: 20, width: 30, height: 40 };
view.setBounds(bounds);
expect(view.getBounds()).to.deep.equal(bounds);
w.addBrowserView(view);
expect(view.getBounds()).to.deep.equal(bounds);
});
});
describe('BrowserWindow.setBrowserView()', () => {

View File

@@ -5456,6 +5456,42 @@ describe('BrowserWindow module', () => {
expect(w.isFullScreenable()).to.be.true('isFullScreenable');
});
});
it('does not open non-fullscreenable child windows in fullscreen if parent is fullscreen', async () => {
const w = new BrowserWindow();
const enterFS = once(w, 'enter-full-screen');
w.setFullScreen(true);
await enterFS;
const child = new BrowserWindow({ parent: w, resizable: false, fullscreenable: false });
const shown = once(child, 'show');
await shown;
expect(child.resizable).to.be.false('resizable');
expect(child.fullScreen).to.be.false('fullscreen');
expect(child.fullScreenable).to.be.false('fullscreenable');
});
it('is set correctly with different resizable values', async () => {
const w1 = new BrowserWindow({
resizable: false,
fullscreenable: false
});
const w2 = new BrowserWindow({
resizable: true,
fullscreenable: false
});
const w3 = new BrowserWindow({
fullscreenable: false
});
expect(w1.isFullScreenable()).to.be.false('isFullScreenable');
expect(w2.isFullScreenable()).to.be.false('isFullScreenable');
expect(w3.isFullScreenable()).to.be.false('isFullScreenable');
});
});
ifdescribe(process.platform === 'darwin')('isHiddenInMissionControl state', () => {

View File

@@ -5,8 +5,8 @@ import * as path from 'node:path';
import { setTimeout } from 'node:timers/promises';
import { ifdescribe } from './lib/spec-helpers';
// FIXME: The tests are skipped on arm/arm64 and ia32.
ifdescribe(!(['arm', 'arm64', 'ia32'].includes(process.arch)))('contentTracing', () => {
// FIXME: The tests are skipped on linux arm/arm64
ifdescribe(!(['arm', 'arm64'].includes(process.arch)) || (process.platform !== 'linux'))('contentTracing', () => {
const record = async (options: TraceConfig | TraceCategoriesAndOptions, outputFilePath: string | undefined, recordTimeInMilliseconds = 1e1) => {
await app.whenReady();
@@ -120,9 +120,7 @@ ifdescribe(!(['arm', 'arm64', 'ia32'].includes(process.arch)))('contentTracing',
describe('captured events', () => {
it('include V8 samples from the main process', async function () {
// This test is flaky on macOS CI.
this.retries(3);
this.timeout(60000);
await contentTracing.startRecording({
categoryFilter: 'disabled-by-default-v8.cpu_profiler',
traceOptions: 'record-until-full'
@@ -131,7 +129,7 @@ ifdescribe(!(['arm', 'arm64', 'ia32'].includes(process.arch)))('contentTracing',
const start = Date.now();
let n = 0;
const f = () => {};
while (Date.now() - start < 200 || n < 500) {
while (Date.now() - start < 200 && n < 500) {
await setTimeout(0);
f();
n++;

View File

@@ -283,6 +283,29 @@ describe('setDisplayMediaRequestHandler', () => {
expect(ok).to.be.true(message);
});
it('returns a MediaStream with BrowserCaptureMediaStreamTrack when the current tab is selected', async () => {
const ses = session.fromPartition('' + Math.random());
let requestHandlerCalled = false;
ses.setDisplayMediaRequestHandler((request, callback) => {
requestHandlerCalled = true;
callback({ video: w.webContents.mainFrame });
});
const w = new BrowserWindow({ show: false, webPreferences: { session: ses } });
await w.loadURL(serverUrl);
const { ok, message } = await w.webContents.executeJavaScript(`
navigator.mediaDevices.getDisplayMedia({
preferCurrentTab: true,
video: true,
audio: false,
}).then(stream => {
const [videoTrack] = stream.getVideoTracks();
return { ok: videoTrack instanceof BrowserCaptureMediaStreamTrack, message: null };
}, e => ({ok: false, message: e.message}))
`, true);
expect(requestHandlerCalled).to.be.true();
expect(ok).to.be.true(message);
});
ifit(!(process.platform === 'darwin' && process.arch === 'x64'))('can supply a screen response to preferCurrentTab', async () => {
const ses = session.fromPartition('' + Math.random());
let requestHandlerCalled = false;

View File

@@ -827,277 +827,383 @@ describe('session module', () => {
fs.unlinkSync(downloadFilePath);
};
it('can download using session.downloadURL', (done) => {
session.defaultSession.once('will-download', function (e, item) {
item.savePath = downloadFilePath;
item.on('done', function (e, state) {
try {
assertDownload(state, item);
done();
} catch (e) {
done(e);
}
});
});
session.defaultSession.downloadURL(`${url}:${port}`);
});
it('can download using session.downloadURL with a valid auth header', async () => {
const server = http.createServer((req, res) => {
const { authorization } = req.headers;
if (!authorization || authorization !== 'Basic i-am-an-auth-header') {
res.statusCode = 401;
res.setHeader('WWW-Authenticate', 'Basic realm="Restricted"');
res.end();
} else {
res.writeHead(200, {
'Content-Length': mockPDF.length,
'Content-Type': 'application/pdf',
'Content-Disposition': req.url === '/?testFilename' ? 'inline' : contentDisposition
});
res.end(mockPDF);
}
});
const { port } = await listen(server);
const downloadDone: Promise<Electron.DownloadItem> = new Promise((resolve) => {
session.defaultSession.once('will-download', (e, item) => {
item.savePath = downloadFilePath;
item.on('done', () => {
try {
resolve(item);
} catch {}
});
});
});
session.defaultSession.downloadURL(`${url}:${port}`, {
headers: {
Authorization: 'Basic i-am-an-auth-header'
}
});
const item = await downloadDone;
expect(item.getState()).to.equal('completed');
expect(item.getFilename()).to.equal('mock.pdf');
expect(item.getMimeType()).to.equal('application/pdf');
expect(item.getReceivedBytes()).to.equal(mockPDF.length);
expect(item.getTotalBytes()).to.equal(mockPDF.length);
expect(item.getContentDisposition()).to.equal(contentDisposition);
});
it('throws when session.downloadURL is called with invalid headers', () => {
expect(() => {
session.defaultSession.downloadURL(`${url}:${port}`, {
// @ts-ignore this line is intentionally incorrect
headers: 'i-am-a-bad-header'
});
}).to.throw(/Invalid value for headers - must be an object/);
});
it('can download using session.downloadURL with an invalid auth header', async () => {
const server = http.createServer((req, res) => {
const { authorization } = req.headers;
if (!authorization || authorization !== 'Basic i-am-an-auth-header') {
res.statusCode = 401;
res.setHeader('WWW-Authenticate', 'Basic realm="Restricted"');
res.end();
} else {
res.writeHead(200, {
'Content-Length': mockPDF.length,
'Content-Type': 'application/pdf',
'Content-Disposition': req.url === '/?testFilename' ? 'inline' : contentDisposition
});
res.end(mockPDF);
}
});
const { port } = await listen(server);
const downloadFailed: Promise<Electron.DownloadItem> = new Promise((resolve) => {
session.defaultSession.once('will-download', (_, item) => {
item.savePath = downloadFilePath;
item.on('done', (e, state) => {
console.log(state);
try {
resolve(item);
} catch {}
});
});
});
session.defaultSession.downloadURL(`${url}:${port}`, {
headers: {
Authorization: 'wtf-is-this'
}
});
const item = await downloadFailed;
expect(item.getState()).to.equal('interrupted');
expect(item.getReceivedBytes()).to.equal(0);
expect(item.getTotalBytes()).to.equal(0);
});
it('can download using WebContents.downloadURL', (done) => {
const w = new BrowserWindow({ show: false });
w.webContents.session.once('will-download', function (e, item) {
item.savePath = downloadFilePath;
item.on('done', function (e, state) {
try {
assertDownload(state, item);
done();
} catch (e) {
done(e);
}
});
});
w.webContents.downloadURL(`${url}:${port}`);
});
it('can download from custom protocols using WebContents.downloadURL', (done) => {
const protocol = session.defaultSession.protocol;
const handler = (ignoredError: any, callback: Function) => {
callback({ url: `${url}:${port}` });
};
protocol.registerHttpProtocol(protocolName, handler);
const w = new BrowserWindow({ show: false });
w.webContents.session.once('will-download', function (e, item) {
item.savePath = downloadFilePath;
item.on('done', function (e, state) {
try {
assertDownload(state, item, true);
done();
} catch (e) {
done(e);
}
});
});
w.webContents.downloadURL(`${protocolName}://item`);
});
it('can download using WebView.downloadURL', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { webviewTag: true } });
await w.loadURL('about:blank');
function webviewDownload ({ fixtures, url, port }: {fixtures: string, url: string, port: string}) {
const webview = new (window as any).WebView();
webview.addEventListener('did-finish-load', () => {
webview.downloadURL(`${url}:${port}/`);
});
webview.src = `file://${fixtures}/api/blank.html`;
document.body.appendChild(webview);
}
const done: Promise<[string, Electron.DownloadItem]> = new Promise(resolve => {
w.webContents.session.once('will-download', function (e, item) {
describe('session.downloadURL', () => {
it('can perform a download', (done) => {
session.defaultSession.once('will-download', function (e, item) {
item.savePath = downloadFilePath;
item.on('done', function (e, state) {
resolve([state, item]);
});
});
});
await w.webContents.executeJavaScript(`(${webviewDownload})(${JSON.stringify({ fixtures, url, port })})`);
const [state, item] = await done;
assertDownload(state, item);
});
it('can cancel download', (done) => {
const w = new BrowserWindow({ show: false });
w.webContents.session.once('will-download', function (e, item) {
item.savePath = downloadFilePath;
item.on('done', function (e, state) {
try {
expect(state).to.equal('cancelled');
expect(item.getFilename()).to.equal('mock.pdf');
expect(item.getMimeType()).to.equal('application/pdf');
expect(item.getReceivedBytes()).to.equal(0);
expect(item.getTotalBytes()).to.equal(mockPDF.length);
expect(item.getContentDisposition()).to.equal(contentDisposition);
done();
} catch (e) {
done(e);
}
});
item.cancel();
});
w.webContents.downloadURL(`${url}:${port}/`);
});
it('can generate a default filename', function (done) {
if (process.env.APPVEYOR === 'True') {
// FIXME(alexeykuzmin): Skip the test.
// this.skip()
return done();
}
const w = new BrowserWindow({ show: false });
w.webContents.session.once('will-download', function (e, item) {
item.savePath = downloadFilePath;
item.on('done', function () {
try {
expect(item.getFilename()).to.equal('download.pdf');
done();
} catch (e) {
done(e);
}
});
item.cancel();
});
w.webContents.downloadURL(`${url}:${port}/?testFilename`);
});
it('can set options for the save dialog', (done) => {
const filePath = path.join(__dirname, 'fixtures', 'mock.pdf');
const options = {
window: null,
title: 'title',
message: 'message',
buttonLabel: 'buttonLabel',
nameFieldLabel: 'nameFieldLabel',
defaultPath: '/',
filters: [{
name: '1', extensions: ['.1', '.2']
}, {
name: '2', extensions: ['.3', '.4', '.5']
}],
showsTagField: true,
securityScopedBookmarks: true
};
const w = new BrowserWindow({ show: false });
w.webContents.session.once('will-download', function (e, item) {
item.setSavePath(filePath);
item.setSaveDialogOptions(options);
item.on('done', function () {
try {
expect(item.getSaveDialogOptions()).to.deep.equal(options);
done();
} catch (e) {
done(e);
}
});
item.cancel();
});
w.webContents.downloadURL(`${url}:${port}`);
});
describe('when a save path is specified and the URL is unavailable', () => {
it('does not display a save dialog and reports the done state as interrupted', (done) => {
const w = new BrowserWindow({ show: false });
w.webContents.session.once('will-download', function (e, item) {
item.savePath = downloadFilePath;
if (item.getState() === 'interrupted') {
item.resume();
}
item.on('done', function (e, state) {
try {
expect(state).to.equal('interrupted');
assertDownload(state, item);
done();
} catch (e) {
done(e);
}
});
});
w.webContents.downloadURL(`file://${path.join(__dirname, 'does-not-exist.txt')}`);
session.defaultSession.downloadURL(`${url}:${port}`);
});
it('can perform a download with a valid auth header', async () => {
const server = http.createServer((req, res) => {
const { authorization } = req.headers;
if (!authorization || authorization !== 'Basic i-am-an-auth-header') {
res.statusCode = 401;
res.setHeader('WWW-Authenticate', 'Basic realm="Restricted"');
res.end();
} else {
res.writeHead(200, {
'Content-Length': mockPDF.length,
'Content-Type': 'application/pdf',
'Content-Disposition': req.url === '/?testFilename' ? 'inline' : contentDisposition
});
res.end(mockPDF);
}
});
const { port } = await listen(server);
const downloadDone: Promise<Electron.DownloadItem> = new Promise((resolve) => {
session.defaultSession.once('will-download', (e, item) => {
item.savePath = downloadFilePath;
item.on('done', () => {
try {
resolve(item);
} catch { }
});
});
});
session.defaultSession.downloadURL(`${url}:${port}`, {
headers: {
Authorization: 'Basic i-am-an-auth-header'
}
});
const item = await downloadDone;
expect(item.getState()).to.equal('completed');
expect(item.getFilename()).to.equal('mock.pdf');
expect(item.getMimeType()).to.equal('application/pdf');
expect(item.getReceivedBytes()).to.equal(mockPDF.length);
expect(item.getTotalBytes()).to.equal(mockPDF.length);
expect(item.getContentDisposition()).to.equal(contentDisposition);
});
it('throws when called with invalid headers', () => {
expect(() => {
session.defaultSession.downloadURL(`${url}:${port}`, {
// @ts-ignore this line is intentionally incorrect
headers: 'i-am-a-bad-header'
});
}).to.throw(/Invalid value for headers - must be an object/);
});
it('correctly handles a download with an invalid auth header', async () => {
const server = http.createServer((req, res) => {
const { authorization } = req.headers;
if (!authorization || authorization !== 'Basic i-am-an-auth-header') {
res.statusCode = 401;
res.setHeader('WWW-Authenticate', 'Basic realm="Restricted"');
res.end();
} else {
res.writeHead(200, {
'Content-Length': mockPDF.length,
'Content-Type': 'application/pdf',
'Content-Disposition': req.url === '/?testFilename' ? 'inline' : contentDisposition
});
res.end(mockPDF);
}
});
const { port } = await listen(server);
const downloadFailed: Promise<Electron.DownloadItem> = new Promise((resolve) => {
session.defaultSession.once('will-download', (_, item) => {
item.savePath = downloadFilePath;
item.on('done', (e, state) => {
console.log(state);
try {
resolve(item);
} catch { }
});
});
});
session.defaultSession.downloadURL(`${url}:${port}`, {
headers: {
Authorization: 'wtf-is-this'
}
});
const item = await downloadFailed;
expect(item.getState()).to.equal('interrupted');
expect(item.getReceivedBytes()).to.equal(0);
expect(item.getTotalBytes()).to.equal(0);
});
});
describe('webContents.downloadURL', () => {
it('can perform a download', (done) => {
const w = new BrowserWindow({ show: false });
w.webContents.session.once('will-download', function (e, item) {
item.savePath = downloadFilePath;
item.on('done', function (e, state) {
try {
assertDownload(state, item);
done();
} catch (e) {
done(e);
}
});
});
w.webContents.downloadURL(`${url}:${port}`);
});
it('can perform a download with a valid auth header', async () => {
const server = http.createServer((req, res) => {
const { authorization } = req.headers;
if (!authorization || authorization !== 'Basic i-am-an-auth-header') {
res.statusCode = 401;
res.setHeader('WWW-Authenticate', 'Basic realm="Restricted"');
res.end();
} else {
res.writeHead(200, {
'Content-Length': mockPDF.length,
'Content-Type': 'application/pdf',
'Content-Disposition': req.url === '/?testFilename' ? 'inline' : contentDisposition
});
res.end(mockPDF);
}
});
const { port } = await listen(server);
const w = new BrowserWindow({ show: false });
const downloadDone: Promise<Electron.DownloadItem> = new Promise((resolve) => {
w.webContents.session.once('will-download', (e, item) => {
item.savePath = downloadFilePath;
item.on('done', () => {
try {
resolve(item);
} catch { }
});
});
});
w.webContents.downloadURL(`${url}:${port}`, {
headers: {
Authorization: 'Basic i-am-an-auth-header'
}
});
const item = await downloadDone;
expect(item.getState()).to.equal('completed');
expect(item.getFilename()).to.equal('mock.pdf');
expect(item.getMimeType()).to.equal('application/pdf');
expect(item.getReceivedBytes()).to.equal(mockPDF.length);
expect(item.getTotalBytes()).to.equal(mockPDF.length);
expect(item.getContentDisposition()).to.equal(contentDisposition);
});
it('throws when called with invalid headers', () => {
const w = new BrowserWindow({ show: false });
expect(() => {
w.webContents.downloadURL(`${url}:${port}`, {
// @ts-ignore this line is intentionally incorrect
headers: 'i-am-a-bad-header'
});
}).to.throw(/Invalid value for headers - must be an object/);
});
it('correctly handles a download and an invalid auth header', async () => {
const server = http.createServer((req, res) => {
const { authorization } = req.headers;
if (!authorization || authorization !== 'Basic i-am-an-auth-header') {
res.statusCode = 401;
res.setHeader('WWW-Authenticate', 'Basic realm="Restricted"');
res.end();
} else {
res.writeHead(200, {
'Content-Length': mockPDF.length,
'Content-Type': 'application/pdf',
'Content-Disposition': req.url === '/?testFilename' ? 'inline' : contentDisposition
});
res.end(mockPDF);
}
});
const { port } = await listen(server);
const w = new BrowserWindow({ show: false });
const downloadFailed: Promise<Electron.DownloadItem> = new Promise((resolve) => {
w.webContents.session.once('will-download', (_, item) => {
item.savePath = downloadFilePath;
item.on('done', (e, state) => {
console.log(state);
try {
resolve(item);
} catch { }
});
});
});
w.webContents.downloadURL(`${url}:${port}`, {
headers: {
Authorization: 'wtf-is-this'
}
});
const item = await downloadFailed;
expect(item.getState()).to.equal('interrupted');
expect(item.getReceivedBytes()).to.equal(0);
expect(item.getTotalBytes()).to.equal(0);
});
it('can download from custom protocols', (done) => {
const protocol = session.defaultSession.protocol;
const handler = (ignoredError: any, callback: Function) => {
callback({ url: `${url}:${port}` });
};
protocol.registerHttpProtocol(protocolName, handler);
const w = new BrowserWindow({ show: false });
w.webContents.session.once('will-download', function (e, item) {
item.savePath = downloadFilePath;
item.on('done', function (e, state) {
try {
assertDownload(state, item, true);
done();
} catch (e) {
done(e);
}
});
});
w.webContents.downloadURL(`${protocolName}://item`);
});
it('can cancel download', (done) => {
const w = new BrowserWindow({ show: false });
w.webContents.session.once('will-download', function (e, item) {
item.savePath = downloadFilePath;
item.on('done', function (e, state) {
try {
expect(state).to.equal('cancelled');
expect(item.getFilename()).to.equal('mock.pdf');
expect(item.getMimeType()).to.equal('application/pdf');
expect(item.getReceivedBytes()).to.equal(0);
expect(item.getTotalBytes()).to.equal(mockPDF.length);
expect(item.getContentDisposition()).to.equal(contentDisposition);
done();
} catch (e) {
done(e);
}
});
item.cancel();
});
w.webContents.downloadURL(`${url}:${port}/`);
});
it('can generate a default filename', function (done) {
if (process.env.APPVEYOR === 'True') {
// FIXME(alexeykuzmin): Skip the test.
// this.skip()
return done();
}
const w = new BrowserWindow({ show: false });
w.webContents.session.once('will-download', function (e, item) {
item.savePath = downloadFilePath;
item.on('done', function () {
try {
expect(item.getFilename()).to.equal('download.pdf');
done();
} catch (e) {
done(e);
}
});
item.cancel();
});
w.webContents.downloadURL(`${url}:${port}/?testFilename`);
});
it('can set options for the save dialog', (done) => {
const filePath = path.join(__dirname, 'fixtures', 'mock.pdf');
const options = {
window: null,
title: 'title',
message: 'message',
buttonLabel: 'buttonLabel',
nameFieldLabel: 'nameFieldLabel',
defaultPath: '/',
filters: [{
name: '1', extensions: ['.1', '.2']
}, {
name: '2', extensions: ['.3', '.4', '.5']
}],
showsTagField: true,
securityScopedBookmarks: true
};
const w = new BrowserWindow({ show: false });
w.webContents.session.once('will-download', function (e, item) {
item.setSavePath(filePath);
item.setSaveDialogOptions(options);
item.on('done', function () {
try {
expect(item.getSaveDialogOptions()).to.deep.equal(options);
done();
} catch (e) {
done(e);
}
});
item.cancel();
});
w.webContents.downloadURL(`${url}:${port}`);
});
describe('when a save path is specified and the URL is unavailable', () => {
it('does not display a save dialog and reports the done state as interrupted', (done) => {
const w = new BrowserWindow({ show: false });
w.webContents.session.once('will-download', function (e, item) {
item.savePath = downloadFilePath;
if (item.getState() === 'interrupted') {
item.resume();
}
item.on('done', function (e, state) {
try {
expect(state).to.equal('interrupted');
done();
} catch (e) {
done(e);
}
});
});
w.webContents.downloadURL(`file://${path.join(__dirname, 'does-not-exist.txt')}`);
});
});
});
describe('WebView.downloadURL', () => {
it('can perform a download', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { webviewTag: true } });
await w.loadURL('about:blank');
function webviewDownload ({ fixtures, url, port }: { fixtures: string, url: string, port: string }) {
const webview = new (window as any).WebView();
webview.addEventListener('did-finish-load', () => {
webview.downloadURL(`${url}:${port}/`);
});
webview.src = `file://${fixtures}/api/blank.html`;
document.body.appendChild(webview);
}
const done: Promise<[string, Electron.DownloadItem]> = new Promise(resolve => {
w.webContents.session.once('will-download', function (e, item) {
item.savePath = downloadFilePath;
item.on('done', function (e, state) {
resolve([state, item]);
});
});
});
await w.webContents.executeJavaScript(`(${webviewDownload})(${JSON.stringify({ fixtures, url, port })})`);
const [state, item] = await done;
assertDownload(state, item);
});
});
});

View File

@@ -375,6 +375,16 @@ describe('webContents module', () => {
await expect(w.loadURL(w.getURL() + '#foo')).to.eventually.be.fulfilled();
});
it('resolves after browser initiated navigation', async () => {
let finishedLoading = false;
w.webContents.on('did-finish-load', function () {
finishedLoading = true;
});
await w.loadFile(path.join(fixturesPath, 'pages', 'navigate_in_page_and_wait.html'));
expect(finishedLoading).to.be.true();
});
it('rejects when failing to load a file URL', async () => {
await expect(w.loadURL('file:non-existent')).to.eventually.be.rejected()
.and.have.property('code', 'ERR_FILE_NOT_FOUND');
@@ -1905,16 +1915,6 @@ describe('webContents module', () => {
});
});
ifdescribe(features.isPrintingEnabled())('getPrinters()', () => {
afterEach(closeAllWindows);
it('can get printer list', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { sandbox: true } });
await w.loadURL('about:blank');
const printers = w.webContents.getPrinters();
expect(printers).to.be.an('array');
});
});
ifdescribe(features.isPrintingEnabled())('getPrintersAsync()', () => {
afterEach(closeAllWindows);
it('can get printer list', async () => {

View File

@@ -2050,10 +2050,32 @@ describe('chromium features', () => {
});
});
describe('chrome://accessibility', () => {
it('loads the page successfully', async () => {
const w = new BrowserWindow({ show: false });
await w.loadURL('chrome://accessibility');
const pageExists = await w.webContents.executeJavaScript(
"window.hasOwnProperty('chrome') && window.chrome.hasOwnProperty('send')"
);
expect(pageExists).to.be.true();
});
});
describe('chrome://gpu', () => {
it('loads the page successfully', async () => {
const w = new BrowserWindow({ show: false });
await w.loadURL('chrome://gpu');
const pageExists = await w.webContents.executeJavaScript(
"window.hasOwnProperty('chrome') && window.chrome.hasOwnProperty('send')"
);
expect(pageExists).to.be.true();
});
});
describe('chrome://media-internals', () => {
it('loads the page successfully', async () => {
const w = new BrowserWindow({ show: false });
w.loadURL('chrome://media-internals');
await w.loadURL('chrome://media-internals');
const pageExists = await w.webContents.executeJavaScript(
"window.hasOwnProperty('chrome') && window.chrome.hasOwnProperty('send')"
);
@@ -2064,7 +2086,7 @@ describe('chromium features', () => {
describe('chrome://webrtc-internals', () => {
it('loads the page successfully', async () => {
const w = new BrowserWindow({ show: false });
w.loadURL('chrome://webrtc-internals');
await w.loadURL('chrome://webrtc-internals');
const pageExists = await w.webContents.executeJavaScript(
"window.hasOwnProperty('chrome') && window.chrome.hasOwnProperty('send')"
);

View File

@@ -69,6 +69,67 @@ describe('chrome extensions', () => {
`)).to.eventually.have.property('id');
});
describe('host_permissions', async () => {
let customSession: Session;
let w: BrowserWindow;
beforeEach(() => {
customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
w = new BrowserWindow({
show: false,
webPreferences: {
session: customSession,
sandbox: true
}
});
});
afterEach(closeAllWindows);
it('recognize malformed host permissions', async () => {
await w.loadURL(url);
const extPath = path.join(fixtures, 'extensions', 'host-permissions', 'malformed');
customSession.loadExtension(extPath);
const warning = await new Promise(resolve => { process.on('warning', resolve); });
const malformedHost = /Permission 'malformed_host' is unknown or URL pattern is malformed/;
expect(warning).to.match(malformedHost);
});
it('can grant special privileges to urls with host permissions', async () => {
const extPath = path.join(fixtures, 'extensions', 'host-permissions', 'privileged-tab-info');
await customSession.loadExtension(extPath);
await w.loadURL(url);
const message = { method: 'query' };
w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
const [,, responseString] = await once(w.webContents, 'console-message');
const response = JSON.parse(responseString);
expect(response).to.have.lengthOf(1);
const tab = response[0];
expect(tab).to.have.property('url').that.is.a('string');
expect(tab).to.have.property('title').that.is.a('string');
expect(tab).to.have.property('active').that.is.a('boolean');
expect(tab).to.have.property('autoDiscardable').that.is.a('boolean');
expect(tab).to.have.property('discarded').that.is.a('boolean');
expect(tab).to.have.property('groupId').that.is.a('number');
expect(tab).to.have.property('highlighted').that.is.a('boolean');
expect(tab).to.have.property('id').that.is.a('number');
expect(tab).to.have.property('incognito').that.is.a('boolean');
expect(tab).to.have.property('index').that.is.a('number');
expect(tab).to.have.property('pinned').that.is.a('boolean');
expect(tab).to.have.property('selected').that.is.a('boolean');
expect(tab).to.have.property('windowId').that.is.a('number');
});
});
it('supports minimum_chrome_version manifest key', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
const w = new BrowserWindow({
@@ -81,7 +142,7 @@ describe('chrome extensions', () => {
await w.loadURL('about:blank');
const extPath = path.join(fixtures, 'extensions', 'chrome-too-low-version');
const extPath = path.join(fixtures, 'extensions', 'minimum-chrome-version');
const load = customSession.loadExtension(extPath);
await expect(load).to.eventually.be.rejectedWith(
`Loading extension at ${extPath} failed with: This extension requires Chromium version 999 or greater.`
@@ -842,15 +903,14 @@ describe('chrome extensions', () => {
before(async () => {
customSession = session.fromPartition(`persist:${uuid.v4()}`);
await customSession.loadExtension(path.join(fixtures, 'extensions', 'tabs-api-async'));
await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-tabs', 'api-async'));
});
beforeEach(() => {
w = new BrowserWindow({
show: false,
webPreferences: {
session: customSession,
nodeIntegration: true
session: customSession
}
});
});
@@ -913,27 +973,55 @@ describe('chrome extensions', () => {
});
});
it('get', async () => {
await w.loadURL(url);
describe('get', () => {
it('returns tab properties', async () => {
await w.loadURL(url);
const message = { method: 'get' };
w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
const message = { method: 'get' };
w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
const [,, responseString] = await once(w.webContents, 'console-message');
const [,, responseString] = await once(w.webContents, 'console-message');
const response = JSON.parse(responseString);
expect(response).to.have.property('active').that.is.a('boolean');
expect(response).to.have.property('autoDiscardable').that.is.a('boolean');
expect(response).to.have.property('discarded').that.is.a('boolean');
expect(response).to.have.property('groupId').that.is.a('number');
expect(response).to.have.property('highlighted').that.is.a('boolean');
expect(response).to.have.property('id').that.is.a('number');
expect(response).to.have.property('incognito').that.is.a('boolean');
expect(response).to.have.property('index').that.is.a('number');
expect(response).to.have.property('pinned').that.is.a('boolean');
expect(response).to.have.property('selected').that.is.a('boolean');
expect(response).to.have.property('url').that.is.a('string');
expect(response).to.have.property('windowId').that.is.a('number');
const response = JSON.parse(responseString);
expect(response).to.have.property('url').that.is.a('string');
expect(response).to.have.property('title').that.is.a('string');
expect(response).to.have.property('active').that.is.a('boolean');
expect(response).to.have.property('autoDiscardable').that.is.a('boolean');
expect(response).to.have.property('discarded').that.is.a('boolean');
expect(response).to.have.property('groupId').that.is.a('number');
expect(response).to.have.property('highlighted').that.is.a('boolean');
expect(response).to.have.property('id').that.is.a('number');
expect(response).to.have.property('incognito').that.is.a('boolean');
expect(response).to.have.property('index').that.is.a('number');
expect(response).to.have.property('pinned').that.is.a('boolean');
expect(response).to.have.property('selected').that.is.a('boolean');
expect(response).to.have.property('windowId').that.is.a('number');
});
it('does not return privileged properties without tabs permission', async () => {
const noPrivilegeSes = session.fromPartition(`persist:${uuid.v4()}`);
await noPrivilegeSes.loadExtension(path.join(fixtures, 'extensions', 'chrome-tabs', 'no-privileges'));
w = new BrowserWindow({ show: false, webPreferences: { session: noPrivilegeSes } });
await w.loadURL(url);
w.webContents.executeJavaScript('window.postMessage(\'{}\', \'*\')');
const [,, responseString] = await once(w.webContents, 'console-message');
const response = JSON.parse(responseString);
expect(response).not.to.have.property('url');
expect(response).not.to.have.property('title');
expect(response).to.have.property('active').that.is.a('boolean');
expect(response).to.have.property('autoDiscardable').that.is.a('boolean');
expect(response).to.have.property('discarded').that.is.a('boolean');
expect(response).to.have.property('groupId').that.is.a('number');
expect(response).to.have.property('highlighted').that.is.a('boolean');
expect(response).to.have.property('id').that.is.a('number');
expect(response).to.have.property('incognito').that.is.a('boolean');
expect(response).to.have.property('index').that.is.a('number');
expect(response).to.have.property('pinned').that.is.a('boolean');
expect(response).to.have.property('selected').that.is.a('boolean');
expect(response).to.have.property('windowId').that.is.a('number');
});
});
it('reload', async () => {
@@ -960,6 +1048,19 @@ describe('chrome extensions', () => {
const [,, responseString] = await once(w.webContents, 'console-message');
const response = JSON.parse(responseString);
expect(response).to.have.property('url').that.is.a('string');
expect(response).to.have.property('title').that.is.a('string');
expect(response).to.have.property('active').that.is.a('boolean');
expect(response).to.have.property('autoDiscardable').that.is.a('boolean');
expect(response).to.have.property('discarded').that.is.a('boolean');
expect(response).to.have.property('groupId').that.is.a('number');
expect(response).to.have.property('highlighted').that.is.a('boolean');
expect(response).to.have.property('id').that.is.a('number');
expect(response).to.have.property('incognito').that.is.a('boolean');
expect(response).to.have.property('index').that.is.a('number');
expect(response).to.have.property('pinned').that.is.a('boolean');
expect(response).to.have.property('selected').that.is.a('boolean');
expect(response).to.have.property('windowId').that.is.a('number');
expect(response).to.have.property('mutedInfo').that.is.a('object');
const { mutedInfo } = response;
expect(mutedInfo).to.deep.eq({
@@ -1028,5 +1129,75 @@ describe('chrome extensions', () => {
});
});
});
describe('chrome.scripting', () => {
let customSession: Session;
let w = null as unknown as BrowserWindow;
before(async () => {
customSession = session.fromPartition(`persist:${uuid.v4()}`);
await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-scripting'));
});
beforeEach(() => {
w = new BrowserWindow({
show: false,
webPreferences: {
session: customSession,
nodeIntegration: true
}
});
});
afterEach(closeAllWindows);
it('executeScript', async () => {
await w.loadURL(url);
const message = { method: 'executeScript' };
w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
const updated = await once(w.webContents, 'page-title-updated');
expect(updated[1]).to.equal('HEY HEY HEY');
});
it('registerContentScripts', async () => {
await w.loadURL(url);
const message = { method: 'registerContentScripts' };
w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
const [,, responseString] = await once(w.webContents, 'console-message');
const response = JSON.parse(responseString);
expect(response).to.be.an('array').with.lengthOf(1);
expect(response[0]).to.deep.equal({
allFrames: false,
id: 'session-script',
js: ['content.js'],
matchOriginAsFallback: false,
matches: ['<all_urls>'],
persistAcrossSessions: false,
runAt: 'document_start',
world: 'ISOLATED'
});
});
it('insertCSS', async () => {
await w.loadURL(url);
const bgBefore = await w.webContents.executeJavaScript('window.getComputedStyle(document.body).backgroundColor');
expect(bgBefore).to.equal('rgba(0, 0, 0, 0)');
const message = { method: 'insertCSS' };
w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
const [,, responseString] = await once(w.webContents, 'console-message');
const response = JSON.parse(responseString);
expect(response.success).to.be.true();
const bgAfter = await w.webContents.executeJavaScript('window.getComputedStyle(document.body).backgroundColor');
expect(bgAfter).to.equal('rgb(255, 0, 0)');
});
});
});
});

View File

@@ -0,0 +1,51 @@
/* global chrome */
const handleRequest = async (request, sender, sendResponse) => {
const { method } = request;
const tabId = sender.tab.id;
switch (method) {
case 'executeScript': {
chrome.scripting.executeScript({
target: { tabId },
function: () => {
document.title = 'HEY HEY HEY';
return document.title;
}
}).then(() => {
console.log('success');
}).catch((err) => {
console.log('error', err);
});
break;
}
case 'registerContentScripts': {
await chrome.scripting.registerContentScripts([{
id: 'session-script',
js: ['content.js'],
persistAcrossSessions: false,
matches: ['<all_urls>'],
runAt: 'document_start'
}]);
chrome.scripting.getRegisteredContentScripts().then(sendResponse);
break;
}
case 'insertCSS': {
chrome.scripting.insertCSS({
target: { tabId },
css: 'body { background-color: red; }'
}).then(() => {
sendResponse({ success: true });
});
break;
}
}
};
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
handleRequest(request, sender, sendResponse);
return true;
});

View File

View File

@@ -0,0 +1,30 @@
/* global chrome */
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
sendResponse(request);
});
const map = {
executeScript () {
chrome.runtime.sendMessage({ method: 'executeScript' }, response => {
console.log(JSON.stringify(response));
});
},
registerContentScripts () {
chrome.runtime.sendMessage({ method: 'registerContentScripts' }, response => {
console.log(JSON.stringify(response));
});
},
insertCSS () {
chrome.runtime.sendMessage({ method: 'insertCSS' }, response => {
console.log(JSON.stringify(response));
});
}
};
const dispatchTest = (event) => {
const { method, args = [] } = JSON.parse(event.data);
map[method](...args);
};
window.addEventListener('message', dispatchTest, false);

View File

@@ -0,0 +1,17 @@
{
"name": "execute-script",
"version": "1.0",
"permissions": [
"scripting"
],
"host_permissions": ["<all_urls>"],
"content_scripts": [{
"matches": [ "<all_urls>"],
"js": ["main.js"],
"run_at": "document_start"
}],
"background": {
"service_worker": "background.js"
},
"manifest_version": 3
}

View File

@@ -1,5 +1,5 @@
{
"name": "tabs-api-async",
"name": "api-async",
"version": "1.0",
"content_scripts": [
{
@@ -8,6 +8,7 @@
"run_at": "document_start"
}
],
"permissions": ["tabs"],
"background": {
"service_worker": "background.js"
},

View File

@@ -0,0 +1,6 @@
/* global chrome */
chrome.runtime.onMessage.addListener((_request, sender, sendResponse) => {
chrome.tabs.get(sender.tab.id).then(sendResponse);
return true;
});

View File

@@ -0,0 +1,11 @@
/* global chrome */
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
sendResponse(request);
});
window.addEventListener('message', () => {
chrome.runtime.sendMessage({}, response => {
console.log(JSON.stringify(response));
});
}, false);

Some files were not shown because too many files have changed in this diff Show More