mirror of
https://github.com/electron/electron.git
synced 2026-02-26 03:01:17 -05:00
Compare commits
17 Commits
roller/nod
...
v27.0.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5707be53af | ||
|
|
e1465f6723 | ||
|
|
c406384e8c | ||
|
|
c5ce6de82b | ||
|
|
d89b7f0a4e | ||
|
|
dfbd4c4335 | ||
|
|
d79189056d | ||
|
|
6fd069231f | ||
|
|
56e749782e | ||
|
|
864dd4af40 | ||
|
|
3d31570f8d | ||
|
|
3411959886 | ||
|
|
b0b1f2c727 | ||
|
|
4128e9f0e0 | ||
|
|
0bd5e36411 | ||
|
|
c97a4ce691 | ||
|
|
fbb982350b |
@@ -118,7 +118,7 @@ for:
|
|||||||
- ps: .\src\electron\script\start-goma.ps1 -gomaDir $env:LOCAL_GOMA_DIR
|
- ps: .\src\electron\script\start-goma.ps1 -gomaDir $env:LOCAL_GOMA_DIR
|
||||||
- ps: >-
|
- ps: >-
|
||||||
if (Test-Path 'env:RAW_GOMA_AUTH') {
|
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') {
|
if ($goma_login -eq 'Login as Fermi Planck') {
|
||||||
Write-warning "Goma authentication is correct";
|
Write-warning "Goma authentication is correct";
|
||||||
} else {
|
} else {
|
||||||
@@ -168,7 +168,7 @@ for:
|
|||||||
- ninja -C out/Default electron:hunspell_dictionaries_zip
|
- ninja -C out/Default electron:hunspell_dictionaries_zip
|
||||||
- ninja -C out/Default electron:electron_chromedriver_zip
|
- ninja -C out/Default electron:electron_chromedriver_zip
|
||||||
- ninja -C out/Default third_party/electron_node:headers
|
- 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: >-
|
- 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
|
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
|
- python3 electron/build/profile_toolchain.py --output-json=out/Default/windows_toolchain_profile.json
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ for:
|
|||||||
- ps: .\src\electron\script\start-goma.ps1 -gomaDir $env:LOCAL_GOMA_DIR
|
- ps: .\src\electron\script\start-goma.ps1 -gomaDir $env:LOCAL_GOMA_DIR
|
||||||
- ps: >-
|
- ps: >-
|
||||||
if (Test-Path 'env:RAW_GOMA_AUTH') {
|
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') {
|
if ($goma_login -eq 'Login as Fermi Planck') {
|
||||||
Write-warning "Goma authentication is correct";
|
Write-warning "Goma authentication is correct";
|
||||||
} else {
|
} else {
|
||||||
@@ -166,7 +166,7 @@ for:
|
|||||||
- ninja -C out/Default electron:hunspell_dictionaries_zip
|
- ninja -C out/Default electron:hunspell_dictionaries_zip
|
||||||
- ninja -C out/Default electron:electron_chromedriver_zip
|
- ninja -C out/Default electron:electron_chromedriver_zip
|
||||||
- ninja -C out/Default third_party/electron_node:headers
|
- 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: >-
|
- 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
|
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
|
- python3 electron/build/profile_toolchain.py --output-json=out/Default/windows_toolchain_profile.json
|
||||||
|
|||||||
@@ -412,18 +412,7 @@ Returns:
|
|||||||
|
|
||||||
* `event` Event
|
* `event` Event
|
||||||
* `webContents` [WebContents](web-contents.md)
|
* `webContents` [WebContents](web-contents.md)
|
||||||
* `details` Object
|
* `details` [RenderProcessGoneDetails](structures/render-process-gone-details.md)
|
||||||
* `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.
|
|
||||||
|
|
||||||
Emitted when the renderer process unexpectedly disappears. This is normally
|
Emitted when the renderer process unexpectedly disappears. This is normally
|
||||||
because it was crashed or killed.
|
because it was crashed or killed.
|
||||||
|
|||||||
@@ -820,10 +820,14 @@ win.setBounds({ width: 100 })
|
|||||||
console.log(win.getBounds())
|
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()`
|
#### `win.getBounds()`
|
||||||
|
|
||||||
Returns [`Rectangle`](structures/rectangle.md) - The `bounds` of the window as `Object`.
|
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()`
|
#### `win.getBackgroundColor()`
|
||||||
|
|
||||||
Returns `string` - Gets the background color of the window in Hex (`#RRGGBB`) format.
|
Returns `string` - Gets the background color of the window in Hex (`#RRGGBB`) format.
|
||||||
|
|||||||
@@ -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.
|
with the [will-download](#event-will-download) event.
|
||||||
|
|
||||||
**Note:** This does not perform any security checks that relate to a page's origin,
|
**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)`
|
#### `ses.createInterruptedDownload(options)`
|
||||||
|
|
||||||
|
|||||||
13
docs/api/structures/render-process-gone-details.md
Normal file
13
docs/api/structures/render-process-gone-details.md
Normal 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.
|
||||||
@@ -479,18 +479,7 @@ checking `reason === 'killed'` when you switch to that event.
|
|||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
* `event` Event
|
* `event` Event
|
||||||
* `details` Object
|
* `details` [RenderProcessGoneDetails](structures/render-process-gone-details.md)
|
||||||
* `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.
|
|
||||||
|
|
||||||
Emitted when the renderer process unexpectedly disappears. This is normally
|
Emitted when the renderer process unexpectedly disappears. This is normally
|
||||||
because it was crashed or killed.
|
because it was crashed or killed.
|
||||||
@@ -1057,9 +1046,11 @@ const win = new BrowserWindow()
|
|||||||
win.loadFile('src/index.html')
|
win.loadFile('src/index.html')
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `contents.downloadURL(url)`
|
#### `contents.downloadURL(url[, options])`
|
||||||
|
|
||||||
* `url` string
|
* `url` string
|
||||||
|
* `options` Object (optional)
|
||||||
|
* `headers` Record<string, string> (optional) - HTTP request headers.
|
||||||
|
|
||||||
Initiates a download of the resource at `url` without navigating. The
|
Initiates a download of the resource at `url` without navigating. The
|
||||||
`will-download` event of `session` will be triggered.
|
`will-download` event of `session` will be triggered.
|
||||||
|
|||||||
@@ -280,9 +280,11 @@ if the page fails to load (see
|
|||||||
Loads the `url` in the webview, the `url` must contain the protocol prefix,
|
Loads the `url` in the webview, the `url` must contain the protocol prefix,
|
||||||
e.g. the `http://` or `file://`.
|
e.g. the `http://` or `file://`.
|
||||||
|
|
||||||
### `<webview>.downloadURL(url)`
|
### `<webview>.downloadURL(url[, options])`
|
||||||
|
|
||||||
* `url` string
|
* `url` string
|
||||||
|
* `options` Object (optional)
|
||||||
|
* `headers` Record<string, string> (optional) - HTTP request headers.
|
||||||
|
|
||||||
Initiates a download of the resource at `url` without navigating.
|
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'
|
### Event: 'plugin-crashed'
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,48 @@ Electron Forge is a tool for packaging and publishing Electron applications.
|
|||||||
It unifies Electron's build tooling ecosystem into
|
It unifies Electron's build tooling ecosystem into
|
||||||
a single extensible interface so that anyone can jump right into making Electron apps.
|
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
|
## Getting started
|
||||||
|
|
||||||
The [Electron Forge docs][] contain detailed information on taking your application
|
The [Electron Forge docs][] contain detailed information on taking your application
|
||||||
from source code to your end users' machines.
|
from source code to your end users' machines.
|
||||||
This includes:
|
This includes:
|
||||||
|
|
||||||
* Packaging your application [(package)][]
|
- Packaging your application [(package)][]
|
||||||
* Generating executables and installers for each OS [(make)][], and,
|
- Generating executables and installers for each OS [(make)][], and,
|
||||||
* Publishing these files to online platforms to download [(publish)][].
|
- Publishing these files to online platforms to download [(publish)][].
|
||||||
|
|
||||||
For beginners, we recommend following through Electron's [tutorial][] to develop, build,
|
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
|
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
|
## Getting help
|
||||||
|
|
||||||
* If you need help with developing your app, our [community Discord server][discord] is a great place
|
- 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.
|
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][]
|
- 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
|
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.
|
template and submit a new issue.
|
||||||
|
|
||||||
[Electron Forge Docs]: https://www.electronforge.io/
|
[Electron Forge Docs]: https://www.electronforge.io/
|
||||||
[step 5]: ./tutorial-5-packaging.md
|
[step 5]: ./tutorial-5-packaging.md
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ template("electron_extra_paks") {
|
|||||||
"$root_gen_dir/content/browser/tracing/tracing_resources.pak",
|
"$root_gen_dir/content/browser/tracing/tracing_resources.pak",
|
||||||
"$root_gen_dir/content/browser/webrtc/resources/webrtc_internals_resources.pak",
|
"$root_gen_dir/content/browser/webrtc/resources/webrtc_internals_resources.pak",
|
||||||
"$root_gen_dir/content/content_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/mojo/public/js/mojo_bindings_resources.pak",
|
||||||
"$root_gen_dir/net/net_resources.pak",
|
"$root_gen_dir/net/net_resources.pak",
|
||||||
"$root_gen_dir/third_party/blink/public/resources/blink_resources.pak",
|
"$root_gen_dir/third_party/blink/public/resources/blink_resources.pak",
|
||||||
@@ -74,6 +75,7 @@ template("electron_extra_paks") {
|
|||||||
"//chrome/common:resources",
|
"//chrome/common:resources",
|
||||||
"//components/resources",
|
"//components/resources",
|
||||||
"//content:content_resources",
|
"//content:content_resources",
|
||||||
|
"//content/browser/resources/gpu:resources",
|
||||||
"//content/browser/resources/media:resources",
|
"//content/browser/resources/media:resources",
|
||||||
"//content/browser/tracing:resources",
|
"//content/browser/tracing:resources",
|
||||||
"//content/browser/webrtc/resources",
|
"//content/browser/webrtc/resources",
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ auto_filenames = {
|
|||||||
"docs/api/structures/protocol-response.md",
|
"docs/api/structures/protocol-response.md",
|
||||||
"docs/api/structures/rectangle.md",
|
"docs/api/structures/rectangle.md",
|
||||||
"docs/api/structures/referrer.md",
|
"docs/api/structures/referrer.md",
|
||||||
|
"docs/api/structures/render-process-gone-details.md",
|
||||||
"docs/api/structures/resolved-endpoint.md",
|
"docs/api/structures/resolved-endpoint.md",
|
||||||
"docs/api/structures/resolved-host.md",
|
"docs/api/structures/resolved-host.md",
|
||||||
"docs/api/structures/scrubber-item.md",
|
"docs/api/structures/scrubber-item.md",
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ make_gtk_getlibgtk_public.patch
|
|||||||
build_disable_print_content_analysis.patch
|
build_disable_print_content_analysis.patch
|
||||||
custom_protocols_plzserviceworker.patch
|
custom_protocols_plzserviceworker.patch
|
||||||
feat_filter_out_non-shareable_windows_in_the_current_application_in.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
|
disable_freezing_flags_after_init_in_node.patch
|
||||||
short-circuit_permissions_checks_in_mediastreamdevicescontroller.patch
|
short-circuit_permissions_checks_in_mediastreamdevicescontroller.patch
|
||||||
chore_add_electron_deps_to_gitignores.patch
|
chore_add_electron_deps_to_gitignores.patch
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -45,10 +45,10 @@ index 1e9dcbb9f0a5e401a50d63be490755bc3eeaa1a3..1e2e0a321df6fe9dea4401ed327c9373
|
|||||||
// RenderFrameMetadataProvider::Observer implementation.
|
// RenderFrameMetadataProvider::Observer implementation.
|
||||||
void OnRenderFrameMetadataChangedBeforeActivation(
|
void OnRenderFrameMetadataChangedBeforeActivation(
|
||||||
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
|
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
|
--- a/content/browser/web_contents/web_contents_impl.cc
|
||||||
+++ b/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",
|
"WebContentsImpl::OnFocusedElementChangedInFrame",
|
||||||
"render_frame_host", frame);
|
"render_frame_host", frame);
|
||||||
RenderWidgetHostViewBase* root_view =
|
RenderWidgetHostViewBase* root_view =
|
||||||
|
|||||||
@@ -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
|
this for iframes, but not for webviews as they are essentially main
|
||||||
frames instead of child frames.
|
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
|
Note that we also need to manually update embedder's
|
||||||
`api::WebContents::IsFullscreenForTabOrPending` value.
|
`api::WebContents::IsFullscreenForTabOrPending` value.
|
||||||
@@ -35,3 +36,67 @@ index 01eb116b51037bff5da7a87d119b78387f5e72c6..7ab5609215ce352b2595b4953e1b9ca0
|
|||||||
// Focus the window if another frame may have delegated the capability.
|
// Focus the window if another frame may have delegated the capability.
|
||||||
if (had_fullscreen_token && !GetView()->HasFocus())
|
if (had_fullscreen_token && !GetView()->HasFocus())
|
||||||
GetView()->Focus();
|
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(
|
||||||
|
|||||||
@@ -41,3 +41,4 @@ fix_ftbfs_werror_wextra-semi.patch
|
|||||||
fix_isurl_implementation.patch
|
fix_isurl_implementation.patch
|
||||||
ci_ensure_node_tests_set_electron_run_as_node.patch
|
ci_ensure_node_tests_set_electron_run_as_node.patch
|
||||||
chore_update_fixtures_errors_force_colors_snapshot.patch
|
chore_update_fixtures_errors_force_colors_snapshot.patch
|
||||||
|
fix_assert_module_in_the_renderer_process.patch
|
||||||
|
|||||||
75
patches/node/fix_assert_module_in_the_renderer_process.patch
Normal file
75
patches/node/fix_assert_module_in_the_renderer_process.patch
Normal 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()))
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
param([string]$gomaDir=$PWD)
|
param([string]$gomaDir=$PWD)
|
||||||
$cmdPath = Join-Path -Path $gomaDir -ChildPath "goma_ctl.py"
|
$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;
|
$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)
|
Do { sleep $waitIncrement; $timedOut = (($waitTime+=$waitIncrement) -gt $timeout); iex "$gomaDir\gomacc.exe port 2" > $null; } Until(($LASTEXITCODE -eq 0) -or $timedOut)
|
||||||
if ($timedOut) {
|
if ($timedOut) {
|
||||||
|
|||||||
@@ -54,13 +54,14 @@ namespace {
|
|||||||
// See https://nodejs.org/api/cli.html#cli_options
|
// See https://nodejs.org/api/cli.html#cli_options
|
||||||
void ExitIfContainsDisallowedFlags(const std::vector<std::string>& argv) {
|
void ExitIfContainsDisallowedFlags(const std::vector<std::string>& argv) {
|
||||||
// Options that are unilaterally disallowed.
|
// Options that are unilaterally disallowed.
|
||||||
static constexpr auto disallowed = base::MakeFixedFlatSet<base::StringPiece>({
|
static constexpr auto disallowed =
|
||||||
"--enable-fips",
|
base::MakeFixedFlatSetSorted<base::StringPiece>({
|
||||||
"--force-fips",
|
"--enable-fips",
|
||||||
"--openssl-config",
|
"--force-fips",
|
||||||
"--use-bundled-ca",
|
"--openssl-config",
|
||||||
"--use-openssl-ca",
|
"--use-bundled-ca",
|
||||||
});
|
"--use-openssl-ca",
|
||||||
|
});
|
||||||
|
|
||||||
for (const auto& arg : argv) {
|
for (const auto& arg : argv) {
|
||||||
const auto key = base::StringPiece(arg).substr(0, arg.find('='));
|
const auto key = base::StringPiece(arg).substr(0, arg.find('='));
|
||||||
|
|||||||
@@ -768,10 +768,20 @@ void BaseWindow::AddBrowserView(gin::Handle<BrowserView> browser_view) {
|
|||||||
browser_view->SetOwnerWindow(nullptr);
|
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_->AddBrowserView(browser_view->view());
|
||||||
window_->AddDraggableRegionProvider(browser_view.get());
|
window_->AddDraggableRegionProvider(browser_view.get());
|
||||||
browser_view->SetOwnerWindow(this);
|
browser_view->SetOwnerWindow(this);
|
||||||
browser_views_.emplace_back().Reset(isolate(), browser_view.ToV8());
|
browser_views_.emplace_back().Reset(isolate(), browser_view.ToV8());
|
||||||
|
|
||||||
|
// Recalibrate bounds relative to the containing window.
|
||||||
|
if (!bounds.IsEmpty())
|
||||||
|
browser_view->SetBounds(bounds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ class BrowserView : public gin::Wrappable<BrowserView>,
|
|||||||
BrowserView(const BrowserView&) = delete;
|
BrowserView(const BrowserView&) = delete;
|
||||||
BrowserView& operator=(const BrowserView&) = delete;
|
BrowserView& operator=(const BrowserView&) = delete;
|
||||||
|
|
||||||
|
gfx::Rect GetBounds();
|
||||||
|
void SetBounds(const gfx::Rect& bounds);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
BrowserView(gin::Arguments* args, const gin_helper::Dictionary& options);
|
BrowserView(gin::Arguments* args, const gin_helper::Dictionary& options);
|
||||||
~BrowserView() override;
|
~BrowserView() override;
|
||||||
@@ -78,8 +81,6 @@ class BrowserView : public gin::Wrappable<BrowserView>,
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void SetAutoResize(AutoResizeFlags flags);
|
void SetAutoResize(AutoResizeFlags flags);
|
||||||
void SetBounds(const gfx::Rect& bounds);
|
|
||||||
gfx::Rect GetBounds();
|
|
||||||
void SetBackgroundColor(const std::string& color_name);
|
void SetBackgroundColor(const std::string& color_name);
|
||||||
v8::Local<v8::Value> GetWebContents(v8::Isolate*);
|
v8::Local<v8::Value> GetWebContents(v8::Isolate*);
|
||||||
|
|
||||||
|
|||||||
@@ -2444,12 +2444,25 @@ void WebContents::ReloadIgnoringCache() {
|
|||||||
/* check_for_repost */ true);
|
/* check_for_repost */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebContents::DownloadURL(const GURL& url) {
|
void WebContents::DownloadURL(const GURL& url, gin::Arguments* args) {
|
||||||
auto* browser_context = web_contents()->GetBrowserContext();
|
std::map<std::string, std::string> headers;
|
||||||
auto* download_manager = browser_context->GetDownloadManager();
|
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(
|
std::unique_ptr<download::DownloadUrlParameters> download_params(
|
||||||
content::DownloadRequestUtils::CreateDownloadForWebContentsMainFrame(
|
content::DownloadRequestUtils::CreateDownloadForWebContentsMainFrame(
|
||||||
web_contents(), url, MISSING_TRAFFIC_ANNOTATION));
|
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));
|
download_manager->DownloadUrl(std::move(download_params));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ class WebContents : public ExclusiveAccessContext,
|
|||||||
void LoadURL(const GURL& url, const gin_helper::Dictionary& options);
|
void LoadURL(const GURL& url, const gin_helper::Dictionary& options);
|
||||||
void Reload();
|
void Reload();
|
||||||
void ReloadIgnoringCache();
|
void ReloadIgnoringCache();
|
||||||
void DownloadURL(const GURL& url);
|
void DownloadURL(const GURL& url, gin::Arguments* args);
|
||||||
GURL GetURL() const;
|
GURL GetURL() const;
|
||||||
std::u16string GetTitle() const;
|
std::u16string GetTitle() const;
|
||||||
bool IsLoading() const;
|
bool IsLoading() const;
|
||||||
|
|||||||
@@ -580,7 +580,7 @@ void ElectronBrowserContext::DisplayMediaDeviceChosen(
|
|||||||
blink::MediaStreamDevice video_device(request.video_type, id, name);
|
blink::MediaStreamDevice video_device(request.video_type, id, name);
|
||||||
video_device.display_media_info = DesktopMediaIDToDisplayMediaInformation(
|
video_device.display_media_info = DesktopMediaIDToDisplayMediaInformation(
|
||||||
nullptr, url::Origin::Create(request.security_origin),
|
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;
|
devices.video_device = video_device;
|
||||||
} else if (result_dict.Get("video", &rfh)) {
|
} else if (result_dict.Get("video", &rfh)) {
|
||||||
auto* web_contents = content::WebContents::FromRenderFrameHost(rfh);
|
auto* web_contents = content::WebContents::FromRenderFrameHost(rfh);
|
||||||
@@ -592,7 +592,7 @@ void ElectronBrowserContext::DisplayMediaDeviceChosen(
|
|||||||
base::UTF16ToUTF8(web_contents->GetTitle()));
|
base::UTF16ToUTF8(web_contents->GetTitle()));
|
||||||
video_device.display_media_info = DesktopMediaIDToDisplayMediaInformation(
|
video_device.display_media_info = DesktopMediaIDToDisplayMediaInformation(
|
||||||
web_contents, url::Origin::Create(request.security_origin),
|
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;
|
devices.video_device = video_device;
|
||||||
} else {
|
} else {
|
||||||
gin_helper::ErrorThrower(args->isolate())
|
gin_helper::ErrorThrower(args->isolate())
|
||||||
|
|||||||
@@ -204,11 +204,11 @@ ElectronBrowserMainParts* ElectronBrowserMainParts::self_ = nullptr;
|
|||||||
|
|
||||||
ElectronBrowserMainParts::ElectronBrowserMainParts()
|
ElectronBrowserMainParts::ElectronBrowserMainParts()
|
||||||
: fake_browser_process_(std::make_unique<BrowserProcessImpl>()),
|
: fake_browser_process_(std::make_unique<BrowserProcessImpl>()),
|
||||||
browser_(std::make_unique<Browser>()),
|
node_bindings_{
|
||||||
node_bindings_(
|
NodeBindings::Create(NodeBindings::BrowserEnvironment::kBrowser)},
|
||||||
NodeBindings::Create(NodeBindings::BrowserEnvironment::kBrowser)),
|
electron_bindings_{
|
||||||
electron_bindings_(
|
std::make_unique<ElectronBindings>(node_bindings_->uv_loop())},
|
||||||
std::make_unique<ElectronBindings>(node_bindings_->uv_loop())) {
|
browser_{std::make_unique<Browser>()} {
|
||||||
DCHECK(!self_) << "Cannot have two ElectronBrowserMainParts";
|
DCHECK(!self_) << "Cannot have two ElectronBrowserMainParts";
|
||||||
self_ = this;
|
self_ = this;
|
||||||
}
|
}
|
||||||
@@ -266,26 +266,25 @@ void ElectronBrowserMainParts::PostEarlyInitialization() {
|
|||||||
|
|
||||||
node_bindings_->Initialize(js_env_->isolate()->GetCurrentContext());
|
node_bindings_->Initialize(js_env_->isolate()->GetCurrentContext());
|
||||||
// Create the global environment.
|
// Create the global environment.
|
||||||
node::Environment* env = node_bindings_->CreateEnvironment(
|
node_env_ = node_bindings_->CreateEnvironment(
|
||||||
js_env_->isolate()->GetCurrentContext(), js_env_->platform());
|
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.
|
// 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.
|
// 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.
|
// Create explicit microtasks runner.
|
||||||
js_env_->CreateMicrotasksRunner();
|
js_env_->CreateMicrotasksRunner();
|
||||||
|
|
||||||
// Wrap the uv loop with global env.
|
// Wrap the uv loop with global env.
|
||||||
node_bindings_->set_uv_env(env);
|
node_bindings_->set_uv_env(node_env_.get());
|
||||||
|
|
||||||
// Load everything.
|
// Load everything.
|
||||||
node_bindings_->LoadEnvironment(env);
|
node_bindings_->LoadEnvironment(node_env_.get());
|
||||||
|
|
||||||
// We already initialized the feature list in PreEarlyInitialization(), but
|
// We already initialized the feature list in PreEarlyInitialization(), but
|
||||||
// the user JS script would not have had a chance to alter the command-line
|
// 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
|
// Destroy node platform after all destructors_ are executed, as they may
|
||||||
// invoke Node/V8 APIs inside them.
|
// invoke Node/V8 APIs inside them.
|
||||||
node_env_->env()->set_trace_sync_io(false);
|
node_env_->set_trace_sync_io(false);
|
||||||
js_env_->DestroyMicrotasksRunner();
|
js_env_->DestroyMicrotasksRunner();
|
||||||
node::Stop(node_env_->env(), node::StopFlags::kDoNotTerminateIsolate);
|
node::Stop(node_env_.get(), node::StopFlags::kDoNotTerminateIsolate);
|
||||||
node_env_.reset();
|
node_env_.reset();
|
||||||
|
|
||||||
auto default_context_key = ElectronBrowserContext::PartitionKey("", false);
|
auto default_context_key = ElectronBrowserContext::PartitionKey("", false);
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ class Screen;
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
namespace node {
|
||||||
|
class Environment;
|
||||||
|
}
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
class LinuxUiGetter;
|
class LinuxUiGetter;
|
||||||
}
|
}
|
||||||
@@ -47,7 +51,6 @@ class Browser;
|
|||||||
class ElectronBindings;
|
class ElectronBindings;
|
||||||
class JavascriptEnvironment;
|
class JavascriptEnvironment;
|
||||||
class NodeBindings;
|
class NodeBindings;
|
||||||
class NodeEnvironment;
|
|
||||||
|
|
||||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||||
class ElectronExtensionsClient;
|
class ElectronExtensionsClient;
|
||||||
@@ -157,11 +160,20 @@ class ElectronBrowserMainParts : public content::BrowserMainParts {
|
|||||||
// Before then, we just exit() without any intermediate steps.
|
// Before then, we just exit() without any intermediate steps.
|
||||||
absl::optional<int> exit_code_;
|
absl::optional<int> exit_code_;
|
||||||
|
|
||||||
std::unique_ptr<JavascriptEnvironment> js_env_;
|
|
||||||
std::unique_ptr<Browser> browser_;
|
|
||||||
std::unique_ptr<NodeBindings> node_bindings_;
|
std::unique_ptr<NodeBindings> node_bindings_;
|
||||||
|
|
||||||
|
// depends-on: node_bindings_
|
||||||
std::unique_ptr<ElectronBindings> electron_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<IconManager> icon_manager_;
|
||||||
std::unique_ptr<base::FieldTrialList> field_trial_list_;
|
std::unique_ptr<base::FieldTrialList> field_trial_list_;
|
||||||
|
|
||||||
|
|||||||
@@ -301,6 +301,7 @@ ExtensionFunction::ResponseAction TabsQueryFunction::Run() {
|
|||||||
|
|
||||||
tabs::Tab tab;
|
tabs::Tab tab;
|
||||||
tab.id = contents->ID();
|
tab.id = contents->ID();
|
||||||
|
tab.title = base::UTF16ToUTF8(wc->GetTitle());
|
||||||
tab.url = wc->GetLastCommittedURL().spec();
|
tab.url = wc->GetLastCommittedURL().spec();
|
||||||
tab.active = contents->IsFocused();
|
tab.active = contents->IsFocused();
|
||||||
tab.audible = contents->IsCurrentlyAudible();
|
tab.audible = contents->IsCurrentlyAudible();
|
||||||
@@ -322,12 +323,18 @@ ExtensionFunction::ResponseAction TabsGetFunction::Run() {
|
|||||||
return RespondNow(Error("No such tab"));
|
return RespondNow(Error("No such tab"));
|
||||||
|
|
||||||
tabs::Tab tab;
|
tabs::Tab tab;
|
||||||
|
|
||||||
tab.id = tab_id;
|
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
|
// "title" and "url" properties are considered privileged data and can
|
||||||
// permission check here.
|
// only be checked if the extension has the "tabs" permission or it has
|
||||||
tab.url = contents->web_contents()->GetLastCommittedURL().spec();
|
// 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();
|
tab.active = contents->IsFocused();
|
||||||
|
|
||||||
@@ -609,10 +616,16 @@ ExtensionFunction::ResponseValue TabsUpdateFunction::GetResult() {
|
|||||||
auto* api_web_contents = electron::api::WebContents::From(web_contents_);
|
auto* api_web_contents = electron::api::WebContents::From(web_contents_);
|
||||||
tab.id = (api_web_contents ? api_web_contents->ID() : -1);
|
tab.id = (api_web_contents ? api_web_contents->ID() : -1);
|
||||||
|
|
||||||
// TODO(nornagon): in Chrome, the tab URL is only available to extensions
|
// "title" and "url" properties are considered privileged data and can
|
||||||
// that have the "tabs" (or "activeTab") permission. We should do the same
|
// only be checked if the extension has the "tabs" permission or it has
|
||||||
// permission check here.
|
// access to the WebContents's origin.
|
||||||
tab.url = web_contents_->GetLastCommittedURL().spec();
|
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)
|
if (api_web_contents)
|
||||||
tab.active = api_web_contents->IsFocused();
|
tab.active = api_web_contents->IsFocused();
|
||||||
|
|||||||
@@ -104,9 +104,10 @@ gin::IsolateHolder CreateIsolateHolder(v8::Isolate* isolate) {
|
|||||||
|
|
||||||
JavascriptEnvironment::JavascriptEnvironment(uv_loop_t* event_loop,
|
JavascriptEnvironment::JavascriptEnvironment(uv_loop_t* event_loop,
|
||||||
bool setup_wasm_streaming)
|
bool setup_wasm_streaming)
|
||||||
: isolate_(Initialize(event_loop, setup_wasm_streaming)),
|
: isolate_holder_{CreateIsolateHolder(
|
||||||
isolate_holder_(CreateIsolateHolder(isolate_)),
|
Initialize(event_loop, setup_wasm_streaming))},
|
||||||
locker_(isolate_) {
|
isolate_{isolate_holder_.isolate()},
|
||||||
|
locker_{isolate_} {
|
||||||
isolate_->Enter();
|
isolate_->Enter();
|
||||||
|
|
||||||
v8::HandleScope scope(isolate_);
|
v8::HandleScope scope(isolate_);
|
||||||
@@ -339,12 +340,4 @@ void JavascriptEnvironment::DestroyMicrotasksRunner() {
|
|||||||
base::CurrentThread::Get()->RemoveTaskObserver(microtasks_runner_.get());
|
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
|
} // namespace electron
|
||||||
|
|||||||
@@ -43,29 +43,17 @@ class JavascriptEnvironment {
|
|||||||
v8::Isolate* Initialize(uv_loop_t* event_loop, bool setup_wasm_streaming);
|
v8::Isolate* Initialize(uv_loop_t* event_loop, bool setup_wasm_streaming);
|
||||||
std::unique_ptr<node::MultiIsolatePlatform> platform_;
|
std::unique_ptr<node::MultiIsolatePlatform> platform_;
|
||||||
|
|
||||||
raw_ptr<v8::Isolate> isolate_;
|
|
||||||
gin::IsolateHolder isolate_holder_;
|
gin::IsolateHolder isolate_holder_;
|
||||||
|
|
||||||
|
// owned-by: isolate_holder_
|
||||||
|
const raw_ptr<v8::Isolate> isolate_;
|
||||||
|
|
||||||
|
// depends-on: isolate_
|
||||||
v8::Locker locker_;
|
v8::Locker locker_;
|
||||||
|
|
||||||
std::unique_ptr<MicrotasksRunner> microtasks_runner_;
|
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
|
} // namespace electron
|
||||||
|
|
||||||
#endif // ELECTRON_SHELL_BROWSER_JAVASCRIPT_ENVIRONMENT_H_
|
#endif // ELECTRON_SHELL_BROWSER_JAVASCRIPT_ENVIRONMENT_H_
|
||||||
|
|||||||
@@ -55,25 +55,27 @@ void NativeBrowserViewMac::SetBounds(const gfx::Rect& bounds) {
|
|||||||
auto* iwc_view = GetInspectableWebContentsView();
|
auto* iwc_view = GetInspectableWebContentsView();
|
||||||
if (!iwc_view)
|
if (!iwc_view)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto* view = iwc_view->GetNativeView().GetNativeNSView();
|
auto* view = iwc_view->GetNativeView().GetNativeNSView();
|
||||||
auto* superview = view.superview;
|
const auto superview_height =
|
||||||
const auto superview_height = superview ? superview.frame.size.height : 0;
|
view.superview ? view.superview.frame.size.height : 0;
|
||||||
view.frame =
|
int y_coord = superview_height - bounds.y() - bounds.height();
|
||||||
NSMakeRect(bounds.x(), superview_height - bounds.y() - bounds.height(),
|
|
||||||
bounds.width(), bounds.height());
|
view.frame = NSMakeRect(bounds.x(), y_coord, bounds.width(), bounds.height());
|
||||||
}
|
}
|
||||||
|
|
||||||
gfx::Rect NativeBrowserViewMac::GetBounds() {
|
gfx::Rect NativeBrowserViewMac::GetBounds() {
|
||||||
auto* iwc_view = GetInspectableWebContentsView();
|
auto* iwc_view = GetInspectableWebContentsView();
|
||||||
if (!iwc_view)
|
if (!iwc_view)
|
||||||
return gfx::Rect();
|
return gfx::Rect();
|
||||||
|
|
||||||
NSView* view = iwc_view->GetNativeView().GetNativeNSView();
|
NSView* view = iwc_view->GetNativeView().GetNativeNSView();
|
||||||
const int superview_height =
|
const int superview_height =
|
||||||
(view.superview) ? view.superview.frame.size.height : 0;
|
view.superview ? view.superview.frame.size.height : 0;
|
||||||
return gfx::Rect(
|
int y_coord = superview_height - view.frame.origin.y - view.frame.size.height;
|
||||||
view.frame.origin.x,
|
|
||||||
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.width, view.frame.size.height);
|
view.frame.size.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NativeBrowserViewMac::SetBackgroundColor(SkColor color) {
|
void NativeBrowserViewMac::SetBackgroundColor(SkColor color) {
|
||||||
|
|||||||
@@ -834,23 +834,19 @@ void NativeWindowMac::SetResizable(bool resizable) {
|
|||||||
ScopedDisableResize disable_resize;
|
ScopedDisableResize disable_resize;
|
||||||
SetStyleMask(resizable, NSWindowStyleMaskResizable);
|
SetStyleMask(resizable, NSWindowStyleMaskResizable);
|
||||||
|
|
||||||
|
bool was_fullscreenable = IsFullScreenable();
|
||||||
|
|
||||||
// Right now, resizable and fullscreenable are decoupled in
|
// Right now, resizable and fullscreenable are decoupled in
|
||||||
// documentation and on Windows/Linux. Chromium disables
|
// documentation and on Windows/Linux. Chromium disables
|
||||||
// fullscreenability if resizability is false on macOS as well
|
// fullscreen collection behavior as well as the maximize traffic
|
||||||
// as disabling the maximize traffic light unless the window
|
// light in SetCanResize if resizability is false on macOS unless
|
||||||
// is both resizable and maximizable. To work around this, we want
|
// the window is both resizable and maximizable. We want consistent
|
||||||
// to match behavior on other platforms by disabiliting the maximize
|
// cross-platform behavior, so if resizability is disabled we disable
|
||||||
// button but keeping fullscreenability enabled.
|
// the maximize button and ensure fullscreenability matches user setting.
|
||||||
// TODO(codebytere): refactor this once we have a better solution.
|
|
||||||
SetCanResize(resizable);
|
SetCanResize(resizable);
|
||||||
if (!resizable) {
|
SetFullScreenable(was_fullscreenable);
|
||||||
SetFullScreenable(true);
|
[[window_ standardWindowButton:NSWindowZoomButton]
|
||||||
[[window_ standardWindowButton:NSWindowZoomButton] setEnabled:false];
|
setEnabled:resizable ? was_fullscreenable : false];
|
||||||
} else {
|
|
||||||
SetFullScreenable(true);
|
|
||||||
[[window_ standardWindowButton:NSWindowZoomButton]
|
|
||||||
setEnabled:IsFullScreenable()];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NativeWindowMac::IsResizable() {
|
bool NativeWindowMac::IsResizable() {
|
||||||
|
|||||||
@@ -150,7 +150,13 @@ void ClientFrameViewLinux::Init(NativeWindowViews* window,
|
|||||||
}
|
}
|
||||||
|
|
||||||
gfx::Insets ClientFrameViewLinux::GetBorderDecorationInsets() const {
|
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 {
|
gfx::Insets ClientFrameViewLinux::GetInputInsets() const {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ electron::UsbChooserContext* GetChooserContext(
|
|||||||
// These extensions can claim the smart card USB class and automatically gain
|
// These extensions can claim the smart card USB class and automatically gain
|
||||||
// permissions for devices that have an interface with this class.
|
// permissions for devices that have an interface with this class.
|
||||||
constexpr auto kSmartCardPrivilegedExtensionIds =
|
constexpr auto kSmartCardPrivilegedExtensionIds =
|
||||||
base::MakeFixedFlatSet<base::StringPiece>({
|
base::MakeFixedFlatSetSorted<base::StringPiece>({
|
||||||
// Smart Card Connector Extension and its Beta version, see
|
// Smart Card Connector Extension and its Beta version, see
|
||||||
// crbug.com/1233881.
|
// crbug.com/1233881.
|
||||||
"khpfeaanjngmcnplbdlpegiifgpfgdco",
|
"khpfeaanjngmcnplbdlpegiifgpfgdco",
|
||||||
|
|||||||
@@ -20,5 +20,11 @@
|
|||||||
"extension_types": [
|
"extension_types": [
|
||||||
"extension"
|
"extension"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"tabs": {
|
||||||
|
"channel": "stable",
|
||||||
|
"extension_types": [
|
||||||
|
"extension"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,6 +39,8 @@ constexpr APIPermissionInfo::InitInfo permissions_to_register[] = {
|
|||||||
{mojom::APIPermissionID::kPdfViewerPrivate, "pdfViewerPrivate"},
|
{mojom::APIPermissionID::kPdfViewerPrivate, "pdfViewerPrivate"},
|
||||||
#endif
|
#endif
|
||||||
{mojom::APIPermissionID::kManagement, "management"},
|
{mojom::APIPermissionID::kManagement, "management"},
|
||||||
|
{mojom::APIPermissionID::kTab, "tabs",
|
||||||
|
APIPermissionInfo::kFlagRequiresManagementUIWarning},
|
||||||
};
|
};
|
||||||
base::span<const APIPermissionInfo::InitInfo> GetPermissionInfos() {
|
base::span<const APIPermissionInfo::InitInfo> GetPermissionInfos() {
|
||||||
return base::make_span(permissions_to_register);
|
return base::make_span(permissions_to_register);
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ void ErrorMessageListener(v8::Local<v8::Message> message,
|
|||||||
// If node CLI inspect support is disabled, allow no debug options.
|
// If node CLI inspect support is disabled, allow no debug options.
|
||||||
bool IsAllowedOption(base::StringPiece option) {
|
bool IsAllowedOption(base::StringPiece option) {
|
||||||
static constexpr auto debug_options =
|
static constexpr auto debug_options =
|
||||||
base::MakeFixedFlatSet<base::StringPiece>({
|
base::MakeFixedFlatSetSorted<base::StringPiece>({
|
||||||
"--debug",
|
"--debug",
|
||||||
"--debug-brk",
|
"--debug-brk",
|
||||||
"--debug-port",
|
"--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.
|
// This should be aligned with what's possible to set via the process object.
|
||||||
static constexpr auto options = base::MakeFixedFlatSet<base::StringPiece>({
|
static constexpr auto options =
|
||||||
"--trace-warnings",
|
base::MakeFixedFlatSetSorted<base::StringPiece>({
|
||||||
"--trace-deprecation",
|
"--dns-result-order",
|
||||||
"--throw-deprecation",
|
"--no-deprecation",
|
||||||
"--no-deprecation",
|
"--throw-deprecation",
|
||||||
"--dns-result-order",
|
"--trace-deprecation",
|
||||||
});
|
"--trace-warnings",
|
||||||
|
});
|
||||||
|
|
||||||
if (debug_options.contains(option))
|
if (debug_options.contains(option))
|
||||||
return electron::fuses::IsNodeCliInspectEnabled();
|
return electron::fuses::IsNodeCliInspectEnabled();
|
||||||
@@ -262,14 +263,21 @@ bool IsAllowedOption(base::StringPiece option) {
|
|||||||
// See https://nodejs.org/api/cli.html#cli_node_options_options
|
// See https://nodejs.org/api/cli.html#cli_node_options_options
|
||||||
void SetNodeOptions(base::Environment* env) {
|
void SetNodeOptions(base::Environment* env) {
|
||||||
// Options that are unilaterally disallowed
|
// Options that are unilaterally disallowed
|
||||||
static constexpr auto disallowed = base::MakeFixedFlatSet<base::StringPiece>(
|
static constexpr auto disallowed =
|
||||||
{"--enable-fips", "--force-fips", "--openssl-config", "--use-bundled-ca",
|
base::MakeFixedFlatSetSorted<base::StringPiece>({
|
||||||
"--use-openssl-ca", "--experimental-policy"});
|
"--enable-fips",
|
||||||
|
"--experimental-policy",
|
||||||
|
"--force-fips",
|
||||||
|
"--openssl-config",
|
||||||
|
"--use-bundled-ca",
|
||||||
|
"--use-openssl-ca",
|
||||||
|
});
|
||||||
|
|
||||||
static constexpr auto pkg_opts = base::MakeFixedFlatSet<base::StringPiece>({
|
static constexpr auto pkg_opts =
|
||||||
"--http-parser",
|
base::MakeFixedFlatSetSorted<base::StringPiece>({
|
||||||
"--max-http-header-size",
|
"--http-parser",
|
||||||
});
|
"--max-http-header-size",
|
||||||
|
});
|
||||||
|
|
||||||
if (env->HasVar("NODE_OPTIONS")) {
|
if (env->HasVar("NODE_OPTIONS")) {
|
||||||
if (electron::fuses::IsNodeOptionsEnabled()) {
|
if (electron::fuses::IsNodeOptionsEnabled()) {
|
||||||
@@ -469,7 +477,7 @@ void NodeBindings::Initialize(v8::Local<v8::Context> context) {
|
|||||||
g_is_initialized = true;
|
g_is_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
node::Environment* NodeBindings::CreateEnvironment(
|
std::shared_ptr<node::Environment> NodeBindings::CreateEnvironment(
|
||||||
v8::Handle<v8::Context> context,
|
v8::Handle<v8::Context> context,
|
||||||
node::MultiIsolatePlatform* platform,
|
node::MultiIsolatePlatform* platform,
|
||||||
std::vector<std::string> args,
|
std::vector<std::string> args,
|
||||||
@@ -636,10 +644,25 @@ node::Environment* NodeBindings::CreateEnvironment(
|
|||||||
base::PathService::Get(content::CHILD_PROCESS_EXE, &helper_exec_path);
|
base::PathService::Get(content::CHILD_PROCESS_EXE, &helper_exec_path);
|
||||||
process.Set("helperExecPath", 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,
|
v8::Handle<v8::Context> context,
|
||||||
node::MultiIsolatePlatform* platform) {
|
node::MultiIsolatePlatform* platform) {
|
||||||
#if BUILDFLAG(IS_WIN)
|
#if BUILDFLAG(IS_WIN)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#ifndef ELECTRON_SHELL_COMMON_NODE_BINDINGS_H_
|
#ifndef ELECTRON_SHELL_COMMON_NODE_BINDINGS_H_
|
||||||
#define ELECTRON_SHELL_COMMON_NODE_BINDINGS_H_
|
#define ELECTRON_SHELL_COMMON_NODE_BINDINGS_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -25,12 +26,6 @@ class SingleThreadTaskRunner;
|
|||||||
|
|
||||||
namespace electron {
|
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.
|
// 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
|
// 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();
|
std::vector<std::string> ParseNodeCliFlags();
|
||||||
|
|
||||||
// Create the environment and load node.js.
|
// Create the environment and load node.js.
|
||||||
node::Environment* CreateEnvironment(v8::Handle<v8::Context> context,
|
std::shared_ptr<node::Environment> CreateEnvironment(
|
||||||
node::MultiIsolatePlatform* platform,
|
v8::Handle<v8::Context> context,
|
||||||
std::vector<std::string> args,
|
node::MultiIsolatePlatform* platform,
|
||||||
std::vector<std::string> exec_args);
|
std::vector<std::string> args,
|
||||||
node::Environment* CreateEnvironment(v8::Handle<v8::Context> context,
|
std::vector<std::string> exec_args);
|
||||||
node::MultiIsolatePlatform* platform);
|
|
||||||
|
std::shared_ptr<node::Environment> CreateEnvironment(
|
||||||
|
v8::Handle<v8::Context> context,
|
||||||
|
node::MultiIsolatePlatform* platform);
|
||||||
|
|
||||||
// Load node.js in the environment.
|
// Load node.js in the environment.
|
||||||
void LoadEnvironment(node::Environment* env);
|
void LoadEnvironment(node::Environment* env);
|
||||||
@@ -111,12 +109,6 @@ class NodeBindings {
|
|||||||
// Notify embed thread to start polling after environment is loaded.
|
// Notify embed thread to start polling after environment is loaded.
|
||||||
void StartPolling();
|
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 {
|
node::IsolateData* isolate_data(v8::Local<v8::Context> context) const {
|
||||||
if (context->GetNumberOfEmbedderDataFields() <=
|
if (context->GetNumberOfEmbedderDataFields() <=
|
||||||
kElectronContextEmbedderDataIndex) {
|
kElectronContextEmbedderDataIndex) {
|
||||||
@@ -167,6 +159,12 @@ class NodeBindings {
|
|||||||
raw_ptr<uv_loop_t> uv_loop_;
|
raw_ptr<uv_loop_t> uv_loop_;
|
||||||
|
|
||||||
private:
|
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.
|
// Thread to poll uv events.
|
||||||
static void EmbedThreadRunner(void* arg);
|
static void EmbedThreadRunner(void* arg);
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ void ElectronRendererClient::DidCreateScriptContext(
|
|||||||
v8::Maybe<bool> initialized = node::InitializeContext(renderer_context);
|
v8::Maybe<bool> initialized = node::InitializeContext(renderer_context);
|
||||||
CHECK(!initialized.IsNothing() && initialized.FromJust());
|
CHECK(!initialized.IsNothing() && initialized.FromJust());
|
||||||
|
|
||||||
node::Environment* env =
|
std::shared_ptr<node::Environment> env =
|
||||||
node_bindings_->CreateEnvironment(renderer_context, nullptr);
|
node_bindings_->CreateEnvironment(renderer_context, nullptr);
|
||||||
|
|
||||||
// If we have disabled the site instance overrides we should prevent loading
|
// 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);
|
BindProcess(env->isolate(), &process_dict, render_frame);
|
||||||
|
|
||||||
// Load everything.
|
// Load everything.
|
||||||
node_bindings_->LoadEnvironment(env);
|
node_bindings_->LoadEnvironment(env.get());
|
||||||
|
|
||||||
if (node_bindings_->uv_env() == nullptr) {
|
if (node_bindings_->uv_env() == nullptr) {
|
||||||
// Make uv loop being wrapped by window context.
|
// 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.
|
// Give the node loop a run to make sure everything is ready.
|
||||||
node_bindings_->StartPolling();
|
node_bindings_->StartPolling();
|
||||||
@@ -124,7 +124,9 @@ void ElectronRendererClient::WillReleaseScriptContext(
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
node::Environment* env = node::Environment::GetCurrent(context);
|
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;
|
return;
|
||||||
|
|
||||||
gin_helper::EmitEvent(env->isolate(), env->process_object(), "exit");
|
gin_helper::EmitEvent(env->isolate(), env->process_object(), "exit");
|
||||||
@@ -143,9 +145,7 @@ void ElectronRendererClient::WillReleaseScriptContext(
|
|||||||
DCHECK_EQ(microtask_queue->GetMicrotasksScopeDepth(), 0);
|
DCHECK_EQ(microtask_queue->GetMicrotasksScopeDepth(), 0);
|
||||||
microtask_queue->set_microtasks_policy(v8::MicrotasksPolicy::kExplicit);
|
microtask_queue->set_microtasks_policy(v8::MicrotasksPolicy::kExplicit);
|
||||||
|
|
||||||
node::FreeEnvironment(env);
|
environments_.erase(iter);
|
||||||
node::FreeIsolateData(node_bindings_->isolate_data(context));
|
|
||||||
node_bindings_->clear_isolate_data(context);
|
|
||||||
|
|
||||||
microtask_queue->set_microtasks_policy(old_policy);
|
microtask_queue->set_microtasks_policy(old_policy);
|
||||||
|
|
||||||
@@ -201,7 +201,11 @@ node::Environment* ElectronRendererClient::GetEnvironment(
|
|||||||
auto context =
|
auto context =
|
||||||
GetContext(render_frame->GetWebFrame(), v8::Isolate::GetCurrent());
|
GetContext(render_frame->GetWebFrame(), v8::Isolate::GetCurrent());
|
||||||
node::Environment* env = node::Environment::GetCurrent(context);
|
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
|
} // namespace electron
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class ElectronRendererClient : public RendererClientBase {
|
|||||||
// The node::Environment::GetCurrent API does not return nullptr when it
|
// The node::Environment::GetCurrent API does not return nullptr when it
|
||||||
// is called for a context without node::Environment, so we have to keep
|
// is called for a context without node::Environment, so we have to keep
|
||||||
// a book of the environments created.
|
// 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
|
// Getting main script context from web frame would lazily initializes
|
||||||
// its script context. Doing so in a web page without scripts would trigger
|
// its script context. Doing so in a web page without scripts would trigger
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include "base/containers/cxx20_erase_set.h"
|
||||||
#include "base/no_destructor.h"
|
#include "base/no_destructor.h"
|
||||||
|
#include "base/ranges/algorithm.h"
|
||||||
#include "base/threading/thread_local.h"
|
#include "base/threading/thread_local.h"
|
||||||
#include "shell/common/api/electron_bindings.h"
|
#include "shell/common/api/electron_bindings.h"
|
||||||
#include "shell/common/gin_helper/event_emitter_caller.h"
|
#include "shell/common/gin_helper/event_emitter_caller.h"
|
||||||
@@ -61,20 +63,23 @@ void WebWorkerObserver::WorkerScriptReadyForEvaluation(
|
|||||||
// Setup node environment for each window.
|
// Setup node environment for each window.
|
||||||
v8::Maybe<bool> initialized = node::InitializeContext(worker_context);
|
v8::Maybe<bool> initialized = node::InitializeContext(worker_context);
|
||||||
CHECK(!initialized.IsNothing() && initialized.FromJust());
|
CHECK(!initialized.IsNothing() && initialized.FromJust());
|
||||||
node::Environment* env =
|
std::shared_ptr<node::Environment> env =
|
||||||
node_bindings_->CreateEnvironment(worker_context, nullptr);
|
node_bindings_->CreateEnvironment(worker_context, nullptr);
|
||||||
|
|
||||||
// Add Electron extended APIs.
|
// Add Electron extended APIs.
|
||||||
electron_bindings_->BindTo(env->isolate(), env->process_object());
|
electron_bindings_->BindTo(env->isolate(), env->process_object());
|
||||||
|
|
||||||
// Load everything.
|
// Load everything.
|
||||||
node_bindings_->LoadEnvironment(env);
|
node_bindings_->LoadEnvironment(env.get());
|
||||||
|
|
||||||
// Make uv loop being wrapped by window context.
|
// 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.
|
// Give the node loop a run to make sure everything is ready.
|
||||||
node_bindings_->StartPolling();
|
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) {
|
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);
|
DCHECK_EQ(microtask_queue->GetMicrotasksScopeDepth(), 0);
|
||||||
microtask_queue->set_microtasks_policy(v8::MicrotasksPolicy::kExplicit);
|
microtask_queue->set_microtasks_policy(v8::MicrotasksPolicy::kExplicit);
|
||||||
|
|
||||||
node::FreeEnvironment(env);
|
base::EraseIf(environments_,
|
||||||
node::FreeIsolateData(node_bindings_->isolate_data(context));
|
[env](auto const& item) { return item.get() == env; });
|
||||||
node_bindings_->clear_isolate_data(context);
|
|
||||||
|
|
||||||
microtask_queue->set_microtasks_policy(old_policy);
|
microtask_queue->set_microtasks_policy(old_policy);
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,16 @@
|
|||||||
#define ELECTRON_SHELL_RENDERER_WEB_WORKER_OBSERVER_H_
|
#define ELECTRON_SHELL_RENDERER_WEB_WORKER_OBSERVER_H_
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
#include "v8/include/v8.h"
|
#include "v8/include/v8.h"
|
||||||
|
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
class Environment;
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
|
||||||
namespace electron {
|
namespace electron {
|
||||||
|
|
||||||
class ElectronBindings;
|
class ElectronBindings;
|
||||||
@@ -35,6 +42,7 @@ class WebWorkerObserver {
|
|||||||
private:
|
private:
|
||||||
std::unique_ptr<NodeBindings> node_bindings_;
|
std::unique_ptr<NodeBindings> node_bindings_;
|
||||||
std::unique_ptr<ElectronBindings> electron_bindings_;
|
std::unique_ptr<ElectronBindings> electron_bindings_;
|
||||||
|
std::set<std::shared_ptr<node::Environment>> environments_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace electron
|
} // namespace electron
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ NodeService::NodeService(
|
|||||||
|
|
||||||
NodeService::~NodeService() {
|
NodeService::~NodeService() {
|
||||||
if (!node_env_stopped_) {
|
if (!node_env_stopped_) {
|
||||||
node_env_->env()->set_trace_sync_io(false);
|
node_env_->set_trace_sync_io(false);
|
||||||
js_env_->DestroyMicrotasksRunner();
|
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
|
#endif
|
||||||
|
|
||||||
// Create the global environment.
|
// Create the global environment.
|
||||||
node::Environment* env = node_bindings_->CreateEnvironment(
|
node_env_ = node_bindings_->CreateEnvironment(
|
||||||
js_env_->isolate()->GetCurrentContext(), js_env_->platform(),
|
js_env_->isolate()->GetCurrentContext(), js_env_->platform(),
|
||||||
params->args, params->exec_args);
|
params->args, params->exec_args);
|
||||||
node_env_ = std::make_unique<NodeEnvironment>(env);
|
|
||||||
|
|
||||||
node::SetProcessExitHandler(
|
node::SetProcessExitHandler(
|
||||||
env, [this](node::Environment* env, int exit_code) {
|
node_env_.get(), [this](node::Environment* env, int exit_code) {
|
||||||
// Destroy node platform.
|
// Destroy node platform.
|
||||||
env->set_trace_sync_io(false);
|
env->set_trace_sync_io(false);
|
||||||
js_env_->DestroyMicrotasksRunner();
|
js_env_->DestroyMicrotasksRunner();
|
||||||
@@ -72,20 +71,21 @@ void NodeService::Initialize(node::mojom::NodeServiceParamsPtr params) {
|
|||||||
receiver_.ResetWithReason(exit_code, "");
|
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.
|
// 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.
|
// 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);
|
process.SetHidden("_serviceStartupScript", params->script);
|
||||||
|
|
||||||
// Setup microtask runner.
|
// Setup microtask runner.
|
||||||
js_env_->CreateMicrotasksRunner();
|
js_env_->CreateMicrotasksRunner();
|
||||||
|
|
||||||
// Wrap the uv loop with global env.
|
// 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
|
// LoadEnvironment should be called after setting up
|
||||||
// JavaScriptEnvironment including the microtask runner
|
// 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
|
// the exit handler set above will be triggered and it expects
|
||||||
// both Node Env and JavaScriptEnviroment are setup to perform
|
// both Node Env and JavaScriptEnviroment are setup to perform
|
||||||
// a clean shutdown of this process.
|
// a clean shutdown of this process.
|
||||||
node_bindings_->LoadEnvironment(env);
|
node_bindings_->LoadEnvironment(node_env_.get());
|
||||||
|
|
||||||
// Run entry script.
|
// Run entry script.
|
||||||
node_bindings_->PrepareEmbedThread();
|
node_bindings_->PrepareEmbedThread();
|
||||||
|
|||||||
@@ -11,12 +11,17 @@
|
|||||||
#include "mojo/public/cpp/bindings/receiver.h"
|
#include "mojo/public/cpp/bindings/receiver.h"
|
||||||
#include "shell/services/node/public/mojom/node_service.mojom.h"
|
#include "shell/services/node/public/mojom/node_service.mojom.h"
|
||||||
|
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
class Environment;
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
|
||||||
namespace electron {
|
namespace electron {
|
||||||
|
|
||||||
class ElectronBindings;
|
class ElectronBindings;
|
||||||
class JavascriptEnvironment;
|
class JavascriptEnvironment;
|
||||||
class NodeBindings;
|
class NodeBindings;
|
||||||
class NodeEnvironment;
|
|
||||||
|
|
||||||
class NodeService : public node::mojom::NodeService {
|
class NodeService : public node::mojom::NodeService {
|
||||||
public:
|
public:
|
||||||
@@ -35,7 +40,7 @@ class NodeService : public node::mojom::NodeService {
|
|||||||
std::unique_ptr<JavascriptEnvironment> js_env_;
|
std::unique_ptr<JavascriptEnvironment> js_env_;
|
||||||
std::unique_ptr<NodeBindings> node_bindings_;
|
std::unique_ptr<NodeBindings> node_bindings_;
|
||||||
std::unique_ptr<ElectronBindings> electron_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};
|
mojo::Receiver<node::mojom::NodeService> receiver_{this};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -147,6 +147,41 @@ describe('BrowserView module', () => {
|
|||||||
view.setBounds({} as any);
|
view.setBounds({} as any);
|
||||||
}).to.throw(/conversion failure/);
|
}).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()', () => {
|
describe('BrowserView.getBounds()', () => {
|
||||||
@@ -156,6 +191,16 @@ describe('BrowserView module', () => {
|
|||||||
view.setBounds(bounds);
|
view.setBounds(bounds);
|
||||||
expect(view.getBounds()).to.deep.equal(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()', () => {
|
describe('BrowserWindow.setBrowserView()', () => {
|
||||||
|
|||||||
@@ -5456,6 +5456,42 @@ describe('BrowserWindow module', () => {
|
|||||||
expect(w.isFullScreenable()).to.be.true('isFullScreenable');
|
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', () => {
|
ifdescribe(process.platform === 'darwin')('isHiddenInMissionControl state', () => {
|
||||||
|
|||||||
@@ -283,6 +283,29 @@ describe('setDisplayMediaRequestHandler', () => {
|
|||||||
expect(ok).to.be.true(message);
|
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 () => {
|
ifit(!(process.platform === 'darwin' && process.arch === 'x64'))('can supply a screen response to preferCurrentTab', async () => {
|
||||||
const ses = session.fromPartition('' + Math.random());
|
const ses = session.fromPartition('' + Math.random());
|
||||||
let requestHandlerCalled = false;
|
let requestHandlerCalled = false;
|
||||||
|
|||||||
@@ -827,277 +827,383 @@ describe('session module', () => {
|
|||||||
fs.unlinkSync(downloadFilePath);
|
fs.unlinkSync(downloadFilePath);
|
||||||
};
|
};
|
||||||
|
|
||||||
it('can download using session.downloadURL', (done) => {
|
describe('session.downloadURL', () => {
|
||||||
session.defaultSession.once('will-download', function (e, item) {
|
it('can perform a download', (done) => {
|
||||||
item.savePath = downloadFilePath;
|
session.defaultSession.once('will-download', function (e, item) {
|
||||||
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) {
|
|
||||||
item.savePath = downloadFilePath;
|
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) {
|
item.on('done', function (e, state) {
|
||||||
try {
|
try {
|
||||||
expect(state).to.equal('interrupted');
|
assertDownload(state, item);
|
||||||
done();
|
done();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
done(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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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', () => {
|
describe('chrome://media-internals', () => {
|
||||||
it('loads the page successfully', async () => {
|
it('loads the page successfully', async () => {
|
||||||
const w = new BrowserWindow({ show: false });
|
const w = new BrowserWindow({ show: false });
|
||||||
w.loadURL('chrome://media-internals');
|
await w.loadURL('chrome://media-internals');
|
||||||
const pageExists = await w.webContents.executeJavaScript(
|
const pageExists = await w.webContents.executeJavaScript(
|
||||||
"window.hasOwnProperty('chrome') && window.chrome.hasOwnProperty('send')"
|
"window.hasOwnProperty('chrome') && window.chrome.hasOwnProperty('send')"
|
||||||
);
|
);
|
||||||
@@ -2064,7 +2086,7 @@ describe('chromium features', () => {
|
|||||||
describe('chrome://webrtc-internals', () => {
|
describe('chrome://webrtc-internals', () => {
|
||||||
it('loads the page successfully', async () => {
|
it('loads the page successfully', async () => {
|
||||||
const w = new BrowserWindow({ show: false });
|
const w = new BrowserWindow({ show: false });
|
||||||
w.loadURL('chrome://webrtc-internals');
|
await w.loadURL('chrome://webrtc-internals');
|
||||||
const pageExists = await w.webContents.executeJavaScript(
|
const pageExists = await w.webContents.executeJavaScript(
|
||||||
"window.hasOwnProperty('chrome') && window.chrome.hasOwnProperty('send')"
|
"window.hasOwnProperty('chrome') && window.chrome.hasOwnProperty('send')"
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -842,15 +842,14 @@ describe('chrome extensions', () => {
|
|||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
customSession = session.fromPartition(`persist:${uuid.v4()}`);
|
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(() => {
|
beforeEach(() => {
|
||||||
w = new BrowserWindow({
|
w = new BrowserWindow({
|
||||||
show: false,
|
show: false,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
session: customSession,
|
session: customSession
|
||||||
nodeIntegration: true
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -913,27 +912,55 @@ describe('chrome extensions', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('get', async () => {
|
describe('get', () => {
|
||||||
await w.loadURL(url);
|
it('returns tab properties', async () => {
|
||||||
|
await w.loadURL(url);
|
||||||
|
|
||||||
const message = { method: 'get' };
|
const message = { method: 'get' };
|
||||||
w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
|
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);
|
const response = JSON.parse(responseString);
|
||||||
expect(response).to.have.property('active').that.is.a('boolean');
|
expect(response).to.have.property('url').that.is.a('string');
|
||||||
expect(response).to.have.property('autoDiscardable').that.is.a('boolean');
|
expect(response).to.have.property('title').that.is.a('string');
|
||||||
expect(response).to.have.property('discarded').that.is.a('boolean');
|
expect(response).to.have.property('active').that.is.a('boolean');
|
||||||
expect(response).to.have.property('groupId').that.is.a('number');
|
expect(response).to.have.property('autoDiscardable').that.is.a('boolean');
|
||||||
expect(response).to.have.property('highlighted').that.is.a('boolean');
|
expect(response).to.have.property('discarded').that.is.a('boolean');
|
||||||
expect(response).to.have.property('id').that.is.a('number');
|
expect(response).to.have.property('groupId').that.is.a('number');
|
||||||
expect(response).to.have.property('incognito').that.is.a('boolean');
|
expect(response).to.have.property('highlighted').that.is.a('boolean');
|
||||||
expect(response).to.have.property('index').that.is.a('number');
|
expect(response).to.have.property('id').that.is.a('number');
|
||||||
expect(response).to.have.property('pinned').that.is.a('boolean');
|
expect(response).to.have.property('incognito').that.is.a('boolean');
|
||||||
expect(response).to.have.property('selected').that.is.a('boolean');
|
expect(response).to.have.property('index').that.is.a('number');
|
||||||
expect(response).to.have.property('url').that.is.a('string');
|
expect(response).to.have.property('pinned').that.is.a('boolean');
|
||||||
expect(response).to.have.property('windowId').that.is.a('number');
|
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 () => {
|
it('reload', async () => {
|
||||||
@@ -960,6 +987,19 @@ describe('chrome extensions', () => {
|
|||||||
const [,, responseString] = await once(w.webContents, 'console-message');
|
const [,, responseString] = await once(w.webContents, 'console-message');
|
||||||
const response = JSON.parse(responseString);
|
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');
|
expect(response).to.have.property('mutedInfo').that.is.a('object');
|
||||||
const { mutedInfo } = response;
|
const { mutedInfo } = response;
|
||||||
expect(mutedInfo).to.deep.eq({
|
expect(mutedInfo).to.deep.eq({
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "tabs-api-async",
|
"name": "api-async",
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
"run_at": "document_start"
|
"run_at": "document_start"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"permissions": ["tabs"],
|
||||||
"background": {
|
"background": {
|
||||||
"service_worker": "background.js"
|
"service_worker": "background.js"
|
||||||
},
|
},
|
||||||
6
spec/fixtures/extensions/chrome-tabs/no-privileges/background.js
vendored
Normal file
6
spec/fixtures/extensions/chrome-tabs/no-privileges/background.js
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/* global chrome */
|
||||||
|
|
||||||
|
chrome.runtime.onMessage.addListener((_request, sender, sendResponse) => {
|
||||||
|
chrome.tabs.get(sender.tab.id).then(sendResponse);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
11
spec/fixtures/extensions/chrome-tabs/no-privileges/main.js
vendored
Normal file
11
spec/fixtures/extensions/chrome-tabs/no-privileges/main.js
vendored
Normal 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);
|
||||||
19
spec/fixtures/extensions/chrome-tabs/no-privileges/manifest.json
vendored
Normal file
19
spec/fixtures/extensions/chrome-tabs/no-privileges/manifest.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "no-privileges",
|
||||||
|
"version": "1.0",
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"matches": [
|
||||||
|
"<all_urls>"
|
||||||
|
],
|
||||||
|
"js": [
|
||||||
|
"main.js"
|
||||||
|
],
|
||||||
|
"run_at": "document_start"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"background": {
|
||||||
|
"service_worker": "background.js"
|
||||||
|
},
|
||||||
|
"manifest_version": 3
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user