summaryrefslogtreecommitdiffstats
path: root/devtools/client/aboutdebugging/test
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /devtools/client/aboutdebugging/test
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'devtools/client/aboutdebugging/test')
-rw-r--r--devtools/client/aboutdebugging/test/.eslintrc.js26
-rw-r--r--devtools/client/aboutdebugging/test/addons/bad/manifest.json1
-rw-r--r--devtools/client/aboutdebugging/test/addons/bug1273184.xpibin0 -> 4246 bytes
-rw-r--r--devtools/client/aboutdebugging/test/addons/test-devtools-webextension-nobg/manifest.json10
-rw-r--r--devtools/client/aboutdebugging/test/addons/test-devtools-webextension/bg.js20
-rw-r--r--devtools/client/aboutdebugging/test/addons/test-devtools-webextension/manifest.json17
-rw-r--r--devtools/client/aboutdebugging/test/addons/test-devtools-webextension/popup.html10
-rw-r--r--devtools/client/aboutdebugging/test/addons/test-devtools-webextension/popup.js13
-rw-r--r--devtools/client/aboutdebugging/test/addons/unpacked/bootstrap.js22
-rw-r--r--devtools/client/aboutdebugging/test/addons/unpacked/install.rdf26
-rw-r--r--devtools/client/aboutdebugging/test/browser.ini44
-rw-r--r--devtools/client/aboutdebugging/test/browser_addons_debug_bootstrapped.js83
-rw-r--r--devtools/client/aboutdebugging/test/browser_addons_debug_webextension.js74
-rw-r--r--devtools/client/aboutdebugging/test/browser_addons_debug_webextension_inspector.js82
-rw-r--r--devtools/client/aboutdebugging/test/browser_addons_debug_webextension_nobg.js84
-rw-r--r--devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js189
-rw-r--r--devtools/client/aboutdebugging/test/browser_addons_debugging_initial_state.js73
-rw-r--r--devtools/client/aboutdebugging/test/browser_addons_install.js51
-rw-r--r--devtools/client/aboutdebugging/test/browser_addons_reload.js207
-rw-r--r--devtools/client/aboutdebugging/test/browser_addons_toggle_debug.js65
-rw-r--r--devtools/client/aboutdebugging/test/browser_page_not_found.js37
-rw-r--r--devtools/client/aboutdebugging/test/browser_service_workers.js51
-rw-r--r--devtools/client/aboutdebugging/test/browser_service_workers_not_compatible.js60
-rw-r--r--devtools/client/aboutdebugging/test/browser_service_workers_push.js105
-rw-r--r--devtools/client/aboutdebugging/test/browser_service_workers_push_service.js122
-rw-r--r--devtools/client/aboutdebugging/test/browser_service_workers_start.js97
-rw-r--r--devtools/client/aboutdebugging/test/browser_service_workers_status.js72
-rw-r--r--devtools/client/aboutdebugging/test/browser_service_workers_timeout.js92
-rw-r--r--devtools/client/aboutdebugging/test/browser_service_workers_unregister.js77
-rw-r--r--devtools/client/aboutdebugging/test/browser_tabs.js59
-rw-r--r--devtools/client/aboutdebugging/test/head.js367
-rw-r--r--devtools/client/aboutdebugging/test/service-workers/delay-sw.html22
-rw-r--r--devtools/client/aboutdebugging/test/service-workers/delay-sw.js17
-rw-r--r--devtools/client/aboutdebugging/test/service-workers/empty-sw.html22
-rw-r--r--devtools/client/aboutdebugging/test/service-workers/empty-sw.js1
-rw-r--r--devtools/client/aboutdebugging/test/service-workers/push-sw.html32
-rw-r--r--devtools/client/aboutdebugging/test/service-workers/push-sw.js33
37 files changed, 2363 insertions, 0 deletions
diff --git a/devtools/client/aboutdebugging/test/.eslintrc.js b/devtools/client/aboutdebugging/test/.eslintrc.js
new file mode 100644
index 000000000..8c4bee0ef
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/.eslintrc.js
@@ -0,0 +1,26 @@
+"use strict";
+
+module.exports = {
+ // Extend from the shared list of defined globals for mochitests.
+ "extends": "../../../.eslintrc.mochitests.js",
+ // All globals made available in aboutdebugging head.js file.
+ "globals": {
+ "AddonManager": true,
+ "addTab": true,
+ "assertHasTarget": true,
+ "CHROME_ROOT": true,
+ "changeAboutDebuggingHash": true,
+ "closeAboutDebugging": true,
+ "getServiceWorkerList": true,
+ "getSupportsFile": true,
+ "installAddon": true,
+ "openAboutDebugging": true,
+ "openPanel": true,
+ "removeTab": true,
+ "uninstallAddon": true,
+ "unregisterServiceWorker": true,
+ "waitForInitialAddonList": true,
+ "waitForMutation": true,
+ "waitForServiceWorkerRegistered": true
+ }
+};
diff --git a/devtools/client/aboutdebugging/test/addons/bad/manifest.json b/devtools/client/aboutdebugging/test/addons/bad/manifest.json
new file mode 100644
index 000000000..4ab10b4de
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/addons/bad/manifest.json
@@ -0,0 +1 @@
+this is not valid json
diff --git a/devtools/client/aboutdebugging/test/addons/bug1273184.xpi b/devtools/client/aboutdebugging/test/addons/bug1273184.xpi
new file mode 100644
index 000000000..e1c42376e
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/addons/bug1273184.xpi
Binary files differ
diff --git a/devtools/client/aboutdebugging/test/addons/test-devtools-webextension-nobg/manifest.json b/devtools/client/aboutdebugging/test/addons/test-devtools-webextension-nobg/manifest.json
new file mode 100644
index 000000000..289d8b918
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/addons/test-devtools-webextension-nobg/manifest.json
@@ -0,0 +1,10 @@
+{
+ "manifest_version": 2,
+ "name": "test-devtools-webextension-nobg",
+ "version": "1.0",
+ "applications": {
+ "gecko": {
+ "id": "test-devtools-webextension-nobg@mozilla.org"
+ }
+ }
+}
diff --git a/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/bg.js b/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/bg.js
new file mode 100644
index 000000000..7ab93c46a
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/bg.js
@@ -0,0 +1,20 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-env browser */
+/* global browser */
+
+"use strict";
+
+document.body.innerText = "Background Page Body Test Content";
+
+// This function are called from the webconsole test:
+// browser_addons_debug_webextension.js
+
+function myWebExtensionAddonFunction() { // eslint-disable-line no-unused-vars
+ console.log("Background page function called", browser.runtime.getManifest());
+}
+
+function myWebExtensionShowPopup() { // eslint-disable-line no-unused-vars
+ console.log("readyForOpenPopup");
+}
diff --git a/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/manifest.json b/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/manifest.json
new file mode 100644
index 000000000..f224e5dcf
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/manifest.json
@@ -0,0 +1,17 @@
+{
+ "manifest_version": 2,
+ "name": "test-devtools-webextension",
+ "version": "1.0",
+ "applications": {
+ "gecko": {
+ "id": "test-devtools-webextension@mozilla.org"
+ }
+ },
+ "background": {
+ "scripts": ["bg.js"]
+ },
+ "browser_action": {
+ "default_title": "WebExtension Popup Debugging",
+ "default_popup": "popup.html"
+ }
+}
diff --git a/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/popup.html b/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/popup.html
new file mode 100644
index 000000000..4e3f7aba2
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/popup.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <script src="popup.js"></script>
+ </head>
+ <body>
+ Background Page Body Test Content
+ </body>
+</html>
diff --git a/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/popup.js b/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/popup.js
new file mode 100644
index 000000000..035375682
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/popup.js
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-env browser */
+/* global browser */
+
+"use strict";
+
+// This function is called from the webconsole test:
+// browser_addons_debug_webextension.js
+function myWebExtensionPopupAddonFunction() { // eslint-disable-line no-unused-vars
+ console.log("Popup page function called", browser.runtime.getManifest());
+}
diff --git a/devtools/client/aboutdebugging/test/addons/unpacked/bootstrap.js b/devtools/client/aboutdebugging/test/addons/unpacked/bootstrap.js
new file mode 100644
index 000000000..d96e31e5e
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/addons/unpacked/bootstrap.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-env browser */
+/* exported startup, shutdown, install, uninstall */
+
+"use strict";
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+// This function is called from the webconsole test:
+// browser_addons_debug_bootstrapped.js
+function myBootstrapAddonFunction() { // eslint-disable-line no-unused-vars
+ Services.obs.notifyObservers(null, "addon-console-works", null);
+}
+
+function startup() {
+ Services.obs.notifyObservers(null, "test-devtools", null);
+}
+function shutdown() {}
+function install() {}
+function uninstall() {}
diff --git a/devtools/client/aboutdebugging/test/addons/unpacked/install.rdf b/devtools/client/aboutdebugging/test/addons/unpacked/install.rdf
new file mode 100644
index 000000000..91c7474cc
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/addons/unpacked/install.rdf
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<!--
+# 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/.
+-->
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+ <Description about="urn:mozilla:install-manifest"
+ em:id="test-devtools@mozilla.org"
+ em:name="test-devtools"
+ em:version="1.0"
+ em:type="2"
+ em:creator="Mozilla">
+
+ <em:bootstrap>true</em:bootstrap>
+ <em:targetApplication>
+ <Description>
+ <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+ <em:minVersion>44.0a1</em:minVersion>
+ <em:maxVersion>*</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+ </Description>
+</RDF>
diff --git a/devtools/client/aboutdebugging/test/browser.ini b/devtools/client/aboutdebugging/test/browser.ini
new file mode 100644
index 000000000..90ed59d21
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser.ini
@@ -0,0 +1,44 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ head.js
+ addons/unpacked/bootstrap.js
+ addons/unpacked/install.rdf
+ addons/bad/manifest.json
+ addons/bug1273184.xpi
+ addons/test-devtools-webextension/*
+ addons/test-devtools-webextension-nobg/*
+ service-workers/delay-sw.html
+ service-workers/delay-sw.js
+ service-workers/empty-sw.html
+ service-workers/empty-sw.js
+ service-workers/push-sw.html
+ service-workers/push-sw.js
+ !/devtools/client/framework/test/shared-head.js
+
+[browser_addons_debug_bootstrapped.js]
+[browser_addons_debug_webextension.js]
+tags = webextensions
+[browser_addons_debug_webextension_inspector.js]
+tags = webextensions
+[browser_addons_debug_webextension_nobg.js]
+tags = webextensions
+[browser_addons_debug_webextension_popup.js]
+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_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]
+[browser_tabs.js]
+skip-if = true # Bug 1304941
diff --git a/devtools/client/aboutdebugging/test/browser_addons_debug_bootstrapped.js b/devtools/client/aboutdebugging/test/browser_addons_debug_bootstrapped.js
new file mode 100644
index 000000000..982c4c726
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_bootstrapped.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
+requestLongerTimeout(2);
+
+const ADDON_ID = "test-devtools@mozilla.org";
+const ADDON_NAME = "test-devtools";
+
+const { BrowserToolboxProcess } = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
+
+add_task(function* () {
+ yield new Promise(resolve => {
+ let options = {"set": [
+ // Force enabling of addons debugging
+ ["devtools.chrome.enabled", true],
+ ["devtools.debugger.remote-enabled", true],
+ // Disable security prompt
+ ["devtools.debugger.prompt-connection", false],
+ // Enable Browser toolbox test script execution via env variable
+ ["devtools.browser-toolbox.allow-unsafe-script", true],
+ ]};
+ SpecialPowers.pushPrefEnv(options, resolve);
+ });
+
+ let { tab, document } = yield openAboutDebugging("addons");
+ yield waitForInitialAddonList(document);
+ yield installAddon({
+ document,
+ path: "addons/unpacked/install.rdf",
+ name: ADDON_NAME,
+ });
+
+ // Retrieve the DEBUG button for the addon
+ let names = [...document.querySelectorAll("#addons .target-name")];
+ let name = names.filter(element => element.textContent === ADDON_NAME)[0];
+ ok(name, "Found the addon in the list");
+ let targetElement = name.parentNode.parentNode;
+ let debugBtn = targetElement.querySelector(".debug-button");
+ ok(debugBtn, "Found its debug button");
+
+ // Wait for a notification sent by a script evaluated the test addon via
+ // the web console.
+ let onCustomMessage = new Promise(done => {
+ Services.obs.addObserver(function listener() {
+ Services.obs.removeObserver(listener, "addon-console-works");
+ done();
+ }, "addon-console-works", false);
+ });
+
+ // Be careful, this JS function is going to be executed in the addon toolbox,
+ // which lives in another process. So do not try to use any scope variable!
+ let env = Cc["@mozilla.org/process/environment;1"]
+ .getService(Ci.nsIEnvironment);
+ let testScript = function () {
+ /* eslint-disable no-undef */
+ toolbox.selectTool("webconsole")
+ .then(console => {
+ let { jsterm } = console.hud;
+ return jsterm.execute("myBootstrapAddonFunction()");
+ })
+ .then(() => toolbox.destroy());
+ /* eslint-enable no-undef */
+ };
+ env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
+ registerCleanupFunction(() => {
+ env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
+ });
+
+ let onToolboxClose = BrowserToolboxProcess.once("close");
+
+ debugBtn.click();
+
+ yield onCustomMessage;
+ ok(true, "Received the notification message from the bootstrap.js function");
+
+ yield onToolboxClose;
+ ok(true, "Addon toolbox closed");
+
+ yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
+ yield closeAboutDebugging(tab);
+});
diff --git a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension.js b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension.js
new file mode 100644
index 000000000..5082c1f3f
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
+requestLongerTimeout(2);
+
+const ADDON_ID = "test-devtools-webextension@mozilla.org";
+const ADDON_NAME = "test-devtools-webextension";
+const ADDON_MANIFEST_PATH = "addons/test-devtools-webextension/manifest.json";
+
+const {
+ BrowserToolboxProcess
+} = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
+
+/**
+ * This test file ensures that the webextension addon developer toolbox:
+ * - when the debug button is clicked on a webextension, the opened toolbox
+ * has a working webconsole with the background page as default target;
+ */
+add_task(function* testWebExtensionsToolboxWebConsole() {
+ let {
+ tab, document, debugBtn,
+ } = yield setupTestAboutDebuggingWebExtension(ADDON_NAME, ADDON_MANIFEST_PATH);
+
+ // Wait for a notification sent by a script evaluated the test addon via
+ // the web console.
+ let onCustomMessage = new Promise(done => {
+ Services.obs.addObserver(function listener(message, topic) {
+ let apiMessage = message.wrappedJSObject;
+ if (!apiMessage.originAttributes ||
+ apiMessage.originAttributes.addonId != ADDON_ID) {
+ return;
+ }
+ Services.obs.removeObserver(listener, "console-api-log-event");
+ done(apiMessage.arguments);
+ }, "console-api-log-event", false);
+ });
+
+ // Be careful, this JS function is going to be executed in the addon toolbox,
+ // which lives in another process. So do not try to use any scope variable!
+ let env = Cc["@mozilla.org/process/environment;1"]
+ .getService(Ci.nsIEnvironment);
+ let testScript = function () {
+ /* eslint-disable no-undef */
+ toolbox.selectTool("webconsole")
+ .then(console => {
+ let { jsterm } = console.hud;
+ return jsterm.execute("myWebExtensionAddonFunction()");
+ })
+ .then(() => toolbox.destroy());
+ /* eslint-enable no-undef */
+ };
+ env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
+ registerCleanupFunction(() => {
+ env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
+ });
+
+ let onToolboxClose = BrowserToolboxProcess.once("close");
+
+ debugBtn.click();
+
+ let args = yield onCustomMessage;
+ ok(true, "Received console message from the background page function as expected");
+ is(args[0], "Background page function called", "Got the expected console message");
+ is(args[1] && args[1].name, ADDON_NAME,
+ "Got the expected manifest from WebExtension API");
+
+ yield onToolboxClose;
+ ok(true, "Addon toolbox closed");
+
+ yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
+ yield closeAboutDebugging(tab);
+});
diff --git a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_inspector.js b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_inspector.js
new file mode 100644
index 000000000..3adc918d8
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_inspector.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
+requestLongerTimeout(2);
+
+const ADDON_ID = "test-devtools-webextension@mozilla.org";
+const ADDON_NAME = "test-devtools-webextension";
+const ADDON_PATH = "addons/test-devtools-webextension/manifest.json";
+
+const {
+ BrowserToolboxProcess
+} = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
+
+/**
+ * This test file ensures that the webextension addon developer toolbox:
+ * - the webextension developer toolbox has a working Inspector panel, with the
+ * background page as default target;
+ */
+add_task(function* testWebExtensionsToolboxInspector() {
+ let {
+ tab, document, debugBtn,
+ } = yield setupTestAboutDebuggingWebExtension(ADDON_NAME, ADDON_PATH);
+
+ // Be careful, this JS function is going to be executed in the addon toolbox,
+ // which lives in another process. So do not try to use any scope variable!
+ let env = Cc["@mozilla.org/process/environment;1"]
+ .getService(Ci.nsIEnvironment);
+ let testScript = function () {
+ /* eslint-disable no-undef */
+ toolbox.selectTool("inspector")
+ .then(inspector => {
+ return inspector.walker.querySelector(inspector.walker.rootNode, "body");
+ })
+ .then((nodeActor) => {
+ if (!nodeActor) {
+ throw new Error("nodeActor not found");
+ }
+
+ dump("Got a nodeActor\n");
+
+ if (!(nodeActor.inlineTextChild)) {
+ throw new Error("inlineTextChild not found");
+ }
+
+ dump("Got a nodeActor with an inline text child\n");
+
+ let expectedValue = "Background Page Body Test Content";
+ let actualValue = nodeActor.inlineTextChild._form.nodeValue;
+
+ if (String(actualValue).trim() !== String(expectedValue).trim()) {
+ throw new Error(
+ `mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
+ );
+ }
+
+ dump("Got the expected inline text content in the selected node\n");
+ return Promise.resolve();
+ })
+ .then(() => toolbox.destroy())
+ .catch((error) => {
+ dump("Error while running code in the browser toolbox process:\n");
+ dump(error + "\n");
+ dump("stack:\n" + error.stack + "\n");
+ });
+ /* eslint-enable no-undef */
+ };
+ env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
+ registerCleanupFunction(() => {
+ env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
+ });
+
+ let onToolboxClose = BrowserToolboxProcess.once("close");
+ debugBtn.click();
+ yield onToolboxClose;
+
+ ok(true, "Addon toolbox closed");
+
+ yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
+ yield closeAboutDebugging(tab);
+});
diff --git a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_nobg.js b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_nobg.js
new file mode 100644
index 000000000..0e731fc5d
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_nobg.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
+requestLongerTimeout(2);
+
+const ADDON_NOBG_ID = "test-devtools-webextension-nobg@mozilla.org";
+const ADDON_NOBG_NAME = "test-devtools-webextension-nobg";
+const ADDON_NOBG_PATH = "addons/test-devtools-webextension-nobg/manifest.json";
+
+const {
+ BrowserToolboxProcess
+} = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
+
+/**
+ * This test file ensures that the webextension addon developer toolbox:
+ * - the webextension developer toolbox is connected to a fallback page when the
+ * background page is not available (and in the fallback page document body contains
+ * the expected message, which warns the user that the current page is not a real
+ * webextension context);
+ */
+add_task(function* testWebExtensionsToolboxNoBackgroundPage() {
+ let {
+ tab, document, debugBtn,
+ } = yield setupTestAboutDebuggingWebExtension(ADDON_NOBG_NAME, ADDON_NOBG_PATH);
+
+ // Be careful, this JS function is going to be executed in the addon toolbox,
+ // which lives in another process. So do not try to use any scope variable!
+ let env = Cc["@mozilla.org/process/environment;1"]
+ .getService(Ci.nsIEnvironment);
+ let testScript = function () {
+ /* eslint-disable no-undef */
+ toolbox.selectTool("inspector")
+ .then(inspector => {
+ return inspector.walker.querySelector(inspector.walker.rootNode, "body");
+ })
+ .then((nodeActor) => {
+ if (!nodeActor) {
+ throw new Error("nodeActor not found");
+ }
+
+ dump("Got a nodeActor\n");
+
+ if (!(nodeActor.inlineTextChild)) {
+ throw new Error("inlineTextChild not found");
+ }
+
+ dump("Got a nodeActor with an inline text child\n");
+
+ let expectedValue = "Your addon does not have any document opened yet.";
+ let actualValue = nodeActor.inlineTextChild._form.nodeValue;
+
+ if (actualValue !== expectedValue) {
+ throw new Error(
+ `mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
+ );
+ }
+
+ dump("Got the expected inline text content in the selected node\n");
+ return Promise.resolve();
+ })
+ .then(() => toolbox.destroy())
+ .catch((error) => {
+ dump("Error while running code in the browser toolbox process:\n");
+ dump(error + "\n");
+ dump("stack:\n" + error.stack + "\n");
+ });
+ /* eslint-enable no-undef */
+ };
+ env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
+ registerCleanupFunction(() => {
+ env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
+ });
+
+ let onToolboxClose = BrowserToolboxProcess.once("close");
+ debugBtn.click();
+ yield onToolboxClose;
+
+ ok(true, "Addon toolbox closed");
+
+ yield uninstallAddon({document, id: ADDON_NOBG_ID, name: ADDON_NOBG_NAME});
+ yield closeAboutDebugging(tab);
+});
diff --git a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
new file mode 100644
index 000000000..d2cb8031e
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
@@ -0,0 +1,189 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
+requestLongerTimeout(2);
+
+const ADDON_ID = "test-devtools-webextension@mozilla.org";
+const ADDON_NAME = "test-devtools-webextension";
+const ADDON_MANIFEST_PATH = "addons/test-devtools-webextension/manifest.json";
+
+const {
+ BrowserToolboxProcess
+} = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
+
+/**
+ * This test file ensures that the webextension addon developer toolbox:
+ * - when the debug button is clicked on a webextension, the opened toolbox
+ * has a working webconsole with the background page as default target;
+ * - the webextension developer toolbox has a working Inspector panel, with the
+ * background page as default target;
+ * - the webextension developer toolbox is connected to a fallback page when the
+ * background page is not available (and in the fallback page document body contains
+ * the expected message, which warns the user that the current page is not a real
+ * webextension context);
+ * - the webextension developer toolbox has a frame list menu and the noautohide toolbar
+ * toggle button, and they can be used to switch the current target to the extension
+ * popup page.
+ */
+
+/**
+ * Returns the widget id for an extension with the passed id.
+ */
+function makeWidgetId(id) {
+ id = id.toLowerCase();
+ return id.replace(/[^a-z0-9_-]/g, "_");
+}
+
+add_task(function* testWebExtensionsToolboxSwitchToPopup() {
+ let {
+ tab, document, debugBtn,
+ } = yield setupTestAboutDebuggingWebExtension(ADDON_NAME, ADDON_MANIFEST_PATH);
+
+ let onReadyForOpenPopup = new Promise(done => {
+ Services.obs.addObserver(function listener(message, topic) {
+ let apiMessage = message.wrappedJSObject;
+ if (!apiMessage.originAttributes ||
+ apiMessage.originAttributes.addonId != ADDON_ID) {
+ return;
+ }
+
+ if (apiMessage.arguments[0] == "readyForOpenPopup") {
+ Services.obs.removeObserver(listener, "console-api-log-event");
+ done();
+ }
+ }, "console-api-log-event", false);
+ });
+
+ // Be careful, this JS function is going to be executed in the addon toolbox,
+ // which lives in another process. So do not try to use any scope variable!
+ let env = Cc["@mozilla.org/process/environment;1"]
+ .getService(Ci.nsIEnvironment);
+ let testScript = function () {
+ /* eslint-disable no-undef */
+
+ let jsterm;
+ let popupFramePromise;
+
+ toolbox.selectTool("webconsole")
+ .then(console => {
+ dump(`Clicking the noautohide button\n`);
+ toolbox.doc.getElementById("command-button-noautohide").click();
+ dump(`Clicked the noautohide button\n`);
+
+ popupFramePromise = new Promise(resolve => {
+ let listener = (event, data) => {
+ if (data.frames.some(({url}) => url && url.endsWith("popup.html"))) {
+ toolbox.target.off("frame-update", listener);
+ resolve();
+ }
+ };
+ toolbox.target.on("frame-update", listener);
+ });
+
+ let waitForFrameListUpdate = new Promise((done) => {
+ toolbox.target.once("frame-update", () => {
+ done(console);
+ });
+ });
+
+ jsterm = console.hud.jsterm;
+ jsterm.execute("myWebExtensionShowPopup()");
+
+ // Wait the initial frame update (which list the background page).
+ return waitForFrameListUpdate;
+ })
+ .then((console) => {
+ // Wait the new frame update (once the extension popup has been opened).
+ return popupFramePromise;
+ })
+ .then(() => {
+ dump(`Clicking the frame list button\n`);
+ let btn = toolbox.doc.getElementById("command-button-frames");
+ let menu = toolbox.showFramesMenu({target: btn});
+ dump(`Clicked the frame list button\n`);
+ return menu.once("open").then(() => {
+ return menu;
+ });
+ })
+ .then(frameMenu => {
+ let frames = frameMenu.items;
+
+ if (frames.length != 2) {
+ throw Error(`Number of frames found is wrong: ${frames.length} != 2`);
+ }
+
+ let popupFrameBtn = frames.filter((frame) => {
+ return frame.label.endsWith("popup.html");
+ }).pop();
+
+ if (!popupFrameBtn) {
+ throw Error("Extension Popup frame not found in the listed frames");
+ }
+
+ let waitForNavigated = toolbox.target.once("navigate");
+
+ popupFrameBtn.click();
+
+ return waitForNavigated;
+ })
+ .then(() => {
+ return jsterm.execute("myWebExtensionPopupAddonFunction()");
+ })
+ .then(() => toolbox.destroy())
+ .catch((error) => {
+ dump("Error while running code in the browser toolbox process:\n");
+ dump(error + "\n");
+ dump("stack:\n" + error.stack + "\n");
+ });
+ /* eslint-enable no-undef */
+ };
+ env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
+ registerCleanupFunction(() => {
+ env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
+ });
+
+ // Wait for a notification sent by a script evaluated the test addon via
+ // the web console.
+ let onPopupCustomMessage = new Promise(done => {
+ Services.obs.addObserver(function listener(message, topic) {
+ let apiMessage = message.wrappedJSObject;
+ if (!apiMessage.originAttributes ||
+ apiMessage.originAttributes.addonId != ADDON_ID) {
+ return;
+ }
+
+ if (apiMessage.arguments[0] == "Popup page function called") {
+ Services.obs.removeObserver(listener, "console-api-log-event");
+ done(apiMessage.arguments);
+ }
+ }, "console-api-log-event", false);
+ });
+
+ let onToolboxClose = BrowserToolboxProcess.once("close");
+
+ debugBtn.click();
+
+ yield onReadyForOpenPopup;
+
+ let browserActionId = makeWidgetId(ADDON_ID) + "-browser-action";
+ let browserActionEl = window.document.getElementById(browserActionId);
+
+ ok(browserActionEl, "Got the browserAction button from the browser UI");
+ browserActionEl.click();
+ info("Clicked on the browserAction button");
+
+ let args = yield onPopupCustomMessage;
+ ok(true, "Received console message from the popup page function as expected");
+ is(args[0], "Popup page function called", "Got the expected console message");
+ is(args[1] && args[1].name, ADDON_NAME,
+ "Got the expected manifest from WebExtension API");
+
+ yield onToolboxClose;
+
+ ok(true, "Addon toolbox closed");
+
+ yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
+ yield closeAboutDebugging(tab);
+});
diff --git a/devtools/client/aboutdebugging/test/browser_addons_debugging_initial_state.js b/devtools/client/aboutdebugging/test/browser_addons_debugging_initial_state.js
new file mode 100644
index 000000000..8a63d0061
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_addons_debugging_initial_state.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that addons debugging controls are properly enabled/disabled depending
+// on the values of the relevant preferences:
+// - devtools.chrome.enabled
+// - devtools.debugger.remote-enabled
+
+const ADDON_ID = "test-devtools@mozilla.org";
+const ADDON_NAME = "test-devtools";
+
+const TEST_DATA = [
+ {
+ chromeEnabled: false,
+ debuggerRemoteEnable: false,
+ expected: false,
+ }, {
+ chromeEnabled: false,
+ debuggerRemoteEnable: true,
+ expected: false,
+ }, {
+ chromeEnabled: true,
+ debuggerRemoteEnable: false,
+ expected: false,
+ }, {
+ chromeEnabled: true,
+ debuggerRemoteEnable: true,
+ expected: true,
+ }
+];
+
+add_task(function* () {
+ for (let testData of TEST_DATA) {
+ yield testCheckboxState(testData);
+ }
+});
+
+function* testCheckboxState(testData) {
+ info("Set preferences as defined by the current test data.");
+ yield new Promise(resolve => {
+ let options = {"set": [
+ ["devtools.chrome.enabled", testData.chromeEnabled],
+ ["devtools.debugger.remote-enabled", testData.debuggerRemoteEnable],
+ ]};
+ SpecialPowers.pushPrefEnv(options, resolve);
+ });
+
+ let { tab, document } = yield openAboutDebugging("addons");
+ yield waitForInitialAddonList(document);
+
+ info("Install a test addon.");
+ yield installAddon({
+ document,
+ path: "addons/unpacked/install.rdf",
+ name: ADDON_NAME,
+ });
+
+ info("Test checkbox checked state.");
+ let addonDebugCheckbox = document.querySelector("#enable-addon-debugging");
+ is(addonDebugCheckbox.checked, testData.expected,
+ "Addons debugging checkbox should be in expected state.");
+
+ info("Test debug buttons disabled state.");
+ let debugButtons = [...document.querySelectorAll("#addons .debug-button")];
+ ok(debugButtons.every(b => b.disabled != testData.expected),
+ "Debug buttons should be in the expected state");
+
+ info("Uninstall test addon installed earlier.");
+ yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
+
+ yield closeAboutDebugging(tab);
+}
diff --git a/devtools/client/aboutdebugging/test/browser_addons_install.js b/devtools/client/aboutdebugging/test/browser_addons_install.js
new file mode 100644
index 000000000..4c3a97c9f
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_addons_install.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const ADDON_ID = "test-devtools@mozilla.org";
+const ADDON_NAME = "test-devtools";
+
+add_task(function* () {
+ let { tab, document } = yield openAboutDebugging("addons");
+ yield waitForInitialAddonList(document);
+
+ // Install this add-on, and verify that it appears in the about:debugging UI
+ yield installAddon({
+ document,
+ path: "addons/unpacked/install.rdf",
+ name: ADDON_NAME,
+ });
+
+ // Install the add-on, and verify that it disappears in the about:debugging UI
+ yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
+
+ yield closeAboutDebugging(tab);
+});
+
+add_task(function* () {
+ let { tab, document } = yield openAboutDebugging("addons");
+ yield waitForInitialAddonList(document);
+
+ // Start an observer that looks for the install error before
+ // actually doing the install
+ let top = document.querySelector(".addons-top");
+ let promise = waitForMutation(top, { childList: true });
+
+ // Mock the file picker to select a test addon
+ let MockFilePicker = SpecialPowers.MockFilePicker;
+ MockFilePicker.init(null);
+ let file = getSupportsFile("addons/bad/manifest.json");
+ MockFilePicker.returnFiles = [file.file];
+
+ // Trigger the file picker by clicking on the button
+ document.getElementById("load-addon-from-file").click();
+
+ // Now wait for the install error to appear.
+ yield promise;
+
+ // And check that it really is there.
+ let err = document.querySelector(".addons-install-error");
+ isnot(err, null, "Addon install error message appeared");
+
+ yield closeAboutDebugging(tab);
+});
diff --git a/devtools/client/aboutdebugging/test/browser_addons_reload.js b/devtools/client/aboutdebugging/test/browser_addons_reload.js
new file mode 100644
index 000000000..506495a60
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_addons_reload.js
@@ -0,0 +1,207 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const ADDON_ID = "test-devtools@mozilla.org";
+const ADDON_NAME = "test-devtools";
+
+/**
+ * Returns a promise that resolves when the given add-on event is fired. The
+ * resolved value is an array of arguments passed for the event.
+ */
+function promiseAddonEvent(event) {
+ return new Promise(resolve => {
+ let listener = {
+ [event]: function (...args) {
+ AddonManager.removeAddonListener(listener);
+ resolve(args);
+ }
+ };
+
+ AddonManager.addAddonListener(listener);
+ });
+}
+
+function* tearDownAddon(addon) {
+ const onUninstalled = promiseAddonEvent("onUninstalled");
+ addon.uninstall();
+ const [uninstalledAddon] = yield onUninstalled;
+ is(uninstalledAddon.id, addon.id,
+ `Add-on was uninstalled: ${uninstalledAddon.id}`);
+}
+
+function getReloadButton(document, addonName) {
+ const names = [...document.querySelectorAll("#addons .target-name")];
+ const name = names.filter(element => element.textContent === addonName)[0];
+ ok(name, `Found ${addonName} add-on in the list`);
+ const targetElement = name.parentNode.parentNode;
+ const reloadButton = targetElement.querySelector(".reload-button");
+ info(`Found reload button for ${addonName}`);
+ return reloadButton;
+}
+
+function installAddonWithManager(filePath) {
+ return new Promise((resolve, reject) => {
+ AddonManager.getInstallForFile(filePath, install => {
+ if (!install) {
+ throw new Error(`An install was not created for ${filePath}`);
+ }
+ install.addListener({
+ onDownloadFailed: reject,
+ onDownloadCancelled: reject,
+ onInstallFailed: reject,
+ onInstallCancelled: reject,
+ onInstallEnded: resolve
+ });
+ install.install();
+ });
+ });
+}
+
+function getAddonByID(addonId) {
+ return new Promise(resolve => {
+ AddonManager.getAddonByID(addonId, addon => resolve(addon));
+ });
+}
+
+/**
+ * Creates a web extension from scratch in a temporary location.
+ * The object must be removed when you're finished working with it.
+ */
+class TempWebExt {
+ constructor(addonId) {
+ this.addonId = addonId;
+ this.tmpDir = FileUtils.getDir("TmpD", ["browser_addons_reload"]);
+ if (!this.tmpDir.exists()) {
+ this.tmpDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+ }
+ this.sourceDir = this.tmpDir.clone();
+ this.sourceDir.append(this.addonId);
+ if (!this.sourceDir.exists()) {
+ this.sourceDir.create(Ci.nsIFile.DIRECTORY_TYPE,
+ FileUtils.PERMS_DIRECTORY);
+ }
+ }
+
+ writeManifest(manifestData) {
+ const manifest = this.sourceDir.clone();
+ manifest.append("manifest.json");
+ if (manifest.exists()) {
+ manifest.remove(true);
+ }
+ const fos = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ fos.init(manifest,
+ FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
+ FileUtils.MODE_TRUNCATE,
+ FileUtils.PERMS_FILE, 0);
+
+ const manifestString = JSON.stringify(manifestData);
+ fos.write(manifestString, manifestString.length);
+ fos.close();
+ }
+
+ remove() {
+ return this.tmpDir.remove(true);
+ }
+}
+
+add_task(function* reloadButtonReloadsAddon() {
+ const { tab, document } = yield openAboutDebugging("addons");
+ yield waitForInitialAddonList(document);
+ yield installAddon({
+ document,
+ path: "addons/unpacked/install.rdf",
+ name: ADDON_NAME,
+ });
+
+ const reloadButton = getReloadButton(document, ADDON_NAME);
+ is(reloadButton.disabled, false, "Reload button should not be disabled");
+ is(reloadButton.title, "", "Reload button should not have a tooltip");
+ const onInstalled = promiseAddonEvent("onInstalled");
+
+ const onBootstrapInstallCalled = new Promise(done => {
+ Services.obs.addObserver(function listener() {
+ Services.obs.removeObserver(listener, ADDON_NAME, false);
+ info("Add-on was re-installed: " + ADDON_NAME);
+ done();
+ }, ADDON_NAME, false);
+ });
+
+ reloadButton.click();
+
+ const [reloadedAddon] = yield onInstalled;
+ is(reloadedAddon.name, ADDON_NAME,
+ "Add-on was reloaded: " + reloadedAddon.name);
+
+ yield onBootstrapInstallCalled;
+ yield tearDownAddon(reloadedAddon);
+ yield closeAboutDebugging(tab);
+});
+
+add_task(function* reloadButtonRefreshesMetadata() {
+ const { tab, document } = yield openAboutDebugging("addons");
+ yield waitForInitialAddonList(document);
+
+ const manifestBase = {
+ "manifest_version": 2,
+ "name": "Temporary web extension",
+ "version": "1.0",
+ "applications": {
+ "gecko": {
+ "id": ADDON_ID
+ }
+ }
+ };
+
+ const tempExt = new TempWebExt(ADDON_ID);
+ tempExt.writeManifest(manifestBase);
+
+ const onAddonListUpdated = waitForMutation(getAddonList(document),
+ { childList: true });
+ const onInstalled = promiseAddonEvent("onInstalled");
+ yield AddonManager.installTemporaryAddon(tempExt.sourceDir);
+ const [addon] = yield onInstalled;
+ info(`addon installed: ${addon.id}`);
+ yield onAddonListUpdated;
+
+ const newName = "Temporary web extension (updated)";
+ tempExt.writeManifest(Object.assign({}, manifestBase, {name: newName}));
+
+ // Wait for the add-on list to be updated with the reloaded name.
+ const onReInstall = promiseAddonEvent("onInstalled");
+ const onAddonReloaded = waitForContentMutation(getAddonList(document));
+
+ const reloadButton = getReloadButton(document, manifestBase.name);
+ reloadButton.click();
+
+ yield onAddonReloaded;
+ const [reloadedAddon] = yield onReInstall;
+ // Make sure the name was updated correctly.
+ const allAddons = [...document.querySelectorAll("#addons .target-name")]
+ .map(element => element.textContent);
+ const nameWasUpdated = allAddons.some(name => name === newName);
+ ok(nameWasUpdated, `New name appeared in reloaded add-ons: ${allAddons}`);
+
+ yield tearDownAddon(reloadedAddon);
+ tempExt.remove();
+ yield closeAboutDebugging(tab);
+});
+
+add_task(function* onlyTempInstalledAddonsCanBeReloaded() {
+ const { tab, document } = yield openAboutDebugging("addons");
+ yield waitForInitialAddonList(document);
+ const onAddonListUpdated = waitForMutation(getAddonList(document),
+ { childList: true });
+ yield installAddonWithManager(getSupportsFile("addons/bug1273184.xpi").file);
+ yield onAddonListUpdated;
+ const addon = yield getAddonByID("bug1273184@tests");
+
+ const reloadButton = getReloadButton(document, addon.name);
+ ok(reloadButton, "Reload button exists");
+ is(reloadButton.disabled, true, "Reload button should be disabled");
+ ok(reloadButton.title, "Disabled reload button should have a tooltip");
+
+ yield tearDownAddon(addon);
+ yield closeAboutDebugging(tab);
+});
diff --git a/devtools/client/aboutdebugging/test/browser_addons_toggle_debug.js b/devtools/client/aboutdebugging/test/browser_addons_toggle_debug.js
new file mode 100644
index 000000000..1f67cac5b
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_addons_toggle_debug.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that individual Debug buttons are disabled when "Addons debugging"
+// is disabled.
+// Test that the buttons are updated dynamically if the preference changes.
+
+const ADDON_ID = "test-devtools@mozilla.org";
+const ADDON_NAME = "test-devtools";
+
+add_task(function* () {
+ info("Turn off addon debugging.");
+ yield new Promise(resolve => {
+ let options = {"set": [
+ ["devtools.chrome.enabled", false],
+ ["devtools.debugger.remote-enabled", false],
+ ]};
+ SpecialPowers.pushPrefEnv(options, resolve);
+ });
+
+ let { tab, document } = yield openAboutDebugging("addons");
+ yield waitForInitialAddonList(document);
+
+ info("Install a test addon.");
+ yield installAddon({
+ document,
+ path: "addons/unpacked/install.rdf",
+ name: ADDON_NAME,
+ });
+
+ let addonDebugCheckbox = document.querySelector("#enable-addon-debugging");
+ ok(!addonDebugCheckbox.checked, "Addons debugging should be disabled.");
+
+ info("Check all debug buttons are disabled.");
+ let debugButtons = [...document.querySelectorAll("#addons .debug-button")];
+ ok(debugButtons.every(b => b.disabled), "Debug buttons should be disabled");
+
+ info("Click on 'Enable addons debugging' checkbox.");
+ let addonsContainer = document.getElementById("addons");
+ let onAddonsMutation = waitForMutation(addonsContainer,
+ { subtree: true, attributes: true });
+ addonDebugCheckbox.click();
+ yield onAddonsMutation;
+
+ info("Check all debug buttons are enabled.");
+ ok(addonDebugCheckbox.checked, "Addons debugging should be enabled.");
+ debugButtons = [...document.querySelectorAll("#addons .debug-button")];
+ ok(debugButtons.every(b => !b.disabled), "Debug buttons should be enabled");
+
+ info("Click again on 'Enable addons debugging' checkbox.");
+ onAddonsMutation = waitForMutation(addonsContainer,
+ { subtree: true, attributes: true });
+ addonDebugCheckbox.click();
+ yield onAddonsMutation;
+
+ info("Check all debug buttons are disabled again.");
+ debugButtons = [...document.querySelectorAll("#addons .debug-button")];
+ ok(debugButtons.every(b => b.disabled), "Debug buttons should be disabled");
+
+ info("Uninstall addon installed earlier.");
+ yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
+
+ yield closeAboutDebugging(tab);
+});
diff --git a/devtools/client/aboutdebugging/test/browser_page_not_found.js b/devtools/client/aboutdebugging/test/browser_page_not_found.js
new file mode 100644
index 000000000..107bc8b91
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_page_not_found.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that navigating to a about:debugging#invalid-hash should show up an
+// error page.
+// Every url navigating including #invalid-hash should be kept in history and
+// navigate back as expected.
+add_task(function* () {
+ let { tab, document } = yield openAboutDebugging("invalid-hash");
+ let element = document.querySelector(".header-name");
+ is(element.textContent, "Page not found", "Show error page");
+
+ yield openPanel(document, "addons-panel");
+ yield waitForInitialAddonList(document);
+ element = document.querySelector(".header-name");
+ is(element.textContent, "Add-ons", "Show Addons");
+
+ yield changeAboutDebuggingHash(document, "invalid-hash");
+ element = document.querySelector(".header-name");
+ is(element.textContent, "Page not found", "Show error page");
+
+ gBrowser.goBack();
+ yield waitForMutation(
+ document.querySelector(".main-content"), {childList: true});
+ yield waitForInitialAddonList(document);
+ element = document.querySelector(".header-name");
+ is(element.textContent, "Add-ons", "Show Addons");
+
+ gBrowser.goBack();
+ yield waitForMutation(
+ document.querySelector(".main-content"), {childList: true});
+ element = document.querySelector(".header-name");
+ is(element.textContent, "Page not found", "Show error page");
+
+ yield closeAboutDebugging(tab);
+});
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 000000000..74e4efb3e
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_service_workers.js
@@ -0,0 +1,51 @@
+/* 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 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": [
+ // Accept workers from mochitest's http.
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.openWindow.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]};
+ SpecialPowers.pushPrefEnv(options, done);
+ });
+
+ let { tab, document } = yield openAboutDebugging("workers");
+
+ let swTab = yield addTab(TAB_URL);
+
+ let serviceWorkersElement = getServiceWorkerList(document);
+
+ yield waitForMutation(serviceWorkersElement, { childList: true });
+
+ // 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);
+
+ try {
+ yield unregisterServiceWorker(swTab, serviceWorkersElement);
+ ok(true, "Service worker registration unregistered");
+ } catch (e) {
+ ok(false, "SW not unregistered; " + e);
+ }
+
+ // 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/browser_service_workers_not_compatible.js b/devtools/client/aboutdebugging/test/browser_service_workers_not_compatible.js
new file mode 100644
index 000000000..6221230b5
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_not_compatible.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that Service Worker section should show warning message in
+// about:debugging if any of following conditions is met:
+// 1. service worker is disabled
+// 2. the about:debugging pannel is openned in private browsing mode
+// 3. the about:debugging pannel is openned in private content window
+
+var imgClass = ".service-worker-disabled .warning";
+
+add_task(function* () {
+ yield new Promise(done => {
+ info("disable service workers");
+ let options = {"set": [
+ ["dom.serviceWorkers.enabled", false],
+ ]};
+ SpecialPowers.pushPrefEnv(options, done);
+ });
+
+ let { tab, document } = yield openAboutDebugging("workers");
+ // Check that the warning img appears in the UI
+ let img = document.querySelector(imgClass);
+ ok(img, "warning message is rendered");
+
+ yield closeAboutDebugging(tab);
+});
+
+add_task(function* () {
+ yield new Promise(done => {
+ info("set private browsing mode as default");
+ let options = {"set": [
+ ["browser.privatebrowsing.autostart", true],
+ ]};
+ SpecialPowers.pushPrefEnv(options, done);
+ });
+
+ let { tab, document } = yield openAboutDebugging("workers");
+ // Check that the warning img appears in the UI
+ let img = document.querySelector(imgClass);
+ ok(img, "warning message is rendered");
+
+ yield closeAboutDebugging(tab);
+});
+
+add_task(function* () {
+ info("Opening a new private window");
+ let win = OpenBrowserWindow({private: true});
+ yield waitForDelayedStartupFinished(win);
+
+ let { tab, document } = yield openAboutDebugging("workers", win);
+ // Check that the warning img appears in the UI
+ let img = document.querySelector(imgClass);
+ ok(img, "warning message is rendered");
+
+ yield closeAboutDebugging(tab);
+ win.close();
+});
diff --git a/devtools/client/aboutdebugging/test/browser_service_workers_push.js b/devtools/client/aboutdebugging/test/browser_service_workers_push.js
new file mode 100644
index 000000000..ff7789458
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_push.js
@@ -0,0 +1,105 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* global sendAsyncMessage */
+
+"use strict";
+
+// Test that clicking on the Push button next to a Service Worker works as
+// intended in about:debugging.
+// 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.enabled", true],
+ ["dom.serviceWorkers.openWindow.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]};
+ SpecialPowers.pushPrefEnv(options, done);
+ });
+
+ 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);
+
+ info("Make the test page notify us when the service worker sends a message.");
+
+ yield ContentTask.spawn(swTab.linkedBrowser, {}, function () {
+ let win = content.wrappedJSObject;
+ win.navigator.serviceWorker.addEventListener("message", function (event) {
+ sendAsyncMessage(event.data);
+ }, false);
+ });
+
+ // Expect the service worker to claim the test window when activating.
+ let mm = swTab.linkedBrowser.messageManager;
+ let onClaimed = new Promise(done => {
+ mm.addMessageListener("sw-claimed", function listener() {
+ mm.removeMessageListener("sw-claimed", listener);
+ done();
+ });
+ });
+
+ // Wait for the service-workers list to update.
+ yield onMutation;
+
+ // Check that the service worker appears in the UI.
+ assertHasTarget(true, document, "service-workers", SERVICE_WORKER);
+
+ info("Ensure that the registration resolved before trying to interact with " +
+ "the service worker.");
+ yield waitForServiceWorkerRegistered(swTab);
+ ok(true, "Service worker registration resolved");
+
+ yield waitForServiceWorkerActivation(SERVICE_WORKER, document);
+
+ // Retrieve the Push 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 targetElement = name.parentNode.parentNode;
+
+ let pushBtn = targetElement.querySelector(".push-button");
+ ok(pushBtn, "Found its push button");
+
+ info("Wait for the service worker to claim the test window before " +
+ "proceeding.");
+ yield onClaimed;
+
+ info("Click on the Push button and wait for the service worker to receive " +
+ "a push notification");
+ let onPushNotification = new Promise(done => {
+ mm.addMessageListener("sw-pushed", function listener() {
+ mm.removeMessageListener("sw-pushed", listener);
+ done();
+ });
+ });
+ pushBtn.click();
+ yield onPushNotification;
+ ok(true, "Service worker received a push notification");
+
+ // Finally, unregister the service worker itself.
+ 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);
+});
diff --git a/devtools/client/aboutdebugging/test/browser_service_workers_push_service.js b/devtools/client/aboutdebugging/test/browser_service_workers_push_service.js
new file mode 100644
index 000000000..732380a4c
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_push_service.js
@@ -0,0 +1,122 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that a Service Worker registration's Push Service subscription appears
+// in about:debugging if it exists, and disappears when unregistered.
+
+// 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";
+
+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.enabled", true],
+ ["dom.serviceWorkers.openWindow.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ // Enable the push service.
+ ["dom.push.enabled", true],
+ ["dom.push.connection.enabled", true],
+ ]
+ });
+
+ info("Mock the push service");
+ PushService.service = {
+ _registrations: new Map(),
+ _notify(scope) {
+ Services.obs.notifyObservers(
+ null,
+ PushService.subscriptionModifiedTopic,
+ scope);
+ },
+ init() {},
+ register(pageRecord) {
+ let registration = {
+ endpoint: FAKE_ENDPOINT
+ };
+ this._registrations.set(pageRecord.scope, registration);
+ this._notify(pageRecord.scope);
+ return Promise.resolve(registration);
+ },
+ registration(pageRecord) {
+ return Promise.resolve(this._registrations.get(pageRecord.scope));
+ },
+ unregister(pageRecord) {
+ let deleted = this._registrations.delete(pageRecord.scope);
+ if (deleted) {
+ this._notify(pageRecord.scope);
+ }
+ return Promise.resolve(deleted);
+ },
+ };
+
+ let { tab, document } = yield openAboutDebugging("workers");
+
+ // Listen for mutations in the service-workers list.
+ let serviceWorkersElement = document.getElementById("service-workers");
+ let onMutation = waitForMutation(serviceWorkersElement, { childList: true });
+
+ // Open a tab that registers a push service worker.
+ let swTab = yield addTab(TAB_URL);
+
+ // Wait for the service-workers list to update.
+ yield onMutation;
+
+ // Check that the service worker appears in the UI.
+ assertHasTarget(true, document, "service-workers", SERVICE_WORKER);
+
+ yield waitForServiceWorkerActivation(SERVICE_WORKER, document);
+
+ // Wait for the service worker details to update.
+ 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 targetContainer = name.parentNode.parentNode;
+ let targetDetailsElement = targetContainer.querySelector(".target-details");
+
+ // Retrieve the push subscription endpoint URL, and verify it looks good.
+ let pushURL = targetContainer.querySelector(".service-worker-push-url");
+ if (!pushURL) {
+ yield waitForMutation(targetDetailsElement, { childList: true });
+ pushURL = targetContainer.querySelector(".service-worker-push-url");
+ }
+
+ ok(pushURL, "Found the push service URL in the service worker details");
+ is(pushURL.textContent, FAKE_ENDPOINT, "The push service URL looks correct");
+
+ // Unsubscribe from the push service.
+ ContentTask.spawn(swTab.linkedBrowser, {}, function () {
+ let win = content.wrappedJSObject;
+ return win.sub.unsubscribe();
+ });
+
+ // Wait for the service worker details to update again.
+ yield waitForMutation(targetDetailsElement, { childList: true });
+ ok(!targetContainer.querySelector(".service-worker-push-url"),
+ "The push service URL should be removed");
+
+ // Finally, unregister the service worker itself.
+ try {
+ yield unregisterServiceWorker(swTab, serviceWorkersElement);
+ ok(true, "Service worker registration unregistered");
+ } catch (e) {
+ ok(false, "SW not unregistered; " + e);
+ }
+
+ info("Unmock the push service");
+ PushService.service = null;
+
+ yield removeTab(swTab);
+ yield closeAboutDebugging(tab);
+});
diff --git a/devtools/client/aboutdebugging/test/browser_service_workers_start.js b/devtools/client/aboutdebugging/test/browser_service_workers_start.js
new file mode 100644
index 000000000..8c15d97c4
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_start.js
@@ -0,0 +1,97 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that clicking on the Start button next to a Service Worker works as
+// intended in about:debugging.
+// It should cause a worker to start running in a child process.
+
+// 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.enabled", true],
+ ["dom.serviceWorkers.openWindow.enabled", true],
+ ["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);
+ });
+
+ 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.
+ let swTab = yield addTab(TAB_URL);
+
+ // Wait for the service-workers list to update.
+ yield onMutation;
+
+ // Check that the service worker appears in the UI.
+ assertHasTarget(true, document, "service-workers", SERVICE_WORKER);
+
+ info("Ensure that the registration resolved before trying to interact with " +
+ "the service worker.");
+ yield waitForServiceWorkerRegistered(swTab);
+ ok(true, "Service worker registration resolved");
+
+ yield waitForServiceWorkerActivation(SERVICE_WORKER, document);
+
+ // Retrieve the Target element corresponding to the service 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 targetElement = name.parentNode.parentNode;
+
+ // The service worker may already be killed with the low 1s timeout
+ if (!targetElement.querySelector(".start-button")) {
+ // Check that there is a Debug button but not a Start button.
+ ok(targetElement.querySelector(".debug-button"), "Found its debug button");
+
+ // Wait for the service worker to be killed due to inactivity.
+ yield waitForMutation(targetElement, { childList: true });
+ } else {
+ // Check that there is no Debug button when the SW is already shut down.
+ ok(!targetElement.querySelector(".debug-button"), "No debug button when " +
+ "the worker is already killed");
+ }
+
+ // We should now have a Start button but no Debug button.
+ let startBtn = targetElement.querySelector(".start-button");
+ ok(startBtn, "Found its start button");
+ ok(!targetElement.querySelector(".debug-button"), "No debug button");
+
+ // Click on the Start button and wait for the service worker to be back.
+ let onStarted = waitForMutation(targetElement, { childList: true });
+ startBtn.click();
+ yield onStarted;
+
+ // Check that we have a Debug button but not a Start button again.
+ ok(targetElement.querySelector(".debug-button"), "Found its debug button");
+ ok(!targetElement.querySelector(".start-button"), "No start button");
+
+ // Finally, unregister the service worker itself.
+ 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);
+});
diff --git a/devtools/client/aboutdebugging/test/browser_service_workers_status.js b/devtools/client/aboutdebugging/test/browser_service_workers_status.js
new file mode 100644
index 000000000..dff384ee5
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_status.js
@@ -0,0 +1,72 @@
+/* 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 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.enabled", true],
+ ["dom.serviceWorkers.openWindow.enabled", true],
+ ["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],
+ ["dom.ipc.processCount", 1],
+ ]
+ });
+
+ 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);
+
+ info("Make the test page notify us when the service worker sends a message.");
+
+ // Wait for the service-workers list to update.
+ yield onMutation;
+
+ // Check that the service worker appears in the UI
+ 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 targetElement = name.parentNode.parentNode;
+ let status = targetElement.querySelector(".target-status");
+ is(status.textContent, "Registering", "Service worker is currently registering");
+
+ yield waitForMutation(serviceWorkersElement, { childList: true, subtree: true });
+ is(status.textContent, "Running", "Service worker is currently running");
+
+ yield waitForMutation(serviceWorkersElement, { attributes: true, subtree: true });
+ is(status.textContent, "Stopped", "Service worker is currently stopped");
+
+ try {
+ yield unregisterServiceWorker(swTab, serviceWorkersElement);
+ ok(true, "Service worker unregistered");
+ } catch (e) {
+ ok(false, "Service worker not unregistered; " + e);
+ }
+
+ // 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/browser_service_workers_timeout.js b/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js
new file mode 100644
index 000000000..94e064029
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js
@@ -0,0 +1,92 @@
+/* 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 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.enabled", true],
+ ["dom.serviceWorkers.openWindow.enabled", true],
+ ["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);
+ });
+
+ let { tab, document } = yield openAboutDebugging("workers");
+
+ let swTab = yield addTab(TAB_URL);
+
+ let serviceWorkersElement = getServiceWorkerList(document);
+ yield waitForMutation(serviceWorkersElement, { childList: true });
+
+ assertHasTarget(true, document, "service-workers", SERVICE_WORKER);
+
+ // Ensure that the registration resolved before trying to connect to the sw
+ yield waitForServiceWorkerRegistered(swTab);
+ 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 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);
+ });
+ });
+ debugBtn.click();
+
+ let toolbox = yield onToolboxReady;
+
+ // 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);
+ });
+
+ assertHasTarget(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 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.
+ try {
+ yield unregisterServiceWorker(swTab, serviceWorkersElement);
+ ok(true, "Service worker registration unregistered");
+ } catch (e) {
+ ok(false, "SW not unregistered; " + e);
+ }
+
+ assertHasTarget(false, document, "service-workers", SERVICE_WORKER);
+
+ yield removeTab(swTab);
+ yield closeAboutDebugging(tab);
+});
diff --git a/devtools/client/aboutdebugging/test/browser_service_workers_unregister.js b/devtools/client/aboutdebugging/test/browser_service_workers_unregister.js
new file mode 100644
index 000000000..b6076ea07
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_unregister.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that clicking on the unregister link in the Service Worker details works
+// as intended in about:debugging.
+// It should unregister the service worker, which should trigger an update of
+// the displayed list of service workers.
+
+// 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.enabled", true],
+ ["dom.serviceWorkers.openWindow.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.ipc.processCount", 1],
+ ]};
+ SpecialPowers.pushPrefEnv(options, done);
+ });
+
+ 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.
+ let swTab = yield addTab(TAB_URL);
+
+ // Wait for the service workers-list to update.
+ yield onMutation;
+
+ // Check that the service worker appears in the UI.
+ assertHasTarget(true, document, "service-workers", SERVICE_WORKER);
+
+ yield waitForServiceWorkerActivation(SERVICE_WORKER, document);
+
+ info("Ensure that the registration resolved before trying to interact with " +
+ "the service worker.");
+ yield waitForServiceWorkerRegistered(swTab);
+ ok(true, "Service worker registration resolved");
+
+ let targets = document.querySelectorAll("#service-workers .target");
+ is(targets.length, 1, "One service worker is now displayed.");
+
+ let target = targets[0];
+ let name = target.querySelector(".target-name");
+ is(name.textContent, SERVICE_WORKER, "Found the service worker in the list");
+
+ info("Check the scope displayed scope is correct");
+ let scope = target.querySelector(".service-worker-scope");
+ is(scope.textContent, SCOPE,
+ "The expected scope is displayed in the service worker info.");
+
+ info("Unregister the service worker via the unregister link.");
+ let unregisterLink = target.querySelector(".unregister-link");
+ ok(unregisterLink, "Found the unregister link");
+
+ onMutation = waitForMutation(serviceWorkersElement, { childList: true });
+ unregisterLink.click();
+ yield onMutation;
+
+ is(document.querySelector("#service-workers .target"), null,
+ "No service worker displayed anymore.");
+
+ yield removeTab(swTab);
+ yield closeAboutDebugging(tab);
+});
diff --git a/devtools/client/aboutdebugging/test/browser_tabs.js b/devtools/client/aboutdebugging/test/browser_tabs.js
new file mode 100644
index 000000000..8cdeef17d
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_tabs.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TAB_URL = "data:text/html,<title>foo</title>";
+
+add_task(function* setup() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [["dom.ipc.processCount", 1]]
+ });
+});
+
+add_task(function* () {
+ let { tab, document } = yield openAboutDebugging("tabs");
+
+ // Wait for initial tabs list which may be empty
+ let tabsElement = getTabList(document);
+ if (tabsElement.querySelectorAll(".target-name").length == 0) {
+ yield waitForMutation(tabsElement, { childList: true });
+ }
+ // Refresh tabsElement to get the .target-list element
+ tabsElement = getTabList(document);
+
+ let names = [...tabsElement.querySelectorAll(".target-name")];
+ let initialTabCount = names.length;
+
+ // Open a new tab in background and wait for its addition in the UI
+ let onNewTab = waitForMutation(tabsElement, { childList: true });
+ let newTab = yield addTab(TAB_URL, { background: true });
+ yield onNewTab;
+
+ // Check that the new tab appears in the UI, but with an empty name
+ let newNames = [...tabsElement.querySelectorAll(".target-name")];
+ newNames = newNames.filter(node => !names.includes(node));
+ is(newNames.length, 1, "A new tab appeared in the list");
+ let newTabTarget = newNames[0];
+
+ // Then wait for title update, but on slow test runner, the title may already
+ // be set to the expected value
+ if (newTabTarget.textContent != "foo") {
+ yield waitForContentMutation(newTabTarget);
+ }
+
+ // Check that the new tab appears in the UI
+ is(newTabTarget.textContent, "foo", "The tab title got updated");
+ is(newTabTarget.title, TAB_URL, "The tab tooltip is the url");
+
+ // Finally, close the tab
+ let onTabsUpdate = waitForMutation(tabsElement, { childList: true });
+ yield removeTab(newTab);
+ yield onTabsUpdate;
+
+ // Check that the tab disappeared from the UI
+ names = [...tabsElement.querySelectorAll("#tabs .target-name")];
+ is(names.length, initialTabCount, "The tab disappeared from the UI");
+
+ yield closeAboutDebugging(tab);
+});
diff --git a/devtools/client/aboutdebugging/test/head.js b/devtools/client/aboutdebugging/test/head.js
new file mode 100644
index 000000000..001d36e34
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/head.js
@@ -0,0 +1,367 @@
+/* Any copyright is dedicated to the Public Domain.
+ 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 */
+/* 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);
+
+const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
+const { Management } = Cu.import("resource://gre/modules/Extension.jsm", {});
+
+flags.testing = true;
+registerCleanupFunction(() => {
+ flags.testing = false;
+});
+
+function* openAboutDebugging(page, win) {
+ info("opening about:debugging");
+ let url = "about:debugging";
+ if (page) {
+ url += "#" + page;
+ }
+
+ let tab = yield addTab(url, { window: win });
+ let browser = tab.linkedBrowser;
+ let document = browser.contentDocument;
+
+ if (!document.querySelector(".app")) {
+ yield waitForMutation(document.body, { childList: true });
+ }
+
+ return { tab, document };
+}
+
+/**
+ * Change url hash for current about:debugging tab, return a promise after
+ * new content is loaded.
+ * @param {DOMDocument} document container document from current tab
+ * @param {String} hash hash for about:debugging
+ * @return {Promise}
+ */
+function changeAboutDebuggingHash(document, hash) {
+ info(`Opening about:debugging#${hash}`);
+ window.openUILinkIn(`about:debugging#${hash}`, "current");
+ return waitForMutation(
+ document.querySelector(".main-content"), {childList: true});
+}
+
+function openPanel(document, panelId) {
+ info(`Opening ${panelId} panel`);
+ document.querySelector(`[aria-controls="${panelId}"]`).click();
+ return waitForMutation(
+ document.querySelector(".main-content"), {childList: true});
+}
+
+function closeAboutDebugging(tab) {
+ info("Closing about:debugging");
+ return removeTab(tab);
+}
+
+function getSupportsFile(path) {
+ let cr = Cc["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Ci.nsIChromeRegistry);
+ let uri = Services.io.newURI(CHROME_URL_ROOT + path, null, null);
+ let fileurl = cr.convertChromeURL(uri);
+ return fileurl.QueryInterface(Ci.nsIFileURL);
+}
+
+/**
+ * Depending on whether there are addons installed, return either a target list
+ * element or its container.
+ * @param {DOMDocument} document #addons section container document
+ * @return {DOMNode} target list or container element
+ */
+function getAddonList(document) {
+ return document.querySelector("#addons .target-list") ||
+ document.querySelector("#addons .targets");
+}
+
+/**
+ * Depending on whether there are service workers installed, return either a
+ * target list element or its container.
+ * @param {DOMDocument} document #service-workers section container document
+ * @return {DOMNode} target list or container element
+ */
+function getServiceWorkerList(document) {
+ return document.querySelector("#service-workers .target-list") ||
+ document.querySelector("#service-workers.targets");
+}
+
+/**
+ * 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");
+}
+
+function* installAddon({document, path, name, isWebExtension}) {
+ // Mock the file picker to select a test addon
+ let MockFilePicker = SpecialPowers.MockFilePicker;
+ MockFilePicker.init(null);
+ let file = getSupportsFile(path);
+ MockFilePicker.returnFiles = [file.file];
+
+ let addonList = getAddonList(document);
+ let addonListMutation = waitForMutation(addonList, { childList: true });
+
+ let onAddonInstalled;
+
+ if (isWebExtension) {
+ onAddonInstalled = new Promise(done => {
+ Management.on("startup", function listener(event, extension) {
+ if (extension.name != name) {
+ return;
+ }
+
+ Management.off("startup", listener);
+ done();
+ });
+ });
+ } else {
+ // Wait for a "test-devtools" message sent by the addon's bootstrap.js file
+ onAddonInstalled = new Promise(done => {
+ Services.obs.addObserver(function listener() {
+ Services.obs.removeObserver(listener, "test-devtools");
+
+ done();
+ }, "test-devtools", false);
+ });
+ }
+ // Trigger the file picker by clicking on the button
+ document.getElementById("load-addon-from-file").click();
+
+ yield onAddonInstalled;
+ ok(true, "Addon installed and running its bootstrap.js file");
+
+ // Check that the addon appears in the UI
+ yield addonListMutation;
+ let names = [...addonList.querySelectorAll(".target-name")];
+ names = names.map(element => element.textContent);
+ ok(names.includes(name),
+ "The addon name appears in the list of addons: " + names);
+}
+
+function* uninstallAddon({document, id, name}) {
+ let addonList = getAddonList(document);
+ let addonListMutation = waitForMutation(addonList, { childList: true });
+
+ // Now uninstall this addon
+ yield new Promise(done => {
+ AddonManager.getAddonByID(id, addon => {
+ let listener = {
+ onUninstalled: function (uninstalledAddon) {
+ if (uninstalledAddon != addon) {
+ return;
+ }
+ AddonManager.removeAddonListener(listener);
+
+ done();
+ }
+ };
+ AddonManager.addAddonListener(listener);
+ addon.uninstall();
+ });
+ });
+
+ // Ensure that the UI removes the addon from the list
+ yield addonListMutation;
+ let names = [...addonList.querySelectorAll(".target-name")];
+ names = names.map(element => element.textContent);
+ ok(!names.includes(name),
+ "After uninstall, the addon name disappears from the list of addons: "
+ + names);
+}
+
+/**
+ * Returns a promise that will resolve when the add-on list has been updated.
+ *
+ * @param {Node} document
+ * @return {Promise}
+ */
+function waitForInitialAddonList(document) {
+ const addonListContainer = getAddonList(document);
+ let addonCount = addonListContainer.querySelectorAll(".target");
+ addonCount = addonCount ? [...addonCount].length : -1;
+ info("Waiting for add-ons to load. Current add-on count: " + addonCount);
+
+ // This relies on the network speed of the actor responding to the
+ // listAddons() request and also the speed of openAboutDebugging().
+ let result;
+ if (addonCount > 0) {
+ info("Actually, the add-ons have already loaded");
+ result = Promise.resolve();
+ } else {
+ result = waitForMutation(addonListContainer, { childList: true });
+ }
+ return result;
+}
+
+/**
+ * Returns a promise that will resolve after receiving a mutation matching the
+ * provided mutation options on the provided target.
+ * @param {Node} target
+ * @param {Object} mutationOptions
+ * @return {Promise}
+ */
+function waitForMutation(target, mutationOptions) {
+ return new Promise(resolve => {
+ let observer = new MutationObserver(() => {
+ observer.disconnect();
+ resolve();
+ });
+ observer.observe(target, mutationOptions);
+ });
+}
+
+/**
+ * Returns a promise that will resolve after receiving a mutation in the subtree of the
+ * provided target. Depending on the current React implementation, a text change might be
+ * observable as a childList mutation or a characterData mutation.
+ *
+ * @param {Node} target
+ * @return {Promise}
+ */
+function waitForContentMutation(target) {
+ return waitForMutation(target, {
+ characterData: true,
+ childList: true,
+ subtree: true
+ });
+}
+
+/**
+ * Checks if an about:debugging TargetList element contains a Target element
+ * corresponding to the specified name.
+ * @param {Boolean} expected
+ * @param {Document} document
+ * @param {String} type
+ * @param {String} name
+ */
+function assertHasTarget(expected, document, type, name) {
+ let names = [...document.querySelectorAll("#" + type + " .target-name")];
+ names = names.map(element => element.textContent);
+ is(names.includes(name), expected,
+ "The " + type + " url appears in the list: " + names);
+}
+
+/**
+ * Returns a promise that will resolve after the service worker in the page
+ * has successfully registered itself.
+ * @param {Tab} tab
+ * @return {Promise} Resolves when the service worker is registered.
+ */
+function waitForServiceWorkerRegistered(tab) {
+ return ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ // Retrieve the `sw` promise created in the html page.
+ let { sw } = content.wrappedJSObject;
+ yield sw;
+ });
+}
+
+/**
+ * Asks the service worker within the test page to unregister, and returns a
+ * promise that will resolve when it has successfully unregistered itself and the
+ * about:debugging UI has fully processed this update.
+ *
+ * @param {Tab} tab
+ * @param {Node} serviceWorkersElement
+ * @return {Promise} Resolves when the service worker is unregistered.
+ */
+function* unregisterServiceWorker(tab, serviceWorkersElement) {
+ let onMutation = waitForMutation(serviceWorkersElement, { childList: true });
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ // Retrieve the `sw` promise created in the html page
+ let { sw } = content.wrappedJSObject;
+ let registration = yield sw;
+ yield registration.unregister();
+ });
+ return onMutation;
+}
+
+/**
+ * Waits for the creation of a new window, usually used with create private
+ * browsing window.
+ * Returns a promise that will resolve when the window is successfully created.
+ * @param {window} win
+ */
+function waitForDelayedStartupFinished(win) {
+ return new Promise(function (resolve) {
+ Services.obs.addObserver(function observer(subject, topic) {
+ if (win == subject) {
+ Services.obs.removeObserver(observer, topic);
+ resolve();
+ }
+ }, "browser-delayed-startup-finished", false);
+ });
+}
+
+/**
+ * open the about:debugging page and install an addon
+ */
+function* setupTestAboutDebuggingWebExtension(name, path) {
+ yield new Promise(resolve => {
+ let options = {"set": [
+ // Force enabling of addons debugging
+ ["devtools.chrome.enabled", true],
+ ["devtools.debugger.remote-enabled", true],
+ // Disable security prompt
+ ["devtools.debugger.prompt-connection", false],
+ // Enable Browser toolbox test script execution via env variable
+ ["devtools.browser-toolbox.allow-unsafe-script", true],
+ ]};
+ SpecialPowers.pushPrefEnv(options, resolve);
+ });
+
+ let { tab, document } = yield openAboutDebugging("addons");
+ yield waitForInitialAddonList(document);
+
+ yield installAddon({
+ document,
+ path,
+ name,
+ isWebExtension: true,
+ });
+
+ // Retrieve the DEBUG button for the addon
+ let names = [...document.querySelectorAll("#addons .target-name")];
+ let nameEl = names.filter(element => element.textContent === name)[0];
+ ok(name, "Found the addon in the list");
+ let targetElement = nameEl.parentNode.parentNode;
+ let debugBtn = targetElement.querySelector(".debug-button");
+ ok(debugBtn, "Found its debug button");
+
+ return { tab, document, debugBtn };
+}
+
+/**
+ * Wait for aboutdebugging to be notified about the activation of the service worker
+ * corresponding to the provided service worker url.
+ */
+function* waitForServiceWorkerActivation(swUrl, document) {
+ let serviceWorkersElement = getServiceWorkerList(document);
+ let names = serviceWorkersElement.querySelectorAll(".target-name");
+ let name = [...names].filter(element => element.textContent === swUrl)[0];
+
+ 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 });
+ }
+}
diff --git a/devtools/client/aboutdebugging/test/service-workers/delay-sw.html b/devtools/client/aboutdebugging/test/service-workers/delay-sw.html
new file mode 100644
index 000000000..545830eba
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/service-workers/delay-sw.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Service worker test</title>
+</head>
+<body>
+<script type="text/javascript">
+"use strict";
+
+var sw = navigator.serviceWorker.register("delay-sw.js");
+sw.then(
+ function () {
+ dump("SW registered\n");
+ },
+ function (e) {
+ dump("SW not registered: " + e + "\n");
+ }
+);
+</script>
+</body>
+</html>
diff --git a/devtools/client/aboutdebugging/test/service-workers/delay-sw.js b/devtools/client/aboutdebugging/test/service-workers/delay-sw.js
new file mode 100644
index 000000000..3f16c5058
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/service-workers/delay-sw.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-env worker */
+
+"use strict";
+
+function wait(ms) {
+ return new Promise(resolve => {
+ setTimeout(resolve, ms);
+ });
+}
+
+// Wait for one second to switch from installing to installed.
+self.addEventListener("install", function (event) {
+ event.waitUntil(wait(1000));
+});
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 000000000..a94c2b9ff
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/service-workers/empty-sw.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Service worker test</title>
+</head>
+<body>
+<script type="text/javascript">
+"use strict";
+
+var sw = navigator.serviceWorker.register("empty-sw.js");
+sw.then(
+ function () {
+ dump("SW registered\n");
+ },
+ function (e) {
+ dump("SW not registered: " + e + "\n");
+ }
+);
+</script>
+</body>
+</html>
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 000000000..1e7226402
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/service-workers/empty-sw.js
@@ -0,0 +1 @@
+// Empty, just test registering.
diff --git a/devtools/client/aboutdebugging/test/service-workers/push-sw.html b/devtools/client/aboutdebugging/test/service-workers/push-sw.html
new file mode 100644
index 000000000..7db01f091
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/service-workers/push-sw.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Service worker push test</title>
+</head>
+<body>
+<script type="text/javascript">
+"use strict";
+SpecialPowers.addPermission("desktop-notification", true, document);
+var sw = navigator.serviceWorker.register("push-sw.js");
+var sub = null;
+sw.then(
+ function (registration) {
+ dump("SW registered\n");
+ registration.pushManager.subscribe().then(
+ function (subscription) {
+ sub = subscription;
+ dump("SW subscribed to push: " + sub.endpoint + "\n");
+ },
+ function (error) {
+ dump("SW not subscribed to push: " + error + "\n");
+ }
+ );
+ },
+ function (error) {
+ dump("SW not registered: " + error + "\n");
+ }
+);
+</script>
+</body>
+</html>
diff --git a/devtools/client/aboutdebugging/test/service-workers/push-sw.js b/devtools/client/aboutdebugging/test/service-workers/push-sw.js
new file mode 100644
index 000000000..b5006eedb
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/service-workers/push-sw.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-env worker */
+/* global clients */
+
+"use strict";
+
+// Send a message to all controlled windows.
+function postMessage(message) {
+ return clients.matchAll().then(function (clientlist) {
+ clientlist.forEach(function (client) {
+ client.postMessage(message);
+ });
+ });
+}
+
+// Don't wait for the next page load to become the active service worker.
+self.addEventListener("install", function (event) {
+ event.waitUntil(self.skipWaiting());
+});
+
+// Claim control over the currently open test page when activating.
+self.addEventListener("activate", function (event) {
+ event.waitUntil(self.clients.claim().then(function () {
+ return postMessage("sw-claimed");
+ }));
+});
+
+// Forward all "push" events to the controlled window.
+self.addEventListener("push", function (event) {
+ event.waitUntil(postMessage("sw-pushed"));
+});