function exportIntoContentScope(win, obj, defineAs) { let clone = Cu.createObjectIn(win, { defineAs: defineAs }); let props = Object.getOwnPropertyNames(obj); for (let i = 0; i < props.length; i++) { let propName = props[i]; let propValue = obj[propName]; if (typeof propValue == "function") { Cu.exportFunction(propValue, clone, { defineAs: propName }); } } }
// Exports variables that will be accessed by the non-privileged scripts. function exportData(win, request) { let data = Cu.createObjectIn(win, { defineAs: "JSONView" }); data.debug = debug; data.json = new win.Text(); data.readyState = "uninitialized"; let Locale = { $STR: key => { try { return jsonViewStrings.GetStringFromName(key); } catch (err) { console.error(err); return undefined; } } }; data.Locale = Cu.cloneInto(Locale, win, {cloneFunctions: true}); let headers = { response: [], request: [] }; // The request doesn't have to be always nsIHttpChannel // (e.g. in case of data: URLs) if (request instanceof Ci.nsIHttpChannel) { request.visitResponseHeaders({ visitHeader: function (name, value) { headers.response.push({name: name, value: value}); } }); request.visitRequestHeaders({ visitHeader: function (name, value) { headers.request.push({name: name, value: value}); } }); } data.headers = Cu.cloneInto(headers, win); return data; }
const systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]. createInstance(Ci.nsIPrincipal); const FakeCu = function() { const sandbox = Cu.Sandbox(systemPrincipal, {wantXrays: false}); sandbox.toString = function() { return "[object BackstagePass]"; } this.sandbox = sandbox; } FakeCu.prototype = { ["import"](url, scope) { const {sandbox} = this; sandbox.__URI__ = url; const target = Cu.createObjectIn(sandbox); target.toString = sandbox.toString; Cu.evalInSandbox(`(function(){` + readURISync(url) + `\n})`, sandbox, "1.8", url).call(target); // Borrowed from mozJSComponentLoader.cpp to match errors closer. // https://github.com/mozilla/gecko-dev/blob/f6ca65e8672433b2ce1a0e7c31f72717930b5e27/js/xpconnect/loader/mozJSComponentLoader.cpp#L1205-L1208 if (!Array.isArray(target.EXPORTED_SYMBOLS)) { throw Error("EXPORTED_SYMBOLS is not an array."); } for (let key of target.EXPORTED_SYMBOLS) { scope[key] = target[key]; } return target; }
initialize: function WorkerSandbox(worker, window) { let model = {}; sandboxes.set(this, model); model.worker = worker; // We receive a wrapped window, that may be an xraywrapper if it's content let proto = window; // TODO necessary? // Ensure that `emit` has always the right `this` this.emit = this.emit.bind(this); this.emitSync = this.emitSync.bind(this); // Use expanded principal for content-script if the content is a // regular web content for better isolation. // (This behavior can be turned off for now with the unsafe-content-script // flag to give addon developers time for making the necessary changes) // But prevent it when the Worker isn't used for a content script but for // injecting `addon` object into a Panel scope, for example. // That's because: // 1/ It is useless to use multiple domains as the worker is only used // to communicate with the addon, // 2/ By using it it would prevent the document to have access to any JS // value of the worker. As JS values coming from multiple domain principals // can't be accessed by 'mono-principals' (principal with only one domain). // Even if this principal is for a domain that is specified in the multiple // domain principal. let principals = window; let wantGlobalProperties = []; let isSystemPrincipal = secMan.isSystemPrincipal( window.document.nodePrincipal); if (!isSystemPrincipal && !requiresAddonGlobal(worker)) { if (EXPANDED_PRINCIPALS.length > 0) { // We have to replace XHR constructor of the content document // with a custom cross origin one, automagically added by platform code: delete proto.XMLHttpRequest; wantGlobalProperties.push('XMLHttpRequest'); } if (!waiveSecurityMembrane) principals = EXPANDED_PRINCIPALS.concat(window); } // Create the sandbox and bind it to window in order for content scripts to // have access to all standard globals (window, document, ...) let content = sandbox(principals, { sandboxPrototype: proto, wantXrays: !requiresAddonGlobal(worker), wantGlobalProperties: wantGlobalProperties, wantExportHelpers: true, sameZoneAs: window, metadata: { SDKContentScript: true, 'inner-window-id': getInnerId(window) } }); model.sandbox = content; // We have to ensure that window.top and window.parent are the exact same // object than window object, i.e. the sandbox global object. But not // always, in case of iframes, top and parent are another window object. let top = window.top === window ? content : content.top; let parent = window.parent === window ? content : content.parent; merge(content, { // We need 'this === window === top' to be true in toplevel scope: get window() { return content; }, get top() { return top; }, get parent() { return parent; } }); // Use the Greasemonkey naming convention to provide access to the // unwrapped window object so the content script can access document // JavaScript values. // NOTE: this functionality is experimental and may change or go away // at any time! // // Note that because waivers aren't propagated between origins, we // need the unsafeWindow getter to live in the sandbox. var unsafeWindowGetter = new content.Function('return window.wrappedJSObject || window;'); Object.defineProperty(content, 'unsafeWindow', {get: unsafeWindowGetter}); // Load trusted code that will inject content script API. let ContentWorker = load(content, CONTENT_WORKER_URL); // prepare a clean `self.options` let options = 'contentScriptOptions' in worker ? JSON.stringify(worker.contentScriptOptions) : undefined; // Then call `inject` method and communicate with this script // by trading two methods that allow to send events to the other side: // - `onEvent` called by content script // - `result.emitToContent` called by addon script let onEvent = Cu.exportFunction(onContentEvent.bind(null, this), ContentWorker); let chromeAPI = createChromeAPI(ContentWorker); let result = Cu.waiveXrays(ContentWorker).inject(content, chromeAPI, onEvent, options); // Merge `emitToContent` into our private model of the // WorkerSandbox so we can communicate with content script model.emitToContent = result; let console = new PlainTextConsole(null, getInnerId(window)); // Handle messages send by this script: setListeners(this, console); // Inject `addon` global into target document if document is trusted, // `addon` in document is equivalent to `self` in content script. if (requiresAddonGlobal(worker)) { Object.defineProperty(getUnsafeWindow(window), 'addon', { value: content.self, configurable: true } ); } // Inject our `console` into target document if worker doesn't have a tab // (e.g Panel, PageWorker). // `worker.tab` can't be used because bug 804935. if (!isWindowInTab(window)) { let win = getUnsafeWindow(window); // export our chrome console to content window, as described here: // https://developer.mozilla.org/en-US/docs/Components.utils.createObjectIn let con = Cu.createObjectIn(win); let genPropDesc = function genPropDesc(fun) { return { enumerable: true, configurable: true, writable: true, value: console[fun] }; } const properties = { log: genPropDesc('log'), info: genPropDesc('info'), warn: genPropDesc('warn'), error: genPropDesc('error'), debug: genPropDesc('debug'), trace: genPropDesc('trace'), dir: genPropDesc('dir'), group: genPropDesc('group'), groupCollapsed: genPropDesc('groupCollapsed'), groupEnd: genPropDesc('groupEnd'), time: genPropDesc('time'), timeEnd: genPropDesc('timeEnd'), profile: genPropDesc('profile'), profileEnd: genPropDesc('profileEnd'), exception: genPropDesc('exception'), assert: genPropDesc('assert'), count: genPropDesc('count'), table: genPropDesc('table'), clear: genPropDesc('clear'), dirxml: genPropDesc('dirxml'), markTimeline: genPropDesc('markTimeline'), timeline: genPropDesc('timeline'), timelineEnd: genPropDesc('timelineEnd'), timeStamp: genPropDesc('timeStamp'), }; Object.defineProperties(con, properties); Cu.makeObjectPropsNormal(con); win.console = con; }; emit(events, "content-script-before-inserted", { window: window, worker: worker }); // The order of `contentScriptFile` and `contentScript` evaluation is // intentional, so programs can load libraries like jQuery from script URLs // and use them in scripts. let contentScriptFile = ('contentScriptFile' in worker) ? worker.contentScriptFile : null, contentScript = ('contentScript' in worker) ? worker.contentScript : null; if (contentScriptFile) importScripts.apply(null, [this].concat(contentScriptFile)); if (contentScript) { evaluateIn( this, Array.isArray(contentScript) ? contentScript.join(';\n') : contentScript ); } },
attach: function (window, innerId) { this._innerId = innerId, this._window = window; this._proto = Cu.createObjectIn(this._window); var id = this._scriptId; var uri = this._scriptCode; this._sandbox = sandbox(window, { sandboxName: uri, sandboxPrototype: this._proto, sameZoneAs: window, wantXrays: true, wantComponents: false, wantExportHelpers: false, metadata: { URI: uri, addonID: id, SDKDirectorScript: true, "inner-window-id": innerId } }); // create a CommonJS module object which match the interface from addon-sdk // (addon-sdk/sources/lib/toolkit/loader.js#L678-L686) var module = Cu.cloneInto(Object.create(null, { id: { enumerable: true, value: id }, uri: { enumerable: true, value: uri }, exports: { enumerable: true, value: Cu.createObjectIn(this._sandbox) } }), this._sandbox); // create a console API object let directorScriptConsole = new PlainTextConsole(null, this._innerId); // inject CommonJS module globals into the sandbox prototype Object.defineProperties(this._proto, { module: { enumerable: true, value: module }, exports: { enumerable: true, value: module.exports }, console: { enumerable: true, value: Cu.cloneInto(directorScriptConsole, this._sandbox, { cloneFunctions: true }) } }); Object.defineProperties(this._sandbox, { require: { enumerable: true, value: Cu.cloneInto(function () { throw Error("NOT IMPLEMENTED"); }, this._sandbox, { cloneFunctions: true }) } }); // TODO: if the debugger target is local, the debugger client could pass // to the director actor the resource url instead of the entire javascript source code. // evaluate the director script source in the sandbox evaluate(this._sandbox, this._scriptCode, "javascript:" + this._scriptCode); // prepare the messageport connected to the debugger client let { port1, port2 } = new this._window.MessageChannel(); // prepare the unload callbacks queue var sandboxOnUnloadQueue = this._sandboxOnUnloadQueue = []; // create the attach options var attachOptions = this._attachOptions = Cu.createObjectIn(this._sandbox); Object.defineProperties(attachOptions, { port: { enumerable: true, value: port1 }, window: { enumerable: true, value: window }, scriptOptions: { enumerable: true, value: Cu.cloneInto(this._scriptOptions, this._sandbox) }, onUnload: { enumerable: true, value: Cu.cloneInto(function (cb) { // collect unload callbacks if (typeof cb == "function") { sandboxOnUnloadQueue.push(cb); } }, this._sandbox, { cloneFunctions: true }) } }); // select the attach method var exports = this._proto.module.exports; if (this._scriptOptions && "attachMethod" in this._scriptOptions) { this._sandboxOnAttach = exports[this._scriptOptions.attachMethod]; } else { this._sandboxOnAttach = exports; } if (typeof this._sandboxOnAttach !== "function") { throw Error("the configured attachMethod '" + (this._scriptOptions.attachMethod || "module.exports") + "' is not exported by the directorScript"); } // call the attach method this._sandboxOnAttach.call(this._sandbox, attachOptions); return port2; },