Compare commits

...

12 Commits

Author SHA1 Message Date
Samuel Attard
09302a2fc6 build: use python3 for appveyor commands (#36131) 2022-10-25 16:39:21 -07:00
Samuel Attard
b13f776d1b build: ensure get-version runs in the electron git checkout (#36128) 2022-10-25 11:27:10 -07:00
Samuel Attard
7ca2bb5f9c build: determine electron version from tags not files (#36106)
* build: determine electron version from tags not files

* build: make electron_version dependent on packed-refs and git HEAD

* build: do not delete electron/.git

* build: do not revert a commit we didn't make

* build: gen version file instead of just writing it

* build: update cache and ninja targets

* build: copy resource.h to generated electron.rc

* build: electron_win32_resources should be public deps

* build: also copy the icon
2022-10-24 23:44:43 -07:00
flynow10
ad289d120f docs: fixed MenuItem documentation to use the same formatting for all role options (#36112)
Fixed MenuItem documentation

Some of the items in the list of possible roles were formatted differently, which made it harder to read.
Sorry if this was intended, but I found it easier to read with them all formatted the same.
2022-10-24 14:51:50 -04:00
Sudowoodo Release Bot
85b193178f Bump v23.0.0-nightly.20221024 2022-10-24 06:01:28 -07:00
Sudowoodo Release Bot
3aed596fba Bump v23.0.0-nightly.20221021 2022-10-21 06:01:08 -07:00
Shelley Vohr
3e730ab016 test: exit properly when node-spec-runner validates specs (#36091)
test: exit properly when node spec runner validates specs
2022-10-21 11:13:59 +02:00
John Kleinschmidt
0158077bec test: disable flaky linux arm/arm64 crash test case (#36094)
test: disable failing linux arm/arm64 crash test case
2022-10-20 18:06:06 -04:00
Shelley Vohr
7921fec761 refactor: enable OOPIF printing to PDF (#36051) 2022-10-20 14:30:40 -04:00
Sudowoodo Release Bot
289bdbe4bc Bump v23.0.0-nightly.20221020 2022-10-20 06:03:15 -07:00
Robo
da0fd286b4 feat: UtilityProcess API (#34980)
* chore: initial scaffolding

* chore: implement interface and docs

* chore: address code style review

* fix: cleanup of utility process on shutdown

* chore: simplify NodeBindings::CreateEnvironment

* chore: rename disableLibraryValidation => allowLoadingUnsignedLibraries

* chore: implement process.parentPort

* chore(posix): implement stdio pipe interface

* chore(win): implement stdio interface

* chore: reenable SetNodeOptions for utility process

* chore: add specs

* chore: fix lint

* fix: update kill API

* fix: update process.parentPort API

* fix: exit event

* docs: update exit event

* fix: tests on linux

* chore: expand on some comments

* fix: shutdown of pipe reader

Avoid logging since it is always the case that reader end of
pipe will terminate after the child process.

* fix: remove exit code check for crash spec

* fix: rm PR_SET_NO_NEW_PRIVS for unsandbox utility process

* chore: fix incorrect rebase

* fix: address review feedback

* chore: rename utility_process -> utility

* chore: update docs

* chore: cleanup c++ implemantation

* fix: leak in NodeServiceHost impl

* chore: minor cleanup

* chore: cleanup JS implementation

* chore: flip default stdio to inherit

* fix: some api improvements

* Support cwd option
* Remove path restriction for modulePath
* Rewire impl for env support

* fix: add tests for cwd and env option

* chore: alt impl for reading stdio handles

* chore: support message queuing

* chore: fix lint

* chore: new UtilityProcess => utilityProcess.fork

* fix: support for uncaught exception exits

* chore: remove process.execArgv as default

* fix: windows build

* fix: style changes

* fix: docs and style changes

* chore: update patches

* spec: disable flaky test on win32 arm CI

Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
2022-10-19 22:49:49 -07:00
Samuel Attard
44c40efecf refactor: migrate from asar to @electron/asar (#36070)
* refactor: migrate from asar to @electron/asar

* fix: update asar require calls
2022-10-19 13:39:31 -07:00
88 changed files with 2932 additions and 527 deletions

View File

@@ -455,7 +455,7 @@ step-delete-git-directories: &step-delete-git-directories
command: |
if [ "`uname`" == "Darwin" ]; then
cd src
( find . -type d -name ".git" -not -path "./third_party/angle/*" -not -path "./third_party/dawn/*" ) | xargs rm -rf
( find . -type d -name ".git" -not -path "./third_party/angle/*" -not -path "./third_party/dawn/*" -not -path "./electron/*" ) | xargs rm -rf
fi
# On macOS the yarn install command during gclient sync was run on a linux
@@ -814,7 +814,7 @@ step-maybe-zip-symbols: &step-maybe-zip-symbols
cd src
export BUILD_PATH="$PWD/out/Default"
ninja -C out/Default electron:licenses
ninja -C out/Default electron:electron_version
ninja -C out/Default electron:electron_version_file
DELETE_DSYMS_AFTER_ZIP=1 electron/script/zip-symbols.py -b $BUILD_PATH
step-maybe-cross-arch-snapshot: &step-maybe-cross-arch-snapshot
@@ -874,12 +874,12 @@ step-touch-sync-done: &step-touch-sync-done
step-maybe-restore-src-cache: &step-maybe-restore-src-cache
restore_cache:
keys:
- v14-src-cache-{{ checksum "src/electron/.depshash" }}
- v15-src-cache-{{ checksum "src/electron/.depshash" }}
name: Restoring src cache
step-maybe-restore-src-cache-marker: &step-maybe-restore-src-cache-marker
restore_cache:
keys:
- v14-src-cache-marker-{{ checksum "src/electron/.depshash" }}
- v15-src-cache-marker-{{ checksum "src/electron/.depshash" }}
name: Restoring src cache marker
# Restore exact or closest git cache based on the hash of DEPS and .circle-sync-done
@@ -954,13 +954,14 @@ step-minimize-workspace-size-from-checkout: &step-minimize-workspace-size-from-c
rm -rf third_party/electron_node/deps/openssl
rm -rf third_party/electron_node/deps/v8
rm -rf chrome/test/data/xr/webvr_info
rm -rf src/third_party/angle/third_party/VK-GL-CTS/src
# Save the src cache based on the deps hash
step-save-src-cache: &step-save-src-cache
save_cache:
paths:
- /var/portal
key: v14-src-cache-{{ checksum "/var/portal/src/electron/.depshash" }}
key: v15-src-cache-{{ checksum "/var/portal/src/electron/.depshash" }}
name: Persisting src cache
step-make-src-cache-marker: &step-make-src-cache-marker
run:
@@ -970,7 +971,7 @@ step-save-src-cache-marker: &step-save-src-cache-marker
save_cache:
paths:
- .src-cache-marker
key: v14-src-cache-marker-{{ checksum "/var/portal/src/electron/.depshash" }}
key: v15-src-cache-marker-{{ checksum "/var/portal/src/electron/.depshash" }}
step-maybe-early-exit-no-doc-change: &step-maybe-early-exit-no-doc-change
run:

View File

@@ -107,6 +107,13 @@ branding = read_file("shell/app/BRANDING.json", "json")
electron_project_name = branding.project_name
electron_product_name = branding.product_name
electron_mac_bundle_id = branding.mac_bundle_id
electron_version = exec_script("script/print-version.py",
[],
"trim string",
[
".git/packed-refs",
".git/HEAD",
])
if (is_mas_build) {
assert(is_mac,
@@ -202,6 +209,15 @@ webpack_build("electron_isolated_renderer_bundle") {
out_file = "$target_gen_dir/js2c/isolated_bundle.js"
}
webpack_build("electron_utility_bundle") {
deps = [ ":build_electron_definitions" ]
inputs = auto_filenames.utility_bundle_deps
config_file = "//electron/build/webpack/webpack.config.utility.js"
out_file = "$target_gen_dir/js2c/utility_init.js"
}
action("electron_js2c") {
deps = [
":electron_asar_bundle",
@@ -209,6 +225,7 @@ action("electron_js2c") {
":electron_isolated_renderer_bundle",
":electron_renderer_bundle",
":electron_sandboxed_renderer_bundle",
":electron_utility_bundle",
":electron_worker_bundle",
]
@@ -218,6 +235,7 @@ action("electron_js2c") {
"$target_gen_dir/js2c/isolated_bundle.js",
"$target_gen_dir/js2c/renderer_init.js",
"$target_gen_dir/js2c/sandbox_bundle.js",
"$target_gen_dir/js2c/utility_init.js",
"$target_gen_dir/js2c/worker_init.js",
]
@@ -302,12 +320,9 @@ npm_action("electron_version_args") {
outputs = [ "$target_gen_dir/electron_version.args" ]
args = rebase_path(outputs)
args = rebase_path(outputs) + [ "$electron_version" ]
inputs = [
"ELECTRON_VERSION",
"script/generate-version-json.js",
]
inputs = [ "script/generate-version-json.js" ]
}
templated_file("electron_version_header") {
@@ -319,6 +334,39 @@ templated_file("electron_version_header") {
args_files = get_target_outputs(":electron_version_args")
}
templated_file("electron_win_rc") {
deps = [ ":electron_version_args" ]
template = "build/templates/electron_rc.tmpl"
output = "$target_gen_dir/win-resources/electron.rc"
args_files = get_target_outputs(":electron_version_args")
}
copy("electron_win_resource_files") {
sources = [
"shell/browser/resources/win/electron.ico",
"shell/browser/resources/win/resource.h",
]
outputs = [ "$target_gen_dir/win-resources/{{source_file_part}}" ]
}
templated_file("electron_version_file") {
deps = [ ":electron_version_args" ]
template = "build/templates/version_string.tmpl"
output = "$root_build_dir/version"
args_files = get_target_outputs(":electron_version_args")
}
group("electron_win32_resources") {
public_deps = [
":electron_win_rc",
":electron_win_resource_files",
]
}
action("electron_fuses") {
script = "build/fuses/build.py"
@@ -368,6 +416,7 @@ source_set("electron_lib") {
"chromium_src:chrome",
"chromium_src:chrome_spellchecker",
"shell/common/api:mojo",
"shell/services/node/public/mojom",
"//base:base_static",
"//base/allocator:buildflags",
"//chrome:strings",
@@ -751,7 +800,6 @@ if (is_mac) {
electron_helper_name = "$electron_product_name Helper"
electron_login_helper_name = "$electron_product_name Login Helper"
electron_framework_version = "A"
electron_version = read_file("ELECTRON_VERSION", "trim string")
mac_xib_bundle_data("electron_xibs") {
sources = [ "shell/common/resources/mac/MainMenu.xib" ]
@@ -1192,6 +1240,7 @@ if (is_mac) {
":default_app_asar",
":electron_app_manifest",
":electron_lib",
":electron_win32_resources",
":packed_resources",
"//components/crash/core/app",
"//content:sandbox_helper_win",
@@ -1225,8 +1274,7 @@ if (is_mac) {
if (is_win) {
sources += [
# TODO: we should be generating our .rc files more like how chrome does
"shell/browser/resources/win/electron.rc",
"$target_gen_dir/win-resources/electron.rc",
"shell/browser/resources/win/resource.h",
]
@@ -1408,15 +1456,10 @@ group("licenses") {
]
}
copy("electron_version") {
sources = [ "ELECTRON_VERSION" ]
outputs = [ "$root_build_dir/version" ]
}
dist_zip("electron_dist_zip") {
data_deps = [
":electron_app",
":electron_version",
":electron_version_file",
":licenses",
]
if (is_linux) {

View File

@@ -1 +0,0 @@
23.0.0-nightly.20221019

View File

@@ -199,14 +199,14 @@ for:
}
- ps: >-
if ($env:GN_CONFIG -eq 'release') {
python electron\script\zip-symbols.py
python3 electron\script\zip-symbols.py
appveyor-retry appveyor PushArtifact out/Default/symbols.zip
} else {
# It's useful to have pdb files when debugging testing builds that are
# built on CI.
7z a pdb.zip out\Default\*.pdb
}
- python electron/script/zip_manifests/check-zip-manifest.py out/Default/dist.zip electron/script/zip_manifests/dist_zip.win.%TARGET_ARCH%.manifest
- python3 electron/script/zip_manifests/check-zip-manifest.py out/Default/dist.zip electron/script/zip_manifests/dist_zip.win.%TARGET_ARCH%.manifest
deploy_script:
- cd electron
@@ -214,10 +214,10 @@ for:
if (Test-Path Env:\ELECTRON_RELEASE) {
if (Test-Path Env:\UPLOAD_TO_STORAGE) {
Write-Output "Uploading Electron release distribution to azure"
& python script\release\uploaders\upload.py --verbose --upload_to_storage
& python3 script\release\uploaders\upload.py --verbose --upload_to_storage
} else {
Write-Output "Uploading Electron release distribution to github releases"
& python script\release\uploaders\upload.py --verbose
& python3 script\release\uploaders\upload.py --verbose
}
} elseif (Test-Path Env:\TEST_WOA) {
node script/release/ci-release-build.js --job=electron-woa-testing --ci=GHA --appveyorJobId=$env:APPVEYOR_JOB_ID $env:APPVEYOR_REPO_BRANCH
@@ -303,11 +303,11 @@ for:
- echo Running main test suite & node script/yarn test -- --trace-uncaught --runners=main --enable-logging=file --log-file=%cd%\electron.log
- echo Running native test suite & node script/yarn test -- --trace-uncaught --runners=native --enable-logging=file --log-file=%cd%\electron.log
- cd ..
- echo Verifying non proprietary ffmpeg & python electron\script\verify-ffmpeg.py --build-dir out\Default --source-root %cd% --ffmpeg-path out\ffmpeg
- echo Verifying non proprietary ffmpeg & python3 electron\script\verify-ffmpeg.py --build-dir out\Default --source-root %cd% --ffmpeg-path out\ffmpeg
- echo "About to verify mksnapshot"
- echo Verifying mksnapshot & python electron\script\verify-mksnapshot.py --build-dir out\Default --source-root %cd%
- echo Verifying mksnapshot & python3 electron\script\verify-mksnapshot.py --build-dir out\Default --source-root %cd%
- echo "Done verifying mksnapshot"
- echo Verifying chromedriver & python electron\script\verify-chromedriver.py --build-dir out\Default --source-root %cd%
- echo Verifying chromedriver & python3 electron\script\verify-chromedriver.py --build-dir out\Default --source-root %cd%
- echo "Done verifying chromedriver"
on_finish:

View File

@@ -50,8 +50,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 23,0,0,20221019
PRODUCTVERSION 23,0,0,20221019
FILEVERSION $major,$minor,$patch,$prerelease_number
PRODUCTVERSION $major,$minor,$patch,$prerelease_number
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -68,12 +68,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "GitHub, Inc."
VALUE "FileDescription", "Electron"
VALUE "FileVersion", "23.0.0"
VALUE "FileVersion", "$major.$minor.$patch"
VALUE "InternalName", "electron.exe"
VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved."
VALUE "OriginalFilename", "electron.exe"
VALUE "ProductName", "Electron"
VALUE "ProductVersion", "23.0.0"
VALUE "ProductVersion", "$major.$minor.$patch"
VALUE "SquirrelAwareVersion", "1"
END
END

View File

@@ -0,0 +1 @@
$full_version

View File

@@ -0,0 +1,4 @@
module.exports = require('./webpack.config.base')({
target: 'utility',
alwaysHasNode: true
});

View File

@@ -232,6 +232,8 @@ static_library("chrome") {
"//chrome/browser/printing/printing_service.h",
"//components/printing/browser/print_to_pdf/pdf_print_job.cc",
"//components/printing/browser/print_to_pdf/pdf_print_job.h",
"//components/printing/browser/print_to_pdf/pdf_print_result.cc",
"//components/printing/browser/print_to_pdf/pdf_print_result.h",
"//components/printing/browser/print_to_pdf/pdf_print_utils.cc",
"//components/printing/browser/print_to_pdf/pdf_print_utils.h",
]

View File

@@ -14,7 +14,7 @@ See [`Menu`](menu.md) for examples.
* `menuItem` MenuItem
* `browserWindow` [BrowserWindow](browser-window.md) | undefined - This will not be defined if no window is open.
* `event` [KeyboardEvent](structures/keyboard-event.md)
* `role` string (optional) - Can be `undo`, `redo`, `cut`, `copy`, `paste`, `pasteAndMatchStyle`, `delete`, `selectAll`, `reload`, `forceReload`, `toggleDevTools`, `resetZoom`, `zoomIn`, `zoomOut`, `toggleSpellChecker`, `togglefullscreen`, `window`, `minimize`, `close`, `help`, `about`, `services`, `hide`, `hideOthers`, `unhide`, `quit`, 'showSubstitutions', 'toggleSmartQuotes', 'toggleSmartDashes', 'toggleTextReplacement', `startSpeaking`, `stopSpeaking`, `zoom`, `front`, `appMenu`, `fileMenu`, `editMenu`, `viewMenu`, `shareMenu`, `recentDocuments`, `toggleTabBar`, `selectNextTab`, `selectPreviousTab`, `mergeAllWindows`, `clearRecentDocuments`, `moveTabToNewWindow` or `windowMenu` - Define the action of the menu item, when specified the
* `role` string (optional) - Can be `undo`, `redo`, `cut`, `copy`, `paste`, `pasteAndMatchStyle`, `delete`, `selectAll`, `reload`, `forceReload`, `toggleDevTools`, `resetZoom`, `zoomIn`, `zoomOut`, `toggleSpellChecker`, `togglefullscreen`, `window`, `minimize`, `close`, `help`, `about`, `services`, `hide`, `hideOthers`, `unhide`, `quit`, `showSubstitutions`, `toggleSmartQuotes`, `toggleSmartDashes`, `toggleTextReplacement`, `startSpeaking`, `stopSpeaking`, `zoom`, `front`, `appMenu`, `fileMenu`, `editMenu`, `viewMenu`, `shareMenu`, `recentDocuments`, `toggleTabBar`, `selectNextTab`, `selectPreviousTab`, `mergeAllWindows`, `clearRecentDocuments`, `moveTabToNewWindow` or `windowMenu` - Define the action of the menu item, when specified the
`click` property will be ignored. See [roles](#roles).
* `type` string (optional) - Can be `normal`, `separator`, `submenu`, `checkbox` or
`radio`.

46
docs/api/parent-port.md Normal file
View File

@@ -0,0 +1,46 @@
# parentPort
> Interface for communication with parent process.
Process: [Utility](../glossary.md#utility-process)
`parentPort` is an [EventEmitter][event-emitter].
_This object is not exported from the `'electron'` module. It is only available as a property of the process object in the Electron API._
```js
// Main process
const child = utilityProcess.fork(path.join(__dirname, 'test.js'))
child.postMessage({ message: 'hello' })
child.on('message', (data) => {
console.log(data) // hello world!
})
// Child process
process.parentPort.on('message', (e) => {
process.parentPort.postMessage(`${e.data} world!`)
})
```
## Events
The `parentPort` object emits the following events:
### Event: 'message'
Returns:
* `messageEvent` Object
* `data` any
* `ports` MessagePortMain[]
Emitted when the process receives a message. Messages received on
this port will be queued up until a handler is registered for this
event.
## Methods
### `parentPort.postMessage(message)`
* `message` any
Sends a message from the process to its parent.

View File

@@ -113,6 +113,7 @@ A `string` representing the current process's type, can be:
* `browser` - The main process
* `renderer` - A renderer process
* `worker` - In a web worker
* `utility` - In a node process launched as a service
### `process.versions.chrome` _Readonly_
@@ -134,6 +135,11 @@ Each frame has its own JavaScript context. When contextIsolation is enabled, the
world also has a separate JavaScript context.
This property is only available in the renderer process.
### `process.parentPort`
A [`Electron.ParentPort`](parent-port.md) property if this is a [`UtilityProcess`](utility-process.md)
(or `null` otherwise) allowing communication with the parent process.
## Methods
The `process` object has the following methods:

136
docs/api/utility-process.md Normal file
View File

@@ -0,0 +1,136 @@
# utilityProcess
`utilityProcess` creates a child process with
Node.js and Message ports enabled. It provides the equivalent of [`child_process.fork`][] API from Node.js
but instead uses [Services API][] from Chromium to launch the child process.
Process: [Main](../glossary.md#main-process)<br />
## Methods
### `utilityProcess.fork(modulePath[, args][, options])`
* `modulePath` string - Path to the script that should run as entrypoint in the child process.
* `args` string[] (optional) - List of string arguments that will be available as `process.argv`
in the child process.
* `options` Object (optional)
* `env` Object (optional) - Environment key-value pairs. Default is `process.env`.
* `execArgv` string[] (optional) - List of string arguments passed to the executable.
* `cwd` string (optional) - Current working directory of the child process.
* `stdio` (string[] | string) (optional) - Allows configuring the mode for `stdout` and `stderr`
of the child process. Default is `inherit`.
String value can be one of `pipe`, `ignore`, `inherit`, for more details on these values you can refer to
[stdio][] documentation from Node.js. Currently this option only supports configuring `stdout` and
`stderr` to either `pipe`, `inherit` or `ignore`. Configuring `stdin` is not supported; `stdin` will
always be ignored.
For example, the supported values will be processed as following:
* `pipe`: equivalent to ['ignore', 'pipe', 'pipe'] (the default)
* `ignore`: equivalent to 'ignore', 'ignore', 'ignore']
* `inherit`: equivalent to ['ignore', 'inherit', 'inherit']
* `serviceName` string (optional) - Name of the process that will appear in `name` property of
[`child-process-gone` event of `app`](app.md#event-child-process-gone).
Default is `node.mojom.NodeService`.
* `allowLoadingUnsignedLibraries` boolean (optional) _macOS_ - With this flag, the utility process will be
launched via the `Electron Helper (Plugin).app` helper executable on macOS, which can be
codesigned with `com.apple.security.cs.disable-library-validation` and
`com.apple.security.cs.allow-unsigned-executable-memory` entitlements. This will allow the utility process
to load unsigned libraries. Unless you specifically need this capability, it is best to leave this disabled.
Default is `false`.
Returns [`UtilityProcess`](utility-process.md#class-utilityprocess)
## Class: UtilityProcess
> Instances of the `UtilityProcess` represent the Chromium spawned child process
> with Node.js integration.
`UtilityProcess` is an [EventEmitter][event-emitter].
### Instance Methods
#### `child.postMessage(message, [transfer])`
* `message` any
* `transfer` MessagePortMain[] (optional)
Send a message to the child process, optionally transferring ownership of
zero or more [`MessagePortMain`][] objects.
For example:
```js
// Main process
const { port1, port2 } = new MessageChannelMain()
const child = utilityProcess.fork(path.join(__dirname, 'test.js'))
child.postMessage({ message: 'hello' }, [port1])
// Child process
process.parentPort.once('message', (e) => {
const [port] = e.ports
// ...
})
```
#### `child.kill()`
Returns `boolean`
Terminates the process gracefully. On POSIX, it uses SIGTERM
but will ensure the process is reaped on exit. This function returns
true if the kill is successful, and false otherwise.
### Instance Properties
#### `child.pid`
A `Integer | undefined` representing the process identifier (PID) of the child process.
If the child process fails to spawn due to errors, then the value is `undefined`. When
the child process exits, then the value is `undefined` after the `exit` event is emitted.
#### `child.stdout`
A `NodeJS.ReadableStream | null` that represents the child process's stdout.
If the child was spawned with options.stdio[1] set to anything other than 'pipe', then this will be `null`.
When the child process exits, then the value is `null` after the `exit` event is emitted.
```js
// Main process
const { port1, port2 } = new MessageChannelMain()
const child = utilityProcess.fork(path.join(__dirname, 'test.js'))
child.stdout.on('data', (data) => {
console.log(`Received chunk ${data}`)
})
```
#### `child.stderr`
A `NodeJS.ReadableStream | null` that represents the child process's stderr.
If the child was spawned with options.stdio[2] set to anything other than 'pipe', then this will be `null`.
When the child process exits, then the value is `null` after the `exit` event is emitted.
### Instance Events
#### Event: 'spawn'
Emitted once the child process has spawned successfully.
#### Event: 'exit'
Returns:
* `code` number - Contains the exit code for
the process obtained from waitpid on posix, or GetExitCodeProcess on windows.
Emitted after the child process ends.
#### Event: 'message'
Returns:
* `message` any
Emitted when the child process sends a message using [`process.parentPort.postMessage()`](process.md#processparentport).
[`child_process.fork`]: https://nodejs.org/dist/latest-v16.x/docs/api/child_process.html#child_processforkmodulepath-args-options
[Services API]: https://chromium.googlesource.com/chromium/src/+/master/docs/mojo_and_services.md
[stdio]: https://nodejs.org/dist/latest/docs/api/child_process.html#optionsstdio

View File

@@ -194,6 +194,15 @@ overly prescriptive about how it should be used. Userland enables users to
create and share tools that provide additional functionality on top of what is
available in "core".
### utility process
The utility process is a child of the main process that allows running any
untrusted services that cannot be run in the main process. Chromium uses this
process to perform network I/O, audio/video processing, device inputs etc.
In Electron, you can create this process using [UtilityProcess][] API.
See also: [process](#process), [main process](#main-process)
### V8
V8 is Google's open source JavaScript engine. It is written in C++ and is
@@ -231,4 +240,5 @@ embedded content.
[renderer]: #renderer-process
[userland]: #userland
[using native node modules]: tutorial/using-native-node-modules.md
[UtilityProcess]: api/utility-process.md
[v8]: #v8

View File

@@ -36,6 +36,7 @@ auto_filenames = {
"docs/api/net-log.md",
"docs/api/net.md",
"docs/api/notification.md",
"docs/api/parent-port.md",
"docs/api/power-monitor.md",
"docs/api/power-save-blocker.md",
"docs/api/process.md",
@@ -62,6 +63,7 @@ auto_filenames = {
"docs/api/touch-bar-spacer.md",
"docs/api/touch-bar.md",
"docs/api/tray.md",
"docs/api/utility-process.md",
"docs/api/web-contents.md",
"docs/api/web-frame-main.md",
"docs/api/web-frame.md",
@@ -220,6 +222,7 @@ auto_filenames = {
"lib/browser/api/system-preferences.ts",
"lib/browser/api/touch-bar.ts",
"lib/browser/api/tray.ts",
"lib/browser/api/utility-process.ts",
"lib/browser/api/view.ts",
"lib/browser/api/views/image-view.ts",
"lib/browser/api/web-contents-view.ts",
@@ -331,4 +334,20 @@ auto_filenames = {
"typings/internal-ambient.d.ts",
"typings/internal-electron.d.ts",
]
utility_bundle_deps = [
"lib/browser/message-port-main.ts",
"lib/common/define-properties.ts",
"lib/common/init.ts",
"lib/common/reset-search-paths.ts",
"lib/utility/api/exports/electron.ts",
"lib/utility/api/module-list.ts",
"lib/utility/init.ts",
"lib/utility/parent-port.ts",
"package.json",
"tsconfig.electron.json",
"tsconfig.json",
"typings/internal-ambient.d.ts",
"typings/internal-electron.d.ts",
]
}

View File

@@ -311,6 +311,8 @@ filenames = {
"shell/browser/api/electron_api_tray.h",
"shell/browser/api/electron_api_url_loader.cc",
"shell/browser/api/electron_api_url_loader.h",
"shell/browser/api/electron_api_utility_process.cc",
"shell/browser/api/electron_api_utility_process.h",
"shell/browser/api/electron_api_view.cc",
"shell/browser/api/electron_api_view.h",
"shell/browser/api/electron_api_web_contents.cc",
@@ -679,6 +681,10 @@ filenames = {
"shell/renderer/renderer_client_base.h",
"shell/renderer/web_worker_observer.cc",
"shell/renderer/web_worker_observer.h",
"shell/services/node/node_service.cc",
"shell/services/node/node_service.h",
"shell/services/node/parent_port.cc",
"shell/services/node/parent_port.h",
"shell/utility/electron_content_utility_client.cc",
"shell/utility/electron_content_utility_client.h",
]

View File

@@ -31,6 +31,7 @@ export const browserModuleList: ElectronInternal.ModuleEntry[] = [
{ name: 'systemPreferences', loader: () => require('./system-preferences') },
{ name: 'TouchBar', loader: () => require('./touch-bar') },
{ name: 'Tray', loader: () => require('./tray') },
{ name: 'utilityProcess', loader: () => require('./utility-process') },
{ name: 'View', loader: () => require('./view') },
{ name: 'webContents', loader: () => require('./web-contents') },
{ name: 'WebContentsView', loader: () => require('./web-contents-view') },

View File

@@ -0,0 +1,150 @@
import { EventEmitter } from 'events';
import { Duplex, PassThrough } from 'stream';
import { Socket } from 'net';
import { MessagePortMain } from '@electron/internal/browser/message-port-main';
const { _fork } = process._linkedBinding('electron_browser_utility_process');
class ForkUtilityProcess extends EventEmitter {
#handle: ElectronInternal.UtilityProcessWrapper | null;
#stdout: Duplex | null = null;
#stderr: Duplex | null = null;
constructor (modulePath: string, args?: string[], options?: Electron.ForkOptions) {
super();
if (!modulePath) {
throw new Error('Missing UtilityProcess entry script.');
}
if (args == null) {
args = [];
} else if (typeof args === 'object' && !Array.isArray(args)) {
options = args;
args = [];
}
if (options == null) {
options = {};
} else {
options = { ...options };
}
if (!options) {
throw new Error('Options cannot be undefined.');
}
if (options.execArgv != null) {
if (!Array.isArray(options.execArgv)) {
throw new Error('execArgv must be an array of strings.');
}
}
if (options.serviceName != null) {
if (typeof options.serviceName !== 'string') {
throw new Error('serviceName must be a string.');
}
}
if (options.cwd != null) {
if (typeof options.cwd !== 'string') {
throw new Error('cwd path must be a string.');
}
}
if (typeof options.stdio === 'string') {
const stdio : Array<'pipe' | 'ignore' | 'inherit'> = [];
switch (options.stdio) {
case 'inherit':
case 'ignore':
stdio.push('ignore', options.stdio, options.stdio);
break;
case 'pipe':
this.#stderr = new PassThrough();
this.#stdout = new PassThrough();
stdio.push('ignore', options.stdio, options.stdio);
break;
default:
throw new Error('stdio must be of the following values: inherit, pipe, ignore');
}
options.stdio = stdio;
} else if (Array.isArray(options.stdio)) {
if (options.stdio.length >= 3) {
if (options.stdio[0] !== 'ignore') {
throw new Error('stdin value other than ignore is not supported.');
}
if (options.stdio[1] === 'pipe') {
this.#stdout = new PassThrough();
} else if (options.stdio[1] !== 'ignore' && options.stdio[1] !== 'inherit') {
throw new Error('stdout configuration must be of the following values: inherit, pipe, ignore');
}
if (options.stdio[2] === 'pipe') {
this.#stderr = new PassThrough();
} else if (options.stdio[2] !== 'ignore' && options.stdio[2] !== 'inherit') {
throw new Error('stderr configuration must be of the following values: inherit, pipe, ignore');
}
} else {
throw new Error('configuration missing for stdin, stdout or stderr.');
}
}
this.#handle = _fork({ options, modulePath, args });
this.#handle!.emit = (channel: string | symbol, ...args: any[]) => {
if (channel === 'exit') {
try {
this.emit('exit', ...args);
} finally {
this.#handle = null;
if (this.#stdout) {
this.#stdout.removeAllListeners();
this.#stdout = null;
}
if (this.#stderr) {
this.#stderr.removeAllListeners();
this.#stderr = null;
}
}
return false;
} else if (channel === 'stdout' && this.#stdout) {
new Socket({ fd: args[0], readable: true }).pipe(this.#stdout);
return true;
} else if (channel === 'stderr' && this.#stderr) {
new Socket({ fd: args[0], readable: true }).pipe(this.#stderr);
return true;
} else {
return this.emit(channel, ...args);
}
};
}
get pid () {
return this.#handle?.pid;
}
get stdout () {
return this.#stdout;
}
get stderr () {
return this.#stderr;
}
postMessage (message: any, transfer?: MessagePortMain[]) {
if (Array.isArray(transfer)) {
transfer = transfer.map((o: any) => o instanceof MessagePortMain ? o._internalPort : o);
return this.#handle?.postMessage(message, transfer);
}
return this.#handle?.postMessage(message);
}
kill () : boolean {
if (this.#handle === null) {
return false;
}
return this.#handle.kill();
}
}
export function fork (modulePath: string, args?: string[], options?: Electron.ForkOptions) {
return new ForkUtilityProcess(modulePath, args, options);
}

View File

@@ -33,20 +33,29 @@ function wrap <T extends AnyFn> (func: T, wrapper: (fn: AnyFn) => T) {
return wrapped;
}
// process.nextTick and setImmediate make use of uv_check and uv_prepare to
// run the callbacks, however since we only run uv loop on requests, the
// callbacks wouldn't be called until something else activated the uv loop,
// which would delay the callbacks for arbitrary long time. So we should
// initiatively activate the uv loop once process.nextTick and setImmediate is
// called.
process.nextTick = wrapWithActivateUvLoop(process.nextTick);
global.setImmediate = timers.setImmediate = wrapWithActivateUvLoop(timers.setImmediate);
global.clearImmediate = timers.clearImmediate;
// setTimeout needs to update the polling timeout of the event loop, when
// called under Chromium's event loop the node's event loop won't get a chance
// to update the timeout, so we have to force the node's event loop to
// recalculate the timeout in browser process.
// recalculate the timeout in the process.
timers.setTimeout = wrapWithActivateUvLoop(timers.setTimeout);
timers.setInterval = wrapWithActivateUvLoop(timers.setInterval);
// Only override the global setTimeout/setInterval impls in the browser process
if (process.type === 'browser') {
// Update the global version of the timer apis to use the above wrapper
// only in the process that runs node event loop alongside chromium
// event loop. We skip renderer with nodeIntegration here because node globals
// are deleted in these processes, see renderer/init.js for reference.
if (process.type === 'browser' ||
process.type === 'utility') {
global.setTimeout = timers.setTimeout;
global.setInterval = timers.setInterval;
}

View File

@@ -0,0 +1,21 @@
{
"rules": {
"no-restricted-imports": [
"error",
{
"paths": [
"electron",
"electron/renderer"
],
"patterns": [
"./*",
"../*",
"@electron/internal/isolated_renderer/*",
"@electron/internal/renderer/*",
"@electron/internal/sandboxed_worker/*",
"@electron/internal/worker/*"
]
}
]
}
}

View File

@@ -0,0 +1,6 @@
import { defineProperties } from '@electron/internal/common/define-properties';
import { utilityNodeModuleList } from '@electron/internal/utility/api/module-list';
module.exports = {};
defineProperties(module.exports, utilityNodeModuleList);

View File

@@ -0,0 +1,2 @@
// Utility side modules, please sort alphabetically.
export const utilityNodeModuleList: ElectronInternal.ModuleEntry[] = [];

38
lib/utility/init.ts Normal file
View File

@@ -0,0 +1,38 @@
import { ParentPort } from '@electron/internal/utility/parent-port';
const Module = require('module');
const v8Util = process._linkedBinding('electron_common_v8_util');
const entryScript: string = v8Util.getHiddenValue(process, '_serviceStartupScript');
// We modified the original process.argv to let node.js load the init.js,
// we need to restore it here.
process.argv.splice(1, 1, entryScript);
// Clear search paths.
require('../common/reset-search-paths');
// Import common settings.
require('@electron/internal/common/init');
const parentPort: ParentPort = new ParentPort();
Object.defineProperty(process, 'parentPort', {
enumerable: true,
writable: false,
value: parentPort
});
// Based on third_party/electron_node/lib/internal/worker/io.js
parentPort.on('newListener', (name: string) => {
if (name === 'message' && parentPort.listenerCount('message') === 0) {
parentPort.start();
}
});
parentPort.on('removeListener', (name: string) => {
if (name === 'message' && parentPort.listenerCount('message') === 0) {
parentPort.pause();
}
});
// Finally load entry script.
process._firstFileName = Module._resolveFilename(entryScript, null, false);
Module._load(entryScript, Module, true);

View File

@@ -0,0 +1,30 @@
import { EventEmitter } from 'events';
import { MessagePortMain } from '@electron/internal/browser/message-port-main';
const { createParentPort } = process._linkedBinding('electron_utility_parent_port');
export class ParentPort extends EventEmitter {
#port: ParentPort
constructor () {
super();
this.#port = createParentPort();
this.#port.emit = (channel: string | symbol, event: { ports: any[] }) => {
if (channel === 'message') {
event = { ...event, ports: event.ports.map(p => new MessagePortMain(p)) };
}
this.emit(channel, event);
return false;
};
}
start () : void {
this.#port.start();
}
pause () : void {
this.#port.pause();
}
postMessage (message: any) : void {
this.#port.postMessage(message);
}
}

View File

@@ -1,10 +1,11 @@
{
"name": "electron",
"version": "23.0.0-nightly.20221019",
"version": "0.0.0-development",
"repository": "https://github.com/electron/electron",
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
"devDependencies": {
"@azure/storage-blob": "^12.9.0",
"@electron/asar": "^3.2.1",
"@electron/docs-parser": "^0.12.4",
"@electron/typescript-definitions": "^8.9.6",
"@octokit/auth-app": "^2.10.0",
@@ -31,7 +32,6 @@
"@types/webpack-env": "^1.17.0",
"@typescript-eslint/eslint-plugin": "^4.4.1",
"@typescript-eslint/parser": "^4.4.1",
"asar": "^3.1.0",
"aws-sdk": "^2.814.0",
"buffer": "^6.0.3",
"check-for-leaks": "^1.2.1",

View File

@@ -115,7 +115,9 @@ add_electron_deps_to_license_credits_file.patch
fix_crash_loading_non-standard_schemes_in_iframes.patch
fix_return_v8_value_from_localframe_requestexecutescript.patch
create_browser_v8_snapshot_file_name_fuse.patch
feat_configure_launch_options_for_service_process.patch
feat_ensure_mas_builds_of_the_same_application_can_use_safestorage.patch
fix_on-screen-keyboard_hides_on_input_blur_in_webview.patch
preconnect_manager.patch
fix_remove_caption-removing_style_call.patch
build_allow_electron_to_use_exec_script.patch

View File

@@ -3,28 +3,28 @@ From: Jeremy Apthorp <nornagon@nornagon.net>
Date: Mon, 26 Aug 2019 12:02:51 -0700
Subject: allow new privileges in unsandboxed child processes
This allows unsandboxed renderers to launch setuid processes on Linux.
This allows unsandboxed child process to launch setuid processes on Linux.
diff --git a/content/browser/child_process_launcher_helper_linux.cc b/content/browser/child_process_launcher_helper_linux.cc
index dd5ccfc0bdc2e071999d1bf864dc065dd1311407..7464e84f6e610749dce5c3a46afce262f29020cc 100644
index dd5ccfc0bdc2e071999d1bf864dc065dd1311407..cfadd28fca9f80bf57578db78d5472c4f75414e1 100644
--- a/content/browser/child_process_launcher_helper_linux.cc
+++ b/content/browser/child_process_launcher_helper_linux.cc
@@ -54,6 +54,18 @@ bool ChildProcessLauncherHelper::BeforeLaunchOnLauncherThread(
if (GetProcessType() == switches::kRendererProcess) {
const int sandbox_fd = SandboxHostLinux::GetInstance()->GetChildSocket();
@@ -56,6 +56,18 @@ bool ChildProcessLauncherHelper::BeforeLaunchOnLauncherThread(
options->fds_to_remap.push_back(std::make_pair(sandbox_fd, GetSandboxFD()));
+
+ // (For Electron), if we're launching without zygote, that means we're
+ // launching an unsandboxed process (since all sandboxed processes are
+ // forked from the zygote). Relax the allow_new_privs option to permit
+ // launching suid processes from unsandboxed renderers.
+ ZygoteHandle zygote_handle =
+ base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoZygote)
+ ? nullptr
+ : delegate_->GetZygote();
+ if (!zygote_handle) {
+ options->allow_new_privs = true;
+ }
}
+ // (For Electron), if we're launching without zygote, that means we're
+ // launching an unsandboxed process (since all sandboxed processes are
+ // forked from the zygote). Relax the allow_new_privs option to permit
+ // launching suid processes from unsandboxed child processes.
+ ZygoteHandle zygote_handle =
+ base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoZygote)
+ ? nullptr
+ : delegate_->GetZygote();
+ if (!zygote_handle) {
+ options->allow_new_privs = true;
+ }
+
for (const auto& remapped_fd : file_data_->additional_remapped_fds) {
options->fds_to_remap.emplace_back(remapped_fd.second.get(),
remapped_fd.first);

View File

@@ -0,0 +1,18 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samuel Attard <sattard@salesforce.com>
Date: Fri, 21 Oct 2022 16:29:06 -0700
Subject: build: allow electron to use exec_script
This is similar to the //build usecase so we're OK adding ourselves here
diff --git a/.gn b/.gn
index 55f5ee19f13e49dfd0aa2300d980d813474c95ef..483e0f0598bfed76b4a4283be3cfa1fdb9eafe5a 100644
--- a/.gn
+++ b/.gn
@@ -169,4 +169,6 @@ exec_script_whitelist =
"//tools/grit/grit_rule.gni",
"//tools/gritsettings/BUILD.gn",
+
+ "//electron/BUILD.gn"
]

View File

@@ -0,0 +1,671 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: deepak1556 <hop2deep@gmail.com>
Date: Wed, 17 Aug 2022 22:04:47 +0900
Subject: feat: configure launch options for service process
- POSIX:
Allows configuring base::LaunchOptions::fds_to_remap when launching the child process.
- Win:
Allows configuring base::LaunchOptions::handles_to_inherit, base::LaunchOptions::stdout_handle
and base::LaunchOptions::stderr_handle when launching the child process.
- All:
Allows configuring base::LauncOptions::current_directory, base::LaunchOptions::enviroment
and base::LaunchOptions::clear_environment.
An example use of this option, UtilityProcess API allows reading the output From
stdout and stderr of child process by creating a pipe, whose write end is remapped
to STDOUT_FILENO/STD_OUTPUT_HANDLE and STDERR_FILENO/STD_ERROR_HANDLE allowing the
parent process to read from the pipe.
diff --git a/content/browser/child_process_launcher.h b/content/browser/child_process_launcher.h
index ba1f0d6e958cdb534b8af7717a0d6d8f2ee296bf..626f771ffbd88f1cf2e9475b745456f98575cda1 100644
--- a/content/browser/child_process_launcher.h
+++ b/content/browser/child_process_launcher.h
@@ -31,6 +31,7 @@
#if BUILDFLAG(IS_WIN)
#include "base/win/windows_types.h"
+#include "base/win/scoped_handle.h"
#endif
#if BUILDFLAG(IS_POSIX)
@@ -163,7 +164,10 @@ struct ChildProcessLauncherFileData {
delete;
~ChildProcessLauncherFileData();
-#if BUILDFLAG(IS_POSIX)
+#if BUILDFLAG(IS_WIN)
+ base::win::ScopedHandle stdout_handle;
+ base::win::ScopedHandle stderr_handle;
+#elif BUILDFLAG(IS_POSIX)
// Files opened by the browser and passed as corresponding file descriptors
// in the child process.
// Currently only supported on Linux, ChromeOS and Android platforms.
diff --git a/content/browser/child_process_launcher_helper_linux.cc b/content/browser/child_process_launcher_helper_linux.cc
index cfadd28fca9f80bf57578db78d5472c4f75414e1..4925dc5cafbf312c3c9640d5873d62193e87f636 100644
--- a/content/browser/child_process_launcher_helper_linux.cc
+++ b/content/browser/child_process_launcher_helper_linux.cc
@@ -73,7 +73,9 @@ bool ChildProcessLauncherHelper::BeforeLaunchOnLauncherThread(
remapped_fd.first);
}
+ options->current_directory = delegate_->GetCurrentDirectory();
options->environment = delegate_->GetEnvironment();
+ options->clear_environment = !delegate_->ShouldInheritEnvironment();
return true;
}
diff --git a/content/browser/child_process_launcher_helper_mac.cc b/content/browser/child_process_launcher_helper_mac.cc
index d74a40c0e5731281b132cc1c3dc2416f9dc2b083..dd8a9d35af617441c6643ed643b459a35b612969 100644
--- a/content/browser/child_process_launcher_helper_mac.cc
+++ b/content/browser/child_process_launcher_helper_mac.cc
@@ -73,7 +73,8 @@ bool ChildProcessLauncherHelper::BeforeLaunchOnLauncherThread(
'mojo', base::MachRendezvousPort(endpoint.TakeMachReceiveRight())));
options->environment = delegate_->GetEnvironment();
-
+ options->clear_environment = !delegate_->ShouldInheritEnvironment();
+ options->current_directory = delegate_->GetCurrentDirectory();
options->disclaim_responsibility = delegate_->DisclaimResponsibility();
options->enable_cpu_security_mitigations =
delegate_->EnableCpuSecurityMitigations();
diff --git a/content/browser/child_process_launcher_helper_win.cc b/content/browser/child_process_launcher_helper_win.cc
index 799ad0a6e0b5c629d10f481d10dd4d6959d40b42..13c610ae1bb24fb6d274a082562dcd103df50513 100644
--- a/content/browser/child_process_launcher_helper_win.cc
+++ b/content/browser/child_process_launcher_helper_win.cc
@@ -19,6 +19,8 @@
#include "sandbox/policy/win/sandbox_win.h"
#include "sandbox/win/src/sandbox_types.h"
+#include <windows.h>
+
namespace content {
namespace internal {
@@ -54,6 +56,30 @@ bool ChildProcessLauncherHelper::BeforeLaunchOnLauncherThread(
mojo_channel_->PrepareToPassRemoteEndpoint(&options->handles_to_inherit,
command_line());
}
+
+ if (file_data_->stdout_handle.IsValid() || file_data_->stderr_handle.IsValid()) {
+ // base::LaunchProcess requires that if any of the stdio handle is customized then
+ // the other two handles should also be set.
+ // https://source.chromium.org/chromium/chromium/src/+/main:base/process/launch_win.cc;l=341-350
+ options->stdin_handle = INVALID_HANDLE_VALUE;
+ if (file_data_->stdout_handle.IsValid()) {
+ options->stdout_handle = file_data_->stdout_handle.get();
+ } else {
+ options->stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
+ }
+
+ if (file_data_->stderr_handle.IsValid()) {
+ options->stderr_handle = file_data_->stderr_handle.get();
+ } else {
+ options->stderr_handle = GetStdHandle(STD_ERROR_HANDLE);
+ }
+ options->handles_to_inherit.push_back(options->stdout_handle);
+ options->handles_to_inherit.push_back(options->stderr_handle);
+ }
+
+ options->current_directory = delegate_->GetCurrentDirectory();
+ options->environment = delegate_->GetEnvironment();
+ options->clear_environment = !delegate_->ShouldInheritEnvironment();
return true;
}
@@ -81,7 +107,7 @@ ChildProcessLauncherHelper::LaunchProcessOnLauncherThread(
ChildProcessLauncherHelper::Process process;
*launch_result =
StartSandboxedProcess(delegate_.get(), *command_line(),
- options.handles_to_inherit, &process.process);
+ options, &process.process);
return process;
}
diff --git a/content/browser/service_process_host_impl.cc b/content/browser/service_process_host_impl.cc
index e547f42bc0d06b485797ccc1605969259631831f..0f3041f4a5b636440d9579303721f2ae7e1855c6 100644
--- a/content/browser/service_process_host_impl.cc
+++ b/content/browser/service_process_host_impl.cc
@@ -190,6 +190,15 @@ void LaunchServiceProcess(mojo::GenericPendingReceiver receiver,
host->SetExtraCommandLineSwitches(std::move(options.extra_switches));
if (options.child_flags)
host->set_child_flags(*options.child_flags);
+#if BUILDFLAG(IS_WIN)
+ host->SetStdioHandles(std::move(options.stdout_handle), std::move(options.stderr_handle));
+#elif BUILDFLAG(IS_POSIX)
+ host->SetAdditionalFds(std::move(options.fds_to_remap));
+#endif
+ host->SetCurrentDirectory(options.current_directory);
+ host->SetEnv(options.environment);
+ if (options.clear_environment)
+ host->ClearEnvironment();
host->Start();
host->GetChildProcess()->BindServiceInterface(std::move(receiver));
}
diff --git a/content/browser/utility_process_host.cc b/content/browser/utility_process_host.cc
index 171b8440c25580d717f87c4f68bd8f4734b5fcf1..35826081dc3fc2f17fd7ceaf25c2c014ae623304 100644
--- a/content/browser/utility_process_host.cc
+++ b/content/browser/utility_process_host.cc
@@ -108,11 +108,13 @@ const ChildProcessData& UtilityProcessHost::GetData() {
return process_->GetData();
}
-#if BUILDFLAG(IS_POSIX)
void UtilityProcessHost::SetEnv(const base::EnvironmentMap& env) {
env_ = env;
}
-#endif
+
+void UtilityProcessHost::ClearEnvironment() {
+ inherit_environment_ = false;
+}
bool UtilityProcessHost::Start() {
return StartProcess();
@@ -153,6 +155,24 @@ void UtilityProcessHost::SetExtraCommandLineSwitches(
extra_switches_ = std::move(switches);
}
+#if BUILDFLAG(IS_WIN)
+void UtilityProcessHost::SetStdioHandles(
+ base::win::ScopedHandle stdout_handle,
+ base::win::ScopedHandle stderr_handle) {
+ stdout_handle_ = std::move(stdout_handle);
+ stderr_handle_ = std::move(stderr_handle);
+}
+#elif BUILDFLAG(IS_POSIX)
+void UtilityProcessHost::SetAdditionalFds(base::FileHandleMappingVector mapping) {
+ fds_to_remap_ = std::move(mapping);
+}
+#endif
+
+void UtilityProcessHost::SetCurrentDirectory(
+ const base::FilePath& cwd) {
+ current_directory_ = cwd;
+}
+
mojom::ChildProcess* UtilityProcessHost::GetChildProcess() {
return static_cast<ChildProcessHostImpl*>(process_->GetHost())
->child_process();
@@ -356,9 +376,22 @@ bool UtilityProcessHost::StartProcess() {
}
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
+#if BUILDFLAG(IS_WIN)
+ file_data->stdout_handle = std::move(stdout_handle_);
+ file_data->stderr_handle = std::move(stderr_handle_);
+#elif BUILDFLAG(IS_POSIX)
+ if (!fds_to_remap_.empty()) {
+ for (const auto& remapped_fd : fds_to_remap_) {
+ file_data->additional_remapped_fds.emplace(
+ remapped_fd.second, remapped_fd.first);
+ }
+ }
+#endif
+
std::unique_ptr<UtilitySandboxedProcessLauncherDelegate> delegate =
std::make_unique<UtilitySandboxedProcessLauncherDelegate>(
- sandbox_type_, env_, *cmd_line);
+ sandbox_type_, env_, current_directory_, *cmd_line,
+ inherit_environment_);
process_->LaunchWithFileData(std::move(delegate), std::move(cmd_line),
std::move(file_data), true);
diff --git a/content/browser/utility_process_host.h b/content/browser/utility_process_host.h
index 13de4795df7731f27760901aff17c143008a72c1..3b8af456d86e7aaf3b57e6b039c7f444e1c9e5fe 100644
--- a/content/browser/utility_process_host.h
+++ b/content/browser/utility_process_host.h
@@ -29,6 +29,10 @@
#include "mojo/public/cpp/system/message_pipe.h"
#endif
+#if BUILDFLAG(IS_WIN)
+#include "base/win/scoped_handle.h"
+#endif
+
namespace base {
class Thread;
} // namespace base
@@ -87,9 +91,13 @@ class CONTENT_EXPORT UtilityProcessHost
// Returns information about the utility child process.
const ChildProcessData& GetData();
-#if BUILDFLAG(IS_POSIX)
+
+ // Set/Unset environment variables.
void SetEnv(const base::EnvironmentMap& env);
-#endif
+
+ // Clear the environment for the new process before processing
+ // changes from SetEnv.
+ void ClearEnvironment();
// Starts the utility process.
bool Start();
@@ -118,6 +126,16 @@ class CONTENT_EXPORT UtilityProcessHost
// Provides extra switches to append to the process's command line.
void SetExtraCommandLineSwitches(std::vector<std::string> switches);
+#if BUILDFLAG(IS_WIN)
+ void SetStdioHandles(base::win::ScopedHandle stdout_handle,
+ base::win::ScopedHandle stderr_handle);
+#elif BUILDFLAG(IS_POSIX)
+ void SetAdditionalFds(base::FileHandleMappingVector mapping);
+#endif
+
+ // Sets the working directory of the process.
+ void SetCurrentDirectory(const base::FilePath& cwd);
+
// Returns a control interface for the running child process.
mojom::ChildProcess* GetChildProcess();
@@ -159,6 +177,22 @@ class CONTENT_EXPORT UtilityProcessHost
// Extra command line switches to append.
std::vector<std::string> extra_switches_;
+#if BUILDFLAG(IS_WIN)
+ // Specifies the handles for redirection of stdout and stderr.
+ base::win::ScopedHandle stdout_handle_;
+ base::win::ScopedHandle stderr_handle_;
+#elif BUILDFLAG(IS_POSIX)
+ // Specifies file descriptors to propagate into the child process
+ // based on the mapping.
+ base::FileHandleMappingVector fds_to_remap_;
+#endif
+
+ // If not empty, change to this directory before executing the new process.
+ base::FilePath current_directory_;
+
+ // Inherit enviroment from parent process.
+ bool inherit_environment_ = true;
+
// Indicates whether the process has been successfully launched yet, or if
// launch failed.
enum class LaunchState {
diff --git a/content/browser/utility_sandbox_delegate.cc b/content/browser/utility_sandbox_delegate.cc
index 070ee151ee96baa771cec6fe4de9f8762eff91bc..d7621b234e45f94a2ca8bc79f25345025b3bc48a 100644
--- a/content/browser/utility_sandbox_delegate.cc
+++ b/content/browser/utility_sandbox_delegate.cc
@@ -29,13 +29,15 @@ UtilitySandboxedProcessLauncherDelegate::
UtilitySandboxedProcessLauncherDelegate(
sandbox::mojom::Sandbox sandbox_type,
const base::EnvironmentMap& env,
- const base::CommandLine& cmd_line)
+ const base::FilePath& cwd,
+ const base::CommandLine& cmd_line,
+ bool inherit_environment)
:
-#if BUILDFLAG(IS_POSIX)
env_(env),
-#endif
+ current_directory_(cwd),
sandbox_type_(sandbox_type),
- cmd_line_(cmd_line) {
+ cmd_line_(cmd_line),
+ inherit_environment_(inherit_environment) {
#if DCHECK_IS_ON()
bool supported_sandbox_type =
sandbox_type_ == sandbox::mojom::Sandbox::kNoSandbox ||
@@ -93,11 +95,17 @@ UtilitySandboxedProcessLauncherDelegate::GetSandboxType() {
return sandbox_type_;
}
-#if BUILDFLAG(IS_POSIX)
base::EnvironmentMap UtilitySandboxedProcessLauncherDelegate::GetEnvironment() {
return env_;
}
-#endif // BUILDFLAG(IS_POSIX)
+
+bool UtilitySandboxedProcessLauncherDelegate::ShouldInheritEnvironment() {
+ return inherit_environment_;
+}
+
+base::FilePath UtilitySandboxedProcessLauncherDelegate::GetCurrentDirectory() {
+ return current_directory_;
+}
#if BUILDFLAG(USE_ZYGOTE_HANDLE)
ZygoteHandle UtilitySandboxedProcessLauncherDelegate::GetZygote() {
diff --git a/content/browser/utility_sandbox_delegate.h b/content/browser/utility_sandbox_delegate.h
index 41d93b41e7fff8ba4a7138d05035e4bc24b7a85b..20cb410fc71994e26cff6ac9801d42ebd11d9fee 100644
--- a/content/browser/utility_sandbox_delegate.h
+++ b/content/browser/utility_sandbox_delegate.h
@@ -26,7 +26,9 @@ class UtilitySandboxedProcessLauncherDelegate
public:
UtilitySandboxedProcessLauncherDelegate(sandbox::mojom::Sandbox sandbox_type,
const base::EnvironmentMap& env,
- const base::CommandLine& cmd_line);
+ const base::FilePath& cwd,
+ const base::CommandLine& cmd_line,
+ bool inherit_environment);
~UtilitySandboxedProcessLauncherDelegate() override;
sandbox::mojom::Sandbox GetSandboxType() override;
@@ -45,16 +47,16 @@ class UtilitySandboxedProcessLauncherDelegate
ZygoteHandle GetZygote() override;
#endif // BUILDFLAG(USE_ZYGOTE_HANDLE)
-#if BUILDFLAG(IS_POSIX)
base::EnvironmentMap GetEnvironment() override;
-#endif // BUILDFLAG(IS_POSIX)
+ bool ShouldInheritEnvironment() override;
+ base::FilePath GetCurrentDirectory() override;
private:
-#if BUILDFLAG(IS_POSIX)
base::EnvironmentMap env_;
-#endif // BUILDFLAG(IS_POSIX)
+ base::FilePath current_directory_;
sandbox::mojom::Sandbox sandbox_type_;
base::CommandLine cmd_line_;
+ bool inherit_environment_;
};
} // namespace content
diff --git a/content/common/sandbox_init_win.cc b/content/common/sandbox_init_win.cc
index 498f60227d13eb2e476413f88eaa58cc0babf461..b2d7a009477293bf73f3ae4a0c8452d1b1bf1dd8 100644
--- a/content/common/sandbox_init_win.cc
+++ b/content/common/sandbox_init_win.cc
@@ -23,7 +23,7 @@ namespace content {
sandbox::ResultCode StartSandboxedProcess(
SandboxedProcessLauncherDelegate* delegate,
const base::CommandLine& target_command_line,
- const base::HandlesToInheritVector& handles_to_inherit,
+ const base::LaunchOptions& options,
base::Process* process) {
std::string type_str =
target_command_line.GetSwitchValueASCII(switches::kProcessType);
@@ -45,7 +45,7 @@ sandbox::ResultCode StartSandboxedProcess(
}
return sandbox::policy::SandboxWin::StartSandboxedProcess(
- full_command_line, type_str, handles_to_inherit, delegate, process);
+ full_command_line, type_str, options, delegate, process);
}
} // namespace content
diff --git a/content/public/browser/service_process_host.cc b/content/public/browser/service_process_host.cc
index 6d25170e3badb65745c7dbea9c9664bdf8c91b0e..df79ba6137c8a9264ba32e4f9e1c1d7893e8f38a 100644
--- a/content/public/browser/service_process_host.cc
+++ b/content/public/browser/service_process_host.cc
@@ -46,12 +46,45 @@ ServiceProcessHost::Options::WithExtraCommandLineSwitches(
return *this;
}
+#if BUILDFLAG(IS_WIN)
+ServiceProcessHost::Options& ServiceProcessHost::Options::WithStdoutHandle(
+ base::win::ScopedHandle handle) {
+ stdout_handle = std::move(handle);
+ return *this;
+}
+
+ServiceProcessHost::Options& ServiceProcessHost::Options::WithStderrHandle(
+ base::win::ScopedHandle handle) {
+ stderr_handle = std::move(handle);
+ return *this;
+}
+#elif BUILDFLAG(IS_POSIX)
+ServiceProcessHost::Options& ServiceProcessHost::Options::WithAdditionalFds(
+ base::FileHandleMappingVector mapping) {
+ fds_to_remap = std::move(mapping);
+ return *this;
+}
+#endif
+
ServiceProcessHost::Options& ServiceProcessHost::Options::WithProcessCallback(
base::OnceCallback<void(const base::Process&)> callback) {
process_callback = std::move(callback);
return *this;
}
+ServiceProcessHost::Options& ServiceProcessHost::Options::WithCurrentDirectory(
+ const base::FilePath& cwd) {
+ current_directory = cwd;
+ return *this;
+}
+
+ServiceProcessHost::Options& ServiceProcessHost::Options::WithEnvironment(
+ const base::EnvironmentMap& env, bool new_environment) {
+ environment = env;
+ clear_environment = new_environment;
+ return *this;
+}
+
ServiceProcessHost::Options ServiceProcessHost::Options::Pass() {
return std::move(*this);
}
diff --git a/content/public/browser/service_process_host.h b/content/public/browser/service_process_host.h
index a308d46612c1b30163cf9988117d2224a43ab5ad..5a41c3c907c0f0cf42759c52e7493cbf675f6fa6 100644
--- a/content/public/browser/service_process_host.h
+++ b/content/public/browser/service_process_host.h
@@ -13,6 +13,7 @@
#include "base/callback.h"
#include "base/command_line.h"
#include "base/observer_list_types.h"
+#include "base/process/launch.h"
#include "base/process/process_handle.h"
#include "base/strings/string_piece.h"
#include "build/chromecast_buildflags.h"
@@ -29,6 +30,10 @@
#include "mojo/public/cpp/system/message_pipe.h"
#endif
+#if BUILDFLAG(IS_WIN)
+#include "base/win/scoped_handle.h"
+#endif
+
namespace base {
class Process;
} // namespace base
@@ -88,11 +93,30 @@ class CONTENT_EXPORT ServiceProcessHost {
// Specifies extra command line switches to append before launch.
Options& WithExtraCommandLineSwitches(std::vector<std::string> switches);
+#if BUILDFLAG(IS_WIN)
+ // Specifies the handles for redirection of stdout and stderr.
+ Options& WithStdoutHandle(base::win::ScopedHandle stdout_handle);
+ Options& WithStderrHandle(base::win::ScopedHandle stderr_handle);
+#elif BUILDFLAG(IS_POSIX)
+ // Specifies file descriptors to propagate into the child process
+ // based on the mapping.
+ Options& WithAdditionalFds(base::FileHandleMappingVector mapping);
+#endif
+
// Specifies a callback to be invoked with service process once it's
// launched. Will be on UI thread.
Options& WithProcessCallback(
base::OnceCallback<void(const base::Process&)>);
+ // Specifies the working directory for the launched process.
+ Options& WithCurrentDirectory(const base::FilePath& cwd);
+
+ // Specifies the environment that should be applied to the process.
+ // |new_environment| controls whether the process should inherit
+ // environment from the parent process.
+ Options& WithEnvironment(const base::EnvironmentMap& environment,
+ bool new_environment);
+
// Passes the contents of this Options object to a newly returned Options
// value. This must be called when moving a built Options object into a call
// to |Launch()|.
@@ -101,7 +125,16 @@ class CONTENT_EXPORT ServiceProcessHost {
std::u16string display_name;
absl::optional<int> child_flags;
std::vector<std::string> extra_switches;
+#if BUILDFLAG(IS_WIN)
+ base::win::ScopedHandle stdout_handle;
+ base::win::ScopedHandle stderr_handle;
+#elif BUILDFLAG(IS_POSIX)
+ base::FileHandleMappingVector fds_to_remap;
+#endif
base::OnceCallback<void(const base::Process&)> process_callback;
+ base::FilePath current_directory;
+ base::EnvironmentMap environment;
+ bool clear_environment = false;
};
// An interface which can be implemented and registered/unregistered with
diff --git a/content/public/common/sandbox_init_win.h b/content/public/common/sandbox_init_win.h
index 9bb4b30ba0f5d37ec2b28f0848d94f34c24f9423..c19cceae4215d74ae74f6e6005125f326453f955 100644
--- a/content/public/common/sandbox_init_win.h
+++ b/content/public/common/sandbox_init_win.h
@@ -29,7 +29,7 @@ class SandboxedProcessLauncherDelegate;
CONTENT_EXPORT sandbox::ResultCode StartSandboxedProcess(
SandboxedProcessLauncherDelegate* delegate,
const base::CommandLine& target_command_line,
- const base::HandlesToInheritVector& handles_to_inherit,
+ const base::LaunchOptions& options,
base::Process* process);
} // namespace content
diff --git a/content/public/common/sandboxed_process_launcher_delegate.cc b/content/public/common/sandboxed_process_launcher_delegate.cc
index ee7cdddba192f151346b74b68ef1eabe5f46e84a..4378d5ac7f455eb54f9f39364184649d7a63666f 100644
--- a/content/public/common/sandboxed_process_launcher_delegate.cc
+++ b/content/public/common/sandboxed_process_launcher_delegate.cc
@@ -53,11 +53,17 @@ ZygoteHandle SandboxedProcessLauncherDelegate::GetZygote() {
}
#endif // BUILDFLAG(USE_ZYGOTE_HANDLE)
-#if BUILDFLAG(IS_POSIX)
base::EnvironmentMap SandboxedProcessLauncherDelegate::GetEnvironment() {
return base::EnvironmentMap();
}
-#endif // BUILDFLAG(IS_POSIX)
+
+bool SandboxedProcessLauncherDelegate::ShouldInheritEnvironment() {
+ return true;
+}
+
+base::FilePath SandboxedProcessLauncherDelegate::GetCurrentDirectory() {
+ return base::FilePath();
+}
#if BUILDFLAG(IS_MAC)
diff --git a/content/public/common/sandboxed_process_launcher_delegate.h b/content/public/common/sandboxed_process_launcher_delegate.h
index 1e8f3994764a2b4e4efb87a08c522cc0e0103e18..83cc16ffbf484aa78b1c350d20a5a15ffd0dd0e8 100644
--- a/content/public/common/sandboxed_process_launcher_delegate.h
+++ b/content/public/common/sandboxed_process_launcher_delegate.h
@@ -6,6 +6,7 @@
#define CONTENT_PUBLIC_COMMON_SANDBOXED_PROCESS_LAUNCHER_DELEGATE_H_
#include "base/environment.h"
+#include "base/files/file_path.h"
#include "base/files/scoped_file.h"
#include "base/process/process.h"
#include "build/build_config.h"
@@ -48,10 +49,14 @@ class CONTENT_EXPORT SandboxedProcessLauncherDelegate
virtual ZygoteHandle GetZygote();
#endif // BUILDFLAG(USE_ZYGOTE_HANDLE)
-#if BUILDFLAG(IS_POSIX)
// Override this if the process needs a non-empty environment map.
virtual base::EnvironmentMap GetEnvironment();
-#endif // BUILDFLAG(IS_POSIX)
+
+ // Override this if the process should not inherit parent environment.
+ virtual bool ShouldInheritEnvironment();
+
+ // Specifies the directory to change to before executing the process.
+ virtual base::FilePath GetCurrentDirectory();
#if BUILDFLAG(IS_MAC)
// Whether or not to disclaim TCC responsibility for the process, defaults to
diff --git a/sandbox/policy/win/sandbox_win.cc b/sandbox/policy/win/sandbox_win.cc
index 2191f51de17cfde5bb39f8231c8210dea6aa4fdd..6239f68771832d245d7270fd83e04f4fdce44032 100644
--- a/sandbox/policy/win/sandbox_win.cc
+++ b/sandbox/policy/win/sandbox_win.cc
@@ -851,11 +851,9 @@ ResultCode GenerateConfigForSandboxedProcess(const base::CommandLine& cmd_line,
// command line flag.
ResultCode LaunchWithoutSandbox(
const base::CommandLine& cmd_line,
- const base::HandlesToInheritVector& handles_to_inherit,
+ base::LaunchOptions options,
SandboxDelegate* delegate,
base::Process* process) {
- base::LaunchOptions options;
- options.handles_to_inherit = handles_to_inherit;
// Network process runs in a job even when unsandboxed. This is to ensure it
// does not outlive the browser, which could happen if there is a lot of I/O
// on process shutdown, in which case TerminateProcess can fail. See
@@ -1091,7 +1089,7 @@ bool SandboxWin::InitTargetServices(TargetServices* target_services) {
ResultCode SandboxWin::GeneratePolicyForSandboxedProcess(
const base::CommandLine& cmd_line,
const std::string& process_type,
- const base::HandlesToInheritVector& handles_to_inherit,
+ const base::LaunchOptions& options,
SandboxDelegate* delegate,
TargetPolicy* policy) {
const base::CommandLine& launcher_process_command_line =
@@ -1105,7 +1103,7 @@ ResultCode SandboxWin::GeneratePolicyForSandboxedProcess(
}
// Add any handles to be inherited to the policy.
- for (HANDLE handle : handles_to_inherit)
+ for (HANDLE handle : options.handles_to_inherit)
policy->AddHandleToShare(handle);
if (!policy->GetConfig()->IsConfigured()) {
@@ -1120,6 +1118,13 @@ ResultCode SandboxWin::GeneratePolicyForSandboxedProcess(
// have no effect. These calls can fail with SBOX_ERROR_BAD_PARAMS.
policy->SetStdoutHandle(GetStdHandle(STD_OUTPUT_HANDLE));
policy->SetStderrHandle(GetStdHandle(STD_ERROR_HANDLE));
+#else
+ if (options.stdout_handle != nullptr && options.stdout_handle != INVALID_HANDLE_VALUE) {
+ policy->SetStdoutHandle(options.stdout_handle);
+ }
+ if (options.stderr_handle != nullptr && options.stderr_handle != INVALID_HANDLE_VALUE) {
+ policy->SetStderrHandle(options.stderr_handle);
+ }
#endif
if (!delegate->PreSpawnTarget(policy))
@@ -1132,7 +1137,7 @@ ResultCode SandboxWin::GeneratePolicyForSandboxedProcess(
ResultCode SandboxWin::StartSandboxedProcess(
const base::CommandLine& cmd_line,
const std::string& process_type,
- const base::HandlesToInheritVector& handles_to_inherit,
+ const base::LaunchOptions& options,
SandboxDelegate* delegate,
base::Process* process) {
const base::ElapsedTimer timer;
@@ -1140,7 +1145,7 @@ ResultCode SandboxWin::StartSandboxedProcess(
// Avoid making a policy if we won't use it.
if (IsUnsandboxedProcess(delegate->GetSandboxType(), cmd_line,
*base::CommandLine::ForCurrentProcess())) {
- return LaunchWithoutSandbox(cmd_line, handles_to_inherit, delegate,
+ return LaunchWithoutSandbox(cmd_line, options, delegate,
process);
}
@@ -1151,7 +1156,7 @@ ResultCode SandboxWin::StartSandboxedProcess(
auto policy = g_broker_services->CreatePolicy(tag);
auto time_policy_created = timer.Elapsed();
ResultCode result = GeneratePolicyForSandboxedProcess(
- cmd_line, process_type, handles_to_inherit, delegate, policy.get());
+ cmd_line, process_type, options, delegate, policy.get());
if (SBOX_ALL_OK != result)
return result;
auto time_policy_generated = timer.Elapsed();
diff --git a/sandbox/policy/win/sandbox_win.h b/sandbox/policy/win/sandbox_win.h
index d1adadc10de3053f69fde39387d196054a96beda..0111a9c4becca009f17a3839d4d4bef3d9d880b8 100644
--- a/sandbox/policy/win/sandbox_win.h
+++ b/sandbox/policy/win/sandbox_win.h
@@ -50,7 +50,7 @@ class SANDBOX_POLICY_EXPORT SandboxWin {
static ResultCode StartSandboxedProcess(
const base::CommandLine& cmd_line,
const std::string& process_type,
- const base::HandlesToInheritVector& handles_to_inherit,
+ const base::LaunchOptions& options,
SandboxDelegate* delegate,
base::Process* process);
@@ -64,7 +64,7 @@ class SANDBOX_POLICY_EXPORT SandboxWin {
static ResultCode GeneratePolicyForSandboxedProcess(
const base::CommandLine& cmd_line,
const std::string& process_type,
- const base::HandlesToInheritVector& handles_to_inherit,
+ const base::LaunchOptions& options,
SandboxDelegate* delegate,
TargetPolicy* policy);

View File

@@ -40,6 +40,10 @@ const main = async () => {
{
name: 'asar_bundle_deps',
config: 'webpack.config.asar.js'
},
{
name: 'utility_bundle_deps',
config: 'webpack.config.utility.js'
}
];

View File

@@ -3,8 +3,7 @@ const path = require('path');
const semver = require('semver');
const outputPath = process.argv[2];
const currentVersion = fs.readFileSync(path.resolve(__dirname, '../ELECTRON_VERSION'), 'utf8').trim();
const currentVersion = process.argv[3];
const parsed = semver.parse(currentVersion);
@@ -20,9 +19,11 @@ const {
} = parsed;
fs.writeFileSync(outputPath, JSON.stringify({
full_version: currentVersion,
major,
minor,
patch,
prerelease,
prerelease_number: prerelease ? parsed.prerelease[parsed.prerelease.length - 1] : '0',
has_prerelease: prerelease === '' ? 0 : 1
}, null, 2));

View File

@@ -1,4 +1,4 @@
const asar = require('asar');
const asar = require('@electron/asar');
const crypto = require('crypto');
const fs = require('fs');

View File

@@ -1,4 +1,4 @@
const asar = require('asar');
const asar = require('@electron/asar');
const assert = require('assert');
const fs = require('fs-extra');
const os = require('os');

22
script/lib/get-version.js Normal file
View File

@@ -0,0 +1,22 @@
const { spawnSync } = require('child_process');
const path = require('path');
module.exports.getElectronVersion = () => {
// Find the nearest tag to the current HEAD
// This is equivilant to our old logic of "use a value in package.json" for the following reasons
//
// 1. Whenever we updated the package.json we ALSO pushed a tag with the same version
// 2. Whenever we _reverted_ a bump all we actually did was push a commit that deleted the tag and changed the version number back
//
// The only difference in the "git describe" technique is that technically a commit can "change" it's version
// number if a tag is created / removed retroactively. i.e. the first time a commit is pushed it will be 1.2.3
// and after the tag is made rebuilding the same commit will result in it being 1.2.4
const output = spawnSync('git', ['describe', '--tags', '--abbrev=0'], {
cwd: path.resolve(__dirname, '..', '..')
});
if (output.status !== 0) {
console.error(output.stderr);
throw new Error('Failed to get current electron version');
}
return output.stdout.toString().trim();
};

View File

@@ -15,7 +15,9 @@ except ImportError:
from urllib2 import urlopen
import zipfile
from lib.config import is_verbose_mode
# from lib.config import is_verbose_mode
def is_verbose_mode():
return False
ELECTRON_DIR = os.path.abspath(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
@@ -149,11 +151,17 @@ def get_electron_branding():
with open(branding_file_path) as f:
return json.load(f)
cached_electron_version = None
def get_electron_version():
SOURCE_ROOT = os.path.abspath(os.path.join(__file__, '..', '..', '..'))
version_file = os.path.join(SOURCE_ROOT, 'ELECTRON_VERSION')
with open(version_file) as f:
return 'v' + f.read().strip()
global cached_electron_version
if cached_electron_version is None:
cached_electron_version = str.strip(execute([
'node',
'-p',
'require("./script/lib/get-version").getElectronVersion()'
], cwd=ELECTRON_DIR).decode())
return cached_electron_version
def store_artifact(prefix, key_prefix, files):
# Azure Storage

View File

@@ -67,6 +67,8 @@ async function main () {
console.error(`Found ${missing.length} missing disabled specs: \n${missing.join('\n')}`);
process.exit(1);
}
process.exit(0);
}
const options = args.default ? defaultOptions : getCustomOptions();

3
script/print-version.py Normal file
View File

@@ -0,0 +1,3 @@
from lib.util import get_electron_version
print(get_electron_version())

View File

@@ -6,6 +6,7 @@ const got = require('got');
const semver = require('semver');
const { getCurrentBranch, ELECTRON_DIR } = require('../lib/utils');
const { getElectronVersion } = require('../lib/get-version');
const rootPackageJson = require('../../package.json');
const { Octokit } = require('@octokit/rest');
@@ -34,7 +35,6 @@ const files = [
const jsonFields = [
'name',
'version',
'repository',
'description',
'license',
@@ -44,6 +44,9 @@ const jsonFields = [
let npmTag = '';
const currentElectronVersion = getElectronVersion();
const isNightlyElectronVersion = currentElectronVersion.includes('nightly');
new Promise((resolve, reject) => {
temp.mkdir('electron-npm', (err, dirPath) => {
if (err) {
@@ -68,6 +71,7 @@ new Promise((resolve, reject) => {
jsonFields.forEach((fieldName) => {
packageJson[fieldName] = rootPackageJson[fieldName];
});
packageJson.version = currentElectronVersion;
fs.writeFileSync(
path.join(tempDir, 'package.json'),
JSON.stringify(packageJson, null, 2)
@@ -75,27 +79,27 @@ new Promise((resolve, reject) => {
return octokit.repos.listReleases({
owner: 'electron',
repo: rootPackageJson.version.indexOf('nightly') > 0 ? 'nightlies' : 'electron'
repo: isNightlyElectronVersion ? 'nightlies' : 'electron'
});
})
.then((releases) => {
// download electron.d.ts from release
const release = releases.data.find(
(release) => release.tag_name === `v${rootPackageJson.version}`
(release) => release.tag_name === `v${currentElectronVersion}`
);
if (!release) {
throw new Error(`cannot find release with tag v${rootPackageJson.version}`);
throw new Error(`cannot find release with tag v${currentElectronVersion}`);
}
return release;
})
.then(async (release) => {
const tsdAsset = release.assets.find((asset) => asset.name === 'electron.d.ts');
if (!tsdAsset) {
throw new Error(`cannot find electron.d.ts from v${rootPackageJson.version} release assets`);
throw new Error(`cannot find electron.d.ts from v${currentElectronVersion} release assets`);
}
const typingsContent = await getAssetContents(
rootPackageJson.version.indexOf('nightly') > 0 ? 'nightlies' : 'electron',
isNightlyElectronVersion ? 'nightlies' : 'electron',
tsdAsset.id
);
@@ -106,11 +110,11 @@ new Promise((resolve, reject) => {
.then(async (release) => {
const checksumsAsset = release.assets.find((asset) => asset.name === 'SHASUMS256.txt');
if (!checksumsAsset) {
throw new Error(`cannot find SHASUMS256.txt from v${rootPackageJson.version} release assets`);
throw new Error(`cannot find SHASUMS256.txt from v${currentElectronVersion} release assets`);
}
const checksumsContent = await getAssetContents(
rootPackageJson.version.indexOf('nightly') > 0 ? 'nightlies' : 'electron',
isNightlyElectronVersion ? 'nightlies' : 'electron',
checksumsAsset.id
);
@@ -127,7 +131,7 @@ new Promise((resolve, reject) => {
.then(async (release) => {
const currentBranch = await getCurrentBranch();
if (release.tag_name.indexOf('nightly') > 0) {
if (isNightlyElectronVersion) {
// TODO(main-migration): Simplify once main branch is renamed.
if (currentBranch === 'master' || currentBranch === 'main') {
// Nightlies get published to their own module, so they should be tagged as latest
@@ -164,7 +168,7 @@ new Promise((resolve, reject) => {
.then(() => childProcess.execSync('npm pack', { cwd: tempDir }))
.then(() => {
// test that the package can install electron prebuilt from github release
const tarballPath = path.join(tempDir, `${rootPackageJson.name}-${rootPackageJson.version}.tgz`);
const tarballPath = path.join(tempDir, `${rootPackageJson.name}-${currentElectronVersion}.tgz`);
return new Promise((resolve, reject) => {
const result = childProcess.spawnSync('npm', ['install', tarballPath, '--force', '--silent'], {
env: { ...process.env, electron_config_cache: tempDir },
@@ -190,7 +194,7 @@ new Promise((resolve, reject) => {
});
})
.then((tarballPath) => {
const existingVersionJSON = childProcess.execSync(`npm view electron@${rootPackageJson.version} --json`).toString('utf-8');
const existingVersionJSON = childProcess.execSync(`npm view electron@${currentElectronVersion} --json`).toString('utf-8');
// It's possible this is a re-run and we already have published the package, if not we just publish like normal
if (!existingVersionJSON) {
childProcess.execSync(`npm publish ${tarballPath} --tag ${npmTag} --otp=${process.env.ELECTRON_NPM_OTP}`);
@@ -198,22 +202,21 @@ new Promise((resolve, reject) => {
})
.then(() => {
const currentTags = JSON.parse(childProcess.execSync('npm show electron dist-tags --json').toString());
const localVersion = rootPackageJson.version;
const parsedLocalVersion = semver.parse(localVersion);
const parsedLocalVersion = semver.parse(currentElectronVersion);
if (rootPackageJson.name === 'electron') {
// We should only customly add dist tags for non-nightly releases where the package name is still
// "electron"
if (parsedLocalVersion.prerelease.length === 0 &&
semver.gt(localVersion, currentTags.latest)) {
childProcess.execSync(`npm dist-tag add electron@${localVersion} latest --otp=${process.env.ELECTRON_NPM_OTP}`);
semver.gt(currentElectronVersion, currentTags.latest)) {
childProcess.execSync(`npm dist-tag add electron@${currentElectronVersion} latest --otp=${process.env.ELECTRON_NPM_OTP}`);
}
if (parsedLocalVersion.prerelease[0] === 'beta' &&
semver.gt(localVersion, currentTags.beta)) {
childProcess.execSync(`npm dist-tag add electron@${localVersion} beta --otp=${process.env.ELECTRON_NPM_OTP}`);
semver.gt(currentElectronVersion, currentTags.beta)) {
childProcess.execSync(`npm dist-tag add electron@${currentElectronVersion} 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}`);
semver.gt(currentElectronVersion, currentTags.alpha)) {
childProcess.execSync(`npm dist-tag add electron@${currentElectronVersion} alpha --otp=${process.env.ELECTRON_NPM_OTP}`);
}
}
})

View File

@@ -18,26 +18,6 @@ require('colors');
const pass = '✓'.green;
const fail = '✗'.red;
function getLastBumpCommit (tag) {
const data = execSync(`git log -n1 --grep "Bump ${tag}" --format='format:{"hash": "%H", "message": "%s"}'`).toString();
return JSON.parse(data);
}
async function revertBumpCommit (tag) {
const branch = await getCurrentBranch();
const commitToRevert = getLastBumpCommit(tag).hash;
await GitProcess.exec(['pull', '--rebase']);
await GitProcess.exec(['revert', commitToRevert], ELECTRON_DIR);
const pushDetails = await GitProcess.exec(['push', 'origin', `HEAD:${branch}`, '--follow-tags'], ELECTRON_DIR);
if (pushDetails.exitCode === 0) {
console.log(`${pass} successfully reverted release commit.`);
} else {
const error = GitProcess.parseError(pushDetails.stderr);
console.error(`${fail} could not push release commit: `, error);
process.exit(1);
}
}
async function deleteDraft (releaseId, targetRepo) {
try {
const result = await octokit.repos.getRelease({
@@ -80,9 +60,6 @@ async function cleanReleaseArtifacts () {
const releaseId = args.releaseID.length > 0 ? args.releaseID : null;
const isNightly = args.tag.includes('nightly');
// try to revert commit regardless of tag and draft deletion status
await revertBumpCommit(args.tag);
if (releaseId) {
if (isNightly) {
await deleteDraft(releaseId, 'nightlies');

View File

@@ -12,8 +12,6 @@ const args = require('minimist')(process.argv.slice(2), {
const fs = require('fs');
const { execSync } = require('child_process');
const got = require('got');
const pkg = require('../../package.json');
const pkgVersion = `v${pkg.version}`;
const path = require('path');
const temp = require('temp').track();
const { URL } = require('url');
@@ -25,8 +23,11 @@ const pass = '✓'.green;
const fail = '✗'.red;
const { ELECTRON_DIR } = require('../lib/utils');
const { getElectronVersion } = require('../lib/get-version');
const getUrlHash = require('./get-url-hash');
const pkgVersion = `v${getElectronVersion()}`;
const octokit = new Octokit({
auth: process.env.ELECTRON_GITHUB_TOKEN
});

View File

@@ -1,17 +1,10 @@
#!/usr/bin/env node
const { GitProcess } = require('dugite');
const { promises: fs } = require('fs');
const semver = require('semver');
const path = require('path');
const minimist = require('minimist');
const { ELECTRON_DIR } = require('../lib/utils');
const { getElectronVersion } = require('../lib/get-version');
const versionUtils = require('./version-utils');
const supported = path.resolve(ELECTRON_DIR, 'docs', 'tutorial', 'support.md');
const writeFile = fs.writeFile;
const readFile = fs.readFile;
function parseCommandLine () {
let help;
@@ -37,7 +30,7 @@ function parseCommandLine () {
// run the script
async function main () {
const opts = parseCommandLine();
const currentVersion = await versionUtils.getElectronVersion();
const currentVersion = getElectronVersion();
const version = await nextVersion(opts.bump, currentVersion);
const parsed = semver.parse(version);
@@ -54,20 +47,6 @@ async function main () {
return 0;
}
if (shouldUpdateSupported(opts.bump, currentVersion, version)) {
await updateSupported(version, supported);
}
// update all version-related files
await Promise.all([
updateVersion(version),
updatePackageJSON(version),
updateWinRC(components)
]);
// commit all updated version-related files
await commitVersionBump(version);
console.log(`Bumped to version: ${version}`);
}
@@ -118,10 +97,6 @@ async function nextVersion (bumpType, version) {
return version;
}
function shouldUpdateSupported (bump, current, version) {
return isMajorStable(bump, current) || isMajorNightly(version, current);
}
function isMajorStable (bump, currentVersion) {
if (versionUtils.isBeta(currentVersion) && (bump === 'stable')) return true;
return false;
@@ -134,59 +109,6 @@ function isMajorNightly (version, currentVersion) {
return false;
}
// update VERSION file with latest release info
async function updateVersion (version) {
const versionPath = path.resolve(ELECTRON_DIR, 'ELECTRON_VERSION');
await writeFile(versionPath, version, 'utf8');
}
// update package metadata files with new version
async function updatePackageJSON (version) {
const filePath = path.resolve(ELECTRON_DIR, 'package.json');
const file = require(filePath);
file.version = version;
await writeFile(filePath, JSON.stringify(file, null, 2));
}
// push bump commit to release branch
async function commitVersionBump (version) {
const gitArgs = ['commit', '-a', '-m', `Bump v${version}`, '-n'];
await GitProcess.exec(gitArgs, ELECTRON_DIR);
}
// updates electron.rc file with new semver values
async function updateWinRC (components) {
const filePath = path.resolve(ELECTRON_DIR, 'shell', 'browser', 'resources', 'win', 'electron.rc');
const data = await readFile(filePath, 'utf8');
const arr = data.split('\n');
arr.forEach((line, idx) => {
if (line.includes('FILEVERSION')) {
arr[idx] = ` FILEVERSION ${versionUtils.makeVersion(components, ',', versionUtils.preType.PARTIAL)}`;
arr[idx + 1] = ` PRODUCTVERSION ${versionUtils.makeVersion(components, ',', versionUtils.preType.PARTIAL)}`;
} else if (line.includes('FileVersion')) {
arr[idx] = ` VALUE "FileVersion", "${versionUtils.makeVersion(components, '.')}"`;
arr[idx + 5] = ` VALUE "ProductVersion", "${versionUtils.makeVersion(components, '.')}"`;
}
});
await writeFile(filePath, arr.join('\n'));
}
// 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`, `* ${v - 3}.x.y`];
const contents = await readFile(filePath, 'utf8');
const previousVersions = contents.split('\n').filter((elem) => {
return (/[^\n]*\.x\.y[^\n]*/).test(elem);
}, []);
const newContents = previousVersions.reduce((contents, current, i) => {
return contents.replace(current, newVersions[i]);
}, contents);
await writeFile(filePath, newContents, 'utf8');
}
if (process.mainModule === module) {
main().catch((error) => {
console.error(error);
@@ -194,4 +116,4 @@ if (process.mainModule === module) {
});
}
module.exports = { nextVersion, shouldUpdateSupported, updateSupported };
module.exports = { nextVersion };

View File

@@ -68,12 +68,6 @@ async function nextBeta (v) {
return tags.length === 0 ? `${next}-beta.1` : semver.inc(tags.pop(), 'prerelease');
}
async function getElectronVersion () {
const versionPath = path.resolve(ELECTRON_DIR, 'ELECTRON_VERSION');
const version = await readFile(versionPath, 'utf8');
return version.trim();
}
async function nextNightly (v) {
let next = semver.valid(semver.coerce(v));
const pre = `nightly.${getCurrentDate()}`;
@@ -114,7 +108,6 @@ module.exports = {
nextAlpha,
nextBeta,
makeVersion,
getElectronVersion,
nextNightly,
preType
};

View File

@@ -45,6 +45,7 @@
#include "shell/app/command_line_args.h"
#include "shell/browser/api/electron_api_menu.h"
#include "shell/browser/api/electron_api_session.h"
#include "shell/browser/api/electron_api_utility_process.h"
#include "shell/browser/api/electron_api_web_contents.h"
#include "shell/browser/api/gpuinfo_manager.h"
#include "shell/browser/browser_process_impl.h"
@@ -922,6 +923,12 @@ void App::BrowserChildProcessCrashedOrKilled(
if (!data.name.empty()) {
details.Set("name", data.name);
}
if (data.process_type == content::PROCESS_TYPE_UTILITY) {
base::ProcessId pid = data.GetProcess().Pid();
auto utility_process_wrapper = UtilityProcessWrapper::FromProcessId(pid);
if (utility_process_wrapper)
utility_process_wrapper->Shutdown(info.exit_code);
}
Emit("child-process-gone", details);
}

View File

@@ -0,0 +1,420 @@
// Copyright (c) 2022 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/api/electron_api_utility_process.h"
#include <map>
#include <utility>
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/no_destructor.h"
#include "base/process/kill.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "content/public/browser/service_process_host.h"
#include "content/public/common/child_process_host.h"
#include "content/public/common/result_codes.h"
#include "gin/handle.h"
#include "gin/object_template_builder.h"
#include "gin/wrappable.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "shell/browser/api/message_port.h"
#include "shell/browser/javascript_environment.h"
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/file_path_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/object_template_builder.h"
#include "shell/common/node_includes.h"
#include "shell/common/v8_value_serializer.h"
#include "third_party/blink/public/common/messaging/message_port_descriptor.h"
#include "third_party/blink/public/common/messaging/transferable_message_mojom_traits.h"
#if BUILDFLAG(IS_POSIX)
#include "base/posix/eintr_wrapper.h"
#endif
#if BUILDFLAG(IS_WIN)
#include <fcntl.h>
#include <io.h>
#include "base/win/windows_types.h"
#endif
namespace electron {
base::IDMap<api::UtilityProcessWrapper*, base::ProcessId>&
GetAllUtilityProcessWrappers() {
static base::NoDestructor<
base::IDMap<api::UtilityProcessWrapper*, base::ProcessId>>
s_all_utility_process_wrappers;
return *s_all_utility_process_wrappers;
}
namespace api {
gin::WrapperInfo UtilityProcessWrapper::kWrapperInfo = {
gin::kEmbedderNativeGin};
UtilityProcessWrapper::UtilityProcessWrapper(
node::mojom::NodeServiceParamsPtr params,
std::u16string display_name,
std::map<IOHandle, IOType> stdio,
base::EnvironmentMap env_map,
base::FilePath current_working_directory,
bool use_plugin_helper) {
#if BUILDFLAG(IS_WIN)
base::win::ScopedHandle stdout_write(nullptr);
base::win::ScopedHandle stderr_write(nullptr);
#elif BUILDFLAG(IS_POSIX)
base::FileHandleMappingVector fds_to_remap;
#endif
for (const auto& [io_handle, io_type] : stdio) {
if (io_type == IOType::IO_PIPE) {
#if BUILDFLAG(IS_WIN)
HANDLE read = nullptr;
HANDLE write = nullptr;
// Ideally we would create with SECURITY_ATTRIBUTES.bInheritHandles
// set to TRUE so that the write handle can be duplicated into the
// child process for use,
// See
// https://learn.microsoft.com/en-us/windows/win32/procthread/inheritance#inheriting-handles
// for inheritance behavior of child process. But we don't do it here
// since base::Launch already takes of setting the
// inherit attribute when configuring
// `base::LaunchOptions::handles_to_inherit` Refs
// https://source.chromium.org/chromium/chromium/src/+/main:base/process/launch_win.cc;l=303-332
if (!::CreatePipe(&read, &write, nullptr, 0)) {
PLOG(ERROR) << "pipe creation failed";
return;
}
if (io_handle == IOHandle::STDOUT) {
stdout_write.Set(write);
stdout_read_handle_ = read;
stdout_read_fd_ =
_open_osfhandle(reinterpret_cast<intptr_t>(read), _O_RDONLY);
} else if (io_handle == IOHandle::STDERR) {
stderr_write.Set(write);
stderr_read_handle_ = read;
stderr_read_fd_ =
_open_osfhandle(reinterpret_cast<intptr_t>(read), _O_RDONLY);
}
#elif BUILDFLAG(IS_POSIX)
int pipe_fd[2];
if (HANDLE_EINTR(pipe(pipe_fd)) < 0) {
PLOG(ERROR) << "pipe creation failed";
return;
}
if (io_handle == IOHandle::STDOUT) {
fds_to_remap.push_back(std::make_pair(pipe_fd[1], STDOUT_FILENO));
stdout_read_fd_ = pipe_fd[0];
} else if (io_handle == IOHandle::STDERR) {
fds_to_remap.push_back(std::make_pair(pipe_fd[1], STDERR_FILENO));
stderr_read_fd_ = pipe_fd[0];
}
#endif
} else if (io_type == IOType::IO_IGNORE) {
#if BUILDFLAG(IS_WIN)
HANDLE handle =
CreateFileW(L"NUL", FILE_GENERIC_WRITE | FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
OPEN_EXISTING, 0, nullptr);
if (handle == INVALID_HANDLE_VALUE) {
PLOG(ERROR) << "Failed to create null handle";
return;
}
if (io_handle == IOHandle::STDOUT) {
stdout_write.Set(handle);
} else if (io_handle == IOHandle::STDERR) {
stderr_write.Set(handle);
}
#elif BUILDFLAG(IS_POSIX)
int devnull = open("/dev/null", O_WRONLY);
if (devnull < 0) {
PLOG(ERROR) << "failed to open /dev/null";
return;
}
if (io_handle == IOHandle::STDOUT) {
fds_to_remap.push_back(std::make_pair(devnull, STDOUT_FILENO));
} else if (io_handle == IOHandle::STDERR) {
fds_to_remap.push_back(std::make_pair(devnull, STDERR_FILENO));
}
#endif
}
}
mojo::PendingReceiver<node::mojom::NodeService> receiver =
node_service_remote_.BindNewPipeAndPassReceiver();
content::ServiceProcessHost::Launch(
std::move(receiver),
content::ServiceProcessHost::Options()
.WithDisplayName(display_name.empty()
? std::u16string(u"Node Utility Process")
: display_name)
.WithExtraCommandLineSwitches(params->exec_args)
.WithCurrentDirectory(current_working_directory)
// Inherit parent process environment when there is no custom
// environment provided by the user.
.WithEnvironment(env_map,
env_map.empty() ? false : true /*clear_environment*/)
#if BUILDFLAG(IS_WIN)
.WithStdoutHandle(std::move(stdout_write))
.WithStderrHandle(std::move(stderr_write))
#elif BUILDFLAG(IS_POSIX)
.WithAdditionalFds(std::move(fds_to_remap))
#endif
#if BUILDFLAG(IS_MAC)
.WithChildFlags(use_plugin_helper
? content::ChildProcessHost::CHILD_PLUGIN
: content::ChildProcessHost::CHILD_NORMAL)
#endif
.WithProcessCallback(
base::BindOnce(&UtilityProcessWrapper::OnServiceProcessLaunched,
weak_factory_.GetWeakPtr()))
.Pass());
node_service_remote_.set_disconnect_with_reason_handler(
base::BindOnce(&UtilityProcessWrapper::OnServiceProcessDisconnected,
weak_factory_.GetWeakPtr()));
// We use a separate message pipe to support postMessage API
// instead of the existing receiver interface so that we can
// support queuing of messages without having to block other
// interfaces.
blink::MessagePortDescriptorPair pipe;
host_port_ = pipe.TakePort0();
params->port = pipe.TakePort1();
connector_ = std::make_unique<mojo::Connector>(
host_port_.TakeHandleToEntangleWithEmbedder(),
mojo::Connector::SINGLE_THREADED_SEND,
base::ThreadTaskRunnerHandle::Get());
connector_->set_incoming_receiver(this);
connector_->set_connection_error_handler(base::BindOnce(
&UtilityProcessWrapper::CloseConnectorPort, weak_factory_.GetWeakPtr()));
node_service_remote_->Initialize(std::move(params));
}
UtilityProcessWrapper::~UtilityProcessWrapper() = default;
void UtilityProcessWrapper::OnServiceProcessLaunched(
const base::Process& process) {
DCHECK(node_service_remote_.is_connected());
pid_ = process.Pid();
GetAllUtilityProcessWrappers().AddWithID(this, pid_);
if (stdout_read_fd_ != -1) {
EmitWithoutCustomEvent("stdout", stdout_read_fd_);
}
if (stderr_read_fd_ != -1) {
EmitWithoutCustomEvent("stderr", stderr_read_fd_);
}
// Emit 'spawn' event
EmitWithoutCustomEvent("spawn");
}
void UtilityProcessWrapper::OnServiceProcessDisconnected(
uint32_t error_code,
const std::string& description) {
if (pid_ != base::kNullProcessId)
GetAllUtilityProcessWrappers().Remove(pid_);
CloseConnectorPort();
// Emit 'exit' event
EmitWithoutCustomEvent("exit", error_code);
Unpin();
}
void UtilityProcessWrapper::CloseConnectorPort() {
if (!connector_closed_ && connector_->is_valid()) {
host_port_.GiveDisentangledHandle(connector_->PassMessagePipe());
connector_ = nullptr;
host_port_.Reset();
connector_closed_ = true;
}
}
void UtilityProcessWrapper::Shutdown(int exit_code) {
if (pid_ != base::kNullProcessId)
GetAllUtilityProcessWrappers().Remove(pid_);
node_service_remote_.reset();
CloseConnectorPort();
// Emit 'exit' event
EmitWithoutCustomEvent("exit", exit_code);
Unpin();
}
void UtilityProcessWrapper::PostMessage(gin::Arguments* args) {
if (!node_service_remote_.is_connected())
return;
blink::TransferableMessage transferable_message;
v8::Local<v8::Value> message_value;
if (args->GetNext(&message_value)) {
if (!electron::SerializeV8Value(args->isolate(), message_value,
&transferable_message)) {
// SerializeV8Value sets an exception.
return;
}
}
v8::Local<v8::Value> transferables;
std::vector<gin::Handle<MessagePort>> wrapped_ports;
if (args->GetNext(&transferables)) {
if (!gin::ConvertFromV8(args->isolate(), transferables, &wrapped_ports)) {
gin_helper::ErrorThrower(args->isolate())
.ThrowTypeError("Invalid value for transfer");
return;
}
}
bool threw_exception = false;
transferable_message.ports = MessagePort::DisentanglePorts(
args->isolate(), wrapped_ports, &threw_exception);
if (threw_exception)
return;
mojo::Message mojo_message = blink::mojom::TransferableMessage::WrapAsMessage(
std::move(transferable_message));
connector_->Accept(&mojo_message);
}
bool UtilityProcessWrapper::Kill() const {
if (pid_ == base::kNullProcessId)
return 0;
base::Process process = base::Process::Open(pid_);
bool result = process.Terminate(content::RESULT_CODE_NORMAL_EXIT, false);
// Refs https://bugs.chromium.org/p/chromium/issues/detail?id=818244
// Currently utility process is not sandboxed which
// means Zygote is not used on linux, refs
// content::UtilitySandboxedProcessLauncherDelegate::GetZygote.
// If sandbox feature is enabled for the utility process, then the
// process reap should be signaled through the zygote via
// content::ZygoteCommunication::EnsureProcessTerminated.
base::EnsureProcessTerminated(std::move(process));
return result;
}
v8::Local<v8::Value> UtilityProcessWrapper::GetOSProcessId(
v8::Isolate* isolate) const {
if (pid_ == base::kNullProcessId)
return v8::Undefined(isolate);
return gin::ConvertToV8(isolate, pid_);
}
bool UtilityProcessWrapper::Accept(mojo::Message* mojo_message) {
blink::TransferableMessage message;
if (!blink::mojom::TransferableMessage::DeserializeFromMessage(
std::move(*mojo_message), &message)) {
return false;
}
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Value> message_value =
electron::DeserializeV8Value(isolate, message);
EmitWithoutCustomEvent("message", message_value);
return true;
}
// static
raw_ptr<UtilityProcessWrapper> UtilityProcessWrapper::FromProcessId(
base::ProcessId pid) {
auto* utility_process_wrapper = GetAllUtilityProcessWrappers().Lookup(pid);
return !!utility_process_wrapper ? utility_process_wrapper : nullptr;
}
// static
gin::Handle<UtilityProcessWrapper> UtilityProcessWrapper::Create(
gin::Arguments* args) {
gin_helper::Dictionary dict;
if (!args->GetNext(&dict)) {
args->ThrowTypeError("Options must be an object.");
return gin::Handle<UtilityProcessWrapper>();
}
std::u16string display_name;
bool use_plugin_helper = false;
std::map<IOHandle, IOType> stdio;
base::FilePath current_working_directory;
base::EnvironmentMap env_map;
node::mojom::NodeServiceParamsPtr params =
node::mojom::NodeServiceParams::New();
dict.Get("modulePath", &params->script);
if (dict.Has("args") && !dict.Get("args", &params->args)) {
args->ThrowTypeError("Invalid value for args");
return gin::Handle<UtilityProcessWrapper>();
}
gin_helper::Dictionary opts;
if (dict.Get("options", &opts)) {
if (opts.Has("env") && !opts.Get("env", &env_map)) {
args->ThrowTypeError("Invalid value for env");
return gin::Handle<UtilityProcessWrapper>();
}
if (opts.Has("execArgv") && !opts.Get("execArgv", &params->exec_args)) {
args->ThrowTypeError("Invalid value for execArgv");
return gin::Handle<UtilityProcessWrapper>();
}
opts.Get("serviceName", &display_name);
opts.Get("cwd", &current_working_directory);
std::vector<std::string> stdio_arr{"ignore", "inherit", "inherit"};
opts.Get("stdio", &stdio_arr);
for (size_t i = 0; i < 3; i++) {
IOType type;
if (stdio_arr[i] == "ignore")
type = IOType::IO_IGNORE;
else if (stdio_arr[i] == "inherit")
type = IOType::IO_INHERIT;
else if (stdio_arr[i] == "pipe")
type = IOType::IO_PIPE;
stdio.emplace(static_cast<IOHandle>(i), type);
}
#if BUILDFLAG(IS_MAC)
opts.Get("allowLoadingUnsignedLibraries", &use_plugin_helper);
#endif
}
auto handle = gin::CreateHandle(
args->isolate(),
new UtilityProcessWrapper(std::move(params), display_name,
std::move(stdio), env_map,
current_working_directory, use_plugin_helper));
handle->Pin(args->isolate());
return handle;
}
// static
gin::ObjectTemplateBuilder UtilityProcessWrapper::GetObjectTemplateBuilder(
v8::Isolate* isolate) {
return gin_helper::EventEmitterMixin<
UtilityProcessWrapper>::GetObjectTemplateBuilder(isolate)
.SetMethod("postMessage", &UtilityProcessWrapper::PostMessage)
.SetMethod("kill", &UtilityProcessWrapper::Kill)
.SetProperty("pid", &UtilityProcessWrapper::GetOSProcessId);
}
const char* UtilityProcessWrapper::GetTypeName() {
return "UtilityProcessWrapper";
}
} // namespace api
} // namespace electron
namespace {
void Initialize(v8::Local<v8::Object> exports,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context,
void* priv) {
v8::Isolate* isolate = context->GetIsolate();
gin_helper::Dictionary dict(isolate, exports);
dict.SetMethod("_fork", &electron::api::UtilityProcessWrapper::Create);
}
} // namespace
NODE_LINKED_MODULE_CONTEXT_AWARE(electron_browser_utility_process, Initialize)

View File

@@ -0,0 +1,100 @@
// Copyright (c) 2022 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_API_ELECTRON_API_UTILITY_PROCESS_H_
#define ELECTRON_SHELL_BROWSER_API_ELECTRON_API_UTILITY_PROCESS_H_
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "base/containers/id_map.h"
#include "base/environment.h"
#include "base/memory/weak_ptr.h"
#include "base/process/process_handle.h"
#include "gin/wrappable.h"
#include "mojo/public/cpp/bindings/connector.h"
#include "mojo/public/cpp/bindings/message.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "shell/browser/event_emitter_mixin.h"
#include "shell/common/gin_helper/pinnable.h"
#include "shell/services/node/public/mojom/node_service.mojom.h"
#include "v8/include/v8.h"
namespace gin {
class Arguments;
template <typename T>
class Handle;
} // namespace gin
namespace base {
class Process;
} // namespace base
namespace electron {
namespace api {
class UtilityProcessWrapper
: public gin::Wrappable<UtilityProcessWrapper>,
public gin_helper::Pinnable<UtilityProcessWrapper>,
public gin_helper::EventEmitterMixin<UtilityProcessWrapper>,
public mojo::MessageReceiver {
public:
enum class IOHandle : size_t { STDIN = 0, STDOUT = 1, STDERR = 2 };
enum class IOType { IO_PIPE, IO_INHERIT, IO_IGNORE };
~UtilityProcessWrapper() override;
static gin::Handle<UtilityProcessWrapper> Create(gin::Arguments* args);
static raw_ptr<UtilityProcessWrapper> FromProcessId(base::ProcessId pid);
void Shutdown(int exit_code);
// gin::Wrappable
static gin::WrapperInfo kWrapperInfo;
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
v8::Isolate* isolate) override;
const char* GetTypeName() override;
private:
UtilityProcessWrapper(node::mojom::NodeServiceParamsPtr params,
std::u16string display_name,
std::map<IOHandle, IOType> stdio,
base::EnvironmentMap env_map,
base::FilePath current_working_directory,
bool use_plugin_helper);
void OnServiceProcessDisconnected(uint32_t error_code,
const std::string& description);
void OnServiceProcessLaunched(const base::Process& process);
void CloseConnectorPort();
void PostMessage(gin::Arguments* args);
bool Kill() const;
v8::Local<v8::Value> GetOSProcessId(v8::Isolate* isolate) const;
// mojo::MessageReceiver
bool Accept(mojo::Message* mojo_message) override;
base::ProcessId pid_ = base::kNullProcessId;
#if BUILDFLAG(IS_WIN)
// Non-owning handles, these will be closed when the
// corresponding FD are closed via _close.
HANDLE stdout_read_handle_;
HANDLE stderr_read_handle_;
#endif
int stdout_read_fd_ = -1;
int stderr_read_fd_ = -1;
bool connector_closed_ = false;
std::unique_ptr<mojo::Connector> connector_;
blink::MessagePortDescriptor host_port_;
mojo::Remote<node::mojom::NodeService> node_service_remote_;
base::WeakPtrFactory<UtilityProcessWrapper> weak_factory_{this};
};
} // namespace api
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_API_ELECTRON_API_UTILITY_PROCESS_H_

View File

@@ -172,6 +172,7 @@
#if BUILDFLAG(ENABLE_PRINTING)
#include "chrome/browser/printing/print_view_manager_base.h"
#include "components/printing/browser/print_manager_utils.h"
#include "components/printing/browser/print_to_pdf/pdf_print_result.h"
#include "components/printing/browser/print_to_pdf/pdf_print_utils.h"
#include "printing/backend/print_backend.h" // nogncheck
#include "printing/mojom/print.mojom.h" // nogncheck
@@ -2902,12 +2903,12 @@ v8::Local<v8::Promise> WebContents::PrintToPDF(const base::Value& settings) {
void WebContents::OnPDFCreated(
gin_helper::Promise<v8::Local<v8::Value>> promise,
PrintViewManagerElectron::PrintResult print_result,
print_to_pdf::PdfPrintResult print_result,
scoped_refptr<base::RefCountedMemory> data) {
if (print_result != PrintViewManagerElectron::PrintResult::kPrintSuccess) {
if (print_result != print_to_pdf::PdfPrintResult::kPrintSuccess) {
promise.RejectWithErrorMessage(
"Failed to generate PDF: " +
PrintViewManagerElectron::PrintResultToString(print_result));
print_to_pdf::PdfPrintResultToString(print_result));
return;
}

View File

@@ -46,6 +46,7 @@
#include "ui/gfx/image/image.h"
#if BUILDFLAG(ENABLE_PRINTING)
#include "components/printing/browser/print_to_pdf/pdf_print_result.h"
#include "shell/browser/printing/print_view_manager_electron.h"
#endif
@@ -227,7 +228,7 @@ class WebContents : public ExclusiveAccessContext,
// Print current page as PDF.
v8::Local<v8::Promise> PrintToPDF(const base::Value& settings);
void OnPDFCreated(gin_helper::Promise<v8::Local<v8::Value>> promise,
PrintViewManagerElectron::PrintResult print_result,
print_to_pdf::PdfPrintResult print_result,
scoped_refptr<base::RefCountedMemory> data);
#endif

View File

@@ -24,13 +24,17 @@
#include "components/os_crypt/key_storage_config_linux.h"
#include "components/os_crypt/os_crypt.h"
#include "content/browser/browser_main_loop.h" // nogncheck
#include "content/public/browser/browser_child_process_host_delegate.h"
#include "content/public/browser/browser_child_process_host_iterator.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_data.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/device_service.h"
#include "content/public/browser/first_party_sets_handler.h"
#include "content/public/browser/web_ui_controller_factory.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/process_type.h"
#include "content/public/common/result_codes.h"
#include "electron/buildflags/buildflags.h"
#include "electron/fuses.h"
@@ -39,6 +43,7 @@
#include "services/tracing/public/cpp/stack_sampling/tracing_sampler_profiler.h"
#include "shell/app/electron_main_delegate.h"
#include "shell/browser/api/electron_api_app.h"
#include "shell/browser/api/electron_api_utility_process.h"
#include "shell/browser/browser.h"
#include "shell/browser/browser_process_impl.h"
#include "shell/browser/electron_browser_client.h"
@@ -273,12 +278,15 @@ void ElectronBrowserMainParts::PostEarlyInitialization() {
// Add Electron extended APIs.
electron_bindings_->BindTo(js_env_->isolate(), env->process_object());
// Load everything.
node_bindings_->LoadEnvironment(env);
// Create explicit microtasks runner.
js_env_->CreateMicrotasksRunner();
// Wrap the uv loop with global env.
node_bindings_->set_uv_env(env);
// Load everything.
node_bindings_->LoadEnvironment(env);
// We already initialized the feature list in PreEarlyInitialization(), but
// the user JS script would not have had a chance to alter the command-line
// switches at that point. Lets reinitialize it here to pick up the
@@ -503,7 +511,6 @@ int ElectronBrowserMainParts::PreMainMessageLoopRun() {
void ElectronBrowserMainParts::WillRunMainMessageLoop(
std::unique_ptr<base::RunLoop>& run_loop) {
js_env_->OnMessageLoopCreated();
exit_code_ = content::RESULT_CODE_NORMAL_EXIT;
Browser::Get()->SetMainMessageLoopQuitClosure(
run_loop->QuitWhenIdleClosure());
@@ -565,10 +572,39 @@ void ElectronBrowserMainParts::PostMainMessageLoopRun() {
}
}
// Shutdown utility process created with Electron API before
// stopping Node.js so that exit events can be emitted. We don't let
// content layer perform this action since it destroys
// child process only after this step (PostMainMessageLoopRun) via
// BrowserProcessIOThread::ProcessHostCleanUp() which is too late for our
// use case.
// https://source.chromium.org/chromium/chromium/src/+/main:content/browser/browser_main_loop.cc;l=1086-1108
//
// The following logic is based on
// https://source.chromium.org/chromium/chromium/src/+/main:content/browser/browser_process_io_thread.cc;l=127-159
//
// Although content::BrowserChildProcessHostIterator is only to be called from
// IO thread, it is safe to call from PostMainMessageLoopRun because thread
// restrictions have been lifted.
// https://source.chromium.org/chromium/chromium/src/+/main:content/browser/browser_main_loop.cc;l=1062-1078
for (content::BrowserChildProcessHostIterator it(
content::PROCESS_TYPE_UTILITY);
!it.Done(); ++it) {
if (it.GetDelegate()->GetServiceName() == node::mojom::NodeService::Name_) {
auto& process = it.GetData().GetProcess();
if (!process.IsValid())
continue;
auto utility_process_wrapper =
api::UtilityProcessWrapper::FromProcessId(process.Pid());
if (utility_process_wrapper)
utility_process_wrapper->Shutdown(0 /* exit_code */);
}
}
// Destroy node platform after all destructors_ are executed, as they may
// invoke Node/V8 APIs inside them.
node_env_->env()->set_trace_sync_io(false);
js_env_->OnMessageLoopDestroying();
js_env_->DestroyMicrotasksRunner();
node::Stop(node_env_->env());
node_env_.reset();

View File

@@ -38,6 +38,17 @@ class EventEmitterMixin {
std::forward<Args>(args)...);
}
// this.emit(name, args...);
template <typename... Args>
void EmitWithoutCustomEvent(base::StringPiece name, Args&&... args) {
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Object> wrapper;
if (!static_cast<T*>(this)->GetWrapper(isolate).ToLocal(&wrapper))
return;
gin_helper::EmitEvent(isolate, wrapper, name, std::forward<Args>(args)...);
}
// this.emit(name, event, args...);
template <typename... Args>
bool EmitCustomEvent(base::StringPiece name,

View File

@@ -287,13 +287,13 @@ v8::Isolate* JavascriptEnvironment::GetIsolate() {
return g_isolate;
}
void JavascriptEnvironment::OnMessageLoopCreated() {
void JavascriptEnvironment::CreateMicrotasksRunner() {
DCHECK(!microtasks_runner_);
microtasks_runner_ = std::make_unique<MicrotasksRunner>(isolate());
base::CurrentThread::Get()->AddTaskObserver(microtasks_runner_.get());
}
void JavascriptEnvironment::OnMessageLoopDestroying() {
void JavascriptEnvironment::DestroyMicrotasksRunner() {
DCHECK(microtasks_runner_);
{
v8::HandleScope scope(isolate_);

View File

@@ -29,8 +29,8 @@ class JavascriptEnvironment {
JavascriptEnvironment(const JavascriptEnvironment&) = delete;
JavascriptEnvironment& operator=(const JavascriptEnvironment&) = delete;
void OnMessageLoopCreated();
void OnMessageLoopDestroying();
void CreateMicrotasksRunner();
void DestroyMicrotasksRunner();
node::MultiIsolatePlatform* platform() const { return platform_; }
v8::Isolate* isolate() const { return isolate_; }

View File

@@ -60,124 +60,36 @@ void PrintViewManagerElectron::BindPrintManagerHost(
print_manager->BindReceiver(std::move(receiver), rfh);
}
// static
std::string PrintViewManagerElectron::PrintResultToString(PrintResult result) {
switch (result) {
case kPrintSuccess:
return std::string(); // no error message
case kPrintFailure:
return "Printing failed";
case kInvalidPrinterSettings:
return "Show invalid printer settings error";
case kInvalidMemoryHandle:
return "Invalid memory handle";
case kMetafileMapError:
return "Map to shared memory error";
case kMetafileInvalidHeader:
return "Invalid metafile header";
case kMetafileGetDataError:
return "Get data from metafile error";
case kSimultaneousPrintActive:
return "The previous printing job hasn't finished";
case kPageRangeSyntaxError:
return "Page range syntax error";
case kPageRangeInvalidRange:
return "Page range is invalid (start > end)";
case kPageCountExceeded:
return "Page range exceeds page count";
case kPrintingInProgress:
return "Page is already being printed";
default:
NOTREACHED();
return "Unknown PrintResult";
}
void PrintViewManagerElectron::DidPrintToPdf(
int cookie,
PrintToPdfCallback callback,
print_to_pdf::PdfPrintResult result,
scoped_refptr<base::RefCountedMemory> memory) {
base::Erase(pdf_jobs_, cookie);
std::move(callback).Run(result, memory);
}
void PrintViewManagerElectron::PrintToPdf(
content::RenderFrameHost* rfh,
const std::string& page_ranges,
printing::mojom::PrintPagesParamsPtr print_pages_params,
PrintToPDFCallback callback) {
DCHECK(callback);
PrintToPdfCallback callback) {
// Store cookie in order to track job uniqueness and differentiate
// between regular and headless print jobs.
int cookie = print_pages_params->params->document_cookie;
pdf_jobs_.emplace_back(cookie);
if (callback_) {
std::move(callback).Run(kSimultaneousPrintActive,
base::MakeRefCounted<base::RefCountedString>());
return;
}
if (!rfh->IsRenderFrameLive()) {
std::move(callback).Run(kPrintFailure,
base::MakeRefCounted<base::RefCountedString>());
return;
}
absl::variant<printing::PageRanges, print_to_pdf::PdfPrintResult>
parsed_ranges = print_to_pdf::TextPageRangesToPageRanges(page_ranges);
if (absl::holds_alternative<print_to_pdf::PdfPrintResult>(parsed_ranges)) {
DCHECK_NE(absl::get<print_to_pdf::PdfPrintResult>(parsed_ranges),
print_to_pdf::PdfPrintResult::kPrintSuccess);
std::move(callback).Run(
static_cast<PrintResult>(
absl::get<print_to_pdf::PdfPrintResult>(parsed_ranges)),
base::MakeRefCounted<base::RefCountedString>());
return;
}
printing_rfh_ = rfh;
print_pages_params->pages = absl::get<printing::PageRanges>(parsed_ranges);
headless_jobs_.emplace_back(print_pages_params->params->document_cookie);
callback_ = std::move(callback);
// There is no need for a weak pointer here since the mojo proxy is held
// in the base class. If we're gone, mojo will discard the callback.
GetPrintRenderFrame(rfh)->PrintWithParams(
print_to_pdf::PdfPrintJob::StartJob(
web_contents(), rfh, GetPrintRenderFrame(rfh), page_ranges,
std::move(print_pages_params),
base::BindOnce(&PrintViewManagerElectron::OnDidPrintWithParams,
base::Unretained(this)));
}
void PrintViewManagerElectron::OnDidPrintWithParams(
printing::mojom::PrintWithParamsResultPtr result) {
if (result->is_failure_reason()) {
switch (result->get_failure_reason()) {
case printing::mojom::PrintFailureReason::kGeneralFailure:
FailJob(kPrintFailure);
return;
case printing::mojom::PrintFailureReason::kInvalidPageRange:
FailJob(kPageCountExceeded);
return;
case printing::mojom::PrintFailureReason::kPrintingInProgress:
FailJob(kPrintingInProgress);
return;
}
}
printing::mojom::DidPrintDocumentParamsPtr& params = result->get_params();
auto& content = *params->content;
if (!content.metafile_data_region.IsValid()) {
FailJob(kInvalidMemoryHandle);
return;
}
base::ReadOnlySharedMemoryMapping map = content.metafile_data_region.Map();
if (!map.IsValid()) {
FailJob(kMetafileMapError);
return;
}
std::string data =
std::string(static_cast<const char*>(map.memory()), map.size());
std::move(callback_).Run(kPrintSuccess,
base::RefCountedString::TakeString(&data));
base::Erase(headless_jobs_, params->document_cookie);
Reset();
base::BindOnce(&PrintViewManagerElectron::DidPrintToPdf,
weak_factory_.GetWeakPtr(), cookie, std::move(callback)));
}
void PrintViewManagerElectron::GetDefaultPrintSettings(
GetDefaultPrintSettingsCallback callback) {
if (printing_rfh_) {
// This isn't ideal, but we're not able to access the document cookie here.
if (pdf_jobs_.size() > 0) {
LOG(ERROR) << "Scripted print is not supported";
std::move(callback).Run(printing::mojom::PrintParams::New());
} else {
@@ -188,9 +100,8 @@ void PrintViewManagerElectron::GetDefaultPrintSettings(
void PrintViewManagerElectron::ScriptedPrint(
printing::mojom::ScriptedPrintParamsPtr params,
ScriptedPrintCallback callback) {
auto entry =
std::find(headless_jobs_.begin(), headless_jobs_.end(), params->cookie);
if (entry == headless_jobs_.end()) {
auto entry = std::find(pdf_jobs_.begin(), pdf_jobs_.end(), params->cookie);
if (entry == pdf_jobs_.end()) {
PrintViewManagerBase::ScriptedPrint(std::move(params), std::move(callback));
return;
}
@@ -201,22 +112,13 @@ void PrintViewManagerElectron::ScriptedPrint(
std::move(callback).Run(std::move(default_param), /*cancelled*/ false);
}
void PrintViewManagerElectron::ShowInvalidPrinterSettingsError() {
if (headless_jobs_.size() == 0) {
PrintViewManagerBase::ShowInvalidPrinterSettingsError();
return;
}
FailJob(kInvalidPrinterSettings);
}
#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
void PrintViewManagerElectron::UpdatePrintSettings(
int32_t cookie,
base::Value::Dict job_settings,
UpdatePrintSettingsCallback callback) {
auto entry = std::find(headless_jobs_.begin(), headless_jobs_.end(), cookie);
if (entry == headless_jobs_.end()) {
auto entry = std::find(pdf_jobs_.begin(), pdf_jobs_.end(), cookie);
if (entry == pdf_jobs_.end()) {
PrintViewManagerBase::UpdatePrintSettings(cookie, std::move(job_settings),
std::move(callback));
return;
@@ -247,40 +149,14 @@ void PrintViewManagerElectron::CheckForCancel(int32_t preview_ui_id,
}
#endif // BUILDFLAG(ENABLE_PRINT_PREVIEW)
void PrintViewManagerElectron::RenderFrameDeleted(
content::RenderFrameHost* render_frame_host) {
PrintViewManagerBase::RenderFrameDeleted(render_frame_host);
if (printing_rfh_ != render_frame_host)
return;
FailJob(kPrintFailure);
}
void PrintViewManagerElectron::DidGetPrintedPagesCount(int32_t cookie,
uint32_t number_pages) {
auto entry = std::find(headless_jobs_.begin(), headless_jobs_.end(), cookie);
if (entry == headless_jobs_.end()) {
auto entry = std::find(pdf_jobs_.begin(), pdf_jobs_.end(), cookie);
if (entry == pdf_jobs_.end()) {
PrintViewManagerBase::DidGetPrintedPagesCount(cookie, number_pages);
}
}
void PrintViewManagerElectron::Reset() {
printing_rfh_ = nullptr;
callback_.Reset();
data_.clear();
}
void PrintViewManagerElectron::FailJob(PrintResult result) {
DCHECK_NE(result, kPrintSuccess);
if (callback_) {
std::move(callback_).Run(result,
base::MakeRefCounted<base::RefCountedString>());
}
Reset();
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(PrintViewManagerElectron);
} // namespace electron

View File

@@ -13,6 +13,7 @@
#include "base/memory/ref_counted_memory.h"
#include "build/build_config.h"
#include "chrome/browser/printing/print_view_manager_base.h"
#include "components/printing/browser/print_to_pdf/pdf_print_job.h"
#include "components/printing/common/print.mojom.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents_observer.h"
@@ -21,29 +22,12 @@
namespace electron {
using PrintToPdfCallback = print_to_pdf::PdfPrintJob::PrintToPdfCallback;
class PrintViewManagerElectron
: public printing::PrintViewManagerBase,
public content::WebContentsUserData<PrintViewManagerElectron> {
public:
enum PrintResult {
kPrintSuccess,
kPrintFailure,
kInvalidPrinterSettings,
kInvalidMemoryHandle,
kMetafileMapError,
kMetafileInvalidHeader,
kMetafileGetDataError,
kSimultaneousPrintActive,
kPageRangeSyntaxError,
kPageRangeInvalidRange,
kPageCountExceeded,
kPrintingInProgress
};
using PrintToPDFCallback =
base::OnceCallback<void(PrintResult,
scoped_refptr<base::RefCountedMemory>)>;
~PrintViewManagerElectron() override;
PrintViewManagerElectron(const PrintViewManagerElectron&) = delete;
@@ -54,30 +38,26 @@ class PrintViewManagerElectron
receiver,
content::RenderFrameHost* rfh);
static std::string PrintResultToString(PrintResult result);
void DidPrintToPdf(int cookie,
PrintToPdfCallback callback,
print_to_pdf::PdfPrintResult result,
scoped_refptr<base::RefCountedMemory> memory);
void PrintToPdf(content::RenderFrameHost* rfh,
const std::string& page_ranges,
printing::mojom::PrintPagesParamsPtr print_page_params,
PrintToPDFCallback callback);
PrintToPdfCallback callback);
private:
friend class content::WebContentsUserData<PrintViewManagerElectron>;
explicit PrintViewManagerElectron(content::WebContents* web_contents);
void OnDidPrintWithParams(printing::mojom::PrintWithParamsResultPtr result);
// WebContentsObserver overrides (via PrintManager):
void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
// printing::mojom::PrintManagerHost:
void DidGetPrintedPagesCount(int32_t cookie, uint32_t number_pages) override;
void GetDefaultPrintSettings(
GetDefaultPrintSettingsCallback callback) override;
void ScriptedPrint(printing::mojom::ScriptedPrintParamsPtr params,
ScriptedPrintCallback callback) override;
void ShowInvalidPrinterSettingsError() override;
#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
void UpdatePrintSettings(int32_t cookie,
base::Value::Dict job_settings,
@@ -91,14 +71,9 @@ class PrintViewManagerElectron
int32_t request_id,
CheckForCancelCallback callback) override;
#endif
std::vector<int32_t> pdf_jobs_;
void FailJob(PrintResult result);
void Reset();
raw_ptr<content::RenderFrameHost> printing_rfh_ = nullptr;
PrintToPDFCallback callback_;
std::string data_;
std::vector<int32_t> headless_jobs_;
base::WeakPtrFactory<PrintViewManagerElectron> weak_factory_{this};
WEB_CONTENTS_USER_DATA_KEY_DECL();
};

View File

@@ -70,6 +70,7 @@
V(electron_browser_system_preferences) \
V(electron_browser_base_window) \
V(electron_browser_tray) \
V(electron_browser_utility_process) \
V(electron_browser_view) \
V(electron_browser_web_contents) \
V(electron_browser_web_contents_view) \
@@ -87,7 +88,8 @@
V(electron_renderer_context_bridge) \
V(electron_renderer_crash_reporter) \
V(electron_renderer_ipc) \
V(electron_renderer_web_frame)
V(electron_renderer_web_frame) \
V(electron_utility_parent_port)
#define ELECTRON_VIEWS_MODULES(V) V(electron_browser_image_view)
@@ -390,7 +392,11 @@ void NodeBindings::Initialize() {
std::vector<std::string> argv = {"electron"};
std::vector<std::string> exec_argv;
std::vector<std::string> errors;
uint64_t process_flags = node::ProcessFlags::kEnableStdioInheritance;
uint64_t process_flags = node::ProcessFlags::kNoFlags;
// We do not want the child processes spawned from the utility process
// to inherit the custom stdio handles created for the parent.
if (browser_env_ != BrowserEnvironment::kUtility)
process_flags |= node::ProcessFlags::kEnableStdioInheritance;
if (!fuses::IsNodeOptionsEnabled())
process_flags |= node::ProcessFlags::kDisableNodeOptionsEnv;
@@ -417,16 +423,9 @@ void NodeBindings::Initialize() {
node::Environment* NodeBindings::CreateEnvironment(
v8::Handle<v8::Context> context,
node::MultiIsolatePlatform* platform) {
#if BUILDFLAG(IS_WIN)
auto& atom_args = ElectronCommandLine::argv();
std::vector<std::string> args(atom_args.size());
std::transform(atom_args.cbegin(), atom_args.cend(), args.begin(),
[](auto& a) { return base::WideToUTF8(a); });
#else
auto args = ElectronCommandLine::argv();
#endif
node::MultiIsolatePlatform* platform,
std::vector<std::string> args,
std::vector<std::string> exec_args) {
// Feed node the path to initialization script.
std::string process_type;
switch (browser_env_) {
@@ -439,14 +438,20 @@ node::Environment* NodeBindings::CreateEnvironment(
case BrowserEnvironment::kWorker:
process_type = "worker";
break;
case BrowserEnvironment::kUtility:
process_type = "utility";
break;
}
v8::Isolate* isolate = context->GetIsolate();
gin_helper::Dictionary global(isolate, context->Global());
// Do not set DOM globals for renderer process.
// We must set this before the node bootstrapper which is run inside
// CreateEnvironment
if (browser_env_ != BrowserEnvironment::kBrowser)
// Avoids overriding globals like setImmediate, clearImmediate
// queueMicrotask etc during the bootstrap phase of Node.js
// for processes that already have these defined by DOM.
// Check //third_party/electron_node/lib/internal/bootstrap/node.js
// for the list of overrides on globalThis.
if (browser_env_ == BrowserEnvironment::kRenderer ||
browser_env_ == BrowserEnvironment::kWorker)
global.Set("_noBrowserGlobals", true);
if (browser_env_ == BrowserEnvironment::kBrowser) {
@@ -464,7 +469,6 @@ node::Environment* NodeBindings::CreateEnvironment(
: search_paths));
}
std::vector<std::string> exec_args;
base::FilePath resources_path = GetResourcesPath();
std::string init_script = "electron/js2c/" + process_type + "_init";
@@ -478,7 +482,8 @@ node::Environment* NodeBindings::CreateEnvironment(
node::EnvironmentFlags::kHideConsoleWindows |
node::EnvironmentFlags::kNoGlobalSearchPaths;
if (browser_env_ != BrowserEnvironment::kBrowser) {
if (browser_env_ == BrowserEnvironment::kRenderer ||
browser_env_ == BrowserEnvironment::kWorker) {
// Only one ESM loader can be registered per isolate -
// in renderer processes this should be blink. We need to tell Node.js
// not to register its handler (overriding blinks) in non-browser processes.
@@ -514,7 +519,8 @@ node::Environment* NodeBindings::CreateEnvironment(
// Clean up the global _noBrowserGlobals that we unironically injected into
// the global scope
if (browser_env_ != BrowserEnvironment::kBrowser) {
if (browser_env_ == BrowserEnvironment::kRenderer ||
browser_env_ == BrowserEnvironment::kWorker) {
// We need to bootstrap the env in non-browser processes so that
// _noBrowserGlobals is read correctly before we remove it
global.Delete("_noBrowserGlobals");
@@ -528,15 +534,21 @@ node::Environment* NodeBindings::CreateEnvironment(
// We don't want to abort either in the renderer or browser processes.
// We already listen for uncaught exceptions and handle them there.
is.should_abort_on_uncaught_exception_callback = [](v8::Isolate*) {
return false;
};
// For utility process we expect the process to behave as standard
// Node.js runtime and abort the process with appropriate exit
// code depending on a handler being set for `uncaughtException` event.
if (browser_env_ != BrowserEnvironment::kUtility) {
is.should_abort_on_uncaught_exception_callback = [](v8::Isolate*) {
return false;
};
}
// Use a custom callback here to allow us to leverage Blink's logic in the
// renderer process.
is.allow_wasm_code_generation_callback = AllowWasmCodeGenerationCallback;
if (browser_env_ == BrowserEnvironment::kBrowser) {
if (browser_env_ == BrowserEnvironment::kBrowser ||
browser_env_ == BrowserEnvironment::kUtility) {
// Node.js requires that microtask checkpoints be explicitly invoked.
is.policy = v8::MicrotasksPolicy::kExplicit;
} else {
@@ -585,6 +597,20 @@ node::Environment* NodeBindings::CreateEnvironment(
return env;
}
node::Environment* NodeBindings::CreateEnvironment(
v8::Handle<v8::Context> context,
node::MultiIsolatePlatform* platform) {
#if BUILDFLAG(IS_WIN)
auto& electron_args = ElectronCommandLine::argv();
std::vector<std::string> args(electron_args.size());
std::transform(electron_args.cbegin(), electron_args.cend(), args.begin(),
[](auto& a) { return base::WideToUTF8(a); });
#else
auto args = ElectronCommandLine::argv();
#endif
return CreateEnvironment(context, platform, args, {});
}
void NodeBindings::LoadEnvironment(node::Environment* env) {
node::LoadEnvironment(env, node::StartExecutionCallback{});
gin_helper::EmitEvent(env->isolate(), env->process_object(), "loaded");

View File

@@ -5,7 +5,9 @@
#ifndef ELECTRON_SHELL_COMMON_NODE_BINDINGS_H_
#define ELECTRON_SHELL_COMMON_NODE_BINDINGS_H_
#include <string>
#include <type_traits>
#include <vector>
#include "base/files/file_path.h"
#include "base/memory/weak_ptr.h"
@@ -74,7 +76,7 @@ class UvHandle {
class NodeBindings {
public:
enum class BrowserEnvironment { kBrowser, kRenderer, kWorker };
enum class BrowserEnvironment { kBrowser, kRenderer, kUtility, kWorker };
static NodeBindings* Create(BrowserEnvironment browser_env);
static void RegisterBuiltinModules();
@@ -86,6 +88,10 @@ class NodeBindings {
void Initialize();
// Create the environment and load node.js.
node::Environment* CreateEnvironment(v8::Handle<v8::Context> context,
node::MultiIsolatePlatform* platform,
std::vector<std::string> args,
std::vector<std::string> exec_args);
node::Environment* CreateEnvironment(v8::Handle<v8::Context> context,
node::MultiIsolatePlatform* platform);

View File

@@ -0,0 +1,104 @@
// Copyright (c) 2022 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/services/node/node_service.h"
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/strings/utf_string_conversions.h"
#include "shell/browser/javascript_environment.h"
#include "shell/common/api/electron_bindings.h"
#include "shell/common/gin_converters/file_path_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/node_bindings.h"
#include "shell/common/node_includes.h"
#include "shell/services/node/parent_port.h"
namespace electron {
NodeService::NodeService(
mojo::PendingReceiver<node::mojom::NodeService> receiver)
: node_bindings_(
NodeBindings::Create(NodeBindings::BrowserEnvironment::kUtility)),
electron_bindings_(
std::make_unique<ElectronBindings>(node_bindings_->uv_loop())) {
if (receiver.is_valid())
receiver_.Bind(std::move(receiver));
}
NodeService::~NodeService() {
if (!node_env_stopped_) {
node_env_->env()->set_trace_sync_io(false);
js_env_->DestroyMicrotasksRunner();
node::Stop(node_env_->env());
}
}
void NodeService::Initialize(node::mojom::NodeServiceParamsPtr params) {
if (NodeBindings::IsInitialized())
return;
ParentPort::GetInstance()->Initialize(std::move(params->port));
js_env_ = std::make_unique<JavascriptEnvironment>(node_bindings_->uv_loop());
v8::HandleScope scope(js_env_->isolate());
node_bindings_->Initialize();
// Append program path for process.argv0
auto program = base::CommandLine::ForCurrentProcess()->GetProgram();
#if defined(OS_WIN)
params->args.insert(params->args.begin(), base::WideToUTF8(program.value()));
#else
params->args.insert(params->args.begin(), program.value());
#endif
// Create the global environment.
node::Environment* env = node_bindings_->CreateEnvironment(
js_env_->context(), js_env_->platform(), params->args, params->exec_args);
node_env_ = std::make_unique<NodeEnvironment>(env);
node::SetProcessExitHandler(env,
[this](node::Environment* env, int exit_code) {
// Destroy node platform.
env->set_trace_sync_io(false);
js_env_->DestroyMicrotasksRunner();
node::Stop(env);
node_env_stopped_ = true;
receiver_.ResetWithReason(exit_code, "");
});
env->set_trace_sync_io(env->options()->trace_sync_io);
// Add Electron extended APIs.
electron_bindings_->BindTo(env->isolate(), env->process_object());
// Add entry script to process object.
gin_helper::Dictionary process(env->isolate(), env->process_object());
process.SetHidden("_serviceStartupScript", params->script);
// Setup microtask runner.
js_env_->CreateMicrotasksRunner();
// Wrap the uv loop with global env.
node_bindings_->set_uv_env(env);
// LoadEnvironment should be called after setting up
// JavaScriptEnvironment including the microtask runner
// since this call will start compilation and execution
// of the entry script. If there is an uncaught exception
// the exit handler set above will be triggered and it expects
// both Node Env and JavaScriptEnviroment are setup to perform
// a clean shutdown of this process.
node_bindings_->LoadEnvironment(env);
// Run entry script.
node_bindings_->PrepareEmbedThread();
node_bindings_->StartPolling();
}
} // namespace electron

View File

@@ -0,0 +1,44 @@
// Copyright (c) 2022 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_SERVICES_NODE_NODE_SERVICE_H_
#define ELECTRON_SHELL_SERVICES_NODE_NODE_SERVICE_H_
#include <memory>
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "shell/services/node/public/mojom/node_service.mojom.h"
namespace electron {
class ElectronBindings;
class JavascriptEnvironment;
class NodeBindings;
class NodeEnvironment;
class NodeService : public node::mojom::NodeService {
public:
explicit NodeService(
mojo::PendingReceiver<node::mojom::NodeService> receiver);
~NodeService() override;
NodeService(const NodeService&) = delete;
NodeService& operator=(const NodeService&) = delete;
// mojom::NodeService implementation:
void Initialize(node::mojom::NodeServiceParamsPtr params) override;
private:
bool node_env_stopped_ = false;
std::unique_ptr<JavascriptEnvironment> js_env_;
std::unique_ptr<NodeBindings> node_bindings_;
std::unique_ptr<ElectronBindings> electron_bindings_;
std::unique_ptr<NodeEnvironment> node_env_;
mojo::Receiver<node::mojom::NodeService> receiver_{this};
};
} // namespace electron
#endif // ELECTRON_SHELL_SERVICES_NODE_NODE_SERVICE_H_

View File

@@ -0,0 +1,133 @@
// Copyright (c) 2022 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/services/node/parent_port.h"
#include <utility>
#include "base/no_destructor.h"
#include "gin/data_object_builder.h"
#include "gin/handle.h"
#include "shell/browser/api/message_port.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/event_emitter_caller.h"
#include "shell/common/node_includes.h"
#include "shell/common/v8_value_serializer.h"
#include "third_party/blink/public/common/messaging/transferable_message_mojom_traits.h"
namespace electron {
gin::WrapperInfo ParentPort::kWrapperInfo = {gin::kEmbedderNativeGin};
ParentPort* ParentPort::GetInstance() {
static base::NoDestructor<ParentPort> instance;
return instance.get();
}
ParentPort::ParentPort() = default;
ParentPort::~ParentPort() = default;
void ParentPort::Initialize(blink::MessagePortDescriptor port) {
port_ = std::move(port);
connector_ = std::make_unique<mojo::Connector>(
port_.TakeHandleToEntangleWithEmbedder(),
mojo::Connector::SINGLE_THREADED_SEND,
base::ThreadTaskRunnerHandle::Get());
connector_->PauseIncomingMethodCallProcessing();
connector_->set_incoming_receiver(this);
connector_->set_connection_error_handler(
base::BindOnce(&ParentPort::Close, base::Unretained(this)));
}
void ParentPort::PostMessage(v8::Local<v8::Value> message_value) {
if (!connector_closed_ && connector_ && connector_->is_valid()) {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
blink::TransferableMessage transferable_message;
electron::SerializeV8Value(isolate, message_value, &transferable_message);
mojo::Message mojo_message =
blink::mojom::TransferableMessage::WrapAsMessage(
std::move(transferable_message));
connector_->Accept(&mojo_message);
}
}
void ParentPort::Close() {
if (!connector_closed_ && connector_->is_valid()) {
port_.GiveDisentangledHandle(connector_->PassMessagePipe());
connector_ = nullptr;
port_.Reset();
connector_closed_ = true;
}
}
void ParentPort::Start() {
if (!connector_closed_ && connector_ && connector_->is_valid()) {
connector_->ResumeIncomingMethodCallProcessing();
}
}
void ParentPort::Pause() {
if (!connector_closed_ && connector_ && connector_->is_valid()) {
connector_->PauseIncomingMethodCallProcessing();
}
}
bool ParentPort::Accept(mojo::Message* mojo_message) {
blink::TransferableMessage message;
if (!blink::mojom::TransferableMessage::DeserializeFromMessage(
std::move(*mojo_message), &message)) {
return false;
}
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope(isolate);
auto wrapped_ports =
MessagePort::EntanglePorts(isolate, std::move(message.ports));
v8::Local<v8::Value> message_value =
electron::DeserializeV8Value(isolate, message);
v8::Local<v8::Object> self;
if (!GetWrapper(isolate).ToLocal(&self))
return false;
auto event = gin::DataObjectBuilder(isolate)
.Set("data", message_value)
.Set("ports", wrapped_ports)
.Build();
gin_helper::EmitEvent(isolate, self, "message", event);
return true;
}
// static
gin::Handle<ParentPort> ParentPort::Create(v8::Isolate* isolate) {
return gin::CreateHandle(isolate, ParentPort::GetInstance());
}
// static
gin::ObjectTemplateBuilder ParentPort::GetObjectTemplateBuilder(
v8::Isolate* isolate) {
return gin::Wrappable<ParentPort>::GetObjectTemplateBuilder(isolate)
.SetMethod("postMessage", &ParentPort::PostMessage)
.SetMethod("start", &ParentPort::Start)
.SetMethod("pause", &ParentPort::Pause);
}
const char* ParentPort::GetTypeName() {
return "ParentPort";
}
} // namespace electron
namespace {
void Initialize(v8::Local<v8::Object> exports,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context,
void* priv) {
v8::Isolate* isolate = context->GetIsolate();
gin_helper::Dictionary dict(isolate, exports);
dict.SetMethod("createParentPort", &electron::ParentPort::Create);
}
} // namespace
NODE_LINKED_MODULE_CONTEXT_AWARE(electron_utility_parent_port, Initialize)

View File

@@ -0,0 +1,68 @@
// Copyright (c) 2022 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_SERVICES_NODE_PARENT_PORT_H_
#define ELECTRON_SHELL_SERVICES_NODE_PARENT_PORT_H_
#include <memory>
#include "gin/wrappable.h"
#include "mojo/public/cpp/bindings/connector.h"
#include "mojo/public/cpp/bindings/message.h"
#include "shell/browser/event_emitter_mixin.h"
namespace v8 {
template <class T>
class Local;
class Value;
class Isolate;
} // namespace v8
namespace gin {
class Arguments;
template <typename T>
class Handle;
} // namespace gin
namespace electron {
// There is only a single instance of this class
// for the lifetime of a Utility Process which
// also means that GC lifecycle is ignored by this class.
class ParentPort : public gin::Wrappable<ParentPort>,
public mojo::MessageReceiver {
public:
static ParentPort* GetInstance();
static gin::Handle<ParentPort> Create(v8::Isolate* isolate);
ParentPort(const ParentPort&) = delete;
ParentPort& operator=(const ParentPort&) = delete;
ParentPort();
~ParentPort() override;
void Initialize(blink::MessagePortDescriptor port);
// gin::Wrappable
static gin::WrapperInfo kWrapperInfo;
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
v8::Isolate* isolate) override;
const char* GetTypeName() override;
private:
void PostMessage(v8::Local<v8::Value> message_value);
void Close();
void Start();
void Pause();
// mojo::MessageReceiver
bool Accept(mojo::Message* mojo_message) override;
bool connector_closed_ = false;
std::unique_ptr<mojo::Connector> connector_;
blink::MessagePortDescriptor port_;
};
} // namespace electron
#endif // ELECTRON_SHELL_SERVICES_NODE_PARENT_PORT_H_

View File

@@ -0,0 +1,14 @@
# Copyright (c) 2022 Microsoft, Inc.
# Use of this source code is governed by the MIT license that can be
# found in the LICENSE file.
import("//mojo/public/tools/bindings/mojom.gni")
mojom("mojom") {
sources = [ "node_service.mojom" ]
public_deps = [
"//mojo/public/mojom/base",
"//sandbox/policy/mojom",
"//third_party/blink/public/mojom:mojom_core",
]
}

View File

@@ -0,0 +1,21 @@
// Copyright (c) 2022 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
module node.mojom;
import "mojo/public/mojom/base/file_path.mojom";
import "sandbox/policy/mojom/sandbox.mojom";
import "third_party/blink/public/mojom/messaging/message_port_descriptor.mojom";
struct NodeServiceParams {
mojo_base.mojom.FilePath script;
array<string> args;
array<string> exec_args;
blink.mojom.MessagePortDescriptor port;
};
[ServiceSandbox=sandbox.mojom.Sandbox.kNoSandbox]
interface NodeService {
Initialize(NodeServiceParams params);
};

View File

@@ -16,6 +16,8 @@
#include "services/proxy_resolver/proxy_resolver_factory_impl.h"
#include "services/proxy_resolver/public/mojom/proxy_resolver.mojom.h"
#include "services/service_manager/public/cpp/service.h"
#include "shell/services/node/node_service.h"
#include "shell/services/node/public/mojom/node_service.mojom.h"
#if BUILDFLAG(IS_WIN)
#include "chrome/services/util_win/public/mojom/util_read_icon.mojom.h"
@@ -72,6 +74,10 @@ auto RunProxyResolver(
std::move(receiver));
}
auto RunNodeService(mojo::PendingReceiver<node::mojom::NodeService> receiver) {
return std::make_unique<electron::NodeService>(std::move(receiver));
}
} // namespace
ElectronContentUtilityClient::ElectronContentUtilityClient() = default;
@@ -115,6 +121,8 @@ void ElectronContentUtilityClient::RegisterMainThreadServices(
(BUILDFLAG(ENABLE_PRINTING) && BUILDFLAG(IS_WIN))
services.Add(RunPrintingService);
#endif
services.Add(RunNodeService);
}
void ElectronContentUtilityClient::RegisterIOThreadServices(

View File

@@ -0,0 +1,364 @@
import { expect } from 'chai';
import * as childProcess from 'child_process';
import * as path from 'path';
import { BrowserWindow, MessageChannelMain, utilityProcess } from 'electron/main';
import { emittedOnce } from './events-helpers';
import { ifit } from './spec-helpers';
import { closeWindow } from './window-helpers';
const fixturesPath = path.resolve(__dirname, 'fixtures', 'api', 'utility-process');
const isWindowsOnArm = process.platform === 'win32' && process.arch === 'arm64';
describe('utilityProcess module', () => {
describe('UtilityProcess constructor', () => {
it('throws when empty script path is provided', async () => {
expect(() => {
/* eslint-disable no-new */
utilityProcess.fork('');
/* eslint-disable no-new */
}).to.throw();
});
it('throws when options.stdio is not valid', async () => {
expect(() => {
/* eslint-disable no-new */
utilityProcess.fork(path.join(fixturesPath, 'empty.js'), [], {
execArgv: ['--test', '--test2'],
serviceName: 'test',
stdio: 'ipc'
});
/* eslint-disable no-new */
}).to.throw(/stdio must be of the following values: inherit, pipe, ignore/);
expect(() => {
/* eslint-disable no-new */
utilityProcess.fork(path.join(fixturesPath, 'empty.js'), [], {
execArgv: ['--test', '--test2'],
serviceName: 'test',
stdio: ['ignore', 'ignore']
});
/* eslint-disable no-new */
}).to.throw(/configuration missing for stdin, stdout or stderr/);
expect(() => {
/* eslint-disable no-new */
utilityProcess.fork(path.join(fixturesPath, 'empty.js'), [], {
execArgv: ['--test', '--test2'],
serviceName: 'test',
stdio: ['pipe', 'inherit', 'inherit']
});
/* eslint-disable no-new */
}).to.throw(/stdin value other than ignore is not supported/);
});
});
describe('lifecycle events', () => {
it('emits \'spawn\' when child process successfully launches', async () => {
const child = utilityProcess.fork(path.join(fixturesPath, 'empty.js'));
await emittedOnce(child, 'spawn');
});
it('emits \'exit\' when child process exits gracefully', async () => {
const child = utilityProcess.fork(path.join(fixturesPath, 'empty.js'));
const [code] = await emittedOnce(child, 'exit');
expect(code).to.equal(0);
});
it('emits \'exit\' when child process crashes', async () => {
const child = utilityProcess.fork(path.join(fixturesPath, 'crash.js'));
// Do not check for exit code in this case,
// SIGSEGV code can be 139 or 11 across our different CI pipeline.
await emittedOnce(child, 'exit');
});
it('emits \'exit\' corresponding to the child process', async () => {
const child1 = utilityProcess.fork(path.join(fixturesPath, 'endless.js'));
await emittedOnce(child1, 'spawn');
const child2 = utilityProcess.fork(path.join(fixturesPath, 'crash.js'));
await emittedOnce(child2, 'exit');
expect(child1.kill()).to.be.true();
await emittedOnce(child1, 'exit');
});
it('emits \'exit\' when there is uncaught exception', async () => {
const child = utilityProcess.fork(path.join(fixturesPath, 'exception.js'));
const [code] = await emittedOnce(child, 'exit');
expect(code).to.equal(1);
});
it('emits \'exit\' when process.exit is called', async () => {
const exitCode = 2;
const child = utilityProcess.fork(path.join(fixturesPath, 'custom-exit.js'), [`--exitCode=${exitCode}`]);
const [code] = await emittedOnce(child, 'exit');
expect(code).to.equal(exitCode);
});
});
describe('kill() API', () => {
it('terminates the child process gracefully', async () => {
const child = utilityProcess.fork(path.join(fixturesPath, 'endless.js'), [], {
serviceName: 'endless'
});
await emittedOnce(child, 'spawn');
expect(child.kill()).to.be.true();
await emittedOnce(child, 'exit');
});
});
describe('pid property', () => {
it('is valid when child process launches successfully', async () => {
const child = utilityProcess.fork(path.join(fixturesPath, 'empty.js'));
await emittedOnce(child, 'spawn');
expect(child.pid).to.not.be.null();
});
it('is undefined when child process fails to launch', async () => {
const child = utilityProcess.fork(path.join(fixturesPath, 'does-not-exist.js'));
expect(child.pid).to.be.undefined();
});
});
describe('stdout property', () => {
it('is null when child process launches with default stdio', async () => {
const child = utilityProcess.fork(path.join(fixturesPath, 'log.js'));
await emittedOnce(child, 'spawn');
expect(child.stdout).to.be.null();
expect(child.stderr).to.be.null();
await emittedOnce(child, 'exit');
});
it('is null when child process launches with ignore stdio configuration', async () => {
const child = utilityProcess.fork(path.join(fixturesPath, 'log.js'), [], {
stdio: 'ignore'
});
await emittedOnce(child, 'spawn');
expect(child.stdout).to.be.null();
expect(child.stderr).to.be.null();
await emittedOnce(child, 'exit');
});
it('is valid when child process launches with pipe stdio configuration', async () => {
const child = utilityProcess.fork(path.join(fixturesPath, 'log.js'), [], {
stdio: 'pipe'
});
await emittedOnce(child, 'spawn');
expect(child.stdout).to.not.be.null();
let log = '';
child.stdout!.on('data', (chunk) => {
log += chunk.toString('utf8');
});
await emittedOnce(child, 'exit');
expect(log).to.equal('hello\n');
});
});
describe('stderr property', () => {
it('is null when child process launches with default stdio', async () => {
const child = utilityProcess.fork(path.join(fixturesPath, 'log.js'));
await emittedOnce(child, 'spawn');
expect(child.stdout).to.be.null();
expect(child.stderr).to.be.null();
await emittedOnce(child, 'exit');
});
it('is null when child process launches with ignore stdio configuration', async () => {
const child = utilityProcess.fork(path.join(fixturesPath, 'log.js'), [], {
stdio: 'ignore'
});
await emittedOnce(child, 'spawn');
expect(child.stderr).to.be.null();
await emittedOnce(child, 'exit');
});
ifit(!isWindowsOnArm)('is valid when child process launches with pipe stdio configuration', async () => {
const child = utilityProcess.fork(path.join(fixturesPath, 'log.js'), [], {
stdio: ['ignore', 'pipe', 'pipe']
});
await emittedOnce(child, 'spawn');
expect(child.stderr).to.not.be.null();
let log = '';
child.stderr!.on('data', (chunk) => {
log += chunk.toString('utf8');
});
await emittedOnce(child, 'exit');
expect(log).to.equal('world');
});
});
describe('postMessage() API', () => {
it('establishes a default ipc channel with the child process', async () => {
const result = 'I will be echoed.';
const child = utilityProcess.fork(path.join(fixturesPath, 'post-message.js'));
await emittedOnce(child, 'spawn');
child.postMessage(result);
const [data] = await emittedOnce(child, 'message');
expect(data).to.equal(result);
const exit = emittedOnce(child, 'exit');
expect(child.kill()).to.be.true();
await exit;
});
it('supports queuing messages on the receiving end', async () => {
const child = utilityProcess.fork(path.join(fixturesPath, 'post-message-queue.js'));
const p = emittedOnce(child, 'spawn');
child.postMessage('This message');
child.postMessage(' is');
child.postMessage(' queued');
await p;
const [data] = await emittedOnce(child, 'message');
expect(data).to.equal('This message is queued');
const exit = emittedOnce(child, 'exit');
expect(child.kill()).to.be.true();
await exit;
});
});
describe('behavior', () => {
it('supports starting the v8 inspector with --inspect-brk', (done) => {
const child = utilityProcess.fork(path.join(fixturesPath, 'log.js'), [], {
stdio: 'pipe',
execArgv: ['--inspect-brk']
});
let output = '';
const cleanup = () => {
child.stderr!.removeListener('data', listener);
child.stdout!.removeListener('data', listener);
child.once('exit', () => { done(); });
child.kill();
};
const listener = (data: Buffer) => {
output += data;
if (/Debugger listening on ws:/m.test(output)) {
cleanup();
}
};
child.stderr!.on('data', listener);
child.stdout!.on('data', listener);
});
it('supports starting the v8 inspector with --inspect and a provided port', (done) => {
const child = utilityProcess.fork(path.join(fixturesPath, 'log.js'), [], {
stdio: 'pipe',
execArgv: ['--inspect=17364']
});
let output = '';
const cleanup = () => {
child.stderr!.removeListener('data', listener);
child.stdout!.removeListener('data', listener);
child.once('exit', () => { done(); });
child.kill();
};
const listener = (data: Buffer) => {
output += data;
if (/Debugger listening on ws:/m.test(output)) {
expect(output.trim()).to.contain(':17364', 'should be listening on port 17364');
cleanup();
}
};
child.stderr!.on('data', listener);
child.stdout!.on('data', listener);
});
ifit(process.platform !== 'win32')('supports redirecting stdout to parent process', async () => {
const result = 'Output from utility process';
const appProcess = childProcess.spawn(process.execPath, [path.join(fixturesPath, 'inherit-stdout'), `--payload=${result}`]);
let output = '';
appProcess.stdout.on('data', (data: Buffer) => { output += data; });
await emittedOnce(appProcess, 'exit');
expect(output).to.equal(result);
});
ifit(process.platform !== 'win32')('supports redirecting stderr to parent process', async () => {
const result = 'Error from utility process';
const appProcess = childProcess.spawn(process.execPath, [path.join(fixturesPath, 'inherit-stderr'), `--payload=${result}`]);
let output = '';
appProcess.stderr.on('data', (data: Buffer) => { output += data; });
await emittedOnce(appProcess, 'exit');
expect(output).to.include(result);
});
it('can establish communication channel with sandboxed renderer', async () => {
const result = 'Message from sandboxed renderer';
const w = new BrowserWindow({
show: false,
webPreferences: {
preload: path.join(fixturesPath, 'preload.js')
}
});
await w.loadFile(path.join(__dirname, 'fixtures', 'blank.html'));
// Create Message port pair for Renderer <-> Utility Process.
const { port1: rendererPort, port2: childPort1 } = new MessageChannelMain();
w.webContents.postMessage('port', result, [rendererPort]);
// Send renderer and main channel port to utility process.
const child = utilityProcess.fork(path.join(fixturesPath, 'receive-message.js'));
await emittedOnce(child, 'spawn');
child.postMessage('', [childPort1]);
const [data] = await emittedOnce(child, 'message');
expect(data).to.equal(result);
// Cleanup.
const exit = emittedOnce(child, 'exit');
expect(child.kill()).to.be.true();
await exit;
await closeWindow(w);
});
ifit(process.platform === 'linux')('allows executing a setuid binary with child_process', async () => {
const child = utilityProcess.fork(path.join(fixturesPath, 'suid.js'));
await emittedOnce(child, 'spawn');
const [data] = await emittedOnce(child, 'message');
expect(data).to.not.be.empty();
const exit = emittedOnce(child, 'exit');
expect(child.kill()).to.be.true();
await exit;
});
it('inherits parent env as default', async () => {
const appProcess = childProcess.spawn(process.execPath, [path.join(fixturesPath, 'env-app')], {
env: {
FROM: 'parent',
...process.env
}
});
let output = '';
appProcess.stdout.on('data', (data: Buffer) => { output += data; });
await emittedOnce(appProcess.stdout, 'end');
const result = process.platform === 'win32' ? '\r\nparent' : 'parent';
expect(output).to.equal(result);
});
it('does not inherit parent env when custom env is provided', async () => {
const appProcess = childProcess.spawn(process.execPath, [path.join(fixturesPath, 'env-app'), '--create-custom-env'], {
env: {
FROM: 'parent',
...process.env
}
});
let output = '';
appProcess.stdout.on('data', (data: Buffer) => { output += data; });
await emittedOnce(appProcess.stdout, 'end');
const result = process.platform === 'win32' ? '\r\nchild' : 'child';
expect(output).to.equal(result);
});
it('changes working directory with cwd', async () => {
const child = utilityProcess.fork('./log.js', [], {
cwd: fixturesPath,
stdio: ['ignore', 'pipe', 'ignore']
});
await emittedOnce(child, 'spawn');
expect(child.stdout).to.not.be.null();
let log = '';
child.stdout!.on('data', (chunk) => {
log += chunk.toString('utf8');
});
await emittedOnce(child, 'exit');
expect(log).to.equal('hello\n');
});
});
});

View File

@@ -31,6 +31,22 @@ const runFixtureAndEnsureCleanExit = (args: string[]) => {
});
};
const shouldRunCase = (crashCase: string) => {
switch (crashCase) {
// TODO(jkleinsc) fix this flaky test on Windows 32-bit
case 'quit-on-crashed-event': {
return (process.platform !== 'win32' || process.arch !== 'ia32');
}
// TODO(jkleinsc) fix this test on Linux on arm/arm64
case 'js-execute-iframe': {
return (process.platform !== 'linux' || (process.arch !== 'arm64' && process.arch !== 'arm'));
}
default: {
return true;
}
}
};
describe('crash cases', () => {
afterEach(() => {
for (const child of children) {
@@ -42,8 +58,7 @@ describe('crash cases', () => {
const cases = fs.readdirSync(fixturePath);
for (const crashCase of cases) {
// TODO(jkleinsc) fix this flaky test on Windows 32-bit
ifit(process.platform !== 'win32' || process.arch !== 'ia32' || crashCase !== 'quit-on-crashed-event')(`the "${crashCase}" case should not crash`, () => {
ifit(shouldRunCase(crashCase))(`the "${crashCase}" case should not crash`, () => {
const fixture = path.resolve(fixturePath, crashCase);
const argsFile = path.resolve(fixture, 'electron.args');
const args = [fixture];

View File

@@ -0,0 +1 @@
process.crash();

View File

@@ -0,0 +1,3 @@
const arg = process.argv[2];
const code = arg.split('=')[1];
process.exit(code);

View File

@@ -0,0 +1 @@
process.exit(0);

View File

@@ -0,0 +1 @@
setInterval(() => {}, 2000);

View File

@@ -0,0 +1,22 @@
const { app, utilityProcess } = require('electron');
const path = require('path');
app.whenReady().then(() => {
let child = null;
if (app.commandLine.hasSwitch('create-custom-env')) {
child = utilityProcess.fork(path.join(__dirname, 'test.js'), {
env: {
FROM: 'child'
}
});
} else {
child = utilityProcess.fork(path.join(__dirname, 'test.js'));
}
child.on('message', (data) => {
process.stdout.write(data);
process.stdout.end();
});
child.on('exit', () => {
app.quit();
});
});

View File

@@ -0,0 +1,4 @@
{
"name": "electron-test-utility-process-env-app",
"main": "main.js"
}

View File

@@ -0,0 +1,2 @@
process.parentPort.postMessage(process.env.FROM);
process.exit(0);

View File

@@ -0,0 +1 @@
nonExistingFunc(); // eslint-disable-line

View File

@@ -0,0 +1,10 @@
const { app, utilityProcess } = require('electron');
const path = require('path');
app.whenReady().then(() => {
const payload = app.commandLine.getSwitchValue('payload');
const child = utilityProcess.fork(path.join(__dirname, 'test.js'), [`--payload=${payload}`]);
child.on('exit', () => {
app.quit();
});
});

View File

@@ -0,0 +1,4 @@
{
"name": "electron-test-utility-process-inherit-stderr",
"main": "main.js"
}

View File

@@ -0,0 +1,3 @@
process.stderr.write(process.argv[2].split('--payload=')[1]);
process.stderr.end();
process.exit(0);

View File

@@ -0,0 +1,10 @@
const { app, utilityProcess } = require('electron');
const path = require('path');
app.whenReady().then(() => {
const payload = app.commandLine.getSwitchValue('payload');
const child = utilityProcess.fork(path.join(__dirname, 'test.js'), [`--payload=${payload}`]);
child.on('exit', () => {
app.quit();
});
});

View File

@@ -0,0 +1,4 @@
{
"name": "electron-test-utility-process-inherit-stdout",
"main": "main.js"
}

View File

@@ -0,0 +1,3 @@
process.stdout.write(process.argv[2].split('--payload=')[1]);
process.stdout.end();
process.exit(0);

View File

@@ -0,0 +1,3 @@
console.log('hello');
process.stderr.write('world');
process.exit(0);

View File

@@ -0,0 +1,10 @@
setTimeout(() => {
let called = 0;
let result = '';
process.parentPort.on('message', (e) => {
result += e.data;
if (++called === 3) {
process.parentPort.postMessage(result);
}
});
}, 3000);

View File

@@ -0,0 +1,3 @@
process.parentPort.on('message', (e) => {
process.parentPort.postMessage(e.data);
});

View File

@@ -0,0 +1,5 @@
const { ipcRenderer } = require('electron');
ipcRenderer.on('port', (e, msg) => {
e.ports[0].postMessage(msg);
});

View File

@@ -0,0 +1,6 @@
process.parentPort.on('message', (e) => {
e.ports[0].on('message', (ev) => {
process.parentPort.postMessage(ev.data);
});
e.ports[0].start();
});

View File

@@ -0,0 +1,2 @@
const result = require('child_process').execSync('sudo --help');
process.parentPort.postMessage(result);

View File

@@ -1,7 +1,7 @@
// Use this script to regenerate these fixture files
// using a new version of the asar package
const asar = require('asar');
const asar = require('@electron/asar');
const fs = require('fs');
const os = require('os');
const path = require('path');

View File

@@ -1,15 +1,9 @@
import { expect } from 'chai';
import { GitProcess, IGitExecutionOptions, IGitResult } from 'dugite';
import { nextVersion, shouldUpdateSupported, updateSupported } from '../script/release/version-bumper';
import { nextVersion } 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');
const fixtureDir = path.resolve(__dirname, 'fixtures', 'version-bumper', 'fixture_support.md');
const readFile = fs.readFile;
const writeFile = fs.writeFile;
class GitFake {
branches: {
@@ -96,94 +90,6 @@ describe('version-bumper', () => {
});
});
describe('updateSupported', () => {
let restore: any;
before(async () => {
restore = await readFile(fixtureDir, 'utf8');
});
afterEach(async () => {
await writeFile(fixtureDir, restore, 'utf8');
});
it('updates correctly when a new stable version is promoted from beta', async () => {
const version = '4.0.0';
const currentVersion = '4.0.0-beta.29';
if (shouldUpdateSupported('stable', currentVersion, version)) {
await updateSupported(version, fixtureDir);
}
const contents = await readFile(fixtureDir, 'utf8');
expect(contents).to.contain('4.x.y\n* 3.x.y\n* 2.x.y');
});
it('should not update when a new stable patch version is promoted', async () => {
const version = '3.0.1';
const currentVersion = '3.0.0';
if (shouldUpdateSupported('stable', currentVersion, version)) {
await updateSupported(version, fixtureDir);
}
const contents = await readFile(fixtureDir, 'utf8');
expect(contents).to.contain('3.x.y\n* 2.x.y\n* 1.x.y');
});
it('should not update when a new stable minor version is promoted', async () => {
const version = '3.1.0';
const currentVersion = '3.0.0';
if (shouldUpdateSupported('minor', currentVersion, version)) {
await updateSupported(version, fixtureDir);
}
const contents = await readFile(fixtureDir, 'utf8');
expect(contents).to.contain('3.x.y\n* 2.x.y\n* 1.x.y');
});
it('should not update when a new beta.1 version is promoted', async () => {
const version = '5.0.0-beta.1';
const currentVersion = '4.0.0-beta.29';
if (shouldUpdateSupported('beta', currentVersion, version)) {
await updateSupported(version, fixtureDir);
}
const contents = await readFile(fixtureDir, 'utf8');
expect(contents).to.contain('3.x.y\n* 2.x.y\n* 1.x.y');
});
it('should not update when a new beta.12 version is promoted', async () => {
const version = '4.0.0-beta.12';
const currentVersion = '4.0.0-beta.11';
if (shouldUpdateSupported('beta', currentVersion, version)) {
await updateSupported(version, fixtureDir);
}
const contents = await readFile(fixtureDir, 'utf8');
expect(contents).to.contain('3.x.y\n* 2.x.y\n* 1.x.y');
});
it('should update when a new major nightly version is promoted', async () => {
const version = '4.0.0-nightly.19950901';
const currentVersion = '3.0.0-nightly.19950828';
if (shouldUpdateSupported('nightly', currentVersion, version)) {
await updateSupported(version, fixtureDir);
}
const contents = await readFile(fixtureDir, 'utf8');
expect(contents).to.contain('4.x.y\n* 3.x.y\n* 2.x.y');
});
it('should not update when a new nightly version is promoted', async () => {
const version = '3.0.0-nightly.19950901';
const currentVersion = '3.0.0-nightly.19950828';
if (shouldUpdateSupported('nightly', currentVersion, version)) {
await updateSupported(version, fixtureDir);
}
const contents = await readFile(fixtureDir, 'utf8');
expect(contents).to.contain('3.x.y\n* 2.x.y\n* 1.x.y');
});
});
// 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', () => {

View File

@@ -251,6 +251,7 @@ declare namespace NodeJS {
// Additional properties
_firstFileName?: string;
_serviceStartupScript: string;
helperExecPath: string;
mainModule?: NodeJS.Module | undefined;

View File

@@ -9,7 +9,8 @@ declare namespace Electron {
enum ProcessType {
browser = 'browser',
renderer = 'renderer',
worker = 'worker'
worker = 'worker',
utility = 'utility'
}
interface App {
@@ -254,6 +255,18 @@ declare namespace ElectronInternal {
loader: ModuleLoader;
}
interface UtilityProcessWrapper extends NodeJS.EventEmitter {
readonly pid: (number) | (undefined);
kill(): boolean;
postMessage(message: any, transfer?: any[]): void;
}
interface ParentPort extends NodeJS.EventEmitter {
start(): void;
pause(): void;
postMessage(message: any): void;
}
class WebViewElement extends HTMLElement {
static observedAttributes: Array<string>;

View File

@@ -111,6 +111,18 @@
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
"@electron/asar@^3.2.1":
version "3.2.1"
resolved "https://registry.yarnpkg.com/@electron/asar/-/asar-3.2.1.tgz#c4143896f3dd43b59a80a9c9068d76f77efb62ea"
integrity sha512-hE2cQMZ5+4o7+6T2lUaVbxIzrOjZZfX7dB02xuapyYFJZEAiWTelq6J3mMoxzd0iONDvYLPVKecB5tyjIoVDVA==
dependencies:
chromium-pickle-js "^0.2.0"
commander "^5.0.0"
glob "^7.1.6"
minimatch "^3.0.4"
optionalDependencies:
"@types/glob" "^7.1.1"
"@electron/docs-parser@^0.12.4":
version "0.12.4"
resolved "https://registry.yarnpkg.com/@electron/docs-parser/-/docs-parser-0.12.4.tgz#cca403c8c2200181339c3115cdd25f3fbfc7dea3"
@@ -1189,18 +1201,6 @@ arrify@^1.0.0:
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
asar@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/asar/-/asar-3.1.0.tgz#70b0509449fe3daccc63beb4d3c7d2e24d3c6473"
integrity sha512-vyxPxP5arcAqN4F/ebHd/HhwnAiZtwhglvdmc7BR2f0ywbVNTOpSeyhLDbGXtE/y58hv1oC75TaNIXutnsOZsQ==
dependencies:
chromium-pickle-js "^0.2.0"
commander "^5.0.0"
glob "^7.1.6"
minimatch "^3.0.4"
optionalDependencies:
"@types/glob" "^7.1.1"
assertion-error@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"