Files
tubestation/browser/extensions/shield-recipe-client/lib/ActionSandboxManager.jsm
2017-04-27 12:30:07 -07:00

85 lines
3.0 KiB
JavaScript

/* 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/. */
"use strict";
const {utils: Cu} = Components;
Cu.import("resource://shield-recipe-client/lib/NormandyDriver.jsm");
Cu.import("resource://shield-recipe-client/lib/SandboxManager.jsm");
Cu.import("resource://shield-recipe-client/lib/LogManager.jsm");
this.EXPORTED_SYMBOLS = ["ActionSandboxManager"];
const log = LogManager.getLogger("recipe-sandbox-manager");
/**
* An extension to SandboxManager that prepares a sandbox for executing
* Normandy actions.
*
* Actions register a set of named callbacks, which this class makes available
* for execution. This allows a single action script to define multiple,
* independent steps that execute in isolated sandboxes.
*
* Callbacks are assumed to be async and must return Promises.
*/
this.ActionSandboxManager = class extends SandboxManager {
constructor(actionScript) {
super();
// Prepare the sandbox environment
const driver = new NormandyDriver(this);
this.cloneIntoGlobal("sandboxedDriver", driver, {cloneFunctions: true});
this.evalInSandbox(`
// Shim old API for registering actions
function registerAction(name, Action) {
registerAsyncCallback("action", (driver, recipe) => {
return new Action(driver, recipe).execute();
});
};
this.asyncCallbacks = new Map();
function registerAsyncCallback(name, callback) {
asyncCallbacks.set(name, callback);
}
this.window = this;
this.setTimeout = sandboxedDriver.setTimeout;
this.clearTimeout = sandboxedDriver.clearTimeout;
`);
this.evalInSandbox(actionScript);
}
/**
* Execute a callback in the sandbox with the given name. If the script does
* not register a callback with the given name, we log a message and return.
* @param {String} callbackName
* @param {...*} [args]
* Remaining arguments are cloned into the sandbox and passed as arguments
* to the callback.
* @resolves
* The return value of the callback, cloned into the current compartment, or
* undefined if a matching callback was not found.
* @rejects
* If the sandbox rejects, an error object with the message from the sandbox
* error. Due to sandbox limitations, the stack trace is not preserved.
*/
async runAsyncCallback(callbackName, ...args) {
const callbackWasRegistered = this.evalInSandbox(`asyncCallbacks.has("${callbackName}")`);
if (!callbackWasRegistered) {
log.debug(`Script did not register a callback with the name "${callbackName}"`);
return undefined;
}
this.cloneIntoGlobal("callbackArgs", args);
try {
const result = await this.evalInSandbox(`
asyncCallbacks.get("${callbackName}")(sandboxedDriver, ...callbackArgs);
`);
return Cu.cloneInto(result, {});
} catch (err) {
throw new Error(Cu.cloneInto(err.message, {}));
}
}
};