Bug 1345932 - show warning info in about:debugging#workers if multi-e10s is on;r=ochameau draft
authorJulian Descottes <jdescottes@mozilla.com>
Thu, 16 Mar 2017 16:06:05 +0100
changeset 500105 493253a65392982e7564343da7654a7ba00a3d6c
parent 499439 56287fac00505eb68ee07677779bfb8ee98eb2a3
child 500106 8d9b72bb595190db668f696e649ddec2b674d728
push id49605
push userjdescottes@mozilla.com
push dateThu, 16 Mar 2017 15:07:05 +0000
reviewersochameau
bugs1345932
milestone55.0a1
Bug 1345932 - show warning info in about:debugging#workers if multi-e10s is on;r=ochameau If multi e10s is enabled, service worker debugging is unavailable. We temporarily disabled the start/push/debug buttons in about:debugging for all service workers if multi-e10s is on. A warning section is also displayed on top of about:debugging, letting the user know that they can set dom.ipc.processCount to 1 in order to force a single content process and restore debugging capabilities. MozReview-Commit-ID: IPAlbOq2Hij
devtools/client/aboutdebugging/aboutdebugging.css
devtools/client/aboutdebugging/components/workers/moz.build
devtools/client/aboutdebugging/components/workers/multi-e10s-warning.js
devtools/client/aboutdebugging/components/workers/panel.js
devtools/client/aboutdebugging/components/workers/service-worker-target.js
devtools/client/aboutdebugging/test/browser.ini
devtools/client/aboutdebugging/test/browser_service_workers.js
devtools/client/aboutdebugging/test/browser_service_workers_fetch_flag.js
devtools/client/aboutdebugging/test/browser_service_workers_multi_content_process.js
devtools/client/aboutdebugging/test/browser_service_workers_push.js
devtools/client/aboutdebugging/test/browser_service_workers_push_service.js
devtools/client/aboutdebugging/test/browser_service_workers_start.js
devtools/client/aboutdebugging/test/browser_service_workers_status.js
devtools/client/aboutdebugging/test/browser_service_workers_timeout.js
devtools/client/aboutdebugging/test/browser_service_workers_unregister.js
devtools/client/aboutdebugging/test/head.js
devtools/client/locales/en-US/aboutdebugging.properties
--- a/devtools/client/aboutdebugging/aboutdebugging.css
+++ b/devtools/client/aboutdebugging/aboutdebugging.css
@@ -146,40 +146,58 @@ button {
   text-overflow: ellipsis;
 }
 
 .addons-controls {
   display: flex;
   flex-direction: row;
 }
 
