Bug 1225473 - Support Service workers in child process. r=janx

This commit is contained in:
Alexandre Poirot 2015-12-03 06:42:34 -08:00
parent ccf55f2114
commit 5affcaefe8
11 changed files with 180 additions and 17 deletions

View File

@ -27,7 +27,7 @@ exports.TargetListComponent = React.createClass({
return React.createElement(TargetComponent, { client, target });
});
return (
React.createElement("div", { className: "targets" },
React.createElement("div", { id: this.props.id, className: "targets" },
React.createElement("h4", null, this.props.name),
targets.length > 0 ? targets :
React.createElement("p", null, Strings.GetStringFromName("nothing"))

View File

@ -14,6 +14,8 @@ loader.lazyRequireGetter(this, "TargetListComponent",
"devtools/client/aboutdebugging/components/target-list", true);
loader.lazyRequireGetter(this, "Services");
loader.lazyImporter(this, "Task", "resource://gre/modules/Task.jsm");
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties");
const WorkerIcon = "chrome://devtools/skin/images/debugging-workers.svg";
@ -32,12 +34,16 @@ exports.WorkersComponent = React.createClass({
},
componentDidMount() {
this.props.client.addListener("workerListChanged", this.update);
let client = this.props.client;
client.addListener("workerListChanged", this.update);
client.addListener("processListChanged", this.update);
this.update();
},
componentWillUnmount() {
this.props.client.removeListener("workerListChanged", this.update);
let client = this.props.client;
client.removeListener("processListChanged", this.update);
client.removeListener("workerListChanged", this.update);
},
render() {
@ -45,22 +51,23 @@ exports.WorkersComponent = React.createClass({
let workers = this.state.workers;
return React.createElement("div", { className: "inverted-icons" },
React.createElement(TargetListComponent, {
id: "service-workers",
name: Strings.GetStringFromName("serviceWorkers"),
targets: workers.service, client }),
React.createElement(TargetListComponent, {
id: "shared-workers",
name: Strings.GetStringFromName("sharedWorkers"),
targets: workers.shared, client }),
React.createElement(TargetListComponent, {
id: "other-workers",
name: Strings.GetStringFromName("otherWorkers"),
targets: workers.other, client })
);
},
update() {
let client = this.props.client;
let workers = this.getInitialState().workers;
client.mainRoot.listWorkers(response => {
let forms = response.workers;
this.getWorkerForms().then(forms => {
forms.forEach(form => {
let worker = {
name: form.url,
@ -83,5 +90,29 @@ exports.WorkersComponent = React.createClass({
});
this.setState({ workers });
});
}
},
getWorkerForms: Task.async(function*() {
let client = this.props.client;
// List workers from the Parent process
let result = yield client.mainRoot.listWorkers();
let forms = result.workers;
// And then from the Child processes
let { processes } = yield client.mainRoot.listProcesses();
for (let process of processes) {
// Ignore parent process
if (process.parent) {
continue;
}
let { form } = yield client.getProcess(process.id);
let processActor = form.actor;
let { workers } = yield client.request({to: processActor,
type: "listWorkers"});
forms = forms.concat(workers);
}
return forms;
}),
});

View File

@ -5,5 +5,8 @@ support-files =
head.js
addons/unpacked/bootstrap.js
addons/unpacked/install.rdf
service-workers/empty-sw.html
service-workers/empty-sw.js
[browser_addons_install.js]
[browser_service_workers.js]

View File

@ -0,0 +1,66 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Service workers can't be loaded from chrome://,
// but http:// is ok with dom.serviceWorkers.testing.enabled turned on.
const HTTP_ROOT = CHROME_ROOT.replace("chrome://mochitests/content/",
"http://mochi.test:8888/");
const SERVICE_WORKER = HTTP_ROOT + "service-workers/empty-sw.js";
const TAB_URL = HTTP_ROOT + "service-workers/empty-sw.html";
function waitForWorkersUpdate(document) {
return new Promise(done => {
var observer = new MutationObserver(function(mutations) {
observer.disconnect();
done();
});
var target = document.getElementById("service-workers");
observer.observe(target, { childList: true });
});
}
add_task(function *() {
yield new Promise(done => {
let options = {"set": [
["dom.serviceWorkers.testing.enabled", true],
]};
SpecialPowers.pushPrefEnv(options, done);
});
let { tab, document } = yield openAboutDebugging("workers");
let swTab = yield addTab(TAB_URL);
yield waitForWorkersUpdate(document);
// Check that the service worker appears in the UI
let names = [...document.querySelectorAll("#service-workers .target-name")];
names = names.map(element => element.textContent);
ok(names.includes(SERVICE_WORKER), "The service worker url appears in the list: " + names);
// Use message manager to work with e10s
let frameScript = function () {
// Retrieve the `sw` promise created in the html page
let { sw } = content.wrappedJSObject;
sw.then(function (registration) {
registration.unregister().then(function (success) {
dump("SW unregistered: " + success + "\n");
},
function (e) {
dump("SW not unregistered; " + e + "\n");
});
});
};
swTab.linkedBrowser.messageManager.loadFrameScript("data:,(" + encodeURIComponent(frameScript) + ")()", true);
yield waitForWorkersUpdate(document);
// Check that the service worker disappeared from the UI
names = [...document.querySelectorAll("#service-workers .target-name")];
names = names.map(element => element.textContent);
ok(!names.includes(SERVICE_WORKER), "The service worker url is no longer in the list: " + names);
yield removeTab(swTab);
yield closeAboutDebugging(tab);
});

View File

@ -16,9 +16,13 @@ registerCleanupFunction(() => {
DevToolsUtils.testing = false;
});
function openAboutDebugging() {
function openAboutDebugging(page) {
info("opening about:debugging");
return addTab("about:debugging").then(tab => {
let url = "about:debugging";
if (page) {
url += "#" + page;
}
return addTab(url).then(tab => {
let browser = tab.linkedBrowser;
return {
tab,

View File

@ -0,0 +1,20 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Service worker test</title>
</head>
<body>
<script type="text/javascript">
var sw = navigator.serviceWorker.register("empty-sw.js");
sw.then(
function (registration) {
dump("SW registered\n");
},
function (e) {
dump("SW not registered: " + e + "\n");
}
);
</script>
</body>
</html>

View File

@ -0,0 +1 @@
// Empty, just test registering.

View File

@ -14,6 +14,8 @@ const Services = require("Services");
const { assert } = require("devtools/shared/DevToolsUtils");
const { TabSources } = require("./utils/TabSources");
loader.lazyRequireGetter(this, "WorkerActorList", "devtools/server/actors/worker", true);
function ChildProcessActor(aConnection) {
this.conn = aConnection;
this._contextPool = new ActorPool(this.conn);
@ -32,6 +34,10 @@ function ChildProcessActor(aConnection) {
.createInstance(Ci.nsIPrincipal);
let sandbox = Cu.Sandbox(systemPrincipal);
this._consoleScope = sandbox;
this._workerList = null;
this._workerActorPool = null;
this._onWorkerListChanged = this._onWorkerListChanged.bind(this);
}
exports.ChildProcessActor = ChildProcessActor;
@ -87,9 +93,42 @@ ChildProcessActor.prototype = {
};
},
onListWorkers: function () {
if (!this._workerList) {
this._workerList = new WorkerActorList({});
}
return this._workerList.getList().then(actors => {
let pool = new ActorPool(this.conn);
for (let actor of actors) {
pool.addActor(actor);
}
this.conn.removeActorPool(this._workerActorPool);
this._workerActorPool = pool;
this.conn.addActorPool(this._workerActorPool);
this._workerList.onListChanged = this._onWorkerListChanged;
return {
"from": this.actorID,
"workers": actors.map(actor => actor.form())
};
});
},
_onWorkerListChanged: function () {
this.conn.send({ from: this.actorID, type: "workerListChanged" });
this._workerList.onListChanged = null;
},
disconnect: function() {
this.conn.removeActorPool(this._contextPool);
this._contextPool = null;
// Tell the live lists we aren't watching any more.
if (this._workerList) {
this._workerList.onListChanged = null;
}
},
preNest: function() {
@ -103,4 +142,5 @@ ChildProcessActor.prototype = {
};
ChildProcessActor.prototype.requestTypes = {
"listWorkers": ChildProcessActor.prototype.onListWorkers,
};

View File

@ -207,6 +207,9 @@ RootActor.prototype = {
if (this._parameters.addonList) {
this._parameters.addonList.onListChanged = null;
}
if (this._parameters.workerList) {
this._parameters.workerList.onListChanged = null;
}
if (typeof this._parameters.onShutdown === 'function') {
this._parameters.onShutdown();
}

View File

@ -193,6 +193,9 @@ WorkerActorList.prototype = {
if (typeof onListChanged !== "function" && onListChanged !== null) {
throw new Error("onListChanged must be either a function or null.");
}
if (onListChanged === this._onListChanged) {
return;
}
if (this._mustNotify) {
if (this._onListChanged === null && onListChanged !== null) {

View File

@ -13,14 +13,7 @@ const { DevToolsLoader } = Cu.import("resource://devtools/shared/Loader.jsm", {}
this.EXPORTED_SYMBOLS = ["init"];
var started = false;
function init(msg) {
if (started) {
return;
}
started = true;
// Init a custom, invisible DebuggerServer, in order to not pollute
// the debugger with all devtools modules, nor break the debugger itself with using it
// in the same process.
@ -61,6 +54,5 @@ function init(msg) {
mm.removeMessageListener("debug:content-process-destroy", onDestroy);
DebuggerServer.destroy();
started = false;
});
}