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