Bug 1212797 - Show all registered service workers in about:debugging. r=ochameau
authorJan Keromnes <janx@linux.com>
Wed, 24 Feb 2016 06:30:00 +0100
changeset 321917 f695820b8269c466e403def828a2e99d7ba40f05
parent 321916 2893ca049d37efe2b8ffa44adf359d4d18b64be4
child 321918 0163992fcba2bcbfe7f6de6798a6ce31a84d53ce
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau
bugs1212797
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1212797 - Show all registered service workers in about:debugging. r=ochameau
devtools/client/aboutdebugging/components/target.js
devtools/client/aboutdebugging/components/workers-tab.js
devtools/client/aboutdebugging/test/browser_service_workers_timeout.js
devtools/server/actors/worker.js
--- a/devtools/client/aboutdebugging/components/target.js
+++ b/devtools/client/aboutdebugging/components/target.js
@@ -21,52 +21,65 @@ loader.lazyRequireGetter(this, "gDevTool
   "devtools/client/framework/devtools", true);
 
 const Strings = Services.strings.createBundle(
   "chrome://devtools/locale/aboutdebugging.properties");
 
 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());
+        });
+    });
+  },
 });
--- a/devtools/client/aboutdebugging/components/workers-tab.js
+++ b/devtools/client/aboutdebugging/components/workers-tab.js
@@ -33,23 +33,25 @@ exports.WorkersTab = React.createClass({
         other: []
       }
     };
   },
 
   componentDidMount() {
     let client = this.props.client;
     client.addListener("workerListChanged", this.update);
+    client.addListener("serviceWorkerRegistrationListChanged", this.update);
     client.addListener("processListChanged", this.update);
     this.update();
   },
 
   componentWillUnmount() {
     let client = this.props.client;
     client.removeListener("processListChanged", this.update);
+    client.removeListener("serviceWorkerRegistrationListChanged", this.update);
     client.removeListener("workerListChanged", this.update);
   },
 
   render() {
     let { client } = this.props;
     let { workers } = this.state;
 
     return React.createElement(
@@ -72,57 +74,95 @@ exports.WorkersTab = React.createClass({
             id: "other-workers",
             name: Strings.GetStringFromName("otherWorkers"),
             targets: workers.other, client }))
       );
   },
 
   update() {
     let workers = this.getInitialState().workers;
+
     this.getWorkerForms().then(forms => {
-      forms.forEach(form => {
+      forms.registrations.forEach(form => {
+        workers.service.push({
+          type: "serviceworker",
+          icon: WorkerIcon,
+          name: form.url,
+          url: form.url,
+          scope: form.scope,
+          registrationActor: form.actor
+        });
+      });
+
+      forms.workers.forEach(form => {
         let worker = {
-          name: form.url,
+          type: "worker",
           icon: WorkerIcon,
-          actorID: form.actor
+          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());
+
+      // 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;
+      // 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 };
   }),
 });
--- a/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js
@@ -62,17 +62,18 @@ add_task(function* () {
     });
   });
   ok(true, "Service worker registration resolved");
 
   // Retrieve the DEBUG button for the worker
   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
   let onToolboxReady = new Promise(done => {
     gDevTools.once("toolbox-ready", function(e, toolbox) {
       done(toolbox);
     });
   });
@@ -83,26 +84,28 @@ add_task(function* () {
   // Wait for more than the regular timeout,
   // so that if the worker freezing doesn't work,
   // it will be destroyed and removed from the list
   yield new Promise(done => {
     setTimeout(done, SW_TIMEOUT * 2);
   });
 
   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
   frameScript = function() {
     // Retrieve the `sw` promise created in the html page
     let { sw } = content.wrappedJSObject;
     sw.then(function(registration) {
       registration.unregister().then(function() {
@@ -119,11 +122,16 @@ add_task(function* () {
   yield new Promise(done => {
     mm.addMessageListener("sw-unregistered", function listener() {
       mm.removeMessageListener("sw-unregistered", listener);
       done();
     });
   });
   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);
 });
--- a/devtools/server/actors/worker.js
+++ b/devtools/server/actors/worker.js
@@ -48,22 +48,27 @@ let WorkerActor = protocol.ActorClass({
     this._transport = null;
     this.manage(this);
   },
 
   form: function (detail) {
     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 () {
     if (this._dbg.isClosed) {
       return { error: "closed" };
     }
 
     if (!this._attached) {