diff options
Diffstat (limited to 'addon-sdk/source/lib/sdk/content')
-rw-r--r-- | addon-sdk/source/lib/sdk/content/content-worker.js | 305 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/content/content.js | 17 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/content/context-menu.js | 408 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/content/events.js | 57 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/content/l10n-html.js | 133 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/content/loader.js | 74 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/content/mod.js | 68 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/content/page-mod.js | 236 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/content/page-worker.js | 154 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/content/sandbox.js | 426 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/content/sandbox/events.js | 12 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/content/tab-events.js | 58 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/content/thumbnail.js | 51 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/content/utils.js | 105 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/content/worker-child.js | 158 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/content/worker.js | 180 |
16 files changed, 0 insertions, 2442 deletions
diff --git a/addon-sdk/source/lib/sdk/content/content-worker.js b/addon-sdk/source/lib/sdk/content/content-worker.js deleted file mode 100644 index 0a8225733..000000000 --- a/addon-sdk/source/lib/sdk/content/content-worker.js +++ /dev/null @@ -1,305 +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/. */ - -Object.freeze({ - // TODO: Bug 727854 Use same implementation than common JS modules, - // i.e. EventEmitter module - - /** - * Create an EventEmitter instance. - */ - createEventEmitter: function createEventEmitter(emit) { - let listeners = Object.create(null); - let eventEmitter = Object.freeze({ - emit: emit, - on: function on(name, callback) { - if (typeof callback !== "function") - return this; - if (!(name in listeners)) - listeners[name] = []; - listeners[name].push(callback); - return this; - }, - once: function once(name, callback) { - eventEmitter.on(name, function onceCallback() { - eventEmitter.removeListener(name, onceCallback); - callback.apply(callback, arguments); - }); - }, - removeListener: function removeListener(name, callback) { - if (!(name in listeners)) - return; - let index = listeners[name].indexOf(callback); - if (index == -1) - return; - listeners[name].splice(index, 1); - } - }); - function onEvent(name) { - if (!(name in listeners)) - return []; - let args = Array.slice(arguments, 1); - let results = []; - for (let callback of listeners[name]) { - results.push(callback.apply(null, args)); - } - return results; - } - return { - eventEmitter: eventEmitter, - emit: onEvent - }; - }, - - /** - * Create an EventEmitter instance to communicate with chrome module - * by passing only strings between compartments. - * This function expects `emitToChrome` function, that allows to send - * events to the chrome module. It returns the EventEmitter as `pipe` - * attribute, and, `onChromeEvent` a function that allows chrome module - * to send event into the EventEmitter. - * - * pipe.emit --> emitToChrome - * onChromeEvent --> callback registered through pipe.on - */ - createPipe: function createPipe(emitToChrome) { - let ContentWorker = this; - function onEvent(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; - - let str = JSON.stringify([type, ...args], replacer); - emitToChrome(str); - } - - let { eventEmitter, emit } = - ContentWorker.createEventEmitter(onEvent); - - return { - pipe: eventEmitter, - onChromeEvent: function onChromeEvent(array) { - // We either receive a stringified array, or a real array. - // We still allow to pass an array of objects, in WorkerSandbox.emitSync - // in order to allow sending DOM node reference between content script - // and modules (only used for context-menu API) - let args = typeof array == "string" ? JSON.parse(array) : array; - return emit.apply(null, args); - } - }; - }, - - injectConsole: function injectConsole(exports, pipe) { - exports.console = Object.freeze({ - log: pipe.emit.bind(null, "console", "log"), - info: pipe.emit.bind(null, "console", "info"), - warn: pipe.emit.bind(null, "console", "warn"), - error: pipe.emit.bind(null, "console", "error"), - debug: pipe.emit.bind(null, "console", "debug"), - exception: pipe.emit.bind(null, "console", "exception"), - trace: pipe.emit.bind(null, "console", "trace"), - time: pipe.emit.bind(null, "console", "time"), - timeEnd: pipe.emit.bind(null, "console", "timeEnd") - }); - }, - - injectTimers: function injectTimers(exports, chromeAPI, pipe, console) { - // wrapped functions from `'timer'` module. - // Wrapper adds `try catch` blocks to the callbacks in order to - // emit `error` event if exception is thrown in - // the Worker global scope. - // @see http://www.w3.org/TR/workers/#workerutils - - // List of all living timeouts/intervals - let _timers = Object.create(null); - - // Keep a reference to original timeout functions - let { - setTimeout: chromeSetTimeout, - setInterval: chromeSetInterval, - clearTimeout: chromeClearTimeout, - clearInterval: chromeClearInterval - } = chromeAPI.timers; - - function registerTimer(timer) { - let registerMethod = null; - if (timer.kind == "timeout") - registerMethod = chromeSetTimeout; - else if (timer.kind == "interval") - registerMethod = chromeSetInterval; - else - throw new Error("Unknown timer kind: " + timer.kind); - - if (typeof timer.fun == 'string') { - let code = timer.fun; - timer.fun = () => chromeAPI.sandbox.evaluate(exports, code); - } else if (typeof timer.fun != 'function') { - throw new Error('Unsupported callback type' + typeof timer.fun); - } - - let id = registerMethod(onFire, timer.delay); - function onFire() { - try { - if (timer.kind == "timeout") - delete _timers[id]; - timer.fun.apply(null, timer.args); - } catch(e) { - console.exception(e); - let wrapper = { - instanceOfError: instanceOf(e, Error), - value: e, - }; - if (wrapper.instanceOfError) { - wrapper.value = { - message: e.message, - fileName: e.fileName, - lineNumber: e.lineNumber, - stack: e.stack, - name: e.name, - }; - } - pipe.emit('error', wrapper); - } - } - _timers[id] = timer; - return id; - } - - // copied from sdk/lang/type.js since modules are not available here - function instanceOf(value, Type) { - var isConstructorNameSame; - var isConstructorSourceSame; - - // If `instanceof` returned `true` we know result right away. - var isInstanceOf = value instanceof Type; - - // If `instanceof` returned `false` we do ducktype check since `Type` may be - // from a different sandbox. If a constructor of the `value` or a constructor - // of the value's prototype has same name and source we assume that it's an - // instance of the Type. - if (!isInstanceOf && value) { - isConstructorNameSame = value.constructor.name === Type.name; - isConstructorSourceSame = String(value.constructor) == String(Type); - isInstanceOf = (isConstructorNameSame && isConstructorSourceSame) || - instanceOf(Object.getPrototypeOf(value), Type); - } - return isInstanceOf; - } - - function unregisterTimer(id) { - if (!(id in _timers)) - return; - let { kind } = _timers[id]; - delete _timers[id]; - if (kind == "timeout") - chromeClearTimeout(id); - else if (kind == "interval") - chromeClearInterval(id); - else - throw new Error("Unknown timer kind: " + kind); - } - - function disableAllTimers() { - Object.keys(_timers).forEach(unregisterTimer); - } - - exports.setTimeout = function ContentScriptSetTimeout(callback, delay) { - return registerTimer({ - kind: "timeout", - fun: callback, - delay: delay, - args: Array.slice(arguments, 2) - }); - }; - exports.clearTimeout = function ContentScriptClearTimeout(id) { - unregisterTimer(id); - }; - - exports.setInterval = function ContentScriptSetInterval(callback, delay) { - return registerTimer({ - kind: "interval", - fun: callback, - delay: delay, - args: Array.slice(arguments, 2) - }); - }; - exports.clearInterval = function ContentScriptClearInterval(id) { - unregisterTimer(id); - }; - - // On page-hide, save a list of all existing timers before disabling them, - // in order to be able to restore them on page-show. - // These events are fired when the page goes in/out of bfcache. - // https://developer.mozilla.org/En/Working_with_BFCache - let frozenTimers = []; - pipe.on("pageshow", function onPageShow() { - frozenTimers.forEach(registerTimer); - }); - pipe.on("pagehide", function onPageHide() { - frozenTimers = []; - for (let id in _timers) - frozenTimers.push(_timers[id]); - disableAllTimers(); - // Some other pagehide listeners may register some timers that won't be - // frozen as this particular pagehide listener is called first. - // So freeze these timers on next cycle. - chromeSetTimeout(function () { - for (let id in _timers) - frozenTimers.push(_timers[id]); - disableAllTimers(); - }, 0); - }); - - // Unregister all timers when the page is destroyed - // (i.e. when it is removed from bfcache) - pipe.on("detach", function clearTimeouts() { - disableAllTimers(); - _timers = {}; - frozenTimers = []; - }); - }, - - injectMessageAPI: function injectMessageAPI(exports, pipe, console) { - - let ContentWorker = this; - let { eventEmitter: port, emit : portEmit } = - ContentWorker.createEventEmitter(pipe.emit.bind(null, "event")); - pipe.on("event", portEmit); - - let self = { - port: port, - postMessage: pipe.emit.bind(null, "message"), - on: pipe.on.bind(null), - once: pipe.once.bind(null), - removeListener: pipe.removeListener.bind(null), - }; - Object.defineProperty(exports, "self", { - value: self - }); - }, - - injectOptions: function (exports, options) { - Object.defineProperty( exports.self, "options", { value: JSON.parse( options ) }); - }, - - inject: function (exports, chromeAPI, emitToChrome, options) { - let ContentWorker = this; - let { pipe, onChromeEvent } = - ContentWorker.createPipe(emitToChrome); - - ContentWorker.injectConsole(exports, pipe); - ContentWorker.injectTimers(exports, chromeAPI, pipe, exports.console); - ContentWorker.injectMessageAPI(exports, pipe, exports.console); - if ( options !== undefined ) { - ContentWorker.injectOptions(exports, options); - } - - Object.freeze( exports.self ); - - return onChromeEvent; - } -}); diff --git a/addon-sdk/source/lib/sdk/content/content.js b/addon-sdk/source/lib/sdk/content/content.js deleted file mode 100644 index 9655223a3..000000000 --- a/addon-sdk/source/lib/sdk/content/content.js +++ /dev/null @@ -1,17 +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": "deprecated" -}; - -const { deprecateUsage } = require('../util/deprecate'); - -Object.defineProperty(exports, "Worker", { - get: function() { - deprecateUsage('`sdk/content/content` is deprecated. Please use `sdk/content/worker` directly.'); - return require('./worker').Worker; - } -}); diff --git a/addon-sdk/source/lib/sdk/content/context-menu.js b/addon-sdk/source/lib/sdk/content/context-menu.js deleted file mode 100644 index 2955e2f09..000000000 --- a/addon-sdk/source/lib/sdk/content/context-menu.js +++ /dev/null @@ -1,408 +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"; - -const { Class } = require("../core/heritage"); -const self = require("../self"); -const { WorkerChild } = require("./worker-child"); -const { getInnerId } = require("../window/utils"); -const { Ci } = require("chrome"); -const { Services } = require("resource://gre/modules/Services.jsm"); -const system = require('../system/events'); -const { process } = require('../remote/child'); - -// These functions are roughly copied from sdk/selection which doesn't work -// in the content process -function getElementWithSelection(window) { - let element = Services.focus.getFocusedElementForWindow(window, false, {}); - if (!element) - return null; - - try { - // Accessing selectionStart and selectionEnd on e.g. a button - // results in an exception thrown as per the HTML5 spec. See - // http://www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#textFieldSelection - - let { value, selectionStart, selectionEnd } = element; - - let hasSelection = typeof value === "string" && - !isNaN(selectionStart) && - !isNaN(selectionEnd) && - selectionStart !== selectionEnd; - - return hasSelection ? element : null; - } - catch (err) { - console.exception(err); - return null; - } -} - -function safeGetRange(selection, rangeNumber) { - try { - let { rangeCount } = selection; - let range = null; - - for (let rangeNumber = 0; rangeNumber < rangeCount; rangeNumber++ ) { - range = selection.getRangeAt(rangeNumber); - - if (range && range.toString()) - break; - - range = null; - } - - return range; - } - catch (e) { - return null; - } -} - -function getSelection(window) { - let selection = window.getSelection(); - let range = safeGetRange(selection); - if (range) - return range.toString(); - - let node = getElementWithSelection(window); - if (!node) - return null; - - return node.value.substring(node.selectionStart, node.selectionEnd); -} - -//These are used by PageContext.isCurrent below. If the popupNode or any of -//its ancestors is one of these, Firefox uses a tailored context menu, and so -//the page context doesn't apply. -const NON_PAGE_CONTEXT_ELTS = [ - Ci.nsIDOMHTMLAnchorElement, - Ci.nsIDOMHTMLAppletElement, - Ci.nsIDOMHTMLAreaElement, - Ci.nsIDOMHTMLButtonElement, - Ci.nsIDOMHTMLCanvasElement, - Ci.nsIDOMHTMLEmbedElement, - Ci.nsIDOMHTMLImageElement, - Ci.nsIDOMHTMLInputElement, - Ci.nsIDOMHTMLMapElement, - Ci.nsIDOMHTMLMediaElement, - Ci.nsIDOMHTMLMenuElement, - Ci.nsIDOMHTMLObjectElement, - Ci.nsIDOMHTMLOptionElement, - Ci.nsIDOMHTMLSelectElement, - Ci.nsIDOMHTMLTextAreaElement, -]; - -// List all editable types of inputs. Or is it better to have a list -// of non-editable inputs? -var editableInputs = { - email: true, - number: true, - password: true, - search: true, - tel: true, - text: true, - textarea: true, - url: true -}; - -var CONTEXTS = {}; - -var Context = Class({ - initialize: function(id) { - this.id = id; - }, - - adjustPopupNode: function adjustPopupNode(popupNode) { - return popupNode; - }, - - // Gets state to pass through to the parent process for the node the user - // clicked on - getState: function(popupNode) { - return false; - } -}); - -// Matches when the context-clicked node doesn't have any of -// NON_PAGE_CONTEXT_ELTS in its ancestors -CONTEXTS.PageContext = Class({ - extends: Context, - - getState: function(popupNode) { - // If there is a selection in the window then this context does not match - if (!popupNode.ownerDocument.defaultView.getSelection().isCollapsed) - return false; - - // If the clicked node or any of its ancestors is one of the blocked - // NON_PAGE_CONTEXT_ELTS then this context does not match - while (!(popupNode instanceof Ci.nsIDOMDocument)) { - if (NON_PAGE_CONTEXT_ELTS.some(type => popupNode instanceof type)) - return false; - - popupNode = popupNode.parentNode; - } - - return true; - } -}); - -// Matches when there is an active selection in the window -CONTEXTS.SelectionContext = Class({ - extends: Context, - - getState: function(popupNode) { - if (!popupNode.ownerDocument.defaultView.getSelection().isCollapsed) - return true; - - try { - // The node may be a text box which has selectionStart and selectionEnd - // properties. If not this will throw. - let { selectionStart, selectionEnd } = popupNode; - return !isNaN(selectionStart) && !isNaN(selectionEnd) && - selectionStart !== selectionEnd; - } - catch (e) { - return false; - } - } -}); - -// Matches when the context-clicked node or any of its ancestors matches the -// selector given -CONTEXTS.SelectorContext = Class({ - extends: Context, - - initialize: function initialize(id, selector) { - Context.prototype.initialize.call(this, id); - this.selector = selector; - }, - - adjustPopupNode: function adjustPopupNode(popupNode) { - let selector = this.selector; - - while (!(popupNode instanceof Ci.nsIDOMDocument)) { - if (popupNode.matches(selector)) - return popupNode; - - popupNode = popupNode.parentNode; - } - - return null; - }, - - getState: function(popupNode) { - return !!this.adjustPopupNode(popupNode); - } -}); - -// Matches when the page url matches any of the patterns given -CONTEXTS.URLContext = Class({ - extends: Context, - - getState: function(popupNode) { - return popupNode.ownerDocument.URL; - } -}); - -// Matches when the user-supplied predicate returns true -CONTEXTS.PredicateContext = Class({ - extends: Context, - - getState: function(node) { - let window = node.ownerDocument.defaultView; - let data = {}; - - data.documentType = node.ownerDocument.contentType; - - data.documentURL = node.ownerDocument.location.href; - data.targetName = node.nodeName.toLowerCase(); - data.targetID = node.id || null ; - - if ((data.targetName === 'input' && editableInputs[node.type]) || - data.targetName === 'textarea') { - data.isEditable = !node.readOnly && !node.disabled; - } - else { - data.isEditable = node.isContentEditable; - } - - data.selectionText = getSelection(window, "TEXT"); - - data.srcURL = node.src || null; - data.value = node.value || null; - - while (!data.linkURL && node) { - data.linkURL = node.href || null; - node = node.parentNode; - } - - return data; - }, -}); - -function instantiateContext({ id, type, args }) { - if (!(type in CONTEXTS)) { - console.error("Attempt to use unknown context " + type); - return; - } - return new CONTEXTS[type](id, ...args); -} - -var ContextWorker = Class({ - implements: [ WorkerChild ], - - // Calls the context workers context listeners and returns the first result - // that is either a string or a value that evaluates to true. If all of the - // listeners returned false then returns false. If there are no listeners, - // returns true (show the menu item by default). - getMatchedContext: function getCurrentContexts(popupNode) { - let results = this.sandbox.emitSync("context", popupNode); - if (!results.length) - return true; - return results.reduce((val, result) => val || result); - }, - - // Emits a click event in the worker's port. popupNode is the node that was - // context-clicked, and clickedItemData is the data of the item that was - // clicked. - fireClick: function fireClick(popupNode, clickedItemData) { - this.sandbox.emitSync("click", popupNode, clickedItemData); - } -}); - -// Gets the item's content script worker for a window, creating one if necessary -// Once created it will be automatically destroyed when the window unloads. -// If there is not content scripts for the item then null will be returned. -function getItemWorkerForWindow(item, window) { - if (!item.contentScript && !item.contentScriptFile) - return null; - - let id = getInnerId(window); - let worker = item.workerMap.get(id); - - if (worker) - return worker; - - worker = ContextWorker({ - id: item.id, - window, - manager: item.manager, - contentScript: item.contentScript, - contentScriptFile: item.contentScriptFile, - onDetach: function() { - item.workerMap.delete(id); - } - }); - - item.workerMap.set(id, worker); - - return worker; -} - -// A very simple remote proxy for every item. It's job is to provide data for -// the main process to use to determine visibility state and to call into -// content scripts when clicked. -var RemoteItem = Class({ - initialize: function(options, manager) { - this.id = options.id; - this.contexts = options.contexts.map(instantiateContext); - this.contentScript = options.contentScript; - this.contentScriptFile = options.contentScriptFile; - - this.manager = manager; - - this.workerMap = new Map(); - keepAlive.set(this.id, this); - }, - - destroy: function() { - for (let worker of this.workerMap.values()) { - worker.destroy(); - } - keepAlive.delete(this.id); - }, - - activate: function(popupNode, data) { - let worker = getItemWorkerForWindow(this, popupNode.ownerDocument.defaultView); - if (!worker) - return; - - for (let context of this.contexts) - popupNode = context.adjustPopupNode(popupNode); - - worker.fireClick(popupNode, data); - }, - - // Fills addonInfo with state data to send through to the main process - getContextState: function(popupNode, addonInfo) { - if (!(self.id in addonInfo)) { - addonInfo[self.id] = { - processID: process.id, - items: {} - }; - } - - let worker = getItemWorkerForWindow(this, popupNode.ownerDocument.defaultView); - let contextStates = {}; - for (let context of this.contexts) - contextStates[context.id] = context.getState(popupNode); - - addonInfo[self.id].items[this.id] = { - // It isn't ideal to create a PageContext for every item but there isn't - // a good shared place to do it. - pageContext: (new CONTEXTS.PageContext()).getState(popupNode), - contextStates, - hasWorker: !!worker, - workerContext: worker ? worker.getMatchedContext(popupNode) : true - } - } -}); -exports.RemoteItem = RemoteItem; - -// Holds remote items for this frame. -var keepAlive = new Map(); - -// Called to create remote proxies for items. If they already exist we destroy -// and recreate. This can happen if the item changes in some way or in odd -// timing cases where the frame script is create around the same time as the -// item is created in the main process -process.port.on('sdk/contextmenu/createitems', (process, items) => { - for (let itemoptions of items) { - let oldItem = keepAlive.get(itemoptions.id); - if (oldItem) { - oldItem.destroy(); - } - - let item = new RemoteItem(itemoptions, this); - } -}); - -process.port.on('sdk/contextmenu/destroyitems', (process, items) => { - for (let id of items) { - let item = keepAlive.get(id); - item.destroy(); - } -}); - -var lastPopupNode = null; - -system.on('content-contextmenu', ({ subject }) => { - let { event: { target: popupNode }, addonInfo } = subject.wrappedJSObject; - lastPopupNode = popupNode; - - for (let item of keepAlive.values()) { - item.getContextState(popupNode, addonInfo); - } -}, true); - -process.port.on('sdk/contextmenu/activateitems', (process, items, data) => { - for (let id of items) { - let item = keepAlive.get(id); - if (!item) - continue; - - item.activate(lastPopupNode, data); - } -}); diff --git a/addon-sdk/source/lib/sdk/content/events.js b/addon-sdk/source/lib/sdk/content/events.js deleted file mode 100644 index c085b6179..000000000 --- a/addon-sdk/source/lib/sdk/content/events.js +++ /dev/null @@ -1,57 +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": "experimental" -}; - -const { Ci } = require("chrome"); -const { open } = require("../event/dom"); -const { observe } = require("../event/chrome"); -const { filter, merge, map, expand } = require("../event/utils"); -const { windows } = require("../window/utils"); -const { events: windowEvents } = require("sdk/window/events"); - -// Note: Please note that even though pagehide event is included -// it's not observable reliably since it's not always triggered -// when closing tabs. Implementation can be imrpoved once that -// event will be necessary. -var TYPES = ["DOMContentLoaded", "load", "pageshow", "pagehide"]; - -var insert = observe("document-element-inserted"); -var windowCreate = merge([ - observe("content-document-global-created"), - observe("chrome-document-global-created") -]); -var create = map(windowCreate, function({target, data, type}) { - return { target: target.document, type: type, data: data } -}); - -function streamEventsFrom({document}) { - // Map supported event types to a streams of those events on the given - // `window` for the inserted document and than merge these streams into - // single form stream off all window state change events. - let stateChanges = TYPES.map(function(type) { - return open(document, type, { capture: true }); - }); - - // Since load events on document occur for every loded resource - return filter(merge(stateChanges), function({target}) { - return target instanceof Ci.nsIDOMDocument - }) -} -exports.streamEventsFrom = streamEventsFrom; - -var opened = windows(null, { includePrivate: true }); -var state = merge(opened.map(streamEventsFrom)); - - -var futureReady = filter(windowEvents, ({type}) => - type === "DOMContentLoaded"); -var futureWindows = map(futureReady, ({target}) => target); -var futureState = expand(futureWindows, streamEventsFrom); - -exports.events = merge([insert, create, state, futureState]); diff --git a/addon-sdk/source/lib/sdk/content/l10n-html.js b/addon-sdk/source/lib/sdk/content/l10n-html.js deleted file mode 100644 index f324623dc..000000000 --- a/addon-sdk/source/lib/sdk/content/l10n-html.js +++ /dev/null @@ -1,133 +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 { Ci, Cc, Cu } = require("chrome"); -const core = require("../l10n/core"); -const { loadSheet, removeSheet } = require("../stylesheet/utils"); -const { process, frames } = require("../remote/child"); -var observerService = Cc["@mozilla.org/observer-service;1"] - .getService(Ci.nsIObserverService); -const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm"); -const addObserver = ShimWaiver.getProperty(observerService, "addObserver"); -const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver"); - -const assetsURI = require('../self').data.url(); - -const hideSheetUri = "data:text/css,:root {visibility: hidden !important;}"; - -function translateElementAttributes(element) { - // Translateable attributes - const attrList = ['title', 'accesskey', 'alt', 'label', 'placeholder']; - const ariaAttrMap = { - 'ariaLabel': 'aria-label', - 'ariaValueText': 'aria-valuetext', - 'ariaMozHint': 'aria-moz-hint' - }; - const attrSeparator = '.'; - - // Try to translate each of the attributes - for (let attribute of attrList) { - const data = core.get(element.dataset.l10nId + attrSeparator + attribute); - if (data) - element.setAttribute(attribute, data); - } - - // Look for the aria attribute translations that match fxOS's aliases - for (let attrAlias in ariaAttrMap) { - const data = core.get(element.dataset.l10nId + attrSeparator + attrAlias); - if (data) - element.setAttribute(ariaAttrMap[attrAlias], data); - } -} - -// Taken from Gaia: -// https://github.com/andreasgal/gaia/blob/04fde2640a7f40314643016a5a6c98bf3755f5fd/webapi.js#L1470 -function translateElement(element) { - element = element || document; - - // check all translatable children (= w/ a `data-l10n-id' attribute) - var children = element.querySelectorAll('*[data-l10n-id]'); - var elementCount = children.length; - for (var i = 0; i < elementCount; i++) { - var child = children[i]; - - // translate the child - var key = child.dataset.l10nId; - var data = core.get(key); - if (data) - child.textContent = data; - - translateElementAttributes(child); - } -} -exports.translateElement = translateElement; - -function onDocumentReady2Translate(event) { - let document = event.target; - document.removeEventListener("DOMContentLoaded", onDocumentReady2Translate, - false); - - translateElement(document); - - try { - // Finally display document when we finished replacing all text content - if (document.defaultView) - removeSheet(document.defaultView, hideSheetUri, 'user'); - } - catch(e) { - console.exception(e); - } -} - -function onContentWindow(document) { - // Accept only HTML documents - if (!(document instanceof Ci.nsIDOMHTMLDocument)) - return; - - // Bug 769483: data:URI documents instanciated with nsIDOMParser - // have a null `location` attribute at this time - if (!document.location) - return; - - // Accept only document from this addon - if (document.location.href.indexOf(assetsURI) !== 0) - return; - - try { - // First hide content of the document in order to have content blinking - // between untranslated and translated states - loadSheet(document.defaultView, hideSheetUri, 'user'); - } - catch(e) { - console.exception(e); - } - // Wait for DOM tree to be built before applying localization - document.addEventListener("DOMContentLoaded", onDocumentReady2Translate, - false); -} - -// Listen to creation of content documents in order to translate them as soon -// as possible in their loading process -const ON_CONTENT = "document-element-inserted"; -let enabled = false; -function enable() { - if (enabled) - return; - addObserver(onContentWindow, ON_CONTENT, false); - enabled = true; -} -process.port.on("sdk/l10n/html/enable", enable); - -function disable() { - if (!enabled) - return; - removeObserver(onContentWindow, ON_CONTENT); - enabled = false; -} -process.port.on("sdk/l10n/html/disable", disable); diff --git a/addon-sdk/source/lib/sdk/content/loader.js b/addon-sdk/source/lib/sdk/content/loader.js deleted file mode 100644 index e4f0dd2aa..000000000 --- a/addon-sdk/source/lib/sdk/content/loader.js +++ /dev/null @@ -1,74 +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 { isValidURI, isLocalURL, URL } = require('../url'); -const { contract } = require('../util/contract'); -const { isString, isNil, instanceOf, isJSONable } = require('../lang/type'); -const { validateOptions, - string, array, object, either, required } = require('../deprecated/api-utils'); - -const isValidScriptFile = (value) => - (isString(value) || instanceOf(value, URL)) && isLocalURL(value); - -// map of property validations -const valid = { - contentURL: { - is: either(string, object), - ok: url => isNil(url) || isLocalURL(url) || isValidURI(url), - msg: 'The `contentURL` option must be a valid URL.' - }, - contentScriptFile: { - is: either(string, object, array), - ok: value => isNil(value) || [].concat(value).every(isValidScriptFile), - msg: 'The `contentScriptFile` option must be a local URL or an array of URLs.' - }, - contentScript: { - is: either(string, array), - ok: value => isNil(value) || [].concat(value).every(isString), - msg: 'The `contentScript` option must be a string or an array of strings.' - }, - contentScriptWhen: { - is: required(string), - map: value => value || 'end', - ok: value => ~['start', 'ready', 'end'].indexOf(value), - msg: 'The `contentScriptWhen` option must be either "start", "ready" or "end".' - }, - contentScriptOptions: { - ok: value => isNil(value) || isJSONable(value), - msg: 'The contentScriptOptions should be a jsonable value.' - } -}; -exports.validationAttributes = valid; - -/** - * Shortcut function to validate property with validation. - * @param {Object|Number|String} suspect - * value to validate - * @param {Object} validation - * validation rule passed to `api-utils` - */ -function validate(suspect, validation) { - return validateOptions( - { $: suspect }, - { $: validation } - ).$; -} - -function Allow(script) { - return { - get script() { - return script; - }, - set script(value) { - script = !!value; - } - }; -} - -exports.contract = contract(valid); diff --git a/addon-sdk/source/lib/sdk/content/mod.js b/addon-sdk/source/lib/sdk/content/mod.js deleted file mode 100644 index 81fe9ee42..000000000 --- a/addon-sdk/source/lib/sdk/content/mod.js +++ /dev/null @@ -1,68 +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": "experimental" -}; - -const { Ci } = require("chrome"); -const { dispatcher } = require("../util/dispatcher"); -const { add, remove, iterator } = require("../lang/weak-set"); - -var getTargetWindow = dispatcher("getTargetWindow"); - -getTargetWindow.define(function (target) { - if (target instanceof Ci.nsIDOMWindow) - return target; - if (target instanceof Ci.nsIDOMDocument) - return target.defaultView || null; - - return null; -}); - -exports.getTargetWindow = getTargetWindow; - -var attachTo = dispatcher("attachTo"); -exports.attachTo = attachTo; - -var detachFrom = dispatcher("detatchFrom"); -exports.detachFrom = detachFrom; - -function attach(modification, target) { - if (!modification) - return; - - let window = getTargetWindow(target); - - attachTo(modification, window); - - // modification are stored per content; `window` reference can still be the - // same even if the content is changed, therefore `document` is used instead. - add(modification, window.document); -} -exports.attach = attach; - -function detach(modification, target) { - if (!modification) - return; - - if (target) { - let window = getTargetWindow(target); - detachFrom(modification, window); - remove(modification, window.document); - } - else { - let documents = iterator(modification); - for (let document of documents) { - let window = document.defaultView; - // The window might have already gone away - if (!window) - continue; - detachFrom(modification, document.defaultView); - remove(modification, document); - } - } -} -exports.detach = detach; diff --git a/addon-sdk/source/lib/sdk/content/page-mod.js b/addon-sdk/source/lib/sdk/content/page-mod.js deleted file mode 100644 index 8ff9b1e7b..000000000 --- a/addon-sdk/source/lib/sdk/content/page-mod.js +++ /dev/null @@ -1,236 +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": "stable" -}; - -const { getAttachEventType } = require('../content/utils'); -const { Class } = require('../core/heritage'); -const { Disposable } = require('../core/disposable'); -const { WeakReference } = require('../core/reference'); -const { WorkerChild } = require('./worker-child'); -const { EventTarget } = require('../event/target'); -const { on, emit, once, setListeners } = require('../event/core'); -const { on: domOn, removeListener: domOff } = require('../dom/events'); -const { isRegExp, isUndefined } = require('../lang/type'); -const { merge } = require('../util/object'); -const { isBrowser, getFrames } = require('../window/utils'); -const { getTabs, getURI: getTabURI } = require('../tabs/utils'); -const { ignoreWindow } = require('../private-browsing/utils'); -const { Style } = require("../stylesheet/style"); -const { attach, detach } = require("../content/mod"); -const { has, hasAny } = require("../util/array"); -const { Rules } = require("../util/rules"); -const { List, addListItem, removeListItem } = require('../util/list'); -const { when } = require("../system/unload"); -const { uuid } = require('../util/uuid'); -const { frames, process } = require('../remote/child'); - -const pagemods = new Map(); -const styles = new WeakMap(); -var styleFor = (mod) => styles.get(mod); - -// Helper functions -var modMatchesURI = (mod, uri) => mod.include.matchesAny(uri) && !mod.exclude.matchesAny(uri); - -/** - * PageMod constructor (exported below). - * @constructor - */ -const ChildPageMod = Class({ - implements: [ - EventTarget, - Disposable, - ], - setup: function PageMod(model) { - merge(this, model); - - // Set listeners on {PageMod} itself, not the underlying worker, - // like `onMessage`, as it'll get piped. - setListeners(this, model); - - function deserializeRules(rules) { - for (let rule of rules) { - yield rule.type == "string" ? rule.value - : new RegExp(rule.pattern, rule.flags); - } - } - - let include = [...deserializeRules(this.include)]; - this.include = Rules(); - this.include.add.apply(this.include, include); - - let exclude = [...deserializeRules(this.exclude)]; - this.exclude = Rules(); - this.exclude.add.apply(this.exclude, exclude); - - if (this.contentStyle || this.contentStyleFile) { - styles.set(this, Style({ - uri: this.contentStyleFile, - source: this.contentStyle - })); - } - - pagemods.set(this.id, this); - this.seenDocuments = new WeakMap(); - - // `applyOnExistingDocuments` has to be called after `pagemods.add()` - // otherwise its calls to `onContent` method won't do anything. - if (has(this.attachTo, 'existing')) - applyOnExistingDocuments(this); - }, - - dispose: function() { - let style = styleFor(this); - if (style) - detach(style); - - for (let i in this.include) - this.include.remove(this.include[i]); - - pagemods.delete(this.id); - } -}); - -function onContentWindow({ target: document }) { - // Return if we have no pagemods - if (pagemods.size === 0) - return; - - let window = document.defaultView; - // XML documents don't have windows, and we don't yet support them. - if (!window) - return; - - // Frame event listeners are bound to the frame the event came from by default - let frame = this; - // We apply only on documents in tabs of Firefox - if (!frame.isTab) - return; - - // When the tab is private, only addons with 'private-browsing' flag in - // their package.json can apply content script to private documents - if (ignoreWindow(window)) - return; - - for (let pagemod of pagemods.values()) { - if (modMatchesURI(pagemod, window.location.href)) - onContent(pagemod, window); - } -} -frames.addEventListener("DOMDocElementInserted", onContentWindow, true); - -function applyOnExistingDocuments (mod) { - for (let frame of frames) { - // Fake a newly created document - let window = frame.content; - // on startup with e10s, contentWindow might not exist yet, - // in which case we will get notified by "document-element-inserted". - if (!window || !window.frames) - return; - let uri = window.location.href; - if (has(mod.attachTo, "top") && modMatchesURI(mod, uri)) - onContent(mod, window); - if (has(mod.attachTo, "frame")) - getFrames(window). - filter(iframe => modMatchesURI(mod, iframe.location.href)). - forEach(frame => onContent(mod, frame)); - } -} - -function createWorker(mod, window) { - let workerId = String(uuid()); - - // Instruct the parent to connect to this worker. Do this first so the parent - // side is connected before the worker attempts to send any messages there - let frame = frames.getFrameForWindow(window.top); - frame.port.emit('sdk/page-mod/worker-create', mod.id, { - id: workerId, - url: window.location.href - }); - - // Create a child worker and notify the parent - let worker = WorkerChild({ - id: workerId, - window: window, - contentScript: mod.contentScript, - contentScriptFile: mod.contentScriptFile, - contentScriptOptions: mod.contentScriptOptions - }); - - once(worker, 'detach', () => worker.destroy()); -} - -function onContent (mod, window) { - let isTopDocument = window.top === window; - // Is a top level document and `top` is not set, ignore - if (isTopDocument && !has(mod.attachTo, "top")) - return; - // Is a frame document and `frame` is not set, ignore - if (!isTopDocument && !has(mod.attachTo, "frame")) - return; - - // ensure we attach only once per document - let seen = mod.seenDocuments; - if (seen.has(window.document)) - return; - seen.set(window.document, true); - - let style = styleFor(mod); - if (style) - attach(style, window); - - // Immediately evaluate content script if the document state is already - // matching contentScriptWhen expectations - if (isMatchingAttachState(mod, window)) { - createWorker(mod, window); - return; - } - - let eventName = getAttachEventType(mod) || 'load'; - domOn(window, eventName, function onReady (e) { - if (e.target.defaultView !== window) - return; - domOff(window, eventName, onReady, true); - createWorker(mod, window); - - // Attaching is asynchronous so if the document is already loaded we will - // miss the pageshow event so send a synthetic one. - if (window.document.readyState == "complete") { - mod.on('attach', worker => { - try { - worker.send('pageshow'); - emit(worker, 'pageshow'); - } - catch (e) { - // This can fail if an earlier attach listener destroyed the worker - } - }); - } - }, true); -} - -function isMatchingAttachState (mod, window) { - let state = window.document.readyState; - return 'start' === mod.contentScriptWhen || - // Is `load` event already dispatched? - 'complete' === state || - // Is DOMContentLoaded already dispatched and waiting for it? - ('ready' === mod.contentScriptWhen && state === 'interactive') -} - -process.port.on('sdk/page-mod/create', (process, model) => { - if (pagemods.has(model.id)) - return; - - new ChildPageMod(model); -}); - -process.port.on('sdk/page-mod/destroy', (process, id) => { - let mod = pagemods.get(id); - if (mod) - mod.destroy(); -}); diff --git a/addon-sdk/source/lib/sdk/content/page-worker.js b/addon-sdk/source/lib/sdk/content/page-worker.js deleted file mode 100644 index e9e741120..000000000 --- a/addon-sdk/source/lib/sdk/content/page-worker.js +++ /dev/null @@ -1,154 +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"; - -const { frames } = require("../remote/child"); -const { Class } = require("../core/heritage"); -const { Disposable } = require('../core/disposable'); -const { data } = require("../self"); -const { once } = require("../dom/events"); -const { getAttachEventType } = require("./utils"); -const { Rules } = require('../util/rules'); -const { uuid } = require('../util/uuid'); -const { WorkerChild } = require("./worker-child"); -const { Cc, Ci, Cu } = require("chrome"); -const { observe } = require("../event/chrome"); -const { on } = require("../event/core"); - -const appShell = Cc["@mozilla.org/appshell/appShellService;1"].getService(Ci.nsIAppShellService); - -const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm"); - -const pages = new Map(); - -const DOC_INSERTED = "document-element-inserted"; - -function isValidURL(page, url) { - return !page.rules || page.rules.matchesAny(url); -} - -const ChildPage = Class({ - implements: [ Disposable ], - setup: function(frame, id, options) { - this.id = id; - this.frame = frame; - this.options = options; - - this.webNav = appShell.createWindowlessBrowser(false); - this.docShell.allowJavascript = this.options.allow.script; - - // Accessing the browser's window forces the initial about:blank document to - // be created before we start listening for notifications - this.contentWindow; - - this.webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION); - - pages.set(this.id, this); - - this.contentURL = options.contentURL; - - if (options.include) { - this.rules = Rules(); - this.rules.add.apply(this.rules, [].concat(options.include)); - } - }, - - dispose: function() { - pages.delete(this.id); - this.webProgress.removeProgressListener(this); - this.webNav.close(); - this.webNav = null; - }, - - attachWorker: function() { - if (!isValidURL(this, this.contentWindow.location.href)) - return; - - this.options.id = uuid().toString(); - this.options.window = this.contentWindow; - this.frame.port.emit("sdk/frame/connect", this.id, { - id: this.options.id, - url: this.contentWindow.document.documentURIObject.spec - }); - new WorkerChild(this.options); - }, - - get docShell() { - return this.webNav.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell); - }, - - get webProgress() { - return this.docShell.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebProgress); - }, - - get contentWindow() { - return this.docShell.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); - }, - - get contentURL() { - return this.options.contentURL; - }, - set contentURL(url) { - this.options.contentURL = url; - - url = this.options.contentURL ? data.url(this.options.contentURL) : "about:blank"; - this.webNav.loadURI(url, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null); - }, - - onLocationChange: function(progress, request, location, flags) { - // Ignore inner-frame events - if (progress != this.webProgress) - return; - // Ignore events that don't change the document - if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) - return; - - let event = getAttachEventType(this.options); - // Attaching at the start of the load is handled by the - // document-element-inserted listener. - if (event == DOC_INSERTED) - return; - - once(this.contentWindow, event, () => { - this.attachWorker(); - }, false); - }, - - QueryInterface: XPCOMUtils.generateQI(["nsIWebProgressListener", "nsISupportsWeakReference"]) -}); - -on(observe(DOC_INSERTED), "data", ({ target }) => { - let page = Array.from(pages.values()).find(p => p.contentWindow.document === target); - if (!page) - return; - - if (getAttachEventType(page.options) == DOC_INSERTED) - page.attachWorker(); -}); - -frames.port.on("sdk/frame/create", (frame, id, options) => { - new ChildPage(frame, id, options); -}); - -frames.port.on("sdk/frame/set", (frame, id, params) => { - let page = pages.get(id); - if (!page) - return; - - if ("allowScript" in params) - page.docShell.allowJavascript = params.allowScript; - if ("contentURL" in params) - page.contentURL = params.contentURL; -}); - -frames.port.on("sdk/frame/destroy", (frame, id) => { - let page = pages.get(id); - if (!page) - return; - - page.destroy(); -}); 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}); -} diff --git a/addon-sdk/source/lib/sdk/content/sandbox/events.js b/addon-sdk/source/lib/sdk/content/sandbox/events.js deleted file mode 100644 index d6f7eb004..000000000 --- a/addon-sdk/source/lib/sdk/content/sandbox/events.js +++ /dev/null @@ -1,12 +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": "experimental" -}; - -const events = {}; -exports.events = events; diff --git a/addon-sdk/source/lib/sdk/content/tab-events.js b/addon-sdk/source/lib/sdk/content/tab-events.js deleted file mode 100644 index 9e244a853..000000000 --- a/addon-sdk/source/lib/sdk/content/tab-events.js +++ /dev/null @@ -1,58 +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"; - -const { Ci } = require('chrome'); -const system = require('sdk/system/events'); -const { frames } = require('sdk/remote/child'); -const { WorkerChild } = require('sdk/content/worker-child'); - -// map observer topics to tab event names -const EVENTS = { - 'content-document-global-created': 'create', - 'chrome-document-global-created': 'create', - 'content-document-interactive': 'ready', - 'chrome-document-interactive': 'ready', - 'content-document-loaded': 'load', - 'chrome-document-loaded': 'load', -// 'content-page-shown': 'pageshow', // bug 1024105 -} - -function topicListener({ subject, type }) { - // NOTE detect the window from the subject: - // - on *-global-created the subject is the window - // - in the other cases it is the document object - let window = subject instanceof Ci.nsIDOMWindow ? subject : subject.defaultView; - if (!window){ - return; - } - let frame = frames.getFrameForWindow(window); - if (frame) { - let readyState = frame.content.document.readyState; - frame.port.emit('sdk/tab/event', EVENTS[type], { readyState }); - } -} - -for (let topic in EVENTS) - system.on(topic, topicListener, true); - -// bug 1024105 - content-page-shown notification doesn't pass persisted param -function eventListener({target, type, persisted}) { - let frame = this; - if (target === frame.content.document) { - frame.port.emit('sdk/tab/event', type, persisted); - } -} -frames.addEventListener('pageshow', eventListener, true); - -frames.port.on('sdk/tab/attach', (frame, options) => { - options.window = frame.content; - new WorkerChild(options); -}); - -// Forward the existent frames's readyState. -for (let frame of frames) { - let readyState = frame.content.document.readyState; - frame.port.emit('sdk/tab/event', 'init', { readyState }); -} diff --git a/addon-sdk/source/lib/sdk/content/thumbnail.js b/addon-sdk/source/lib/sdk/content/thumbnail.js deleted file mode 100644 index 783615fc6..000000000 --- a/addon-sdk/source/lib/sdk/content/thumbnail.js +++ /dev/null @@ -1,51 +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 { Cc, Ci, Cu } = require('chrome'); -const AppShellService = Cc['@mozilla.org/appshell/appShellService;1']. - getService(Ci.nsIAppShellService); - -const NS = 'http://www.w3.org/1999/xhtml'; -const COLOR = 'rgb(255,255,255)'; - -/** - * Creates canvas element with a thumbnail of the passed window. - * @param {Window} window - * @returns {Element} - */ -function getThumbnailCanvasForWindow(window) { - let aspectRatio = 0.5625; // 16:9 - let thumbnail = AppShellService.hiddenDOMWindow.document - .createElementNS(NS, 'canvas'); - thumbnail.mozOpaque = true; - thumbnail.width = Math.ceil(window.screen.availWidth / 5.75); - thumbnail.height = Math.round(thumbnail.width * aspectRatio); - let ctx = thumbnail.getContext('2d'); - let snippetWidth = window.innerWidth * .6; - let scale = thumbnail.width / snippetWidth; - ctx.scale(scale, scale); - ctx.drawWindow(window, window.scrollX, window.scrollY, snippetWidth, - snippetWidth * aspectRatio, COLOR); - return thumbnail; -} -exports.getThumbnailCanvasForWindow = getThumbnailCanvasForWindow; - -/** - * Creates Base64 encoded data URI of the thumbnail for the passed window. - * @param {Window} window - * @returns {String} - */ -exports.getThumbnailURIForWindow = function getThumbnailURIForWindow(window) { - return getThumbnailCanvasForWindow(window).toDataURL() -}; - -// default 80x45 blank when not available -exports.BLANK = 'data:image/png;base64,' + - 'iVBORw0KGgoAAAANSUhEUgAAAFAAAAAtCAYAAAA5reyyAAAAJElEQVRoge3BAQ'+ - 'EAAACCIP+vbkhAAQAAAAAAAAAAAAAAAADXBjhtAAGQ0AF/AAAAAElFTkSuQmCC'; diff --git a/addon-sdk/source/lib/sdk/content/utils.js b/addon-sdk/source/lib/sdk/content/utils.js deleted file mode 100644 index 90995a614..000000000 --- a/addon-sdk/source/lib/sdk/content/utils.js +++ /dev/null @@ -1,105 +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' -}; - -var { merge } = require('../util/object'); -var { data } = require('../self'); -var assetsURI = data.url(); -var isArray = Array.isArray; -var method = require('../../method/core'); -var { uuid } = require('../util/uuid'); - -const isAddonContent = ({ contentURL }) => - contentURL && data.url(contentURL).startsWith(assetsURI); - -exports.isAddonContent = isAddonContent; - -function hasContentScript({ contentScript, contentScriptFile }) { - return (isArray(contentScript) ? contentScript.length > 0 : - !!contentScript) || - (isArray(contentScriptFile) ? contentScriptFile.length > 0 : - !!contentScriptFile); -} -exports.hasContentScript = hasContentScript; - -function requiresAddonGlobal(model) { - return model.injectInDocument || (isAddonContent(model) && !hasContentScript(model)); -} -exports.requiresAddonGlobal = requiresAddonGlobal; - -function getAttachEventType(model) { - if (!model) return null; - let when = model.contentScriptWhen; - return requiresAddonGlobal(model) ? 'document-element-inserted' : - when === 'start' ? 'document-element-inserted' : - when === 'ready' ? 'DOMContentLoaded' : - when === 'end' ? 'load' : - null; -} -exports.getAttachEventType = getAttachEventType; - -var attach = method('worker-attach'); -exports.attach = attach; - -var connect = method('worker-connect'); -exports.connect = connect; - -var detach = method('worker-detach'); -exports.detach = detach; - -var destroy = method('worker-destroy'); -exports.destroy = destroy; - -function WorkerHost (workerFor) { - // Define worker properties that just proxy to underlying worker - return ['postMessage', 'port', 'url', 'tab'].reduce(function(proto, name) { - // Use descriptor properties instead so we can call - // the worker function in the context of the worker so we - // don't have to create new functions with `fn.bind(worker)` - let descriptorProp = { - value: function (...args) { - let worker = workerFor(this); - return worker[name].apply(worker, args); - } - }; - - let accessorProp = { - get: function () { return workerFor(this)[name]; }, - set: function (value) { workerFor(this)[name] = value; } - }; - - Object.defineProperty(proto, name, merge({ - enumerable: true, - configurable: false, - }, isDescriptor(name) ? descriptorProp : accessorProp)); - return proto; - }, {}); - - function isDescriptor (prop) { - return ~['postMessage'].indexOf(prop); - } -} -exports.WorkerHost = WorkerHost; - -function makeChildOptions(options) { - function makeStringArray(arrayOrValue) { - if (!arrayOrValue) - return []; - return [].concat(arrayOrValue).map(String); - } - - return { - id: String(uuid()), - contentScript: makeStringArray(options.contentScript), - contentScriptFile: makeStringArray(options.contentScriptFile), - contentScriptOptions: options.contentScriptOptions ? - JSON.stringify(options.contentScriptOptions) : - null, - } -} -exports.makeChildOptions = makeChildOptions; diff --git a/addon-sdk/source/lib/sdk/content/worker-child.js b/addon-sdk/source/lib/sdk/content/worker-child.js deleted file mode 100644 index dbf65a933..000000000 --- a/addon-sdk/source/lib/sdk/content/worker-child.js +++ /dev/null @@ -1,158 +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'; - -const { merge } = require('../util/object'); -const { Class } = require('../core/heritage'); -const { emit } = require('../event/core'); -const { EventTarget } = require('../event/target'); -const { getInnerId, getByInnerId } = require('../window/utils'); -const { instanceOf, isObject } = require('../lang/type'); -const system = require('../system/events'); -const { when } = require('../system/unload'); -const { WorkerSandbox } = require('./sandbox'); -const { Ci } = require('chrome'); -const { process, frames } = require('../remote/child'); - -const EVENTS = { - 'chrome-page-shown': 'pageshow', - 'content-page-shown': 'pageshow', - 'chrome-page-hidden': 'pagehide', - 'content-page-hidden': 'pagehide', - 'inner-window-destroyed': 'detach', -} - -// The parent Worker must have been created (or an async message sent to spawn -// its creation) before creating the WorkerChild or messages from the content -// script to the parent will get lost. -const WorkerChild = Class({ - implements: [EventTarget], - - initialize(options) { - merge(this, options); - keepAlive.set(this.id, this); - - this.windowId = getInnerId(this.window); - if (this.contentScriptOptions) - this.contentScriptOptions = JSON.parse(this.contentScriptOptions); - - this.port = EventTarget(); - this.port.on('*', this.send.bind(this, 'event')); - this.on('*', this.send.bind(this)); - - this.observe = this.observe.bind(this); - - for (let topic in EVENTS) - system.on(topic, this.observe); - - this.receive = this.receive.bind(this); - process.port.on('sdk/worker/message', this.receive); - - this.sandbox = WorkerSandbox(this, this.window); - - // If the document has an unexpected readyState, its worker-child instance is initialized - // as frozen until one of the known readyState is reached. - let initialDocumentReadyState = this.window.document.readyState; - this.frozen = [ - "loading", "interactive", "complete" - ].includes(initialDocumentReadyState) ? false : true; - - if (this.frozen) { - console.warn("SDK worker-child started as frozen on unexpected initial document.readyState", { - initialDocumentReadyState, windowLocation: this.window.location.href, - }); - } - - this.frozenMessages = []; - this.on('pageshow', () => { - this.frozen = false; - this.frozenMessages.forEach(args => this.sandbox.emit(...args)); - this.frozenMessages = []; - }); - this.on('pagehide', () => { - this.frozen = true; - }); - }, - - // messages - receive(process, id, args) { - if (id !== this.id) - return; - args = JSON.parse(args); - - if (this.frozen) - this.frozenMessages.push(args); - else - this.sandbox.emit(...args); - - if (args[0] === 'detach') - this.destroy(args[1]); - }, - - send(...args) { - process.port.emit('sdk/worker/event', this.id, JSON.stringify(args, exceptions)); - }, - - // notifications - observe({ type, subject }) { - if (!this.sandbox) - return; - - if (subject.defaultView && getInnerId(subject.defaultView) === this.windowId) { - this.sandbox.emitSync(EVENTS[type]); - emit(this, EVENTS[type]); - } - - if (type === 'inner-window-destroyed' && - subject.QueryInterface(Ci.nsISupportsPRUint64).data === this.windowId) { - this.destroy(); - } - }, - - get frame() { - return frames.getFrameForWindow(this.window.top); - }, - - // detach/destroy: unload and release the sandbox - destroy(reason) { - if (!this.sandbox) - return; - - for (let topic in EVENTS) - system.off(topic, this.observe); - process.port.off('sdk/worker/message', this.receive); - - this.sandbox.destroy(reason); - this.sandbox = null; - keepAlive.delete(this.id); - - this.send('detach'); - } -}) -exports.WorkerChild = WorkerChild; - -// Error instances JSON poorly -function exceptions(key, value) { - if (!isObject(value) || !instanceOf(value, Error)) - return value; - let _errorType = value.constructor.name; - let { message, fileName, lineNumber, stack, name } = value; - return { _errorType, message, fileName, lineNumber, stack, name }; -} - -// workers for windows in this tab -var keepAlive = new Map(); - -process.port.on('sdk/worker/create', (process, options, cpows) => { - options.window = cpows.window; - let worker = new WorkerChild(options); - - let frame = frames.getFrameForWindow(options.window.top); - frame.port.emit('sdk/worker/connect', options.id, options.window.location.href); -}); - -when(reason => { - for (let worker of keepAlive.values()) - worker.destroy(reason); -}); diff --git a/addon-sdk/source/lib/sdk/content/worker.js b/addon-sdk/source/lib/sdk/content/worker.js deleted file mode 100644 index 39b940a88..000000000 --- a/addon-sdk/source/lib/sdk/content/worker.js +++ /dev/null @@ -1,180 +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 { emit } = require('../event/core'); -const { omit, merge } = require('../util/object'); -const { Class } = require('../core/heritage'); -const { method } = require('../lang/functional'); -const { getInnerId } = require('../window/utils'); -const { EventTarget } = require('../event/target'); -const { isPrivate } = require('../private-browsing/utils'); -const { getTabForBrowser, getTabForContentWindowNoShim, getBrowserForTab } = require('../tabs/utils'); -const { attach, connect, detach, destroy, makeChildOptions } = require('./utils'); -const { ensure } = require('../system/unload'); -const { on: observe } = require('../system/events'); -const { Ci, Cu } = require('chrome'); -const { modelFor: tabFor } = require('sdk/model/core'); -const { remoteRequire, processes, frames } = require('../remote/parent'); -remoteRequire('sdk/content/worker-child'); - -const workers = new WeakMap(); -var modelFor = (worker) => workers.get(worker); - -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."; - -// a handle for communication between content script and addon code -const Worker = Class({ - implements: [EventTarget], - - initialize(options = {}) { - ensure(this, 'detach'); - - let model = { - attached: false, - destroyed: false, - earlyEvents: [], // fired before worker was attached - frozen: true, // document is not yet active - options, - }; - workers.set(this, model); - - this.on('detach', this.detach); - EventTarget.prototype.initialize.call(this, options); - - this.receive = this.receive.bind(this); - - this.port = EventTarget(); - this.port.emit = this.send.bind(this, 'event'); - this.postMessage = this.send.bind(this, 'message'); - - if ('window' in options) { - let window = options.window; - delete options.window; - attach(this, window); - } - }, - - // messages - receive(process, id, args) { - let model = modelFor(this); - if (id !== model.id || !model.attached) - return; - args = JSON.parse(args); - if (model.destroyed && args[0] != 'detach') - return; - - if (args[0] === 'event') - emit(this.port, ...args.slice(1)) - else - emit(this, ...args); - }, - - send(...args) { - let model = modelFor(this); - if (model.destroyed && args[0] !== 'detach') - throw new Error(ERR_DESTROYED); - - if (!model.attached) { - model.earlyEvents.push(args); - return; - } - - processes.port.emit('sdk/worker/message', model.id, JSON.stringify(args)); - }, - - // properties - get url() { - let { url } = modelFor(this); - return url; - }, - - get contentURL() { - return this.url; - }, - - get tab() { - require('sdk/tabs'); - let { frame } = modelFor(this); - if (!frame) - return null; - let rawTab = getTabForBrowser(frame.frameElement); - return rawTab && tabFor(rawTab); - }, - - toString: () => '[object Worker]', - - detach: method(detach), - destroy: method(destroy), -}) -exports.Worker = Worker; - -attach.define(Worker, function(worker, window) { - let model = modelFor(worker); - if (model.attached) - detach(worker); - - let childOptions = makeChildOptions(model.options); - processes.port.emitCPOW('sdk/worker/create', [childOptions], { window }); - - let listener = (frame, id, url) => { - if (id != childOptions.id) - return; - frames.port.off('sdk/worker/connect', listener); - connect(worker, frame, { id, url }); - }; - frames.port.on('sdk/worker/connect', listener); -}); - -connect.define(Worker, function(worker, frame, { id, url }) { - let model = modelFor(worker); - if (model.attached) - detach(worker); - - model.id = id; - model.frame = frame; - model.url = url; - - // Messages from content -> chrome come through the process message manager - // since that lives longer than the frame message manager - processes.port.on('sdk/worker/event', worker.receive); - - model.attached = true; - model.destroyed = false; - model.frozen = false; - - model.earlyEvents.forEach(args => worker.send(...args)); - model.earlyEvents = []; - emit(worker, 'attach'); -}); - -// unload and release the child worker, release window reference -detach.define(Worker, function(worker) { - let model = modelFor(worker); - if (!model.attached) - return; - - processes.port.off('sdk/worker/event', worker.receive); - model.attached = false; - model.destroyed = true; - emit(worker, 'detach'); -}); - -isPrivate.define(Worker, ({ tab }) => isPrivate(tab)); - -// Something in the parent side has destroyed the worker, tell the child to -// detach, the child will respond when it has detached -destroy.define(Worker, function(worker, reason) { - let model = modelFor(worker); - model.destroyed = true; - if (!model.attached) - return; - - worker.send('detach', reason); -}); |