summaryrefslogtreecommitdiffstats
path: root/toolkit/modules/subprocess/Subprocess.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/modules/subprocess/Subprocess.sys.mjs')
-rw-r--r--toolkit/modules/subprocess/Subprocess.sys.mjs201
1 files changed, 201 insertions, 0 deletions
diff --git a/toolkit/modules/subprocess/Subprocess.sys.mjs b/toolkit/modules/subprocess/Subprocess.sys.mjs
new file mode 100644
index 0000000000..aced4a076c
--- /dev/null
+++ b/toolkit/modules/subprocess/Subprocess.sys.mjs
@@ -0,0 +1,201 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * These modules are loosely based on the subprocess.jsm module created
+ * by Jan Gerber and Patrick Brunschwig, though the implementation
+ * differs drastically.
+ */
+
+import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
+import { SubprocessConstants } from "resource://gre/modules/subprocess/subprocess_common.sys.mjs";
+
+const lazy = {};
+
+if (AppConstants.platform == "win") {
+ ChromeUtils.defineESModuleGetters(lazy, {
+ SubprocessImpl: "resource://gre/modules/subprocess/subprocess_win.sys.mjs",
+ });
+} else {
+ // Ignore the "duplicate" definitions here as this are also defined
+ // in the "win" block above.
+ // eslint-disable-next-line mozilla/valid-lazy
+ ChromeUtils.defineESModuleGetters(lazy, {
+ SubprocessImpl: "resource://gre/modules/subprocess/subprocess_unix.sys.mjs",
+ });
+}
+
+function encodeEnvVar(name, value) {
+ if (typeof name === "string" && typeof value === "string") {
+ return `${name}=${value}`;
+ }
+
+ let encoder = new TextEncoder("utf-8");
+ function encode(val) {
+ return typeof val === "string" ? encoder.encode(val) : val;
+ }
+
+ return Uint8Array.of(...encode(name), ...encode("="), ...encode(value), 0);
+}
+
+function platformSupportsDisclaimedSpawn() {
+ return AppConstants.isPlatformAndVersionAtLeast("macosx", 18);
+}
+
+/**
+ * Allows for creation of and communication with OS-level sub-processes.
+ *
+ * @namespace
+ */
+export var Subprocess = {
+ /**
+ * Launches a process, and returns a handle to it.
+ *
+ * @param {object} options
+ * An object describing the process to launch.
+ *
+ * @param {string} options.command
+ * The full path of the executable to launch. Relative paths are not
+ * accepted, and `$PATH` is not searched.
+ *
+ * If a path search is necessary, the {@link Subprocess.pathSearch} method may
+ * be used to map a bare executable name to a full path.
+ *
+ * @param {string[]} [options.arguments]
+ * A list of strings to pass as arguments to the process.
+ *
+ * @param {object} [options.environment] An object containing a key
+ * and value for each environment variable to pass to the
+ * process. Values that are `=== null` are ignored. Only the
+ * object's own, enumerable properties are added to the environment.
+ *
+ * @param {boolean} [options.environmentAppend] If true, append the
+ * environment variables passed in `environment` to the existing set
+ * of environment variables. Values that are `=== null` are removed
+ * from the environment. Otherwise, the values in 'environment'
+ * constitute the entire set of environment variables passed to the
+ * new process.
+ *
+ * @param {string} [options.stderr]
+ * Defines how the process's stderr output is handled. One of:
+ *
+ * - `"ignore"`: (default) The process's standard error is not redirected.
+ * - `"stdout"`: The process's stderr is merged with its stdout.
+ * - `"pipe"`: The process's stderr is redirected to a pipe, which can be read
+ * from via its `stderr` property.
+ *
+ * @param {string} [options.workdir]
+ * The working directory in which to launch the new process.
+ *
+ * @param {boolean} [options.disclaim]
+ * macOS-specific option for 10.14+ OS versions. If true, enables a
+ * macOS-specific process launch option allowing the parent process to
+ * disclaim responsibility for the child process with respect to privacy/
+ * security permission prompts and decisions. This option is ignored on
+ * platforms that do not support it.
+ *
+ * @returns {Promise<Process>}
+ *
+ * @throws {Error}
+ * May be rejected with an Error object if the process can not be
+ * launched. The object will include an `errorCode` property with
+ * one of the following values if it was rejected for the
+ * corresponding reason:
+ *
+ * - Subprocess.ERROR_BAD_EXECUTABLE: The given command could not
+ * be found, or the file that it references is not executable.
+ *
+ * Note that if the process is successfully launched, but exits with
+ * a non-zero exit code, the promise will still resolve successfully.
+ */
+ call(options) {
+ options = Object.assign({}, options);
+
+ options.stderr = options.stderr || "ignore";
+ options.workdir = options.workdir || null;
+ options.disclaim = options.disclaim || false;
+
+ let environment = {};
+ if (!options.environment || options.environmentAppend) {
+ environment = this.getEnvironment();
+ }
+
+ if (options.environment) {
+ Object.assign(environment, options.environment);
+ }
+
+ options.environment = Object.entries(environment)
+ .map(([key, val]) => (val !== null ? encodeEnvVar(key, val) : null))
+ .filter(s => s);
+
+ options.arguments = Array.from(options.arguments || []);
+
+ if (options.disclaim && !platformSupportsDisclaimedSpawn()) {
+ options.disclaim = false;
+ }
+
+ return Promise.resolve(
+ lazy.SubprocessImpl.isExecutableFile(options.command)
+ ).then(isExecutable => {
+ if (!isExecutable) {
+ let error = new Error(
+ `File at path "${options.command}" does not exist, or is not executable`
+ );
+ error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
+ throw error;
+ }
+
+ options.arguments.unshift(options.command);
+
+ return lazy.SubprocessImpl.call(options);
+ });
+ },
+
+ /**
+ * Returns an object with a key-value pair for every variable in the process's
+ * current environment.
+ *
+ * @returns {object}
+ */
+ getEnvironment() {
+ let environment = Object.create(null);
+ for (let [k, v] of lazy.SubprocessImpl.getEnvironment()) {
+ environment[k] = v;
+ }
+ return environment;
+ },
+
+ /**
+ * Searches for the given executable file in the system executable
+ * file paths as specified by the PATH environment variable.
+ *
+ * On Windows, if the unadorned filename cannot be found, the
+ * extensions in the semicolon-separated list in the PATHSEP
+ * environment variable are successively appended to the original
+ * name and searched for in turn.
+ *
+ * @param {string} command
+ * The name of the executable to find.
+ * @param {object} [environment]
+ * An object containing a key for each environment variable to be used
+ * in the search. If not provided, full the current process environment
+ * is used.
+ * @returns {Promise<string>}
+ */
+ pathSearch(command, environment = this.getEnvironment()) {
+ // Promise.resolve lets us get around returning one of the Promise.jsm
+ // pseudo-promises returned by Task.jsm.
+ let path = lazy.SubprocessImpl.pathSearch(command, environment);
+ return Promise.resolve(path);
+ },
+};
+
+Object.assign(Subprocess, SubprocessConstants);
+Object.freeze(Subprocess);
+
+export function getSubprocessImplForTest() {
+ return lazy.SubprocessImpl;
+}