Compare commits

..

42 Commits

Author SHA1 Message Date
Electron Bot
edb65a07f1 Bump v15.0.0-alpha.1 2021-07-21 15:05:18 -07:00
Samuel Attard
82b5fbc396 build: handle alpha tag existing before beta line has finished 2021-07-21 14:50:06 -07:00
Keeley Hammond
19820fc2a7 chore: add additional crash key to gin::Wrappable (#30161) 2021-07-21 09:33:25 -04:00
Electron Bot
adc3f39a9e Bump v15.0.0-nightly.20210721 2021-07-21 06:02:02 -07:00
Samuel Attard
9fe70c5580 build: handle release failure by existing with code 1 (#30216) 2021-07-21 00:45:57 -07:00
Electron Bot
ea69da279f Bump v15.0.0-nightly.20210720 2021-07-20 06:02:23 -07:00
Keeley Hammond
deb75ceaa5 build: update version-bumper to support alpha (#30165)
* build: update version-bumper to support alpha

* build: seperate alpha bump version tests

For easier deletion. If we want to continue supporting an alpha channel,
they can be reintegrated with main tests.

* chore: fix regex

Co-authored-by: Samuel Attard <sam@electronjs.org>

Co-authored-by: Samuel Attard <sam@electronjs.org>
2021-07-19 17:58:15 -07:00
Jeremy Rose
d35fb2a2e3 docs: mention sandboxing in security docs (#30147) 2021-07-19 12:45:47 -07:00
Jeremy Rose
c9ba0d02d7 feat: support crashpad on linux (#29719) 2021-07-19 10:11:10 -07:00
Jeremy Rose
612361c4da chore: remove unused getWebPreferences method (#30160) 2021-07-19 09:29:23 -07:00
Mark Lee
a3298424b3 docs: update default branch for Electron Packager API links (#30175) 2021-07-19 09:25:05 -07:00
Jota
9441ff747d docs: Ffx broken context isolation link in sandbox docs (#30177) 2021-07-19 09:24:27 -07:00
Electron Bot
d4b2f69f36 Bump v15.0.0-nightly.20210719 2021-07-19 06:02:42 -07:00
Electron Bot
cfb2829634 Bump v15.0.0-nightly.20210716 2021-07-16 06:01:06 -07:00
Jeremy Rose
0d9e6f29ba fix: allow colored tray titles when font type is specified (#30146) 2021-07-15 16:45:20 -07:00
Shelley Vohr
1bb689e6dd fix: BrowserWindow transparency not working (#30136) 2021-07-15 16:18:39 -04:00
electron-roller[bot]
063ac19712 chore: bump node to v16.5.0 (main) (#30031)
* chore: bump node in DEPS to v16.4.2

* chore: update patches

* ci: run main and remote woa tests separately

* chore: bump node in DEPS to v16.5.0

* build: restore libplatform headers in distribution

https://github.com/nodejs/node/pull/39288

* build: pass directory instead of list of files to js2c.py

https://github.com/nodejs/node/pull/39069

* chore: various BoringSSL/OpenSSL upstreams

- https://github.com/nodejs/node/pull/39136
- https://github.com/nodejs/node/pull/39138
- https://github.com/nodejs/node/pull/39054

* test: move debugger test case to parallel

https://github.com/nodejs/node/pull/39300

* chore: fixup patch indices

* build: pass directory instead of list of files to js2c.py

https://github.com/nodejs/node/pull/39069

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2021-07-15 11:25:00 -04:00
Electron Bot
849a3b6f81 Bump v15.0.0-nightly.20210715 2021-07-15 07:13:56 -07:00
David Sanders
341b370213 fix: handle redirects within registered protocols (#29796) 2021-07-15 20:14:46 +09:00
Shelley Vohr
3f38681c55 fix: double traffic lights on exit fullscreen (#30114) 2021-07-14 16:45:12 -07:00
Cheng Zhao
05ba6359d0 feat: add signal option to dialog.showMessageBox (#26102)
* mac: add dialog.closeMessageBox API

* win: Implement dialog.closeMessageBox

* mac: Return cancelId with closeMessageBox

* gtk: Implement dialog.closeMessageBox

* win: Fix 32bit build

* win: Reduce the scope of lock

* fix: Build error after rebase

* feat: Use AbortSignal to close message box

* chore: silently handle duplicate ID

* win: Add more notes about the threads

* chore: apply reviews

* fix: base::NoDestructor should be warpped in function

* chore: fix style on windows
2021-07-15 07:59:27 +09:00
George Xu
4b780f9770 docs: update supported versions to match new release cadence (#30121) 2021-07-14 15:38:28 -07:00
Utkarsh Dixit
b2da2f759a docs: add runtime.reload as supported extension api (#29925) 2021-07-14 15:37:53 -07:00
Robo
4931c055a9 spec: disable flaky fullscreen test (#30141) 2021-07-14 15:26:09 -07:00
Antón Molleda
a855aa34d9 docs: fix fiddle path (#30139)
This is breaking the build in `electron/electronjs.org-new` and will
most likely not work when clicking the "Fiddle" button.

Rel: https://github.com/electron/electronjs.org-new/pull/65
2021-07-14 16:16:59 -04:00
Jeremy Rose
bec47f54f4 fix: use correct userData path when unbundled (#30113) 2021-07-14 13:10:37 -07:00
Electron Bot
4db7221c7d Bump v15.0.0-nightly.20210714 2021-07-14 06:02:07 -07:00
Davenury
75b4267aa9 Update quick-start.md (#30064)
Change app-quit link definition, so both window-all-closed and app-quit redirects to appropriate sites.
2021-07-14 20:59:32 +09:00
Milan Burda
c0995b8dff docs: add <webview> 'did-attach' event documentation (#29899) 2021-07-14 20:59:20 +09:00
Cheng Zhao
637ba48b42 fix: pressing ESC should exit fullscreen from webview (#30063) 2021-07-14 20:51:26 +09:00
Jeremy Rose
4d0475c9ce feat: expose location and modifiers on before-input-event (#29850)
* feat: expose location and modifiers on before-input-event

* lint
2021-07-14 20:50:02 +09:00
Keeley Hammond
1897b14af3 chore: update releases to 8 weeks in CONTRIBUTING (#30115)
* chore: update releases to 8 weeks in CONTRIBUTING

* chore: update support.md for four version support
2021-07-13 13:57:19 -07:00
Sofia Nguy
eb2efd4b7e docs: Update timeline for E15 alpha announcement (#30109)
* docs: Update timeline for E15 alpha announcement

* fix line break
2021-07-13 13:54:13 -07:00
1akshat1
d267f979b7 feat: continue-activity event is extended to support webpageURL property (#30042)
Co-authored-by: Akshat Malik <amalik@microstrategy.com>
Co-authored-by: Jeremy Rose <nornagon@nornagon.net>
2021-07-13 13:21:33 -07:00
Electron Bot
3582a513ca Bump v15.0.0-nightly.20210713 2021-07-13 06:02:20 -07:00
Milan Burda
9959f01e4c spec: fix check for electron_common_testing binding in logging-spec.ts (#30086) 2021-07-12 22:11:19 -07:00
Robo
19a6286dfd chore: cherry-pick 9bab573a37 from chromium (#30084)
Refs https://chromium-review.googlesource.com/c/chromium/src/+/3010140
2021-07-12 18:35:29 -07:00
Jeremy Rose
459a8417e3 test: disable failing node tests (#30096) 2021-07-13 09:40:58 +09:00
Jeremy Rose
96ff8d7bd7 build: decode error output as utf8 (#30093) 2021-07-12 15:22:26 -07:00
Jeremy Rose
e26901aba4 fix: crash when invoking login callback synchronously (#30068) 2021-07-12 12:33:41 -07:00
Jeremy Rose
0cb5631b0b fix: return RGBA values from getSystemColor (#30055) 2021-07-12 11:08:10 -07:00
Robo
36079b822a chore: disable fullscreen test on mac arm (#30083) 2021-07-12 08:53:35 -07:00
100 changed files with 2103 additions and 1129 deletions

View File

@@ -45,7 +45,7 @@ executors:
type: enum
enum: ["medium", "xlarge", "2xlarge+"]
docker:
- image: electron.azurecr.io/build:4fc81b50f9c0980699d329bc32062fac20a26701
- image: electron.azurecr.io/build:fe71f448c9b00708c7a8a67a0210bcef5055ac64
resource_class: << parameters.size >>
macos:

View File

@@ -22,7 +22,7 @@ Issues are created [here](https://github.com/electron/electron/issues/new).
### Issue Closure
Bug reports will be closed if the issue has been inactive and the latest affected version no longer receives support. At the moment, Electron maintains its three latest major versions, with a new major version being released every 12 weeks. (For more information on Electron's release cadence, see [this blog post](https://electronjs.org/blog/12-week-cadence).)
Bug reports will be closed if the issue has been inactive and the latest affected version no longer receives support. At the moment, Electron maintains its three latest major versions, with a new major version being released every 8 weeks. (For more information on Electron's release cadence, see [this blog post](https://electronjs.org/blog/8-week-cadence).)
_If an issue has been closed and you still feel it's relevant, feel free to ping a maintainer or add a comment!_

2
DEPS
View File

@@ -17,7 +17,7 @@ vars = {
'chromium_version':
'93.0.4566.0',
'node_version':
'v16.4.1',
'v16.5.0',
'nan_version':
# The following commit hash of NAN is v2.14.2 with *only* changes to the
# test suite. This should be updated to a specific tag when one becomes

View File

@@ -1 +1 @@
15.0.0-nightly.20210712
15.0.0-alpha.1

View File

@@ -53,6 +53,16 @@ steps:
env:
APPVEYOR_TOKEN: $(APPVEYOR_TOKEN)
- powershell: |
$localArtifactPath = "$pwd\src\pdb.zip"
$serverArtifactPath = "$env:APPVEYOR_URL/buildjobs/$env:APPVEYOR_JOB_ID/artifacts/pdb.zip"
Invoke-RestMethod -Method Get -Uri $serverArtifactPath -OutFile $localArtifactPath -Headers @{ "Authorization" = "Bearer $env:APPVEYOR_TOKEN" }
cd src
& "${env:ProgramFiles(x86)}\7-Zip\7z.exe" x -y pdb.zip
displayName: 'Download pdb files for detailed stacktraces'
env:
APPVEYOR_TOKEN: $(APPVEYOR_TOKEN)
- powershell: |
New-Item src\out\Default\gen\node_headers\Release -Type directory
Copy-Item -path src\out\Default\electron.lib -destination src\out\Default\gen\node_headers\Release\node.lib
@@ -63,15 +73,30 @@ steps:
set npm_config_nodedir=%cd%\out\Default\gen\node_headers
set npm_config_arch=arm64
cd electron
# CalculateNativeWinOcclusion is disabled due to https://bugs.chromium.org/p/chromium/issues/detail?id=1139022
node script/yarn test -- --enable-logging --verbose --disable-features=CalculateNativeWinOcclusion
displayName: 'Run Electron tests'
node script/yarn test --runners=main --runTestFilesSeperately --enable-logging --disable-features=CalculateNativeWinOcclusion
displayName: 'Run Electron Main process tests'
env:
ELECTRON_ENABLE_STACK_DUMPING: true
ELECTRON_OUT_DIR: Default
IGNORE_YARN_INSTALL_ERROR: 1
ELECTRON_TEST_RESULTS_DIR: junit
MOCHA_MULTI_REPORTERS: 'mocha-junit-reporter, tap'
MOCHA_REPORTER: mocha-multi-reporters
- script: |
cd src
set npm_config_nodedir=%cd%\out\Default\gen\node_headers
set npm_config_arch=arm64
cd electron
node script/yarn test --runners=remote --enable-logging --disable-features=CalculateNativeWinOcclusion
displayName: 'Run Electron Remote based tests'
env:
ELECTRON_OUT_DIR: Default
IGNORE_YARN_INSTALL_ERROR: 1
ELECTRON_TEST_RESULTS_DIR: junit
MOCHA_MULTI_REPORTERS: 'mocha-junit-reporter, tap'
MOCHA_REPORTER: mocha-multi-reporters
condition: always()
- task: PublishTestResults@2
displayName: 'Publish Test Results'

View File

@@ -16,5 +16,5 @@ try:
subprocess.check_output(args, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
error_msg = "NPM script '{}' failed with code '{}':\n".format(sys.argv[2], e.returncode)
print(error_msg + e.output.decode('ascii'))
print(error_msg + e.output.decode('utf8'))
sys.exit(e.returncode)

View File

@@ -31,12 +31,6 @@ PATHS_TO_SKIP = [
# //chrome/browser/resources/ssl/ssl_error_assistant, but we don't need to
# ship it.
'pyproto',
# On Windows, this binary doesn't exist (the crashpad handler is built-in).
# On MacOS, the binary is called 'chrome_crashpad_handler' and is inside the
# app bundle.
# On Linux, we don't use crashpad, but this binary is still built for some
# reason. Exclude it from the zip.
'./crashpad_handler',
# Skip because these are outputs that we don't need.
'resources/inspector',
'gen/third_party/devtools-frontend/src',

View File

@@ -161,6 +161,8 @@ Returns:
[`NSUserActivity.activityType`][activity-type].
* `userInfo` unknown - Contains app-specific state stored by the activity on
another device.
* `details` Object
* `webpageURL` String (optional) - A string identifying the URL of the webpage accessed by the activity on another device, if available.
Emitted during [Handoff][handoff] when an activity from a different device wants
to be resumed. You should call `event.preventDefault()` if you want to handle

View File

@@ -273,6 +273,11 @@ If `browserWindow` is not shown dialog will not be attached to it. In such case
will result in one button labeled "OK".
* `defaultId` Integer (optional) - Index of the button in the buttons array which will
be selected by default when the message box opens.
* `signal` AbortSignal (optional) - Pass an instance of [AbortSignal][] to
optionally close the message box, the message box will behave as if it was
cancelled by the user. On macOS, `signal` does not work with message boxes
that do not have a parent window, since those message boxes run
synchronously due to platform limitations.
* `title` String (optional) - Title of the message box, some platforms will not show it.
* `detail` String (optional) - Extra information of the message.
* `checkboxLabel` String (optional) - If provided, the message box will
@@ -360,3 +365,5 @@ window is provided.
You can call `BrowserWindow.getCurrentWindow().setSheetOffset(offset)` to change
the offset from the window frame where sheets are attached.
[AbortSignal]: https://nodejs.org/api/globals.html#globals_class_abortsignal

View File

@@ -78,6 +78,7 @@ The following methods of `chrome.runtime` are supported:
- `chrome.runtime.getURL`
- `chrome.runtime.connect`
- `chrome.runtime.sendMessage`
- `chrome.runtime.reload`
The following events of `chrome.runtime` are supported:

View File

@@ -449,6 +449,8 @@ Returns:
* `control` Boolean - Equivalent to [KeyboardEvent.controlKey][keyboardevent].
* `alt` Boolean - Equivalent to [KeyboardEvent.altKey][keyboardevent].
* `meta` Boolean - Equivalent to [KeyboardEvent.metaKey][keyboardevent].
* `location` Number - Equivalent to [KeyboardEvent.location][keyboardevent].
* `modifiers` String[] - See [InputEvent.modifiers](structures/input-event.md).
Emitted before dispatching the `keydown` and `keyup` events in the page.
Calling `event.preventDefault` will prevent the page `keydown`/`keyup` events

View File

@@ -710,6 +710,10 @@ Corresponds to the points in time when the spinner of the tab starts spinning.
Corresponds to the points in time when the spinner of the tab stops spinning.
### Event: 'did-attach'
Fired when attached to the embedder web contents.
### Event: 'dom-ready'
Fired when document in the given frame is loaded.

View File

@@ -133,7 +133,7 @@ are likely using [`electron-packager`], which includes [`electron-osx-sign`] and
If you're using Packager's API, you can pass [in configuration that both signs
and notarizes your
application](https://electron.github.io/electron-packager/master/interfaces/electronpackager.options.html).
application](https://electron.github.io/electron-packager/main/interfaces/electronpackager.options.html).
```js
const packager = require('electron-packager')

View File

@@ -200,6 +200,6 @@ Run the example using Electron Fiddle and then click the "Toggle Dark Mode" butt
[system-wide-dark-mode]: https://developer.apple.com/design/human-interface-guidelines/macos/visual-design/dark-mode/
[electron-forge]: https://www.electronforge.io/
[electron-packager]: https://github.com/electron/electron-packager
[packager-darwindarkmode-api]: https://electron.github.io/electron-packager/master/interfaces/electronpackager.options.html#darwindarkmodesupport
[packager-darwindarkmode-api]: https://electron.github.io/electron-packager/main/interfaces/electronpackager.options.html#darwindarkmodesupport
[prefers-color-scheme]: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme
[event-listeners]: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

View File

@@ -1,23 +1,27 @@
# Electron Release Timelines
Special notes:
* The `-beta.1` and `stable` dates are our solid release dates.
* We strive for weekly beta releases, however we often release more betas than scheduled.
* All dates are our goals but there may be reasons for adjusting the stable deadline, such as security bugs.
* Take a look at the [5.0.0 Timeline blog post](https://electronjs.org/blog/electron-5-0-timeline) for info about publicizing our release dates.
* Since Electron 6.0, we've been targeting every other Chromium version and releasing our stable on the same day as Chrome stable. You can reference Chromium's release schedule [here](https://chromiumdash.appspot.com/schedule). See [Electron's new release cadence blog post](https://www.electronjs.org/blog/12-week-cadence) for more details on our release schedule.
* Electron 15.0 only will include a special Alpha release. Starting in Electron 16.0, we will release on an 8-week cadence. See [Electron's new 8-week cadence blog post](https://www.electronjs.org/blog/8-week-cadence) for more details.
| Version | -beta.1 | Stable | Chrome | Node |
| ------- | ------- | ------ | ------ | ---- |
| 2.0.0 | 2018-02-21 | 2018-05-01 | M61 | v8.9 |
| 3.0.0 | 2018-06-21 | 2018-09-18 | M66 | v10.2 |
| 4.0.0 | 2018-10-11 | 2018-12-20 | M69 | v10.11 |
| 5.0.0 | 2019-01-22 | 2019-04-24 | M73 | v12.0 |
| 6.0.0 | 2019-05-01 | 2019-07-30 | M76 | v12.4 |
| 7.0.0 | 2019-08-01 | 2019-10-22 | M78 | v12.8 |
| 8.0.0 | 2019-10-24 | 2020-02-04 | M80 | v12.13 |
| 9.0.0 | 2020-02-06 | 2020-05-19 | M83 | v12.14 |
| 10.0.0 | 2020-05-21 | 2020-08-25 | M85 | v12.16 |
| 11.0.0 | 2020-08-27 | 2020-11-17 | M87 | v12.18 |
| 12.0.0 | 2020-11-19 | 2021-03-02 | M89 | v14.16 |
| 13.0.0 | 2021-03-04 | 2021-05-25 | M91 | v14.16 |
| 14.0.0 | 2021-05-27 | 2021-08-31 | M93 | TBD |
| Electron | Alpha | Beta | Stable | Chrome | Node |
| ------- | ----- | ------- | ------ | ------ | ---- |
| 2.0.0 | -- | 2018-Feb-21 | 2018-May-01 | M61 | v8.9 |
| 3.0.0 | -- | 2018-Jun-21 | 2018-Sep-18 | M66 | v10.2 |
| 4.0.0 | -- | 2018-Oct-11 | 2018-Dec-20 | M69 | v10.11 |
| 5.0.0 | -- | 2019-Jan-22 | 2019-Apr-24 | M73 | v12.0 |
| 6.0.0 | -- | 2019-May-01 | 2019-Jul-30 | M76 | v12.4 |
| 7.0.0 | -- | 2019-Aug-01 | 2019-Oct-22 | M78 | v12.8 |
| 8.0.0 | -- | 2019-Oct-24 | 2020-Feb-04 | M80 | v12.13 |
| 9.0.0 | -- | 2020-Feb-06 | 2020-May-19 | M83 | v12.14 |
| 10.0.0 | -- | 2020-May-21 | 2020-Aug-25 | M85 | v12.16 |
| 11.0.0 | -- | 2020-Aug-27 | 2020-Nov-17 | M87 | v12.18 |
| 12.0.0 | -- | 2020-Nov-19 | 2021-Mar-02 | M89 | v14.16 |
| 13.0.0 | -- | 2021-Mar-04 | 2021-May-25 | M91 | v14.16 |
| 14.0.0 | -- | 2021-May-27 | 2021-Aug-31 | M93 | TBD |
| 15.0.0 | 2021-Jul-20 | 2021-Sep-01 | 2021-Sep-21 | M94 | TBD |

View File

@@ -168,7 +168,7 @@ After you start your electron app, you can now enter in a URL in your browser th
can leave it empty.
-->
```fiddle docs/fiddles/system/protocol-handler/launch-app-from-url-in-another-app
```fiddle docs/fiddles/system/protocol-handler/launch-app-from-URL-in-another-app
```

View File

@@ -223,7 +223,7 @@ app.on('window-all-closed', function () {
[node-platform]: https://nodejs.org/api/process.html#process_process_platform
[window-all-closed]: ../api/app.md#event-window-all-closed
[window-all-closed]: ../api/app.md#appquit
[app-quit]: ../api/app.md#appquit
#### Open a window if none are open (macOS)

View File

@@ -79,7 +79,7 @@ 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.
[`contextIsolation`][context-isolation] is enabled.
## Configuring the sandbox

View File

@@ -44,7 +44,7 @@ Chromium shared library and Node.js. Vulnerabilities affecting these components
may impact the security of your application. By updating Electron to the latest
version, you ensure that critical vulnerabilities (such as *nodeIntegration bypasses*)
are already patched and cannot be exploited in your application. For more information,
see "[Use a current version of Electron](#15-use-a-current-version-of-electron)".
see "[Use a current version of Electron](#16-use-a-current-version-of-electron)".
* **Evaluate your dependencies.** While NPM provides half a million reusable packages,
it is your responsibility to choose trusted 3rd-party libraries. If you use outdated
@@ -88,18 +88,19 @@ You should at least follow these steps to improve the security of your applicati
1. [Only load secure content](#1-only-load-secure-content)
2. [Disable the Node.js integration in all renderers that display remote content](#2-do-not-enable-nodejs-integration-for-remote-content)
3. [Enable context isolation in all renderers that display remote content](#3-enable-context-isolation-for-remote-content)
4. [Use `ses.setPermissionRequestHandler()` in all sessions that load remote content](#4-handle-session-permission-requests-from-remote-content)
5. [Do not disable `webSecurity`](#5-do-not-disable-websecurity)
6. [Define a `Content-Security-Policy`](#6-define-a-content-security-policy) and use restrictive rules (i.e. `script-src 'self'`)
7. [Do not set `allowRunningInsecureContent` to `true`](#7-do-not-set-allowrunninginsecurecontent-to-true)
8. [Do not enable experimental features](#8-do-not-enable-experimental-features)
9. [Do not use `enableBlinkFeatures`](#9-do-not-use-enableblinkfeatures)
10. [`<webview>`: Do not use `allowpopups`](#10-do-not-use-allowpopups)
11. [`<webview>`: Verify options and params](#11-verify-webview-options-before-creation)
12. [Disable or limit navigation](#12-disable-or-limit-navigation)
13. [Disable or limit creation of new windows](#13-disable-or-limit-creation-of-new-windows)
14. [Do not use `openExternal` with untrusted content](#14-do-not-use-openexternal-with-untrusted-content)
15. [Use a current version of Electron](#15-use-a-current-version-of-electron)
4. [Enable sandboxing](#4-enable-sandboxing)
5. [Use `ses.setPermissionRequestHandler()` in all sessions that load remote content](#5-handle-session-permission-requests-from-remote-content)
6. [Do not disable `webSecurity`](#6-do-not-disable-websecurity)
7. [Define a `Content-Security-Policy`](#7-define-a-content-security-policy) and use restrictive rules (i.e. `script-src 'self'`)
8. [Do not set `allowRunningInsecureContent` to `true`](#8-do-not-set-allowrunninginsecurecontent-to-true)
9. [Do not enable experimental features](#9-do-not-enable-experimental-features)
10. [Do not use `enableBlinkFeatures`](#10-do-not-use-enableblinkfeatures)
11. [`<webview>`: Do not use `allowpopups`](#11-do-not-use-allowpopups)
12. [`<webview>`: Verify options and params](#12-verify-webview-options-before-creation)
13. [Disable or limit navigation](#13-disable-or-limit-navigation)
14. [Disable or limit creation of new windows](#14-disable-or-limit-creation-of-new-windows)
15. [Do not use `openExternal` with untrusted content](#15-do-not-use-openexternal-with-untrusted-content)
16. [Use a current version of Electron](#16-use-a-current-version-of-electron)
To automate the detection of misconfigurations and insecure patterns, it is
possible to use
@@ -239,7 +240,26 @@ and prevent the use of Node primitives `contextIsolation` **must** also be used.
For more information on what `contextIsolation` is and how to enable it please
see our dedicated [Context Isolation](context-isolation.md) document.
## 4) Handle Session Permission Requests From Remote Content
## 4) Enable Sandboxing
[Sandboxing](sandbox.md) is a Chromium feature that uses the operating system to
significantly limit what renderer processes have access to. You should enable
the sandbox in all renderers. Loading, reading or processing any untrusted
content in an unsandboxed process, including the main process, is not advised.
### How?
When creating a window, pass the `sandbox: true` option in `webPreferences`:
```js
const win = new BrowserWindow({
webPreferences: {
sandbox: true
}
})
```
## 5) Handle Session Permission Requests From Remote Content
You may have seen permission requests while using Chrome: They pop up whenever
the website attempts to use a feature that the user has to manually approve (
@@ -277,7 +297,7 @@ session
})
```
## 5) Do Not Disable WebSecurity
## 6) Do Not Disable WebSecurity
_Recommendation is Electron's default_
@@ -318,7 +338,7 @@ const mainWindow = new BrowserWindow()
<webview src="page.html"></webview>
```
## 6) Define a Content Security Policy
## 7) Define a Content Security Policy
A Content Security Policy (CSP) is an additional layer of protection against
cross-site-scripting attacks and data injection attacks. We recommend that they
@@ -374,7 +394,7 @@ on a page directly in the markup using a `<meta>` tag:
<meta http-equiv="Content-Security-Policy" content="default-src 'none'">
```
## 7) Do Not Set `allowRunningInsecureContent` to `true`
## 8) Do Not Set `allowRunningInsecureContent` to `true`
_Recommendation is Electron's default_
@@ -407,7 +427,7 @@ const mainWindow = new BrowserWindow({
const mainWindow = new BrowserWindow({})
```
## 8) Do Not Enable Experimental Features
## 9) Do Not Enable Experimental Features
_Recommendation is Electron's default_
@@ -439,7 +459,7 @@ const mainWindow = new BrowserWindow({
const mainWindow = new BrowserWindow({})
```
## 9) Do Not Use `enableBlinkFeatures`
## 10) Do Not Use `enableBlinkFeatures`
_Recommendation is Electron's default_
@@ -471,7 +491,7 @@ const mainWindow = new BrowserWindow({
const mainWindow = new BrowserWindow()
```
## 10) Do Not Use `allowpopups`
## 11) Do Not Use `allowpopups`
_Recommendation is Electron's default_
@@ -498,7 +518,7 @@ you know it needs that feature.
<webview src="page.html"></webview>
```
## 11) Verify WebView Options Before Creation
## 12) Verify WebView Options Before Creation
A WebView created in a renderer process that does not have Node.js integration
enabled will not be able to enable integration itself. However, a WebView will
@@ -545,7 +565,7 @@ app.on('web-contents-created', (event, contents) => {
Again, this list merely minimizes the risk, it does not remove it. If your goal
is to display a website, a browser will be a more secure option.
## 12) Disable or limit navigation
## 13) Disable or limit navigation
If your app has no need to navigate or only needs to navigate to known pages,
it is a good idea to limit navigation outright to that known scope, disallowing
@@ -589,7 +609,7 @@ app.on('web-contents-created', (event, contents) => {
})
```
## 13) Disable or limit creation of new windows
## 14) Disable or limit creation of new windows
If you have a known set of windows, it's a good idea to limit the creation of
additional windows in your app.
@@ -636,7 +656,7 @@ app.on('web-contents-created', (event, contents) => {
})
```
## 14) Do not use `openExternal` with untrusted content
## 15) Do not use `openExternal` with untrusted content
Shell's [`openExternal`][open-external] allows opening a given protocol URI with
the desktop's native utilities. On macOS, for instance, this function is similar
@@ -663,7 +683,7 @@ const { shell } = require('electron')
shell.openExternal('https://example.com/index.html')
```
## 15) Use a current version of Electron
## 16) Use a current version of Electron
You should strive for always using the latest available version of Electron.
Whenever a new major version is released, you should attempt to update your

View File

@@ -37,6 +37,13 @@ tools and resources.
## Supported Versions
_**Note:** Beginning in September 2021 with Electron 15, the Electron team
will temporarily support the latest **four** stable major versions. This
extended support is intended to help Electron developers transition to
the [new eight week release cadence](https://electronjs.org/blog/8-week-cadence), and will continue until May 2022, with
the release of Electron 19. At that time, the Electron team will drop support
back to the latest three stable major versions._
The latest three *stable* major versions are supported by the Electron team.
For example, if the latest release is 6.1.x, then the 5.0.x as well
as the 4.2.x series are supported. We only support the latest minor release
@@ -63,6 +70,7 @@ until the maintainers feel the maintenance burden is too high to continue doing
### Currently supported versions
* 14.x.y
* 13.x.y
* 12.x.y
* 11.x.y

View File

@@ -22,6 +22,11 @@ enum OpenFileDialogProperties {
dontAddToRecent = 1 << 8 // Windows
}
let nextId = 0;
const getNextId = function () {
return ++nextId;
};
const normalizeAccessKey = (text: string) => {
if (typeof text !== 'string') return text;
@@ -157,6 +162,7 @@ const messageBox = (sync: boolean, window: BrowserWindow | null, options?: Messa
let {
buttons = [],
cancelId,
signal,
checkboxLabel = '',
checkboxChecked,
defaultId = -1,
@@ -196,10 +202,21 @@ const messageBox = (sync: boolean, window: BrowserWindow | null, options?: Messa
}
}
// AbortSignal processing.
let id: number | undefined;
if (signal) {
// Generate an ID used for closing the message box.
id = getNextId();
// Close the message box when signal is aborted.
if (signal.aborted) { return Promise.resolve({ cancelId, checkboxChecked }); }
signal.addEventListener('abort', () => dialogBinding._closeMessageBox(id));
}
const settings = {
window,
messageBoxType,
buttons,
id,
defaultId,
cancelId,
noLink,

View File

@@ -1,6 +1,6 @@
{
"name": "electron",
"version": "15.0.0-nightly.20210712",
"version": "15.0.0-alpha.1",
"repository": "https://github.com/electron/electron",
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
"devDependencies": {

View File

@@ -101,3 +101,5 @@ build_do_not_depend_on_packed_resource_integrity.patch
refactor_restore_base_adaptcallbackforrepeating.patch
hack_to_allow_gclient_sync_with_host_os_mac_on_linux_in_ci.patch
don_t_run_pcscan_notifythreadcreated_if_pcscan_is_disabled.patch
set_svgimage_page_after_document_install.patch
add_gin_wrappable_crash_key.patch

View File

@@ -0,0 +1,44 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: VerteDinde <khammond@slack-corp.com>
Date: Thu, 15 Jul 2021 12:16:50 -0700
Subject: chore: add gin::wrappable wrapperinfo crash key
This patch adds an additional crash key for gin::Wrappable, to help
debug a crash that is occurring during garbage collection in SecondWeakCallback.
The crash seems to be due to a class that is holding a reference to
gin::Wrappable even after being deleted. This added crash key compares
the soon-to-be-deleted WrapperInfo with known WrapperInfo components to
help determine where the crash originated from.
This patch should not be upstreamed, and can be removed in Electron 15 and
beyond once we identify the cause of the crash.
diff --git a/gin/wrappable.cc b/gin/wrappable.cc
index fe07eb94a8e679859bba6d76ff0d6ee86bd0c67e..d0066fca501eae5be4177440b44dbecc8e34c897 100644
--- a/gin/wrappable.cc
+++ b/gin/wrappable.cc
@@ -8,6 +8,10 @@
#include "gin/object_template_builder.h"
#include "gin/per_isolate_data.h"
+#if !defined(MAS_BUILD)
+#include "electron/shell/common/crash_keys.h"
+#endif
+
namespace gin {
WrappableBase::WrappableBase() = default;
@@ -36,6 +40,12 @@ void WrappableBase::FirstWeakCallback(
void WrappableBase::SecondWeakCallback(
const v8::WeakCallbackInfo<WrappableBase>& data) {
WrappableBase* wrappable = data.GetParameter();
+
+#if !defined(MAS_BUILD)
+ WrapperInfo* info = static_cast<WrapperInfo*>(data.GetInternalField(0));
+ electron::crash_keys::SetCrashKeyForGinWrappable(info);
+#endif
+
delete wrappable;
}

View File

@@ -74,6 +74,32 @@ index 39557cce474439238255ecd28030215085db0c81..5b3f980837911c710686ab91a2a81c31
#if defined(OS_ANDROID)
// Used by WebView to sample crashes without generating the unwanted dumps. If
// the returned value is less than 100, crash dumping will be sampled to that
diff --git a/components/crash/core/app/crashpad_linux.cc b/components/crash/core/app/crashpad_linux.cc
index 5f97c1ef00d9c63a7b16265cc97d9f145adae550..8c3028f228373b5e1145fe3235dc06663f8b087f 100644
--- a/components/crash/core/app/crashpad_linux.cc
+++ b/components/crash/core/app/crashpad_linux.cc
@@ -165,6 +165,7 @@ base::FilePath PlatformCrashpadInitialization(
// where crash_reporter provides it's own values for lsb-release.
annotations["lsb-release"] = base::GetLinuxDistro();
#endif
+ crash_reporter_client->GetProcessSimpleAnnotations(&annotations);
std::vector<std::string> arguments;
if (crash_reporter_client->ShouldMonitorCrashHandlerExpensively()) {
@@ -186,6 +187,13 @@ base::FilePath PlatformCrashpadInitialization(
}
#endif
+ if (!crash_reporter_client->GetShouldRateLimit()) {
+ arguments.push_back("--no-rate-limit");
+ }
+ if (!crash_reporter_client->GetShouldCompressUploads()) {
+ arguments.push_back("--no-upload-gzip");
+ }
+
bool result =
client.StartHandler(handler_path, database_path, metrics_path, url,
annotations, arguments, false, false);
diff --git a/components/crash/core/app/crashpad_mac.mm b/components/crash/core/app/crashpad_mac.mm
index e3fc1fb2bcab31d6a7cb325a892acb26dc00d4e4..fd654d6e514de416457c283caeb1895dba6286e1 100644
--- a/components/crash/core/app/crashpad_mac.mm

View File

@@ -0,0 +1,48 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fredrik=20S=C3=B6derqvist?= <fs@opera.com>
Date: Fri, 9 Jul 2021 08:44:55 +0000
Subject: Set SVGImage::page_ after document install
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
We can end up having the associated ImageResource call
SVGImage::ResetAnimation() before the Document has been associated with
the SVGImage's LocalFrame, but after the link to the initial Document
was severed, if a GC is triggered within that window and ends up
collecting the last observer of the ImageResource.
By assigning |SVGImage::page_| after the installing the document, we
close this hole since SVGImage::RootElement() (called by
SVGImage::ResetAnimation()) will now observe a null Page and return null
without attempting to dereference the document.
Bug: 1216190
Change-Id: I26e08848e5b9bd52e3377841eee35e4acc03d320
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3010140
Reviewed-by: Stephen Chenney <schenney@chromium.org>
Commit-Queue: Fredrik Söderquist <fs@opera.com>
Cr-Commit-Position: refs/heads/master@{#899922}
diff --git a/third_party/blink/renderer/core/svg/graphics/svg_image.cc b/third_party/blink/renderer/core/svg/graphics/svg_image.cc
index b23ad2192bec4d1cac9d704074d12c9e00d4d2f5..ff2bf69be27f0afcb6a9909e716495e8d4a127ef 100644
--- a/third_party/blink/renderer/core/svg/graphics/svg_image.cc
+++ b/third_party/blink/renderer/core/svg/graphics/svg_image.cc
@@ -851,12 +851,15 @@ Image::SizeAvailability SVGImage::DataChanged(bool all_data_received) {
// SVG Images are transparent.
frame->View()->SetBaseBackgroundColor(Color::kTransparent);
- page_ = page;
-
TRACE_EVENT0("blink", "SVGImage::dataChanged::load");
frame->ForceSynchronousDocumentInstall("image/svg+xml", Data());
+ // Set up our Page reference after installing our document. This avoids
+ // tripping on a non-existing (null) Document if a GC is triggered during the
+ // set up and ends up collecting the last owner/observer of this image.
+ page_ = page;
+
// Intrinsic sizing relies on computed style (e.g. font-size and
// writing-mode).
frame->GetDocument()->UpdateStyleAndLayoutTree();

View File

@@ -888,10 +888,10 @@ index 0000000000000000000000000000000000000000..2c9d2826c85bdd033f1df1d6188df636
+}
diff --git a/filenames.json b/filenames.json
new file mode 100644
index 0000000000000000000000000000000000000000..7225bdb6b281837253978430a665e9ee5a9e0646
index 0000000000000000000000000000000000000000..6ce2f7899ffc39e145a767685a51597fa4473e50
--- /dev/null
+++ b/filenames.json
@@ -0,0 +1,530 @@
@@ -0,0 +1,544 @@
+// This file is automatically generated by generate_gn_filenames_json.py
+// DO NOT EDIT
+{
@@ -926,6 +926,14 @@ index 0000000000000000000000000000000000000000..7225bdb6b281837253978430a665e9ee
+ ]
+ },
+ {
+ "dest_dir": "include/node//libplatform/",
+ "files": [
+ "//v8/include/libplatform/libplatform-export.h",
+ "//v8/include/libplatform/libplatform.h",
+ "//v8/include/libplatform/v8-tracing.h"
+ ]
+ },
+ {
+ "dest_dir": "include/node//uv/",
+ "files": [
+ "deps/uv/include/uv/aix.h",
@@ -953,250 +961,260 @@ index 0000000000000000000000000000000000000000..7225bdb6b281837253978430a665e9ee
+ }
+ ],
+ "library_files": [
+ "lib/internal/bootstrap/environment.js",
+ "lib/internal/bootstrap/loaders.js",
+ "lib/internal/bootstrap/node.js",
+ "lib/internal/bootstrap/pre_execution.js",
+ "lib/internal/bootstrap/switches/does_own_process_state.js",
+ "lib/internal/bootstrap/switches/does_not_own_process_state.js",
+ "lib/internal/bootstrap/switches/is_main_thread.js",
+ "lib/internal/bootstrap/switches/is_not_main_thread.js",
+ "lib/internal/per_context/primordials.js",
+ "lib/internal/per_context/domexception.js",
+ "lib/internal/per_context/messageport.js",
+ "lib/async_hooks.js",
+ "lib/assert.js",
+ "lib/assert/strict.js",
+ "lib/buffer.js",
+ "lib/child_process.js",
+ "lib/console.js",
+ "lib/constants.js",
+ "lib/crypto.js",
+ "lib/cluster.js",
+ "lib/diagnostics_channel.js",
+ "lib/dgram.js",
+ "lib/dns.js",
+ "lib/dns/promises.js",
+ "lib/domain.js",
+ "lib/events.js",
+ "lib/fs.js",
+ "lib/fs/promises.js",
+ "lib/http.js",
+ "lib/http2.js",
+ "lib/_http_agent.js",
+ "lib/_http_client.js",
+ "lib/_http_common.js",
+ "lib/_http_incoming.js",
+ "lib/_http_outgoing.js",
+ "lib/_http_server.js",
+ "lib/https.js",
+ "lib/inspector.js",
+ "lib/module.js",
+ "lib/net.js",
+ "lib/os.js",
+ "lib/path.js",
+ "lib/path/posix.js",
+ "lib/path/win32.js",
+ "lib/perf_hooks.js",
+ "lib/process.js",
+ "lib/punycode.js",
+ "lib/querystring.js",
+ "lib/readline.js",
+ "lib/repl.js",
+ "lib/stream.js",
+ "lib/stream/promises.js",
+ "lib/_stream_readable.js",
+ "lib/_stream_writable.js",
+ "lib/_stream_duplex.js",
+ "lib/_stream_transform.js",
+ "lib/_stream_passthrough.js",
+ "lib/_stream_wrap.js",
+ "lib/string_decoder.js",
+ "lib/sys.js",
+ "lib/timers/promises.js",
+ "lib/timers.js",
+ "lib/tls.js",
+ "lib/_tls_common.js",
+ "lib/_tls_wrap.js",
+ "lib/trace_events.js",
+ "lib/tty.js",
+ "lib/url.js",
+ "lib/events.js",
+ "lib/repl.js",
+ "lib/util.js",
+ "lib/util/types.js",
+ "lib/v8.js",
+ "lib/dgram.js",
+ "lib/vm.js",
+ "lib/wasi.js",
+ "lib/stream.js",
+ "lib/child_process.js",
+ "lib/assert.js",
+ "lib/_tls_wrap.js",
+ "lib/http2.js",
+ "lib/inspector.js",
+ "lib/os.js",
+ "lib/_http_server.js",
+ "lib/console.js",
+ "lib/perf_hooks.js",
+ "lib/readline.js",
+ "lib/punycode.js",
+ "lib/_http_incoming.js",
+ "lib/https.js",
+ "lib/_stream_wrap.js",
+ "lib/domain.js",
+ "lib/dns.js",
+ "lib/_http_client.js",
+ "lib/diagnostics_channel.js",
+ "lib/tty.js",
+ "lib/_http_agent.js",
+ "lib/timers.js",
+ "lib/_http_outgoing.js",
+ "lib/querystring.js",
+ "lib/_tls_common.js",
+ "lib/module.js",
+ "lib/_stream_passthrough.js",
+ "lib/_stream_transform.js",
+ "lib/worker_threads.js",
+ "lib/sys.js",
+ "lib/_stream_duplex.js",
+ "lib/path.js",
+ "lib/_http_common.js",
+ "lib/string_decoder.js",
+ "lib/cluster.js",
+ "lib/v8.js",
+ "lib/crypto.js",
+ "lib/wasi.js",
+ "lib/_stream_readable.js",
+ "lib/zlib.js",
+ "lib/internal/abort_controller.js",
+ "lib/internal/assert.js",
+ "lib/internal/assert/assertion_error.js",
+ "lib/internal/assert/calltracker.js",
+ "lib/internal/async_hooks.js",
+ "lib/internal/blob.js",
+ "lib/internal/blocklist.js",
+ "lib/internal/buffer.js",
+ "lib/internal/cli_table.js",
+ "lib/internal/child_process.js",
+ "lib/internal/child_process/serialization.js",
+ "lib/internal/cluster/child.js",
+ "lib/internal/cluster/primary.js",
+ "lib/internal/cluster/round_robin_handle.js",
+ "lib/internal/cluster/shared_handle.js",
+ "lib/internal/cluster/utils.js",
+ "lib/internal/cluster/worker.js",
+ "lib/internal/console/constructor.js",
+ "lib/internal/console/global.js",
+ "lib/internal/crypto/aes.js",
+ "lib/internal/crypto/certificate.js",
+ "lib/internal/crypto/cipher.js",
+ "lib/internal/crypto/diffiehellman.js",
+ "lib/internal/crypto/dsa.js",
+ "lib/internal/crypto/ec.js",
+ "lib/internal/crypto/hash.js",
+ "lib/internal/crypto/hashnames.js",
+ "lib/internal/crypto/hkdf.js",
+ "lib/internal/crypto/keygen.js",
+ "lib/internal/crypto/keys.js",
+ "lib/internal/crypto/mac.js",
+ "lib/internal/crypto/pbkdf2.js",
+ "lib/internal/crypto/random.js",
+ "lib/internal/crypto/rsa.js",
+ "lib/internal/crypto/scrypt.js",
+ "lib/internal/crypto/sig.js",
+ "lib/internal/crypto/util.js",
+ "lib/internal/crypto/webcrypto.js",
+ "lib/internal/crypto/x509.js",
+ "lib/url.js",
+ "lib/tls.js",
+ "lib/_stream_writable.js",
+ "lib/async_hooks.js",
+ "lib/process.js",
+ "lib/http.js",
+ "lib/buffer.js",
+ "lib/fs.js",
+ "lib/util/types.js",
+ "lib/timers/promises.js",
+ "lib/path/win32.js",
+ "lib/path/posix.js",
+ "lib/stream/promises.js",
+ "lib/stream/web.js",
+ "lib/internal/constants.js",
+ "lib/internal/debugger/_inspect.js",
+ "lib/internal/debugger/inspect_client.js",
+ "lib/internal/debugger/inspect_repl.js",
+ "lib/internal/dgram.js",
+ "lib/internal/dns/promises.js",
+ "lib/internal/dns/utils.js",
+ "lib/internal/dtrace.js",
+ "lib/internal/encoding.js",
+ "lib/internal/errors.js",
+ "lib/internal/error_serdes.js",
+ "lib/internal/event_target.js",
+ "lib/internal/fixed_queue.js",
+ "lib/internal/freelist.js",
+ "lib/internal/freeze_intrinsics.js",
+ "lib/internal/fs/dir.js",
+ "lib/internal/fs/promises.js",
+ "lib/internal/fs/read_file_context.js",
+ "lib/internal/fs/rimraf.js",
+ "lib/internal/fs/streams.js",
+ "lib/internal/fs/sync_write_stream.js",
+ "lib/internal/fs/utils.js",
+ "lib/internal/fs/watchers.js",
+ "lib/internal/http.js",
+ "lib/internal/heap_utils.js",
+ "lib/internal/histogram.js",
+ "lib/internal/idna.js",
+ "lib/internal/inspector_async_hook.js",
+ "lib/internal/js_stream_socket.js",
+ "lib/internal/legacy/processbinding.js",
+ "lib/internal/linkedlist.js",
+ "lib/internal/main/check_syntax.js",
+ "lib/internal/main/eval_string.js",
+ "lib/internal/main/eval_stdin.js",
+ "lib/internal/main/inspect.js",
+ "lib/internal/main/print_help.js",
+ "lib/internal/main/prof_process.js",
+ "lib/internal/main/repl.js",
+ "lib/internal/main/run_main_module.js",
+ "lib/internal/main/worker_thread.js",
+ "lib/internal/modules/run_main.js",
+ "lib/internal/modules/package_json_reader.js",
+ "lib/internal/modules/cjs/helpers.js",
+ "lib/internal/modules/cjs/loader.js",
+ "lib/internal/modules/esm/loader.js",
+ "lib/internal/modules/esm/create_dynamic_module.js",
+ "lib/internal/modules/esm/get_format.js",
+ "lib/internal/modules/esm/get_source.js",
+ "lib/internal/modules/esm/module_job.js",
+ "lib/internal/modules/esm/module_map.js",
+ "lib/internal/modules/esm/resolve.js",
+ "lib/internal/modules/esm/transform_source.js",
+ "lib/internal/modules/esm/translators.js",
+ "lib/internal/abort_controller.js",
+ "lib/internal/net.js",
+ "lib/internal/options.js",
+ "lib/internal/perf/perf.js",
+ "lib/internal/perf/nodetiming.js",
+ "lib/internal/perf/usertiming.js",
+ "lib/internal/perf/observe.js",
+ "lib/internal/perf/event_loop_delay.js",
+ "lib/internal/perf/event_loop_utilization.js",
+ "lib/internal/perf/timerify.js",
+ "lib/internal/policy/manifest.js",
+ "lib/internal/policy/sri.js",
+ "lib/internal/priority_queue.js",
+ "lib/internal/process/esm_loader.js",
+ "lib/internal/process/execution.js",
+ "lib/internal/process/per_thread.js",
+ "lib/internal/process/policy.js",
+ "lib/internal/process/promises.js",
+ "lib/internal/process/warning.js",
+ "lib/internal/process/worker_thread_only.js",
+ "lib/internal/process/report.js",
+ "lib/internal/process/signal.js",
+ "lib/internal/process/task_queues.js",
+ "lib/internal/querystring.js",
+ "lib/internal/readline/callbacks.js",
+ "lib/internal/readline/emitKeypressEvents.js",
+ "lib/internal/readline/utils.js",
+ "lib/internal/v8_prof_processor.js",
+ "lib/internal/event_target.js",
+ "lib/internal/inspector_async_hook.js",
+ "lib/internal/validators.js",
+ "lib/internal/linkedlist.js",
+ "lib/internal/cli_table.js",
+ "lib/internal/repl.js",
+ "lib/internal/repl/await.js",
+ "lib/internal/repl/history.js",
+ "lib/internal/repl/utils.js",
+ "lib/internal/socketaddress.js",
+ "lib/internal/util.js",
+ "lib/internal/histogram.js",
+ "lib/internal/error_serdes.js",
+ "lib/internal/dgram.js",
+ "lib/internal/child_process.js",
+ "lib/internal/assert.js",
+ "lib/internal/fixed_queue.js",
+ "lib/internal/blocklist.js",
+ "lib/internal/v8_prof_polyfill.js",
+ "lib/internal/options.js",
+ "lib/internal/worker.js",
+ "lib/internal/dtrace.js",
+ "lib/internal/idna.js",
+ "lib/internal/watchdog.js",
+ "lib/internal/encoding.js",
+ "lib/internal/tty.js",
+ "lib/internal/freeze_intrinsics.js",
+ "lib/internal/timers.js",
+ "lib/internal/heap_utils.js",
+ "lib/internal/querystring.js",
+ "lib/internal/js_stream_socket.js",
+ "lib/internal/errors.js",
+ "lib/internal/priority_queue.js",
+ "lib/internal/freelist.js",
+ "lib/internal/blob.js",
+ "lib/internal/socket_list.js",
+ "lib/internal/source_map/prepare_stack_trace.js",
+ "lib/internal/source_map/source_map.js",
+ "lib/internal/source_map/source_map_cache.js",
+ "lib/internal/socketaddress.js",
+ "lib/internal/stream_base_commons.js",
+ "lib/internal/url.js",
+ "lib/internal/async_hooks.js",
+ "lib/internal/http.js",
+ "lib/internal/buffer.js",
+ "lib/internal/trace_events_async_hooks.js",
+ "lib/internal/crypto/sig.js",
+ "lib/internal/crypto/rsa.js",
+ "lib/internal/crypto/aes.js",
+ "lib/internal/crypto/util.js",
+ "lib/internal/crypto/scrypt.js",
+ "lib/internal/crypto/random.js",
+ "lib/internal/crypto/keys.js",
+ "lib/internal/crypto/x509.js",
+ "lib/internal/crypto/certificate.js",
+ "lib/internal/crypto/ec.js",
+ "lib/internal/crypto/keygen.js",
+ "lib/internal/crypto/mac.js",
+ "lib/internal/crypto/diffiehellman.js",
+ "lib/internal/crypto/hkdf.js",
+ "lib/internal/crypto/cipher.js",
+ "lib/internal/crypto/hash.js",
+ "lib/internal/crypto/pbkdf2.js",
+ "lib/internal/crypto/webcrypto.js",
+ "lib/internal/crypto/dsa.js",
+ "lib/internal/crypto/hashnames.js",
+ "lib/internal/cluster/shared_handle.js",
+ "lib/internal/cluster/round_robin_handle.js",
+ "lib/internal/cluster/worker.js",
+ "lib/internal/cluster/primary.js",
+ "lib/internal/cluster/utils.js",
+ "lib/internal/cluster/child.js",
+ "lib/internal/webstreams/util.js",
+ "lib/internal/webstreams/writablestream.js",
+ "lib/internal/webstreams/readablestream.js",
+ "lib/internal/webstreams/queuingstrategies.js",
+ "lib/internal/webstreams/transformstream.js",
+ "lib/internal/webstreams/transfer.js",
+ "lib/internal/bootstrap/loaders.js",
+ "lib/internal/bootstrap/pre_execution.js",
+ "lib/internal/bootstrap/node.js",
+ "lib/internal/bootstrap/environment.js",
+ "lib/internal/bootstrap/switches/does_not_own_process_state.js",
+ "lib/internal/bootstrap/switches/is_not_main_thread.js",
+ "lib/internal/bootstrap/switches/does_own_process_state.js",
+ "lib/internal/bootstrap/switches/is_main_thread.js",
+ "lib/internal/test/binding.js",
+ "lib/internal/test/transfer.js",
+ "lib/internal/timers.js",
+ "lib/internal/tls.js",
+ "lib/internal/trace_events_async_hooks.js",
+ "lib/internal/tty.js",
+ "lib/internal/url.js",
+ "lib/internal/util.js",
+ "lib/internal/util/types.js",
+ "lib/internal/util/inspector.js",
+ "lib/internal/util/comparisons.js",
+ "lib/internal/util/debuglog.js",
+ "lib/internal/util/inspect.js",
+ "lib/internal/util/inspector.js",
+ "lib/internal/util/iterable_weak_map.js",
+ "lib/internal/util/types.js",
+ "lib/internal/http2/core.js",
+ "lib/internal/http2/compat.js",
+ "lib/internal/http2/util.js",
+ "lib/internal/v8_prof_polyfill.js",
+ "lib/internal/v8_prof_processor.js",
+ "lib/internal/validators.js",
+ "lib/internal/stream_base_commons.js",
+ "lib/internal/vm/module.js",
+ "lib/internal/worker.js",
+ "lib/internal/worker/io.js",
+ "lib/internal/worker/js_transferable.js",
+ "lib/internal/watchdog.js",
+ "lib/internal/streams/lazy_transform.js",
+ "lib/internal/streams/add-abort-signal.js",
+ "lib/internal/streams/buffer_list.js",
+ "lib/internal/streams/duplexpair.js",
+ "lib/internal/streams/from.js",
+ "lib/internal/streams/legacy.js",
+ "lib/internal/streams/readable.js",
+ "lib/internal/streams/writable.js",
+ "lib/internal/streams/duplex.js",
+ "lib/internal/streams/passthrough.js",
+ "lib/internal/streams/transform.js",
+ "lib/internal/streams/destroy.js",
+ "lib/internal/streams/legacy.js",
+ "lib/internal/streams/passthrough.js",
+ "lib/internal/streams/readable.js",
+ "lib/internal/streams/from.js",
+ "lib/internal/streams/writable.js",
+ "lib/internal/streams/state.js",
+ "lib/internal/streams/pipeline.js",
+ "lib/internal/streams/buffer_list.js",
+ "lib/internal/streams/end-of-stream.js",
+ "lib/internal/streams/utils.js",
+ "lib/internal/streams/transform.js",
+ "lib/internal/streams/lazy_transform.js",
+ "lib/internal/streams/duplex.js",
+ "lib/internal/streams/pipeline.js",
+ "lib/internal/readline/utils.js",
+ "lib/internal/readline/emitKeypressEvents.js",
+ "lib/internal/readline/callbacks.js",
+ "lib/internal/repl/history.js",
+ "lib/internal/repl/utils.js",
+ "lib/internal/repl/await.js",
+ "lib/internal/legacy/processbinding.js",
+ "lib/internal/assert/calltracker.js",
+ "lib/internal/assert/assertion_error.js",
+ "lib/internal/http2/util.js",
+ "lib/internal/http2/core.js",
+ "lib/internal/http2/compat.js",
+ "lib/internal/per_context/messageport.js",
+ "lib/internal/per_context/primordials.js",
+ "lib/internal/per_context/domexception.js",
+ "lib/internal/vm/module.js",
+ "lib/internal/tls/secure-pair.js",
+ "lib/internal/tls/parse-cert-string.js",
+ "lib/internal/tls/secure-context.js",
+ "lib/internal/child_process/serialization.js",
+ "lib/internal/debugger/inspect_repl.js",
+ "lib/internal/debugger/inspect_client.js",
+ "lib/internal/debugger/_inspect.js",
+ "lib/internal/worker/io.js",
+ "lib/internal/worker/js_transferable.js",
+ "lib/internal/main/repl.js",
+ "lib/internal/main/print_help.js",
+ "lib/internal/main/eval_string.js",
+ "lib/internal/main/check_syntax.js",
+ "lib/internal/main/prof_process.js",
+ "lib/internal/main/worker_thread.js",
+ "lib/internal/main/inspect.js",
+ "lib/internal/main/eval_stdin.js",
+ "lib/internal/main/run_main_module.js",
+ "lib/internal/modules/run_main.js",
+ "lib/internal/modules/package_json_reader.js",
+ "lib/internal/modules/esm/module_job.js",
+ "lib/internal/modules/esm/get_source.js",
+ "lib/internal/modules/esm/translators.js",
+ "lib/internal/modules/esm/resolve.js",
+ "lib/internal/modules/esm/create_dynamic_module.js",
+ "lib/internal/modules/esm/module_map.js",
+ "lib/internal/modules/esm/get_format.js",
+ "lib/internal/modules/esm/transform_source.js",
+ "lib/internal/modules/esm/loader.js",
+ "lib/internal/modules/cjs/helpers.js",
+ "lib/internal/modules/cjs/loader.js",
+ "lib/internal/source_map/source_map.js",
+ "lib/internal/source_map/prepare_stack_trace.js",
+ "lib/internal/source_map/source_map_cache.js",
+ "lib/internal/dns/promises.js",
+ "lib/internal/dns/utils.js",
+ "lib/internal/fs/watchers.js",
+ "lib/internal/fs/promises.js",
+ "lib/internal/fs/read_file_context.js",
+ "lib/internal/fs/rimraf.js",
+ "lib/internal/fs/sync_write_stream.js",
+ "lib/internal/fs/dir.js",
+ "lib/internal/fs/streams.js",
+ "lib/internal/fs/utils.js",
+ "lib/internal/perf/nodetiming.js",
+ "lib/internal/perf/usertiming.js",
+ "lib/internal/perf/performance_entry.js",
+ "lib/internal/perf/performance.js",
+ "lib/internal/perf/timerify.js",
+ "lib/internal/perf/utils.js",
+ "lib/internal/perf/observe.js",
+ "lib/internal/perf/event_loop_delay.js",
+ "lib/internal/perf/event_loop_utilization.js",
+ "lib/internal/policy/manifest.js",
+ "lib/internal/policy/sri.js",
+ "lib/internal/process/task_queues.js",
+ "lib/internal/process/per_thread.js",
+ "lib/internal/process/warning.js",
+ "lib/internal/process/policy.js",
+ "lib/internal/process/promises.js",
+ "lib/internal/process/signal.js",
+ "lib/internal/process/execution.js",
+ "lib/internal/process/esm_loader.js",
+ "lib/internal/process/report.js",
+ "lib/internal/process/worker_thread_only.js",
+ "lib/internal/console/constructor.js",
+ "lib/internal/console/global.js",
+ "lib/assert/strict.js",
+ "lib/dns/promises.js",
+ "lib/fs/promises.js",
+ "//v8/tools/splaytree.mjs",
+ "//v8/tools/codemap.mjs",
+ "//v8/tools/consarray.mjs",
@@ -1210,10 +1228,6 @@ index 0000000000000000000000000000000000000000..7225bdb6b281837253978430a665e9ee
+ "//v8/tools/tickprocessor-driver.mjs",
+ "deps/acorn/acorn/dist/acorn.js",
+ "deps/acorn/acorn-walk/dist/walk.js",
+ "deps/acorn-plugins/acorn-class-fields/index.js",
+ "deps/acorn-plugins/acorn-private-class-elements/index.js",
+ "deps/acorn-plugins/acorn-private-methods/index.js",
+ "deps/acorn-plugins/acorn-static-class-features/index.js",
+ "deps/cjs-module-lexer/lexer.js",
+ "deps/cjs-module-lexer/dist/lexer.js"
+ ],
@@ -1628,7 +1642,7 @@ index 0000000000000000000000000000000000000000..d1d6b51e8c0c5bc6a5d09e217eb30483
+ args = rebase_path(inputs + outputs, root_build_dir)
+}
diff --git a/src/node_version.h b/src/node_version.h
index e4ee8199ca434226f548ddf9f0e4e2770b48df9e..03e59cfeaa32eaba7aeba9be17f986468c34ac6e 100644
index 0523885212d429ee5c4142137524cb127d8adc97..116815364055a01f0c0619f0f22e9a387c2f2e2e 100644
--- a/src/node_version.h
+++ b/src/node_version.h
@@ -89,7 +89,10 @@
@@ -1661,17 +1675,17 @@ index 0000000000000000000000000000000000000000..01f62d4ae6e3b9d539444e3dff069f00
+ main(sys.argv[1:])
diff --git a/tools/generate_gn_filenames_json.py b/tools/generate_gn_filenames_json.py
new file mode 100755
index 0000000000000000000000000000000000000000..cf3b8df67d73b4096a0113b55e2a916b59701b23
index 0000000000000000000000000000000000000000..ece315d915f0a7b2c8e823caccba7ffec8420fdf
--- /dev/null
+++ b/tools/generate_gn_filenames_json.py
@@ -0,0 +1,71 @@
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+import json
+import os
+import sys
+
+import install
+
+import subprocess
+
+def LoadPythonDictionary(path):
+ file_string = open(path).read()
@@ -1704,6 +1718,7 @@ index 0000000000000000000000000000000000000000..cf3b8df67d73b4096a0113b55e2a916b
+ if t['target_name'] == '<(node_lib_target_name)')
+ node_source_blocklist = {
+ '<@(library_files)',
+ '<@(deps_files)',
+ 'common.gypi',
+ '<(SHARED_INTERMEDIATE_DIR)/node_javascript.cc',
+ }
@@ -1713,7 +1728,10 @@ index 0000000000000000000000000000000000000000..cf3b8df67d73b4096a0113b55e2a916b
+ files = [f.replace('deps/v8/', '//v8/', 1) for f in files]
+ return files
+
+ out['library_files'] = filter_v8_files(node_gyp['variables']['library_files'])
+ cwd = os.path.join(os.getcwd(), 'tools/search_files.py')
+ lib_files = subprocess.check_output('{} {} --ext js lib'.format('python3', cwd), shell=True).split()
+ out['library_files'] = filter_v8_files(lib_files)
+ out['library_files'] += filter_v8_files(node_gyp['variables']['deps_files'])
+
+ blocklisted_sources = [
+ f for f in node_lib_target['sources']
@@ -1768,14 +1786,17 @@ index 0000000000000000000000000000000000000000..3088ae4bdf814ae255c9805ebd393b2e
+
+ out_file.writelines(new_contents)
diff --git a/tools/install.py b/tools/install.py
index 045d406d84be301722f3de62abc448db84e751f8..95aa4c985d33ef37a73eebfca8bb8651e5059d37 100755
index 24cf51e73199e60b4c24700e1074fe9bd0a399e6..3cbf4f45fabec1a26e0edebb18fb589fbecfbe68 100755
--- a/tools/install.py
+++ b/tools/install.py
@@ -159,14 +159,15 @@ def files(action):
@@ -159,17 +159,18 @@ def files(action):
def headers(action):
def wanted_v8_headers(files_arg, dest):
v8_headers = [
- 'deps/v8/include/cppgc/common.h',
- 'deps/v8/include/libplatform/libplatform.h',
- 'deps/v8/include/libplatform/libplatform-export.h',
- 'deps/v8/include/libplatform/v8-tracing.h',
- 'deps/v8/include/v8.h',
- 'deps/v8/include/v8-internal.h',
- 'deps/v8/include/v8-platform.h',
@@ -1783,6 +1804,9 @@ index 045d406d84be301722f3de62abc448db84e751f8..95aa4c985d33ef37a73eebfca8bb8651
- 'deps/v8/include/v8-version.h',
- 'deps/v8/include/v8config.h',
+ '../../v8/include/cppgc/common.h',
+ '../../v8/include/libplatform/libplatform.h',
+ '../../v8/include/libplatform/libplatform-export.h',
+ '../../v8/include/libplatform/v8-tracing.h',
+ '../../v8/include/v8.h',
+ '../../v8/include/v8-internal.h',
+ '../../v8/include/v8-platform.h',
@@ -1794,7 +1818,7 @@ index 045d406d84be301722f3de62abc448db84e751f8..95aa4c985d33ef37a73eebfca8bb8651
files_arg = [name for name in files_arg if name in v8_headers]
action(files_arg, dest)
@@ -187,7 +188,7 @@ def headers(action):
@@ -190,7 +191,7 @@ def headers(action):
if sys.platform.startswith('aix'):
action(['out/Release/node.exp'], 'include/node/')
@@ -1804,10 +1828,10 @@ index 045d406d84be301722f3de62abc448db84e751f8..95aa4c985d33ef37a73eebfca8bb8651
if 'false' == variables.get('node_shared_libuv'):
subdir_files('deps/uv/include', 'include/node/', action)
diff --git a/tools/js2c.py b/tools/js2c.py
index d40f28ce2bff2b7fc28ceeafc7772831746e7f89..4af54c3fa00602f9d0ce5cc4dca253d425048706 100755
index d93be2123e0f8c75dd6a0041ef164982db0860e4..4d9317527d46ac8c6d8066bfba707233053b8615 100755
--- a/tools/js2c.py
+++ b/tools/js2c.py
@@ -130,6 +130,14 @@ def NormalizeFileName(filename):
@@ -131,6 +131,14 @@ def NormalizeFileName(filename):
split = split[1:]
if len(split):
filename = '/'.join(split)

View File

@@ -35,10 +35,10 @@ index b45af42d12ff7df8a9e125e87f51af3456811c23..c84ff7feb07aebf656ada7e37d812d9d
async function* watch(filename, options = {}) {
const path = toNamespacedPath(getValidatedPath(filename));
diff --git a/src/node_native_module.cc b/src/node_native_module.cc
index f788732ae569d460a0e596397e589774ba4be4f1..186c24c0ba37781d8d9d0443d18b4f4bb0e02bef 100644
index 2642982330e8cff166d10682af3d847105dc5c2e..f5491b416203877b89db69bde44f4ff50901ed8b 100644
--- a/src/node_native_module.cc
+++ b/src/node_native_module.cc
@@ -19,6 +19,7 @@ NativeModuleLoader NativeModuleLoader::instance_;
@@ -20,6 +20,7 @@ NativeModuleLoader NativeModuleLoader::instance_;
NativeModuleLoader::NativeModuleLoader() : config_(GetConfig()) {
LoadJavaScriptSource();
@@ -59,11 +59,11 @@ index 3be3f2364dd252bcdd668c699a0e7ae1e754e873..b2af1bce312ffca44e7005e11f92327e
bool Exists(const char* id);
diff --git a/tools/js2c.py b/tools/js2c.py
index 4af54c3fa00602f9d0ce5cc4dca253d425048706..18f584473fb87f036ce2a67c5172cccaf0caaa5f 100755
index 4d9317527d46ac8c6d8066bfba707233053b8615..83225036208b68087a6066adf1d1948b84c5c234 100755
--- a/tools/js2c.py
+++ b/tools/js2c.py
@@ -38,6 +38,8 @@ import functools
import codecs
@@ -39,6 +39,8 @@ import codecs
import utils
def ReadFile(filename):
+ if filename.startswith("//v8"):
@@ -71,7 +71,7 @@ index 4af54c3fa00602f9d0ce5cc4dca253d425048706..18f584473fb87f036ce2a67c5172ccca
if is_verbose:
print(filename)
with codecs.open(filename, "r", "utf-8") as f:
@@ -56,13 +58,15 @@ namespace native_module {{
@@ -57,13 +59,15 @@ namespace native_module {{
{0}
@@ -89,7 +89,7 @@ index 4af54c3fa00602f9d0ce5cc4dca253d425048706..18f584473fb87f036ce2a67c5172ccca
}} // namespace native_module
@@ -112,8 +116,8 @@ def GetDefinition(var, source, step=30):
@@ -113,8 +117,8 @@ def GetDefinition(var, source, step=30):
return definition, len(code_points)
@@ -100,7 +100,7 @@ index 4af54c3fa00602f9d0ce5cc4dca253d425048706..18f584473fb87f036ce2a67c5172ccca
name = NormalizeFileName(filename)
slug = SLUGGER_RE.sub('_', name)
var = slug + '_raw'
@@ -123,7 +127,9 @@ def AddModule(filename, definitions, initializers):
@@ -124,7 +128,9 @@ def AddModule(filename, definitions, initializers):
initializers.append(initializer)
def NormalizeFileName(filename):
@@ -111,7 +111,7 @@ index 4af54c3fa00602f9d0ce5cc4dca253d425048706..18f584473fb87f036ce2a67c5172ccca
if split[0] == 'deps':
split = ['internal'] + split
else: # `lib/**/*.js` so drop the 'lib' part
@@ -141,23 +147,36 @@ def NormalizeFileName(filename):
@@ -142,23 +148,36 @@ def NormalizeFileName(filename):
return os.path.splitext(filename)[0]
@@ -157,23 +157,24 @@ index 4af54c3fa00602f9d0ce5cc4dca253d425048706..18f584473fb87f036ce2a67c5172ccca
write_if_chaged(out, target)
@@ -213,18 +232,21 @@ def main():
)
parser.add_argument('--target', help='output file')
@@ -218,6 +237,7 @@ def main():
default=None,
help='input file directory')
parser.add_argument('--verbose', action='store_true', help='output file')
+ parser.add_argument('--only-js', action='store_true', help='do not require or parse any config.gypi files')
parser.add_argument('sources', nargs='*', help='input files')
options = parser.parse_args()
global is_verbose
is_verbose = options.verbose
source_files = functools.reduce(SourceFileByExt, options.sources, {})
@@ -230,12 +250,15 @@ def main():
source_files = functools.reduce(SourceFileByExt, sources, {})
- # Should have exactly 3 types: `.js`, `.mjs` and `.gypi`
- assert len(source_files) == 3
- # Currently config.gypi is the only `.gypi` file allowed
- assert source_files['.gypi'] == ['config.gypi']
- source_files['config.gypi'] = source_files.pop('.gypi')[0]
- JS2C(source_files, options.target)
-
+ if options.only_js:
+ assert len(source_files) == 1
+ else:
@@ -184,5 +185,5 @@ index 4af54c3fa00602f9d0ce5cc4dca253d425048706..18f584473fb87f036ce2a67c5172ccca
+ source_files['config.gypi'] = source_files.pop('.gypi')[0]
+ JS2C(source_files, options.target, options.only_js)
if __name__ == "__main__":
main()

View File

@@ -8,10 +8,10 @@ they use themselves as the entry point. We should try to upstream some form
of this.
diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js
index f7e9ffa74a4f19caa96680b3c955937f7ab31ea0..d1fb880f5f904909a1535f8253ab0b29203304bf 100644
index c90a19d5eed72a55cb9ee89dd2471a32a30c1377..68a52e1ce1f0a2bf4fc2b19ea735948cf6586e06 100644
--- a/lib/internal/bootstrap/pre_execution.js
+++ b/lib/internal/bootstrap/pre_execution.js
@@ -102,10 +102,12 @@ function patchProcessObject(expandArgv1) {
@@ -103,10 +103,12 @@ function patchProcessObject(expandArgv1) {
if (expandArgv1 && process.argv[1] &&
!StringPrototypeStartsWith(process.argv[1], '-')) {
// Expand process.argv[1] into a full path.

View File

@@ -7,7 +7,7 @@ This is used so that we can modify the flag at runtime where
config can only be set at compile time.
diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js
index 8b70f79d2b278f2f6b15abc2eba5045b1ca503b4..bfc6edfae4b4b4f8b7ee2a97209e1801d7c2d4c9 100644
index ef06d0563fa7452348754418867a56c9b8c6f4e1..a313402f93937cf2f1f93eb74422d9609e291d76 100644
--- a/lib/internal/bootstrap/node.js
+++ b/lib/internal/bootstrap/node.js
@@ -193,7 +193,7 @@ const {

View File

@@ -8,7 +8,7 @@ node modules will have different (wrong) ideas about how v8 structs are laid
out in memory on 64-bit machines, and will summarily fail to work.
diff --git a/common.gypi b/common.gypi
index 9481fdb6dd4628833e60ae7099f03eca41edd057..60789d5553352563eb41a341860df70175153e4c 100644
index 65729132b233dc5801f142f05a00253ac951e66a..e717a0b76513401daee6910502ea580485a20612 100644
--- a/common.gypi
+++ b/common.gypi
@@ -64,7 +64,7 @@

View File

@@ -24,7 +24,7 @@ Environment on the V8 context of blink, so no new V8 context is created.
As a result, a renderer process may have multiple Node Environments in it.
diff --git a/src/node.cc b/src/node.cc
index b60be116b6139bd4b7c10485bfdad2c1b905c995..c9d491f01651ef57fb793dda108469cb7ddccc5c 100644
index 3ee25ebbd67a01d607456e5158c98ca2fdea396b..6302bb925339d709a54151a8fc8b58f1a2253881 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -139,6 +139,8 @@ using v8::Undefined;

View File

@@ -6,10 +6,10 @@ Subject: feat: initialize asar support
This patch initializes asar support in Node.js.
diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js
index 83ccfe90c110657f54e068d522c3a75c7f19c75a..f7e9ffa74a4f19caa96680b3c955937f7ab31ea0 100644
index 3b69844dc4ea0c4e17a74ea514c4f0dc390e3a61..c90a19d5eed72a55cb9ee89dd2471a32a30c1377 100644
--- a/lib/internal/bootstrap/pre_execution.js
+++ b/lib/internal/bootstrap/pre_execution.js
@@ -74,6 +74,7 @@ function prepareMainThreadExecution(expandArgv1 = false) {
@@ -75,6 +75,7 @@ function prepareMainThreadExecution(expandArgv1 = false) {
assert(!CJSLoader.hasLoadedAnyUserCJSModule);
loadPreloadModules();
initializeFrozenIntrinsics();
@@ -17,7 +17,7 @@ index 83ccfe90c110657f54e068d522c3a75c7f19c75a..f7e9ffa74a4f19caa96680b3c955937f
}
function patchProcessObject(expandArgv1) {
@@ -469,6 +470,10 @@ function loadPreloadModules() {
@@ -475,6 +476,10 @@ function loadPreloadModules() {
}
}

View File

@@ -9,10 +9,10 @@ errors. This is remedied by adding a small timeout to the test.
We'll either upstream this or figure out a better solution.
diff --git a/test/sequential/test-debugger-address.js b/test/sequential/test-debugger-address.js
index ff31747016c2d49ac87fa272eba3231e9d4fbae5..e4f7b13aad3c60100e56df00165d1af550f1a117 100644
--- a/test/sequential/test-debugger-address.js
+++ b/test/sequential/test-debugger-address.js
diff --git a/test/parallel/test-debugger-address.js b/test/parallel/test-debugger-address.js
index 95dd1c6e3f82835d5ccaf65544d654b71efaa392..ed8dccf91247068455dd593bb3e8c02bddc89ae5 100644
--- a/test/parallel/test-debugger-address.js
+++ b/test/parallel/test-debugger-address.js
@@ -59,6 +59,7 @@ function launchTarget(...args) {
cli = startCLI([`${host || '127.0.0.1'}:${port}`]);
return cli.waitForPrompt();
@@ -22,14 +22,14 @@ index ff31747016c2d49ac87fa272eba3231e9d4fbae5..e4f7b13aad3c60100e56df00165d1af5
.then(() => cli.waitFor(/break/))
.then(() => cli.waitForPrompt())
diff --git a/test/sequential/test-debugger-pid.js b/test/sequential/test-debugger-pid.js
index 97de9f40369d2d1df9674c6df5bbaf78022667c6..3d51a8963ba24e5e5f6a64cd792859535670dd9a 100644
index 402c1f86dd4ed99b413eca5fce8a2db47797b11a..74ef0a1618ccf1f6671bbe2a03548eee6cd0b88c 100644
--- a/test/sequential/test-debugger-pid.js
+++ b/test/sequential/test-debugger-pid.js
@@ -38,6 +38,7 @@ function launchTarget(...args) {
cli = startCLI(['-p', `${target.pid}`]);
return cli.waitForPrompt();
})
+ .then(() => new Promise(resolve => setTimeout(resolve, 1000)))
@@ -41,6 +41,7 @@ function launchTarget(...args) {
.then(() => cli.command('sb("alive.js", 3)'))
.then(() => cli.waitFor(/break/))
.then(() => cli.waitForPrompt())
+ .then(() => new Promise(resolve => setTimeout(resolve, 1000)))
.then(() => {
assert.match(
cli.output,

View File

@@ -7,7 +7,7 @@ common.gypi is a file that's included in the node header bundle, despite
the fact that we do not build node with gyp.
diff --git a/common.gypi b/common.gypi
index aa42c69f96391b72e5e3cbada27fd662cb0cc69d..9481fdb6dd4628833e60ae7099f03eca41edd057 100644
index 71862791dae3be5f05fbe00b91df6240473debb1..65729132b233dc5801f142f05a00253ac951e66a 100644
--- a/common.gypi
+++ b/common.gypi
@@ -81,6 +81,23 @@

View File

@@ -6,7 +6,7 @@ Subject: fix: add v8_enable_reverse_jsargs defines in common.gypi
This can be removed once node upgrades V8 and inevitably has to do this exact same thing. Also hi node people if you are looking at this.
diff --git a/common.gypi b/common.gypi
index 60789d5553352563eb41a341860df70175153e4c..9067a6a27606099ec5decbc4cd74283fced77711 100644
index e717a0b76513401daee6910502ea580485a20612..e7c50b5e46624b537de3ffed51fc21a15b666dc8 100644
--- a/common.gypi
+++ b/common.gypi
@@ -65,6 +65,7 @@

View File

@@ -51,10 +51,10 @@ index b3b1ea908253b9240cc37931f34b2a8c8c9fa3ab..dc37298aa0e13bb79030123f38070d02
return emit_filehandle_warning_;
}
diff --git a/src/env.h b/src/env.h
index 7b136f70fbad1e0a90406add90d5e538577e2a2b..eaf8a17c99aa6a4135c54616f68b15e0620e4378 100644
index e1b89261fcb1e94220424aae2273db9fba010331..45210f074a0ca4d57f9fdc5019e8e82540b28b72 100644
--- a/src/env.h
+++ b/src/env.h
@@ -1197,6 +1197,7 @@ class Environment : public MemoryRetainer {
@@ -1199,6 +1199,7 @@ class Environment : public MemoryRetainer {
inline bool owns_process_state() const;
inline bool owns_inspector() const;
inline bool tracks_unmanaged_fds() const;

View File

@@ -428,19 +428,6 @@ index cae9301517c37c7e90292d71fe5a6086cf55e0be..b9bc86e4d8b897cec583dea16f64f680
};
}
diff --git a/test/parallel/test-crypto-ecdh-convert-key.js b/test/parallel/test-crypto-ecdh-convert-key.js
index f4d5a651ed6b888d3527a462ab5fccee58ea48b6..c0046099df9ec0c7a33ed9baa2127da849871001 100644
--- a/test/parallel/test-crypto-ecdh-convert-key.js
+++ b/test/parallel/test-crypto-ecdh-convert-key.js
@@ -117,7 +117,7 @@ if (getCurves().includes('secp256k1')) {
// rather than Node's generic error message.
const badKey = 'f'.repeat(128);
assert.throws(
- () => ECDH.convertKey(badKey, 'secp256k1', 'hex', 'hex', 'compressed'),
+ () => ECDH.convertKey(badKey, 'secp521r1', 'hex', 'hex', 'compressed'),
/Failed to convert Buffer to EC_POINT/);
// Next statement should not throw an exception.
diff --git a/test/parallel/test-crypto-getcipherinfo.js b/test/parallel/test-crypto-getcipherinfo.js
index 98d2a52eceac4bc564fd2878f77b50c336a67a66..bcb2de6e354c26816000f2400d9c1d46de01888a 100644
--- a/test/parallel/test-crypto-getcipherinfo.js

View File

@@ -186,19 +186,6 @@ index 271db427fa8539feb30c1712574976fb1f623e91..b2b6af1f9e6db54bdff0be7a567255f4
if (EVP_PKEY_paramgen(param_ctx.get(), &raw_params) <= 0)
return EVPKeyCtxPointer();
diff --git a/src/crypto/crypto_ec.cc b/src/crypto/crypto_ec.cc
index ea4c70ad5d8c844860ba3480fc7ef4205f0a3cdc..cdf8dd47d6e2a5894066cec01fbe347af079ec22 100644
--- a/src/crypto/crypto_ec.cc
+++ b/src/crypto/crypto_ec.cc
@@ -314,7 +314,7 @@ void ECDH::SetPrivateKey(const FunctionCallbackInfo<Value>& args) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to set generated public key");
- EC_KEY_copy(ecdh->key_.get(), new_key.get());
+ ecdh->key_.reset(EC_KEY_dup(new_key.get()));
ecdh->group_ = EC_KEY_get0_group(ecdh->key_.get());
}
diff --git a/src/crypto/crypto_hkdf.cc b/src/crypto/crypto_hkdf.cc
index 0aa96ada47abe4b66fb616c665101278bbe0afb6..1e9a4863c5faea5f6b275483ca16f3a6e8dac25b 100644
--- a/src/crypto/crypto_hkdf.cc
@@ -248,26 +235,10 @@ index 7b113a8dcb06b0b0e1329ce0daf7305598ea6545..b04e53a7f24885ffb6639430988d0ffb
const EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(pkey.get());
const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key);
diff --git a/src/crypto/crypto_util.cc b/src/crypto/crypto_util.cc
index 13c40dcb757661220288465c39101de0b4018e90..7d1d4400319292a8ddf3afe013b5678f84c25576 100644
index f18304cd655842e999a39659315c4eb3ce1c0c6e..1aed0e7e88460cea63950f71dac502829d662cff 100644
--- a/src/crypto/crypto_util.cc
+++ b/src/crypto/crypto_util.cc
@@ -139,7 +139,6 @@ void InitCryptoOnce() {
OPENSSL_init_ssl(0, settings);
OPENSSL_INIT_free(settings);
settings = nullptr;
-#endif
#ifndef _WIN32
if (per_process::cli_options->secure_heap != 0) {
@@ -160,6 +159,7 @@ void InitCryptoOnce() {
}
#endif
+#endif
// Turn off compression. Saves memory and protects against CRIME attacks.
// No-op with OPENSSL_NO_COMP builds of OpenSSL.
sk_SSL_COMP_zero(SSL_COMP_get_compression_methods());
@@ -490,24 +490,14 @@ Maybe<bool> Decorate(Environment* env, Local<Object> obj,
@@ -491,24 +491,14 @@ Maybe<bool> Decorate(Environment* env, Local<Object> obj,
V(BIO) \
V(PKCS7) \
V(X509V3) \
@@ -292,7 +263,7 @@ index 13c40dcb757661220288465c39101de0b4018e90..7d1d4400319292a8ddf3afe013b5678f
V(USER) \
#define V(name) case ERR_LIB_##name: lib = #name "_"; break;
@@ -667,7 +657,7 @@ void SecureBuffer(const FunctionCallbackInfo<Value>& args) {
@@ -668,7 +658,7 @@ void SecureBuffer(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsUint32());
Environment* env = Environment::GetCurrent(args);
uint32_t len = args[0].As<Uint32>()->Value();
@@ -301,7 +272,7 @@ index 13c40dcb757661220288465c39101de0b4018e90..7d1d4400319292a8ddf3afe013b5678f
if (data == nullptr) {
// There's no memory available for the allocation.
// Return nothing.
@@ -679,7 +669,7 @@ void SecureBuffer(const FunctionCallbackInfo<Value>& args) {
@@ -680,7 +670,7 @@ void SecureBuffer(const FunctionCallbackInfo<Value>& args) {
data,
len,
[](void* data, size_t len, void* deleter_data) {
@@ -310,7 +281,7 @@ index 13c40dcb757661220288465c39101de0b4018e90..7d1d4400319292a8ddf3afe013b5678f
},
data);
Local<ArrayBuffer> buffer = ArrayBuffer::New(env->isolate(), store);
@@ -687,10 +677,12 @@ void SecureBuffer(const FunctionCallbackInfo<Value>& args) {
@@ -688,10 +678,12 @@ void SecureBuffer(const FunctionCallbackInfo<Value>& args) {
}
void SecureHeapUsed(const FunctionCallbackInfo<Value>& args) {
@@ -337,19 +308,6 @@ index ac95612a0b1a856d7fe07efde59786e811f1b98d..aa62753d7c929027f5265fa4330b0429
#include <openssl/rsa.h>
#include <openssl/dsa.h>
#include <openssl/ssl.h>
diff --git a/src/node.cc b/src/node.cc
index c9d491f01651ef57fb793dda108469cb7ddccc5c..6a55535b5c6ef72b1cdb366299840a2e78643911 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -1035,7 +1035,7 @@ InitializationResult InitializeOncePerProcess(
}
if (init_flags & kInitOpenSSL) {
-#if HAVE_OPENSSL
+#if HAVE_OPENSSL && !defined(OPENSSL_IS_BORINGSSL)
{
std::string extra_ca_certs;
if (credentials::SafeGetenv("NODE_EXTRA_CA_CERTS", &extra_ca_certs))
diff --git a/src/node_metadata.h b/src/node_metadata.h
index 4486d5af2c1622c7c8f44401dc3ebb986d8e3c2e..db1769f1b3f1617ed8dbbea57b5e324183b42be2 100644
--- a/src/node_metadata.h

View File

@@ -7,10 +7,10 @@ This broke the build at some point. Does it still? We should probably remove
this patch and find out!
diff --git a/src/node_internals.h b/src/node_internals.h
index 31076551e70c46da2c32cafd7ac08aee91366537..d57c51ebcc3c9dda1ef41b10ee49453839781deb 100644
index 8f7929994f3243fbd58b47374dfcadafb1feda8f..c333dc3464d2a23437fa22659d38dd17b6678112 100644
--- a/src/node_internals.h
+++ b/src/node_internals.h
@@ -391,10 +391,11 @@ class TraceEventScope {
@@ -386,10 +386,11 @@ class TraceEventScope {
TraceEventScope(const char* category,
const char* name,
void* id) : category_(category), name_(name), id_(id) {

View File

@@ -7,7 +7,7 @@ We use this to allow node's 'fs' module to read from ASAR files as if they were
a real filesystem.
diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js
index 863d4ef5608bcebc9b49c3988509be9cfb18b6cd..8b70f79d2b278f2f6b15abc2eba5045b1ca503b4 100644
index 58f7396990dddb7dd4cf3d23fcdcc1d48f52623e..ef06d0563fa7452348754418867a56c9b8c6f4e1 100644
--- a/lib/internal/bootstrap/node.js
+++ b/lib/internal/bootstrap/node.js
@@ -62,6 +62,10 @@ setupBuffer();

View File

@@ -15,6 +15,8 @@
"parallel/test-child-process-stdio-overlapped",
"parallel/test-cli-node-print-help",
"parallel/test-code-cache",
"parallel/test-cluster-bind-privileged-port",
"parallel/test-cluster-shared-handle-bind-privileged-port",
"parallel/test-crypto-aes-wrap",
"parallel/test-crypto-authenticated-stream",
"parallel/test-crypto-async-sign-verify",

View File

@@ -25,8 +25,9 @@ const runGit = async (args) => {
};
const tagIsSupported = tag => tag && !tag.includes('nightly') && !tag.includes('unsupported');
const tagIsAlpha = tag => tag && tag.includes('alpha');
const tagIsBeta = tag => tag && tag.includes('beta');
const tagIsStable = tag => tagIsSupported(tag) && !tagIsBeta(tag);
const tagIsStable = tag => tagIsSupported(tag) && !tagIsBeta(tag) && !tagIsAlpha(tag);
const getTagsOf = async (point) => {
try {
@@ -108,6 +109,9 @@ const getPreviousStabilizationBranch = async (current) => {
if (newestMatch && semver.lte(semverify(branch), semverify(newestMatch))) {
continue;
}
if ((await getTagsOnBranch(branch)).filter(tag => tagIsStable(tag)).length === 0) {
continue;
}
newestMatch = branch;
}
return newestMatch;

View File

@@ -25,7 +25,7 @@ const pass = '✓'.green;
const fail = '✗'.red;
if (!bumpType && !args.notesOnly) {
console.log('Usage: prepare-release [stable | minor | beta | nightly]' +
console.log('Usage: prepare-release [stable | minor | beta | alpha | nightly]' +
' (--stable) (--notesOnly) (--automaticRelease) (--branch)');
process.exit(1);
}
@@ -93,6 +93,11 @@ async function createRelease (branchToTarget, isBeta) {
'for any bugs you find in it.\n \n This release is published to npm ' +
'under the electron-nightly package and can be installed via `npm install electron-nightly`, ' +
`or \`npm install electron-nightly@${newVersion.substr(1)}\`.\n \n ${releaseNotes.text}`;
} else if (newVersion.indexOf('alpha') > 0) {
releaseBody = 'Note: This is an alpha release. Please file new issues ' +
'for any bugs you find in it.\n \n This release is published to npm ' +
'under the alpha tag and can be installed via `npm install electron@alpha`, ' +
`or \`npm install electron@${newVersion.substr(1)}\`.\n \n ${releaseNotes.text}`;
} else {
releaseBody = 'Note: This is a beta release. Please file new issues ' +
'for any bugs you find in it.\n \n This release is published to npm ' +
@@ -182,7 +187,8 @@ async function promptForVersion (version) {
// function to determine if there have been commits to main since the last release
async function changesToRelease () {
const lastCommitWasRelease = new RegExp('^Bump v[0-9.]*(-beta[0-9.]*)?(-nightly[0-9.]*)?$', 'g');
// eslint-disable-next-line no-useless-escape
const lastCommitWasRelease = new RegExp('^Bump v[0-9]+\.[0-9]+\.[0-9]+(-beta\.[0-9]+)?(-alpha\.[0-9]+)?(-nightly\.[0-9]+)?$', 'g');
const lastCommit = await GitProcess.exec(['log', '-n', '1', '--pretty=format:\'%s\''], ELECTRON_DIR);
return !lastCommitWasRelease.test(lastCommit.stdout);
}

View File

@@ -135,6 +135,9 @@ new Promise((resolve, reject) => {
} else if (!release.prerelease) {
// Tag the release with a `2-0-x` style tag
npmTag = currentBranch;
} else if (release.tag_name.indexOf('alpha') > 0) {
// Tag the release with an `alpha-3-0-x` style tag
npmTag = `alpha-${currentBranch}`;
} else {
// Tag the release with a `beta-3-0-x` style tag
npmTag = `beta-${currentBranch}`;
@@ -175,6 +178,10 @@ new Promise((resolve, reject) => {
semver.gt(localVersion, currentTags.beta)) {
childProcess.execSync(`npm dist-tag add electron@${localVersion} beta --otp=${process.env.ELECTRON_NPM_OTP}`);
}
if (parsedLocalVersion.prerelease[0] === 'alpha' &&
semver.gt(localVersion, currentTags.alpha)) {
childProcess.execSync(`npm dist-tag add electron@${localVersion} alpha --otp=${process.env.ELECTRON_NPM_OTP}`);
}
}
})
.catch((err) => {

View File

@@ -457,4 +457,8 @@ async function verifyShasumsForRemoteFiles (remoteFilesToHash, filesAreNodeJSArt
}
}
makeRelease(args.validateRelease);
makeRelease(args.validateRelease)
.catch((err) => {
console.error('Error occurred while making release:', err);
process.exit(1);
});

View File

@@ -71,13 +71,20 @@ async function main () {
console.log(`Bumped to version: ${version}`);
}
// get next version for release based on [nightly, beta, stable]
// get next version for release based on [nightly, alpha, beta, stable]
async function nextVersion (bumpType, version) {
if (versionUtils.isNightly(version) || versionUtils.isBeta(version)) {
if (
versionUtils.isNightly(version) ||
versionUtils.isAlpha(version) ||
versionUtils.isBeta(version)
) {
switch (bumpType) {
case 'nightly':
version = await versionUtils.nextNightly(version);
break;
case 'alpha':
version = await versionUtils.nextAlpha(version);
break;
case 'beta':
version = await versionUtils.nextBeta(version);
break;
@@ -92,6 +99,8 @@ async function nextVersion (bumpType, version) {
case 'nightly':
version = versionUtils.nextNightly(version);
break;
case 'alpha':
throw new Error('Cannot bump to alpha from stable.');
case 'beta':
throw new Error('Cannot bump to beta from stable.');
case 'minor':
@@ -165,7 +174,7 @@ async function updateWinRC (components) {
// updates support.md file with new semver values (stable only)
async function updateSupported (version, filePath) {
const v = parseInt(version);
const newVersions = [`* ${v}.x.y`, `* ${v - 1}.x.y`, `* ${v - 2}.x.y`];
const newVersions = [`* ${v}.x.y`, `* ${v - 1}.x.y`, `* ${v - 2}.x.y`, `* ${v - 3}`];
const contents = await readFile(filePath, 'utf8');
const previousVersions = contents.split('\n').filter((elem) => {
return (/[^\n]*\.x\.y[^\n]*/).test(elem);

View File

@@ -23,6 +23,7 @@ const getCurrentDate = () => {
};
const isNightly = v => v.includes('nightly');
const isAlpha = v => v.includes('alpha');
const isBeta = v => v.includes('beta');
const isStable = v => {
const parsed = semver.parse(v);
@@ -39,9 +40,22 @@ const makeVersion = (components, delim, pre = preType.NONE) => {
return version;
};
async function nextAlpha (v) {
const next = semver.coerce(semver.clean(v));
const tagBlob = await GitProcess.exec(['tag', '--list', '-l', `v${next}-alpha.*`], ELECTRON_DIR);
const tags = tagBlob.stdout.split('\n').filter(e => e !== '');
tags.sort((t1, t2) => {
const a = parseInt(t1.split('.').pop(), 10);
const b = parseInt(t2.split('.').pop(), 10);
return a - b;
});
// increment the latest existing alpha tag or start at alpha.1 if it's a new alpha line
return tags.length === 0 ? `${next}-alpha.1` : semver.inc(tags.pop(), 'prerelease');
}
async function nextBeta (v) {
const next = semver.coerce(semver.clean(v));
const tagBlob = await GitProcess.exec(['tag', '--list', '-l', `v${next}-beta.*`], ELECTRON_DIR);
const tags = tagBlob.stdout.split('\n').filter(e => e !== '');
tags.sort((t1, t2) => {
@@ -94,8 +108,10 @@ function getNextReleaseBranch (branches) {
module.exports = {
isStable,
isAlpha,
isBeta,
isNightly,
nextAlpha,
nextBeta,
makeVersion,
getElectronVersion,

View File

@@ -13,7 +13,7 @@ const fail = '✗'.red;
const args = require('minimist')(process.argv, {
string: ['runners', 'target'],
boolean: ['buildNativeTests'],
boolean: ['buildNativeTests', 'runTestFilesSeperately'],
unknown: arg => unknownFlags.push(arg)
});
@@ -123,24 +123,55 @@ async function runElectronTests () {
}
}
async function runRemoteBasedElectronTests () {
async function runTestUsingElectron (specDir, testName) {
let exe = path.resolve(BASE, utils.getElectronExec());
const runnerArgs = ['electron/spec', ...unknownArgs.slice(2)];
const runnerArgs = [`electron/${specDir}`, ...unknownArgs.slice(2)];
if (process.platform === 'linux') {
runnerArgs.unshift(path.resolve(__dirname, 'dbus_mock.py'), exe);
exe = 'python3';
}
const { status } = childProcess.spawnSync(exe, runnerArgs, {
const { status, signal } = childProcess.spawnSync(exe, runnerArgs, {
cwd: path.resolve(__dirname, '../..'),
stdio: 'inherit'
});
if (status !== 0) {
const textStatus = process.platform === 'win32' ? `0x${status.toString(16)}` : status.toString();
console.log(`${fail} Electron tests failed with code ${textStatus}.`);
if (status) {
const textStatus = process.platform === 'win32' ? `0x${status.toString(16)}` : status.toString();
console.log(`${fail} Electron tests failed with code ${textStatus}.`);
} else {
console.log(`${fail} Electron tests failed with kill signal ${signal}.`);
}
process.exit(1);
}
console.log(`${pass} Electron remote process tests passed.`);
console.log(`${pass} Electron ${testName} process tests passed.`);
}
const specFilter = (file) => {
if (!/-spec\.[tj]s$/.test(file)) {
return false;
} else {
return true;
}
};
async function runTests (specDir, testName) {
if (args.runTestFilesSeperately) {
const getFiles = require('../spec/static/get-files');
const testFiles = await getFiles(path.resolve(__dirname, `../${specDir}`), { filter: specFilter });
const baseElectronDir = path.resolve(__dirname, '..');
unknownArgs.splice(unknownArgs.length, 0, '--files', '');
testFiles.sort().forEach(async (file) => {
unknownArgs.splice((unknownArgs.length - 1), 1, path.relative(baseElectronDir, file));
console.log(`Running tests for ${unknownArgs[unknownArgs.length - 1]}`);
await runTestUsingElectron(specDir, testName);
});
} else {
await runTestUsingElectron(specDir, testName);
}
}
async function runRemoteBasedElectronTests () {
await runTests('spec', 'remote');
}
async function runNativeElectronTests () {
@@ -195,27 +226,7 @@ async function runNativeElectronTests () {
}
async function runMainProcessElectronTests () {
let exe = path.resolve(BASE, utils.getElectronExec());
const runnerArgs = ['electron/spec-main', ...unknownArgs.slice(2)];
if (process.platform === 'linux') {
runnerArgs.unshift(path.resolve(__dirname, 'dbus_mock.py'), exe);
exe = 'python3';
}
const { status, signal } = childProcess.spawnSync(exe, runnerArgs, {
cwd: path.resolve(__dirname, '../..'),
stdio: 'inherit'
});
if (status !== 0) {
if (status) {
const textStatus = process.platform === 'win32' ? `0x${status.toString(16)}` : status.toString();
console.log(`${fail} Electron tests failed with code ${textStatus}.`);
} else {
console.log(`${fail} Electron tests failed with kill signal ${signal}.`);
}
process.exit(1);
}
console.log(`${pass} Electron main process tests passed.`);
await runTests('spec-main', 'main');
}
async function installSpecModules (dir) {

View File

@@ -3,6 +3,7 @@ LICENSES.chromium.html
chrome-sandbox
chrome_100_percent.pak
chrome_200_percent.pak
crashpad_handler
electron
icudtl.dat
libEGL.so

View File

@@ -3,6 +3,7 @@ LICENSES.chromium.html
chrome-sandbox
chrome_100_percent.pak
chrome_200_percent.pak
crashpad_handler
electron
icudtl.dat
libEGL.so

View File

@@ -3,6 +3,7 @@ LICENSES.chromium.html
chrome-sandbox
chrome_100_percent.pak
chrome_200_percent.pak
crashpad_handler
electron
icudtl.dat
libEGL.so

View File

@@ -3,6 +3,7 @@ LICENSES.chromium.html
chrome-sandbox
chrome_100_percent.pak
chrome_200_percent.pak
crashpad_handler
electron
icudtl.dat
libEGL.so

View File

@@ -153,6 +153,9 @@ bool ElectronCrashReporterClient::GetCrashDumpLocation(
base::FilePath* crash_dir) {
bool result = base::PathService::Get(electron::DIR_CRASH_DUMPS, crash_dir);
{
// If the DIR_CRASH_DUMPS path is overridden with
// app.setPath('crashDumps', ...) then the directory might not have been
// created.
base::ThreadRestrictions::ScopedAllowIO allow_io;
if (result && !base::PathExists(*crash_dir)) {
return base::CreateDirectory(*crash_dir);
@@ -162,13 +165,6 @@ bool ElectronCrashReporterClient::GetCrashDumpLocation(
}
#endif
#if defined(OS_MAC) || defined(OS_LINUX)
bool ElectronCrashReporterClient::GetCrashMetricsLocation(
base::FilePath* metrics_dir) {
return base::PathService::Get(chrome::DIR_USER_DATA, metrics_dir);
}
#endif // OS_MAC || OS_LINUX
bool ElectronCrashReporterClient::IsRunningUnattended() {
return !collect_stats_consent_;
}

View File

@@ -52,10 +52,6 @@ class ElectronCrashReporterClient : public crash_reporter::CrashReporterClient {
bool GetCrashDumpLocation(base::FilePath* crash_dir) override;
#endif
#if defined(OS_MAC) || defined(OS_LINUX)
bool GetCrashMetricsLocation(base::FilePath* metrics_dir) override;
#endif
bool IsRunningUnattended() override;
bool GetCollectStatsConsent() override;

View File

@@ -58,7 +58,8 @@
#endif
#if !defined(MAS_BUILD)
#include "components/crash/core/app/crashpad.h" // nogncheck
#include "components/crash/core/app/crash_switches.h" // nogncheck
#include "components/crash/core/app/crashpad.h" // nogncheck
#include "components/crash/core/common/crash_key.h"
#include "components/crash/core/common/crash_keys.h"
#include "shell/app/electron_crash_reporter_client.h"
@@ -123,7 +124,8 @@ bool ElectronPathProvider(int key, base::FilePath* result) {
case chrome::DIR_USER_DATA:
if (!base::PathService::Get(DIR_APP_DATA, &cur))
return false;
cur = cur.Append(base::FilePath::FromUTF8Unsafe(GetApplicationName()));
cur = cur.Append(base::FilePath::FromUTF8Unsafe(
GetPossiblyOverriddenApplicationName()));
create_dir = true;
break;
case DIR_CRASH_DUMPS:
@@ -153,7 +155,8 @@ bool ElectronPathProvider(int key, base::FilePath* result) {
#endif
if (!base::PathService::Get(parent_key, &cur))
return false;
cur = cur.Append(base::FilePath::FromUTF8Unsafe(GetApplicationName()));
cur = cur.Append(base::FilePath::FromUTF8Unsafe(
GetPossiblyOverriddenApplicationName()));
create_dir = true;
break;
}
@@ -178,7 +181,8 @@ bool ElectronPathProvider(int key, base::FilePath* result) {
return false;
cur = cur.Append(FILE_PATH_LITERAL("Library"));
cur = cur.Append(FILE_PATH_LITERAL("Logs"));
cur = cur.Append(base::FilePath::FromUTF8Unsafe(GetApplicationName()));
cur = cur.Append(base::FilePath::FromUTF8Unsafe(
GetPossiblyOverriddenApplicationName()));
#else
if (!base::PathService::Get(chrome::DIR_USER_DATA, &cur))
return false;
@@ -366,9 +370,19 @@ void ElectronMainDelegate::PreSandboxStartup() {
#endif
#if defined(OS_LINUX)
// Zygote needs to call InitCrashReporter() in RunZygote().
if (process_type != ::switches::kZygoteProcess && !process_type.empty()) {
ElectronCrashReporterClient::Create();
breakpad::InitCrashReporter(process_type);
if (crash_reporter::IsCrashpadEnabled()) {
if (command_line->HasSwitch(
crash_reporter::switches::kCrashpadHandlerPid)) {
crash_reporter::InitializeCrashpad(false, process_type);
crash_reporter::SetFirstChanceExceptionHandler(
v8::TryHandleWebAssemblyTrapPosix);
}
} else {
breakpad::InitCrashReporter(process_type);
}
}
#endif
@@ -463,7 +477,16 @@ void ElectronMainDelegate::ZygoteForked() {
base::CommandLine::ForCurrentProcess();
std::string process_type =
command_line->GetSwitchValueASCII(::switches::kProcessType);
breakpad::InitCrashReporter(process_type);
if (crash_reporter::IsCrashpadEnabled()) {
if (command_line->HasSwitch(
crash_reporter::switches::kCrashpadHandlerPid)) {
crash_reporter::InitializeCrashpad(false, process_type);
crash_reporter::SetFirstChanceExceptionHandler(
v8::TryHandleWebAssemblyTrapPosix);
}
} else {
breakpad::InitCrashReporter(process_type);
}
// Reset the command line for the newly spawned process.
crash_keys::SetCrashKeysFromCommandLine(*command_line);

View File

@@ -716,8 +716,9 @@ void App::OnDidFailToContinueUserActivity(const std::string& type,
void App::OnContinueUserActivity(bool* prevent_default,
const std::string& type,
const base::DictionaryValue& user_info) {
if (Emit("continue-activity", type, user_info)) {
const base::DictionaryValue& user_info,
const base::DictionaryValue& details) {
if (Emit("continue-activity", type, user_info, details)) {
*prevent_default = true;
}
}

View File

@@ -102,7 +102,8 @@ class App : public ElectronBrowserClient::Delegate,
const std::string& error) override;
void OnContinueUserActivity(bool* prevent_default,
const std::string& type,
const base::DictionaryValue& user_info) override;
const base::DictionaryValue& user_info,
const base::DictionaryValue& details) override;
void OnUserActivityWasContinued(
const std::string& type,
const base::DictionaryValue& user_info) override;

View File

@@ -44,6 +44,7 @@
#include "base/guid.h"
#include "components/crash/core/app/breakpad_linux.h"
#include "components/crash/core/common/crash_keys.h"
#include "components/upload_list/combining_upload_list.h"
#include "v8/include/v8-wasm-trap-handler-posix.h"
#include "v8/include/v8.h"
#endif
@@ -150,16 +151,29 @@ void Start(const std::string& submit_url,
? "node"
: command_line->GetSwitchValueASCII(::switches::kProcessType);
#if defined(OS_LINUX)
::crash_keys::SetMetricsClientIdFromGUID(GetClientId());
auto& global_crash_keys = GetGlobalCrashKeysMutable();
for (const auto& pair : global_extra) {
global_crash_keys[pair.first] = pair.second;
if (::crash_reporter::IsCrashpadEnabled()) {
for (const auto& pair : extra)
electron::crash_keys::SetCrashKey(pair.first, pair.second);
{
base::ThreadRestrictions::ScopedAllowIO allow_io;
::crash_reporter::InitializeCrashpad(process_type.empty(), process_type);
}
if (ignore_system_crash_handler) {
crashpad::CrashpadInfo::GetCrashpadInfo()
->set_system_crash_reporter_forwarding(crashpad::TriState::kDisabled);
}
} else {
::crash_keys::SetMetricsClientIdFromGUID(GetClientId());
auto& global_crash_keys = GetGlobalCrashKeysMutable();
for (const auto& pair : global_extra) {
global_crash_keys[pair.first] = pair.second;
}
for (const auto& pair : extra)
electron::crash_keys::SetCrashKey(pair.first, pair.second);
for (const auto& pair : global_extra)
electron::crash_keys::SetCrashKey(pair.first, pair.second);
breakpad::InitCrashReporter(process_type);
}
for (const auto& pair : extra)
electron::crash_keys::SetCrashKey(pair.first, pair.second);
for (const auto& pair : global_extra)
electron::crash_keys::SetCrashKey(pair.first, pair.second);
breakpad::InitCrashReporter(process_type);
#elif defined(OS_MAC)
for (const auto& pair : extra)
electron::crash_keys::SetCrashKey(pair.first, pair.second);
@@ -203,7 +217,20 @@ scoped_refptr<UploadList> CreateCrashUploadList() {
base::PathService::Get(electron::DIR_CRASH_DUMPS, &crash_dir_path);
base::FilePath upload_log_path =
crash_dir_path.AppendASCII(CrashUploadList::kReporterLogFilename);
return base::MakeRefCounted<TextLogUploadList>(upload_log_path);
scoped_refptr<UploadList> result =
base::MakeRefCounted<TextLogUploadList>(upload_log_path);
if (crash_reporter::IsCrashpadEnabled()) {
// Crashpad keeps the records of C++ crashes (segfaults, etc) in its
// internal database. The JavaScript error reporter writes JS error upload
// records to the older text format. Combine the two to present a complete
// list to the user.
// TODO(nornagon): what is "The JavaScript error reporter", and do we care
// about it?
std::vector<scoped_refptr<UploadList>> uploaders = {
base::MakeRefCounted<CrashUploadListCrashpad>(), std::move(result)};
result = base::MakeRefCounted<CombiningUploadList>(std::move(uploaders));
}
return result;
#endif // defined(OS_MAC) || defined(OS_WIN)
}

View File

@@ -91,6 +91,7 @@ void Initialize(v8::Local<v8::Object> exports,
gin_helper::Dictionary dict(isolate, exports);
dict.SetMethod("showMessageBoxSync", &ShowMessageBoxSync);
dict.SetMethod("showMessageBox", &ShowMessageBox);
dict.SetMethod("_closeMessageBox", &electron::CloseMessageBox);
dict.SetMethod("showErrorBox", &electron::ShowErrorBox);
dict.SetMethod("showOpenDialogSync", &ShowOpenDialogSync);
dict.SetMethod("showOpenDialog", &ShowOpenDialog);

View File

@@ -406,7 +406,7 @@ std::string SystemPreferences::GetSystemColor(gin_helper::ErrorThrower thrower,
return "";
}
return ToRGBHex(skia::NSSystemColorToSkColor(sysColor));
return ToRGBAHex(skia::NSSystemColorToSkColor(sysColor));
}
bool SystemPreferences::CanPromptTouchID() {

View File

@@ -914,16 +914,6 @@ void WebContents::InitWithWebContents(content::WebContents* web_contents,
inspectable_web_contents_ = std::make_unique<InspectableWebContents>(
web_contents, browser_context->prefs(), is_guest);
inspectable_web_contents_->SetDelegate(this);
if (web_preferences) {
std::string color_name;
if (web_preferences->GetPreference(options::kBackgroundColor,
&color_name)) {
web_contents->SetPageBaseBackgroundColor(ParseHexColor(color_name));
} else {
web_contents->SetPageBaseBackgroundColor(SK_ColorTRANSPARENT);
}
}
}
WebContents::~WebContents() {
@@ -1383,6 +1373,18 @@ void WebContents::HandleNewRenderFrame(
if (!rwhv)
return;
// Set the background color of RenderWidgetHostView.
auto* web_preferences = WebContentsPreferences::From(web_contents());
if (web_preferences) {
std::string color_name;
if (web_preferences->GetPreference(options::kBackgroundColor,
&color_name)) {
rwhv->SetBackgroundColor(ParseHexColor(color_name));
} else {
rwhv->SetBackgroundColor(SK_ColorTRANSPARENT);
}
}
if (!background_throttling_)
render_frame_host->GetRenderViewHost()->SetSchedulerThrottling(false);
@@ -3050,14 +3052,6 @@ std::vector<base::FilePath> WebContents::GetPreloadPaths() const {
return result;
}
v8::Local<v8::Value> WebContents::GetWebPreferences(
v8::Isolate* isolate) const {
auto* web_preferences = WebContentsPreferences::From(web_contents());
if (!web_preferences)
return v8::Null(isolate);
return gin::ConvertToV8(isolate, *web_preferences->preference());
}
v8::Local<v8::Value> WebContents::GetLastWebPreferences(
v8::Isolate* isolate) const {
auto* web_preferences = WebContentsPreferences::From(web_contents());
@@ -3612,9 +3606,7 @@ void WebContents::UpdateHtmlApiFullscreen(bool fullscreen) {
manager->ForEachGuest(
web_contents(), base::BindRepeating([](content::WebContents* guest) {
WebContents* api_web_contents = WebContents::From(guest);
// Use UpdateHtmlApiFullscreen instead of SetXXX becuase there is no
// need to interact with the owner window.
api_web_contents->UpdateHtmlApiFullscreen(false);
api_web_contents->SetHtmlApiFullscreen(false);
return false;
}));
}
@@ -3719,7 +3711,6 @@ v8::Local<v8::ObjectTemplate> WebContents::FillObjectTemplate(
.SetMethod("getZoomFactor", &WebContents::GetZoomFactor)
.SetMethod("getType", &WebContents::GetType)
.SetMethod("_getPreloadPaths", &WebContents::GetPreloadPaths)
.SetMethod("getWebPreferences", &WebContents::GetWebPreferences)
.SetMethod("getLastWebPreferences", &WebContents::GetLastWebPreferences)
.SetMethod("getOwnerBrowserWindow", &WebContents::GetOwnerBrowserWindow)
.SetMethod("inspectServiceWorker", &WebContents::InspectServiceWorker)

View File

@@ -136,25 +136,25 @@ void Browser::Shutdown() {
}
std::string Browser::GetVersion() const {
std::string ret = GetOverriddenApplicationVersion();
std::string ret = OverriddenApplicationVersion();
if (ret.empty())
ret = GetExecutableFileVersion();
return ret;
}
void Browser::SetVersion(const std::string& version) {
OverrideApplicationVersion(version);
OverriddenApplicationVersion() = version;
}
std::string Browser::GetName() const {
std::string ret = GetOverriddenApplicationName();
std::string ret = OverriddenApplicationName();
if (ret.empty())
ret = GetExecutableFileProductName();
return ret;
}
void Browser::SetName(const std::string& name) {
OverrideApplicationName(name);
OverriddenApplicationName() = name;
}
int Browser::GetBadgeCount() {

View File

@@ -187,7 +187,8 @@ class Browser : public WindowListObserver {
// Resumes an activity via hand-off.
bool ContinueUserActivity(const std::string& type,
base::DictionaryValue user_info);
base::DictionaryValue user_info,
base::DictionaryValue details);
// Indicates that an activity was continued on another device.
void UserActivityWasContinued(const std::string& type,

View File

@@ -277,10 +277,11 @@ void Browser::DidFailToContinueUserActivity(const std::string& type,
}
bool Browser::ContinueUserActivity(const std::string& type,
base::DictionaryValue user_info) {
base::DictionaryValue user_info,
base::DictionaryValue details) {
bool prevent_default = false;
for (BrowserObserver& observer : observers_)
observer.OnContinueUserActivity(&prevent_default, type, user_info);
observer.OnContinueUserActivity(&prevent_default, type, user_info, details);
return prevent_default;
}

View File

@@ -70,7 +70,8 @@ class BrowserObserver : public base::CheckedObserver {
// The browser wants to resume a user activity via handoff. (macOS only)
virtual void OnContinueUserActivity(bool* prevent_default,
const std::string& type,
const base::DictionaryValue& user_info) {}
const base::DictionaryValue& user_info,
const base::DictionaryValue& details) {}
// The browser wants to notify that an user activity was resumed. (macOS only)
virtual void OnUserActivityWasContinued(
const std::string& type,

View File

@@ -298,6 +298,12 @@ breakpad::CrashHandlerHostLinux* CreateCrashHandlerHost(
}
int GetCrashSignalFD(const base::CommandLine& command_line) {
if (crash_reporter::IsCrashpadEnabled()) {
int fd;
pid_t pid;
return crash_reporter::GetHandlerSocket(&fd, &pid) ? fd : -1;
}
// Extensions have the same process type as renderers.
if (command_line.HasSwitch(extensions::switches::kExtensionProcess)) {
static breakpad::CrashHandlerHostLinux* crash_handler = nullptr;
@@ -526,20 +532,37 @@ void ElectronBrowserClient::AppendExtraCommandLineSwitches(
#if defined(OS_LINUX)
bool enable_crash_reporter = false;
enable_crash_reporter = breakpad::IsCrashReporterEnabled();
if (crash_reporter::IsCrashpadEnabled()) {
command_line->AppendSwitch(::switches::kEnableCrashpad);
enable_crash_reporter = true;
int fd;
pid_t pid;
if (crash_reporter::GetHandlerSocket(&fd, &pid)) {
command_line->AppendSwitchASCII(
crash_reporter::switches::kCrashpadHandlerPid,
base::NumberToString(pid));
}
} else {
enable_crash_reporter = breakpad::IsCrashReporterEnabled();
}
if (enable_crash_reporter) {
std::string switch_value =
api::crash_reporter::GetClientId() + ",no_channel";
command_line->AppendSwitchASCII(::switches::kEnableCrashReporter,
switch_value);
for (const auto& pair : api::crash_reporter::GetGlobalCrashKeys()) {
if (!switch_value.empty())
switch_value += ",";
switch_value += pair.first;
switch_value += "=";
switch_value += pair.second;
if (!crash_reporter::IsCrashpadEnabled()) {
for (const auto& pair : api::crash_reporter::GetGlobalCrashKeys()) {
if (!switch_value.empty())
switch_value += ",";
switch_value += pair.first;
switch_value += "=";
switch_value += pair.second;
}
command_line->AppendSwitchASCII(switches::kGlobalCrashKeys, switch_value);
}
command_line->AppendSwitchASCII(switches::kGlobalCrashKeys, switch_value);
}
#endif

View File

@@ -118,11 +118,7 @@ ElectronBrowserContext::ElectronBrowserContext(const std::string& partition,
base::StringToInt(command_line->GetSwitchValueASCII(switches::kDiskCacheSize),
&max_cache_size_);
if (!base::PathService::Get(chrome::DIR_USER_DATA, &path_)) {
base::PathService::Get(DIR_APP_DATA, &path_);
path_ = path_.Append(base::FilePath::FromUTF8Unsafe(GetApplicationName()));
base::PathService::Override(chrome::DIR_USER_DATA, path_);
}
CHECK(base::PathService::Get(chrome::DIR_USER_DATA, &path_));
if (!in_memory && !partition.empty())
path_ = path_.Append(FILE_PATH_LITERAL("Partitions"))

View File

@@ -66,11 +66,15 @@ void LoginHandler::EmitEvent(
details.Set("firstAuthAttempt", first_auth_attempt);
details.Set("responseHeaders", response_headers.get());
auto weak_this = weak_factory_.GetWeakPtr();
bool default_prevented =
api_web_contents->Emit("login", std::move(details), auth_info,
base::BindOnce(&LoginHandler::CallbackFromJS,
weak_factory_.GetWeakPtr()));
if (!default_prevented && auth_required_callback_) {
// ⚠️ NB, if CallbackFromJS is called during Emit(), |this| will have been
// deleted. Check the weak ptr before accessing any member variables to
// prevent UAF.
if (weak_this && !default_prevented && auth_required_callback_) {
std::move(auth_required_callback_).Run(absl::nullopt);
}
}

View File

@@ -154,13 +154,16 @@ static NSDictionary* UNNotificationResponseToNSDictionary(
#endif
restorationHandler {
std::string activity_type(base::SysNSStringToUTF8(userActivity.activityType));
NSURL* url = userActivity.webpageURL;
NSDictionary* details = url ? @{@"webpageURL" : [url absoluteString]} : @{};
if (!userActivity.userInfo)
return NO;
electron::Browser* browser = electron::Browser::Get();
return browser->ContinueUserActivity(
activity_type,
electron::NSDictionaryToDictionaryValue(userActivity.userInfo))
electron::NSDictionaryToDictionaryValue(userActivity.userInfo),
electron::NSDictionaryToDictionaryValue(details))
? YES
: NO;
}

View File

@@ -1690,7 +1690,7 @@ bool NativeWindowMac::IsActive() const {
}
void NativeWindowMac::ReorderButtonsView() {
if (buttons_view_) {
if (buttons_view_ && !IsFullscreen()) {
[buttons_view_ removeFromSuperview];
[[window_ contentView] addSubview:buttons_view_];
}

View File

@@ -170,6 +170,83 @@ void OnWrite(std::unique_ptr<WriteData> write_data, MojoResult result) {
} // namespace
ElectronURLLoaderFactory::RedirectedRequest::RedirectedRequest(
const net::RedirectInfo& redirect_info,
mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory_remote)
: redirect_info_(redirect_info),
request_id_(request_id),
options_(options),
request_(request),
client_(std::move(client)),
traffic_annotation_(traffic_annotation) {
loader_receiver_.Bind(std::move(loader_receiver));
loader_receiver_.set_disconnect_handler(
base::BindOnce(&ElectronURLLoaderFactory::RedirectedRequest::DeleteThis,
base::Unretained(this)));
target_factory_remote_.Bind(std::move(target_factory_remote));
target_factory_remote_.set_disconnect_handler(base::BindOnce(
&ElectronURLLoaderFactory::RedirectedRequest::OnTargetFactoryError,
base::Unretained(this)));
}
ElectronURLLoaderFactory::RedirectedRequest::~RedirectedRequest() = default;
void ElectronURLLoaderFactory::RedirectedRequest::FollowRedirect(
const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const net::HttpRequestHeaders& modified_cors_exempt_headers,
const absl::optional<GURL>& new_url) {
// Update |request_| with info from the redirect, so that it's accurate
// The following references code in WorkerScriptLoader::FollowRedirect
bool should_clear_upload = false;
net::RedirectUtil::UpdateHttpRequest(
request_.url, request_.method, redirect_info_, removed_headers,
modified_headers, &request_.headers, &should_clear_upload);
request_.cors_exempt_headers.MergeFrom(modified_cors_exempt_headers);
for (const std::string& name : removed_headers)
request_.cors_exempt_headers.RemoveHeader(name);
if (should_clear_upload)
request_.request_body = nullptr;
request_.url = redirect_info_.new_url;
request_.method = redirect_info_.new_method;
request_.site_for_cookies = redirect_info_.new_site_for_cookies;
request_.referrer = GURL(redirect_info_.new_referrer);
request_.referrer_policy = redirect_info_.new_referrer_policy;
// Create a new loader to process the redirect and destroy this one
target_factory_remote_->CreateLoaderAndStart(
loader_receiver_.Unbind(), request_id_, options_, request_,
std::move(client_), traffic_annotation_);
DeleteThis();
}
void ElectronURLLoaderFactory::RedirectedRequest::OnTargetFactoryError() {
// Can't create a new loader at this point, so the request can't continue
mojo::Remote<network::mojom::URLLoaderClient> client_remote(
std::move(client_));
client_remote->OnComplete(
network::URLLoaderCompletionStatus(net::ERR_FAILED));
client_remote.reset();
DeleteThis();
}
void ElectronURLLoaderFactory::RedirectedRequest::DeleteThis() {
loader_receiver_.reset();
target_factory_remote_.reset();
delete this;
}
// static
mojo::PendingRemote<network::mojom::URLLoaderFactory>
ElectronURLLoaderFactory::Create(ProtocolType type,
@@ -202,12 +279,18 @@ void ElectronURLLoaderFactory::CreateLoaderAndStart(
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
mojo::PendingRemote<network::mojom::URLLoaderFactory> proxy_factory;
// |StartLoading| is used for both intercepted and registered protocols,
// and on redirects it needs a factory to use to create a loader for the
// new request. So in this case, this factory is the target factory.
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory;
this->Clone(target_factory.InitWithNewPipeAndPassReceiver());
handler_.Run(
request,
base::BindOnce(&ElectronURLLoaderFactory::StartLoading, std::move(loader),
request_id, options, request, std::move(client),
traffic_annotation, std::move(proxy_factory), type_));
traffic_annotation, std::move(target_factory), type_));
}
// static
@@ -230,7 +313,7 @@ void ElectronURLLoaderFactory::StartLoading(
const network::ResourceRequest& request,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
mojo::PendingRemote<network::mojom::URLLoaderFactory> proxy_factory,
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory,
ProtocolType type,
gin::Arguments* args) {
// Send network error when there is no argument passed.
@@ -280,13 +363,6 @@ void ElectronURLLoaderFactory::StartLoading(
request.url.Resolve(location),
net::RedirectUtil::GetReferrerPolicyHeader(head->headers.get()), false);
network::ResourceRequest new_request = request;
new_request.method = redirect_info.new_method;
new_request.url = redirect_info.new_url;
new_request.site_for_cookies = redirect_info.new_site_for_cookies;
new_request.referrer = GURL(redirect_info.new_referrer);
new_request.referrer_policy = redirect_info.new_referrer_policy;
DCHECK(client.is_valid());
mojo::Remote<network::mojom::URLLoaderClient> client_remote(
@@ -294,33 +370,15 @@ void ElectronURLLoaderFactory::StartLoading(
client_remote->OnReceiveRedirect(redirect_info, std::move(head));
// Unbound client, so it an be passed to sub-methods
client = client_remote.Unbind();
// When the redirection comes from an intercepted scheme (which has
// |proxy_factory| passed), we ask the proxy factory to create a loader
// for new URL, otherwise we call |StartLoadingHttp|, which creates
// loader with default factory.
//
// Note that when handling requests for intercepted scheme, creating loader
// with default factory (i.e. calling StartLoadingHttp) would bypass the
// ProxyingURLLoaderFactory, we have to explicitly use the proxy factory to
// create loader so it is possible to have handlers of intercepted scheme
// getting called recursively, which is a behavior expected in protocol
// module.
//
// I'm not sure whether this is an intended behavior in Chromium.
if (proxy_factory.is_valid()) {
mojo::Remote<network::mojom::URLLoaderFactory> proxy_factory_remote(
std::move(proxy_factory));
// Bind the URLLoader receiver and wait for a FollowRedirect request, or for
// the remote to disconnect, which will happen if the request is aborted.
// That may happen when the redirect is to a different scheme, which will
// cause the URL loader to be destroyed and a new one created using the
// factory for that scheme.
new RedirectedRequest(redirect_info, std::move(loader), request_id, options,
request, client_remote.Unbind(), traffic_annotation,
std::move(target_factory));
proxy_factory_remote->CreateLoaderAndStart(
std::move(loader), request_id, options, new_request,
std::move(client), traffic_annotation);
} else {
StartLoadingHttp(std::move(loader), new_request, std::move(client),
traffic_annotation,
gin::Dictionary::CreateEmpty(args->isolate()));
}
return;
}
@@ -360,7 +418,7 @@ void ElectronURLLoaderFactory::StartLoading(
}
StartLoading(std::move(loader), request_id, options, request,
std::move(client), traffic_annotation,
std::move(proxy_factory), type, args);
std::move(target_factory), type, args);
break;
}
}

View File

@@ -8,6 +8,7 @@
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
@@ -15,9 +16,11 @@
#include "mojo/public/cpp/bindings/remote.h"
#include "net/url_request/url_request_job_factory.h"
#include "services/network/public/cpp/self_deleting_url_loader_factory.h"
#include "services/network/public/mojom/url_loader.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "shell/common/gin_helper/dictionary.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace electron {
@@ -43,6 +46,52 @@ using HandlersMap =
// Implementation of URLLoaderFactory.
class ElectronURLLoaderFactory : public network::SelfDeletingURLLoaderFactory {
public:
// This class binds a URLLoader receiver in the case of a redirect, waiting
// for |FollowRedirect| to be called at which point the new request will be
// started, and the receiver will be unbound letting a new URLLoader bind it
class RedirectedRequest : public network::mojom::URLLoader {
public:
RedirectedRequest(
const net::RedirectInfo& redirect_info,
mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
mojo::PendingRemote<network::mojom::URLLoaderFactory>
target_factory_remote);
~RedirectedRequest() override;
// network::mojom::URLLoader:
void FollowRedirect(
const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const net::HttpRequestHeaders& modified_cors_exempt_headers,
const absl::optional<GURL>& new_url) override;
void SetPriority(net::RequestPriority priority,
int32_t intra_priority_value) override {}
void PauseReadingBodyFromNet() override {}
void ResumeReadingBodyFromNet() override {}
void OnTargetFactoryError();
void DeleteThis();
private:
net::RedirectInfo redirect_info_;
mojo::Receiver<network::mojom::URLLoader> loader_receiver_{this};
int32_t request_id_;
uint32_t options_;
network::ResourceRequest request_;
mojo::PendingRemote<network::mojom::URLLoaderClient> client_;
net::MutableNetworkTrafficAnnotationTag traffic_annotation_;
mojo::Remote<network::mojom::URLLoaderFactory> target_factory_remote_;
DISALLOW_COPY_AND_ASSIGN(RedirectedRequest);
};
static mojo::PendingRemote<network::mojom::URLLoaderFactory> Create(
ProtocolType type,
const ProtocolHandler& handler);
@@ -64,7 +113,7 @@ class ElectronURLLoaderFactory : public network::SelfDeletingURLLoaderFactory {
const network::ResourceRequest& request,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
mojo::PendingRemote<network::mojom::URLLoaderFactory> proxy_factory,
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory,
ProtocolType type,
gin::Arguments* args);

View File

@@ -50,8 +50,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 15,0,0,20210712
PRODUCTVERSION 15,0,0,20210712
FILEVERSION 15,0,0,1
PRODUCTVERSION 15,0,0,1
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L

View File

@@ -6,10 +6,10 @@
#define SHELL_BROWSER_UI_MESSAGE_BOX_H_
#include <string>
#include <utility>
#include <vector>
#include "base/callback_forward.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/gfx/image/image_skia.h"
namespace electron {
@@ -24,12 +24,11 @@ enum class MessageBoxType {
kQuestion,
};
using DialogResult = std::pair<int, bool>;
struct MessageBoxSettings {
electron::NativeWindow* parent_window = nullptr;
MessageBoxType type = electron::MessageBoxType::kNone;
std::vector<std::string> buttons;
absl::optional<int> id;
int default_id;
int cancel_id;
bool no_link = false;
@@ -53,6 +52,8 @@ typedef base::OnceCallback<void(int code, bool checkbox_checked)>
void ShowMessageBox(const MessageBoxSettings& settings,
MessageBoxCallback callback);
void CloseMessageBox(int id);
// Like ShowMessageBox with simplest settings, but safe to call at very early
// stage of application.
void ShowErrorBox(const std::u16string& title, const std::u16string& content);

View File

@@ -2,15 +2,19 @@
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/ui/gtk_util.h"
#include "shell/browser/ui/message_box.h"
#include <map>
#include "base/callback.h"
#include "base/containers/contains.h"
#include "base/no_destructor.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "shell/browser/browser.h"
#include "shell/browser/native_window_observer.h"
#include "shell/browser/native_window_views.h"
#include "shell/browser/ui/gtk_util.h"
#include "shell/browser/unresponsive_suppressor.h"
#include "ui/base/glib/glib_signal.h"
#include "ui/gfx/image/image_skia.h"
@@ -38,10 +42,17 @@ MessageBoxSettings::~MessageBoxSettings() = default;
namespace {
// <ID, messageBox> map
std::map<int, GtkWidget*>& GetDialogsMap() {
static base::NoDestructor<std::map<int, GtkWidget*>> dialogs;
return *dialogs;
}
class GtkMessageBox : public NativeWindowObserver {
public:
explicit GtkMessageBox(const MessageBoxSettings& settings)
: cancel_id_(settings.cancel_id),
: id_(settings.id),
cancel_id_(settings.cancel_id),
parent_(static_cast<NativeWindow*>(settings.parent_window)) {
// Create dialog.
dialog_ =
@@ -50,6 +61,8 @@ class GtkMessageBox : public NativeWindowObserver {
GetMessageType(settings.type), // type
GTK_BUTTONS_NONE, // no buttons
"%s", settings.message.c_str());
if (id_)
GetDialogsMap()[*id_] = dialog_;
if (!settings.detail.empty())
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog_),
"%s", settings.detail.c_str());
@@ -183,6 +196,9 @@ class GtkMessageBox : public NativeWindowObserver {
private:
electron::UnresponsiveSuppressor unresponsive_suppressor_;
// The id of the dialog.
absl::optional<int> id_;
// The id to return when the dialog is closed without pressing buttons.
int cancel_id_ = 0;
@@ -196,6 +212,8 @@ class GtkMessageBox : public NativeWindowObserver {
};
void GtkMessageBox::OnResponseDialog(GtkWidget* widget, int response) {
if (id_)
GetDialogsMap().erase(*id_);
gtk_widget_hide(dialog_);
if (response < 0)
@@ -217,9 +235,20 @@ int ShowMessageBoxSync(const MessageBoxSettings& settings) {
void ShowMessageBox(const MessageBoxSettings& settings,
MessageBoxCallback callback) {
if (settings.id && base::Contains(GetDialogsMap(), *settings.id))
CloseMessageBox(*settings.id);
(new GtkMessageBox(settings))->RunAsynchronous(std::move(callback));
}
void CloseMessageBox(int id) {
auto it = GetDialogsMap().find(id);
if (it == GetDialogsMap().end()) {
LOG(ERROR) << "CloseMessageBox called with nonexistent ID";
return;
}
gtk_window_close(GTK_WINDOW(it->second));
}
void ShowErrorBox(const std::u16string& title, const std::u16string& content) {
if (Browser::Get()->is_ready()) {
electron::MessageBoxSettings settings;

View File

@@ -4,6 +4,7 @@
#include "shell/browser/ui/message_box.h"
#include <map>
#include <string>
#include <utility>
#include <vector>
@@ -11,8 +12,10 @@
#import <Cocoa/Cocoa.h>
#include "base/callback.h"
#include "base/containers/contains.h"
#include "base/mac/mac_util.h"
#include "base/mac/scoped_nsobject.h"
#include "base/no_destructor.h"
#include "base/strings/sys_string_conversions.h"
#include "shell/browser/native_window.h"
#include "skia/ext/skia_utils_mac.h"
@@ -26,6 +29,12 @@ MessageBoxSettings::~MessageBoxSettings() = default;
namespace {
// <ID, messageBox> map
std::map<int, NSAlert*>& GetDialogsMap() {
static base::NoDestructor<std::map<int, NSAlert*>> dialogs;
return *dialogs;
}
NSAlert* CreateNSAlert(const MessageBoxSettings& settings) {
// Ignore the title; it's the window title on other platforms and ignorable.
NSAlert* alert = [[NSAlert alloc] init];
@@ -128,6 +137,12 @@ void ShowMessageBox(const MessageBoxSettings& settings,
int ret = [[alert autorelease] runModal];
std::move(callback).Run(ret, alert.suppressionButton.state == NSOnState);
} else {
if (settings.id) {
if (base::Contains(GetDialogsMap(), *settings.id))
CloseMessageBox(*settings.id);
GetDialogsMap()[*settings.id] = alert;
}
NSWindow* window =
settings.parent_window
? settings.parent_window->GetNativeWindow().GetNativeNSWindow()
@@ -136,9 +151,19 @@ void ShowMessageBox(const MessageBoxSettings& settings,
// Duplicate the callback object here since c is a reference and gcd would
// only store the pointer, by duplication we can force gcd to store a copy.
__block MessageBoxCallback callback_ = std::move(callback);
__block absl::optional<int> id = std::move(settings.id);
__block int cancel_id = settings.cancel_id;
[alert beginSheetModalForWindow:window
completionHandler:^(NSModalResponse response) {
if (id)
GetDialogsMap().erase(*id);
// When the alert is cancelled programmatically, the
// response would be something like -1000. This currently
// only happens when users call CloseMessageBox API, and we
// should return cancelId as result.
if (response < 0)
response = cancel_id;
std::move(callback_).Run(
response, alert.suppressionButton.state == NSOnState);
[alert release];
@@ -146,6 +171,15 @@ void ShowMessageBox(const MessageBoxSettings& settings,
}
}
void CloseMessageBox(int id) {
auto it = GetDialogsMap().find(id);
if (it == GetDialogsMap().end()) {
LOG(ERROR) << "CloseMessageBox called with nonexistent ID";
return;
}
[NSApp endSheet:it->second.window];
}
void ShowErrorBox(const std::u16string& title, const std::u16string& content) {
NSAlert* alert = [[NSAlert alloc] init];
[alert setMessageText:base::SysUTF16ToNSString(title)];

View File

@@ -11,8 +11,11 @@
#include <map>
#include <vector>
#include "base/containers/contains.h"
#include "base/no_destructor.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "base/task/post_task.h"
#include "base/win/scoped_gdi_object.h"
#include "shell/browser/browser.h"
@@ -30,6 +33,41 @@ MessageBoxSettings::~MessageBoxSettings() = default;
namespace {
struct DialogResult {
int button_id;
bool verification_flag_checked;
};
// <ID, messageBox> map.
//
// Note that the HWND is stored in a unique_ptr, because the pointer of HWND
// will be passed between threads and we need to ensure the memory of HWND is
// not changed while dialogs map is modified.
std::map<int, std::unique_ptr<HWND>>& GetDialogsMap() {
static base::NoDestructor<std::map<int, std::unique_ptr<HWND>>> dialogs;
return *dialogs;
}
// Speical HWND used by the dialogs map.
//
// - ID is used but window has not been created yet.
const HWND kHwndReserve = reinterpret_cast<HWND>(-1);
// - Notification to cancel message box.
const HWND kHwndCancel = reinterpret_cast<HWND>(-2);
// Lock used for modifying HWND between threads.
//
// Note that there might be multiple dialogs being opened at the same time, but
// we only use one lock for them all, because each dialog is independent from
// each other and there is no need to use different lock for each one.
// Also note that the |GetDialogsMap| is only used in the main thread, what is
// shared between threads is the memory of HWND, so there is no need to use lock
// when accessing dialogs map.
base::Lock& GetHWNDLock() {
static base::NoDestructor<base::Lock> lock;
return *lock;
}
// Small command ID values are already taken by Windows, we have to start from
// a large number to avoid conflicts with Windows.
const int kIDStart = 100;
@@ -76,6 +114,31 @@ void MapToCommonID(const std::vector<std::wstring>& buttons,
}
}
// Callback of the task dialog. The TaskDialogIndirect API does not provide the
// HWND of the dialog, and we have to listen to the TDN_CREATED message to get
// it.
// Note that this callback runs in dialog thread instead of main thread, so it
// is possible for CloseMessageBox to be called before or all after the dialog
// window is created.
HRESULT CALLBACK
TaskDialogCallback(HWND hwnd, UINT msg, WPARAM, LPARAM, LONG_PTR data) {
if (msg == TDN_CREATED) {
HWND* target = reinterpret_cast<HWND*>(data);
// Lock since CloseMessageBox might be called.
base::AutoLock lock(GetHWNDLock());
if (*target == kHwndCancel) {
// The dialog is cancelled before it is created, close it directly.
::PostMessage(hwnd, WM_CLOSE, 0, 0);
} else if (*target == kHwndReserve) {
// Otherwise save the hwnd.
*target = hwnd;
} else {
NOTREACHED();
}
}
return S_OK;
}
DialogResult ShowTaskDialogWstr(NativeWindow* parent,
MessageBoxType type,
const std::vector<std::wstring>& buttons,
@@ -87,7 +150,8 @@ DialogResult ShowTaskDialogWstr(NativeWindow* parent,
const std::u16string& detail,
const std::u16string& checkbox_label,
bool checkbox_checked,
const gfx::ImageSkia& icon) {
const gfx::ImageSkia& icon,
HWND* hwnd) {
TASKDIALOG_FLAGS flags =
TDF_SIZE_TO_CONTENT | // Show all content.
TDF_ALLOW_DIALOG_CANCELLATION; // Allow canceling the dialog.
@@ -169,12 +233,17 @@ DialogResult ShowTaskDialogWstr(NativeWindow* parent,
config.dwFlags |= TDF_USE_COMMAND_LINKS; // custom buttons as links.
}
int button_id;
// Pass a callback to receive the HWND of the message box.
if (hwnd) {
config.pfCallback = &TaskDialogCallback;
config.lpCallbackData = reinterpret_cast<LONG_PTR>(hwnd);
}
int id = 0;
BOOL verificationFlagChecked = FALSE;
TaskDialogIndirect(&config, &id, nullptr, &verificationFlagChecked);
BOOL verification_flag_checked = FALSE;
TaskDialogIndirect(&config, &id, nullptr, &verification_flag_checked);
int button_id;
if (id_map.find(id) != id_map.end()) // common button.
button_id = id_map[id];
else if (id >= kIDStart) // custom button.
@@ -182,10 +251,11 @@ DialogResult ShowTaskDialogWstr(NativeWindow* parent,
else
button_id = cancel_id;
return std::make_pair(button_id, verificationFlagChecked);
return {button_id, static_cast<bool>(verification_flag_checked)};
}
DialogResult ShowTaskDialogUTF8(const MessageBoxSettings& settings) {
DialogResult ShowTaskDialogUTF8(const MessageBoxSettings& settings,
HWND* hwnd) {
std::vector<std::wstring> buttons;
for (const auto& button : settings.buttons)
buttons.push_back(base::UTF8ToWide(button));
@@ -199,31 +269,67 @@ DialogResult ShowTaskDialogUTF8(const MessageBoxSettings& settings) {
return ShowTaskDialogWstr(
settings.parent_window, settings.type, buttons, settings.default_id,
settings.cancel_id, settings.no_link, title, message, detail,
checkbox_label, settings.checkbox_checked, settings.icon);
checkbox_label, settings.checkbox_checked, settings.icon, hwnd);
}
} // namespace
int ShowMessageBoxSync(const MessageBoxSettings& settings) {
electron::UnresponsiveSuppressor suppressor;
DialogResult result = ShowTaskDialogUTF8(settings);
return result.first;
DialogResult result = ShowTaskDialogUTF8(settings, nullptr);
return result.button_id;
}
void ShowMessageBox(const MessageBoxSettings& settings,
MessageBoxCallback callback) {
dialog_thread::Run(base::BindOnce(&ShowTaskDialogUTF8, settings),
base::BindOnce(
[](MessageBoxCallback callback, DialogResult result) {
std::move(callback).Run(result.first, result.second);
},
std::move(callback)));
// The dialog is created in a new thread so we don't know its HWND yet, put
// kHwndReserve in the dialogs map for now.
HWND* hwnd = nullptr;
if (settings.id) {
if (base::Contains(GetDialogsMap(), *settings.id))
CloseMessageBox(*settings.id);
auto it = GetDialogsMap().emplace(*settings.id,
std::make_unique<HWND>(kHwndReserve));
hwnd = it.first->second.get();
}
dialog_thread::Run(
base::BindOnce(&ShowTaskDialogUTF8, settings, base::Unretained(hwnd)),
base::BindOnce(
[](MessageBoxCallback callback, absl::optional<int> id,
DialogResult result) {
if (id)
GetDialogsMap().erase(*id);
std::move(callback).Run(result.button_id,
result.verification_flag_checked);
},
std::move(callback), settings.id));
}
void CloseMessageBox(int id) {
auto it = GetDialogsMap().find(id);
if (it == GetDialogsMap().end()) {
LOG(ERROR) << "CloseMessageBox called with nonexistent ID";
return;
}
HWND* hwnd = it->second.get();
// Lock since the TaskDialogCallback might be saving the dialog's HWND.
base::AutoLock lock(GetHWNDLock());
DCHECK(*hwnd != kHwndCancel);
if (*hwnd == kHwndReserve) {
// If the dialog window has not been created yet, tell it to cancel.
*hwnd = kHwndCancel;
} else {
// Otherwise send a message to close it.
::PostMessage(*hwnd, WM_CLOSE, 0, 0);
}
}
void ShowErrorBox(const std::u16string& title, const std::u16string& content) {
electron::UnresponsiveSuppressor suppressor;
ShowTaskDialogWstr(nullptr, MessageBoxType::kError, {}, -1, 0, false,
u"Error", title, content, u"", false, gfx::ImageSkia());
u"Error", title, content, u"", false, gfx::ImageSkia(),
nullptr);
}
} // namespace electron

View File

@@ -121,7 +121,7 @@
weight:NSFontWeightRegular]
};
[attributed_title
setAttributes:attributes
addAttributes:attributes
range:NSMakeRange(0, [attributed_title length])];
}
} else if ([font_type isEqualToString:@"monospacedDigit"]) {
@@ -132,7 +132,7 @@
weight:NSFontWeightRegular]
};
[attributed_title
setAttributes:attributes
addAttributes:attributes
range:NSMakeRange(0, [attributed_title length])];
}
}

View File

@@ -13,27 +13,21 @@
namespace electron {
namespace {
base::NoDestructor<std::string> g_overridden_application_name;
base::NoDestructor<std::string> g_overridden_application_version;
} // namespace
// name
void OverrideApplicationName(const std::string& name) {
*g_overridden_application_name = name;
}
std::string GetOverriddenApplicationName() {
return *g_overridden_application_name;
std::string& OverriddenApplicationName() {
static base::NoDestructor<std::string> overridden_application_name;
return *overridden_application_name;
}
// version
void OverrideApplicationVersion(const std::string& version) {
*g_overridden_application_version = version;
std::string& OverriddenApplicationVersion() {
static base::NoDestructor<std::string> overridden_application_version;
return *overridden_application_version;
}
std::string GetOverriddenApplicationVersion() {
return *g_overridden_application_version;
std::string GetPossiblyOverriddenApplicationName() {
std::string ret = OverriddenApplicationName();
if (!ret.empty())
return ret;
return GetApplicationName();
}
std::string GetApplicationUserAgent() {

View File

@@ -13,11 +13,10 @@
namespace electron {
void OverrideApplicationName(const std::string& name);
std::string GetOverriddenApplicationName();
std::string& OverriddenApplicationName();
std::string& OverriddenApplicationVersion();
void OverrideApplicationVersion(const std::string& version);
std::string GetOverriddenApplicationVersion();
std::string GetPossiblyOverriddenApplicationName();
std::string GetApplicationName();
std::string GetApplicationVersion();

View File

@@ -33,7 +33,7 @@ namespace electron {
std::string GetApplicationName() {
// attempt #1: the string set in app.setName()
std::string ret = GetOverriddenApplicationName();
std::string ret = OverriddenApplicationName();
// attempt #2: the 'Name' entry from .desktop file's [Desktop] section
if (ret.empty()) {
@@ -64,7 +64,7 @@ std::string GetApplicationVersion() {
// try to use the string set in app.setVersion()
if (ret.empty())
ret = GetOverriddenApplicationVersion();
ret = OverriddenApplicationVersion();
// no known version number; return some safe fallback
if (ret.empty()) {

View File

@@ -19,6 +19,21 @@
#include "shell/common/options_switches.h"
#include "third_party/crashpad/crashpad/client/annotation.h"
#include "gin/wrappable.h"
#include "shell/browser/api/electron_api_browser_view.h"
#include "shell/browser/api/electron_api_cookies.h"
#include "shell/browser/api/electron_api_desktop_capturer.h"
#include "shell/browser/api/electron_api_menu.h"
#include "shell/browser/api/electron_api_net_log.h"
#include "shell/browser/api/electron_api_notification.h"
#include "shell/browser/api/electron_api_power_monitor.h"
#include "shell/browser/api/electron_api_protocol.h"
#include "shell/browser/api/electron_api_service_worker_context.h"
#include "shell/browser/api/electron_api_web_contents.h"
#include "shell/browser/api/electron_api_web_frame_main.h"
#include "shell/browser/api/electron_api_web_request.h"
#include "shell/common/api/electron_api_native_image.h"
namespace electron {
namespace crash_keys {
@@ -155,6 +170,48 @@ void SetPlatformCrashKey() {
#endif
}
void SetCrashKeyForGinWrappable(gin::WrapperInfo* info) {
std::string crash_location;
// Adds a breadcrumb for crashes within gin::WrappableBase::SecondWeakCallback
// (see patch: add_gin_wrappable_crash_key.patch)
// Compares the pointers for the kWrapperInfo within SecondWeakCallback
// with the wrapper info from classes that use gin::Wrappable and
// could potentially retain a reference after deletion.
if (info == &electron::api::WebContents::kWrapperInfo)
crash_location = "WebContents";
else if (info == &electron::api::BrowserView::kWrapperInfo)
crash_location = "BrowserView";
else if (info == &electron::api::Notification::kWrapperInfo)
crash_location = "Notification";
else if (info == &electron::api::Cookies::kWrapperInfo)
crash_location = "Cookies";
else if (info == &electron::api::DesktopCapturer::kWrapperInfo)
crash_location = "DesktopCapturer";
else if (info == &electron::api::NetLog::kWrapperInfo)
crash_location = "NetLog";
else if (info == &electron::api::NativeImage::kWrapperInfo)
crash_location = "NativeImage";
else if (info == &electron::api::Menu::kWrapperInfo)
crash_location = "Menu";
else if (info == &electron::api::PowerMonitor::kWrapperInfo)
crash_location = "PowerMonitor";
else if (info == &electron::api::Protocol::kWrapperInfo)
crash_location = "Protocol";
else if (info == &electron::api::ServiceWorkerContext::kWrapperInfo)
crash_location = "ServiceWorkerContext";
else if (info == &electron::api::WebFrameMain::kWrapperInfo)
crash_location = "WebFrameMain";
else if (info == &electron::api::WebRequest::kWrapperInfo)
crash_location = "WebRequest";
else
crash_location =
"Deleted kWrapperInfo does not match listed component. Please review "
"listed crash keys.";
SetCrashKey("gin-wrappable-fatal.location", crash_location);
}
} // namespace crash_keys
} // namespace electron

View File

@@ -8,6 +8,8 @@
#include <map>
#include <string>
#include "gin/wrappable.h"
namespace base {
class CommandLine;
}
@@ -22,6 +24,7 @@ void GetCrashKeys(std::map<std::string, std::string>* keys);
void SetCrashKeysFromCommandLine(const base::CommandLine& command_line);
void SetPlatformCrashKey();
void SetCrashKeyForGinWrappable(gin::WrapperInfo* info);
} // namespace crash_keys

View File

@@ -144,10 +144,44 @@ struct Converter<blink::WebInputEvent::Modifiers> {
*out = blink::WebInputEvent::Modifiers::kIsLeft;
else if (modifier == "right")
*out = blink::WebInputEvent::Modifiers::kIsRight;
// TODO(nornagon): the rest of the modifiers
return true;
}
};
std::vector<std::string> ModifiersToArray(int modifiers) {
using Modifiers = blink::WebInputEvent::Modifiers;
std::vector<std::string> modifier_strings;
if (modifiers & Modifiers::kShiftKey)
modifier_strings.push_back("shift");
if (modifiers & Modifiers::kControlKey)
modifier_strings.push_back("control");
if (modifiers & Modifiers::kAltKey)
modifier_strings.push_back("alt");
if (modifiers & Modifiers::kMetaKey)
modifier_strings.push_back("meta");
if (modifiers & Modifiers::kIsKeyPad)
modifier_strings.push_back("iskeypad");
if (modifiers & Modifiers::kIsAutoRepeat)
modifier_strings.push_back("isautorepeat");
if (modifiers & Modifiers::kLeftButtonDown)
modifier_strings.push_back("leftbuttondown");
if (modifiers & Modifiers::kMiddleButtonDown)
modifier_strings.push_back("middlebuttondown");
if (modifiers & Modifiers::kRightButtonDown)
modifier_strings.push_back("rightbuttondown");
if (modifiers & Modifiers::kCapsLockOn)
modifier_strings.push_back("capslock");
if (modifiers & Modifiers::kNumLockOn)
modifier_strings.push_back("numlock");
if (modifiers & Modifiers::kIsLeft)
modifier_strings.push_back("left");
if (modifiers & Modifiers::kIsRight)
modifier_strings.push_back("right");
// TODO(nornagon): the rest of the modifiers
return modifier_strings;
}
blink::WebInputEvent::Type GetWebInputEventType(v8::Isolate* isolate,
v8::Local<v8::Value> val) {
blink::WebInputEvent::Type type = blink::WebInputEvent::Type::kUndefined;
@@ -219,6 +253,51 @@ bool Converter<blink::WebKeyboardEvent>::FromV8(v8::Isolate* isolate,
return true;
}
int GetKeyLocationCode(const blink::WebInputEvent& key) {
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/events/keyboard_event.h;l=46;drc=1ff6437e65b183e673b7b4f25060b74dc2ba5c37
enum KeyLocationCode {
kDomKeyLocationStandard = 0x00,
kDomKeyLocationLeft = 0x01,
kDomKeyLocationRight = 0x02,
kDomKeyLocationNumpad = 0x03
};
using Modifiers = blink::WebInputEvent::Modifiers;
if (key.GetModifiers() & Modifiers::kIsKeyPad)
return kDomKeyLocationNumpad;
if (key.GetModifiers() & Modifiers::kIsLeft)
return kDomKeyLocationLeft;
if (key.GetModifiers() & Modifiers::kIsRight)
return kDomKeyLocationRight;
return kDomKeyLocationStandard;
}
v8::Local<v8::Value> Converter<blink::WebKeyboardEvent>::ToV8(
v8::Isolate* isolate,
const blink::WebKeyboardEvent& in) {
gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
if (in.GetType() == blink::WebInputEvent::Type::kRawKeyDown)
dict.Set("type", "keyDown");
else if (in.GetType() == blink::WebInputEvent::Type::kKeyUp)
dict.Set("type", "keyUp");
dict.Set("key", ui::KeycodeConverter::DomKeyToKeyString(in.dom_key));
dict.Set("code", ui::KeycodeConverter::DomCodeToCodeString(
static_cast<ui::DomCode>(in.dom_code)));
using Modifiers = blink::WebInputEvent::Modifiers;
dict.Set("isAutoRepeat", (in.GetModifiers() & Modifiers::kIsAutoRepeat) != 0);
dict.Set("isComposing", (in.GetModifiers() & Modifiers::kIsComposing) != 0);
dict.Set("shift", (in.GetModifiers() & Modifiers::kShiftKey) != 0);
dict.Set("control", (in.GetModifiers() & Modifiers::kControlKey) != 0);
dict.Set("alt", (in.GetModifiers() & Modifiers::kAltKey) != 0);
dict.Set("meta", (in.GetModifiers() & Modifiers::kMetaKey) != 0);
dict.Set("location", GetKeyLocationCode(in));
dict.Set("_modifiers", in.GetModifiers());
dict.Set("modifiers", ModifiersToArray(in.GetModifiers()));
return dict.GetHandle();
}
bool Converter<blink::WebMouseEvent>::FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
blink::WebMouseEvent* out) {

View File

@@ -36,6 +36,8 @@ struct Converter<blink::WebKeyboardEvent> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
blink::WebKeyboardEvent* out);
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
const blink::WebKeyboardEvent& in);
};
template <>

View File

@@ -303,25 +303,7 @@ bool Converter<content::NativeWebKeyboardEvent>::FromV8(
v8::Local<v8::Value> Converter<content::NativeWebKeyboardEvent>::ToV8(
v8::Isolate* isolate,
const content::NativeWebKeyboardEvent& in) {
gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
if (in.GetType() == blink::WebInputEvent::Type::kRawKeyDown)
dict.Set("type", "keyDown");
else if (in.GetType() == blink::WebInputEvent::Type::kKeyUp)
dict.Set("type", "keyUp");
dict.Set("key", ui::KeycodeConverter::DomKeyToKeyString(in.dom_key));
dict.Set("code", ui::KeycodeConverter::DomCodeToCodeString(
static_cast<ui::DomCode>(in.dom_code)));
using Modifiers = blink::WebInputEvent::Modifiers;
dict.Set("isAutoRepeat", (in.GetModifiers() & Modifiers::kIsAutoRepeat) != 0);
dict.Set("isComposing", (in.GetModifiers() & Modifiers::kIsComposing) != 0);
dict.Set("shift", (in.GetModifiers() & Modifiers::kShiftKey) != 0);
dict.Set("control", (in.GetModifiers() & Modifiers::kControlKey) != 0);
dict.Set("alt", (in.GetModifiers() & Modifiers::kAltKey) != 0);
dict.Set("meta", (in.GetModifiers() & Modifiers::kMetaKey) != 0);
return dict.GetHandle();
return ConvertToV8(isolate, static_cast<blink::WebKeyboardEvent>(in));
}
} // namespace gin

View File

@@ -4,9 +4,9 @@
#include "shell/common/gin_converters/message_box_converter.h"
#include "gin/dictionary.h"
#include "shell/common/gin_converters/image_converter.h"
#include "shell/common/gin_converters/native_window_converter.h"
#include "shell/common/gin_helper/dictionary.h"
namespace gin {
@@ -14,7 +14,7 @@ bool Converter<electron::MessageBoxSettings>::FromV8(
v8::Isolate* isolate,
v8::Local<v8::Value> val,
electron::MessageBoxSettings* out) {
gin::Dictionary dict(nullptr);
gin_helper::Dictionary dict;
int type = 0;
if (!ConvertFromV8(isolate, val, &dict))
return false;
@@ -22,6 +22,7 @@ bool Converter<electron::MessageBoxSettings>::FromV8(
dict.Get("messageBoxType", &type);
out->type = static_cast<electron::MessageBoxType>(type);
dict.Get("buttons", &out->buttons);
dict.GetOptional("id", &out->id);
dict.Get("defaultId", &out->default_id);
dict.Get("cancelId", &out->cancel_id);
dict.Get("title", &out->title);

View File

@@ -924,6 +924,10 @@ describe('app module', () => {
expect(app.getPath('recent')).to.equal('C:\\fake-path');
});
}
it('uses the app name in getPath(userData)', () => {
expect(app.getPath('userData')).to.include(app.name);
});
});
describe('setPath(name, path)', () => {

View File

@@ -4204,7 +4204,8 @@ describe('BrowserWindow module', () => {
expect(w.isFullScreen()).to.be.false('isFullScreen');
});
it('multiple windows inherit correct fullscreen state', async () => {
// FIXME: https://github.com/electron/electron/issues/30140
xit('multiple windows inherit correct fullscreen state', async () => {
const w = new BrowserWindow();
const enterFullScreen = emittedOnce(w, 'enter-full-screen');
w.setFullScreen(true);

View File

@@ -138,224 +138,455 @@ function waitForNewFileInDir (dir: string): Promise<string[]> {
// TODO(nornagon): Fix tests on linux/arm.
ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_TESTS)('crashReporter module', function () {
describe('should send minidump', () => {
it('when renderer crashes', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('renderer', port);
const crash = await waitForCrash();
checkCrash('renderer', crash);
expect(crash.mainProcessSpecific).to.be.undefined();
});
it('when sandboxed renderer crashes', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('sandboxed-renderer', port);
const crash = await waitForCrash();
checkCrash('renderer', crash);
expect(crash.mainProcessSpecific).to.be.undefined();
});
// TODO(nornagon): Minidump generation in main/node process on Linux/Arm is
// broken (//components/crash prints "Failed to generate minidump"). Figure
// out why.
ifit(!isLinuxOnArm)('when main process crashes', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('main', port);
const crash = await waitForCrash();
checkCrash('browser', crash);
expect(crash.mainProcessSpecific).to.equal('mps');
});
ifit(!isLinuxOnArm)('when a node process crashes', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('node', port);
const crash = await waitForCrash();
checkCrash('node', crash);
expect(crash.mainProcessSpecific).to.be.undefined();
expect(crash.rendererSpecific).to.be.undefined();
});
describe('with guid', () => {
for (const processType of ['main', 'renderer', 'sandboxed-renderer']) {
it(`when ${processType} crashes`, async () => {
for (const withLinuxCrashpad of (process.platform === 'linux' ? [false, true] : [false])) {
const crashpadExtraArgs = withLinuxCrashpad ? ['--enable-crashpad'] : [];
describe(withLinuxCrashpad ? '(with crashpad)' : '', () => {
describe('should send minidump', () => {
it('when renderer crashes', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp(processType, port);
runCrashApp('renderer', port, crashpadExtraArgs);
const crash = await waitForCrash();
expect(crash.guid).to.be.a('string');
checkCrash('renderer', crash);
expect(crash.mainProcessSpecific).to.be.undefined();
});
}
it('is a consistent id', async () => {
let crash1Guid;
let crash2Guid;
{
it('when sandboxed renderer crashes', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('main', port);
runCrashApp('sandboxed-renderer', port, crashpadExtraArgs);
const crash = await waitForCrash();
crash1Guid = crash.guid;
}
{
checkCrash('renderer', crash);
expect(crash.mainProcessSpecific).to.be.undefined();
});
// TODO(nornagon): Minidump generation in main/node process on Linux/Arm is
// broken (//components/crash prints "Failed to generate minidump"). Figure
// out why.
ifit(!isLinuxOnArm)('when main process crashes', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('main', port);
runCrashApp('main', port, crashpadExtraArgs);
const crash = await waitForCrash();
crash2Guid = crash.guid;
}
expect(crash2Guid).to.equal(crash1Guid);
});
});
checkCrash('browser', crash);
expect(crash.mainProcessSpecific).to.equal('mps');
});
describe('with extra parameters', () => {
it('when renderer crashes', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('renderer', port, ['--set-extra-parameters-in-renderer']);
const crash = await waitForCrash();
checkCrash('renderer', crash);
expect(crash.mainProcessSpecific).to.be.undefined();
expect(crash.rendererSpecific).to.equal('rs');
expect(crash.addedThenRemoved).to.be.undefined();
});
ifit(!isLinuxOnArm)('when a node process crashes', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('node', port, crashpadExtraArgs);
const crash = await waitForCrash();
checkCrash('node', crash);
expect(crash.mainProcessSpecific).to.be.undefined();
expect(crash.rendererSpecific).to.be.undefined();
});
it('when sandboxed renderer crashes', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('sandboxed-renderer', port, ['--set-extra-parameters-in-renderer']);
const crash = await waitForCrash();
checkCrash('renderer', crash);
expect(crash.mainProcessSpecific).to.be.undefined();
expect(crash.rendererSpecific).to.equal('rs');
expect(crash.addedThenRemoved).to.be.undefined();
});
describe('with guid', () => {
for (const processType of ['main', 'renderer', 'sandboxed-renderer']) {
it(`when ${processType} crashes`, async () => {
const { port, waitForCrash } = await startServer();
runCrashApp(processType, port, crashpadExtraArgs);
const crash = await waitForCrash();
expect(crash.guid).to.be.a('string');
});
}
it('contains v8 crash keys when a v8 crash occurs', async () => {
const { remotely } = await startRemoteControlApp();
const { port, waitForCrash } = await startServer();
await remotely((port: number) => {
require('electron').crashReporter.start({
submitURL: `http://127.0.0.1:${port}`,
compress: false,
ignoreSystemCrashHandler: true
it('is a consistent id', async () => {
let crash1Guid;
let crash2Guid;
{
const { port, waitForCrash } = await startServer();
runCrashApp('main', port, crashpadExtraArgs);
const crash = await waitForCrash();
crash1Guid = crash.guid;
}
{
const { port, waitForCrash } = await startServer();
runCrashApp('main', port, crashpadExtraArgs);
const crash = await waitForCrash();
crash2Guid = crash.guid;
}
expect(crash2Guid).to.equal(crash1Guid);
});
}, [port]);
remotely(() => {
const { BrowserWindow } = require('electron');
const bw = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
bw.loadURL('about:blank');
bw.webContents.executeJavaScript('process._linkedBinding(\'electron_common_v8_util\').triggerFatalErrorForTesting()');
});
const crash = await waitForCrash();
expect(crash.prod).to.equal('Electron');
expect(crash._productName).to.equal('electron-test-remote-control');
expect(crash.process_type).to.equal('renderer');
expect(crash['electron.v8-fatal.location']).to.equal('v8::Context::New()');
expect(crash['electron.v8-fatal.message']).to.equal('Circular extension dependency');
describe('with extra parameters', () => {
it('when renderer crashes', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('renderer', port, ['--set-extra-parameters-in-renderer', ...crashpadExtraArgs]);
const crash = await waitForCrash();
checkCrash('renderer', crash);
expect(crash.mainProcessSpecific).to.be.undefined();
expect(crash.rendererSpecific).to.equal('rs');
expect(crash.addedThenRemoved).to.be.undefined();
});
it('when sandboxed renderer crashes', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('sandboxed-renderer', port, ['--set-extra-parameters-in-renderer', ...crashpadExtraArgs]);
const crash = await waitForCrash();
checkCrash('renderer', crash);
expect(crash.mainProcessSpecific).to.be.undefined();
expect(crash.rendererSpecific).to.equal('rs');
expect(crash.addedThenRemoved).to.be.undefined();
});
it('contains v8 crash keys when a v8 crash occurs', async () => {
const { remotely } = await startRemoteControlApp(crashpadExtraArgs);
const { port, waitForCrash } = await startServer();
await remotely((port: number) => {
require('electron').crashReporter.start({
submitURL: `http://127.0.0.1:${port}`,
compress: false,
ignoreSystemCrashHandler: true
});
}, [port]);
remotely(() => {
const { BrowserWindow } = require('electron');
const bw = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
bw.loadURL('about:blank');
bw.webContents.executeJavaScript('process._linkedBinding(\'electron_common_v8_util\').triggerFatalErrorForTesting()');
});
const crash = await waitForCrash();
expect(crash.prod).to.equal('Electron');
expect(crash._productName).to.equal('electron-test-remote-control');
expect(crash.process_type).to.equal('renderer');
expect(crash['electron.v8-fatal.location']).to.equal('v8::Context::New()');
expect(crash['electron.v8-fatal.message']).to.equal('Circular extension dependency');
});
});
});
});
});
ifdescribe(!isLinuxOnArm)('extra parameter limits', () => {
function stitchLongCrashParam (crash: any, paramKey: string) {
if (crash[paramKey]) return crash[paramKey];
let chunk = 1;
let stitched = '';
while (crash[`${paramKey}__${chunk}`]) {
stitched += crash[`${paramKey}__${chunk}`];
chunk++;
}
return stitched;
}
ifdescribe(!isLinuxOnArm)('extra parameter limits', () => {
function stitchLongCrashParam (crash: any, paramKey: string) {
if (crash[paramKey]) return crash[paramKey];
let chunk = 1;
let stitched = '';
while (crash[`${paramKey}__${chunk}`]) {
stitched += crash[`${paramKey}__${chunk}`];
chunk++;
}
return stitched;
}
it('should truncate extra values longer than 5 * 4096 characters', async () => {
const { port, waitForCrash } = await startServer();
const { remotely } = await startRemoteControlApp();
remotely((port: number) => {
require('electron').crashReporter.start({
submitURL: `http://127.0.0.1:${port}`,
compress: false,
ignoreSystemCrashHandler: true,
extra: { longParam: 'a'.repeat(100000) }
it('should truncate extra values longer than 5 * 4096 characters', async () => {
const { port, waitForCrash } = await startServer();
const { remotely } = await startRemoteControlApp(crashpadExtraArgs);
remotely((port: number) => {
require('electron').crashReporter.start({
submitURL: `http://127.0.0.1:${port}`,
compress: false,
ignoreSystemCrashHandler: true,
extra: { longParam: 'a'.repeat(100000) }
});
setTimeout(() => process.crash());
}, port);
const crash = await waitForCrash();
expect(stitchLongCrashParam(crash, 'longParam')).to.have.lengthOf(160 * 127 + (withLinuxCrashpad ? 159 : 0), 'crash should have truncated longParam');
});
setTimeout(() => process.crash());
}, port);
const crash = await waitForCrash();
expect(stitchLongCrashParam(crash, 'longParam')).to.have.lengthOf(160 * 127, 'crash should have truncated longParam');
});
it('should omit extra keys with names longer than the maximum', async () => {
const kKeyLengthMax = 39;
const { port, waitForCrash } = await startServer();
const { remotely } = await startRemoteControlApp();
remotely((port: number, kKeyLengthMax: number) => {
require('electron').crashReporter.start({
submitURL: `http://127.0.0.1:${port}`,
compress: false,
ignoreSystemCrashHandler: true,
extra: {
['a'.repeat(kKeyLengthMax + 10)]: 'value',
['b'.repeat(kKeyLengthMax)]: 'value',
'not-long': 'not-long-value'
it('should omit extra keys with names longer than the maximum', async () => {
const kKeyLengthMax = 39;
const { port, waitForCrash } = await startServer();
const { remotely } = await startRemoteControlApp(crashpadExtraArgs);
remotely((port: number, kKeyLengthMax: number) => {
require('electron').crashReporter.start({
submitURL: `http://127.0.0.1:${port}`,
compress: false,
ignoreSystemCrashHandler: true,
extra: {
['a'.repeat(kKeyLengthMax + 10)]: 'value',
['b'.repeat(kKeyLengthMax)]: 'value',
'not-long': 'not-long-value'
}
});
require('electron').crashReporter.addExtraParameter('c'.repeat(kKeyLengthMax + 10), 'value');
setTimeout(() => process.crash());
}, port, kKeyLengthMax);
const crash = await waitForCrash();
expect(crash).not.to.have.property('a'.repeat(kKeyLengthMax + 10));
expect(crash).not.to.have.property('a'.repeat(kKeyLengthMax));
expect(crash).to.have.property('b'.repeat(kKeyLengthMax), 'value');
expect(crash).to.have.property('not-long', 'not-long-value');
expect(crash).not.to.have.property('c'.repeat(kKeyLengthMax + 10));
expect(crash).not.to.have.property('c'.repeat(kKeyLengthMax));
});
});
describe('globalExtra', () => {
ifit(!isLinuxOnArm)('should be sent with main process dumps', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('main', port, ['--add-global-param=globalParam:globalValue', ...crashpadExtraArgs]);
const crash = await waitForCrash();
expect(crash.globalParam).to.equal('globalValue');
});
it('should be sent with renderer process dumps', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('renderer', port, ['--add-global-param=globalParam:globalValue', ...crashpadExtraArgs]);
const crash = await waitForCrash();
expect(crash.globalParam).to.equal('globalValue');
});
it('should be sent with sandboxed renderer process dumps', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('sandboxed-renderer', port, ['--add-global-param=globalParam:globalValue', ...crashpadExtraArgs]);
const crash = await waitForCrash();
expect(crash.globalParam).to.equal('globalValue');
});
ifit(!isLinuxOnArm)('should not be overridden by extra in main process', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('main', port, ['--add-global-param=mainProcessSpecific:global', ...crashpadExtraArgs]);
const crash = await waitForCrash();
expect(crash.mainProcessSpecific).to.equal('global');
});
ifit(!isLinuxOnArm)('should not be overridden by extra in renderer process', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('main', port, ['--add-global-param=rendererSpecific:global', ...crashpadExtraArgs]);
const crash = await waitForCrash();
expect(crash.rendererSpecific).to.equal('global');
});
});
// TODO(nornagon): also test crashing main / sandboxed renderers.
ifit(!isWindowsOnArm)('should not send a minidump when uploadToServer is false', async () => {
const { port, waitForCrash, getCrashes } = await startServer();
waitForCrash().then(() => expect.fail('expected not to receive a dump'));
await runCrashApp('renderer', port, ['--no-upload', ...crashpadExtraArgs]);
// wait a sec in case the crash reporter is about to upload a crash
await delay(1000);
expect(getCrashes()).to.have.length(0);
});
describe('getUploadedReports', () => {
it('returns an array of reports', async () => {
const { remotely } = await startRemoteControlApp(crashpadExtraArgs);
await remotely(() => {
require('electron').crashReporter.start({ submitURL: 'http://127.0.0.1' });
});
const reports = await remotely(() => require('electron').crashReporter.getUploadedReports());
expect(reports).to.be.an('array');
});
});
// TODO(nornagon): re-enable on woa
ifdescribe(!isWindowsOnArm)('getLastCrashReport', () => {
it('returns the last uploaded report', async () => {
const { remotely } = await startRemoteControlApp(crashpadExtraArgs);
const { port, waitForCrash } = await startServer();
// 0. clear the crash reports directory.
const dir = await remotely(() => require('electron').app.getPath('crashDumps'));
try {
fs.rmdirSync(dir, { recursive: true });
fs.mkdirSync(dir);
} catch (e) { /* ignore */ }
// 1. start the crash reporter.
await remotely((port: number) => {
require('electron').crashReporter.start({
submitURL: `http://127.0.0.1:${port}`,
compress: false,
ignoreSystemCrashHandler: true
});
}, [port]);
// 2. generate a crash in the renderer.
remotely(() => {
const { BrowserWindow } = require('electron');
const bw = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
bw.loadURL('about:blank');
bw.webContents.executeJavaScript('process.crash()');
});
await waitForCrash();
// 3. get the crash from getLastCrashReport.
const firstReport = await remotely(() => require('electron').crashReporter.getLastCrashReport());
expect(firstReport).to.not.be.null();
expect(firstReport.date).to.be.an.instanceOf(Date);
expect((+new Date()) - (+firstReport.date)).to.be.lessThan(30000);
});
});
describe('getParameters', () => {
it('returns all of the current parameters', async () => {
const { remotely } = await startRemoteControlApp(crashpadExtraArgs);
await remotely(() => {
require('electron').crashReporter.start({
submitURL: 'http://127.0.0.1',
extra: { extra1: 'hi' }
});
});
const parameters = await remotely(() => require('electron').crashReporter.getParameters());
expect(parameters).to.have.property('extra1', 'hi');
});
it('reflects added and removed parameters', async () => {
const { remotely } = await startRemoteControlApp(crashpadExtraArgs);
await remotely(() => {
require('electron').crashReporter.start({ submitURL: 'http://127.0.0.1' });
require('electron').crashReporter.addExtraParameter('hello', 'world');
});
{
const parameters = await remotely(() => require('electron').crashReporter.getParameters());
expect(parameters).to.have.property('hello', 'world');
}
await remotely(() => { require('electron').crashReporter.removeExtraParameter('hello'); });
{
const parameters = await remotely(() => require('electron').crashReporter.getParameters());
expect(parameters).not.to.have.property('hello');
}
});
require('electron').crashReporter.addExtraParameter('c'.repeat(kKeyLengthMax + 10), 'value');
setTimeout(() => process.crash());
}, port, kKeyLengthMax);
const crash = await waitForCrash();
expect(crash).not.to.have.property('a'.repeat(kKeyLengthMax + 10));
expect(crash).not.to.have.property('a'.repeat(kKeyLengthMax));
expect(crash).to.have.property('b'.repeat(kKeyLengthMax), 'value');
expect(crash).to.have.property('not-long', 'not-long-value');
expect(crash).not.to.have.property('c'.repeat(kKeyLengthMax + 10));
expect(crash).not.to.have.property('c'.repeat(kKeyLengthMax));
});
});
describe('globalExtra', () => {
ifit(!isLinuxOnArm)('should be sent with main process dumps', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('main', port, ['--add-global-param=globalParam:globalValue']);
const crash = await waitForCrash();
expect(crash.globalParam).to.equal('globalValue');
});
it('can be called in the renderer', async () => {
const { remotely } = await startRemoteControlApp(crashpadExtraArgs);
const rendererParameters = await remotely(async () => {
const { crashReporter, BrowserWindow } = require('electron');
crashReporter.start({ submitURL: 'http://' });
const bw = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
bw.loadURL('about:blank');
await bw.webContents.executeJavaScript('require(\'electron\').crashReporter.addExtraParameter(\'hello\', \'world\')');
return bw.webContents.executeJavaScript('require(\'electron\').crashReporter.getParameters()');
});
if (process.platform === 'linux') {
// On Linux, 'getParameters' will also include the global parameters,
// because breakpad doesn't support global parameters.
expect(rendererParameters).to.have.property('hello', 'world');
} else {
expect(rendererParameters).to.deep.equal({ hello: 'world' });
}
});
it('should be sent with renderer process dumps', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('renderer', port, ['--add-global-param=globalParam:globalValue']);
const crash = await waitForCrash();
expect(crash.globalParam).to.equal('globalValue');
});
it('can be called in a node child process', async () => {
function slurp (stream: NodeJS.ReadableStream): Promise<string> {
return new Promise((resolve, reject) => {
const chunks: Buffer[] = [];
stream.on('data', chunk => { chunks.push(chunk); });
stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
stream.on('error', e => reject(e));
});
}
// TODO(nornagon): how to enable crashpad in a node child process...?
const child = childProcess.fork(path.join(__dirname, 'fixtures', 'module', 'print-crash-parameters.js'), [], { silent: true });
const output = await slurp(child.stdout!);
expect(JSON.parse(output)).to.deep.equal({ hello: 'world' });
});
});
it('should be sent with sandboxed renderer process dumps', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('sandboxed-renderer', port, ['--add-global-param=globalParam:globalValue']);
const crash = await waitForCrash();
expect(crash.globalParam).to.equal('globalValue');
});
describe('crash dumps directory', () => {
it('is set by default', () => {
expect(app.getPath('crashDumps')).to.be.a('string');
});
ifit(!isLinuxOnArm)('should not be overridden by extra in main process', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('main', port, ['--add-global-param=mainProcessSpecific:global']);
const crash = await waitForCrash();
expect(crash.mainProcessSpecific).to.equal('global');
});
it('is inside the user data dir', () => {
expect(app.getPath('crashDumps')).to.include(app.getPath('userData'));
});
ifit(!isLinuxOnArm)('should not be overridden by extra in renderer process', async () => {
const { port, waitForCrash } = await startServer();
runCrashApp('main', port, ['--add-global-param=rendererSpecific:global']);
const crash = await waitForCrash();
expect(crash.rendererSpecific).to.equal('global');
});
});
function crash (processType: string, remotely: Function) {
if (processType === 'main') {
return remotely(() => {
setTimeout(() => { process.crash(); });
});
} else if (processType === 'renderer') {
return remotely(() => {
const { BrowserWindow } = require('electron');
const bw = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
bw.loadURL('about:blank');
bw.webContents.executeJavaScript('process.crash()');
});
} else if (processType === 'sandboxed-renderer') {
const preloadPath = path.join(__dirname, 'fixtures', 'apps', 'crash', 'sandbox-preload.js');
return remotely((preload: string) => {
const { BrowserWindow } = require('electron');
const bw = new BrowserWindow({ show: false, webPreferences: { sandbox: true, preload, contextIsolation: false } });
bw.loadURL('about:blank');
}, preloadPath);
} else if (processType === 'node') {
const crashScriptPath = path.join(__dirname, 'fixtures', 'apps', 'crash', 'node-crash.js');
return remotely((crashScriptPath: string) => {
const { app } = require('electron');
const childProcess = require('child_process');
const version = app.getVersion();
const url = 'http://127.0.0.1';
childProcess.fork(crashScriptPath, [url, version], { silent: true });
}, crashScriptPath);
}
}
// TODO(nornagon): also test crashing main / sandboxed renderers.
ifit(!isWindowsOnArm)('should not send a minidump when uploadToServer is false', async () => {
const { port, waitForCrash, getCrashes } = await startServer();
waitForCrash().then(() => expect.fail('expected not to receive a dump'));
await runCrashApp('renderer', port, ['--no-upload']);
// wait a sec in case the crash reporter is about to upload a crash
await delay(1000);
expect(getCrashes()).to.have.length(0);
});
const processList = process.platform === 'linux' ? ['main', 'renderer', 'sandboxed-renderer']
: ['main', 'renderer', 'sandboxed-renderer', 'node'];
for (const crashingProcess of processList) {
describe(`when ${crashingProcess} crashes`, () => {
it('stores crashes in the crash dump directory when uploadToServer: false', async () => {
const { remotely } = await startRemoteControlApp(crashpadExtraArgs);
const crashesDir = await remotely(() => {
const { crashReporter, app } = require('electron');
crashReporter.start({ submitURL: 'http://127.0.0.1', uploadToServer: false, ignoreSystemCrashHandler: true });
return app.getPath('crashDumps');
});
let reportsDir = crashesDir;
if (process.platform === 'darwin' || (process.platform === 'linux' && withLinuxCrashpad)) {
reportsDir = path.join(crashesDir, 'completed');
} else if (process.platform === 'win32') {
reportsDir = path.join(crashesDir, 'reports');
}
const newFileAppeared = waitForNewFileInDir(reportsDir);
crash(crashingProcess, remotely);
const newFiles = await newFileAppeared;
expect(newFiles.length).to.be.greaterThan(0);
if (process.platform === 'linux' && !withLinuxCrashpad) {
if (crashingProcess === 'main') {
expect(newFiles[0]).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{8}-[0-9a-f]{8}\.dmp$/);
} else {
const process = crashingProcess === 'sandboxed-renderer' ? 'renderer' : crashingProcess;
const regex = RegExp(`chromium-${process}-minidump-[0-9a-f]{16}.dmp`);
expect(newFiles[0]).to.match(regex);
}
} else {
expect(newFiles[0]).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.dmp$/);
}
});
it('respects an overridden crash dump directory', async () => {
const { remotely } = await startRemoteControlApp(crashpadExtraArgs);
const crashesDir = path.join(app.getPath('temp'), uuid.v4());
const remoteCrashesDir = await remotely((crashesDir: string) => {
const { crashReporter, app } = require('electron');
app.setPath('crashDumps', crashesDir);
crashReporter.start({ submitURL: 'http://127.0.0.1', uploadToServer: false, ignoreSystemCrashHandler: true });
return app.getPath('crashDumps');
}, crashesDir);
expect(remoteCrashesDir).to.equal(crashesDir);
let reportsDir = crashesDir;
if (process.platform === 'darwin' || (process.platform === 'linux' && withLinuxCrashpad)) {
reportsDir = path.join(crashesDir, 'completed');
} else if (process.platform === 'win32') {
reportsDir = path.join(crashesDir, 'reports');
}
const newFileAppeared = waitForNewFileInDir(reportsDir);
crash(crashingProcess, remotely);
const newFiles = await newFileAppeared;
expect(newFiles.length).to.be.greaterThan(0);
if (process.platform === 'linux' && !withLinuxCrashpad) {
if (crashingProcess === 'main') {
expect(newFiles[0]).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{8}-[0-9a-f]{8}\.dmp$/);
} else {
const process = crashingProcess !== 'sandboxed-renderer' ? crashingProcess : 'renderer';
const regex = RegExp(`chromium-${process}-minidump-[0-9a-f]{16}.dmp`);
expect(newFiles[0]).to.match(regex);
}
} else {
expect(newFiles[0]).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.dmp$/);
}
});
});
}
});
});
}
describe('start() option validation', () => {
it('requires that the submitURL option be specified', () => {
@@ -380,54 +611,6 @@ ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_
});
});
describe('getUploadedReports', () => {
it('returns an array of reports', async () => {
const { remotely } = await startRemoteControlApp();
await remotely(() => {
require('electron').crashReporter.start({ submitURL: 'http://127.0.0.1' });
});
const reports = await remotely(() => require('electron').crashReporter.getUploadedReports());
expect(reports).to.be.an('array');
});
});
// TODO(nornagon): re-enable on woa
ifdescribe(!isWindowsOnArm)('getLastCrashReport', () => {
it('returns the last uploaded report', async () => {
const { remotely } = await startRemoteControlApp();
const { port, waitForCrash } = await startServer();
// 0. clear the crash reports directory.
const dir = await remotely(() => require('electron').app.getPath('crashDumps'));
try {
fs.rmdirSync(dir, { recursive: true });
fs.mkdirSync(dir);
} catch (e) { /* ignore */ }
// 1. start the crash reporter.
await remotely((port: number) => {
require('electron').crashReporter.start({
submitURL: `http://127.0.0.1:${port}`,
compress: false,
ignoreSystemCrashHandler: true
});
}, [port]);
// 2. generate a crash in the renderer.
remotely(() => {
const { BrowserWindow } = require('electron');
const bw = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
bw.loadURL('about:blank');
bw.webContents.executeJavaScript('process.crash()');
});
await waitForCrash();
// 3. get the crash from getLastCrashReport.
const firstReport = await remotely(() => require('electron').crashReporter.getLastCrashReport());
expect(firstReport).to.not.be.null();
expect(firstReport.date).to.be.an.instanceOf(Date);
expect((+new Date()) - (+firstReport.date)).to.be.lessThan(30000);
});
});
describe('getUploadToServer()', () => {
it('returns true when uploadToServer is set to true (by default)', async () => {
const { remotely } = await startRemoteControlApp();
@@ -454,183 +637,6 @@ ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_
});
});
describe('getParameters', () => {
it('returns all of the current parameters', async () => {
const { remotely } = await startRemoteControlApp();
await remotely(() => {
require('electron').crashReporter.start({
submitURL: 'http://127.0.0.1',
extra: { extra1: 'hi' }
});
});
const parameters = await remotely(() => require('electron').crashReporter.getParameters());
expect(parameters).to.have.property('extra1', 'hi');
});
it('reflects added and removed parameters', async () => {
const { remotely } = await startRemoteControlApp();
await remotely(() => {
require('electron').crashReporter.start({ submitURL: 'http://127.0.0.1' });
require('electron').crashReporter.addExtraParameter('hello', 'world');
});
{
const parameters = await remotely(() => require('electron').crashReporter.getParameters());
expect(parameters).to.have.property('hello', 'world');
}
await remotely(() => { require('electron').crashReporter.removeExtraParameter('hello'); });
{
const parameters = await remotely(() => require('electron').crashReporter.getParameters());
expect(parameters).not.to.have.property('hello');
}
});
it('can be called in the renderer', async () => {
const { remotely } = await startRemoteControlApp();
const rendererParameters = await remotely(async () => {
const { crashReporter, BrowserWindow } = require('electron');
crashReporter.start({ submitURL: 'http://' });
const bw = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
bw.loadURL('about:blank');
await bw.webContents.executeJavaScript('require(\'electron\').crashReporter.addExtraParameter(\'hello\', \'world\')');
return bw.webContents.executeJavaScript('require(\'electron\').crashReporter.getParameters()');
});
if (process.platform === 'linux') {
// On Linux, 'getParameters' will also include the global parameters,
// because breakpad doesn't support global parameters.
expect(rendererParameters).to.have.property('hello', 'world');
} else {
expect(rendererParameters).to.deep.equal({ hello: 'world' });
}
});
it('can be called in a node child process', async () => {
function slurp (stream: NodeJS.ReadableStream): Promise<string> {
return new Promise((resolve, reject) => {
const chunks: Buffer[] = [];
stream.on('data', chunk => { chunks.push(chunk); });
stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
stream.on('error', e => reject(e));
});
}
const child = childProcess.fork(path.join(__dirname, 'fixtures', 'module', 'print-crash-parameters.js'), [], { silent: true });
const output = await slurp(child.stdout!);
expect(JSON.parse(output)).to.deep.equal({ hello: 'world' });
});
});
describe('crash dumps directory', () => {
it('is set by default', () => {
expect(app.getPath('crashDumps')).to.be.a('string');
});
it('is inside the user data dir', () => {
expect(app.getPath('crashDumps')).to.include(app.getPath('userData'));
});
function crash (processType: string, remotely: Function) {
if (processType === 'main') {
return remotely(() => {
setTimeout(() => { process.crash(); });
});
} else if (processType === 'renderer') {
return remotely(() => {
const { BrowserWindow } = require('electron');
const bw = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
bw.loadURL('about:blank');
bw.webContents.executeJavaScript('process.crash()');
});
} else if (processType === 'sandboxed-renderer') {
const preloadPath = path.join(__dirname, 'fixtures', 'apps', 'crash', 'sandbox-preload.js');
return remotely((preload: string) => {
const { BrowserWindow } = require('electron');
const bw = new BrowserWindow({ show: false, webPreferences: { sandbox: true, preload, contextIsolation: false } });
bw.loadURL('about:blank');
}, preloadPath);
} else if (processType === 'node') {
const crashScriptPath = path.join(__dirname, 'fixtures', 'apps', 'crash', 'node-crash.js');
return remotely((crashScriptPath: string) => {
const { app } = require('electron');
const childProcess = require('child_process');
const version = app.getVersion();
const url = 'http://127.0.0.1';
childProcess.fork(crashScriptPath, [url, version], { silent: true });
}, crashScriptPath);
}
}
const processList = process.platform === 'linux' ? ['main', 'renderer', 'sandboxed-renderer']
: ['main', 'renderer', 'sandboxed-renderer', 'node'];
for (const crashingProcess of processList) {
describe(`when ${crashingProcess} crashes`, () => {
it('stores crashes in the crash dump directory when uploadToServer: false', async () => {
const { remotely } = await startRemoteControlApp();
const crashesDir = await remotely(() => {
const { crashReporter, app } = require('electron');
crashReporter.start({ submitURL: 'http://127.0.0.1', uploadToServer: false, ignoreSystemCrashHandler: true });
return app.getPath('crashDumps');
});
let reportsDir = crashesDir;
if (process.platform === 'darwin') {
reportsDir = path.join(crashesDir, 'completed');
} else if (process.platform === 'win32') {
reportsDir = path.join(crashesDir, 'reports');
}
const newFileAppeared = waitForNewFileInDir(reportsDir);
crash(crashingProcess, remotely);
const newFiles = await newFileAppeared;
expect(newFiles.length).to.be.greaterThan(0);
if (process.platform === 'linux') {
if (crashingProcess === 'main') {
expect(newFiles[0]).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{8}-[0-9a-f]{8}\.dmp$/);
} else {
const process = crashingProcess === 'sandboxed-renderer' ? 'renderer' : crashingProcess;
const regex = RegExp(`chromium-${process}-minidump-[0-9a-f]{16}.dmp`);
expect(newFiles[0]).to.match(regex);
}
} else {
expect(newFiles[0]).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.dmp$/);
}
});
it('respects an overridden crash dump directory', async () => {
const { remotely } = await startRemoteControlApp();
const crashesDir = path.join(app.getPath('temp'), uuid.v4());
const remoteCrashesDir = await remotely((crashesDir: string) => {
const { crashReporter, app } = require('electron');
app.setPath('crashDumps', crashesDir);
crashReporter.start({ submitURL: 'http://127.0.0.1', uploadToServer: false, ignoreSystemCrashHandler: true });
return app.getPath('crashDumps');
}, crashesDir);
expect(remoteCrashesDir).to.equal(crashesDir);
let reportsDir = crashesDir;
if (process.platform === 'darwin') {
reportsDir = path.join(crashesDir, 'completed');
} else if (process.platform === 'win32') {
reportsDir = path.join(crashesDir, 'reports');
}
const newFileAppeared = waitForNewFileInDir(reportsDir);
crash(crashingProcess, remotely);
const newFiles = await newFileAppeared;
expect(newFiles.length).to.be.greaterThan(0);
if (process.platform === 'linux') {
if (crashingProcess === 'main') {
expect(newFiles[0]).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{8}-[0-9a-f]{8}\.dmp$/);
} else {
const process = crashingProcess !== 'sandboxed-renderer' ? crashingProcess : 'renderer';
const regex = RegExp(`chromium-${process}-minidump-[0-9a-f]{16}.dmp`);
expect(newFiles[0]).to.match(regex);
}
} else {
expect(newFiles[0]).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.dmp$/);
}
});
});
}
});
describe('when not started', () => {
it('does not prevent process from crashing', async () => {
const appPath = path.join(__dirname, '..', 'spec', 'fixtures', 'api', 'cookie-app');

View File

@@ -1,7 +1,7 @@
import { expect } from 'chai';
import { dialog, BrowserWindow } from 'electron/main';
import { closeAllWindows } from './window-helpers';
import { ifit } from './spec-helpers';
import { ifit, delay } from './spec-helpers';
describe('dialog module', () => {
describe('showOpenDialog', () => {
@@ -121,6 +121,62 @@ describe('dialog module', () => {
});
});
describe('showMessageBox with signal', () => {
afterEach(closeAllWindows);
it('closes message box immediately', async () => {
const controller = new AbortController();
const signal = controller.signal;
const w = new BrowserWindow();
const p = dialog.showMessageBox(w, { signal, message: 'i am message' });
controller.abort();
const result = await p;
expect(result.response).to.equal(0);
});
it('closes message box after a while', async () => {
const controller = new AbortController();
const signal = controller.signal;
const w = new BrowserWindow();
const p = dialog.showMessageBox(w, { signal, message: 'i am message' });
await delay(500);
controller.abort();
const result = await p;
expect(result.response).to.equal(0);
});
it('cancels message box', async () => {
const controller = new AbortController();
const signal = controller.signal;
const w = new BrowserWindow();
const p = dialog.showMessageBox(w, {
signal,
message: 'i am message',
buttons: ['OK', 'Cancel'],
cancelId: 1
});
controller.abort();
const result = await p;
expect(result.response).to.equal(1);
});
it('cancels message box after a while', async () => {
const controller = new AbortController();
const signal = controller.signal;
const w = new BrowserWindow();
const p = dialog.showMessageBox(w, {
signal,
message: 'i am message',
buttons: ['OK', 'Cancel'],
cancelId: 1
});
await delay(500);
controller.abort();
const result = await p;
expect(result.response).to.equal(1);
});
});
describe('showErrorBox', () => {
it('throws errors when the options are invalid', () => {
expect(() => {

View File

@@ -120,6 +120,24 @@ describe('protocol module', () => {
const r = await ajax(protocolName + '://fake-host');
expect(r.data).to.equal(text);
});
it('can redirect to the same scheme', async () => {
registerStringProtocol(protocolName, (request, callback) => {
if (request.url === `${protocolName}://fake-host/redirect`) {
callback({
statusCode: 302,
headers: {
Location: `${protocolName}://fake-host`
}
});
} else {
expect(request.url).to.equal(`${protocolName}://fake-host`);
callback('redirected');
}
});
const r = await ajax(`${protocolName}://fake-host/redirect`);
expect(r.data).to.equal('redirected');
});
});
describe('protocol.unregisterProtocol', () => {

View File

@@ -503,16 +503,6 @@ describe('webContents module', () => {
});
});
describe('getWebPreferences() API', () => {
afterEach(closeAllWindows);
it('should not crash when called for devTools webContents', async () => {
const w = new BrowserWindow({ show: false });
w.webContents.openDevTools();
await emittedOnce(w.webContents, 'devtools-opened');
expect(w.webContents.devToolsWebContents!.getWebPreferences()).to.be.null();
});
});
describe('openDevTools() API', () => {
afterEach(closeAllWindows);
it('can show window with activation', async () => {

View File

@@ -181,6 +181,42 @@ describe('webRequest module', () => {
expect(data).to.equal('/header/received');
});
it('can change the request headers on a custom protocol redirect', async () => {
protocol.registerStringProtocol('custom-scheme', (req, callback) => {
if (req.url === 'custom-scheme://fake-host/redirect') {
callback({
statusCode: 302,
headers: {
Location: 'custom-scheme://fake-host'
}
});
} else {
let content = '';
if (req.headers.Accept === '*/*;test/header') {
content = 'header-received';
}
callback(content);
}
});
// Note that we need to do navigation every time after a protocol is
// registered or unregistered, otherwise the new protocol won't be
// recognized by current page when NetworkService is used.
await contents.loadFile(path.join(__dirname, 'fixtures', 'pages', 'jquery.html'));
try {
ses.webRequest.onBeforeSendHeaders((details, callback) => {
const requestHeaders = details.requestHeaders;
requestHeaders.Accept = '*/*;test/header';
callback({ requestHeaders: requestHeaders });
});
const { data } = await ajax('custom-scheme://fake-host/redirect');
expect(data).to.equal('header-received');
} finally {
protocol.unregisterProtocol('custom-scheme');
}
});
it('can change request origin', async () => {
ses.webRequest.onBeforeSendHeaders((details, callback) => {
const requestHeaders = details.requestHeaders;

View File

@@ -64,6 +64,7 @@ until the maintainers feel the maintenance burden is too high to continue doing
### Currently supported versions
* 4.x.y
* 3.x.y
* 2.x.y
* 1.x.y

View File

@@ -7,8 +7,17 @@ import * as fs from 'fs/promises';
import * as path from 'path';
import * as uuid from 'uuid';
function isTestingBindingAvailable () {
try {
process._linkedBinding('electron_common_testing');
return true;
} catch {
return false;
}
}
// This test depends on functions that are only available when DCHECK_IS_ON.
ifdescribe(process._linkedBinding('electron_common_testing'))('logging', () => {
ifdescribe(isTestingBindingAvailable())('logging', () => {
it('does not log by default', async () => {
// ELECTRON_ENABLE_LOGGING is turned on in the appveyor config.
const { ELECTRON_ENABLE_LOGGING: _, ...envWithoutEnableLogging } = process.env;

View File

@@ -1,6 +1,8 @@
import { expect } from 'chai';
import { GitProcess, IGitExecutionOptions, IGitResult } from 'dugite';
import { nextVersion, shouldUpdateSupported, updateSupported } from '../script/release/version-bumper';
import * as utils from '../script/release/version-utils';
import * as sinon from 'sinon';
import { ifdescribe } from './spec-helpers';
const { promises: fs } = require('fs');
const path = require('path');
@@ -9,6 +11,53 @@ const fixtureDir = path.resolve(__dirname, 'fixtures', 'version-bumper', 'fixtur
const readFile = fs.readFile;
const writeFile = fs.writeFile;
class GitFake {
branches: {
[key: string]: string[],
};
constructor () {
this.branches = {};
}
setBranch (channel: string): void {
this.branches[channel] = [];
}
setVersion (channel: string, latestTag: string): void {
const tags = [latestTag];
if (channel === 'alpha') {
const versionStrs = latestTag.split(`${channel}.`);
const latest = parseInt(versionStrs[1]);
for (let i = latest; i >= 1; i--) {
tags.push(`${versionStrs[0]}${channel}.${latest - i}`);
}
}
this.branches[channel] = tags;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
exec (args: string[], path: string, options?: IGitExecutionOptions | undefined): Promise<IGitResult> {
let stdout = '';
const stderr = '';
const exitCode = 0;
// handle for promoting from current master HEAD
let branch = 'stable';
const v = (args[2] === 'HEAD') ? 'stable' : args[3];
if (v.includes('nightly')) branch = 'nightly';
if (v.includes('alpha')) branch = 'alpha';
if (v.includes('beta')) branch = 'beta';
if (!this.branches[branch]) this.setBranch(branch);
stdout = this.branches[branch].join('\n');
return Promise.resolve({ exitCode, stdout, stderr });
}
}
describe('version-bumper', () => {
describe('makeVersion', () => {
it('makes a version with a period delimeter', () => {
@@ -138,101 +187,160 @@ describe('version-bumper', () => {
// On macOS Circle CI we don't have a real git environment due to running
// gclient sync on a linux machine. These tests therefore don't run as expected.
ifdescribe(!(process.platform === 'linux' && process.arch.indexOf('arm') === 0) && process.platform !== 'darwin')('nextVersion', () => {
const nightlyPattern = /[0-9.]*(-nightly.(\d{4})(\d{2})(\d{2}))$/g;
describe('bump versions', () => {
const nightlyPattern = /[0-9.]*(-nightly.(\d{4})(\d{2})(\d{2}))$/g;
const betaPattern = /[0-9.]*(-beta[0-9.]*)/g;
it('bumps to nightly from stable', async () => {
const version = 'v2.0.0';
const next = await nextVersion('nightly', version);
const matches = next.match(nightlyPattern);
expect(matches).to.have.lengthOf(1);
});
it('bumps to nightly from beta', async () => {
const version = 'v2.0.0-beta.1';
const next = await nextVersion('nightly', version);
const matches = next.match(nightlyPattern);
expect(matches).to.have.lengthOf(1);
});
it('bumps to nightly from nightly', async () => {
const version = 'v2.0.0-nightly.19950901';
const next = await nextVersion('nightly', version);
const matches = next.match(nightlyPattern);
expect(matches).to.have.lengthOf(1);
});
it('bumps to a nightly version above our switch from N-0-x to N-x-y branch names', async () => {
const version = 'v2.0.0-nightly.19950901';
const next = await nextVersion('nightly', version);
// If it starts with v8 then we didn't bump above the 8-x-y branch
expect(next.startsWith('v8')).to.equal(false);
});
it('throws error when bumping to beta from stable', () => {
const version = 'v2.0.0';
return expect(
nextVersion('beta', version)
).to.be.rejectedWith('Cannot bump to beta from stable.');
});
// TODO ELECTRON 15: Re-enable after Electron 15 alpha has released
it.skip('bumps to beta from nightly', async () => {
const version = 'v2.0.0-nightly.19950901';
const next = await nextVersion('beta', version);
const matches = next.match(betaPattern);
expect(matches).to.have.lengthOf(1);
});
it('bumps to beta from beta', async () => {
const version = 'v2.0.0-beta.8';
const next = await nextVersion('beta', version);
expect(next).to.equal('2.0.0-beta.9');
});
it('bumps to beta from beta if the previous beta is at least beta.10', async () => {
const version = 'v6.0.0-beta.15';
const next = await nextVersion('beta', version);
expect(next).to.equal('6.0.0-beta.16');
});
it('bumps to stable from beta', async () => {
const version = 'v2.0.0-beta.1';
const next = await nextVersion('stable', version);
expect(next).to.equal('2.0.0');
});
it('bumps to stable from stable', async () => {
const version = 'v2.0.0';
const next = await nextVersion('stable', version);
expect(next).to.equal('2.0.1');
});
it('bumps to minor from stable', async () => {
const version = 'v2.0.0';
const next = await nextVersion('minor', version);
expect(next).to.equal('2.1.0');
});
it('bumps to stable from nightly', async () => {
const version = 'v2.0.0-nightly.19950901';
const next = await nextVersion('stable', version);
expect(next).to.equal('2.0.0');
});
it('throws on an invalid version', () => {
const version = 'vI.AM.INVALID';
return expect(
nextVersion('beta', version)
).to.be.rejectedWith(`Invalid current version: ${version}`);
});
it('throws on an invalid bump type', () => {
const version = 'v2.0.0';
return expect(
nextVersion('WRONG', version)
).to.be.rejectedWith('Invalid bump type.');
});
});
});
// If we don't plan on continuing to support an alpha channel past Electron 15,
// these tests will be removed. Otherwise, integrate into the bump versions tests
describe('bump versions - alpha channel', () => {
const alphaPattern = /[0-9.]*(-alpha[0-9.]*)/g;
const betaPattern = /[0-9.]*(-beta[0-9.]*)/g;
it('bumps to nightly from stable', async () => {
const version = 'v2.0.0';
const next = await nextVersion('nightly', version);
const matches = next.match(nightlyPattern);
expect(matches).to.have.lengthOf(1);
const sandbox = sinon.createSandbox();
const gitFake = new GitFake();
beforeEach(() => {
const wrapper = (args: string[], path: string, options?: IGitExecutionOptions | undefined) => gitFake.exec(args, path, options);
sandbox.replace(GitProcess, 'exec', wrapper);
});
it('bumps to nightly from beta', async () => {
const version = 'v2.0.0-beta.1';
const next = await nextVersion('nightly', version);
const matches = next.match(nightlyPattern);
expect(matches).to.have.lengthOf(1);
afterEach(() => {
gitFake.branches = {};
sandbox.restore();
});
it('bumps to nightly from nightly', async () => {
it('bumps to alpha from nightly', async () => {
const version = 'v2.0.0-nightly.19950901';
const next = await nextVersion('nightly', version);
const matches = next.match(nightlyPattern);
gitFake.setVersion('nightly', version);
const next = await nextVersion('alpha', version);
const matches = next.match(alphaPattern);
expect(matches).to.have.lengthOf(1);
});
it('bumps to a nightly version above our switch from N-0-x to N-x-y branch names', async () => {
const version = 'v2.0.0-nightly.19950901';
const next = await nextVersion('nightly', version);
// If it starts with v8 then we didn't bump above the 8-x-y branch
expect(next.startsWith('v8')).to.equal(false);
});
it('throws error when bumping to beta from stable', () => {
it('throws error when bumping to alpha from stable', () => {
const version = 'v2.0.0';
return expect(
nextVersion('beta', version)
).to.be.rejectedWith('Cannot bump to beta from stable.');
nextVersion('alpha', version)
).to.be.rejectedWith('Cannot bump to alpha from stable.');
});
it('bumps to beta from nightly', async () => {
const version = 'v2.0.0-nightly.19950901';
it('bumps to alpha from alpha', async () => {
const version = 'v2.0.0-alpha.8';
gitFake.setVersion('alpha', version);
const next = await nextVersion('alpha', version);
expect(next).to.equal('2.0.0-alpha.9');
});
it('bumps to alpha from alpha if the previous alpha is at least alpha.10', async () => {
const version = 'v6.0.0-alpha.15';
gitFake.setVersion('alpha', version);
const next = await nextVersion('alpha', version);
expect(next).to.equal('6.0.0-alpha.16');
});
it('bumps to beta from alpha', async () => {
const version = 'v2.0.0-alpha.8';
gitFake.setVersion('alpha', version);
const next = await nextVersion('beta', version);
const matches = next.match(betaPattern);
expect(matches).to.have.lengthOf(1);
});
it('bumps to beta from beta', async () => {
const version = 'v2.0.0-beta.8';
const next = await nextVersion('beta', version);
expect(next).to.equal('2.0.0-beta.9');
});
it('bumps to beta from beta if the previous beta is at least beta.10', async () => {
const version = 'v6.0.0-beta.10';
const next = await nextVersion('beta', version);
// Last 6.0.0 beta we did was beta.15
// So we expect a beta.16 here
expect(next).to.equal('6.0.0-beta.16');
});
it('bumps to stable from beta', async () => {
const version = 'v2.0.0-beta.1';
const next = await nextVersion('stable', version);
expect(next).to.equal('2.0.0');
});
it('bumps to stable from stable', async () => {
const version = 'v2.0.0';
const next = await nextVersion('stable', version);
expect(next).to.equal('2.0.1');
});
it('bumps to minor from stable', async () => {
const version = 'v2.0.0';
const next = await nextVersion('minor', version);
expect(next).to.equal('2.1.0');
});
it('bumps to stable from nightly', async () => {
const version = 'v2.0.0-nightly.19950901';
const next = await nextVersion('stable', version);
expect(next).to.equal('2.0.0');
});
it('throws on an invalid version', () => {
const version = 'vI.AM.INVALID';
return expect(
nextVersion('beta', version)
).to.be.rejectedWith(`Invalid current version: ${version}`);
});
it('throws on an invalid bump type', () => {
const version = 'v2.0.0';
return expect(
nextVersion('WRONG', version)
).to.be.rejectedWith('Invalid bump type.');
expect(next).to.equal('2.0.0-beta.1');
});
});
});

View File

@@ -3,6 +3,7 @@ import * as url from 'url';
import { BrowserWindow, session, ipcMain, app, WebContents } from 'electron/main';
import { closeAllWindows } from './window-helpers';
import { emittedOnce, emittedUntil } from './events-helpers';
import { ifit, delay } from './spec-helpers';
import { expect } from 'chai';
async function loadWebView (w: WebContents, attributes: Record<string, string>, openDevTools: boolean = false): Promise<void> {
@@ -181,6 +182,26 @@ describe('<webview> tag', function () {
});
});
describe('did-attach event', () => {
it('is emitted when a webview has been attached', async () => {
const w = new BrowserWindow({
webPreferences: {
webviewTag: true
}
});
await w.loadURL('about:blank');
const message = await w.webContents.executeJavaScript(`new Promise((resolve, reject) => {
const webview = new WebView()
webview.setAttribute('src', 'about:blank')
webview.addEventListener('did-attach', (e) => {
resolve('ok')
})
document.body.appendChild(webview)
})`);
expect(message).to.equal('ok');
});
});
describe('did-change-theme-color event', () => {
it('emits when theme color changes', async () => {
const w = new BrowserWindow({
@@ -406,6 +427,35 @@ describe('<webview> tag', function () {
await parentFullscreen;
expect(await w.webContents.executeJavaScript('isIframeFullscreen()')).to.be.true();
});
// FIXME(zcbenz): Fullscreen events do not work on Linux.
// This test is flaky on arm64 macOS.
ifit(process.platform !== 'linux' && process.arch !== 'arm64')('exiting fullscreen should unfullscreen window', async () => {
const [w, webview] = await loadWebViewWindow();
const enterFullScreen = emittedOnce(w, 'enter-full-screen');
await webview.executeJavaScript('document.getElementById("div").requestFullscreen()', true);
await enterFullScreen;
const leaveFullScreen = emittedOnce(w, 'leave-full-screen');
await webview.executeJavaScript('document.exitFullscreen()', true);
await leaveFullScreen;
await delay(0);
expect(w.isFullScreen()).to.be.false();
});
// Sending ESC via sendInputEvent only works on Windows.
ifit(process.platform === 'win32')('pressing ESC should unfullscreen window', async () => {
const [w, webview] = await loadWebViewWindow();
const enterFullScreen = emittedOnce(w, 'enter-full-screen');
await webview.executeJavaScript('document.getElementById("div").requestFullscreen()', true);
await enterFullScreen;
const leaveFullScreen = emittedOnce(w, 'leave-full-screen');
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Escape' });
await leaveFullScreen;
await delay(0);
expect(w.isFullScreen()).to.be.false();
});
});
describe('nativeWindowOpen option', () => {

View File

@@ -57,7 +57,6 @@ declare namespace Electron {
interface WebContents {
_loadURL(url: string, options: ElectronInternal.LoadURLOptions): void;
getOwnerBrowserWindow(): Electron.BrowserWindow;
getWebPreferences(): Electron.WebPreferences;
getLastWebPreferences(): Electron.WebPreferences;
_getPreloadPaths(): string[];
equal(other: WebContents): boolean;