Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
/*
* 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 {
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();
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;
}