summaryrefslogtreecommitdiffstats
path: root/addon-sdk/source/lib/sdk/content/sandbox.js
diff options
context:
space:
mode:
Diffstat (limited to 'addon-sdk/source/lib/sdk/content/sandbox.js')
-rw-r--r--addon-sdk/source/lib/sdk/content/sandbox.js426
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});
-}