summaryrefslogtreecommitdiffstats
path: root/toolkit/jetpack/sdk/content/worker.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/jetpack/sdk/content/worker.js')
-rw-r--r--toolkit/jetpack/sdk/content/worker.js180
1 files changed, 180 insertions, 0 deletions
diff --git a/toolkit/jetpack/sdk/content/worker.js b/toolkit/jetpack/sdk/content/worker.js
new file mode 100644
index 000000000..39b940a88
--- /dev/null
+++ b/toolkit/jetpack/sdk/content/worker.js
@@ -0,0 +1,180 @@
+/* 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);
+});