-.addons-install-error {
-  background-color: #f3b0b0;
+.addons-install-error,
+.service-worker-multi-process {
   padding: 5px 10px;
   margin-top: 5px;
   margin-inline-end: 4px;
 }
 
-.service-worker-disabled .warning,
-.addons-install-error .warning {
+.addons-install-error {
+  background-color: #f3b0b0;
+}
+
+.service-worker-multi-process {
+  background-color: #ffeebb;
+  line-height: 1.5em;
+}
+
+.service-worker-multi-process .update-button {
+  margin: 5px 0;
+}
+
+.warning {
   background-image: url(chrome://devtools/skin/images/alerticon-warning.png);
   background-size: 13px 12px;
-  margin-inline-end: 10px;
   display: inline-block;
   width: 13px;
   height: 12px;
+  margin-inline-end: 10px;
 }
 
 @media (min-resolution: 1.1dppx) {
-  .service-worker-disabled .warning,
-  .addons-install-error .warning {
+  .warning {
     background-image: url(chrome://devtools/skin/images/alerticon-warning@2x.png);
   }
 }
 
+.addons-install-error .warning,
+.service-worker-multi-process .warning {
+  /* The warning icon can be hard to see on red / yellow backgrounds, this turns the icon
+  to a black icon. */
+  filter: brightness(0%);
+}
+
 .addons-options {
   flex: 1;
 }
 
 .addons-debugging-label {
   display: inline-block;
   margin-inline-end: 1ch;
 }
--- a/devtools/client/aboutdebugging/components/workers/moz.build
+++ b/devtools/client/aboutdebugging/components/workers/moz.build
@@ -1,9 +1,10 @@
 # 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/.
 
 DevToolsModules(
+    'multi-e10s-warning.js',
     'panel.js',
     'service-worker-target.js',
     'target.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/components/workers/multi-e10s-warning.js
@@ -0,0 +1,60 @@
+/* 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/. */
+
+/* eslint-env browser */
+
+"use strict";
+
+loader.lazyImporter(this, "PrivateBrowsingUtils",
+  "resource://gre/modules/PrivateBrowsingUtils.jsm");
+const { createClass, DOM: dom } =
+  require("devtools/client/shared/vendor/react");
+const Services = require("Services");
+const { Ci } = require("chrome");
+
+loader.lazyImporter(this, "PrivateBrowsingUtils",
+  "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+loader.lazyRequireGetter(this, "DebuggerClient",
+  "devtools/shared/client/main", true);
+
+const Strings = Services.strings.createBundle("chrome://devtools/locale/aboutdebugging.properties");
+const PROCESS_COUNT_PREF = "dom.ipc.processCount";
+
+module.exports = createClass({
+  displayName: "multiE10SWarning",
+
+  onUpdatePreferenceClick() {
+    let message = Strings.GetStringFromName("multiProcessWarningConfirmUpdate");
+    if (window.confirm(message)) {
+      Services.prefs.setIntPref(PROCESS_COUNT_PREF, 1);
+      // Restart the browser.
+      Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
+    }
+  },
+
+  render() {
+    return dom.div(
+      {
+        className: "service-worker-multi-process"
+      },
+      dom.div(
+        {},
+        dom.div({ className: "warning" }),
+        dom.b({}, Strings.GetStringFromName("multiProcessWarningTitle"))
+      ),
+      dom.div(
+        {},
+        Strings.GetStringFromName("multiProcessWarningMessage")
+      ),
+      dom.button(
+        {
+          className: "update-button",
+          onClick: this.onUpdatePreferenceClick,
+        },
+        Strings.GetStringFromName("multiProcessWarningUpdateLink")
+      )
+    );
+  },
+});
--- a/devtools/client/aboutdebugging/components/workers/panel.js
+++ b/devtools/client/aboutdebugging/components/workers/panel.js
@@ -11,67 +11,80 @@ const { Ci } = require("chrome");
 const { createClass, createFactory, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
 const { getWorkerForms } = require("../../modules/worker");
 const Services = require("Services");
 
 const PanelHeader = createFactory(require("../panel-header"));
 const TargetList = createFactory(require("../target-list"));
 const WorkerTarget = createFactory(require("./target"));
+const MultiE10SWarning = createFactory(require("./multi-e10s-warning"));
 const ServiceWorkerTarget = createFactory(require("./service-worker-target"));
 
 loader.lazyImporter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 loader.lazyRequireGetter(this, "DebuggerClient",
   "devtools/shared/client/main", true);
 
 const Strings = Services.strings.createBundle(
   "chrome://devtools/locale/aboutdebugging.properties");
 
 const WorkerIcon = "chrome://devtools/skin/images/debugging-workers.svg";
 const MORE_INFO_URL = "https://developer.mozilla.org/en-US/docs/Tools/about%3Adebugging";
+const PROCESS_COUNT_PREF = "dom.ipc.processCount";
 
 module.exports = createClass({
   displayName: "WorkersPanel",
 
   propTypes: {
     client: PropTypes.instanceOf(DebuggerClient).isRequired,
     id: PropTypes.string.isRequired
   },
 
   getInitialState() {
     return {
       workers: {
         service: [],
         shared: [],
         other: []
-      }
+      },
+      processCount: 1,
     };
   },
 
   componentDidMount() {
     let client = this.props.client;
-    client.addListener("workerListChanged", this.update);
-    client.addListener("serviceWorkerRegistrationListChanged", this.update);
-    client.addListener("processListChanged", this.update);
-    client.addListener("registration-changed", this.update);
+    client.addListener("workerListChanged", this.updateWorkers);
+    client.addListener("serviceWorkerRegistrationListChanged", this.updateWorkers);
+    client.addListener("processListChanged", this.updateWorkers);
+    client.addListener("registration-changed", this.updateWorkers);
 
-    this.update();
+    Services.prefs.addObserver(PROCESS_COUNT_PREF, this.updateMultiE10S, false);
+
+    this.updateMultiE10S();
+    this.updateWorkers();
   },
 
   componentWillUnmount() {
     let client = this.props.client;
-    client.removeListener("processListChanged", this.update);
-    client.removeListener("serviceWorkerRegistrationListChanged", this.update);
-    client.removeListener("workerListChanged", this.update);
-    client.removeListener("registration-changed", this.update);
+    client.removeListener("processListChanged", this.updateWorkers);
+    client.removeListener("serviceWorkerRegistrationListChanged", this.updateWorkers);
+    client.removeListener("workerListChanged", this.updateWorkers);
+    client.removeListener("registration-changed", this.updateWorkers);
+
+    Services.prefs.removeObserver(PROCESS_COUNT_PREF, this.updateMultiE10S);
   },
 
-  update() {
+  updateMultiE10S() {
+    let processCount = Services.prefs.getIntPref(PROCESS_COUNT_PREF);
+    this.setState({ processCount });
+  },
+
+  updateWorkers() {
     let workers = this.getInitialState().workers;
 
     getWorkerForms(this.props.client).then(forms => {
       forms.registrations.forEach(form => {
         workers.service.push({
           icon: WorkerIcon,
           name: form.url,
           url: form.url,
@@ -131,16 +144,20 @@ module.exports = createClass({
     for (let registration of registrations) {
       if (registration.scope === form.scope) {
         return registration;
       }
     }
     return null;
   },
 
+  isE10S() {
+    return Services.appinfo.browserTabsRemoteAutostart;
+  },
+
   renderServiceWorkersError() {
     let isWindowPrivate = PrivateBrowsingUtils.isContentWindowPrivate(window);
     let isPrivateBrowsingMode = PrivateBrowsingUtils.permanentPrivateBrowsing;
     let isServiceWorkerDisabled = !Services.prefs
                                     .getBoolPref("dom.serviceWorkers.enabled");
 
     let isDisabled = isWindowPrivate || isPrivateBrowsingMode || isServiceWorkerDisabled;
     if (!isDisabled) {
@@ -161,36 +178,41 @@ module.exports = createClass({
         Strings.GetStringFromName("moreInfo")
       ),
       ")"
     );
   },
 
   render() {
     let { client, id } = this.props;
-    let { workers } = this.state;
+    let { workers, processCount } = this.state;
+
+    let isE10S = Services.appinfo.browserTabsRemoteAutostart;
+    let isMultiE10S = isE10S && processCount > 1;
 
     return dom.div(
       {
         id: id + "-panel",
         className: "panel",
         role: "tabpanel",
         "aria-labelledby": id + "-header"
       },
       PanelHeader({
         id: id + "-header",
         name: Strings.GetStringFromName("workers")
       }),
+      isMultiE10S ? MultiE10SWarning() : "",
       dom.div(
         {
           id: "workers",
           className: "inverted-icons"
         },
         TargetList({
           client,
+          debugDisabled: isMultiE10S,
           error: this.renderServiceWorkersError(),
           id: "service-workers",
           name: Strings.GetStringFromName("serviceWorkers"),
           sort: true,
           targetClass: ServiceWorkerTarget,
           targets: workers.service
         }),
         TargetList({
--- a/devtools/client/aboutdebugging/components/workers/service-worker-target.js
+++ b/devtools/client/aboutdebugging/components/workers/service-worker-target.js
@@ -150,28 +150,30 @@ module.exports = createClass({
     // ACTIVE state. Unable to know the actual state ("installing", "waiting"), we
     // display a custom state "registering" for now. See Bug 1153292.
     return "registering";
   },
 
   renderButtons() {
     let pushButton = dom.button({
       className: "push-button",
-      onClick: this.push
+      onClick: this.push,
+      disabled: this.props.debugDisabled
     }, Strings.GetStringFromName("push"));
 
     let debugButton = dom.button({
       className: "debug-button",
       onClick: this.debug,
       disabled: this.props.debugDisabled
     }, Strings.GetStringFromName("debug"));
 
     let startButton = dom.button({
       className: "start-button",
       onClick: this.start,
+      disabled: this.props.debugDisabled
     }, Strings.GetStringFromName("start"));
 
     if (this.isRunning()) {
       if (this.isActive()) {
         return [pushButton, debugButton];
       }
       // Only debug button is available if the service worker is not active.
       return debugButton;
@@ -182,17 +184,17 @@ module.exports = createClass({
   renderUnregisterLink() {
     if (!this.isActive()) {
       // If not active, there might be no registrationActor available.
       return null;
     }
 
     return dom.a({
       onClick: this.unregister,
-      className: "unregister-link"
+      className: "unregister-link",
     }, Strings.GetStringFromName("unregister"));
   },
 
   render() {
     let { target } = this.props;
     let { pushSubscription } = this.state;
     let status = this.getServiceWorkerStatus();
 
--- a/devtools/client/aboutdebugging/test/browser.ini
+++ b/devtools/client/aboutdebugging/test/browser.ini
@@ -30,16 +30,17 @@ tags = webextensions
 tags = webextensions
 [browser_addons_debugging_initial_state.js]
 [browser_addons_install.js]
 [browser_addons_reload.js]
 [browser_addons_toggle_debug.js]
 [browser_page_not_found.js]
 [browser_service_workers.js]
 [browser_service_workers_fetch_flag.js]
+[browser_service_workers_multi_content_process.js]
 [browser_service_workers_not_compatible.js]
 [browser_service_workers_push.js]
 [browser_service_workers_push_service.js]
 [browser_service_workers_start.js]
 [browser_service_workers_status.js]
 [browser_service_workers_timeout.js]
 skip-if = true # Bug 1232931
 [browser_service_workers_unregister.js]
--- a/devtools/client/aboutdebugging/test/browser_service_workers.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers.js
@@ -4,23 +4,17 @@
 "use strict";
 
 // Service workers can't be loaded from chrome://,
 // but http:// is ok with dom.serviceWorkers.testing.enabled turned on.
 const SERVICE_WORKER = URL_ROOT + "service-workers/empty-sw.js";
 const TAB_URL = URL_ROOT + "service-workers/empty-sw.html";
 
 add_task(function* () {
-  yield new Promise(done => {
-    let options = {"set": [
-      ["dom.serviceWorkers.enabled", true],
-      ["dom.serviceWorkers.testing.enabled", true],
-    ]};
-    SpecialPowers.pushPrefEnv(options, done);
-  });
+  yield enableServiceWorkerDebugging();
 
   let { tab, document } = yield openAboutDebugging("workers");
 
   let swTab = yield addTab(TAB_URL);
 
   let serviceWorkersElement = getServiceWorkerList(document);
 
   yield waitForMutation(serviceWorkersElement, { childList: true });
--- a/devtools/client/aboutdebugging/test/browser_service_workers_fetch_flag.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_fetch_flag.js
@@ -4,24 +4,17 @@
 "use strict";
 
 // Service workers can't be loaded from chrome://,
 // but http:// is ok with dom.serviceWorkers.testing.enabled turned on.
 const EMPTY_SW_TAB_URL = URL_ROOT + "service-workers/empty-sw.html";
 const FETCH_SW_TAB_URL = URL_ROOT + "service-workers/fetch-sw.html";
 
 function* testBody(url, expecting) {
-  yield new Promise(done => {
-    let options = {"set": [
-      ["dom.serviceWorkers.enabled", true],
-      ["dom.serviceWorkers.testing.enabled", true],
-    ]};
-    SpecialPowers.pushPrefEnv(options, done);
-  });
-
+  yield enableServiceWorkerDebugging();
   let { tab, document } = yield openAboutDebugging("workers");
 
   let swTab = yield addTab(url);
 
   let serviceWorkersElement = getServiceWorkerList(document);
   yield waitForMutation(serviceWorkersElement, { childList: true });
 
   let fetchFlags =
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_multi_content_process.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Service worker debugging is unavailable when multi-e10s is enabled.
+// Check that the appropriate warning panel is displayed when there are more than 1
+// content process available.
+
+const SERVICE_WORKER = URL_ROOT + "service-workers/empty-sw.js";
+const TAB_URL = URL_ROOT + "service-workers/empty-sw.html";
+
+add_task(function* () {
+  yield enableServiceWorkerDebugging();
+  info("Force two content processes");
+  yield pushPref("dom.ipc.processCount", 2);
+
+  let { tab, document } = yield openAboutDebugging("workers");
+
+  let warningSection = document.querySelector(".service-worker-multi-process");
+  let img = warningSection.querySelector(".warning");
+  ok(img, "warning message is rendered");
+
+  let swTab = yield addTab(TAB_URL, { background: true });
+  let serviceWorkersElement = getServiceWorkerList(document);
+
+  yield waitForMutation(serviceWorkersElement, { childList: true });
+
+  info("Check that service worker buttons are disabled.");
+  // Check that the service worker appears in the UI
+  let serviceWorkerContainer = getServiceWorkerContainer(SERVICE_WORKER, document);
+  let debugButton = serviceWorkerContainer.querySelector(".debug-button");
+  ok(debugButton.disabled, "Start/Debug button is disabled");
+
+  info("Update the preference to 1");
+  let onWarningCleared = waitUntil(() => {
+    return document.querySelector(".service-worker-multi-process");
+  });
+  yield pushPref("dom.ipc.processCount", 1);
+  yield onWarningCleared;
+  ok(!debugButton.disabled, "Debug button is enabled.");
+
+  info("Update the preference back to 2");
+  let onWarningRestored = waitUntil(() => {
+    return document.querySelector(".service-worker-multi-process");
+  });
+  yield pushPref("dom.ipc.processCount", 2);
+  yield onWarningRestored;
+  ok(debugButton.disabled, "Debug button is disabled again.");
+
+  info("Unregister service worker");
+  try {
+    yield unregisterServiceWorker(swTab, serviceWorkersElement);
+    ok(true, "Service worker registration unregistered");
+  } catch (e) {
+    ok(false, "SW not unregistered; " + e);
+  }
+
+  yield removeTab(swTab);
+  yield closeAboutDebugging(tab);
+});
--- a/devtools/client/aboutdebugging/test/browser_service_workers_push.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_push.js
@@ -10,25 +10,17 @@
 // It should trigger a "push" notification in the worker.
 
 // Service workers can't be loaded from chrome://, but http:// is ok with
 // dom.serviceWorkers.testing.enabled turned on.
 const SERVICE_WORKER = URL_ROOT + "service-workers/push-sw.js";
 const TAB_URL = URL_ROOT + "service-workers/push-sw.html";
 
 add_task(function* () {
-  info("Turn on workers via mochitest http.");
-  yield new Promise(done => {
-    let options = { "set": [
-      // Accept workers from mochitest's http.
-      ["dom.serviceWorkers.testing.enabled", true],
-    ]};
-    SpecialPowers.pushPrefEnv(options, done);
-  });
-
+  yield enableServiceWorkerDebugging();
   let { tab, document } = yield openAboutDebugging("workers");
 
   // Listen for mutations in the service-workers list.
   let serviceWorkersElement = getServiceWorkerList(document);
   let onMutation = waitForMutation(serviceWorkersElement, { childList: true });
 
   // Open a tab that registers a push service worker.
   let swTab = yield addTab(TAB_URL);
--- a/devtools/client/aboutdebugging/test/browser_service_workers_push_service.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_push_service.js
@@ -13,24 +13,19 @@ const TAB_URL = URL_ROOT + "service-work
 
 const FAKE_ENDPOINT = "https://fake/endpoint";
 
 const PushService = Cc["@mozilla.org/push/Service;1"]
   .getService(Ci.nsIPushService).wrappedJSObject;
 
 add_task(function* () {
   info("Turn on workers via mochitest http.");
-  yield SpecialPowers.pushPrefEnv({
-    "set": [
-      // Accept workers from mochitest's http.
-      ["dom.serviceWorkers.testing.enabled", true],
-      // Enable the push service.
-      ["dom.push.connection.enabled", true],
-    ]
-  });
+  yield enableServiceWorkerDebugging();
+  // Enable the push service.
+  yield pushPref("dom.push.connection.enabled", true);
 
   info("Mock the push service");
   PushService.service = {
     _registrations: new Map(),
     _notify(scope) {
       Services.obs.notifyObservers(
         null,
         PushService.subscriptionModifiedTopic,
--- a/devtools/client/aboutdebugging/test/browser_service_workers_start.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_start.js
@@ -10,27 +10,19 @@
 // Service workers can't be loaded from chrome://, but http:// is ok with
 // dom.serviceWorkers.testing.enabled turned on.
 const SERVICE_WORKER = URL_ROOT + "service-workers/empty-sw.js";
 const TAB_URL = URL_ROOT + "service-workers/empty-sw.html";
 
 const SW_TIMEOUT = 1000;
 
 add_task(function* () {
-  info("Turn on workers via mochitest http.");
-  yield new Promise(done => {
-    let options = { "set": [
-      // Accept workers from mochitest's http.
-      ["dom.serviceWorkers.testing.enabled", true],
-      // Reduce the timeout to accelerate service worker freezing
-      ["dom.serviceWorkers.idle_timeout", SW_TIMEOUT],
-      ["dom.serviceWorkers.idle_extended_timeout", SW_TIMEOUT],
-    ]};
-    SpecialPowers.pushPrefEnv(options, done);
-  });
+  yield enableServiceWorkerDebugging();
+  yield pushPref("dom.serviceWorkers.idle_timeout", SW_TIMEOUT);
+  yield pushPref("dom.serviceWorkers.idle_extended_timeout", SW_TIMEOUT);
 
   let { tab, document } = yield openAboutDebugging("workers");
 
   // Listen for mutations in the service-workers list.
   let serviceWorkersElement = getServiceWorkerList(document);
   let onMutation = waitForMutation(serviceWorkersElement, { childList: true });
 
   // Open a tab that registers an empty service worker.
--- a/devtools/client/aboutdebugging/test/browser_service_workers_status.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_status.js
@@ -7,25 +7,19 @@
 // but http:// is ok with dom.serviceWorkers.testing.enabled turned on.
 const SERVICE_WORKER = URL_ROOT + "service-workers/delay-sw.js";
 const TAB_URL = URL_ROOT + "service-workers/delay-sw.html";
 const SW_TIMEOUT = 2000;
 
 requestLongerTimeout(2);
 
 add_task(function* () {
-  yield SpecialPowers.pushPrefEnv({
-    "set": [
-      // Accept workers from mochitest's http.
-      ["dom.serviceWorkers.testing.enabled", true],
-      ["dom.serviceWorkers.idle_timeout", SW_TIMEOUT],
-      ["dom.serviceWorkers.idle_extended_timeout", SW_TIMEOUT],
-      ["dom.ipc.processCount", 1],
-    ]
-  });
+  yield enableServiceWorkerDebugging();
+  yield pushPref("dom.serviceWorkers.idle_timeout", SW_TIMEOUT);
+  yield pushPref("dom.serviceWorkers.idle_extended_timeout", SW_TIMEOUT);
 
   let { tab, document } = yield openAboutDebugging("workers");
 
   // Listen for mutations in the service-workers list.
   let serviceWorkersElement = getServiceWorkerList(document);
   let onMutation = waitForMutation(serviceWorkersElement, { childList: true });
 
   let swTab = yield addTab(TAB_URL);
--- a/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js
@@ -6,27 +6,19 @@
 // Service workers can't be loaded from chrome://,
 // but http:// is ok with dom.serviceWorkers.testing.enabled turned on.
 const SERVICE_WORKER = URL_ROOT + "service-workers/empty-sw.js";
 const TAB_URL = URL_ROOT + "service-workers/empty-sw.html";
 
 const SW_TIMEOUT = 1000;
 
 add_task(function* () {
-  yield new Promise(done => {
-    let options = {"set": [
-      // Accept workers from mochitest's http
-      ["dom.serviceWorkers.testing.enabled", true],
-      // Reduce the timeout to expose issues when service worker
-      // freezing is broken
-      ["dom.serviceWorkers.idle_timeout", SW_TIMEOUT],
-      ["dom.serviceWorkers.idle_extended_timeout", SW_TIMEOUT],
-    ]};
-    SpecialPowers.pushPrefEnv(options, done);
-  });
+  yield enableServiceWorkerDebugging();
+  yield pushPref("dom.serviceWorkers.idle_timeout", SW_TIMEOUT);
+  yield pushPref("dom.serviceWorkers.idle_extended_timeout", SW_TIMEOUT);
 
   let { tab, document } = yield openAboutDebugging("workers");
 
   let swTab = yield addTab(TAB_URL);
 
   let serviceWorkersElement = getServiceWorkerList(document);
   yield waitForMutation(serviceWorkersElement, { childList: true });
 
--- a/devtools/client/aboutdebugging/test/browser_service_workers_unregister.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_unregister.js
@@ -10,25 +10,17 @@
 
 // Service workers can't be loaded from chrome://, but http:// is ok with
 // dom.serviceWorkers.testing.enabled turned on.
 const SCOPE = URL_ROOT + "service-workers/";
 const SERVICE_WORKER = SCOPE + "empty-sw.js";
 const TAB_URL = SCOPE + "empty-sw.html";
 
 add_task(function* () {
-  info("Turn on workers via mochitest http.");
-  yield new Promise(done => {
-    let options = { "set": [
-      // Accept workers from mochitest's http.
-      ["dom.serviceWorkers.testing.enabled", true],
-      ["dom.ipc.processCount", 1],
-    ]};
-    SpecialPowers.pushPrefEnv(options, done);
-  });
+  yield enableServiceWorkerDebugging();
 
   let { tab, document } = yield openAboutDebugging("workers");
 
   // Listen for mutations in the service-workers list.
   let serviceWorkersElement = getServiceWorkerList(document);
   let onMutation = waitForMutation(serviceWorkersElement, { childList: true });
 
   // Open a tab that registers an empty service worker.
--- a/devtools/client/aboutdebugging/test/head.js
+++ b/devtools/client/aboutdebugging/test/head.js
@@ -2,17 +2,18 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* eslint-env browser */
 /* exported openAboutDebugging, changeAboutDebuggingHash, closeAboutDebugging,
    installAddon, uninstallAddon, waitForMutation, waitForContentMutation, assertHasTarget,
    getServiceWorkerList, getTabList, openPanel, waitForInitialAddonList,
    waitForServiceWorkerRegistered, unregisterServiceWorker,
    waitForDelayedStartupFinished, setupTestAboutDebuggingWebExtension,
-   waitForServiceWorkerActivation */
+   waitForServiceWorkerActivation, enableServiceWorkerDebugging,
+   getServiceWorkerContainer */
 /* import-globals-from ../../framework/test/shared-head.js */
 
 "use strict";
 
 // Load the shared-head file first.
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
   this);
@@ -127,16 +128,36 @@ function getInstalledAddonNames(document
  * @return {DOMNode}                 target list or container element
  */
 function getServiceWorkerList(document) {
   return document.querySelector("#service-workers .target-list") ||
     document.querySelector("#service-workers.targets");
 }
 
 /**
+ * Retrieve the container element for the service worker corresponding to the provided
+ * name.
+ *
+ * @param  {String} name
+ *         expected service worker name
+ * @param  {DOMDocument} document
+ *         #service-workers section container document
+ * @return {DOMNode} container element
+ */
+function getServiceWorkerContainer(name, document) {
+  let nameElements = [...document.querySelectorAll("#service-workers .target-name")];
+  let nameElement = nameElements.filter(element => element.textContent === name)[0];
+  if (nameElement) {
+    return nameElement.closest(".target-container");
+  }
+
+  return null;
+}
+
+/**
  * Depending on whether there are tabs opened, return either a
  * target list element or its container.
  * @param  {DOMDocument}  document   #tabs section container document
  * @return {DOMNode}                 target list or container element
  */
 function getTabList(document) {
   return document.querySelector("#tabs .target-list") ||
     document.querySelector("#tabs.targets");
@@ -392,8 +413,25 @@ function* waitForServiceWorkerActivation
 
   let targetElement = name.parentNode.parentNode;
   let targetStatus = targetElement.querySelector(".target-status");
   while (targetStatus.textContent === "Registering") {
     // Wait for the status to leave the "registering" stage.
     yield waitForMutation(serviceWorkersElement, { childList: true, subtree: true });
   }
 }
+
+/**
+ * Set all preferences needed to enable service worker debugging and testing.
+ */
+function enableServiceWorkerDebugging() {
+  return new Promise(done => {
+    let options = { "set": [
+      // Enable service workers.
+      ["dom.serviceWorkers.enabled", true],
+      // Accept workers from mochitest's http.
+      ["dom.serviceWorkers.testing.enabled", true],
+      // Force single content process.
+      ["dom.ipc.processCount", 1],
+    ]};
+    SpecialPowers.pushPrefEnv(options, done);
+  });
+}
--- a/devtools/client/locales/en-US/aboutdebugging.properties
+++ b/devtools/client/locales/en-US/aboutdebugging.properties
@@ -114,8 +114,28 @@ pageNotFound = Page not found
 # %S will be replaced by the name of the page at run-time.
 doesNotExist = #%S does not exist!
 
 # LOCALIZATION NOTE (nothing):
 # This string is displayed when the list of workers is empty.
 nothing = Nothing yet.
 
 configurationIsNotCompatible = Your browser configuration is not compatible with Service Workers
+
+# LOCALIZATION NOTE (multiProcessWarningTitle):
+# This string is displayed as a warning message on top of the about:debugging#workers
+# page when multi-e10s is enabled
+multiProcessWarningTitle = Service Worker debugging is not compatible with multiple content processes at the moment.
+
+# LOCALIZATION NOTE (multiProcessWarningMessage):
+# This string is displayed in the warning section for multi-e10s in
+# about:debugging#workers
+multiProcessWarningMessage = The preference “dom.ipc.processCount” can be set to 1 to force a single content process.
+
+# LOCALIZATION NOTE (multiProcessWarningLink):
+# This string is the text content of a link in the warning section for multi-e10s in
+# about:debugging#workers. The link updates the pref and restarts the browser.
+multiProcessWarningUpdateLink = Set dom.ipc.processCount to 1
+
+# LOCALIZATION NOTE (multiProcessWarningConfirmUpdate):
+# This string is displayed as a confirmation message when the user clicks on
+# the multiProcessWarningUpdateLink in about:debugging#workers
+multiProcessWarningConfirmUpdate = Set “dom.ipc.processCount” to 1 and restart the browser?