Files
UXP-Fixed/toolkit/jetpack/sdk/content/worker.js
T
2018-02-09 06:46:43 -05:00

181 lines
4.9 KiB
JavaScript

/* 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);
});