diff --git a/devtools/client/aboutdebugging/components/target-list.js b/devtools/client/aboutdebugging/components/target-list.js index 81886046b84f..6fcf9c34cd2e 100644 --- a/devtools/client/aboutdebugging/components/target-list.js +++ b/devtools/client/aboutdebugging/components/target-list.js @@ -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")) diff --git a/devtools/client/aboutdebugging/components/workers.js b/devtools/client/aboutdebugging/components/workers.js index 628a8f93e7ab..87c4578e2991 100644 --- a/devtools/client/aboutdebugging/components/workers.js +++ b/devtools/client/aboutdebugging/components/workers.js @@ -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; + }), }); diff --git a/devtools/client/aboutdebugging/test/browser.ini b/devtools/client/aboutdebugging/test/browser.ini index 64d323388833..7308c9517629 100644 --- a/devtools/client/aboutdebugging/test/browser.ini +++ b/devtools/client/aboutdebugging/test/browser.ini @@ -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] diff --git a/devtools/client/aboutdebugging/test/browser_service_workers.js b/devtools/client/aboutdebugging/test/browser_service_workers.js new file mode 100644 index 000000000000..fccfeefb9040 --- /dev/null +++ b/devtools/client/aboutdebugging/test/browser_service_workers.js @@ -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); +}); diff --git a/devtools/client/aboutdebugging/test/head.js b/devtools/client/aboutdebugging/test/head.js index b61bb4dd1b5c..c2722640737e 100644 --- a/devtools/client/aboutdebugging/test/head.js +++ b/devtools/client/aboutdebugging/test/head.js @@ -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, diff --git a/devtools/client/aboutdebugging/test/service-workers/empty-sw.html b/devtools/client/aboutdebugging/test/service-workers/empty-sw.html new file mode 100644 index 000000000000..d475883f8cea --- /dev/null +++ b/devtools/client/aboutdebugging/test/service-workers/empty-sw.html @@ -0,0 +1,20 @@ + + + + + Service worker test + + + + + diff --git a/devtools/client/aboutdebugging/test/service-workers/empty-sw.js b/devtools/client/aboutdebugging/test/service-workers/empty-sw.js new file mode 100644 index 000000000000..1e7226402c29 --- /dev/null +++ b/devtools/client/aboutdebugging/test/service-workers/empty-sw.js @@ -0,0 +1 @@ +// Empty, just test registering. diff --git a/devtools/server/actors/child-process.js b/devtools/server/actors/child-process.js index d99e2121e1c9..521487c6736f 100644 --- a/devtools/server/actors/child-process.js +++ b/devtools/server/actors/child-process.js @@ -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, }; diff --git a/devtools/server/actors/root.js b/devtools/server/actors/root.js index da17d7a21b6f..f5f73fa89862 100644 --- a/devtools/server/actors/root.js +++ b/devtools/server/actors/root.js @@ -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(); } diff --git a/devtools/server/actors/worker.js b/devtools/server/actors/worker.js index 84c5996f6cb2..46b9d3b861db 100644 --- a/devtools/server/actors/worker.js +++ b/devtools/server/actors/worker.js @@ -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) { diff --git a/devtools/server/content-server.jsm b/devtools/server/content-server.jsm index 8c7b8501794b..87e15500a089 100644 --- a/devtools/server/content-server.jsm +++ b/devtools/server/content-server.jsm @@ -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; }); }