From eb7e62b31cbaf41ad4802d84f92fca34433e5a23 Mon Sep 17 00:00:00 2001 From: Jan Keromnes Date: Wed, 24 Feb 2016 06:30:00 +0100 Subject: [PATCH] Bug 1212797 - Show all registered service workers in about:debugging. r=ochameau --- .../aboutdebugging/components/target.js | 77 ++++++++++------- .../aboutdebugging/components/workers-tab.js | 84 ++++++++++++++----- .../test/browser_service_workers_timeout.js | 18 ++-- devtools/server/actors/worker.js | 7 +- 4 files changed, 126 insertions(+), 60 deletions(-) diff --git a/devtools/client/aboutdebugging/components/target.js b/devtools/client/aboutdebugging/components/target.js index f96e1731104b..b2bf58388ee5 100644 --- a/devtools/client/aboutdebugging/components/target.js +++ b/devtools/client/aboutdebugging/components/target.js @@ -26,47 +26,60 @@ const Strings = Services.strings.createBundle( exports.Target = React.createClass({ displayName: "Target", - debug() { - let { client, target } = this.props; - switch (target.type) { - case "extension": - BrowserToolboxProcess.init({ addonID: target.addonID }); - break; - case "serviceworker": - // Fall through. - case "sharedworker": - // Fall through. - case "worker": - let workerActor = this.props.target.actorID; - client.attachWorker(workerActor, (response, workerClient) => { - gDevTools.showToolbox(TargetFactory.forWorker(workerClient), - "jsdebugger", Toolbox.HostType.WINDOW) - .then(toolbox => { - toolbox.once("destroy", () => workerClient.detach()); - }); - }); - break; - default: - alert("Not implemented yet!"); - } - }, - render() { let { target, debugDisabled } = this.props; + let isServiceWorker = (target.type === "serviceworker"); + let isRunning = (!isServiceWorker || target.workerActor); return React.createElement("div", { className: "target" }, React.createElement("img", { className: "target-icon", role: "presentation", src: target.icon }), React.createElement("div", { className: "target-details" }, - React.createElement("div", { className: "target-name" }, target.name), - React.createElement("div", { className: "target-url" }, target.url) + React.createElement("div", { className: "target-name" }, target.name) ), - React.createElement("button", { - className: "debug-button", - onClick: this.debug, - disabled: debugDisabled, - }, Strings.GetStringFromName("debug")) + (isRunning ? + React.createElement("button", { + className: "debug-button", + onClick: this.debug, + disabled: debugDisabled, + }, Strings.GetStringFromName("debug")) : + null + ) ); }, + + debug() { + let { target } = this.props; + switch (target.type) { + case "extension": + BrowserToolboxProcess.init({ addonID: target.addonID }); + break; + case "serviceworker": + if (target.workerActor) { + this.openWorkerToolbox(target.workerActor); + } + break; + case "sharedworker": + this.openWorkerToolbox(target.workerActor); + break; + case "worker": + this.openWorkerToolbox(target.workerActor); + break; + default: + alert("Not implemented yet!"); + break; + } + }, + + openWorkerToolbox(workerActor) { + let { client } = this.props; + client.attachWorker(workerActor, (response, workerClient) => { + gDevTools.showToolbox(TargetFactory.forWorker(workerClient), + "jsdebugger", Toolbox.HostType.WINDOW) + .then(toolbox => { + toolbox.once("destroy", () => workerClient.detach()); + }); + }); + }, }); diff --git a/devtools/client/aboutdebugging/components/workers-tab.js b/devtools/client/aboutdebugging/components/workers-tab.js index 59d96e218313..ce2d7f8d319a 100644 --- a/devtools/client/aboutdebugging/components/workers-tab.js +++ b/devtools/client/aboutdebugging/components/workers-tab.js @@ -38,6 +38,7 @@ exports.WorkersTab = React.createClass({ componentDidMount() { let client = this.props.client; client.addListener("workerListChanged", this.update); + client.addListener("serviceWorkerRegistrationListChanged", this.update); client.addListener("processListChanged", this.update); this.update(); }, @@ -45,6 +46,7 @@ exports.WorkersTab = React.createClass({ componentWillUnmount() { let client = this.props.client; client.removeListener("processListChanged", this.update); + client.removeListener("serviceWorkerRegistrationListChanged", this.update); client.removeListener("workerListChanged", this.update); }, @@ -77,52 +79,90 @@ exports.WorkersTab = React.createClass({ update() { let workers = this.getInitialState().workers; + this.getWorkerForms().then(forms => { - forms.forEach(form => { - let worker = { - name: form.url, + forms.registrations.forEach(form => { + workers.service.push({ + type: "serviceworker", icon: WorkerIcon, - actorID: form.actor + name: form.url, + url: form.url, + scope: form.scope, + registrationActor: form.actor + }); + }); + + forms.workers.forEach(form => { + let worker = { + type: "worker", + icon: WorkerIcon, + name: form.url, + url: form.url, + workerActor: form.actor }; switch (form.type) { case Ci.nsIWorkerDebugger.TYPE_SERVICE: - worker.type = "serviceworker"; - workers.service.push(worker); + for (let registration of workers.service) { + if (registration.scope === form.scope) { + // XXX: Race, sometimes a ServiceWorkerRegistrationInfo doesn't + // have a scriptSpec, but its associated WorkerDebugger does. + if (!registration.url) { + registration.name = registration.url = form.url; + } + registration.workerActor = form.actor; + break; + } + } break; case Ci.nsIWorkerDebugger.TYPE_SHARED: worker.type = "sharedworker"; workers.shared.push(worker); break; default: - worker.type = "worker"; workers.other.push(worker); } }); + + // XXX: Filter out the service worker registrations for which we couldn't + // find the scriptSpec. + workers.service = workers.service.filter(reg => !!reg.url); + this.setState({ workers }); }); }, getWorkerForms: Task.async(function*() { let client = this.props.client; + let registrations = []; + let workers = []; - // List workers from the Parent process - let result = yield client.mainRoot.listWorkers(); - let forms = result.workers; + try { + // List service worker registrations + ({ registrations } = + yield client.mainRoot.listServiceWorkerRegistrations()); - // And then from the Child processes - let { processes } = yield client.mainRoot.listProcesses(); - for (let process of processes) { - // Ignore parent process - if (process.parent) { - continue; + // List workers from the Parent process + ({ workers } = yield client.mainRoot.listWorkers()); + + // 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 response = yield client.request({ + to: processActor, + type: "listWorkers" + }); + workers = workers.concat(response.workers); } - let { form } = yield client.getProcess(process.id); - let processActor = form.actor; - let { workers } = yield client.request({to: processActor, - type: "listWorkers"}); - forms = forms.concat(workers); + } catch (e) { + // Something went wrong, maybe our client is disconnected? } - return forms; + return { registrations, workers }; }), }); diff --git a/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js b/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js index 83e12233ab57..a6e6b30089f0 100644 --- a/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js +++ b/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js @@ -67,7 +67,8 @@ add_task(function* () { let names = [...document.querySelectorAll("#service-workers .target-name")]; let name = names.filter(element => element.textContent === SERVICE_WORKER)[0]; ok(name, "Found the service worker in the list"); - let debugBtn = name.parentNode.parentNode.querySelector("button"); + let targetElement = name.parentNode.parentNode; + let debugBtn = targetElement.querySelector(".debug-button"); ok(debugBtn, "Found its debug button"); // Click on it and wait for the toolbox to be ready @@ -88,16 +89,18 @@ add_task(function* () { }); assertHasWorker(true, document, "service-workers", SERVICE_WORKER); + ok(targetElement.querySelector(".debug-button"), + "The debug button is still there"); yield toolbox.destroy(); toolbox = null; // Now ensure that the worker is correctly destroyed // after we destroy the toolbox. - // The list should update once it get destroyed. - yield waitForMutation(serviceWorkersElement, { childList: true }); - - assertHasWorker(false, document, "service-workers", SERVICE_WORKER); + // The DEBUG button should disappear once the worker is destroyed. + yield waitForMutation(targetElement, { childList: true }); + ok(!targetElement.querySelector(".debug-button"), + "The debug button was removed when the worker was killed"); // Finally, unregister the service worker itself // Use message manager to work with e10s @@ -124,6 +127,11 @@ add_task(function* () { }); ok(true, "Service worker registration unregistered"); + // Now ensure that the worker registration is correctly removed. + // The list should update once the registration is destroyed. + yield waitForMutation(serviceWorkersElement, { childList: true }); + assertHasWorker(false, document, "service-workers", SERVICE_WORKER); + yield removeTab(swTab); yield closeAboutDebugging(tab); }); diff --git a/devtools/server/actors/worker.js b/devtools/server/actors/worker.js index da039355350a..1704818f990b 100644 --- a/devtools/server/actors/worker.js +++ b/devtools/server/actors/worker.js @@ -53,12 +53,17 @@ let WorkerActor = protocol.ActorClass({ if (detail === "actorid") { return this.actorID; } - return { + let form = { actor: this.actorID, consoleActor: this._consoleActor, url: this._dbg.url, type: this._dbg.type }; + if (this._dbg.type === Ci.nsIWorkerDebugger.TYPE_SERVICE) { + let registration = this._getServiceWorkerRegistrationInfo(); + form.scope = registration.scope; + } + return form; }, attach: method(function () {