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 #36089

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Expand Up @@ -210,13 +210,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 @@ -226,6 +236,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 @@ -406,6 +417,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
@@ -0,0 +1,4 @@
module.exports = require('./webpack.config.base')({
target: 'utility',
alwaysHasNode: true
});
46 changes: 46 additions & 0 deletions docs/api/parent-port.md
@@ -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.
6 changes: 6 additions & 0 deletions docs/api/process.md
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
136 changes: 136 additions & 0 deletions docs/api/utility-process.md
@@ -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
10 changes: 10 additions & 0 deletions docs/glossary.md
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.
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
17 changes: 17 additions & 0 deletions docs/tutorial/process-model.md
Expand Up @@ -214,8 +214,25 @@ This feature is incredibly useful for two main purposes:
URL, you can add custom properties onto the renderer's `window` global that can
be used for desktop-only logic on the web client's side.

## The utility process

Each Electron app can spawn multiple child processes from the main process using
the [UtilityProcess][] API. The utility process runs in a Node.js environment,
meaning it has the ability to `require` modules and use all of Node.js APIs.
The utility process can be used to host for example: untrusted services,
CPU intensive tasks or crash prone components which would have previously
been hosted in the main process or process spawned with Node.js [`child_process.fork`][] API.
The primary difference between the utility process and process spawned by Node.js
child_process module is that the utility process can establish a communication
channel with a renderer process using [`MessagePort`][]s. An Electron app can
always prefer the [UtilityProcess][] API over Node.js [`child_process.fork`][] API when
there is need to fork a child process from the main process.

[window-mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Window
[`MessagePort`]: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort
[`child_process.fork`]: https://nodejs.org/dist/latest-v16.x/docs/api/child_process.html#child_processforkmodulepath-args-options
[context-isolation]: ./context-isolation.md
[context-bridge]: ../api/context-bridge.md
[ipcrenderer]: ../api/ipc-renderer.md
[UtilityProcess]: ../api/utility-process.md
[tutorial]: ./tutorial-1-prerequisites.md
19 changes: 19 additions & 0 deletions filenames.auto.gni
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,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",
]
}
6 changes: 6 additions & 0 deletions filenames.gni
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 @@ -678,6 +680,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",
]
Expand Down
1 change: 1 addition & 0 deletions lib/browser/api/module-list.ts
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