1 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
2 /* vim: set sts=2 sw=2 et tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 * These modules are loosely based on the subprocess.jsm module created
9 * by Jan Gerber and Patrick Brunschwig, though the implementation
10 * differs drastically.
13 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
14 import { SubprocessConstants } from "resource://gre/modules/subprocess/subprocess_common.sys.mjs";
18 if (AppConstants.platform == "win") {
19 ChromeUtils.defineESModuleGetters(lazy, {
20 SubprocessImpl: "resource://gre/modules/subprocess/subprocess_win.sys.mjs",
23 ChromeUtils.defineESModuleGetters(lazy, {
24 SubprocessImpl: "resource://gre/modules/subprocess/subprocess_unix.sys.mjs",
28 function encodeEnvVar(name, value) {
29 if (typeof name === "string" && typeof value === "string") {
30 return `${name}=${value}`;
33 let encoder = new TextEncoder();
34 function encode(val) {
35 return typeof val === "string" ? encoder.encode(val) : val;
38 return Uint8Array.of(...encode(name), ...encode("="), ...encode(value), 0);
41 function platformSupportsDisclaimedSpawn() {
42 return AppConstants.isPlatformAndVersionAtLeast("macosx", 18);
46 * Allows for creation of and communication with OS-level sub-processes.
50 export var Subprocess = {
52 * Launches a process, and returns a handle to it.
54 * @param {object} options
55 * An object describing the process to launch.
57 * @param {string} options.command
58 * The full path of the executable to launch. Relative paths are not
59 * accepted, and `$PATH` is not searched.
61 * If a path search is necessary, the {@link Subprocess.pathSearch} method may
62 * be used to map a bare executable name to a full path.
64 * @param {string[]} [options.arguments]
65 * A list of strings to pass as arguments to the process.
67 * @param {object} [options.environment] An object containing a key
68 * and value for each environment variable to pass to the
69 * process. Values that are `=== null` are ignored. Only the
70 * object's own, enumerable properties are added to the environment.
72 * @param {boolean} [options.environmentAppend] If true, append the
73 * environment variables passed in `environment` to the existing set
74 * of environment variables. Values that are `=== null` are removed
75 * from the environment. Otherwise, the values in 'environment'
76 * constitute the entire set of environment variables passed to the
79 * @param {string} [options.stderr]
80 * Defines how the process's stderr output is handled. One of:
82 * - `"ignore"`: (default) The process's standard error is not redirected.
83 * - `"stdout"`: The process's stderr is merged with its stdout.
84 * - `"pipe"`: The process's stderr is redirected to a pipe, which can be read
85 * from via its `stderr` property.
87 * @param {string} [options.workdir]
88 * The working directory in which to launch the new process.
90 * @param {boolean} [options.disclaim]
91 * macOS-specific option for 10.14+ OS versions. If true, enables a
92 * macOS-specific process launch option allowing the parent process to
93 * disclaim responsibility for the child process with respect to privacy/
94 * security permission prompts and decisions. This option is ignored on
95 * platforms that do not support it.
97 * @returns {Promise<Process>}
100 * May be rejected with an Error object if the process can not be
101 * launched. The object will include an `errorCode` property with
102 * one of the following values if it was rejected for the
103 * corresponding reason:
105 * - Subprocess.ERROR_BAD_EXECUTABLE: The given command could not
106 * be found, or the file that it references is not executable.
108 * Note that if the process is successfully launched, but exits with
109 * a non-zero exit code, the promise will still resolve successfully.
112 options = Object.assign({}, options);
114 options.stderr = options.stderr || "ignore";
115 options.workdir = options.workdir || null;
116 options.disclaim = options.disclaim || false;
118 let environment = {};
119 if (!options.environment || options.environmentAppend) {
120 environment = this.getEnvironment();
123 if (options.environment) {
124 Object.assign(environment, options.environment);
127 options.environment = Object.entries(environment)
128 .map(([key, val]) => (val !== null ? encodeEnvVar(key, val) : null))
131 options.arguments = Array.from(options.arguments || []);
133 if (options.disclaim && !platformSupportsDisclaimedSpawn()) {
134 options.disclaim = false;
137 return Promise.resolve(
138 lazy.SubprocessImpl.isExecutableFile(options.command)
139 ).then(isExecutable => {
141 let error = new Error(
142 `File at path "${options.command}" does not exist, or is not executable`
144 error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
148 options.arguments.unshift(options.command);
150 return lazy.SubprocessImpl.call(options);
155 * Returns an object with a key-value pair for every variable in the process's
156 * current environment.
161 let environment = Object.create(null);
162 for (let [k, v] of lazy.SubprocessImpl.getEnvironment()) {
169 * Searches for the given executable file in the system executable
170 * file paths as specified by the PATH environment variable.
172 * On Windows, if the unadorned filename cannot be found, the
173 * extensions in the semicolon-separated list in the PATHSEP
174 * environment variable are successively appended to the original
175 * name and searched for in turn.
177 * @param {string} command
178 * The name of the executable to find.
179 * @param {object} [environment]
180 * An object containing a key for each environment variable to be used
181 * in the search. If not provided, full the current process environment
183 * @returns {Promise<string>}
185 pathSearch(command, environment = this.getEnvironment()) {
186 // Promise.resolve lets us get around returning one of the Promise.jsm
187 // pseudo-promises returned by Task.jsm.
188 let path = lazy.SubprocessImpl.pathSearch(command, environment);
189 return Promise.resolve(path);
193 * Connect to an already-running subprocess
194 * given the file descriptors for its stdin, stdout and stderr.
196 * @param {number[]} fds
197 * A list of three file descriptors [stdin, stdout, stderr].
199 * @returns {Promise<Process>}
201 connectRunning(fds) {
202 return lazy.SubprocessImpl.connectRunning(fds);
206 Object.assign(Subprocess, SubprocessConstants);
207 Object.freeze(Subprocess);
209 export function getSubprocessImplForTest() {
210 return lazy.SubprocessImpl;