Compare commits

..

30 Commits

Author SHA1 Message Date
Electron Bot
323de2fbe7 Bump v13.0.0-beta.27 2021-05-17 06:31:57 -07:00
trop[bot]
cd673a3697 docs: --force-fieldtrials was h2 rather than h3 (#29181)
All the other argument headers were h3 (`###`) but `--force-fieldtrials` was h2 (`##`) for some reason.
I changed it to make it consistent with the others.

Co-authored-by: Noelle Leigh <5957867+noelleleigh@users.noreply.github.com>
2021-05-16 18:15:39 -07:00
trop[bot]
0097a370e3 fix: remove background color hack in vibrancy (#29165)
Co-authored-by: Cheng Zhao <zcbenz@gmail.com>
2021-05-14 18:30:27 -07:00
trop[bot]
692c510867 fix: illegal access errors with nodeIntegrationInSubFrames (#29170) 2021-05-14 17:43:18 +02:00
trop[bot]
e6f15c4380 fix: ensure extensions w/o a background page have file access (#29171)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2021-05-14 17:42:55 +02:00
Electron Bot
cce8dda92f Bump v13.0.0-beta.26 2021-05-13 06:32:01 -07:00
trop[bot]
034f750c46 Create README.md (#29149)
Co-authored-by: Ondreas <74183220+OndreasCZ@users.noreply.github.com>
2021-05-12 23:40:50 -07:00
Erick Zhao
120a8acfd0 docs: rework sandbox guide (#29104)
Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
Co-authored-by: Biru Mohanathas <birunthan@mohanathas.com>
Co-authored-by: Jeremy Rose <nornagon@nornagon.net>

Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
Co-authored-by: Biru Mohanathas <birunthan@mohanathas.com>
Co-authored-by: Jeremy Rose <nornagon@nornagon.net>
2021-05-13 11:08:01 +09:00
trop[bot]
01992fd65c chore: remove no-op EnableWebComponentsV0 feature (#29127)
Co-authored-by: Jeremy Rose <jeremya@chromium.org>
2021-05-13 11:07:09 +09:00
trop[bot]
af6a5e6c30 fix: Menu.setApplicationMenu can return a useless array 29088 (#29129)
Co-authored-by: tabishmahfuz1 <tabish.mahfuz1@gmail.com>
2021-05-12 17:35:23 -07:00
trop[bot]
caac2e8fc7 fix: prevent crash on web-contents creation when error is thrown (#29106)
* fix: prevent crash when error occurs during event emitter CallMethod

* wip: emit error event within trycatch

* fix: handle uncaught exceptions within node on web_contents init

* fix: create gin_helper::CallMethodCatchException

* test: add web-contents create crash to test cases

* test: clean up test data for web-contents crash

Co-authored-by: Jeremy Rose <jeremya@chromium.org>

* fix: convert CatchException to WebContents static helper method

* fix: restore try_catch to callsite

Co-authored-by: VerteDinde <keeleymhammond@gmail.com>
Co-authored-by: VerteDinde <khammond@slack-corp.com>
Co-authored-by: Keeley Hammond <vertedinde@electronjs.org>
Co-authored-by: Jeremy Rose <jeremya@chromium.org>
2021-05-12 12:03:34 -07:00
Jeremy Rose
9a4a049498 chore: cherry-pick 8089dbfc616f from chromium (#29109)
* chore: cherry-pick 8089dbfc616f from chromium

* resolve conflicts
2021-05-12 12:03:09 -07:00
trop[bot]
68059d69a5 build: merge double space in SHASUM validation logic (#29120)
Co-authored-by: Samuel Attard <sam@electronjs.org>
2021-05-12 01:53:51 -07:00
Electron Bot
0f7334a77e Bump v13.0.0-beta.25 2021-05-11 18:10:53 -07:00
Electron Bot
eee8bee71b Revert "Bump v13.0.0-beta.25"
This reverts commit 2795185d46.
2021-05-11 17:19:54 -07:00
Electron Bot
2795185d46 Bump v13.0.0-beta.25 2021-05-11 13:59:38 -07:00
trop[bot]
877f096d6b build: offload hash checking logic to lambda worker during release (#29105)
Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
2021-05-11 13:57:32 -07:00
trop[bot]
b33bb3a860 fix: update NSView radii on fullscreen transition (#29099)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2021-05-11 16:44:18 +02:00
Milan Burda
86f4126051 fix: webFrame spell checker APIs crashing in sandboxed renderers (#29053) (#29087) 2021-05-10 16:42:30 -07:00
Electron Bot
f895162234 Bump v13.0.0-beta.24 2021-05-10 12:16:53 -07:00
Electron Bot
05e41f89b4 chore: bump chromium to 91.0.4472.38 (13-x-y) (#29045) 2021-05-07 09:29:33 -07:00
Electron Bot
d7378f953a Bump v13.0.0-beta.23 2021-05-07 00:28:38 -07:00
trop[bot]
ed0b654fee chore: cherry-pick 7abc7e45b2 from node (#29048)
Backports: https://github.com/nodejs/node/pull/38506

Co-authored-by: Fedor Indutny <fedor@indutny.com>
2021-05-07 00:19:03 -07:00
Electron Bot
c1c8cbf995 Bump v13.0.0-beta.22 2021-05-06 06:31:26 -07:00
trop[bot]
ab3aa3581a fix: drag region BrowserView calculations on macOS (#29017)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2021-05-06 20:29:06 +09:00
trop[bot]
20a43d9a72 fix: <webview> focus / blur events don't work with contextIsolation enabled (#29025)
Co-authored-by: Milan Burda <milan.burda@gmail.com>
2021-05-06 20:25:18 +09:00
trop[bot]
27d04084c2 docs: menu must be added on whenReady (#29043)
* Add that that menu must be added on whenReady

When an application menu is added before 'whenReady' all items seem to work except 'recent documents'

This causes the issue listed here: https://github.com/electron/electron/issues/17388

* Make example more complete

* Remove semicolons

* Update docs/tutorial/recent-documents.md

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

Co-authored-by: Matthijs Groen <matthijs.groen@gmail.com>
Co-authored-by: Erick Zhao <erick@hotmail.ca>
2021-05-06 20:23:30 +09:00
trop[bot]
46649965c9 spec: attempt to fix flaky nativeTheme spec (#29036)
Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
2021-05-05 21:49:48 -07:00
trop[bot]
bea84969d6 docs: link to BrowserView from webview page (#29008)
* docs: Link to `BrowserView` from `webview` page

* fix relative link

Co-authored-by: Hamish Macpherson <hamstu@gmail.com>
2021-05-05 11:30:52 -07:00
trop[bot]
ae67ec24f3 refactor: use "as const" for constant mappings (#29000)
Co-authored-by: Milan Burda <milan.burda@gmail.com>
2021-05-05 15:59:47 +09:00
59 changed files with 1088 additions and 634 deletions

2
DEPS
View File

@@ -14,7 +14,7 @@ gclient_gn_args = [
vars = {
'chromium_version':
'91.0.4472.33',
'91.0.4472.38',
'node_version':
'v14.16.0',
'nan_version':

View File

@@ -1 +1 @@
13.0.0-beta.21
13.0.0-beta.27

View File

@@ -5,7 +5,7 @@
[![devDependency Status](https://david-dm.org/electron/electron/dev-status.svg)](https://david-dm.org/electron/electron?type=dev)
[![Electron Discord Invite](https://img.shields.io/discord/745037351163527189?color=%237289DA&label=chat&logo=discord&logoColor=white)](https://discord.com/invite/electron)
:memo: Available Translations: 🇨🇳 🇹🇼 🇧🇷 🇪🇸 🇰🇷 🇯🇵 🇷🇺 🇫🇷 🇹🇭 🇳🇱 🇹🇷 🇮🇩 🇺🇦 🇨🇿 🇮🇹 🇵🇱.
:memo: Available Translations: 🇨🇳 🇧🇷 🇪🇸 🇯🇵 🇷🇺 🇫🇷 🇺🇸 🇩🇪.
View these docs in other languages at [electron/i18n](https://github.com/electron/i18n/tree/master/content/).
The Electron framework lets you write cross-platform desktop applications

View File

@@ -59,6 +59,7 @@ an issue:
* [Using Native Node.js Modules](tutorial/using-native-node-modules.md)
* [Performance Strategies](tutorial/performance.md)
* [Security Strategies](tutorial/security.md)
* [Process Sandboxing](tutorial/sandbox.md)
* [Accessibility](tutorial/accessibility.md)
* [Manually Enabling Accessibility Features](tutorial/accessibility.md#manually-enabling-accessibility-features)
* [Testing and Debugging](tutorial/application-debugging.md)

View File

@@ -238,7 +238,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
window shadow and window animations. Default is `true`.
* `vibrancy` String (optional) - Add a type of vibrancy effect to the window, only on
macOS. Can be `appearance-based`, `light`, `dark`, `titlebar`, `selection`,
`menu`, `popover`, `sidebar`, `medium-light`, `ultra-dark`, `header`, `sheet`, `window`, `hud`, `fullscreen-ui`, `tooltip`, `content`, `under-window`, or `under-page`. Please note that using `frame: false` in combination with a vibrancy value requires that you use a non-default `titleBarStyle` as well. Also note that `appearance-based`, `light`, `dark`, `medium-light`, and `ultra-dark` are deprecated and have been removed in macOS Catalina (10.15).
`menu`, `popover`, `sidebar`, `medium-light`, `ultra-dark`, `header`, `sheet`, `window`, `hud`, `fullscreen-ui`, `tooltip`, `content`, `under-window`, or `under-page`. Please note that `appearance-based`, `light`, `dark`, `medium-light`, and `ultra-dark` are deprecated and have been removed in macOS Catalina (10.15).
* `zoomToPageWidth` Boolean (optional) - Controls the behavior on macOS when
option-clicking the green stoplight button on the toolbar or by clicking the
Window > Zoom menu item. If `true`, the window will grow to the preferred
@@ -272,7 +272,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
associated with the window, making it compatible with the Chromium
OS-level sandbox and disabling the Node.js engine. This is not the same as
the `nodeIntegration` option and the APIs available to the preload script
are more limited. Read more about the option [here](sandbox-option.md).
are more limited. Read more about the option [here](../tutorial/sandbox.md).
* `enableRemoteModule` Boolean (optional) - Whether to enable the [`remote`](remote.md) module.
Default is `false`.
* `session` [Session](session.md#class-session) (optional) - Sets the session used by the

View File

@@ -80,7 +80,7 @@ This switch can not be used in `app.commandLine.appendSwitch` since it is parsed
earlier than user's app is loaded, but you can set the `ELECTRON_ENABLE_LOGGING`
environment variable to achieve the same effect.
## --force-fieldtrials=`trials`
### --force-fieldtrials=`trials`
Field trials to be forcefully enabled or disabled.
@@ -142,7 +142,8 @@ proxy server flags that are passed.
### --no-sandbox
Disables Chromium sandbox, which is now enabled by default.
Disables the Chromium [sandbox](https://www.chromium.org/developers/design-documents/sandbox).
Forces renderer process and Chromium helper processes to run un-sandboxed.
Should only be used for testing.
### --proxy-bypass-list=`hosts`

View File

@@ -1,182 +0,0 @@
# `sandbox` Option
> Create a browser window with a sandboxed renderer. With this
option enabled, the renderer must communicate via IPC to the main process in order to access node APIs.
One of the key security features of Chromium is that all blink rendering/JavaScript
code is executed within a sandbox. This sandbox uses OS-specific features to ensure
that exploits in the renderer process cannot harm the system.
In other words, when the sandbox is enabled, the renderers can only make changes
to the system by delegating tasks to the main process via IPC.
[Here's](https://www.chromium.org/developers/design-documents/sandbox) more
information about the sandbox.
Since a major feature in Electron is the ability to run Node.js in the
renderer process (making it easier to develop desktop applications using web
technologies), the sandbox is disabled by electron. This is because
most Node.js APIs require system access. `require()` for example, is not
possible without file system permissions, which are not available in a sandboxed
environment.
Usually this is not a problem for desktop applications since the code is always
trusted, but it makes Electron less secure than Chromium for displaying
untrusted web content. For applications that require more security, the
`sandbox` flag will force Electron to spawn a classic Chromium renderer that is
compatible with the sandbox.
A sandboxed renderer doesn't have a Node.js environment running and doesn't
expose Node.js JavaScript APIs to client code. The only exception is the preload script,
which has access to a subset of the Electron renderer API.
Another difference is that sandboxed renderers don't modify any of the default
JavaScript APIs. Consequently, some APIs such as `window.open` will work as they
do in Chromium (i.e. they do not return a [`BrowserWindowProxy`](browser-window-proxy.md)).
## Example
To create a sandboxed window, pass `sandbox: true` to `webPreferences`:
```js
let win
app.whenReady().then(() => {
win = new BrowserWindow({
webPreferences: {
sandbox: true
}
})
win.loadURL('http://google.com')
})
```
In the above code the [`BrowserWindow`](browser-window.md) that was created has Node.js disabled and can communicate
only via IPC. The use of this option stops Electron from creating a Node.js runtime in the renderer. Also,
within this new window `window.open` follows the native behavior (by default Electron creates a [`BrowserWindow`](browser-window.md)
and returns a proxy to this via `window.open`).
[`app.enableSandbox`](app.md#appenablesandbox) can be used to force `sandbox: true` for all `BrowserWindow` instances.
```js
let win
app.enableSandbox()
app.whenReady().then(() => {
// no need to pass `sandbox: true` since `app.enableSandbox()` was called.
win = new BrowserWindow()
win.loadURL('http://google.com')
})
```
## Preload
An app can make customizations to sandboxed renderers using a preload script.
Here's an example:
```js
let win
app.whenReady().then(() => {
win = new BrowserWindow({
webPreferences: {
sandbox: true,
preload: path.join(app.getAppPath(), 'preload.js')
}
})
win.loadURL('http://google.com')
})
```
and preload.js:
```js
// This file is loaded whenever a javascript context is created. It runs in a
// private scope that can access a subset of Electron renderer APIs. Without
// contextIsolation enabled, it's possible to accidentally leak privileged
// globals like ipcRenderer to web content.
const { ipcRenderer } = require('electron')
const defaultWindowOpen = window.open
window.open = function customWindowOpen (url, ...args) {
ipcRenderer.send('report-window-open', location.origin, url, args)
return defaultWindowOpen(url + '?from_electron=1', ...args)
}
```
Important things to notice in the preload script:
- Even though the sandboxed renderer doesn't have Node.js running, it still has
access to a limited node-like environment: `Buffer`, `process`, `setImmediate`,
`clearImmediate` and `require` are available.
- The preload script must be contained in a single script, but it is possible to have
complex preload code composed with multiple modules by using a tool like
webpack or browserify. An example of using browserify is below.
To create a browserify bundle and use it as a preload script, something like
the following should be used:
```sh
browserify preload/index.js \
-x electron \
--insert-global-vars=__filename,__dirname -o preload.js
```
The `-x` flag should be used with any required module that is already exposed in
the preload scope, and tells browserify to use the enclosing `require` function
for it. `--insert-global-vars` will ensure that `process`, `Buffer` and
`setImmediate` are also taken from the enclosing scope(normally browserify
injects code for those).
Currently the `require` function provided in the preload scope exposes the
following modules:
- `electron`
- `crashReporter`
- `desktopCapturer`
- `ipcRenderer`
- `nativeImage`
- `webFrame`
- `events`
- `timers`
- `url`
More may be added as needed to expose more Electron APIs in the sandbox.
## Rendering untrusted content
Rendering untrusted content in Electron is still somewhat uncharted territory,
though some apps are finding success (e.g. Beaker Browser). Our goal is to get
as close to Chrome as we can in terms of the security of sandboxed content, but
ultimately we will always be behind due to a few fundamental issues:
1. We do not have the dedicated resources or expertise that Chromium has to
apply to the security of its product. We do our best to make use of what we
have, to inherit everything we can from Chromium, and to respond quickly to
security issues, but Electron cannot be as secure as Chromium without the
resources that Chromium is able to dedicate.
2. Some security features in Chrome (such as Safe Browsing and Certificate
Transparency) require a centralized authority and dedicated servers, both of
which run counter to the goals of the Electron project. As such, we disable
those features in Electron, at the cost of the associated security they
would otherwise bring.
3. There is only one Chromium, whereas there are many thousands of apps built
on Electron, all of which behave slightly differently. Accounting for those
differences can yield a huge possibility space, and make it challenging to
ensure the security of the platform in unusual use cases.
4. We can't push security updates to users directly, so we rely on app vendors
to upgrade the version of Electron underlying their app in order for
security updates to reach users.
Here are some things to consider before rendering untrusted content:
- A preload script can accidentally leak privileged APIs to untrusted code,
unless [`contextIsolation`](../tutorial/security.md#3-enable-context-isolation-for-remote-content)
is also enabled.
- Some bug in the V8 engine may allow malicious code to access the renderer
preload APIs, effectively granting full access to the system through the
`remote` module. Therefore, it is highly recommended to [disable the `remote`
module](../tutorial/security.md#15-disable-the-remote-module).
If disabling is not feasible, you should selectively [filter the `remote`
module](../tutorial/security.md#16-filter-the-remote-module).
- While we make our best effort to backport Chromium security fixes to older
versions of Electron, we do not make a guarantee that every fix will be
backported. Your best chance at staying secure is to be on the latest stable
version of Electron.

View File

@@ -5,7 +5,7 @@
Electron's `webview` tag is based on [Chromium's `webview`][chrome-webview], which
is undergoing dramatic architectural changes. This impacts the stability of `webviews`,
including rendering, navigation, and event routing. We currently recommend to not
use the `webview` tag and to consider alternatives, like `iframe`, Electron's `BrowserView`,
use the `webview` tag and to consider alternatives, like `iframe`, [Electron's `BrowserView`](browser-view.md),
or an architecture that avoids embedded content altogether.
## Enabling

View File

@@ -83,6 +83,22 @@ following code snippet to your menu template:
}
```
Make sure the application menu is added after the [`'ready'`](../api/app.md#event-ready)
event and not before, or the menu item will be disabled:
```javascript
const { app, Menu } = require('electron')
const template = [
// Menu template here
]
const menu = Menu.buildFromTemplate(template)
app.whenReady().then(() => {
Menu.setApplicationMenu(menu)
})
```
![macOS Recent Documents menu item][menu-item-image]
When a file is requested from the recent documents menu, the `open-file` event

169
docs/tutorial/sandbox.md Normal file
View File

@@ -0,0 +1,169 @@
# Process Sandboxing
One key security feature in Chromium is that processes can be executed within a sandbox.
The sandbox limits the harm that malicious code can cause by limiting access to most
system resources — sandboxed processes can only freely use CPU cycles and memory.
In order to perform operations requiring additional privilege, sandboxed processes
use dedicated communication channels to delegate tasks to more privileged processes.
In Chromium, sandboxing is applied to most processes other than the main process.
This includes renderer processes, as well as utility processes such as the audio service,
the GPU service and the network service.
See Chromium's [Sandbox design document][sandbox] for more information.
## Electron's sandboxing policies
Electron comes with a mixed sandbox environment, meaning sandboxed processes can run
alongside privileged ones. By default, renderer processes are not sandboxed, but
utility processes are. Note that as in Chromium, the main (browser) process is
privileged and cannot be sandboxed.
Historically, this mixed sandbox approach was established because having Node.js available
in the renderer is an extremely powerful tool for app developers. Unfortunately, this
feature is also an equally massive security vulnerability.
Theoretically, unsandboxed renderers are not a problem for desktop applications that
only display trusted code, but they make Electron less secure than Chromium for
displaying untrusted web content. However, even purportedly trusted code may be
dangerous — there are countless attack vectors that malicious actors can use, from
cross-site scripting to content injection to man-in-the-middle attacks on remotely loaded
websites, just to name a few. For this reason, we recommend enabling renderer sandboxing
for the vast majority of cases under an abundance of caution.
<!--TODO: update this guide when #28466 is either solved or closed -->
Note that there is an active discussion in the issue tracker to enable renderer sandboxing
by default. See [#28466][issue-28466]) for details.
## Sandbox behaviour in Electron
Sandboxed processes in Electron behave _mostly_ in the same way as Chromium's do, but
Electron has a few additional concepts to consider because it interfaces with Node.js.
### Renderer processes
When renderer processes in Electron are sandboxed, they behave in the same way as a
regular Chrome renderer would. A sandboxed renderer won't have a Node.js
environment initialized.
<!-- TODO(erickzhao): when we have a solid guide for IPC, link it here -->
Therefore, when the sandbox is enabled, renderer processes can only perform privileged
tasks (such as interacting with the filesystem, making changes to the system, or spawning
subprocesses) by delegating these tasks to the main process via inter-process
communication (IPC).
### Preload scripts
In order to allow renderer processes to communicate with the main process, preload
scripts attached to sandboxed renderers will still have a polyfilled subset of Node.js
APIs available. A `require` function similar to Node's `require` module is exposed,
but can only import a subset of Electron and Node's built-in modules:
* `electron` (only renderer process modules)
* [`events`](https://nodejs.org/api/events.html)
* [`timers`](https://nodejs.org/api/timers.html)
* [`url`](https://nodejs.org/api/url.html)
In addition, the preload script also polyfills certain Node.js primitives as globals:
* [`Buffer`](https://nodejs.org/api/Buffer.html)
* [`process`](../api/process.md)
* [`clearImmediate`](https://nodejs.org/api/timers.html#timers_clearimmediate_immediate)
* [`setImmediate`](https://nodejs.org/api/timers.html#timers_setimmediate_callback_args)
Because the `require` function is a polyfill with limited functionality, you will not be
able to use [CommonJS modules][commonjs] to separate your preload script into multiple
files. If you need to split your preload code, use a bundler such as [webpack][webpack]
or [Parcel][parcel].
Note that because the environment presented to the `preload` script is substantially
more privileged than that of a sandboxed renderer, it is still possible to leak
privileged APIs to untrusted code running in the renderer process unless
[`contextIsolation`][contextIsolation] is enabled.
## Configuring the sandbox
### Enabling the sandbox for a single process
In Electron, renderer sandboxing can be enabled on a per-process basis with
the `sandbox: true` preference in the [`BrowserWindow`][browser-window] constructor.
```js
// main.js
app.whenReady().then(() => {
const win = new BrowserWindow({
webPreferences: {
sandbox: true
}
})
win.loadURL('https://google.com')
})
```
### Enabling the sandbox globally
If you want to force sandboxing for all renderers, you can also use the
[`app.enableSandbox`][enable-sandbox] API. Note that this API has to be called before the
app's `ready` event.
```js
// main.js
app.enableSandbox()
app.whenReady().then(() => {
// no need to pass `sandbox: true` since `app.enableSandbox()` was called.
const win = new BrowserWindow()
win.loadURL('https://google.com')
})
```
### Disabling Chromium's sandbox (testing only)
You can also disable Chromium's sandbox entirely with the [`--no-sandbox`][no-sandbox]
CLI flag, which will disable the sandbox for all processes (including utility processes).
We highly recommend that you only use this flag for testing purposes, and **never**
in production.
Note that the `sandbox: true` option will still disable the renderer's Node.js
environment.
## A note on rendering untrusted content
Rendering untrusted content in Electron is still somewhat uncharted territory,
though some apps are finding success (e.g. [Beaker Browser][beaker]).
Our goal is to get as close to Chrome as we can in terms of the security of
sandboxed content, but ultimately we will always be behind due to a few fundamental
issues:
1. We do not have the dedicated resources or expertise that Chromium has to
apply to the security of its product. We do our best to make use of what we
have, to inherit everything we can from Chromium, and to respond quickly to
security issues, but Electron cannot be as secure as Chromium without the
resources that Chromium is able to dedicate.
2. Some security features in Chrome (such as Safe Browsing and Certificate
Transparency) require a centralized authority and dedicated servers, both of
which run counter to the goals of the Electron project. As such, we disable
those features in Electron, at the cost of the associated security they
would otherwise bring.
3. There is only one Chromium, whereas there are many thousands of apps built
on Electron, all of which behave slightly differently. Accounting for those
differences can yield a huge possibility space, and make it challenging to
ensure the security of the platform in unusual use cases.
4. We can't push security updates to users directly, so we rely on app vendors
to upgrade the version of Electron underlying their app in order for
security updates to reach users.
While we make our best effort to backport Chromium security fixes to older
versions of Electron, we do not make a guarantee that every fix will be
backported. Your best chance at staying secure is to be on the latest stable
version of Electron.
[sandbox]: https://chromium.googlesource.com/chromium/src/+/master/docs/design/sandbox.md
[issue-28466]: https://github.com/electron/electron/issues/28466
[browser-window]: ../api/browser-window.md
[enable-sandbox]: ../api/app.md#appenablesandbox
[no-sandbox]: ../api/command-line-switches.md#--no-sandbox
[commonjs]: https://nodejs.org/api/modules.html#modules_modules_commonjs_modules
[webpack]: https://webpack.js.org/
[parcel]: https://parceljs.org/
[context-isolation]: ./context-isolation.md
[beaker]: https://github.com/beakerbrowser/beaker

View File

@@ -692,5 +692,5 @@ which potential security issues are not as widely known.
[window-open-handler]: ../api/web-contents.md#contentssetwindowopenhandlerhandler
[will-navigate]: ../api/web-contents.md#event-will-navigate
[open-external]: ../api/shell.md#shellopenexternalurl-options
[sandbox]: ../api/sandbox-option.md
[sandbox]: ../tutorial/sandbox.md
[responsible-disclosure]: https://en.wikipedia.org/wiki/Responsible_disclosure

View File

@@ -43,7 +43,6 @@ auto_filenames = {
"docs/api/process.md",
"docs/api/protocol.md",
"docs/api/remote.md",
"docs/api/sandbox-option.md",
"docs/api/screen.md",
"docs/api/service-workers.md",
"docs/api/session.md",

View File

@@ -2,10 +2,10 @@ import { app, BrowserWindow } from 'electron/main';
import type { OpenDialogOptions, OpenDialogReturnValue, MessageBoxOptions, SaveDialogOptions, SaveDialogReturnValue, MessageBoxReturnValue, CertificateTrustDialogOptions } from 'electron/main';
const dialogBinding = process._linkedBinding('electron_browser_dialog');
const DialogType = {
OPEN: 'OPEN' as 'OPEN',
SAVE: 'SAVE' as 'SAVE'
};
enum DialogType {
OPEN = 'OPEN',
SAVE = 'SAVE'
}
enum SaveFileDialogProperties {
createDirectory = 1 << 0,
@@ -72,7 +72,7 @@ const setupSaveDialogProperties = (properties: (keyof typeof SaveFileDialogPrope
return dialogProperties;
};
const setupDialogProperties = (type: keyof typeof DialogType, properties: string[]): number => {
const setupDialogProperties = (type: DialogType, properties: string[]): number => {
if (type === DialogType.OPEN) {
return setupOpenDialogProperties(properties as (keyof typeof OpenFileDialogProperties)[]);
} else if (type === DialogType.SAVE) {

View File

@@ -177,7 +177,7 @@ Menu.setApplicationMenu = function (menu: MenuType) {
bindings.setApplicationMenu(menu);
} else {
const windows = BaseWindow.getAllWindows();
return windows.map(w => w.setMenu(menu));
windows.map(w => w.setMenu(menu));
}
};

View File

@@ -61,7 +61,7 @@ const PDFPageSizes: Record<string, ElectronInternal.MediaSize> = {
width_microns: 279400,
custom_display_name: 'Tabloid'
}
};
} as const;
// The minimum micron size Chromium accepts is that where:
// Per printing/units.h:
@@ -110,7 +110,7 @@ const defaultPrintingSetting = {
printerType: 2,
title: undefined as string | undefined,
url: undefined as string | undefined
};
} as const;
// JavaScript implementations of WebContents.
const binding = process._linkedBinding('electron_browser_web_contents');
@@ -192,7 +192,7 @@ WebContents.prototype.executeJavaScriptInIsolatedWorld = async function (worldId
let pendingPromise: Promise<any> | undefined;
WebContents.prototype.printToPDF = async function (options) {
const printSettings = {
const printSettings: Record<string, any> = {
...defaultPrintingSetting,
requestID: getNextId()
};

View File

@@ -1,4 +1,4 @@
export const webViewEvents: Record<string, string[]> = {
export const webViewEvents: Record<string, readonly string[]> = {
'load-commit': ['url', 'isMainFrame'],
'did-attach': [],
'did-finish-load': [],
@@ -33,4 +33,4 @@ export const webViewEvents: Record<string, string[]> = {
'found-in-page': ['result'],
'did-change-theme-color': ['themeColor'],
'update-target-url': ['url']
};
} as const;

View File

@@ -8,7 +8,7 @@ import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
const DEPRECATED_EVENTS: Record<string, string> = {
'page-title-updated': 'page-title-set'
};
} as const;
const dispatchEvent = function (
webView: WebViewImpl, eventName: string, eventKey: string, ...args: Array<any>

View File

@@ -162,7 +162,7 @@ export class WebViewImpl {
// Emits focus/blur events.
onFocusChange () {
const hasFocus = document.activeElement === this.webviewNode;
const hasFocus = this.webviewNode.ownerDocument.activeElement === this.webviewNode;
if (hasFocus !== this.hasFocus) {
this.hasFocus = hasFocus;
this.dispatchEvent(hasFocus ? 'focus' : 'blur');

View File

@@ -1,6 +1,6 @@
{
"name": "electron",
"version": "13.0.0-beta.21",
"version": "13.0.0-beta.27",
"repository": "https://github.com/electron/electron",
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
"devDependencies": {
@@ -64,7 +64,6 @@
"shx": "^0.3.2",
"standard-markdown": "^6.0.0",
"stream-json": "^1.7.1",
"sumchecker": "^2.0.2",
"tap-xunit": "^2.4.1",
"temp": "^0.8.3",
"timers-browserify": "1.4.2",

View File

@@ -107,3 +107,4 @@ fix_add_check_for_sandbox_then_result.patch
moves_background_color_setter_of_webview_to_blinks_webprefs_logic.patch
fix_expose_decrementcapturercount_in_web_contents_impl.patch
add_setter_for_browsermainloop_result_code.patch
cherry-pick-8089dbfc616f.patch

View File

@@ -21,10 +21,10 @@ index 08a3eb686360e7a53cca75437cbe5f4d15cb9a17..78d3287ef7a708f44bd8ef0290618ef7
&no_javascript_access);
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 77cd0e129e6d248dd0ef12ffd5e10d895ba86076..d04c66887760d0131576f01a8c843af28b556d8c 100644
index 5efcb22b41690bd178f99fe0745b3438ea043978..63294a8b9ff67cf08d627db17ec374f9e4eb0082 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -3608,6 +3608,14 @@ RenderFrameHostDelegate* WebContentsImpl::CreateNewWindow(
@@ -3609,6 +3609,14 @@ RenderFrameHostDelegate* WebContentsImpl::CreateNewWindow(
}
auto* new_contents_impl = new_contents.get();
@@ -39,7 +39,7 @@ index 77cd0e129e6d248dd0ef12ffd5e10d895ba86076..d04c66887760d0131576f01a8c843af2
new_contents_impl->GetController().SetSessionStorageNamespace(
partition_id, session_storage_namespace);
@@ -3650,12 +3658,6 @@ RenderFrameHostDelegate* WebContentsImpl::CreateNewWindow(
@@ -3651,12 +3659,6 @@ RenderFrameHostDelegate* WebContentsImpl::CreateNewWindow(
AddWebContentsDestructionObserver(new_contents_impl);
}
@@ -68,10 +68,10 @@ index 32d021e049b34a72ab6cf8d86ba0c46a0046d069..f94644667683872fdb2ce3c7a66aef99
// Operation result when the renderer asks the browser to create a new window.
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index c4d39b0ec9cdccbf57328ade2f93cac0462666aa..0f3a1e7e855baa873aedacd1d0d2bc78d5e36b55 100644
index 97c47faadb9224497ab9d4551db147e564fa2c55..4c1e26a579f77801941f0ad017e671ec27ac788a 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -560,6 +560,8 @@ bool ContentBrowserClient::CanCreateWindow(
@@ -567,6 +567,8 @@ bool ContentBrowserClient::CanCreateWindow(
const std::string& frame_name,
WindowOpenDisposition disposition,
const blink::mojom::WindowFeatures& features,
@@ -81,7 +81,7 @@ index c4d39b0ec9cdccbf57328ade2f93cac0462666aa..0f3a1e7e855baa873aedacd1d0d2bc78
bool opener_suppressed,
bool* no_javascript_access) {
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 3b672e4267cda9ff6f6b0f2c2b0a33765d81d3c5..1e74af3b625e80d92c8282d3d15567df7e08d336 100644
index aac67f9733e5a5f1adba22268337d2cc3319d386..2b95f110e48b3938f3a67759e89d7fe969da4933 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -160,6 +160,7 @@ class NetworkService;
@@ -92,7 +92,7 @@ index 3b672e4267cda9ff6f6b0f2c2b0a33765d81d3c5..1e74af3b625e80d92c8282d3d15567df
} // namespace network
namespace sandbox {
@@ -915,6 +916,8 @@ class CONTENT_EXPORT ContentBrowserClient {
@@ -923,6 +924,8 @@ class CONTENT_EXPORT ContentBrowserClient {
const std::string& frame_name,
WindowOpenDisposition disposition,
const blink::mojom::WindowFeatures& features,

View File

@@ -0,0 +1,59 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Francois Doray <fdoray@chromium.org>
Date: Mon, 26 Apr 2021 19:48:20 +0000
Subject: Cleanup the "NoDetachBelowInitialCapacity" feature.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Motivation: The feature is not used since 2019.
Bug: 847501
Change-Id: Ic6fde174ef8d742f7beb57d0c67ae2477dc96cc2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2845241
Reviewed-by: Etienne Pierre-Doray <etiennep@chromium.org>
Commit-Queue: François Doray <fdoray@chromium.org>
Cr-Commit-Position: refs/heads/master@{#876279}
diff --git a/base/task/task_features.cc b/base/task/task_features.cc
index b7452cf330b71ce76fd63281fafc0618f652e8c6..4e498738ffeea1513aa475c69f29844e8d327d22 100644
--- a/base/task/task_features.cc
+++ b/base/task/task_features.cc
@@ -11,9 +11,6 @@ namespace base {
const Feature kAllTasksUserBlocking{"AllTasksUserBlocking",
FEATURE_DISABLED_BY_DEFAULT};
-const Feature kNoDetachBelowInitialCapacity = {
- "NoDetachBelowInitialCapacity", base::FEATURE_DISABLED_BY_DEFAULT};
-
const Feature kMayBlockWithoutDelay = {"MayBlockWithoutDelay",
base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/base/task/task_features.h b/base/task/task_features.h
index 4e361b8e6ba92e4db0614e7ae0daf60b9942bfb1..c80499566e18819172a38d7c7eef3a0080eac3da 100644
--- a/base/task/task_features.h
+++ b/base/task/task_features.h
@@ -15,10 +15,6 @@ struct Feature;
extern const BASE_EXPORT Feature kAllTasksUserBlocking;
-// Under this feature, unused threads in ThreadGroup are only detached
-// if the total number of threads in the pool is above the initial capacity.
-extern const BASE_EXPORT Feature kNoDetachBelowInitialCapacity;
-
// Under this feature, workers blocked with MayBlock are replaced immediately
// instead of waiting for a threshold in the foreground thread group.
extern const BASE_EXPORT Feature kMayBlockWithoutDelay;
diff --git a/base/task/thread_pool/thread_group_impl.cc b/base/task/thread_pool/thread_group_impl.cc
index 0aa268df2eff3de9f018ce6643f8690131395f4d..4f5b6f39231ab9c82dcb69b400360ff35b90dd6b 100644
--- a/base/task/thread_pool/thread_group_impl.cc
+++ b/base/task/thread_pool/thread_group_impl.cc
@@ -713,8 +713,6 @@ bool ThreadGroupImpl::WorkerThreadDelegateImpl::CanCleanupLockRequired(
return !last_used_time.is_null() &&
subtle::TimeTicksNowIgnoringOverride() - last_used_time >=
outer_->after_start().suggested_reclaim_time &&
- (outer_->workers_.size() > outer_->after_start().initial_max_tasks ||
- !FeatureList::IsEnabled(kNoDetachBelowInitialCapacity)) &&
LIKELY(!outer_->worker_cleanup_disallowed_for_testing_);
}

View File

@@ -264,10 +264,10 @@ index c5c5a7b63b5b3b62a9517cbef3ae23ce57a3c89c..4f1b7e88d6d2ae89a60311c8aeb1fcee
void AddNewContents(content::WebContents* source,
std::unique_ptr<content::WebContents> new_contents,
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 1f72cfc963aa7c3b5a7ec6a80e16d5fccad59525..27c7e9c6b327d5f83ecaf235effa6a85d9f4af77 100644
index c4fcae4562a2c7d060e6928fc497cfee6393d0fe..d3521a5ed9fb44efa117a5ec63e0e48a73dd118a 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -3560,8 +3560,7 @@ RenderFrameHostDelegate* WebContentsImpl::CreateNewWindow(
@@ -3561,8 +3561,7 @@ RenderFrameHostDelegate* WebContentsImpl::CreateNewWindow(
if (delegate_ && delegate_->IsWebContentsCreationOverridden(
source_site_instance, params.window_container_type,

View File

@@ -15,7 +15,7 @@ the redraw locking mechanism, which fixes these issues. The electron issue
can be found at https://github.com/electron/electron/issues/1821
diff --git a/ui/views/win/hwnd_message_handler.cc b/ui/views/win/hwnd_message_handler.cc
index 8bcb03a45d2fac0ce6b074aec0950628c3dd5d5c..022333504e32bfaf03e8072e7c450d12390258c4 100644
index df245fc922024b3ac50cc9381d0645f65635959b..8bde0c6d99c6433bfb16aafe094c6a499f68f2be 100644
--- a/ui/views/win/hwnd_message_handler.cc
+++ b/ui/views/win/hwnd_message_handler.cc
@@ -305,6 +305,10 @@ constexpr int kSynthesizedMouseMessagesTimeDifference = 500;
@@ -37,7 +37,7 @@ index 8bcb03a45d2fac0ce6b074aec0950628c3dd5d5c..022333504e32bfaf03e8072e7c450d12
(!(GetWindowLong(hwnd_, GWL_STYLE) & WS_CAPTION) ||
!ui::win::IsAeroGlassEnabled())) {
if (should_lock_)
@@ -971,6 +976,10 @@ HWNDMessageHandler::RegisterUnadjustedMouseEvent() {
@@ -974,6 +979,10 @@ HWNDMessageHandler::RegisterUnadjustedMouseEvent() {
return scoped_enable;
}

View File

@@ -6,10 +6,10 @@ Subject: feat: enable setting aspect ratio to 0
Make SetAspectRatio accept 0 as valid input, which would reset to null.
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
index c8c26cfacffb6cbdc799eb8ca45d839bd1d8413a..4e93695ae1e43dcc095e74d2fac33f4ac3d36e6b 100644
index 6a804c0d36a96612a2c6d594fa800e9911289d5e..44b4fedbbb5990c3b4fbd6c556c8334db7b2bd5a 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
@@ -484,7 +484,7 @@ void DesktopWindowTreeHostWin::SetOpacity(float opacity) {
@@ -487,7 +487,7 @@ void DesktopWindowTreeHostWin::SetOpacity(float opacity) {
}
void DesktopWindowTreeHostWin::SetAspectRatio(const gfx::SizeF& aspect_ratio) {
@@ -19,10 +19,10 @@ index c8c26cfacffb6cbdc799eb8ca45d839bd1d8413a..4e93695ae1e43dcc095e74d2fac33f4a
aspect_ratio.height());
}
diff --git a/ui/views/win/hwnd_message_handler.cc b/ui/views/win/hwnd_message_handler.cc
index 022333504e32bfaf03e8072e7c450d12390258c4..08c62766349d6082cba543357d21cb3f039f42b3 100644
index 8bde0c6d99c6433bfb16aafe094c6a499f68f2be..552f027291a5b0e88a900c01c30afefde2c0928e 100644
--- a/ui/views/win/hwnd_message_handler.cc
+++ b/ui/views/win/hwnd_message_handler.cc
@@ -921,8 +921,11 @@ void HWNDMessageHandler::SetFullscreen(bool fullscreen) {
@@ -924,8 +924,11 @@ void HWNDMessageHandler::SetFullscreen(bool fullscreen) {
}
void HWNDMessageHandler::SetAspectRatio(float aspect_ratio) {

View File

@@ -8,7 +8,7 @@ we invoke it in order to expose contents.decrementCapturerCount([stayHidden, sta
to users. We should try to upstream this.
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index b32e156d3ed18f4753eca9d75a39f3ce990bc93f..0354de2c8e8482475a006a1ec3c2df5db8102d3d 100644
index d42008f7893bb36419b46e9e397c965deef957ea..17034e75d2ab5bd4e716e9c72277c77a53387808 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -394,6 +394,9 @@ class CONTENT_EXPORT WebContentsImpl : public WebContents,
@@ -21,7 +21,7 @@ index b32e156d3ed18f4753eca9d75a39f3ce990bc93f..0354de2c8e8482475a006a1ec3c2df5d
bool IsBeingCaptured() override;
bool IsBeingVisiblyCaptured() override;
bool IsAudioMuted() override;
@@ -1707,10 +1710,6 @@ class CONTENT_EXPORT WebContentsImpl : public WebContents,
@@ -1708,10 +1711,6 @@ class CONTENT_EXPORT WebContentsImpl : public WebContents,
// shown in the address bar), as opposed to one in for example a Prerender.
bool IsPrimaryFrameTree(const FrameTree& frame_tree) const;
@@ -33,7 +33,7 @@ index b32e156d3ed18f4753eca9d75a39f3ce990bc93f..0354de2c8e8482475a006a1ec3c2df5d
// Delegate for notifying our owner about stuff. Not owned by us.
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h
index 9d089057f886c7b6b10ded3c2b37f5580f3b67f7..517d5e18e064f23aebd3eed0e6c0a5e41a32c863 100644
index 2206586b2be0bca29ffe5ed227dcec67d2e56b27..05aec6c82bfba6b360cb4b4dd3298d2328f06f9e 100644
--- a/content/public/browser/web_contents.h
+++ b/content/public/browser/web_contents.h
@@ -570,6 +570,7 @@ class WebContents : public PageNavigator,

View File

@@ -13,10 +13,10 @@ This patch can be removed once app.allowRendererProcessReuse is forced
to true as then Chromiums assumptions around processes become correct.
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 337b1ee65a8573da45d08609cc8ab4df5d784f50..6af90dc03657fb628196283c8802490771e5e9b5 100644
index 86e4aa44938dcb578ad3d517500f96efc97a958f..14eadcb063b2b9d4734db3d6160f320ab26ffe04 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -3027,11 +3027,13 @@ bool WebContentsImpl::HandleMouseEvent(const blink::WebMouseEvent& event) {
@@ -3028,11 +3028,13 @@ bool WebContentsImpl::HandleMouseEvent(const blink::WebMouseEvent& event) {
WebContentsImpl* outermost = GetOutermostWebContents();
if (event.button == blink::WebPointerProperties::Button::kBack &&
outermost->GetController().CanGoBack()) {

View File

@@ -229,7 +229,7 @@ index 40c314243ae5aa8540ce6528273ee2f0b7f0aca1..dce5cf6cde036d35c1b78b8fa7686176
size_t GetRelatedActiveContentsCount() override;
bool RequiresDedicatedProcess() override;
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 0f3a1e7e855baa873aedacd1d0d2bc78d5e36b55..810fc333793bf3494c697328ec124e7de4a93dcb 100644
index 4c1e26a579f77801941f0ad017e671ec27ac788a..02bee8a66b277f3fe091caa9b88b1d848729f759 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -72,6 +72,21 @@
@@ -255,7 +255,7 @@ index 0f3a1e7e855baa873aedacd1d0d2bc78d5e36b55..810fc333793bf3494c697328ec124e7d
const MainFunctionParams& parameters) {
return nullptr;
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 1e74af3b625e80d92c8282d3d15567df7e08d336..3fbe401a6b98da5e67fc0fbc412de5c1ba416512 100644
index 2b95f110e48b3938f3a67759e89d7fe969da4933..5f8d59a53346a215b6283dea07b52f2c40153491 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -261,8 +261,45 @@ class CONTENT_EXPORT ContentBrowserClient {

View File

@@ -43,10 +43,10 @@ index 76bce6517555132e3f873c8294eb0ed5d2a3a87a..fc8916ac6dc76968e0cbd06877ffb80c
void RenderWidgetHostImpl::ShowContextMenuAtPoint(
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index d04c66887760d0131576f01a8c843af28b556d8c..1f72cfc963aa7c3b5a7ec6a80e16d5fccad59525 100644
index 63294a8b9ff67cf08d627db17ec374f9e4eb0082..c4fcae4562a2c7d060e6928fc497cfee6393d0fe 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -4151,6 +4151,11 @@ TextInputManager* WebContentsImpl::GetTextInputManager() {
@@ -4152,6 +4152,11 @@ TextInputManager* WebContentsImpl::GetTextInputManager() {
return text_input_manager_.get();
}
@@ -59,7 +59,7 @@ index d04c66887760d0131576f01a8c843af28b556d8c..1f72cfc963aa7c3b5a7ec6a80e16d5fc
RenderWidgetHostImpl* render_widget_host) {
return render_widget_host == GetMainFrame()->GetRenderWidgetHost();
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 39b702a18a199390fbc2256c27b0e01a333dd63e..b32e156d3ed18f4753eca9d75a39f3ce990bc93f 100644
index 4bcb2c44a7d338ef1107f465670ef46ab0ec14eb..d42008f7893bb36419b46e9e397c965deef957ea 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -953,6 +953,7 @@ class CONTENT_EXPORT WebContentsImpl : public WebContents,

View File

@@ -9,10 +9,10 @@ is needed for OSR.
Originally landed in https://github.com/electron/libchromiumcontent/pull/226.
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 27c7e9c6b327d5f83ecaf235effa6a85d9f4af77..337b1ee65a8573da45d08609cc8ab4df5d784f50 100644
index d3521a5ed9fb44efa117a5ec63e0e48a73dd118a..86e4aa44938dcb578ad3d517500f96efc97a958f 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -2739,6 +2739,12 @@ void WebContentsImpl::Init(const WebContents::CreateParams& params) {
@@ -2740,6 +2740,12 @@ void WebContentsImpl::Init(const WebContents::CreateParams& params) {
frame_tree_.Init(site_instance.get(), params.renderer_initiated_creation,
params.main_frame_name, type);
@@ -25,7 +25,7 @@ index 27c7e9c6b327d5f83ecaf235effa6a85d9f4af77..337b1ee65a8573da45d08609cc8ab4df
WebContentsViewDelegate* delegate =
GetContentClient()->browser()->GetWebContentsViewDelegate(this);
@@ -2749,6 +2755,7 @@ void WebContentsImpl::Init(const WebContents::CreateParams& params) {
@@ -2750,6 +2756,7 @@ void WebContentsImpl::Init(const WebContents::CreateParams& params) {
view_.reset(CreateWebContentsView(this, delegate,
&render_view_host_delegate_view_));
}
@@ -34,7 +34,7 @@ index 27c7e9c6b327d5f83ecaf235effa6a85d9f4af77..337b1ee65a8573da45d08609cc8ab4df
CHECK(view_.get());
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h
index fe5bc4ade5cdbe1332ae9134907f8c956529c48c..9d089057f886c7b6b10ded3c2b37f5580f3b67f7 100644
index 7b2836816e92e368018af4a663b27f23a94117af..2206586b2be0bca29ffe5ed227dcec67d2e56b27 100644
--- a/content/public/browser/web_contents.h
+++ b/content/public/browser/web_contents.h
@@ -88,8 +88,11 @@ class BrowserContext;

View File

@@ -34,3 +34,4 @@ build_add_mjs_support_to_js2c.patch
src_inline_asynccleanuphookhandle_in_headers.patch
fix_handle_new_tostring_behavior_in_v8_serdes_test.patch
fix_the_--harmony-weak-refs_has_been_removed_remove_from_specs.patch
node-api_faster_threadsafe_function.patch

View File

@@ -0,0 +1,262 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Fedor Indutny <fedor@indutny.com>
Date: Sat, 1 May 2021 11:26:46 -0700
Subject: node-api: faster threadsafe_function
Invoke threadsafe_function during the same tick and avoid marshalling
costs between threads and/or churning event loop if either:
1. There's a queued call already
2. `Push()` is called while the main thread was running
threadsafe_function
PR-URL: https://github.com/nodejs/node/pull/38506
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
diff --git a/src/node_api.cc b/src/node_api.cc
index f1a5265b6a7234dc754aedc86ecd3132f3d90b09..d1076b29aeb5133a0325d3e7ebd097d207e4f4a6 100644
--- a/src/node_api.cc
+++ b/src/node_api.cc
@@ -12,6 +12,7 @@
#include "tracing/traced_value.h"
#include "util-inl.h"
+#include <atomic>
#include <memory>
struct node_napi_env__ : public napi_env__ {
@@ -131,6 +132,7 @@ class ThreadSafeFunction : public node::AsyncResource {
*v8::String::Utf8Value(env_->isolate, name)),
thread_count(thread_count_),
is_closing(false),
+ dispatch_state(kDispatchIdle),
context(context_),
max_queue_size(max_queue_size_),
env(env_),
@@ -170,10 +172,8 @@ class ThreadSafeFunction : public node::AsyncResource {
return napi_closing;
}
} else {
- if (uv_async_send(&async) != 0) {
- return napi_generic_failure;
- }
queue.push(data);
+ Send();
return napi_ok;
}
}
@@ -205,9 +205,7 @@ class ThreadSafeFunction : public node::AsyncResource {
if (is_closing && max_queue_size > 0) {
cond->Signal(lock);
}
- if (uv_async_send(&async) != 0) {
- return napi_generic_failure;
- }
+ Send();
}
}
@@ -232,7 +230,6 @@ class ThreadSafeFunction : public node::AsyncResource {
cond = std::make_unique<node::ConditionVariable>();
}
if (max_queue_size == 0 || cond) {
- CHECK_EQ(0, uv_idle_init(loop, &idle));
return napi_ok;
}
@@ -257,21 +254,46 @@ class ThreadSafeFunction : public node::AsyncResource {
napi_status Unref() {
uv_unref(reinterpret_cast<uv_handle_t*>(&async));
- uv_unref(reinterpret_cast<uv_handle_t*>(&idle));
return napi_ok;
}
napi_status Ref() {
uv_ref(reinterpret_cast<uv_handle_t*>(&async));
- uv_ref(reinterpret_cast<uv_handle_t*>(&idle));
return napi_ok;
}
- void DispatchOne() {
+ inline void* Context() {
+ return context;
+ }
+
+ protected:
+ void Dispatch() {
+ bool has_more = true;
+
+ // Limit maximum synchronous iteration count to prevent event loop
+ // starvation. See `src/node_messaging.cc` for an inspiration.
+ unsigned int iterations_left = kMaxIterationCount;
+ while (has_more && --iterations_left != 0) {
+ dispatch_state = kDispatchRunning;
+ has_more = DispatchOne();
+
+ // Send() was called while we were executing the JS function
+ if (dispatch_state.exchange(kDispatchIdle) != kDispatchRunning) {
+ has_more = true;
+ }
+ }
+
+ if (has_more) {
+ Send();
+ }
+ }
+
+ bool DispatchOne() {
void* data = nullptr;
bool popped_value = false;
+ bool has_more = false;
{
node::Mutex::ScopedLock lock(this->mutex);
@@ -296,9 +318,9 @@ class ThreadSafeFunction : public node::AsyncResource {
cond->Signal(lock);
}
CloseHandlesAndMaybeDelete();
- } else {
- CHECK_EQ(0, uv_idle_stop(&idle));
}
+ } else {
+ has_more = true;
}
}
}
@@ -316,6 +338,8 @@ class ThreadSafeFunction : public node::AsyncResource {
call_js_cb(env, js_callback, context, data);
});
}
+
+ return has_more;
}
void Finalize() {
@@ -329,10 +353,6 @@ class ThreadSafeFunction : public node::AsyncResource {
EmptyQueueAndDelete();
}
- inline void* Context() {
- return context;
- }
-
void CloseHandlesAndMaybeDelete(bool set_closing = false) {
v8::HandleScope scope(env->isolate);
if (set_closing) {
@@ -352,18 +372,20 @@ class ThreadSafeFunction : public node::AsyncResource {
ThreadSafeFunction* ts_fn =
node::ContainerOf(&ThreadSafeFunction::async,
reinterpret_cast<uv_async_t*>(handle));
- v8::HandleScope scope(ts_fn->env->isolate);
- ts_fn->env->node_env()->CloseHandle(
- reinterpret_cast<uv_handle_t*>(&ts_fn->idle),
- [](uv_handle_t* handle) -> void {
- ThreadSafeFunction* ts_fn =
- node::ContainerOf(&ThreadSafeFunction::idle,
- reinterpret_cast<uv_idle_t*>(handle));
- ts_fn->Finalize();
- });
+ ts_fn->Finalize();
});
}
+ void Send() {
+ // Ask currently running Dispatch() to make one more iteration
+ unsigned char current_state = dispatch_state.fetch_or(kDispatchPending);
+ if ((current_state & kDispatchRunning) == kDispatchRunning) {
+ return;
+ }
+
+ CHECK_EQ(0, uv_async_send(&async));
+ }
+
// Default way of calling into JavaScript. Used when ThreadSafeFunction is
// without a call_js_cb_.
static void CallJs(napi_env env, napi_value cb, void* context, void* data) {
@@ -387,16 +409,10 @@ class ThreadSafeFunction : public node::AsyncResource {
}
}
- static void IdleCb(uv_idle_t* idle) {
- ThreadSafeFunction* ts_fn =
- node::ContainerOf(&ThreadSafeFunction::idle, idle);
- ts_fn->DispatchOne();
- }
-
static void AsyncCb(uv_async_t* async) {
ThreadSafeFunction* ts_fn =
node::ContainerOf(&ThreadSafeFunction::async, async);
- CHECK_EQ(0, uv_idle_start(&ts_fn->idle, IdleCb));
+ ts_fn->Dispatch();
}
static void Cleanup(void* data) {
@@ -405,14 +421,20 @@ class ThreadSafeFunction : public node::AsyncResource {
}
private:
+ static const unsigned char kDispatchIdle = 0;
+ static const unsigned char kDispatchRunning = 1 << 0;
+ static const unsigned char kDispatchPending = 1 << 1;
+
+ static const unsigned int kMaxIterationCount = 1000;
+
// These are variables protected by the mutex.
node::Mutex mutex;
std::unique_ptr<node::ConditionVariable> cond;
std::queue<void*> queue;
uv_async_t async;
- uv_idle_t idle;
size_t thread_count;
bool is_closing;
+ std::atomic_uchar dispatch_state;
// These are variables set once, upon creation, and then never again, which
// means we don't need the mutex to read them.
diff --git a/test/node-api/test_threadsafe_function/binding.c b/test/node-api/test_threadsafe_function/binding.c
index c9c526153804c60b5bd5844d2c2aacac7f0fb514..ae3ec67de43cfffdfb10772761dbd69f01c623bb 100644
--- a/test/node-api/test_threadsafe_function/binding.c
+++ b/test/node-api/test_threadsafe_function/binding.c
@@ -7,7 +7,7 @@
#include <node_api.h>
#include "../../js-native-api/common.h"
-#define ARRAY_LENGTH 10
+#define ARRAY_LENGTH 10000
#define MAX_QUEUE_SIZE 2
static uv_thread_t uv_threads[2];
@@ -72,7 +72,7 @@ static void data_source_thread(void* data) {
for (index = ARRAY_LENGTH - 1; index > -1 && !queue_was_closing; index--) {
status = napi_call_threadsafe_function(ts_fn, &ints[index],
ts_fn_info->block_on_full);
- if (ts_fn_info->max_queue_size == 0) {
+ if (ts_fn_info->max_queue_size == 0 && (index % 1000 == 0)) {
// Let's make this thread really busy for 200 ms to give the main thread a
// chance to abort.
uint64_t start = uv_hrtime();
diff --git a/test/node-api/test_threadsafe_function/test.js b/test/node-api/test_threadsafe_function/test.js
index 3603d79ee6b5d36590503989d8168368eaf12b03..ccd3f4228a793ae77eff760309e31191ba8de49a 100644
--- a/test/node-api/test_threadsafe_function/test.js
+++ b/test/node-api/test_threadsafe_function/test.js
@@ -210,6 +210,15 @@ new Promise(function testWithoutJSMarshaller(resolve) {
}))
.then((result) => assert.strictEqual(result.indexOf(0), -1))
+// Make sure that threadsafe function isn't stalled when we hit
+// `kMaxIterationCount` in `src/node_api.cc`
+.then(() => testWithJSMarshaller({
+ threadStarter: 'StartThreadNonblocking',
+ maxQueueSize: binding.ARRAY_LENGTH >>> 1,
+ quitAfter: binding.ARRAY_LENGTH
+}))
+.then((result) => assert.deepStrictEqual(result, expectedArray))
+
// Start a child process to test rapid teardown
.then(() => testUnref(binding.MAX_QUEUE_SIZE))

View File

@@ -0,0 +1,31 @@
const AWS = require('aws-sdk');
const lambda = new AWS.Lambda({
credentials: {
accessKeyId: process.env.AWS_LAMBDA_EXECUTE_KEY,
secretAccessKey: process.env.AWS_LAMBDA_EXECUTE_SECRET
},
region: 'us-east-1'
});
module.exports = function getUrlHash (targetUrl, algorithm = 'sha256') {
return new Promise((resolve, reject) => {
lambda.invoke({
FunctionName: 'hasher',
Payload: JSON.stringify({
targetUrl,
algorithm
})
}, (err, data) => {
if (err) return reject(err);
try {
const response = JSON.parse(data.Payload);
if (response.statusCode !== 200) return reject(new Error('non-200 status code received from hasher function'));
if (!response.hash) return reject(new Error('Successful lambda call but failed to get valid hash'));
resolve(response.hash);
} catch (err) {
return reject(err);
}
});
});
};

View File

@@ -135,8 +135,7 @@ async function pushRelease (branch) {
async function runReleaseBuilds (branch) {
await ciReleaseBuild(branch, {
ghRelease: true,
automaticRelease: args.automaticRelease
ghRelease: true
});
}

View File

@@ -5,20 +5,16 @@ if (!process.env.CI) require('dotenv-safe').load();
const args = require('minimist')(process.argv.slice(2), {
boolean: [
'validateRelease',
'skipVersionCheck',
'automaticRelease',
'verboseNugget'
],
default: { verboseNugget: false }
});
const fs = require('fs');
const { execSync } = require('child_process');
const nugget = require('nugget');
const got = require('got');
const pkg = require('../../package.json');
const pkgVersion = `v${pkg.version}`;
const path = require('path');
const sumchecker = require('sumchecker');
const temp = require('temp').track();
const { URL } = require('url');
const { Octokit } = require('@octokit/rest');
@@ -29,6 +25,7 @@ const pass = '✓'.green;
const fail = '✗'.red;
const { ELECTRON_DIR } = require('../lib/utils');
const getUrlHash = require('./get-url-hash');
const octokit = new Octokit({
auth: process.env.ELECTRON_GITHUB_TOKEN
@@ -64,7 +61,7 @@ async function getDraftRelease (version, skipValidation) {
async function validateReleaseAssets (release, validatingRelease) {
const requiredAssets = assetsForVersion(release.tag_name, validatingRelease).sort();
const extantAssets = release.assets.map(asset => asset.name).sort();
const downloadUrls = release.assets.map(asset => asset.browser_download_url).sort();
const downloadUrls = release.assets.map(asset => ({ url: asset.browser_download_url, file: asset.name })).sort((a, b) => a.file.localeCompare(b.file));
failureCount = 0;
requiredAssets.forEach(asset => {
@@ -74,15 +71,15 @@ async function validateReleaseAssets (release, validatingRelease) {
if (!validatingRelease || !release.draft) {
if (release.draft) {
await verifyAssets(release);
await verifyDraftGitHubReleaseAssets(release);
} else {
await verifyShasums(downloadUrls)
await verifyShasumsForRemoteFiles(downloadUrls)
.catch(err => {
console.log(`${fail} error verifyingShasums`, err);
});
}
const s3Urls = s3UrlsForVersion(release.tag_name);
await verifyShasums(s3Urls, true);
const s3RemoteFiles = s3RemoteFilesForVersion(release.tag_name);
await verifyShasumsForRemoteFiles(s3RemoteFiles, true);
}
}
@@ -174,21 +171,29 @@ function assetsForVersion (version, validatingRelease) {
return patterns;
}
function s3UrlsForVersion (version) {
function s3RemoteFilesForVersion (version) {
const bucket = 'https://gh-contractor-zcbenz.s3.amazonaws.com/';
const patterns = [
`${bucket}atom-shell/dist/${version}/iojs-${version}-headers.tar.gz`,
`${bucket}atom-shell/dist/${version}/iojs-${version}.tar.gz`,
`${bucket}atom-shell/dist/${version}/node-${version}.tar.gz`,
`${bucket}atom-shell/dist/${version}/node.lib`,
`${bucket}atom-shell/dist/${version}/win-x64/iojs.lib`,
`${bucket}atom-shell/dist/${version}/win-x86/iojs.lib`,
`${bucket}atom-shell/dist/${version}/x64/node.lib`,
`${bucket}atom-shell/dist/${version}/SHASUMS.txt`,
`${bucket}atom-shell/dist/${version}/SHASUMS256.txt`,
`${bucket}atom-shell/dist/index.json`
const versionPrefix = `${bucket}atom-shell/dist/${version}/`;
const filePaths = [
`iojs-${version}-headers.tar.gz`,
`iojs-${version}.tar.gz`,
`node-${version}.tar.gz`,
'node.lib',
'x64/node.lib',
'win-x64/iojs.lib',
'win-x86/iojs.lib',
'win-arm64/iojs.lib',
'win-x64/node.lib',
'win-x86/node.lib',
'win-arm64/node.lib',
'arm64/node.lib',
'SHASUMS.txt',
'SHASUMS256.txt'
];
return patterns;
return filePaths.map((filePath) => ({
file: filePath,
url: `${versionPrefix}${filePath}`
}));
}
function runScript (scriptName, scriptArgs, cwd) {
@@ -366,13 +371,13 @@ async function makeTempDir () {
});
}
async function verifyAssets (release) {
const downloadDir = await makeTempDir();
const SHASUM_256_FILENAME = 'SHASUMS256.txt';
const SHASUM_1_FILENAME = 'SHASUMS.txt';
console.log('Downloading files from GitHub to verify shasums');
const shaSumFile = 'SHASUMS256.txt';
async function verifyDraftGitHubReleaseAssets (release) {
console.log('Fetching authenticated GitHub artifact URLs to verify shasums');
let filesToCheck = await Promise.all(release.assets.map(async asset => {
const remoteFilesToHash = await Promise.all(release.assets.map(async asset => {
const requestOptions = await octokit.repos.getReleaseAsset.endpoint({
owner: 'electron',
repo: targetRepo,
@@ -391,137 +396,59 @@ async function verifyAssets (release) {
headers
});
await downloadFiles(response.headers.location, downloadDir, asset.name);
return asset.name;
return { url: response.headers.location, file: asset.name };
})).catch(err => {
console.log(`${fail} Error downloading files from GitHub`, err);
process.exit(1);
});
filesToCheck = filesToCheck.filter(fileName => fileName !== shaSumFile);
let checkerOpts;
await validateChecksums({
algorithm: 'sha256',
filesToCheck,
fileDirectory: downloadDir,
shaSumFile,
checkerOpts,
fileSource: 'GitHub'
});
await verifyShasumsForRemoteFiles(remoteFilesToHash);
}
function downloadFiles (urls, directory, targetName) {
return new Promise((resolve, reject) => {
const nuggetOpts = { dir: directory };
nuggetOpts.quiet = !args.verboseNugget;
if (targetName) nuggetOpts.target = targetName;
nugget(urls, nuggetOpts, (err) => {
if (err) {
reject(err);
} else {
console.log(`${pass} all files downloaded successfully!`);
resolve();
}
});
});
async function getShaSumMappingFromUrl (shaSumFileUrl, fileNamePrefix) {
const response = await got(shaSumFileUrl);
const raw = response.body;
return raw.split('\n').map(line => line.trim()).filter(Boolean).reduce((map, line) => {
const [sha, file] = line.replace(' ', ' ').split(' ');
map[file.slice(fileNamePrefix.length)] = sha;
return map;
}, {});
}
async function verifyShasums (urls, isS3) {
const fileSource = isS3 ? 'S3' : 'GitHub';
console.log(`Downloading files from ${fileSource} to verify shasums`);
const downloadDir = await makeTempDir();
let filesToCheck = [];
try {
if (!isS3) {
await downloadFiles(urls, downloadDir);
filesToCheck = urls.map(url => {
const currentUrl = new URL(url);
return path.basename(currentUrl.pathname);
}).filter(file => file.indexOf('SHASUMS') === -1);
} else {
const s3VersionPath = `/atom-shell/dist/${pkgVersion}/`;
await Promise.all(urls.map(async (url) => {
const currentUrl = new URL(url);
const dirname = path.dirname(currentUrl.pathname);
const filename = path.basename(currentUrl.pathname);
const s3VersionPathIdx = dirname.indexOf(s3VersionPath);
if (s3VersionPathIdx === -1 || dirname === s3VersionPath) {
if (s3VersionPathIdx !== -1 && filename.indexof('SHASUMS') === -1) {
filesToCheck.push(filename);
}
await downloadFiles(url, downloadDir);
} else {
const subDirectory = dirname.substr(s3VersionPathIdx + s3VersionPath.length);
const fileDirectory = path.join(downloadDir, subDirectory);
try {
fs.statSync(fileDirectory);
} catch (err) {
fs.mkdirSync(fileDirectory);
}
filesToCheck.push(path.join(subDirectory, filename));
await downloadFiles(url, fileDirectory);
}
}));
}
} catch (err) {
console.log(`${fail} Error downloading files from ${fileSource}`, err);
process.exit(1);
}
console.log(`${pass} Successfully downloaded the files from ${fileSource}.`);
let checkerOpts;
if (isS3) {
checkerOpts = { defaultTextEncoding: 'binary' };
}
await validateChecksums({
algorithm: 'sha256',
filesToCheck,
fileDirectory: downloadDir,
shaSumFile: 'SHASUMS256.txt',
checkerOpts,
fileSource
});
if (isS3) {
await validateChecksums({
algorithm: 'sha1',
filesToCheck,
fileDirectory: downloadDir,
shaSumFile: 'SHASUMS.txt',
checkerOpts,
fileSource
});
async function validateFileHashesAgainstShaSumMapping (remoteFilesWithHashes, mapping) {
for (const remoteFileWithHash of remoteFilesWithHashes) {
check(remoteFileWithHash.hash === mapping[remoteFileWithHash.file], `Release asset ${remoteFileWithHash.file} should have hash of ${mapping[remoteFileWithHash.file]} but found ${remoteFileWithHash.hash}`, true);
}
}
async function validateChecksums (validationArgs) {
console.log(`Validating checksums for files from ${validationArgs.fileSource} ` +
`against ${validationArgs.shaSumFile}.`);
const shaSumFilePath = path.join(validationArgs.fileDirectory, validationArgs.shaSumFile);
const checker = new sumchecker.ChecksumValidator(validationArgs.algorithm,
shaSumFilePath, validationArgs.checkerOpts);
await checker.validate(validationArgs.fileDirectory, validationArgs.filesToCheck)
.catch(err => {
if (err instanceof sumchecker.ChecksumMismatchError) {
console.error(`${fail} The checksum of ${err.filename} from ` +
`${validationArgs.fileSource} did not match the shasum in ` +
`${validationArgs.shaSumFile}`);
} else if (err instanceof sumchecker.ChecksumParseError) {
console.error(`${fail} The checksum file ${validationArgs.shaSumFile} ` +
`from ${validationArgs.fileSource} could not be parsed.`, err);
} else if (err instanceof sumchecker.NoChecksumFoundError) {
console.error(`${fail} The file ${err.filename} from ` +
`${validationArgs.fileSource} was not in the shasum file ` +
`${validationArgs.shaSumFile}.`);
} else {
console.error(`${fail} Error matching files from ` +
`${validationArgs.fileSource} shasums in ${validationArgs.shaSumFile}.`, err);
}
process.exit(1);
});
console.log(`${pass} All files from ${validationArgs.fileSource} match ` +
`shasums defined in ${validationArgs.shaSumFile}.`);
async function verifyShasumsForRemoteFiles (remoteFilesToHash, filesAreNodeJSArtifacts = false) {
console.log(`Generating SHAs for ${remoteFilesToHash.length} files to verify shasums`);
// Only used for node.js artifact uploads
const shaSum1File = remoteFilesToHash.find(({ file }) => file === SHASUM_1_FILENAME);
// Used for both node.js artifact uploads and normal electron artifacts
const shaSum256File = remoteFilesToHash.find(({ file }) => file === SHASUM_256_FILENAME);
remoteFilesToHash = remoteFilesToHash.filter(({ file }) => file !== SHASUM_1_FILENAME && file !== SHASUM_256_FILENAME);
const remoteFilesWithHashes = await Promise.all(remoteFilesToHash.map(async (file) => {
return {
hash: await getUrlHash(file.url, 'sha256'),
...file
};
}));
await validateFileHashesAgainstShaSumMapping(remoteFilesWithHashes, await getShaSumMappingFromUrl(shaSum256File.url, filesAreNodeJSArtifacts ? '' : '*'));
if (filesAreNodeJSArtifacts) {
const remoteFilesWithSha1Hashes = await Promise.all(remoteFilesToHash.map(async (file) => {
return {
hash: await getUrlHash(file.url, 'sha1'),
...file
};
}));
await validateFileHashesAgainstShaSumMapping(remoteFilesWithSha1Hashes, await getShaSumMappingFromUrl(shaSum1File.url, filesAreNodeJSArtifacts ? '' : '*'));
}
}
makeRelease(args.validateRelease);

View File

@@ -3712,7 +3712,11 @@ gin::Handle<WebContents> WebContents::New(
const gin_helper::Dictionary& options) {
gin::Handle<WebContents> handle =
gin::CreateHandle(isolate, new WebContents(isolate, options));
v8::TryCatch try_catch(isolate);
gin_helper::CallMethod(isolate, handle.get(), "_init");
if (try_catch.HasCaught()) {
node::errors::TriggerUncaughtException(isolate, try_catch);
}
return handle;
}
@@ -3723,7 +3727,11 @@ gin::Handle<WebContents> WebContents::CreateAndTake(
Type type) {
gin::Handle<WebContents> handle = gin::CreateHandle(
isolate, new WebContents(isolate, std::move(web_contents), type));
v8::TryCatch try_catch(isolate);
gin_helper::CallMethod(isolate, handle.get(), "_init");
if (try_catch.HasCaught()) {
node::errors::TriggerUncaughtException(isolate, try_catch);
}
return handle;
}
@@ -3743,7 +3751,11 @@ gin::Handle<WebContents> WebContents::FromOrCreate(
WebContents* api_web_contents = From(web_contents);
if (!api_web_contents) {
api_web_contents = new WebContents(isolate, web_contents);
v8::TryCatch try_catch(isolate);
gin_helper::CallMethod(isolate, api_web_contents, "_init");
if (try_catch.HasCaught()) {
node::errors::TriggerUncaughtException(isolate, try_catch);
}
}
return gin::CreateHandle(isolate, api_web_contents);
}

View File

@@ -27,6 +27,7 @@
#include "base/task/post_task.h"
#include "chrome/browser/browser_process.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/chrome_version.h"
#include "components/net_log/chrome_net_log.h"
#include "components/network_hints/common/network_hints.mojom.h"
@@ -142,6 +143,7 @@
#include "extensions/browser/extension_message_filter.h"
#include "extensions/browser/extension_navigation_throttle.h"
#include "extensions/browser/extension_navigation_ui_data.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_protocols.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extensions_browser_client.h"
@@ -246,6 +248,15 @@ enum class RenderProcessHostPrivilege {
kExtension,
};
// Copied from chrome/browser/extensions/extension_util.cc.
bool AllowFileAccess(const std::string& extension_id,
content::BrowserContext* context) {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
::switches::kDisableExtensionsFileAccessCheck) ||
extensions::ExtensionPrefs::Get(context)->AllowFileAccess(
extension_id);
}
RenderProcessHostPrivilege GetPrivilegeRequiredByUrl(
const GURL& url,
extensions::ExtensionRegistry* registry) {
@@ -1423,12 +1434,12 @@ void ElectronBrowserClient::RegisterNonNetworkSubresourceURLLoaderFactories(
{content::kChromeUIResourcesHost}));
}
// Extension with a background page get file access that gets approval from
// ChildProcessSecurityPolicy.
extensions::ExtensionHost* host =
extensions::ProcessManager::Get(web_contents->GetBrowserContext())
->GetBackgroundHostForExtension(extension->id());
if (host) {
// Extensions with the necessary permissions get access to file:// URLs that
// gets approval from ChildProcessSecurityPolicy. Keep this logic in sync with
// ExtensionWebContentsObserver::RenderFrameCreated.
extensions::Manifest::Type type = extension->GetType();
if (type == extensions::Manifest::TYPE_EXTENSION &&
AllowFileAccess(extension->id(), web_contents->GetBrowserContext())) {
factories->emplace(url::kFileScheme,
FileURLLoaderFactory::Create(render_process_id));
}

View File

@@ -38,12 +38,6 @@ void InitializeFeatureList() {
std::string(",") +
net::features::kCookiesWithoutSameSiteMustBeSecure.name;
// https://www.polymer-project.org/blog/2018-10-02-webcomponents-v0-deprecations
// https://chromium-review.googlesource.com/c/chromium/src/+/1869562
// Any website which uses older WebComponents will fail in without this
// enabled, since Electron does not support origin trials.
enable_features += std::string(",") + "WebComponentsV0Enabled";
#if !BUILDFLAG(ENABLE_PICTURE_IN_PICTURE)
disable_features += std::string(",") + media::kPictureInPicture.name;
#endif

View File

@@ -347,9 +347,7 @@ void NativeBrowserViewMac::UpdateDraggableRegions(
std::vector<gfx::Rect> drag_exclude_rects;
if (draggable_regions_.empty()) {
const auto bounds = GetBounds();
drag_exclude_rects.emplace_back(bounds.x(), bounds.y(), webViewWidth,
webViewHeight);
drag_exclude_rects.emplace_back(0, 0, webViewWidth, webViewHeight);
} else {
drag_exclude_rects = CalculateNonDraggableRegions(
DraggableRegionsToSkRegion(draggable_regions_), webViewWidth,

View File

@@ -160,6 +160,8 @@ class NativeWindowMac : public NativeWindow,
// Use a custom content view instead of Chromium's BridgedContentView.
void OverrideNSWindowContentView();
void UpdateVibrancyRadii(bool fullscreen);
// Set the attribute of NSWindow while work around a bug of zoom button.
void SetStyleMask(bool on, NSUInteger flag);
void SetCollectionBehavior(bool on, NSUInteger flag);
@@ -268,8 +270,7 @@ class NativeWindowMac : public NativeWindow,
NSInteger original_level_;
NSUInteger simple_fullscreen_mask_;
base::scoped_nsobject<NSColor> background_color_before_vibrancy_;
bool transparency_before_vibrancy_ = false;
std::string vibrancy_type_;
// The presentation options before entering simple fullscreen mode.
NSApplicationPresentationOptions simple_fullscreen_options_;

View File

@@ -1299,14 +1299,50 @@ void NativeWindowMac::SetAutoHideCursor(bool auto_hide) {
[window_ setDisableAutoHideCursor:!auto_hide];
}
void NativeWindowMac::UpdateVibrancyRadii(bool fullscreen) {
NSView* vibrant_view = [window_ vibrantView];
NSVisualEffectView* effect_view = (NSVisualEffectView*)vibrant_view;
if (effect_view != nil && !vibrancy_type_.empty()) {
const bool no_rounded_corner =
[window_ styleMask] & NSWindowStyleMaskFullSizeContentView;
if (!has_frame() && !is_modal() && !no_rounded_corner) {
CGFloat radius;
if (fullscreen) {
radius = 0.0f;
} else if (@available(macOS 11.0, *)) {
radius = 9.0f;
} else {
// Smaller corner radius on versions prior to Big Sur.
radius = 5.0f;
}
CGFloat dimension = 2 * radius + 1;
NSSize size = NSMakeSize(dimension, dimension);
NSImage* maskImage = [NSImage imageWithSize:size
flipped:NO
drawingHandler:^BOOL(NSRect rect) {
NSBezierPath* bezierPath = [NSBezierPath
bezierPathWithRoundedRect:rect
xRadius:radius
yRadius:radius];
[[NSColor blackColor] set];
[bezierPath fill];
return YES;
}];
[maskImage setCapInsets:NSEdgeInsetsMake(radius, radius, radius, radius)];
[maskImage setResizingMode:NSImageResizingModeStretch];
[effect_view setMaskImage:maskImage];
[window_ setCornerMask:maskImage];
}
}
}
void NativeWindowMac::SetVibrancy(const std::string& type) {
NSView* vibrant_view = [window_ vibrantView];
if (type.empty()) {
if (background_color_before_vibrancy_) {
[window_ setBackgroundColor:background_color_before_vibrancy_];
[window_ setTitlebarAppearsTransparent:transparency_before_vibrancy_];
}
if (vibrant_view == nil)
return;
@@ -1316,13 +1352,7 @@ void NativeWindowMac::SetVibrancy(const std::string& type) {
return;
}
background_color_before_vibrancy_.reset([[window_ backgroundColor] retain]);
transparency_before_vibrancy_ = [window_ titlebarAppearsTransparent];
if (title_bar_style_ != TitleBarStyle::kNormal) {
[window_ setTitlebarAppearsTransparent:YES];
[window_ setBackgroundColor:[NSColor clearColor]];
}
vibrancy_type_ = type;
NSVisualEffectView* effect_view = (NSVisualEffectView*)vibrant_view;
if (effect_view == nil) {
@@ -1341,39 +1371,11 @@ void NativeWindowMac::SetVibrancy(const std::string& type) {
[effect_view setState:NSVisualEffectStateFollowsWindowActiveState];
}
// Make Vibrant view have rounded corners, so the frameless window can keep
// its rounded corners.
const bool no_rounded_corner =
[window_ styleMask] & NSWindowStyleMaskFullSizeContentView;
if (!has_frame() && !is_modal() && !no_rounded_corner) {
CGFloat radius;
if (@available(macOS 11.0, *)) {
radius = 9.0f;
} else {
radius = 5.0f; // smaller corner radius on older versions
}
CGFloat dimension = 2 * radius + 1;
NSSize size = NSMakeSize(dimension, dimension);
NSImage* maskImage = [NSImage imageWithSize:size
flipped:NO
drawingHandler:^BOOL(NSRect rect) {
NSBezierPath* bezierPath = [NSBezierPath
bezierPathWithRoundedRect:rect
xRadius:radius
yRadius:radius];
[[NSColor blackColor] set];
[bezierPath fill];
return YES;
}];
[maskImage setCapInsets:NSEdgeInsetsMake(radius, radius, radius, radius)];
[maskImage setResizingMode:NSImageResizingModeStretch];
[effect_view setMaskImage:maskImage];
}
[[window_ contentView] addSubview:effect_view
positioned:NSWindowBelow
relativeTo:nil];
UpdateVibrancyRadii(IsFullscreen());
}
std::string dep_warn = " has been deprecated and removed as of macOS 10.15.";
@@ -1381,7 +1383,6 @@ void NativeWindowMac::SetVibrancy(const std::string& type) {
node::Environment::GetCurrent(JavascriptEnvironment::GetIsolate());
NSVisualEffectMaterial vibrancyType;
if (type == "appearance-based") {
EmitWarning(env, "NSVisualEffectMaterialAppearanceBased" + dep_warn,
"electron");
@@ -1619,6 +1620,8 @@ void NativeWindowMac::NotifyWindowWillEnterFullScreen() {
[buttons_view_ removeFromSuperview];
InternalSetStandardButtonsVisibility(true);
}
UpdateVibrancyRadii(true);
}
void NativeWindowMac::NotifyWindowWillLeaveFullScreen() {
@@ -1630,6 +1633,7 @@ void NativeWindowMac::NotifyWindowWillLeaveFullScreen() {
}
RedrawTrafficLights();
UpdateVibrancyRadii(false);
}
void NativeWindowMac::Cleanup() {

View File

@@ -50,8 +50,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 13,0,0,21
PRODUCTVERSION 13,0,0,21
FILEVERSION 13,0,0,27
PRODUCTVERSION 13,0,0,27
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L

View File

@@ -64,6 +64,7 @@
#include "env.h"
#include "node.h"
#include "node_buffer.h"
#include "node_errors.h"
#include "node_internals.h"
#include "node_options-inl.h"
#include "node_options.h"

View File

@@ -29,7 +29,7 @@
#include "shell/renderer/api/context_bridge/object_cache.h"
#include "shell/renderer/api/electron_api_context_bridge.h"
#include "shell/renderer/api/electron_api_spell_check_client.h"
#include "shell/renderer/electron_renderer_client.h"
#include "shell/renderer/renderer_client_base.h"
#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
#include "third_party/blink/public/common/page/page_zoom.h"
#include "third_party/blink/public/common/web_cache/web_cache_resource_type_stats.h"
@@ -118,7 +118,7 @@ bool SpellCheckWord(v8::Isolate* isolate,
size_t start;
size_t length;
ElectronRendererClient* client = ElectronRendererClient::Get();
RendererClientBase* client = RendererClientBase::Get();
auto* render_frame = GetRenderFrame(window);
if (!render_frame)
return true;

View File

@@ -78,6 +78,7 @@ void ElectronRenderFrameObserver::DidInstallConditionalFeatures(
bool is_not_opened = !render_frame_->GetWebFrame()->Opener() ||
prefs.node_leakage_in_renderers;
bool allow_node_in_sub_frames = prefs.node_integration_in_sub_frames;
bool should_create_isolated_context =
use_context_isolation && is_main_world &&
(is_main_frame || allow_node_in_sub_frames) &&
@@ -155,12 +156,24 @@ bool ElectronRenderFrameObserver::IsIsolatedWorld(int world_id) {
bool ElectronRenderFrameObserver::ShouldNotifyClient(int world_id) {
auto prefs = render_frame_->GetBlinkPreferences();
// This is necessary because if an iframe is created and a source is not
// set, the iframe loads about:blank and creates a script context for the
// same. We don't want to create a Node.js environment here because if the src
// is later set, the JS necessary to do that triggers illegal access errors
// when the initial about:blank Node.js environment is cleaned up. See:
// https://source.chromium.org/chromium/chromium/src/+/main:content/renderer/render_frame_impl.h;l=870-892;drc=4b6001440a18740b76a1c63fa2a002cc941db394
GURL url = render_frame_->GetWebFrame()->GetDocument().Url();
bool allow_node_in_sub_frames = prefs.node_integration_in_sub_frames;
if (allow_node_in_sub_frames && url.IsAboutBlank() &&
!render_frame_->IsMainFrame())
return false;
if (prefs.context_isolation &&
(render_frame_->IsMainFrame() || allow_node_in_sub_frames))
return IsIsolatedWorld(world_id);
else
return IsMainWorld(world_id);
return IsMainWorld(world_id);
}
} // namespace electron

View File

@@ -35,27 +35,15 @@ bool IsDevToolsExtension(content::RenderFrame* render_frame) {
} // namespace
// static
ElectronRendererClient* ElectronRendererClient::self_ = nullptr;
ElectronRendererClient::ElectronRendererClient()
: node_bindings_(
NodeBindings::Create(NodeBindings::BrowserEnvironment::kRenderer)),
electron_bindings_(new ElectronBindings(node_bindings_->uv_loop())) {
DCHECK(!self_) << "Cannot have two ElectronRendererClient";
self_ = this;
}
electron_bindings_(new ElectronBindings(node_bindings_->uv_loop())) {}
ElectronRendererClient::~ElectronRendererClient() {
asar::ClearArchives();
}
// static
ElectronRendererClient* ElectronRendererClient::Get() {
DCHECK(self_);
return self_;
}
void ElectronRendererClient::RenderFrameCreated(
content::RenderFrame* render_frame) {
new ElectronRenderFrameObserver(render_frame, this);
@@ -92,8 +80,8 @@ void ElectronRendererClient::DidCreateScriptContext(
// TODO(zcbenz): Do not create Node environment if node integration is not
// enabled.
// Only load node if we are a main frame or a devtools extension
// unless node support has been explicitly enabled for sub frames
// Only load Node.js if we are a main frame or a devtools extension
// unless Node.js support has been explicitly enabled for subframes.
auto prefs = render_frame->GetBlinkPreferences();
bool reuse_renderer_processes_enabled =
prefs.disable_electron_site_instance_overrides;
@@ -109,6 +97,7 @@ void ElectronRendererClient::DidCreateScriptContext(
(is_not_opened || reuse_renderer_processes_enabled);
bool is_devtools = IsDevToolsExtension(render_frame);
bool allow_node_in_subframes = prefs.node_integration_in_sub_frames;
bool should_load_node =
(is_main_frame || is_devtools || allow_node_in_subframes) &&
!IsWebViewFrame(renderer_context, render_frame);

View File

@@ -26,8 +26,6 @@ class ElectronRendererClient : public RendererClientBase {
ElectronRendererClient();
~ElectronRendererClient() override;
static ElectronRendererClient* Get();
// electron::RendererClientBase:
void DidCreateScriptContext(v8::Handle<v8::Context> context,
content::RenderFrame* render_frame) override;
@@ -68,8 +66,6 @@ class ElectronRendererClient : public RendererClientBase {
// assertion, so we have to keep a book of injected web frames.
std::set<content::RenderFrame*> injected_frames_;
static ElectronRendererClient* self_;
DISALLOW_COPY_AND_ASSIGN(ElectronRendererClient);
};

View File

@@ -204,8 +204,10 @@ void ElectronSandboxedRendererClient::DidCreateScriptContext(
bool is_main_frame = render_frame->IsMainFrame();
bool is_devtools =
IsDevTools(render_frame) || IsDevToolsExtension(render_frame);
bool allow_node_in_sub_frames =
render_frame->GetBlinkPreferences().node_integration_in_sub_frames;
bool should_load_preload =
(is_main_frame || is_devtools || allow_node_in_sub_frames) &&
!IsWebViewFrame(context, render_frame);

View File

@@ -92,6 +92,9 @@ std::vector<std::string> ParseSchemesCLISwitch(base::CommandLine* command_line,
base::SPLIT_WANT_NONEMPTY);
}
// static
RendererClientBase* g_renderer_client_base = nullptr;
} // namespace
RendererClientBase::RendererClientBase() {
@@ -123,9 +126,13 @@ RendererClientBase::RendererClientBase() {
DCHECK(command_line->HasSwitch(::switches::kRendererClientId));
renderer_client_id_ =
command_line->GetSwitchValueASCII(::switches::kRendererClientId);
g_renderer_client_base = this;
}
RendererClientBase::~RendererClientBase() = default;
RendererClientBase::~RendererClientBase() {
g_renderer_client_base = nullptr;
}
void RendererClientBase::DidCreateScriptContext(
v8::Handle<v8::Context> context,
@@ -137,6 +144,12 @@ void RendererClientBase::DidCreateScriptContext(
global.SetHidden("contextId", context_id);
}
// static
RendererClientBase* RendererClientBase::Get() {
DCHECK(g_renderer_client_base);
return g_renderer_client_base;
}
void RendererClientBase::BindProcess(v8::Isolate* isolate,
gin_helper::Dictionary* process,
content::RenderFrame* render_frame) {

View File

@@ -60,6 +60,8 @@ class RendererClientBase : public content::ContentRendererClient
RendererClientBase();
~RendererClientBase() override;
static RendererClientBase* Get();
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
// service_manager::LocalInterfaceProvider implementation.
void GetInterface(const std::string& name,

View File

@@ -1,5 +1,5 @@
import { expect } from 'chai';
import { nativeTheme, systemPreferences, BrowserWindow } from 'electron/main';
import { nativeTheme, systemPreferences, BrowserWindow, ipcMain } from 'electron/main';
import * as os from 'os';
import * as path from 'path';
import * as semver from 'semver';
@@ -75,14 +75,24 @@ describe('nativeTheme module', () => {
};
it('should override the result of prefers-color-scheme CSS media query', async () => {
const w = new BrowserWindow({ show: false });
const w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: false, nodeIntegration: true } });
await w.loadFile(path.resolve(__dirname, 'fixtures', 'blank.html'));
await w.webContents.executeJavaScript(`
window.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', () => require('electron').ipcRenderer.send('theme-change'))
`);
const originalSystemIsDark = await getPrefersColorSchemeIsDark(w);
let changePromise: Promise<any[]> = emittedOnce(ipcMain, 'theme-change');
nativeTheme.themeSource = 'dark';
if (!originalSystemIsDark) await changePromise;
expect(await getPrefersColorSchemeIsDark(w)).to.equal(true);
changePromise = emittedOnce(ipcMain, 'theme-change');
nativeTheme.themeSource = 'light';
await changePromise;
expect(await getPrefersColorSchemeIsDark(w)).to.equal(false);
changePromise = emittedOnce(ipcMain, 'theme-change');
nativeTheme.themeSource = 'system';
if (originalSystemIsDark) await changePromise;
expect(await getPrefersColorSchemeIsDark(w)).to.equal(originalSystemIsDark);
w.close();
});

View File

@@ -0,0 +1,29 @@
<html>
<body>
<iframe id="mainframe"></iframe>
<script>
const net = require('net');
const path = require('path');
document.getElementById("mainframe").src="./page2.html";
const p = process.platform === 'win32'
? path.join('\\\\?\\pipe', process.cwd(), 'myctl')
: '/tmp/echo.sock';
const client = net.createConnection({ path: p }, () => {
console.log('connected to server');
client.write('world!\r\n');
});
client.on('data', (data) => {
console.log(data.toString());
client.end();
});
client.on('end', () => {
console.log('disconnected from server');
});
</script>
</body>
</html>

View File

@@ -0,0 +1,51 @@
const { app, BrowserWindow } = require('electron');
const net = require('net');
const path = require('path');
function createWindow () {
const mainWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
nodeIntegrationInSubFrames: true
}
});
mainWindow.loadFile('index.html');
}
app.whenReady().then(() => {
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
const server = net.createServer((c) => {
console.log('client connected');
c.on('end', () => {
console.log('client disconnected');
app.quit();
});
c.write('hello\r\n');
c.pipe(c);
});
server.on('error', (err) => {
throw err;
});
const p = process.platform === 'win32'
? path.join('\\\\?\\pipe', process.cwd(), 'myctl')
: '/tmp/echo.sock';
server.listen(p, () => {
console.log('server bound');
});

View File

@@ -0,0 +1,4 @@
<!DOCTYPE html>
<html>
<body>HELLO</body>
</html>

View File

@@ -0,0 +1,14 @@
const { app, BrowserWindow } = require('electron');
app.whenReady().then(() => {
const mainWindow = new BrowserWindow({
show: false
});
mainWindow.loadFile('about:blank');
app.on('web-contents-created', () => {
throw new Error();
});
app.quit();
});

View File

@@ -62,166 +62,177 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
});
after(() => server.close());
beforeEach(async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true,
partition: `unique-spell-${Date.now()}`,
contextIsolation: false
}
});
w.webContents.session.setSpellCheckerDictionaryDownloadURL(`http://127.0.0.1:${(server.address() as AddressInfo).port}/`);
w.webContents.session.setSpellCheckerLanguages(['en-US']);
await w.loadFile(path.resolve(__dirname, './fixtures/chromium/spellchecker.html'));
});
const fixtures = path.resolve(__dirname, '../spec/fixtures');
const preload = path.join(fixtures, 'module', 'preload-electron.js');
afterEach(async () => {
await closeWindow(w);
});
// Context menu test can not run on Windows.
const shouldRun = process.platform !== 'win32';
ifit(shouldRun)('should detect correctly spelled words as correct', async () => {
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typography"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
const contextMenuParams = await rightClickUntil((contextMenuParams) => contextMenuParams.selectionText.length > 0);
expect(contextMenuParams.misspelledWord).to.eq('');
expect(contextMenuParams.dictionarySuggestions).to.have.lengthOf(0);
});
ifit(shouldRun)('should detect incorrectly spelled words as incorrect', async () => {
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
const contextMenuParams = await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0);
expect(contextMenuParams.misspelledWord).to.eq('typograpy');
expect(contextMenuParams.dictionarySuggestions).to.have.length.of.at.least(1);
});
ifit(shouldRun)('should detect incorrectly spelled words as incorrect after disabling all languages and re-enabling', async () => {
w.webContents.session.setSpellCheckerLanguages([]);
await delay(500);
w.webContents.session.setSpellCheckerLanguages(['en-US']);
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
const contextMenuParams = await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0);
expect(contextMenuParams.misspelledWord).to.eq('typograpy');
expect(contextMenuParams.dictionarySuggestions).to.have.length.of.at.least(1);
});
ifit(shouldRun)('should expose webFrame spellchecker correctly', async () => {
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0);
const callWebFrameFn = (expr: string) => w.webContents.executeJavaScript('require("electron").webFrame.' + expr);
expect(await callWebFrameFn('isWordMisspelled("typography")')).to.equal(false);
expect(await callWebFrameFn('isWordMisspelled("typograpy")')).to.equal(true);
expect(await callWebFrameFn('getWordSuggestions("typography")')).to.be.empty();
expect(await callWebFrameFn('getWordSuggestions("typograpy")')).to.not.be.empty();
});
describe('spellCheckerEnabled', () => {
it('is enabled by default', async () => {
expect(w.webContents.session.spellCheckerEnabled).to.be.true();
});
ifit(shouldRun)('can be dynamically changed', async () => {
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0);
const callWebFrameFn = (expr: string) => w.webContents.executeJavaScript('require("electron").webFrame.' + expr);
w.webContents.session.spellCheckerEnabled = false;
v8Util.runUntilIdle();
expect(w.webContents.session.spellCheckerEnabled).to.be.false();
// spellCheckerEnabled is sent to renderer asynchronously and there is
// no event notifying when it is finished, so wait a little while to
// ensure the setting has been changed in renderer.
await delay(500);
expect(await callWebFrameFn('isWordMisspelled("typograpy")')).to.equal(false);
w.webContents.session.spellCheckerEnabled = true;
v8Util.runUntilIdle();
expect(w.webContents.session.spellCheckerEnabled).to.be.true();
await delay(500);
expect(await callWebFrameFn('isWordMisspelled("typograpy")')).to.equal(true);
});
});
describe('custom dictionary word list API', () => {
let ses: Session;
beforeEach(async () => {
// ensure a new session runs on each test run
ses = session.fromPartition(`persist:customdictionary-test-${Date.now()}`);
});
afterEach(async () => {
if (ses) {
await ses.clearStorageData();
ses = null as any;
}
});
describe('ses.listWordsFromSpellCheckerDictionary', () => {
it('should successfully list words in custom dictionary', async () => {
const words = ['foo', 'bar', 'baz'];
const results = words.map(word => ses.addWordToSpellCheckerDictionary(word));
expect(results).to.eql([true, true, true]);
const wordList = await ses.listWordsInSpellCheckerDictionary();
expect(wordList).to.have.deep.members(words);
const generateSpecs = (description: string, sandbox: boolean) => {
describe(description, () => {
beforeEach(async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
partition: `unique-spell-${Date.now()}`,
contextIsolation: false,
preload,
sandbox
}
});
w.webContents.session.setSpellCheckerDictionaryDownloadURL(`http://127.0.0.1:${(server.address() as AddressInfo).port}/`);
w.webContents.session.setSpellCheckerLanguages(['en-US']);
await w.loadFile(path.resolve(__dirname, './fixtures/chromium/spellchecker.html'));
});
it('should return an empty array if no words are added', async () => {
const wordList = await ses.listWordsInSpellCheckerDictionary();
expect(wordList).to.have.length(0);
afterEach(async () => {
await closeWindow(w);
});
// Context menu test can not run on Windows.
const shouldRun = process.platform !== 'win32';
ifit(shouldRun)('should detect correctly spelled words as correct', async () => {
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typography"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
const contextMenuParams = await rightClickUntil((contextMenuParams) => contextMenuParams.selectionText.length > 0);
expect(contextMenuParams.misspelledWord).to.eq('');
expect(contextMenuParams.dictionarySuggestions).to.have.lengthOf(0);
});
ifit(shouldRun)('should detect incorrectly spelled words as incorrect', async () => {
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
const contextMenuParams = await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0);
expect(contextMenuParams.misspelledWord).to.eq('typograpy');
expect(contextMenuParams.dictionarySuggestions).to.have.length.of.at.least(1);
});
ifit(shouldRun)('should detect incorrectly spelled words as incorrect after disabling all languages and re-enabling', async () => {
w.webContents.session.setSpellCheckerLanguages([]);
await delay(500);
w.webContents.session.setSpellCheckerLanguages(['en-US']);
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
const contextMenuParams = await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0);
expect(contextMenuParams.misspelledWord).to.eq('typograpy');
expect(contextMenuParams.dictionarySuggestions).to.have.length.of.at.least(1);
});
ifit(shouldRun)('should expose webFrame spellchecker correctly', async () => {
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0);
const callWebFrameFn = (expr: string) => w.webContents.executeJavaScript(`electron.webFrame.${expr}`);
expect(await callWebFrameFn('isWordMisspelled("typography")')).to.equal(false);
expect(await callWebFrameFn('isWordMisspelled("typograpy")')).to.equal(true);
expect(await callWebFrameFn('getWordSuggestions("typography")')).to.be.empty();
expect(await callWebFrameFn('getWordSuggestions("typograpy")')).to.not.be.empty();
});
describe('spellCheckerEnabled', () => {
it('is enabled by default', async () => {
expect(w.webContents.session.spellCheckerEnabled).to.be.true();
});
ifit(shouldRun)('can be dynamically changed', async () => {
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0);
const callWebFrameFn = (expr: string) => w.webContents.executeJavaScript(`electron.webFrame.${expr}`);
w.webContents.session.spellCheckerEnabled = false;
v8Util.runUntilIdle();
expect(w.webContents.session.spellCheckerEnabled).to.be.false();
// spellCheckerEnabled is sent to renderer asynchronously and there is
// no event notifying when it is finished, so wait a little while to
// ensure the setting has been changed in renderer.
await delay(500);
expect(await callWebFrameFn('isWordMisspelled("typograpy")')).to.equal(false);
w.webContents.session.spellCheckerEnabled = true;
v8Util.runUntilIdle();
expect(w.webContents.session.spellCheckerEnabled).to.be.true();
await delay(500);
expect(await callWebFrameFn('isWordMisspelled("typograpy")')).to.equal(true);
});
});
describe('custom dictionary word list API', () => {
let ses: Session;
beforeEach(async () => {
// ensure a new session runs on each test run
ses = session.fromPartition(`persist:customdictionary-test-${Date.now()}`);
});
afterEach(async () => {
if (ses) {
await ses.clearStorageData();
ses = null as any;
}
});
describe('ses.listWordsFromSpellCheckerDictionary', () => {
it('should successfully list words in custom dictionary', async () => {
const words = ['foo', 'bar', 'baz'];
const results = words.map(word => ses.addWordToSpellCheckerDictionary(word));
expect(results).to.eql([true, true, true]);
const wordList = await ses.listWordsInSpellCheckerDictionary();
expect(wordList).to.have.deep.members(words);
});
it('should return an empty array if no words are added', async () => {
const wordList = await ses.listWordsInSpellCheckerDictionary();
expect(wordList).to.have.length(0);
});
});
describe('ses.addWordToSpellCheckerDictionary', () => {
it('should successfully add word to custom dictionary', async () => {
const result = ses.addWordToSpellCheckerDictionary('foobar');
expect(result).to.equal(true);
const wordList = await ses.listWordsInSpellCheckerDictionary();
expect(wordList).to.eql(['foobar']);
});
it('should fail for an empty string', async () => {
const result = ses.addWordToSpellCheckerDictionary('');
expect(result).to.equal(false);
const wordList = await ses.listWordsInSpellCheckerDictionary;
expect(wordList).to.have.length(0);
});
// remove API will always return false because we can't add words
it('should fail for non-persistent sessions', async () => {
const tempSes = session.fromPartition('temporary');
const result = tempSes.addWordToSpellCheckerDictionary('foobar');
expect(result).to.equal(false);
});
});
describe('ses.removeWordFromSpellCheckerDictionary', () => {
it('should successfully remove words to custom dictionary', async () => {
const result1 = ses.addWordToSpellCheckerDictionary('foobar');
expect(result1).to.equal(true);
const wordList1 = await ses.listWordsInSpellCheckerDictionary();
expect(wordList1).to.eql(['foobar']);
const result2 = ses.removeWordFromSpellCheckerDictionary('foobar');
expect(result2).to.equal(true);
const wordList2 = await ses.listWordsInSpellCheckerDictionary();
expect(wordList2).to.have.length(0);
});
it('should fail for words not in custom dictionary', () => {
const result2 = ses.removeWordFromSpellCheckerDictionary('foobar');
expect(result2).to.equal(false);
});
});
});
});
};
describe('ses.addWordToSpellCheckerDictionary', () => {
it('should successfully add word to custom dictionary', async () => {
const result = ses.addWordToSpellCheckerDictionary('foobar');
expect(result).to.equal(true);
const wordList = await ses.listWordsInSpellCheckerDictionary();
expect(wordList).to.eql(['foobar']);
});
it('should fail for an empty string', async () => {
const result = ses.addWordToSpellCheckerDictionary('');
expect(result).to.equal(false);
const wordList = await ses.listWordsInSpellCheckerDictionary;
expect(wordList).to.have.length(0);
});
// remove API will always return false because we can't add words
it('should fail for non-persistent sessions', async () => {
const tempSes = session.fromPartition('temporary');
const result = tempSes.addWordToSpellCheckerDictionary('foobar');
expect(result).to.equal(false);
});
});
describe('ses.removeWordFromSpellCheckerDictionary', () => {
it('should successfully remove words to custom dictionary', async () => {
const result1 = ses.addWordToSpellCheckerDictionary('foobar');
expect(result1).to.equal(true);
const wordList1 = await ses.listWordsInSpellCheckerDictionary();
expect(wordList1).to.eql(['foobar']);
const result2 = ses.removeWordFromSpellCheckerDictionary('foobar');
expect(result2).to.equal(true);
const wordList2 = await ses.listWordsInSpellCheckerDictionary();
expect(wordList2).to.have.length(0);
});
it('should fail for words not in custom dictionary', () => {
const result2 = ses.removeWordFromSpellCheckerDictionary('foobar');
expect(result2).to.equal(false);
});
});
});
generateSpecs('without sandbox', false);
generateSpecs('with sandbox', true);
});

View File

@@ -721,5 +721,27 @@ describe('<webview> tag', function () {
})`);
expect(message).to.equal('hi');
});
it('emits focus event when contextIsolation is enabled', async () => {
const w = new BrowserWindow({
show: false,
webPreferences: {
webviewTag: true,
contextIsolation: true
}
});
await w.loadURL('about:blank');
await w.webContents.executeJavaScript(`new Promise((resolve, reject) => {
const webview = new WebView()
webview.setAttribute('src', 'about:blank')
webview.addEventListener('dom-ready', () => {
webview.focus()
})
webview.addEventListener('focus', () => {
resolve();
})
document.body.appendChild(webview)
})`);
});
});
});

View File

@@ -0,0 +1 @@
window.electron = require('electron');

View File

@@ -7619,13 +7619,6 @@ strip-json-comments@~2.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
sumchecker@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-2.0.2.tgz#0f42c10e5d05da5d42eea3e56c3399a37d6c5b3e"
integrity sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4=
dependencies:
debug "^2.2.0"
supports-color@^4.1.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b"