michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /** michael@0: * michael@0: * `deprecated/traits-worker` was previously `content/worker` and kept michael@0: * only due to `deprecated/symbiont` using it, which is necessary for michael@0: * `widget`, until that reaches deprecation EOL. michael@0: * michael@0: */ michael@0: michael@0: "use strict"; michael@0: michael@0: module.metadata = { michael@0: "stability": "deprecated" michael@0: }; michael@0: michael@0: const { Trait } = require('./traits'); michael@0: const { EventEmitter, EventEmitterTrait } = require('./events'); michael@0: const { Ci, Cu, Cc } = require('chrome'); michael@0: const timer = require('../timers'); michael@0: const { URL } = require('../url'); michael@0: const unload = require('../system/unload'); michael@0: const observers = require('../system/events'); michael@0: const { Cortex } = require('./cortex'); michael@0: const { sandbox, evaluate, load } = require("../loader/sandbox"); michael@0: const { merge } = require('../util/object'); michael@0: const { getInnerId } = require("../window/utils"); michael@0: const { getTabForWindow } = require('../tabs/helpers'); michael@0: const { getTabForContentWindow } = require('../tabs/utils'); michael@0: michael@0: /* Trick the linker in order to ensure shipping these files in the XPI. michael@0: require('../content/content-worker.js'); michael@0: Then, retrieve URL of these files in the XPI: michael@0: */ michael@0: let prefix = module.uri.split('deprecated/traits-worker.js')[0]; michael@0: const CONTENT_WORKER_URL = prefix + 'content/content-worker.js'; michael@0: michael@0: // Fetch additional list of domains to authorize access to for each content michael@0: // script. It is stored in manifest `metadata` field which contains michael@0: // package.json data. This list is originaly defined by authors in michael@0: // `permissions` attribute of their package.json addon file. michael@0: const permissions = require('@loader/options').metadata['permissions'] || {}; michael@0: const EXPANDED_PRINCIPALS = permissions['cross-domain-content'] || []; michael@0: michael@0: const JS_VERSION = '1.8'; michael@0: michael@0: const ERR_DESTROYED = michael@0: "Couldn't find the worker to receive this message. " + michael@0: "The script may not be initialized yet, or may already have been unloaded."; michael@0: michael@0: const ERR_FROZEN = "The page is currently hidden and can no longer be used " + michael@0: "until it is visible again."; michael@0: michael@0: michael@0: const WorkerSandbox = EventEmitter.compose({ michael@0: michael@0: /** michael@0: * Emit a message to the worker content sandbox michael@0: */ michael@0: emit: function emit() { michael@0: // First ensure having a regular array michael@0: // (otherwise, `arguments` would be mapped to an object by `stringify`) michael@0: let array = Array.slice(arguments); michael@0: // JSON.stringify is buggy with cross-sandbox values, michael@0: // it may return "{}" on functions. Use a replacer to match them correctly. michael@0: function replacer(k, v) { michael@0: return typeof v === "function" ? undefined : v; michael@0: } michael@0: // Ensure having an asynchronous behavior michael@0: let self = this; michael@0: timer.setTimeout(function () { michael@0: self._emitToContent(JSON.stringify(array, replacer)); michael@0: }, 0); michael@0: }, michael@0: michael@0: /** michael@0: * Synchronous version of `emit`. michael@0: * /!\ Should only be used when it is strictly mandatory /!\ michael@0: * Doesn't ensure passing only JSON values. michael@0: * Mainly used by context-menu in order to avoid breaking it. michael@0: */ michael@0: emitSync: function emitSync() { michael@0: let args = Array.slice(arguments); michael@0: return this._emitToContent(args); michael@0: }, michael@0: michael@0: /** michael@0: * Tells if content script has at least one listener registered for one event, michael@0: * through `self.on('xxx', ...)`. michael@0: * /!\ Shouldn't be used. Implemented to avoid breaking context-menu API. michael@0: */ michael@0: hasListenerFor: function hasListenerFor(name) { michael@0: return this._hasListenerFor(name); michael@0: }, michael@0: michael@0: /** michael@0: * Method called by the worker sandbox when it needs to send a message michael@0: */ michael@0: _onContentEvent: function onContentEvent(args) { michael@0: // As `emit`, we ensure having an asynchronous behavior michael@0: let self = this; michael@0: timer.setTimeout(function () { michael@0: // We emit event to chrome/addon listeners michael@0: self._emit.apply(self, JSON.parse(args)); michael@0: }, 0); michael@0: }, michael@0: michael@0: /** michael@0: * Configures sandbox and loads content scripts into it. michael@0: * @param {Worker} worker michael@0: * content worker michael@0: */ michael@0: constructor: function WorkerSandbox(worker) { michael@0: this._addonWorker = worker; michael@0: michael@0: // Ensure that `emit` has always the right `this` michael@0: this.emit = this.emit.bind(this); michael@0: this.emitSync = this.emitSync.bind(this); michael@0: michael@0: // We receive a wrapped window, that may be an xraywrapper if it's content michael@0: let window = worker._window; michael@0: let proto = window; michael@0: michael@0: // Eventually use expanded principal sandbox feature, if some are given. michael@0: // michael@0: // But prevent it when the Worker isn't used for a content script but for michael@0: // injecting `addon` object into a Panel, Widget, ... scope. michael@0: // That's because: michael@0: // 1/ It is useless to use multiple domains as the worker is only used michael@0: // to communicate with the addon, michael@0: // 2/ By using it it would prevent the document to have access to any JS michael@0: // value of the worker. As JS values coming from multiple domain principals michael@0: // can't be accessed by "mono-principals" (principal with only one domain). michael@0: // Even if this principal is for a domain that is specified in the multiple michael@0: // domain principal. michael@0: let principals = window; michael@0: let wantGlobalProperties = [] michael@0: if (EXPANDED_PRINCIPALS.length > 0 && !worker._injectInDocument) { michael@0: principals = EXPANDED_PRINCIPALS.concat(window); michael@0: // We have to replace XHR constructor of the content document michael@0: // with a custom cross origin one, automagically added by platform code: michael@0: delete proto.XMLHttpRequest; michael@0: wantGlobalProperties.push("XMLHttpRequest"); michael@0: } michael@0: michael@0: // Instantiate trusted code in another Sandbox in order to prevent content michael@0: // script from messing with standard classes used by proxy and API code. michael@0: let apiSandbox = sandbox(principals, { wantXrays: true, sameZoneAs: window }); michael@0: apiSandbox.console = console; michael@0: michael@0: // Create the sandbox and bind it to window in order for content scripts to michael@0: // have access to all standard globals (window, document, ...) michael@0: let content = this._sandbox = sandbox(principals, { michael@0: sandboxPrototype: proto, michael@0: wantXrays: true, michael@0: wantGlobalProperties: wantGlobalProperties, michael@0: sameZoneAs: window, michael@0: metadata: { michael@0: SDKContentScript: true, michael@0: 'inner-window-id': getInnerId(window) michael@0: } michael@0: }); michael@0: // We have to ensure that window.top and window.parent are the exact same michael@0: // object than window object, i.e. the sandbox global object. But not michael@0: // always, in case of iframes, top and parent are another window object. michael@0: let top = window.top === window ? content : content.top; michael@0: let parent = window.parent === window ? content : content.parent; michael@0: merge(content, { michael@0: // We need "this === window === top" to be true in toplevel scope: michael@0: get window() content, michael@0: get top() top, michael@0: get parent() parent, michael@0: // Use the Greasemonkey naming convention to provide access to the michael@0: // unwrapped window object so the content script can access document michael@0: // JavaScript values. michael@0: // NOTE: this functionality is experimental and may change or go away michael@0: // at any time! michael@0: get unsafeWindow() window.wrappedJSObject michael@0: }); michael@0: michael@0: // Load trusted code that will inject content script API. michael@0: // We need to expose JS objects defined in same principal in order to michael@0: // avoid having any kind of wrapper. michael@0: load(apiSandbox, CONTENT_WORKER_URL); michael@0: michael@0: // prepare a clean `self.options` michael@0: let options = 'contentScriptOptions' in worker ? michael@0: JSON.stringify( worker.contentScriptOptions ) : michael@0: undefined; michael@0: michael@0: // Then call `inject` method and communicate with this script michael@0: // by trading two methods that allow to send events to the other side: michael@0: // - `onEvent` called by content script michael@0: // - `result.emitToContent` called by addon script michael@0: // Bug 758203: We have to explicitely define `__exposedProps__` in order michael@0: // to allow access to these chrome object attributes from this sandbox with michael@0: // content priviledges michael@0: // https://developer.mozilla.org/en/XPConnect_wrappers#Other_security_wrappers michael@0: let chromeAPI = { michael@0: timers: { michael@0: setTimeout: timer.setTimeout, michael@0: setInterval: timer.setInterval, michael@0: clearTimeout: timer.clearTimeout, michael@0: clearInterval: timer.clearInterval, michael@0: __exposedProps__: { michael@0: setTimeout: 'r', michael@0: setInterval: 'r', michael@0: clearTimeout: 'r', michael@0: clearInterval: 'r' michael@0: } michael@0: }, michael@0: sandbox: { michael@0: evaluate: evaluate, michael@0: __exposedProps__: { michael@0: evaluate: 'r', michael@0: } michael@0: }, michael@0: __exposedProps__: { michael@0: timers: 'r', michael@0: sandbox: 'r', michael@0: } michael@0: }; michael@0: let onEvent = this._onContentEvent.bind(this); michael@0: // `ContentWorker` is defined in CONTENT_WORKER_URL file michael@0: let result = apiSandbox.ContentWorker.inject(content, chromeAPI, onEvent, options); michael@0: this._emitToContent = result.emitToContent; michael@0: this._hasListenerFor = result.hasListenerFor; michael@0: michael@0: // Handle messages send by this script: michael@0: let self = this; michael@0: // console.xxx calls michael@0: this.on("console", function consoleListener(kind) { michael@0: console[kind].apply(console, Array.slice(arguments, 1)); michael@0: }); michael@0: michael@0: // self.postMessage calls michael@0: this.on("message", function postMessage(data) { michael@0: // destroyed? michael@0: if (self._addonWorker) michael@0: self._addonWorker._emit('message', data); michael@0: }); michael@0: michael@0: // self.port.emit calls michael@0: this.on("event", function portEmit(name, args) { michael@0: // destroyed? michael@0: if (self._addonWorker) michael@0: self._addonWorker._onContentScriptEvent.apply(self._addonWorker, arguments); michael@0: }); michael@0: michael@0: // unwrap, recreate and propagate async Errors thrown from content-script michael@0: this.on("error", function onError({instanceOfError, value}) { michael@0: if (self._addonWorker) { michael@0: let error = value; michael@0: if (instanceOfError) { michael@0: error = new Error(value.message, value.fileName, value.lineNumber); michael@0: error.stack = value.stack; michael@0: error.name = value.name; michael@0: } michael@0: self._addonWorker._emit('error', error); michael@0: } michael@0: }); michael@0: michael@0: // Inject `addon` global into target document if document is trusted, michael@0: // `addon` in document is equivalent to `self` in content script. michael@0: if (worker._injectInDocument) { michael@0: let win = window.wrappedJSObject ? window.wrappedJSObject : window; michael@0: Object.defineProperty(win, "addon", { michael@0: value: content.self michael@0: } michael@0: ); michael@0: } michael@0: michael@0: // Inject our `console` into target document if worker doesn't have a tab michael@0: // (e.g Panel, PageWorker, Widget). michael@0: // `worker.tab` can't be used because bug 804935. michael@0: if (!getTabForContentWindow(window)) { michael@0: let win = window.wrappedJSObject ? window.wrappedJSObject : window; michael@0: michael@0: // export our chrome console to content window as described here: michael@0: // https://developer.mozilla.org/en-US/docs/Components.utils.createObjectIn michael@0: let con = Cu.createObjectIn(win); michael@0: michael@0: let genPropDesc = function genPropDesc(fun) { michael@0: return { enumerable: true, configurable: true, writable: true, michael@0: value: console[fun] }; michael@0: } michael@0: michael@0: const properties = { michael@0: log: genPropDesc('log'), michael@0: info: genPropDesc('info'), michael@0: warn: genPropDesc('warn'), michael@0: error: genPropDesc('error'), michael@0: debug: genPropDesc('debug'), michael@0: trace: genPropDesc('trace'), michael@0: dir: genPropDesc('dir'), michael@0: group: genPropDesc('group'), michael@0: groupCollapsed: genPropDesc('groupCollapsed'), michael@0: groupEnd: genPropDesc('groupEnd'), michael@0: time: genPropDesc('time'), michael@0: timeEnd: genPropDesc('timeEnd'), michael@0: profile: genPropDesc('profile'), michael@0: profileEnd: genPropDesc('profileEnd'), michael@0: __noSuchMethod__: { enumerable: true, configurable: true, writable: true, michael@0: value: function() {} } michael@0: }; michael@0: michael@0: Object.defineProperties(con, properties); michael@0: Cu.makeObjectPropsNormal(con); michael@0: michael@0: win.console = con; michael@0: }; michael@0: michael@0: // The order of `contentScriptFile` and `contentScript` evaluation is michael@0: // intentional, so programs can load libraries like jQuery from script URLs michael@0: // and use them in scripts. michael@0: let contentScriptFile = ('contentScriptFile' in worker) ? worker.contentScriptFile michael@0: : null, michael@0: contentScript = ('contentScript' in worker) ? worker.contentScript : null; michael@0: michael@0: if (contentScriptFile) { michael@0: if (Array.isArray(contentScriptFile)) michael@0: this._importScripts.apply(this, contentScriptFile); michael@0: else michael@0: this._importScripts(contentScriptFile); michael@0: } michael@0: if (contentScript) { michael@0: this._evaluate( michael@0: Array.isArray(contentScript) ? contentScript.join(';\n') : contentScript michael@0: ); michael@0: } michael@0: }, michael@0: destroy: function destroy() { michael@0: this.emitSync("detach"); michael@0: this._sandbox = null; michael@0: this._addonWorker = null; michael@0: }, michael@0: michael@0: /** michael@0: * JavaScript sandbox where all the content scripts are evaluated. michael@0: * {Sandbox} michael@0: */ michael@0: _sandbox: null, michael@0: michael@0: /** michael@0: * Reference to the addon side of the worker. michael@0: * @type {Worker} michael@0: */ michael@0: _addonWorker: null, michael@0: michael@0: /** michael@0: * Evaluates code in the sandbox. michael@0: * @param {String} code michael@0: * JavaScript source to evaluate. michael@0: * @param {String} [filename='javascript:' + code] michael@0: * Name of the file michael@0: */ michael@0: _evaluate: function(code, filename) { michael@0: try { michael@0: evaluate(this._sandbox, code, filename || 'javascript:' + code); michael@0: } michael@0: catch(e) { michael@0: this._addonWorker._emit('error', e); michael@0: } michael@0: }, michael@0: /** michael@0: * Imports scripts to the sandbox by reading files under urls and michael@0: * evaluating its source. If exception occurs during evaluation michael@0: * `"error"` event is emitted on the worker. michael@0: * This is actually an analog to the `importScript` method in web michael@0: * workers but in our case it's not exposed even though content michael@0: * scripts may be able to do it synchronously since IO operation michael@0: * takes place in the UI process. michael@0: */ michael@0: _importScripts: function _importScripts(url) { michael@0: let urls = Array.slice(arguments, 0); michael@0: for each (let contentScriptFile in urls) { michael@0: try { michael@0: let uri = URL(contentScriptFile); michael@0: if (uri.scheme === 'resource') michael@0: load(this._sandbox, String(uri)); michael@0: else michael@0: throw Error("Unsupported `contentScriptFile` url: " + String(uri)); michael@0: } michael@0: catch(e) { michael@0: this._addonWorker._emit('error', e); michael@0: } michael@0: } michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * Message-passing facility for communication between code running michael@0: * in the content and add-on process. michael@0: * @see https://addons.mozilla.org/en-US/developers/docs/sdk/latest/modules/sdk/content/worker.html michael@0: */ michael@0: const Worker = EventEmitter.compose({ michael@0: on: Trait.required, michael@0: _removeAllListeners: Trait.required, michael@0: michael@0: // List of messages fired before worker is initialized michael@0: get _earlyEvents() { michael@0: delete this._earlyEvents; michael@0: this._earlyEvents = []; michael@0: return this._earlyEvents; michael@0: }, michael@0: michael@0: /** michael@0: * Sends a message to the worker's global scope. Method takes single michael@0: * argument, which represents data to be sent to the worker. The data may michael@0: * be any primitive type value or `JSON`. Call of this method asynchronously michael@0: * emits `message` event with data value in the global scope of this michael@0: * symbiont. michael@0: * michael@0: * `message` event listeners can be set either by calling michael@0: * `self.on` with a first argument string `"message"` or by michael@0: * implementing `onMessage` function in the global scope of this worker. michael@0: * @param {Number|String|JSON} data michael@0: */ michael@0: postMessage: function (data) { michael@0: let args = ['message'].concat(Array.slice(arguments)); michael@0: if (!this._inited) { michael@0: this._earlyEvents.push(args); michael@0: return; michael@0: } michael@0: processMessage.apply(this, args); michael@0: }, michael@0: michael@0: /** michael@0: * EventEmitter, that behaves (calls listeners) asynchronously. michael@0: * A way to send customized messages to / from the worker. michael@0: * Events from in the worker can be observed / emitted via michael@0: * worker.on / worker.emit. michael@0: */ michael@0: get port() { michael@0: // We generate dynamically this attribute as it needs to be accessible michael@0: // before Worker.constructor gets called. (For ex: Panel) michael@0: michael@0: // create an event emitter that receive and send events from/to the worker michael@0: this._port = EventEmitterTrait.create({ michael@0: emit: this._emitEventToContent.bind(this) michael@0: }); michael@0: michael@0: // expose wrapped port, that exposes only public properties: michael@0: // We need to destroy this getter in order to be able to set the michael@0: // final value. We need to update only public port attribute as we never michael@0: // try to access port attribute from private API. michael@0: delete this._public.port; michael@0: this._public.port = Cortex(this._port); michael@0: // Replicate public port to the private object michael@0: delete this.port; michael@0: this.port = this._public.port; michael@0: michael@0: return this._port; michael@0: }, michael@0: michael@0: /** michael@0: * Same object than this.port but private API. michael@0: * Allow access to _emit, in order to send event to port. michael@0: */ michael@0: _port: null, michael@0: michael@0: /** michael@0: * Emit a custom event to the content script, michael@0: * i.e. emit this event on `self.port` michael@0: */ michael@0: _emitEventToContent: function () { michael@0: let args = ['event'].concat(Array.slice(arguments)); michael@0: if (!this._inited) { michael@0: this._earlyEvents.push(args); michael@0: return; michael@0: } michael@0: processMessage.apply(this, args); michael@0: }, michael@0: michael@0: // Is worker connected to the content worker sandbox ? michael@0: _inited: false, michael@0: michael@0: // Is worker being frozen? i.e related document is frozen in bfcache. michael@0: // Content script should not be reachable if frozen. michael@0: _frozen: true, michael@0: michael@0: constructor: function Worker(options) { michael@0: options = options || {}; michael@0: michael@0: if ('contentScriptFile' in options) michael@0: this.contentScriptFile = options.contentScriptFile; michael@0: if ('contentScriptOptions' in options) michael@0: this.contentScriptOptions = options.contentScriptOptions; michael@0: if ('contentScript' in options) michael@0: this.contentScript = options.contentScript; michael@0: michael@0: this._setListeners(options); michael@0: michael@0: unload.ensure(this._public, "destroy"); michael@0: michael@0: // Ensure that worker._port is initialized for contentWorker to be able michael@0: // to send events during worker initialization. michael@0: this.port; michael@0: michael@0: this._documentUnload = this._documentUnload.bind(this); michael@0: this._pageShow = this._pageShow.bind(this); michael@0: this._pageHide = this._pageHide.bind(this); michael@0: michael@0: if ("window" in options) this._attach(options.window); michael@0: }, michael@0: michael@0: _setListeners: function(options) { michael@0: if ('onError' in options) michael@0: this.on('error', options.onError); michael@0: if ('onMessage' in options) michael@0: this.on('message', options.onMessage); michael@0: if ('onDetach' in options) michael@0: this.on('detach', options.onDetach); michael@0: }, michael@0: michael@0: _attach: function(window) { michael@0: this._window = window; michael@0: // Track document unload to destroy this worker. michael@0: // We can't watch for unload event on page's window object as it michael@0: // prevents bfcache from working: michael@0: // https://developer.mozilla.org/En/Working_with_BFCache michael@0: this._windowID = getInnerId(this._window); michael@0: observers.on("inner-window-destroyed", this._documentUnload); michael@0: michael@0: // Listen to pagehide event in order to freeze the content script michael@0: // while the document is frozen in bfcache: michael@0: this._window.addEventListener("pageshow", this._pageShow, true); michael@0: this._window.addEventListener("pagehide", this._pageHide, true); michael@0: michael@0: // will set this._contentWorker pointing to the private API: michael@0: this._contentWorker = WorkerSandbox(this); michael@0: michael@0: // Mainly enable worker.port.emit to send event to the content worker michael@0: this._inited = true; michael@0: this._frozen = false; michael@0: michael@0: // Process all events and messages that were fired before the michael@0: // worker was initialized. michael@0: this._earlyEvents.forEach((function (args) { michael@0: processMessage.apply(this, args); michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: _documentUnload: function _documentUnload({ subject, data }) { michael@0: let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data; michael@0: if (innerWinID != this._windowID) return false; michael@0: this._workerCleanup(); michael@0: return true; michael@0: }, michael@0: michael@0: _pageShow: function _pageShow() { michael@0: this._contentWorker.emitSync("pageshow"); michael@0: this._emit("pageshow"); michael@0: this._frozen = false; michael@0: }, michael@0: michael@0: _pageHide: function _pageHide() { michael@0: this._contentWorker.emitSync("pagehide"); michael@0: this._emit("pagehide"); michael@0: this._frozen = true; michael@0: }, michael@0: michael@0: get url() { michael@0: // this._window will be null after detach michael@0: return this._window ? this._window.document.location.href : null; michael@0: }, michael@0: michael@0: get tab() { michael@0: // this._window will be null after detach michael@0: if (this._window) michael@0: return getTabForWindow(this._window); michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * Tells content worker to unload itself and michael@0: * removes all the references from itself. michael@0: */ michael@0: destroy: function destroy() { michael@0: this._workerCleanup(); michael@0: this._inited = true; michael@0: this._removeAllListeners(); michael@0: }, michael@0: michael@0: /** michael@0: * Remove all internal references to the attached document michael@0: * Tells _port to unload itself and removes all the references from itself. michael@0: */ michael@0: _workerCleanup: function _workerCleanup() { michael@0: // maybe unloaded before content side is created michael@0: // As Symbiont call worker.constructor on document load michael@0: if (this._contentWorker) michael@0: this._contentWorker.destroy(); michael@0: this._contentWorker = null; michael@0: if (this._window) { michael@0: this._window.removeEventListener("pageshow", this._pageShow, true); michael@0: this._window.removeEventListener("pagehide", this._pageHide, true); michael@0: } michael@0: this._window = null; michael@0: // This method may be called multiple times, michael@0: // avoid dispatching `detach` event more than once michael@0: if (this._windowID) { michael@0: this._windowID = null; michael@0: observers.off("inner-window-destroyed", this._documentUnload); michael@0: this._earlyEvents.length = 0; michael@0: this._emit("detach"); michael@0: } michael@0: this._inited = false; michael@0: }, michael@0: michael@0: /** michael@0: * Receive an event from the content script that need to be sent to michael@0: * worker.port. Provide a way for composed object to catch all events. michael@0: */ michael@0: _onContentScriptEvent: function _onContentScriptEvent() { michael@0: this._port._emit.apply(this._port, arguments); michael@0: }, michael@0: michael@0: /** michael@0: * Reference to the content side of the worker. michael@0: * @type {WorkerGlobalScope} michael@0: */ michael@0: _contentWorker: null, michael@0: michael@0: /** michael@0: * Reference to the window that is accessible from michael@0: * the content scripts. michael@0: * @type {Object} michael@0: */ michael@0: _window: null, michael@0: michael@0: /** michael@0: * Flag to enable `addon` object injection in document. (bug 612726) michael@0: * @type {Boolean} michael@0: */ michael@0: _injectInDocument: false michael@0: }); michael@0: michael@0: /** michael@0: * Fired from postMessage and _emitEventToContent, or from the _earlyMessage michael@0: * queue when fired before the content is loaded. Sends arguments to michael@0: * contentWorker if able michael@0: */ michael@0: michael@0: function processMessage () { michael@0: if (!this._contentWorker) michael@0: throw new Error(ERR_DESTROYED); michael@0: if (this._frozen) michael@0: throw new Error(ERR_FROZEN); michael@0: michael@0: this._contentWorker.emit.apply(null, Array.slice(arguments)); michael@0: } michael@0: michael@0: exports.Worker = Worker;