diff --git a/BUILD.gn b/BUILD.gn
index c3d7f685bc8..59df0e17343 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -202,6 +202,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 +218,7 @@ action("electron_js2c") {
":electron_isolated_renderer_bundle",
":electron_renderer_bundle",
":electron_sandboxed_renderer_bundle",
+ ":electron_utility_bundle",
":electron_worker_bundle",
]
@@ -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",
]
@@ -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",
diff --git a/build/webpack/webpack.config.utility.js b/build/webpack/webpack.config.utility.js
new file mode 100644
index 00000000000..a80775d18eb
--- /dev/null
+++ b/build/webpack/webpack.config.utility.js
@@ -0,0 +1,4 @@
+module.exports = require('./webpack.config.base')({
+ target: 'utility',
+ alwaysHasNode: true
+});
diff --git a/docs/api/parent-port.md b/docs/api/parent-port.md
new file mode 100644
index 00000000000..9c6ad3a08d6
--- /dev/null
+++ b/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.
diff --git a/docs/api/process.md b/docs/api/process.md
index 45738999129..9cc34c691e1 100644
--- a/docs/api/process.md
+++ b/docs/api/process.md
@@ -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:
diff --git a/docs/api/utility-process.md b/docs/api/utility-process.md
new file mode 100644
index 00000000000..147f970810e
--- /dev/null
+++ b/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)
+
+## 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
diff --git a/docs/glossary.md b/docs/glossary.md
index 893c598c52b..01ba457ff88 100644
--- a/docs/glossary.md
+++ b/docs/glossary.md
@@ -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
diff --git a/filenames.auto.gni b/filenames.auto.gni
index a7c71081f4e..f34e4c88ccd 100644
--- a/filenames.auto.gni
+++ b/filenames.auto.gni
@@ -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",
+ ]
}
diff --git a/filenames.gni b/filenames.gni
index 994fc5275ac..bd2f91fd6ac 100644
--- a/filenames.gni
+++ b/filenames.gni
@@ -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",
]
diff --git a/lib/browser/api/module-list.ts b/lib/browser/api/module-list.ts
index be516aec288..88c8ea7e338 100644
--- a/lib/browser/api/module-list.ts
+++ b/lib/browser/api/module-list.ts
@@ -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') },
diff --git a/lib/browser/api/utility-process.ts b/lib/browser/api/utility-process.ts
new file mode 100644
index 00000000000..8027a548eb8
--- /dev/null
+++ b/lib/browser/api/utility-process.ts
@@ -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);
+}
diff --git a/lib/common/init.ts b/lib/common/init.ts
index 8e1245dd6ac..630a5f910ea 100644
--- a/lib/common/init.ts
+++ b/lib/common/init.ts
@@ -33,20 +33,29 @@ function wrap (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;
}
diff --git a/lib/utility/.eslintrc.json b/lib/utility/.eslintrc.json
new file mode 100644
index 00000000000..dab1dafc3f2
--- /dev/null
+++ b/lib/utility/.eslintrc.json
@@ -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/*"
+ ]
+ }
+ ]
+ }
+}
diff --git a/lib/utility/api/exports/electron.ts b/lib/utility/api/exports/electron.ts
new file mode 100644
index 00000000000..3e37918b66c
--- /dev/null
+++ b/lib/utility/api/exports/electron.ts
@@ -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);
diff --git a/lib/utility/api/module-list.ts b/lib/utility/api/module-list.ts
new file mode 100644
index 00000000000..53f2e5d9bf8
--- /dev/null
+++ b/lib/utility/api/module-list.ts
@@ -0,0 +1,2 @@
+// Utility side modules, please sort alphabetically.
+export const utilityNodeModuleList: ElectronInternal.ModuleEntry[] = [];
diff --git a/lib/utility/init.ts b/lib/utility/init.ts
new file mode 100644
index 00000000000..d360dd514e5
--- /dev/null
+++ b/lib/utility/init.ts
@@ -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);
diff --git a/lib/utility/parent-port.ts b/lib/utility/parent-port.ts
new file mode 100644
index 00000000000..ba0f2fd8714
--- /dev/null
+++ b/lib/utility/parent-port.ts
@@ -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);
+ }
+}
diff --git a/patches/chromium/.patches b/patches/chromium/.patches
index bae4246c941..9e25d21ab88 100644
--- a/patches/chromium/.patches
+++ b/patches/chromium/.patches
@@ -115,6 +115,7 @@ 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
diff --git a/patches/chromium/allow_new_privileges_in_unsandboxed_child_processes.patch b/patches/chromium/allow_new_privileges_in_unsandboxed_child_processes.patch
index 84f527f5617..1e8e701c77d 100644
--- a/patches/chromium/allow_new_privileges_in_unsandboxed_child_processes.patch
+++ b/patches/chromium/allow_new_privileges_in_unsandboxed_child_processes.patch
@@ -3,28 +3,28 @@ From: Jeremy Apthorp
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);
diff --git a/patches/chromium/feat_configure_launch_options_for_service_process.patch b/patches/chromium/feat_configure_launch_options_for_service_process.patch
new file mode 100644
index 00000000000..b349ea8dfff
--- /dev/null
+++ b/patches/chromium/feat_configure_launch_options_for_service_process.patch
@@ -0,0 +1,671 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: deepak1556
+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
++
+ 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(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 delegate =
+ std::make_unique(
+- 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 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 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 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 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);
+
++ // 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 child_flags;
+ std::vector 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 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);
+
diff --git a/script/gen-filenames.ts b/script/gen-filenames.ts
index 96812d82a34..3e1f41bcc8a 100644
--- a/script/gen-filenames.ts
+++ b/script/gen-filenames.ts
@@ -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'
}
];
diff --git a/shell/browser/api/electron_api_app.cc b/shell/browser/api/electron_api_app.cc
index 2ba1e0adbe8..319b59fab5e 100644
--- a/shell/browser/api/electron_api_app.cc
+++ b/shell/browser/api/electron_api_app.cc
@@ -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);
}
diff --git a/shell/browser/api/electron_api_utility_process.cc b/shell/browser/api/electron_api_utility_process.cc
new file mode 100644
index 00000000000..f70cac01c6a
--- /dev/null
+++ b/shell/browser/api/electron_api_utility_process.cc
@@ -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