fune/addon-sdk/source/lib/sdk/content/worker.js
Dave Townsend 53e0d3bab8 Bug 1146926: Allow attaching a worker to a CPOW window without invoking CPOW operations. r=gabor
By sending the CPOW to the child processes the one that owns it will create the
child worker and then send back the url of the window to set up the parent
side of the worker.

There are two breaking changes here. Workers invoked in this way no longer
attach synchronously. We no longer pass the window through the attach event.

--HG--
extra : commitid : By30pJI1Lj9
extra : rebase_source : bbca0c512d9fb135d2ca9d5ad263e3f8f6c297b3
extra : histedit_source : e50c90e0f20d60ad94949758b28721301c52d60c
2015-11-18 16:33:16 -08:00

180 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);
});