forked from mirrors/gecko-dev
MozReview-Commit-ID: 45Tfs2ZZ06r --HG-- extra : rebase_source : f76738612cb5f78787e9fae8f8c563d5ff5f33d1
118 lines
3.6 KiB
JavaScript
118 lines
3.6 KiB
JavaScript
var EXPORTED_SYMBOLS = ["SandboxManager"];
|
|
|
|
/**
|
|
* A wrapper class with helper methods for manipulating a sandbox.
|
|
*
|
|
* Along with convenient utility methods, SandboxManagers maintain a list of
|
|
* "holds", which prevent the sandbox from being nuked until all registered
|
|
* holds are removed. This allows sandboxes to trigger async operations and
|
|
* automatically nuke themselves when they're done.
|
|
*/
|
|
var SandboxManager = class {
|
|
constructor() {
|
|
this._sandbox = new Cu.Sandbox(null, {
|
|
wantComponents: false,
|
|
wantGlobalProperties: ["URL", "URLSearchParams"],
|
|
});
|
|
this.holds = [];
|
|
}
|
|
|
|
get sandbox() {
|
|
if (this._sandbox) {
|
|
return this._sandbox;
|
|
}
|
|
throw new Error("Tried to use sandbox after it was nuked");
|
|
}
|
|
|
|
addHold(name) {
|
|
this.holds.push(name);
|
|
}
|
|
|
|
removeHold(name) {
|
|
const index = this.holds.indexOf(name);
|
|
if (index === -1) {
|
|
throw new Error(`Tried to remove non-existant hold "${name}"`);
|
|
}
|
|
this.holds.splice(index, 1);
|
|
this.tryCleanup();
|
|
}
|
|
|
|
cloneInto(value, options = {}) {
|
|
return Cu.cloneInto(value, this.sandbox, options);
|
|
}
|
|
|
|
cloneIntoGlobal(name, value, options = {}) {
|
|
const clonedValue = Cu.cloneInto(value, this.sandbox, options);
|
|
this.addGlobal(name, clonedValue);
|
|
return clonedValue;
|
|
}
|
|
|
|
addGlobal(name, value) {
|
|
this.sandbox[name] = value;
|
|
}
|
|
|
|
evalInSandbox(script) {
|
|
return Cu.evalInSandbox(script, this.sandbox);
|
|
}
|
|
|
|
tryCleanup() {
|
|
if (this.holds.length === 0) {
|
|
const sandbox = this._sandbox;
|
|
this._sandbox = null;
|
|
Cu.nukeSandbox(sandbox);
|
|
}
|
|
}
|
|
|
|
isNuked() {
|
|
// Do this in a promise, so other async things can resolve.
|
|
return new Promise((resolve, reject) => {
|
|
if (!this._sandbox) {
|
|
resolve();
|
|
} else {
|
|
reject(new Error(`Sandbox is not nuked. Holds left: ${this.holds}`));
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Wraps a function that returns a Promise from a privileged (i.e. chrome)
|
|
* context and returns a Promise from this SandboxManager's sandbox. Useful
|
|
* for exposing privileged functions to the sandbox, since the sandbox can't
|
|
* access properties on privileged objects, e.g. Promise.then on a privileged
|
|
* Promise.
|
|
* @param {Function} wrappedFunction
|
|
* @param {Object} [options]
|
|
* @param {boolean} [options.cloneInto=false]
|
|
* If true, the value resolved by the privileged Promise is cloned into the
|
|
* sandbox before being resolved by the sandbox Promise. Without this, the
|
|
* result will be Xray-wrapped.
|
|
* @param {boolean} [options.cloneArguments=false]
|
|
* If true, the arguments passed to wrappedFunction will be cloned into the
|
|
* privileged chrome context. If wrappedFunction holds a reference to any of
|
|
* its arguments, you will need this to avoid losing access to the arguments
|
|
* when the sandbox they originate from is nuked.
|
|
* @return {Function}
|
|
*/
|
|
wrapAsync(wrappedFunction, options = {cloneInto: false, cloneArguments: false}) {
|
|
// In order for `this` to work in wrapped functions, we must return a
|
|
// non-arrow function, which requires saving a reference to the manager.
|
|
const sandboxManager = this;
|
|
return function(...args) {
|
|
return new sandboxManager.sandbox.Promise((resolve, reject) => {
|
|
if (options.cloneArguments) {
|
|
args = Cu.cloneInto(args, {});
|
|
}
|
|
|
|
wrappedFunction.apply(this, args).then(result => {
|
|
if (options.cloneInto) {
|
|
result = sandboxManager.cloneInto(result);
|
|
}
|
|
|
|
resolve(result);
|
|
}, err => {
|
|
reject(new sandboxManager.sandbox.Error(err.message, err.fileName, err.lineNumber));
|
|
});
|
|
});
|
|
};
|
|
}
|
|
};
|