Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: UtilityProcess API #34980

Merged
merged 50 commits into from
Oct 20, 2022
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
1f90147
chore: initial scaffolding
deepak1556 Jul 19, 2022
8d6735e
chore: implement interface and docs
deepak1556 Jul 19, 2022
d09f507
chore: address code style review
deepak1556 Jul 22, 2022
7adacda
fix: cleanup of utility process on shutdown
deepak1556 Jul 22, 2022
f74d84d
chore: simplify NodeBindings::CreateEnvironment
deepak1556 Jul 22, 2022
a4caadb
chore: rename disableLibraryValidation => allowLoadingUnsignedLibraries
deepak1556 Jul 22, 2022
ebbd3e5
chore: implement process.parentPort
deepak1556 Aug 11, 2022
fe7d6d5
chore(posix): implement stdio pipe interface
deepak1556 Aug 17, 2022
5e08d9f
chore(win): implement stdio interface
deepak1556 Aug 19, 2022
36cb34a
chore: reenable SetNodeOptions for utility process
deepak1556 Aug 19, 2022
906428e
chore: add specs
deepak1556 Aug 22, 2022
0db19ab
chore: fix lint
deepak1556 Aug 22, 2022
9bed786
fix: update kill API
deepak1556 Sep 13, 2022
489d3ac
fix: update process.parentPort API
deepak1556 Sep 13, 2022
f1393cd
fix: exit event
deepak1556 Sep 13, 2022
1ce8398
docs: update exit event
deepak1556 Sep 13, 2022
540f067
fix: tests on linux
deepak1556 Sep 14, 2022
965c5e3
chore: expand on some comments
deepak1556 Sep 14, 2022
5cb954d
fix: shutdown of pipe reader
deepak1556 Sep 14, 2022
76d398f
fix: remove exit code check for crash spec
deepak1556 Sep 14, 2022
c3dff10
fix: rm PR_SET_NO_NEW_PRIVS for unsandbox utility process
deepak1556 Sep 14, 2022
c8010a8
chore: fix incorrect rebase
deepak1556 Sep 19, 2022
c5f131f
fix: address review feedback
deepak1556 Sep 23, 2022
356bf25
Merge remote-tracking branch 'origin/main' into robo/feat_enable_util…
deepak1556 Sep 28, 2022
c04d850
chore: rename utility_process -> utility
deepak1556 Sep 28, 2022
328ce01
chore: update docs
deepak1556 Sep 28, 2022
4a3a579
chore: cleanup c++ implemantation
deepak1556 Sep 28, 2022
e3b4c22
fix: leak in NodeServiceHost impl
deepak1556 Sep 29, 2022
1b3ac3f
chore: minor cleanup
deepak1556 Sep 29, 2022
8793136
chore: cleanup JS implementation
deepak1556 Sep 29, 2022
6430fc8
chore: flip default stdio to inherit
deepak1556 Sep 29, 2022
9b2c4c8
Merge remote-tracking branch 'origin' into robo/feat_enable_utility_p…
deepak1556 Oct 3, 2022
40a545b
fix: some api improvements
deepak1556 Oct 3, 2022
581bcc9
fix: add tests for cwd and env option
deepak1556 Oct 4, 2022
09b7ba8
Merge remote-tracking branch 'origin' into robo/feat_enable_utility_p…
deepak1556 Oct 4, 2022
6a3c11c
chore: alt impl for reading stdio handles
deepak1556 Oct 4, 2022
5aebc0b
chore: support message queuing
deepak1556 Oct 6, 2022
d7df0c0
Merge remote-tracking branch 'origin' into robo/feat_enable_utility_p…
deepak1556 Oct 6, 2022
98ac626
chore: fix lint
deepak1556 Oct 6, 2022
6616a24
chore: new UtilityProcess => utilityProcess.fork
deepak1556 Oct 6, 2022
82c6114
fix: support for uncaught exception exits
deepak1556 Oct 6, 2022
d33b0b0
chore: remove process.execArgv as default
deepak1556 Oct 6, 2022
014d6e8
fix: windows build
deepak1556 Oct 10, 2022
a82d322
fix: style changes
deepak1556 Oct 11, 2022
77eab25
Merge remote-tracking branch 'origin' into robo/feat_enable_utility_p…
deepak1556 Oct 11, 2022
8632413
Merge remote-tracking branch 'origin' into robo/feat_enable_utility_p…
deepak1556 Oct 12, 2022
f6b160a
fix: docs and style changes
deepak1556 Oct 12, 2022
a8c0069
Merge remote-tracking branch 'origin' into robo/feat_enable_utility_p…
deepak1556 Oct 19, 2022
c806238
chore: update patches
patchup[bot] Oct 19, 2022
e8f8dd8
spec: disable flaky test on win32 arm CI
deepak1556 Oct 19, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,23 @@ 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",
":electron_browser_bundle",
":electron_isolated_renderer_bundle",
":electron_renderer_bundle",
":electron_sandboxed_renderer_bundle",
":electron_utility_bundle",
":electron_worker_bundle",
]

