forked from mirrors/gecko-dev
MozReview-Commit-ID: JVnJZsSA6nO --HG-- extra : transplant_source : q%0CS6%2BYrCJ%13%CA%29i%15%9Ai%C0%D6f%B2
158 lines
4.6 KiB
JavaScript
158 lines
4.6 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';
|
|
|
|
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);
|
|
});
|