Bug 1942239 - Add option to explicitly enable incremental origin initialization in...
[gecko.git] / toolkit / modules / subprocess / Subprocess.sys.mjs
blob2256be40a01d8d54329774968e2333c915eeec50
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/. */
7 /*
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.
11  */
13 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
14 import { SubprocessConstants } from "resource://gre/modules/subprocess/subprocess_common.sys.mjs";
16 const lazy = {};
18 if (AppConstants.platform == "win") {
19   ChromeUtils.defineESModuleGetters(lazy, {
20     SubprocessImpl: "resource://gre/modules/subprocess/subprocess_win.sys.mjs",
21   });
22 } else {
23   ChromeUtils.defineESModuleGetters(lazy, {
24     SubprocessImpl: "resource://gre/modules/subprocess/subprocess_unix.sys.mjs",
25   });
28 function encodeEnvVar(name, value) {
29   if (typeof name === "string" && typeof value === "string") {
30     return `${name}=${value}`;
31   }
33   let encoder = new TextEncoder();
34   function encode(val) {
35     return typeof val === "string" ? encoder.encode(val) : val;
36   }
38   return Uint8Array.of(...encode(name), ...encode("="), ...encode(value), 0);
41 function platformSupportsDisclaimedSpawn() {
42   return AppConstants.isPlatformAndVersionAtLeast("macosx", 18);
45 /**
46  * Allows for creation of and communication with OS-level sub-processes.
47  *
48  * @namespace
49  */
50 export var Subprocess = {
51   /**
52    * Launches a process, and returns a handle to it.
53    *
54    * @param {object} options
55    * An object describing the process to launch.
56    *
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.
60    *
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.
63    *
64    * @param {string[]} [options.arguments]
65    * A list of strings to pass as arguments to the process.
66    *
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.
71    *
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
77    * new process.
78    *
79    * @param {string} [options.stderr]
80    * Defines how the process's stderr output is handled. One of:
81    *
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.
86    *
87    * @param {string} [options.workdir]
88    *        The working directory in which to launch the new process.
89    *
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.
96    *
97    * @returns {Promise<Process>}
98    *
99    * @throws {Error}
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:
104    *
105    * - Subprocess.ERROR_BAD_EXECUTABLE: The given command could not
106    *   be found, or the file that it references is not executable.
107    *
108    * Note that if the process is successfully launched, but exits with
109    * a non-zero exit code, the promise will still resolve successfully.
110    */
111   call(options) {
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();
121     }
123     if (options.environment) {
124       Object.assign(environment, options.environment);
125     }
127     options.environment = Object.entries(environment)
128       .map(([key, val]) => (val !== null ? encodeEnvVar(key, val) : null))
129       .filter(s => s);
131     options.arguments = Array.from(options.arguments || []);
133     if (options.disclaim && !platformSupportsDisclaimedSpawn()) {
134       options.disclaim = false;
135     }
137     return Promise.resolve(
138       lazy.SubprocessImpl.isExecutableFile(options.command)
139     ).then(isExecutable => {
140       if (!isExecutable) {
141         let error = new Error(
142           `File at path "${options.command}" does not exist, or is not executable`
143         );
144         error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
145         throw error;
146       }
148       options.arguments.unshift(options.command);
150       return lazy.SubprocessImpl.call(options);
151     });
152   },
154   /**
155    * Returns an object with a key-value pair for every variable in the process's
156    * current environment.
157    *
158    * @returns {object}
159    */
160   getEnvironment() {
161     let environment = Object.create(null);
162     for (let [k, v] of lazy.SubprocessImpl.getEnvironment()) {
163       environment[k] = v;
164     }
165     return environment;
166   },
168   /**
169    * Searches for the given executable file in the system executable
170    * file paths as specified by the PATH environment variable.
171    *
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.
176    *
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
182    *        is used.
183    * @returns {Promise<string>}
184    */
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);
190   },
192   /**
193    * Connect to an already-running subprocess
194    * given the file descriptors for its stdin, stdout and stderr.
195    *
196    * @param {number[]} fds
197    *        A list of three file descriptors [stdin, stdout, stderr].
198    *
199    * @returns {Promise<Process>}
200    */
201   connectRunning(fds) {
202     return lazy.SubprocessImpl.connectRunning(fds);
203   },
206 Object.assign(Subprocess, SubprocessConstants);
207 Object.freeze(Subprocess);
209 export function getSubprocessImplForTest() {
210   return lazy.SubprocessImpl;