mirror of
https://github.com/electron/electron.git
synced 2026-02-26 03:01:17 -05:00
Compare commits
30 Commits
v13.0.0-be
...
v13.0.0-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
323de2fbe7 | ||
|
|
cd673a3697 | ||
|
|
0097a370e3 | ||
|
|
692c510867 | ||
|
|
e6f15c4380 | ||
|
|
cce8dda92f | ||
|
|
034f750c46 | ||
|
|
120a8acfd0 | ||
|
|
01992fd65c | ||
|
|
af6a5e6c30 | ||
|
|
caac2e8fc7 | ||
|
|
9a4a049498 | ||
|
|
68059d69a5 | ||
|
|
0f7334a77e | ||
|
|
eee8bee71b | ||
|
|
2795185d46 | ||
|
|
877f096d6b | ||
|
|
b33bb3a860 | ||
|
|
86f4126051 | ||
|
|
f895162234 | ||
|
|
05e41f89b4 | ||
|
|
d7378f953a | ||
|
|
ed0b654fee | ||
|
|
c1c8cbf995 | ||
|
|
ab3aa3581a | ||
|
|
20a43d9a72 | ||
|
|
27d04084c2 | ||
|
|
46649965c9 | ||
|
|
bea84969d6 | ||
|
|
ae67ec24f3 |
2
DEPS
2
DEPS
@@ -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':
|
||||
|
||||
@@ -1 +1 @@
|
||||
13.0.0-beta.21
|
||||
13.0.0-beta.27
|
||||
@@ -5,7 +5,7 @@
|
||||
[](https://david-dm.org/electron/electron?type=dev)
|
||||
[](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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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
169
docs/tutorial/sandbox.md
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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()
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
59
patches/chromium/cherry-pick-8089dbfc616f.patch
Normal file
59
patches/chromium/cherry-pick-8089dbfc616f.patch
Normal 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_);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
262
patches/node/node-api_faster_threadsafe_function.patch
Normal file
262
patches/node/node-api_faster_threadsafe_function.patch
Normal 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))
|
||||
|
||||
31
script/release/get-url-hash.js
Normal file
31
script/release/get-url-hash.js
Normal 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -135,8 +135,7 @@ async function pushRelease (branch) {
|
||||
|
||||
async function runReleaseBuilds (branch) {
|
||||
await ciReleaseBuild(branch, {
|
||||
ghRelease: true,
|
||||
automaticRelease: args.automaticRelease
|
||||
ghRelease: true
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
29
spec-main/fixtures/crash-cases/js-execute-iframe/index.html
Normal file
29
spec-main/fixtures/crash-cases/js-execute-iframe/index.html
Normal 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>
|
||||
51
spec-main/fixtures/crash-cases/js-execute-iframe/index.js
Normal file
51
spec-main/fixtures/crash-cases/js-execute-iframe/index.js
Normal 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');
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>HELLO</body>
|
||||
</html>
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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)
|
||||
})`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
1
spec/fixtures/module/preload-electron.js
vendored
Normal file
1
spec/fixtures/module/preload-electron.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
window.electron = require('electron');
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user