diff options
Diffstat (limited to 'tools/addon-sdk-1.12/lib/sdk/content/worker.js')
-rw-r--r-- | tools/addon-sdk-1.12/lib/sdk/content/worker.js | 582 |
1 files changed, 0 insertions, 582 deletions
diff --git a/tools/addon-sdk-1.12/lib/sdk/content/worker.js b/tools/addon-sdk-1.12/lib/sdk/content/worker.js deleted file mode 100644 index b2f204c..0000000 --- a/tools/addon-sdk-1.12/lib/sdk/content/worker.js +++ /dev/null @@ -1,582 +0,0 @@ -/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et: */ -/* 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 { Trait } = require('../deprecated/traits'); -const { EventEmitter, EventEmitterTrait } = require('../deprecated/events'); -const { Ci, Cu, Cc } = require('chrome'); -const timer = require('../timers'); -const { URL } = require('../url'); -const unload = require('../system/unload'); -const observers = require('../deprecated/observer-service'); -const { Cortex } = require('../deprecated/cortex'); -const { sandbox, evaluate, load } = require("../loader/sandbox"); -const { merge } = require('../util/object'); -const xulApp = require("../system/xul-app"); -const USE_JS_PROXIES = !xulApp.versionInRange(xulApp.platformVersion, - "17.0a2", "*"); -const { getTabForWindow } = require('../tabs/helpers'); - -/* Trick the linker in order to ensure shipping these files in the XPI. - require('./content-proxy.js'); - require('./content-worker.js'); - Then, retrieve URL of these files in the XPI: -*/ -let prefix = module.uri.split('worker.js')[0]; -const CONTENT_PROXY_URL = prefix + 'content-proxy.js'; -const CONTENT_WORKER_URL = prefix + 'content-worker.js'; - -const JS_VERSION = '1.8'; - -const ERR_DESTROYED = - "Couldn't find the worker to receive this message. " + - "The script may not be initialized yet, or may already have been unloaded."; - -const ERR_FROZEN = "The page is currently hidden and can no longer be used " + - "until it is visible again."; - -/** - * This key is not exported and should only be used for proxy tests. - * The following `PRIVATE_KEY` is used in addon module scope in order to tell - * Worker API to expose `UNWRAP_ACCESS_KEY` in content script. - * This key allows test-content-proxy.js to unwrap proxy with valueOf: - * let xpcWrapper = proxyWrapper.valueOf(UNWRAP_ACCESS_KEY); - */ -const PRIVATE_KEY = {}; - - -const WorkerSandbox = EventEmitter.compose({ - - /** - * Emit a message to the worker content sandbox - */ - emit: function emit() { - // First ensure having a regular array - // (otherwise, `arguments` would be mapped to an object by `stringify`) - let array = Array.slice(arguments); - // JSON.stringify is buggy with cross-sandbox values, - // it may return "{}" on functions. Use a replacer to match them correctly. - function replacer(k, v) { - return typeof v === "function" ? undefined : v; - } - // Ensure having an asynchronous behavior - let self = this; - timer.setTimeout(function () { - self._emitToContent(JSON.stringify(array, replacer)); - }, 0); - }, - - /** - * 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() { - let args = Array.slice(arguments); - // Bug 732716: Ensure wrapping xrays sent to the content script - // otherwise it will have access to raw xraywrappers and content script - // will assume it is an user object coming from the content script sandbox - if ("_wrap" in this) - args = args.map(this._wrap); - return this._emitToContent(args); - }, - - /** - * Tells if content script has at least one listener registered for one event, - * through `self.on('xxx', ...)`. - * /!\ Shouldn't be used. Implemented to avoid breaking context-menu API. - */ - hasListenerFor: function hasListenerFor(name) { - return this._hasListenerFor(name); - }, - - /** - * Method called by the worker sandbox when it needs to send a message - */ - _onContentEvent: function onContentEvent(args) { - // As `emit`, we ensure having an asynchronous behavior - let self = this; - timer.setTimeout(function () { - // We emit event to chrome/addon listeners - self._emit.apply(self, JSON.parse(args)); - }, 0); - }, - - /** - * Configures sandbox and loads content scripts into it. - * @param {Worker} worker - * content worker - */ - constructor: function WorkerSandbox(worker) { - this._addonWorker = worker; - - // Ensure that `emit` has always the right `this` - this.emit = this.emit.bind(this); - this.emitSync = this.emitSync.bind(this); - - // We receive a wrapped window, that may be an xraywrapper if it's content - let window = worker._window; - let proto = window; - - // Instantiate trusted code in another Sandbox in order to prevent content - // script from messing with standard classes used by proxy and API code. - let apiSandbox = sandbox(window, { wantXrays: true }); - - // Build content proxies only if the document has a non-system principal - // And only on old firefox versions that doesn't ship bug 738244 - if (USE_JS_PROXIES && XPCNativeWrapper.unwrap(window) !== window) { - apiSandbox.console = console; - // Execute the proxy code - load(apiSandbox, CONTENT_PROXY_URL); - // Get a reference of the window's proxy - proto = apiSandbox.create(window); - // Keep a reference to `wrap` function for `emitSync` usage - this._wrap = apiSandbox.wrap; - } - - // Create the sandbox and bind it to window in order for content scripts to - // have access to all standard globals (window, document, ...) - let content = this._sandbox = sandbox(window, { - sandboxPrototype: proto, - wantXrays: true - }); - // 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() content, - get top() top, - get parent() 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! - get unsafeWindow() window.wrappedJSObject - }); - - // Load trusted code that will inject content script API. - // We need to expose JS objects defined in same principal in order to - // avoid having any kind of wrapper. - load(apiSandbox, 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 - // Bug 758203: We have to explicitely define `__exposedProps__` in order - // to allow access to these chrome object attributes from this sandbox with - // content priviledges - // https://developer.mozilla.org/en/XPConnect_wrappers#Other_security_wrappers - let chromeAPI = { - timers: { - setTimeout: timer.setTimeout, - setInterval: timer.setInterval, - clearTimeout: timer.clearTimeout, - clearInterval: timer.clearInterval, - __exposedProps__: { - setTimeout: 'r', - setInterval: 'r', - clearTimeout: 'r', - clearInterval: 'r' - } - }, - __exposedProps__: { - timers: 'r' - } - }; - let onEvent = this._onContentEvent.bind(this); - // `ContentWorker` is defined in CONTENT_WORKER_URL file - let result = apiSandbox.ContentWorker.inject(content, chromeAPI, onEvent, options); - this._emitToContent = result.emitToContent; - this._hasListenerFor = result.hasListenerFor; - - // Handle messages send by this script: - let self = this; - // console.xxx calls - this.on("console", function consoleListener(kind) { - console[kind].apply(console, Array.slice(arguments, 1)); - }); - - // self.postMessage calls - this.on("message", function postMessage(data) { - // destroyed? - if (self._addonWorker) - self._addonWorker._emit('message', data); - }); - - // self.port.emit calls - this.on("event", function portEmit(name, args) { - // destroyed? - if (self._addonWorker) - self._addonWorker._onContentScriptEvent.apply(self._addonWorker, arguments); - }); - - // Internal feature that is only used by SDK tests: - // Expose unlock key to content script context. - // See `PRIVATE_KEY` definition for more information. - if (apiSandbox && worker._expose_key) - content.UNWRAP_ACCESS_KEY = apiSandbox.UNWRAP_ACCESS_KEY; - - // Inject `addon` global into target document if document is trusted, - // `addon` in document is equivalent to `self` in content script. - if (worker._injectInDocument) { - let win = window.wrappedJSObject ? window.wrappedJSObject : window; - Object.defineProperty(win, "addon", { - value: content.self - } - ); - } - - // 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) { - if (Array.isArray(contentScriptFile)) - this._importScripts.apply(this, contentScriptFile); - else - this._importScripts(contentScriptFile); - } - if (contentScript) { - this._evaluate( - Array.isArray(contentScript) ? contentScript.join(';\n') : contentScript - ); - } - }, - destroy: function destroy() { - this.emitSync("detach"); - this._sandbox = null; - this._addonWorker = null; - this._wrap = null; - }, - - /** - * JavaScript sandbox where all the content scripts are evaluated. - * {Sandbox} - */ - _sandbox: null, - - /** - * Reference to the addon side of the worker. - * @type {Worker} - */ - _addonWorker: null, - - /** - * Evaluates code in the sandbox. - * @param {String} code - * JavaScript source to evaluate. - * @param {String} [filename='javascript:' + code] - * Name of the file - */ - _evaluate: function(code, filename) { - try { - evaluate(this._sandbox, code, filename || 'javascript:' + code); - } - catch(e) { - this._addonWorker._emit('error', e); - } - }, - /** - * 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. - */ - _importScripts: function _importScripts(url) { - let urls = Array.slice(arguments, 0); - for each (let contentScriptFile in urls) { - try { - let uri = URL(contentScriptFile); - if (uri.scheme === 'resource') - load(this._sandbox, String(uri)); - else - throw Error("Unsupported `contentScriptFile` url: " + String(uri)); - } - catch(e) { - this._addonWorker._emit('error', e); - } - } - } -}); - -/** - * Message-passing facility for communication between code running - * in the content and add-on process. - * @see https://jetpack.mozillalabs.com/sdk/latest/docs/#module/api-utils/content/worker - */ -const Worker = EventEmitter.compose({ - on: Trait.required, - _removeAllListeners: Trait.required, - - /** - * Sends a message to the worker's global scope. Method takes single - * argument, which represents data to be sent to the worker. The data may - * be any primitive type value or `JSON`. Call of this method asynchronously - * emits `message` event with data value in the global scope of this - * symbiont. - * - * `message` event listeners can be set either by calling - * `self.on` with a first argument string `"message"` or by - * implementing `onMessage` function in the global scope of this worker. - * @param {Number|String|JSON} data - */ - postMessage: function postMessage(data) { - if (!this._contentWorker) - throw new Error(ERR_DESTROYED); - if (this._frozen) - throw new Error(ERR_FROZEN); - - this._contentWorker.emit("message", data); - }, - - /** - * EventEmitter, that behaves (calls listeners) asynchronously. - * A way to send customized messages to / from the worker. - * Events from in the worker can be observed / emitted via - * worker.on / worker.emit. - */ - get port() { - // We generate dynamically this attribute as it needs to be accessible - // before Worker.constructor gets called. (For ex: Panel) - - // create an event emitter that receive and send events from/to the worker - let self = this; - this._port = EventEmitterTrait.create({ - emit: function () self._emitEventToContent(Array.slice(arguments)) - }); - - // expose wrapped port, that exposes only public properties: - // We need to destroy this getter in order to be able to set the - // final value. We need to update only public port attribute as we never - // try to access port attribute from private API. - delete this._public.port; - this._public.port = Cortex(this._port); - // Replicate public port to the private object - delete this.port; - this.port = this._public.port; - - return this._port; - }, - - /** - * Same object than this.port but private API. - * Allow access to _emit, in order to send event to port. - */ - _port: null, - - /** - * Emit a custom event to the content script, - * i.e. emit this event on `self.port` - */ - _emitEventToContent: function _emitEventToContent(args) { - // We need to save events that are emitted before the worker is - // initialized - if (!this._inited) { - this._earlyEvents.push(args); - return; - } - - if (this._frozen) - throw new Error(ERR_FROZEN); - - // We throw exception when the worker has been destroyed - if (!this._contentWorker) { - throw new Error(ERR_DESTROYED); - } - - // Forward the event to the WorkerSandbox object - this._contentWorker.emit.apply(null, ["event"].concat(args)); - }, - - // Is worker connected to the content worker sandbox ? - _inited: false, - - // Is worker being frozen? i.e related document is frozen in bfcache. - // Content script should not be reachable if frozen. - _frozen: true, - - // List of custom events fired before worker is initialized - get _earlyEvents() { - delete this._earlyEvents; - this._earlyEvents = []; - return this._earlyEvents; - }, - - constructor: function Worker(options) { - options = options || {}; - - if ('window' in options) - this._window = options.window; - if ('contentScriptFile' in options) - this.contentScriptFile = options.contentScriptFile; - if ('contentScriptOptions' in options) - this.contentScriptOptions = options.contentScriptOptions; - if ('contentScript' in options) - this.contentScript = options.contentScript; - if ('onError' in options) - this.on('error', options.onError); - if ('onMessage' in options) - this.on('message', options.onMessage); - if ('onDetach' in options) - this.on('detach', options.onDetach); - - // Internal feature that is only used by SDK unit tests. - // See `PRIVATE_KEY` definition for more information. - if ('exposeUnlockKey' in options && options.exposeUnlockKey === PRIVATE_KEY) - this._expose_key = true; - - // Track document unload to destroy this worker. - // We can't watch for unload event on page's window object as it - // prevents bfcache from working: - // https://developer.mozilla.org/En/Working_with_BFCache - this._windowID = this._window. - QueryInterface(Ci.nsIInterfaceRequestor). - getInterface(Ci.nsIDOMWindowUtils). - currentInnerWindowID; - observers.add("inner-window-destroyed", - this._documentUnload = this._documentUnload.bind(this)); - - // Listen to pagehide event in order to freeze the content script - // while the document is frozen in bfcache: - this._window.addEventListener("pageshow", - this._pageShow = this._pageShow.bind(this), - true); - this._window.addEventListener("pagehide", - this._pageHide = this._pageHide.bind(this), - true); - - unload.ensure(this._public, "destroy"); - - // Ensure that worker._port is initialized for contentWorker to be able - // to send use event during WorkerSandbox(this) - this.port; - - // will set this._contentWorker pointing to the private API: - this._contentWorker = WorkerSandbox(this); - - // Mainly enable worker.port.emit to send event to the content worker - this._inited = true; - this._frozen = false; - - // Flush all events that have been fired before the worker is initialized. - this._earlyEvents.forEach((function (args) this._emitEventToContent(args)). - bind(this)); - }, - - _documentUnload: function _documentUnload(subject, topic, data) { - let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data; - if (innerWinID != this._windowID) return false; - this._workerCleanup(); - return true; - }, - - _pageShow: function _pageShow() { - this._contentWorker.emitSync("pageshow"); - this._emit("pageshow"); - this._frozen = false; - }, - - _pageHide: function _pageHide() { - this._contentWorker.emitSync("pagehide"); - this._emit("pagehide"); - this._frozen = true; - }, - - get url() { - // this._window will be null after detach - return this._window ? this._window.document.location.href : null; - }, - - get tab() { - // this._window will be null after detach - if (this._window) - return getTabForWindow(this._window); - return null; - }, - - /** - * Tells content worker to unload itself and - * removes all the references from itself. - */ - destroy: function destroy() { - this._workerCleanup(); - this._removeAllListeners(); - }, - - /** - * Remove all internal references to the attached document - * Tells _port to unload itself and removes all the references from itself. - */ - _workerCleanup: function _workerCleanup() { - // maybe unloaded before content side is created - // As Symbiont call worker.constructor on document load - if (this._contentWorker) - this._contentWorker.destroy(); - this._contentWorker = null; - if (this._window) { - this._window.removeEventListener("pageshow", this._pageShow, true); - this._window.removeEventListener("pagehide", this._pageHide, true); - } - this._window = null; - // This method may be called multiple times, - // avoid dispatching `detach` event more than once - if (this._windowID) { - this._windowID = null; - observers.remove("inner-window-destroyed", this._documentUnload); - this._earlyEvents.slice(0, this._earlyEvents.length); - this._emit("detach"); - } - }, - - /** - * Receive an event from the content script that need to be sent to - * worker.port. Provide a way for composed object to catch all events. - */ - _onContentScriptEvent: function _onContentScriptEvent() { - this._port._emit.apply(this._port, arguments); - }, - - /** - * Reference to the content side of the worker. - * @type {WorkerGlobalScope} - */ - _contentWorker: null, - - /** - * Reference to the window that is accessible from - * the content scripts. - * @type {Object} - */ - _window: null, - - /** - * Flag to enable `addon` object injection in document. (bug 612726) - * @type {Boolean} - */ - _injectInDocument: false -}); -exports.Worker = Worker; |