diff options
author | Matt A. Tobin <email@mattatobin.com> | 2018-02-09 06:46:43 -0500 |
---|---|---|
committer | Matt A. Tobin <email@mattatobin.com> | 2018-02-09 06:46:43 -0500 |
commit | ac46df8daea09899ce30dc8fd70986e258c746bf (patch) | |
tree | 2750d3125fc253fd5b0671e4bd268eff1fd97296 /addon-sdk/source/lib/sdk/content/sandbox.js | |
parent | 8cecf8d5208f3945b35f879bba3015bb1a11bec6 (diff) | |
download | UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar.gz UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar.lz UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar.xz UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.zip |
Move Add-on SDK source to toolkit/jetpack
Diffstat (limited to 'addon-sdk/source/lib/sdk/content/sandbox.js')
-rw-r--r-- | addon-sdk/source/lib/sdk/content/sandbox.js | 426 |
1 files changed, 0 insertions, 426 deletions
diff --git a/addon-sdk/source/lib/sdk/content/sandbox.js b/addon-sdk/source/lib/sdk/content/sandbox.js deleted file mode 100644 index 096ba5c87..000000000 --- a/addon-sdk/source/lib/sdk/content/sandbox.js +++ /dev/null @@ -1,426 +0,0 @@ -/* 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'; - -module.metadata = { - 'stability': 'unstable' -}; - -const { Class } = require('../core/heritage'); -const { EventTarget } = require('../event/target'); -const { on, off, emit } = require('../event/core'); -const { events } = require('./sandbox/events'); -const { requiresAddonGlobal } = require('./utils'); -const { delay: async } = require('../lang/functional'); -const { Ci, Cu, Cc } = require('chrome'); -const timer = require('../timers'); -const { URL } = require('../url'); -const { sandbox, evaluate, load } = require('../loader/sandbox'); -const { merge } = require('../util/object'); -const { getTabForContentWindowNoShim } = require('../tabs/utils'); -const { getInnerId } = require('../window/utils'); -const { PlainTextConsole } = require('../console/plain-text'); -const { data } = require('../self');const { isChildLoader } = require('../remote/core'); -// WeakMap of sandboxes so we can access private values -const sandboxes = new WeakMap(); - -/* Trick the linker in order to ensure shipping these files in the XPI. - require('./content-worker.js'); - Then, retrieve URL of these files in the XPI: -*/ -var prefix = module.uri.split('sandbox.js')[0]; -const CONTENT_WORKER_URL = prefix + 'content-worker.js'; -const metadata = require('@loader/options').metadata; - -// Fetch additional list of domains to authorize access to for each content -// script. It is stored in manifest `metadata` field which contains -// package.json data. This list is originaly defined by authors in -// `permissions` attribute of their package.json addon file. -const permissions = (metadata && metadata['permissions']) || {}; -const EXPANDED_PRINCIPALS = permissions['cross-domain-content'] || []; - -const waiveSecurityMembrane = !!permissions['unsafe-content-script']; - -const nsIScriptSecurityManager = Ci.nsIScriptSecurityManager; -const secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]. - getService(Ci.nsIScriptSecurityManager); - -const JS_VERSION = '1.8'; - -// Tests whether this window is loaded in a tab -function isWindowInTab(window) { - if (isChildLoader) { - let { frames } = require('../remote/child'); - let frame = frames.getFrameForWindow(window.top); - return frame && frame.isTab; - } - else { - // The deprecated sync worker API still does everything in the main process - return getTabForContentWindowNoShim(window); - } -} - -const WorkerSandbox = Class({ - implements: [ EventTarget ], - - /** - * Emit a message to the worker content sandbox - */ - emit: function emit(type, ...args) { - // JSON.stringify is buggy with cross-sandbox values, - // it may return "{}" on functions. Use a replacer to match them correctly. - let replacer = (k, v) => - typeof(v) === "function" - ? (type === "console" ? Function.toString.call(v) : void(0)) - : v; - - // Ensure having an asynchronous behavior - async(() => - emitToContent(this, JSON.stringify([type, ...args], replacer)) - ); - }, - - /** - * Synchronous version of `emit`. - * /!\ Should only be used when it is strictly mandatory /!\ - * Doesn't ensure passing only JSON values. - * Mainly used by context-menu in order to avoid breaking it. - */ - emitSync: function emitSync(...args) { - // because the arguments could be also non JSONable values, - // we need to ensure the array instance is created from - // the content's sandbox - return emitToContent(this, new modelFor(this).sandbox.Array(...args)); - }, - - /** - * Configures sandbox and loads content scripts into it. - * @param {Worker} worker - * content worker - */ - 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 - ); - } - }, - destroy: function destroy(reason) { - if (typeof reason != 'string') - reason = ''; - this.emitSync('event', 'detach', reason); - let model = modelFor(this); - model.sandbox = null - model.worker = null; - }, - -}); - -exports.WorkerSandbox = WorkerSandbox; - -/** - * Imports scripts to the sandbox by reading files under urls and - * evaluating its source. If exception occurs during evaluation - * `'error'` event is emitted on the worker. - * This is actually an analog to the `importScript` method in web - * workers but in our case it's not exposed even though content - * scripts may be able to do it synchronously since IO operation - * takes place in the UI process. - */ -function importScripts (workerSandbox, ...urls) { - let { worker, sandbox } = modelFor(workerSandbox); - for (let i in urls) { - let contentScriptFile = data.url(urls[i]); - - try { - let uri = URL(contentScriptFile); - if (uri.scheme === 'resource') - load(sandbox, String(uri)); - else - throw Error('Unsupported `contentScriptFile` url: ' + String(uri)); - } - catch(e) { - emit(worker, 'error', e); - } - } -} - -function setListeners (workerSandbox, console) { - let { worker } = modelFor(workerSandbox); - // console.xxx calls - workerSandbox.on('console', function consoleListener (kind, ...args) { - console[kind].apply(console, args); - }); - - // self.postMessage calls - workerSandbox.on('message', function postMessage(data) { - // destroyed? - if (worker) - emit(worker, 'message', data); - }); - - // self.port.emit calls - workerSandbox.on('event', function portEmit (...eventArgs) { - // If not destroyed, emit event information to worker - // `eventArgs` has the event name as first element, - // and remaining elements are additional arguments to pass - if (worker) - emit.apply(null, [worker.port].concat(eventArgs)); - }); - - // unwrap, recreate and propagate async Errors thrown from content-script - workerSandbox.on('error', function onError({instanceOfError, value}) { - if (worker) { - let error = value; - if (instanceOfError) { - error = new Error(value.message, value.fileName, value.lineNumber); - error.stack = value.stack; - error.name = value.name; - } - emit(worker, 'error', error); - } - }); -} - -/** - * Evaluates code in the sandbox. - * @param {String} code - * JavaScript source to evaluate. - * @param {String} [filename='javascript:' + code] - * Name of the file - */ -function evaluateIn (workerSandbox, code, filename) { - let { worker, sandbox } = modelFor(workerSandbox); - try { - evaluate(sandbox, code, filename || 'javascript:' + code); - } - catch(e) { - emit(worker, 'error', e); - } -} - -/** - * Method called by the worker sandbox when it needs to send a message - */ -function onContentEvent (workerSandbox, args) { - // As `emit`, we ensure having an asynchronous behavior - async(function () { - // We emit event to chrome/addon listeners - emit.apply(null, [workerSandbox].concat(JSON.parse(args))); - }); -} - - -function modelFor (workerSandbox) { - return sandboxes.get(workerSandbox); -} - -function getUnsafeWindow (win) { - return win.wrappedJSObject || win; -} - -function emitToContent (workerSandbox, args) { - return modelFor(workerSandbox).emitToContent(args); -} - -function createChromeAPI (scope) { - return Cu.cloneInto({ - timers: { - setTimeout: timer.setTimeout.bind(timer), - setInterval: timer.setInterval.bind(timer), - clearTimeout: timer.clearTimeout.bind(timer), - clearInterval: timer.clearInterval.bind(timer), - }, - sandbox: { - evaluate: evaluate, - }, - }, scope, {cloneFunctions: true}); -} |