summaryrefslogtreecommitdiffstats
path: root/toolkit/jetpack/sdk/deprecated/sync-worker.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/jetpack/sdk/deprecated/sync-worker.js')
-rw-r--r--toolkit/jetpack/sdk/deprecated/sync-worker.js288
1 files changed, 288 insertions, 0 deletions
diff --git a/toolkit/jetpack/sdk/deprecated/sync-worker.js b/toolkit/jetpack/sdk/deprecated/sync-worker.js
new file mode 100644
index 000000000..71cadac36
--- /dev/null
+++ b/toolkit/jetpack/sdk/deprecated/sync-worker.js
@@ -0,0 +1,288 @@
+/* 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/. */
+
+/**
+ *
+ * `deprecated/sync-worker` was previously `content/worker`, that was
+ * incompatible with e10s. we are in the process of switching to the new
+ * asynchronous `Worker`, which behaves slightly differently in some edge
+ * cases, so we are keeping this one around for a short period.
+ * try to switch to the new one as soon as possible..
+ *
+ */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Class } = require('../core/heritage');
+const { EventTarget } = require('../event/target');
+const { on, off, emit, setListeners } = require('../event/core');
+const {
+ attach, detach, destroy
+} = require('../content/utils');
+const { method } = require('../lang/functional');
+const { Ci, Cu, Cc } = require('chrome');
+const unload = require('../system/unload');
+const events = require('../system/events');
+const { getInnerId } = require("../window/utils");
+const { WorkerSandbox } = require('../content/sandbox');
+const { isPrivate } = require('../private-browsing/utils');
+
+// A weak map of workers to hold private attributes that
+// should not be exposed
+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.";
+
+const ERR_FROZEN = "The page is currently hidden and can no longer be used " +
+ "until it is visible again.";
+
+/**
+ * Message-passing facility for communication between code running
+ * in the content and add-on process.
+ * @see https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/content_worker
+ */
+const Worker = Class({
+ implements: [EventTarget],
+ initialize: function WorkerConstructor (options) {
+ // Save model in weak map to not expose properties
+ let model = createModel();
+ workers.set(this, model);
+
+ options = options || {};
+
+ if ('contentScriptFile' in options)
+ this.contentScriptFile = options.contentScriptFile;
+ if ('contentScriptOptions' in options)
+ this.contentScriptOptions = options.contentScriptOptions;
+ if ('contentScript' in options)
+ this.contentScript = options.contentScript;
+ if ('injectInDocument' in options)
+ this.injectInDocument = !!options.injectInDocument;
+
+ setListeners(this, options);
+
+ unload.ensure(this, "destroy");
+
+ // Ensure that worker.port is initialized for contentWorker to be able
+ // to send events during worker initialization.
+ this.port = createPort(this);
+
+ model.documentUnload = documentUnload.bind(this);
+ model.pageShow = pageShow.bind(this);
+ model.pageHide = pageHide.bind(this);
+
+ if ('window' in options)
+ attach(this, options.window);
+ },
+
+ /**
+ * Sends a message to the worker's global scope. Method takes single
+ * argument, which represents data to be sent to the worker. The data may
+ * be any primitive type value or `JSON`. Call of this method asynchronously
+ * emits `message` event with data value in the global scope of this
+ * worker.
+ *
+ * `message` event listeners can be set either by calling
+ * `self.on` with a first argument string `"message"` or by
+ * implementing `onMessage` function in the global scope of this worker.
+ * @param {Number|String|JSON} data
+ */
+ postMessage: function (...data) {
+ let model = modelFor(this);
+ let args = ['message'].concat(data);
+ if (!model.inited) {
+ model.earlyEvents.push(args);
+ return;
+ }
+ processMessage.apply(null, [this].concat(args));
+ },
+
+ get url () {
+ let model = modelFor(this);
+ // model.window will be null after detach
+ return model.window ? model.window.document.location.href : null;
+ },
+
+ get contentURL () {
+ let model = modelFor(this);
+ return model.window ? model.window.document.URL : null;
+ },
+
+ // Implemented to provide some of the previous features of exposing sandbox
+ // so that Worker can be extended
+ getSandbox: function () {
+ return modelFor(this).contentWorker;
+ },
+
+ toString: function () { return '[object Worker]'; },
+ attach: method(attach),
+ detach: method(detach),
+ destroy: method(destroy)
+});
+exports.Worker = Worker;
+
+attach.define(Worker, function (worker, window) {
+ let model = modelFor(worker);
+ model.window = window;
+ // Track document unload to destroy this worker.
+ // We can't watch for unload event on page's window object as it
+ // prevents bfcache from working:
+ // https://developer.mozilla.org/En/Working_with_BFCache
+ model.windowID = getInnerId(model.window);
+ events.on("inner-window-destroyed", model.documentUnload);
+
+ // will set model.contentWorker pointing to the private API:
+ model.contentWorker = WorkerSandbox(worker, model.window);
+
+ // Listen to pagehide event in order to freeze the content script
+ // while the document is frozen in bfcache:
+ model.window.addEventListener("pageshow", model.pageShow, true);
+ model.window.addEventListener("pagehide", model.pageHide, true);
+
+ // Mainly enable worker.port.emit to send event to the content worker
+ model.inited = true;
+ model.frozen = false;
+
+ // Fire off `attach` event
+ emit(worker, 'attach', window);
+
+ // Process all events and messages that were fired before the
+ // worker was initialized.
+ model.earlyEvents.forEach(args => processMessage.apply(null, [worker].concat(args)));
+});
+
+/**
+ * Remove all internal references to the attached document
+ * Tells _port to unload itself and removes all the references from itself.
+ */
+detach.define(Worker, function (worker, reason) {
+ let model = modelFor(worker);
+
+ // maybe unloaded before content side is created
+ if (model.contentWorker) {
+ model.contentWorker.destroy(reason);
+ }
+
+ model.contentWorker = null;
+ if (model.window) {
+ model.window.removeEventListener("pageshow", model.pageShow, true);
+ model.window.removeEventListener("pagehide", model.pageHide, true);
+ }
+ model.window = null;
+ // This method may be called multiple times,
+ // avoid dispatching `detach` event more than once
+ if (model.windowID) {
+ model.windowID = null;
+ events.off("inner-window-destroyed", model.documentUnload);
+ model.earlyEvents.length = 0;
+ emit(worker, 'detach');
+ }
+ model.inited = false;
+});
+
+isPrivate.define(Worker, ({ tab }) => isPrivate(tab));
+
+/**
+ * Tells content worker to unload itself and
+ * removes all the references from itself.
+ */
+destroy.define(Worker, function (worker, reason) {
+ detach(worker, reason);
+ modelFor(worker).inited = true;
+ // Specifying no type or listener removes all listeners
+ // from target
+ off(worker);
+ off(worker.port);
+});
+
+/**
+ * Events fired by workers
+ */
+function documentUnload ({ subject, data }) {
+ let model = modelFor(this);
+ let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
+ if (innerWinID != model.windowID) return false;
+ detach(this);
+ return true;
+}
+
+function pageShow () {
+ let model = modelFor(this);
+ model.contentWorker.emitSync('pageshow');
+ emit(this, 'pageshow');
+ model.frozen = false;
+}
+
+function pageHide () {
+ let model = modelFor(this);
+ model.contentWorker.emitSync('pagehide');
+ emit(this, 'pagehide');
+ model.frozen = true;
+}
+
+/**
+ * Fired from postMessage and emitEventToContent, or from the earlyMessage
+ * queue when fired before the content is loaded. Sends arguments to
+ * contentWorker if able
+ */
+
+function processMessage (worker, ...args) {
+ let model = modelFor(worker) || {};
+ if (!model.contentWorker)
+ throw new Error(ERR_DESTROYED);
+ if (model.frozen)
+ throw new Error(ERR_FROZEN);
+ model.contentWorker.emit.apply(null, args);
+}
+
+function createModel () {
+ return {
+ // List of messages fired before worker is initialized
+ earlyEvents: [],
+ // Is worker connected to the content worker sandbox ?
+ inited: false,
+ // Is worker being frozen? i.e related document is frozen in bfcache.
+ // Content script should not be reachable if frozen.
+ frozen: true,
+ /**
+ * Reference to the content side of the worker.
+ * @type {WorkerGlobalScope}
+ */
+ contentWorker: null,
+ /**
+ * Reference to the window that is accessible from
+ * the content scripts.
+ * @type {Object}
+ */
+ window: null
+ };
+}
+
+function createPort (worker) {
+ let port = EventTarget();
+ port.emit = emitEventToContent.bind(null, worker);
+ return port;
+}
+
+/**
+ * Emit a custom event to the content script,
+ * i.e. emit this event on `self.port`
+ */
+function emitEventToContent (worker, ...eventArgs) {
+ let model = modelFor(worker);
+ let args = ['event'].concat(eventArgs);
+ if (!model.inited) {
+ model.earlyEvents.push(args);
+ return;
+ }
+ processMessage.apply(null, [worker].concat(args));
+}