fune/addon-sdk/source/lib/sdk/page-worker.js
Dave Townsend 610674c3bb Bug 1129662: sdk/page-worker should use a remote page. r=krizsa
This makes page-worker load its pages in the remote process. It does so by
creating a single frame in the hidden window used to ensure we have a remote
process when necessary and then a module in the remote process is used to
create windowless browsers to load the pages.

This does break one API, getActiveView, but I don't think we should be
maintaining that and it has been unstable since its inception anyway.

Once downside, the l10n module now has to use the observer service to detect
documents rather than the DOM event, this might be causing more CPOW traffic
since that observer notification is shimmed so we may need to use the shim
waiver there.

--HG--
extra : commitid : FDiGeJzOj6Y
extra : rebase_source : 4a237ee4e75a5b00e8bc17df67dfc9a6db99156e
2015-10-16 13:22:28 -07:00

194 lines
5.3 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": "stable"
};
const { Class } = require('./core/heritage');
const { ns } = require('./core/namespace');
const { pipe, stripListeners } = require('./event/utils');
const { connect, destroy, WorkerHost } = require('./content/utils');
const { Worker } = require('./content/worker');
const { Disposable } = require('./core/disposable');
const { EventTarget } = require('./event/target');
const { setListeners } = require('./event/core');
const { window } = require('./addon/window');
const { create: makeFrame, getDocShell } = require('./frame/utils');
const { contract } = require('./util/contract');
const { contract: loaderContract } = require('./content/loader');
const { Rules } = require('./util/rules');
const { merge } = require('./util/object');
const { uuid } = require('./util/uuid');
const { useRemoteProcesses, remoteRequire, frames } = require("./remote/parent");
remoteRequire("sdk/content/page-worker");
const workers = new WeakMap();
const pages = new Map();
const internal = ns();
let workerFor = (page) => workers.get(page);
let isDisposed = (page) => !pages.has(internal(page).id);
// The frame is used to ensure we have a remote process to load workers in
let remoteFrame = null;
let framePromise = null;
function getFrame() {
if (framePromise)
return framePromise;
framePromise = new Promise(resolve => {
let view = makeFrame(window.document, {
namespaceURI: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
nodeName: "iframe",
type: "content",
remote: useRemoteProcesses,
uri: "about:blank"
});
// Wait for the remote side to connect
let listener = (frame) => {
if (frame.frameElement != view)
return;
frames.off("attach", listener);
remoteFrame = frame;
resolve(frame);
}
frames.on("attach", listener);
});
return framePromise;
}
var pageContract = contract(merge({
allow: {
is: ['object', 'undefined', 'null'],
map: function (allow) { return { script: !allow || allow.script !== false }}
},
onMessage: {
is: ['function', 'undefined']
},
include: {
is: ['string', 'array', 'regexp', 'undefined']
},
contentScriptWhen: {
is: ['string', 'undefined'],
map: (when) => when || "end"
}
}, loaderContract.rules));
function enableScript (page) {
getDocShell(viewFor(page)).allowJavascript = true;
}
function disableScript (page) {
getDocShell(viewFor(page)).allowJavascript = false;
}
function Allow (page) {
return {
get script() {
return internal(page).options.allow.script;
},
set script(value) {
internal(page).options.allow.script = value;
if (isDisposed(page))
return;
remoteFrame.port.emit("sdk/frame/set", internal(page).id, { allowScript: value });
}
};
}
function isValidURL(page, url) {
return !page.rules || page.rules.matchesAny(url);
}
const Page = Class({
implements: [
EventTarget,
Disposable
],
extends: WorkerHost(workerFor),
setup: function Page(options) {
options = pageContract(options);
// Sanitize the options
if ("contentScriptOptions" in options)
options.contentScriptOptions = JSON.stringify(options.contentScriptOptions);
internal(this).id = uuid().toString();
internal(this).options = options;
for (let prop of ['contentScriptFile', 'contentScript', 'contentScriptWhen']) {
this[prop] = options[prop];
}
pages.set(internal(this).id, this);
// Set listeners on the {Page} object itself, not the underlying worker,
// like `onMessage`, as it gets piped
setListeners(this, options);
let worker = new Worker(stripListeners(options));
workers.set(this, worker);
pipe(worker, this);
if (options.include) {
this.rules = Rules();
this.rules.add.apply(this.rules, [].concat(options.include));
}
getFrame().then(frame => {
if (isDisposed(this))
return;
frame.port.emit("sdk/frame/create", internal(this).id, stripListeners(options));
});
},
get allow() { return Allow(this); },
set allow(value) {
if (isDisposed(this))
return;
this.allow.script = pageContract({ allow: value }).allow.script;
},
get contentURL() {
return internal(this).options.contentURL;
},
set contentURL(value) {
if (!isValidURL(this, value))
return;
internal(this).options.contentURL = value;
if (isDisposed(this))
return;
remoteFrame.port.emit("sdk/frame/set", internal(this).id, { contentURL: value });
},
dispose: function () {
if (isDisposed(this))
return;
pages.delete(internal(this).id);
let worker = workerFor(this);
if (worker)
destroy(worker);
remoteFrame.port.emit("sdk/frame/destroy", internal(this).id);
// Destroy the remote frame if all the pages have been destroyed
if (pages.size == 0) {
framePromise = null;
remoteFrame.frameElement.remove();
remoteFrame = null;
}
},
toString: function () { return '[object Page]' }
});
exports.Page = Page;
frames.port.on("sdk/frame/connect", (frame, id, params) => {
let page = pages.get(id);
if (!page)
return;
connect(workerFor(page), frame, params);
});