Expand All @@ -218,6 +228,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",
]

Expand Down Expand Up @@ -368,6 +379,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",
Expand Down
4 changes: 4 additions & 0 deletions build/webpack/webpack.config.utility.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = require('./webpack.config.base')({
target: 'utility',
alwaysHasNode: true
});
44 changes: 44 additions & 0 deletions docs/api/parent-port.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# 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 = new UtilityProcess(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.

## Methods

### `parentPort.postMessage(message)`

* `message` any

Sends a message from the process to its parent.
6 changes: 6 additions & 0 deletions docs/api/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_

Expand All @@ -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:
Expand Down
128 changes: 128 additions & 0 deletions docs/api/utility-process.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# UtilityProcess
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the API is fully finalized, we should also update our explainer docs (e.g. Process Model, Tutorial series) to reflect the existence of this new process type.


`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.

`UtilityProcess` is an [EventEmitter][event-emitter].

## Class: UtilityProcess

> Child process with Node.js integration spawned by Chromium.

Process: [Main](../glossary.md#main-process)<br />

### `new UtilityProcess(modulePath[, args][, options])`
deepak1556 marked this conversation as resolved.
Show resolved Hide resolved

* `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`.
deepak1556 marked this conversation as resolved.
Show resolved Hide resolved
* `execArgv` string[] (optional) - List of string arguments passed to the executable. Default is `process.execArgv`.
* `cwd` string (optional) - Current working directory of the child process.
* `stdio` (string[] | string) (optional) - Child's stdout and stderr configuration. Default is `inherit`.
deepak1556 marked this conversation as resolved.
Show resolved Hide resolved
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 does not allow configuring
stdin and is always set to `ignore`. 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`.

### 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 = new UtilityProcess(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
zcbenz marked this conversation as resolved.
Show resolved Hide resolved
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 = new UtilityProcess(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
10 changes: 10 additions & 0 deletions docs/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
deepak1556 marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down Expand Up @@ -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
18 changes: 18 additions & 0 deletions filenames.auto.gni
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -331,4 +334,19 @@ 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",
"package.json",
"tsconfig.electron.json",
"tsconfig.json",
"typings/internal-ambient.d.ts",
"typings/internal-electron.d.ts",
]
}
6 changes: 6 additions & 0 deletions filenames.gni
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,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",
Expand Down Expand Up @@ -454,6 +456,8 @@ filenames = {
"shell/browser/net/web_request_api_interface.h",
"shell/browser/network_hints_handler_impl.cc",
"shell/browser/network_hints_handler_impl.h",
"shell/browser/node_service_host_impl.cc",
"shell/browser/node_service_host_impl.h",
"shell/browser/notifications/notification.cc",
"shell/browser/notifications/notification.h",
"shell/browser/notifications/notification_delegate.h",
Expand Down Expand Up @@ -679,6 +683,8 @@ 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/utility/electron_content_utility_client.cc",
"shell/utility/electron_content_utility_client.h",
]
Expand Down
1 change: 1 addition & 0 deletions lib/browser/api/module-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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') },
Expand Down