summaryrefslogtreecommitdiffstats
path: root/devtools/client/application/test/browser
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /devtools/client/application/test/browser
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/application/test/browser')
-rw-r--r--devtools/client/application/test/browser/browser.ini80
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_debug-service-worker.js61
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_list-domain-workers.js70
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_list-multiple-workers-same-registration.js64
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_list-several-workers.js54
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_list-single-worker.js64
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_list-unicode.js47
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_list-workers-empty.js29
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_manifest-display.js145
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_manifest-load.js67
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_manifest-open-json.js67
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_manifest-reload.js51
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_open-links.js48
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_sidebar.js82
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_start-service-worker.js54
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_target-switching.js68
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_telemetry-debug-worker.js49
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_telemetry-select-page.js26
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_telemetry-start-worker.js45
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_telemetry-unregister-worker.js37
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_unregister-worker.js36
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_viewsource-service-worker.js50
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_worker-states.js62
-rw-r--r--devtools/client/application/test/browser/head.js134
-rw-r--r--devtools/client/application/test/browser/resources/manifest/icon.svg4
-rw-r--r--devtools/client/application/test/browser/resources/manifest/load-fail.html9
-rw-r--r--devtools/client/application/test/browser/resources/manifest/load-no-manifest.html8
-rw-r--r--devtools/client/application/test/browser/resources/manifest/load-ok-icons.html9
-rw-r--r--devtools/client/application/test/browser/resources/manifest/load-ok-json-error.html10
-rw-r--r--devtools/client/application/test/browser/resources/manifest/load-ok-manifest-link.html9
-rw-r--r--devtools/client/application/test/browser/resources/manifest/load-ok-warnings.html10
-rw-r--r--devtools/client/application/test/browser/resources/manifest/load-ok.html9
-rw-r--r--devtools/client/application/test/browser/resources/manifest/manifest.json3
-rw-r--r--devtools/client/application/test/browser/resources/service-workers/controlled-install-sw.js29
-rw-r--r--devtools/client/application/test/browser/resources/service-workers/controlled-install.html27
-rw-r--r--devtools/client/application/test/browser/resources/service-workers/debug-sw.js18
-rw-r--r--devtools/client/application/test/browser/resources/service-workers/debug.html25
-rw-r--r--devtools/client/application/test/browser/resources/service-workers/dynamic-registration.html19
-rw-r--r--devtools/client/application/test/browser/resources/service-workers/empty-sw.js4
-rw-r--r--devtools/client/application/test/browser/resources/service-workers/empty.html11
-rw-r--r--devtools/client/application/test/browser/resources/service-workers/scope-page.html19
-rw-r--r--devtools/client/application/test/browser/resources/service-workers/simple-unicode.html15
-rw-r--r--devtools/client/application/test/browser/resources/service-workers/simple.html32
43 files changed, 1760 insertions, 0 deletions
diff --git a/devtools/client/application/test/browser/browser.ini b/devtools/client/application/test/browser/browser.ini
new file mode 100644
index 0000000000..4150959be4
--- /dev/null
+++ b/devtools/client/application/test/browser/browser.ini
@@ -0,0 +1,80 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ head.js
+ resources/manifest/icon.svg
+ resources/manifest/load-fail.html
+ resources/manifest/load-no-manifest.html
+ resources/manifest/load-ok-icons.html
+ resources/manifest/load-ok-json-error.html
+ resources/manifest/load-ok-manifest-link.html
+ resources/manifest/load-ok-warnings.html
+ resources/manifest/load-ok.html
+ resources/manifest/manifest.json
+ resources/service-workers/controlled-install-sw.js
+ resources/service-workers/controlled-install.html
+ resources/service-workers/debug-sw.js
+ resources/service-workers/debug.html
+ resources/service-workers/dynamic-registration.html
+ resources/service-workers/empty.html
+ resources/service-workers/empty-sw.js
+ resources/service-workers/scope-page.html
+ resources/service-workers/simple.html
+ resources/service-workers/simple-unicode.html
+ !/devtools/client/debugger/test/mochitest/shared-head.js
+ !/devtools/client/shared/test/shared-head.js
+ !/devtools/client/shared/test/telemetry-test-helpers.js
+
+# Worker-related tests
+[browser_application_panel_debug-service-worker.js]
+skip-if = debug || asan || !serviceworker_e10s # Bug 1559591, 1575578, 1588154
+[browser_application_panel_list-domain-workers.js]
+https_first_disabled = true
+skip-if = debug # Bug 1559591
+[browser_application_panel_list-multiple-workers-same-registration.js]
+https_first_disabled = true
+skip-if = debug # Bug 1559591
+[browser_application_panel_list-several-workers.js]
+https_first_disabled = true
+skip-if = debug # Bug 1559591
+[browser_application_panel_list-single-worker.js]
+https_first_disabled = true
+skip-if = debug # Bug 1559591
+[browser_application_panel_start-service-worker.js]
+skip-if = asan || debug || !serviceworker_e10s || tsan # Bug 1559487, 1559591, 1608640
+[browser_application_panel_list-workers-empty.js]
+[browser_application_panel_list-unicode.js]
+skip-if = debug # Bug 1559591
+[browser_application_panel_unregister-worker.js]
+skip-if = debug # Bug 1559591
+[browser_application_panel_viewsource-service-worker.js]
+https_first_disabled = true
+skip-if = debug || asan || !serviceworker_e10s # Bug 1559591, 1575578, 1588154
+[browser_application_panel_worker-states.js]
+skip-if = asan || debug || !serviceworker_e10s # Bug 1559487, 1559591, 1608640
+# Manifest-related tests
+[browser_application_panel_manifest-display.js]
+[browser_application_panel_manifest-load.js]
+[browser_application_panel_manifest-open-json.js]
+https_first_disabled = true
+[browser_application_panel_manifest-reload.js]
+https_first_disabled = true
+# Telemetry tests
+[browser_application_panel_telemetry-debug-worker.js]
+https_first_disabled = true
+skip-if =
+ asan || debug || !serviceworker_e10s # Bug 1559487, 1559591, 1608640
+ os == 'linux' && bits == 64 && !debug # Bug 1654354
+[browser_application_panel_telemetry-select-page.js]
+[browser_application_panel_telemetry-start-worker.js]
+skip-if = ccov || asan || debug || !serviceworker_e10s || tsan # Bug 1559487, 1559591, 1608640, 1654468
+[browser_application_panel_telemetry-unregister-worker.js]
+skip-if = asan || debug || !serviceworker_e10s # Bug 1559487, 1559591, 1608640
+# Misc tests
+[browser_application_panel_open-links.js]
+skip-if = true # Bug 1467256, 1559591
+[browser_application_panel_sidebar.js]
+[browser_application_panel_target-switching.js]
+https_first_disabled = true
+skip-if = (os == 'win') || (os == 'linux') # Bug 1640234
diff --git a/devtools/client/application/test/browser/browser_application_panel_debug-service-worker.js b/devtools/client/application/test/browser/browser_application_panel_debug-service-worker.js
new file mode 100644
index 0000000000..57dd000edb
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_debug-service-worker.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/debugger/test/mochitest/shared-head.js",
+ this
+);
+
+const TAB_URL = URL_ROOT + "resources/service-workers/debug.html";
+
+add_task(async function () {
+ await enableApplicationPanel();
+
+ const { panel, tab, toolbox, commands } = await openNewTabAndApplicationPanel(
+ TAB_URL
+ );
+
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "service-workers");
+
+ info("Wait until the service worker appears in the application panel");
+ await waitUntil(() => getWorkerContainers(doc).length === 1);
+
+ const container = getWorkerContainers(doc)[0];
+ info("Wait until the inspect link is displayed");
+ await waitUntil(() => {
+ return container.querySelector(".js-inspect-link");
+ });
+
+ info("Click on the inspect link and wait for debugger to be ready");
+ const debugLink = container.querySelector(".js-inspect-link");
+ debugLink.click();
+ await waitFor(() => toolbox.getPanel("jsdebugger"));
+
+ // add a breakpoint at line 11
+ const debuggerContext = createDebuggerContext(toolbox);
+ await waitForLoadedSource(debuggerContext, "debug-sw.js");
+ await addBreakpoint(debuggerContext, "debug-sw.js", 11);
+
+ // force a pause at the breakpoint
+ info("Invoke fetch, expect the service worker script to pause on line 11");
+ await ContentTask.spawn(tab.linkedBrowser, {}, async function () {
+ content.wrappedJSObject.fetchFromWorker();
+ });
+ await waitForPaused(debuggerContext);
+ const workerScript = findSource(debuggerContext, "debug-sw.js");
+ assertPausedAtSourceAndLine(debuggerContext, workerScript.id, 11);
+ await resume(debuggerContext);
+
+ // remove breakpoint
+ await removeBreakpoint(debuggerContext, workerScript.id, 11);
+
+ await unregisterAllWorkers(commands.client, doc);
+
+ // close the tab
+ info("Closing the tab.");
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/devtools/client/application/test/browser/browser_application_panel_list-domain-workers.js b/devtools/client/application/test/browser/browser_application_panel_list-domain-workers.js
new file mode 100644
index 0000000000..ccb0884d0e
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_list-domain-workers.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Check that the application panel only displays service workers from the
+ * current domain.
+ */
+
+const SIMPLE_URL = URL_ROOT + "resources/service-workers/simple.html";
+const OTHER_URL = SIMPLE_URL.replace("example.com", "test1.example.com");
+const EMPTY_URL = (URL_ROOT + "resources/service-workers/empty.html").replace(
+ "example.com",
+ "test2.example.com"
+);
+
+add_task(async function () {
+ await enableApplicationPanel();
+
+ const { panel, commands, tab } = await openNewTabAndApplicationPanel(
+ SIMPLE_URL
+ );
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "service-workers");
+
+ info("Wait until the service worker appears in the application panel");
+ await waitUntil(() => getWorkerContainers(doc).length === 1);
+
+ let scopeEl = getWorkerContainers(doc)[0].querySelector(".js-sw-scope");
+ ok(
+ scopeEl.textContent.startsWith("example.com"),
+ "First service worker registration is displayed for the correct domain"
+ );
+
+ info(
+ "Navigate to another page for a different domain with no service worker"
+ );
+
+ await navigateTo(EMPTY_URL);
+ info("Wait until the service worker list is updated");
+ await waitUntil(
+ () => doc.querySelector(".js-registration-list-empty") !== null
+ );
+ ok(
+ true,
+ "No service workers are shown for an empty page in a different domain."
+ );
+
+ info(
+ "Navigate to another page for a different domain with another service worker"
+ );
+ await navigateTo(OTHER_URL);
+
+ info("Wait until the service worker appears in the application panel");
+ await waitUntil(() => getWorkerContainers(doc).length === 1);
+
+ scopeEl = getWorkerContainers(doc)[0].querySelector(".js-sw-scope");
+ ok(
+ scopeEl.textContent.startsWith("test1.example.com"),
+ "Second service worker registration is displayed for the correct domain"
+ );
+
+ await unregisterAllWorkers(commands.client, doc);
+
+ // close the tab
+ info("Closing the tab.");
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/devtools/client/application/test/browser/browser_application_panel_list-multiple-workers-same-registration.js b/devtools/client/application/test/browser/browser_application_panel_list-multiple-workers-same-registration.js
new file mode 100644
index 0000000000..8e117927d1
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_list-multiple-workers-same-registration.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const WORKER1_URL = URL_ROOT + "resources/service-workers/simple.html";
+const WORKER2_URL = URL_ROOT + "resources/service-workers/debug.html";
+
+add_task(async function () {
+ await enableApplicationPanel();
+
+ await openTabAndWaitForWorker(WORKER1_URL);
+ const { panel, tab, commands } = await openTabAndWaitForWorker(WORKER2_URL);
+
+ const doc = panel.panelWin.document;
+
+ let registrationContainer = getWorkerContainers(doc)[0];
+
+ info("Wait until the unregister button is displayed for the registration");
+ await waitUntil(() => {
+ registrationContainer = getWorkerContainers(doc)[0];
+ return registrationContainer.querySelector(".js-unregister-button");
+ });
+
+ const scopeEl = registrationContainer.querySelector(".js-sw-scope");
+ const expectedScope =
+ "example.com/browser/devtools/client/application/test/" +
+ "browser/resources/service-workers";
+ ok(
+ scopeEl.textContent.startsWith(expectedScope),
+ "Registration has the expected scope"
+ );
+
+ // check the workers data
+ // note that the worker from WORKER2_URL will appear second in the list with
+ // the "installed" state
+ info("Check the workers data for this registration");
+ const workers = registrationContainer.querySelectorAll(".js-sw-worker");
+ is(workers.length, 2, "Registration has two workers");
+ // check url for worker from WORKER1_URL
+ const url1El = workers[0].querySelector(".js-source-url");
+ is(url1El.textContent, "empty-sw.js", "First worker has correct URL");
+ // check url for worker from WORKER2_URL
+ const url2El = workers[1].querySelector(".js-source-url");
+ is(url2El.textContent, "debug-sw.js", "Second worker has correct URL");
+
+ await unregisterAllWorkers(commands.client, doc);
+
+ // close the tab
+ info("Closing the tab.");
+ await BrowserTestUtils.removeTab(tab);
+});
+
+async function openTabAndWaitForWorker(url) {
+ const { panel, commands, tab } = await openNewTabAndApplicationPanel(url);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "service-workers");
+
+ info("Wait until the service worker appears in the application panel");
+ await waitUntil(() => getWorkerContainers(doc).length === 1);
+
+ return { panel, commands, tab };
+}
diff --git a/devtools/client/application/test/browser/browser_application_panel_list-several-workers.js b/devtools/client/application/test/browser/browser_application_panel_list-several-workers.js
new file mode 100644
index 0000000000..4e1295082e
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_list-several-workers.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Check that the application panel can display several service workers applying to the
+ * same domain.
+ */
+
+const SIMPLE_URL = URL_ROOT + "resources/service-workers/simple.html";
+const OTHER_SCOPE_URL = URL_ROOT + "resources/service-workers/scope-page.html";
+
+add_task(async function () {
+ await enableApplicationPanel();
+
+ const { panel, commands, tab } = await openNewTabAndApplicationPanel(
+ SIMPLE_URL
+ );
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "service-workers");
+
+ info("Wait until the service worker appears in the application panel");
+ await waitUntil(() => getWorkerContainers(doc).length === 1);
+
+ info("Wait until the unregister button is displayed for the service worker");
+ await waitUntil(() =>
+ getWorkerContainers(doc)[0].querySelector(".js-unregister-button")
+ );
+
+ ok(true, "First service worker registration is displayed");
+
+ info(
+ "Navigate to another page for the same domain with another service worker"
+ );
+ await navigateTo(OTHER_SCOPE_URL);
+
+ info("Wait until the service worker appears in the application panel");
+ await waitUntil(() => getWorkerContainers(doc).length === 2);
+
+ info("Wait until the unregister button is displayed for the service worker");
+ await waitUntil(() =>
+ getWorkerContainers(doc)[1].querySelector(".js-unregister-button")
+ );
+
+ ok(true, "Second service worker registration is displayed");
+
+ await unregisterAllWorkers(commands.client, doc);
+
+ // close the tab
+ info("Closing the tab.");
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/devtools/client/application/test/browser/browser_application_panel_list-single-worker.js b/devtools/client/application/test/browser/browser_application_panel_list-single-worker.js
new file mode 100644
index 0000000000..74cab13bf6
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_list-single-worker.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TAB_URL =
+ URL_ROOT + "resources/service-workers/dynamic-registration.html";
+
+add_task(async function () {
+ await enableApplicationPanel();
+
+ const { panel, tab } = await openNewTabAndApplicationPanel(TAB_URL);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "service-workers");
+
+ info("Check for non-existing service worker");
+ const isWorkerListEmpty = !!doc.querySelector(".js-registration-list-empty");
+ ok(isWorkerListEmpty, "No Service Worker displayed");
+
+ info("Register a service worker in the page.");
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ content.wrappedJSObject.registerServiceWorker();
+ });
+
+ info("Wait until the service worker appears in the application panel");
+ await waitUntil(() => !!getWorkerContainers(doc).length);
+
+ let workerContainer = getWorkerContainers(doc)[0];
+
+ info("Wait until the unregister button is displayed for the service worker");
+ await waitUntil(() => {
+ workerContainer = getWorkerContainers(doc)[0];
+ return workerContainer.querySelector(".js-unregister-button");
+ });
+
+ const scopeEl = workerContainer.querySelector(".js-sw-scope");
+ const expectedScope =
+ "example.com/browser/devtools/client/application/test/" +
+ "browser/resources/service-workers";
+ ok(
+ scopeEl.textContent.startsWith(expectedScope),
+ "Service worker has the expected scope"
+ );
+
+ const updatedEl = workerContainer.querySelector(".js-sw-updated");
+ ok(
+ updatedEl.textContent.includes(`${new Date().getFullYear()}`),
+ "Service worker has a last updated time"
+ );
+
+ info("Unregister the service worker");
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const registration = await content.wrappedJSObject.sw;
+ registration.unregister();
+ });
+
+ info("Wait until the service worker is removed from the application panel");
+ await waitUntil(() => getWorkerContainers(doc).length === 0);
+
+ // close the tab
+ info("Closing the tab.");
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/devtools/client/application/test/browser/browser_application_panel_list-unicode.js b/devtools/client/application/test/browser/browser_application_panel_list-unicode.js
new file mode 100644
index 0000000000..c747fa9ae3
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_list-unicode.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TAB_URL = (
+ URL_ROOT + "resources/service-workers/simple-unicode.html"
+).replace("example.com", "xn--hxajbheg2az3al.xn--jxalpdlp");
+
+/**
+ * Check that the application panel displays filenames and URL's in human-readable,
+ * Unicode characters, and not encoded URI's or punycode.
+ */
+
+add_task(async function () {
+ await enableApplicationPanel();
+
+ const { panel, tab, commands } = await openNewTabAndApplicationPanel(TAB_URL);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "service-workers");
+
+ info("Wait until the service worker appears in the application panel");
+ await waitUntil(() => getWorkerContainers(doc).length === 1);
+
+ const workerContainer = getWorkerContainers(doc)[0];
+
+ const scopeEl = workerContainer.querySelector(".js-sw-scope");
+ ok(
+ scopeEl.textContent.startsWith(
+ "\u03C0\u03B1\u03C1\u03AC\u03B4\u03B5\u03B9\u03B3\u03BC\u03B1." +
+ "\u03B4\u03BF\u03BA\u03B9\u03BC\u03AE"
+ ),
+ "Service worker has the expected Unicode scope"
+ );
+ const urlEl = workerContainer.querySelector(".js-source-url");
+ ok(
+ urlEl.textContent.endsWith("\u65E5\u672C"),
+ "Service worker has the expected Unicode url"
+ );
+
+ await unregisterAllWorkers(commands.client, doc);
+
+ // close the tab
+ info("Closing the tab.");
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/devtools/client/application/test/browser/browser_application_panel_list-workers-empty.js b/devtools/client/application/test/browser/browser_application_panel_list-workers-empty.js
new file mode 100644
index 0000000000..1bb75d2999
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_list-workers-empty.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Check that the application panel only displays service workers from the
+ * current domain.
+ */
+
+const EMPTY_URL = URL_ROOT + "resources/service-workers/empty.html";
+
+add_task(async function () {
+ await enableApplicationPanel();
+
+ const { panel, tab } = await openNewTabAndApplicationPanel(EMPTY_URL);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "service-workers");
+
+ await waitUntil(
+ () => doc.querySelector(".js-registration-list-empty") !== null
+ );
+ ok(true, "No service workers are shown for an empty page");
+
+ // close the tab
+ info("Closing the tab.");
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/devtools/client/application/test/browser/browser_application_panel_manifest-display.js b/devtools/client/application/test/browser/browser_application_panel_manifest-display.js
new file mode 100644
index 0000000000..714fcc276e
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_manifest-display.js
@@ -0,0 +1,145 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Check that the manifest is being properly shown
+ */
+
+add_task(async function () {
+ info("Test that we are displaying correctly a valid manifest");
+ const url = URL_ROOT + "resources/manifest/load-ok.html";
+
+ await enableApplicationPanel();
+ const { panel, tab } = await openNewTabAndApplicationPanel(url);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "manifest");
+
+ info("Waiting for the manifest to be displayed");
+ await waitUntil(() => doc.querySelector(".js-manifest") !== null);
+ ok(true, "Manifest is being displayed");
+
+ // assert manifest members are being properly displayed
+ checkManifestMember(doc, "name", "Foo");
+ checkManifestMember(doc, "background_color", "#ff0000");
+
+ ok(
+ doc.querySelector(".js-manifest-issues") === null,
+ "No validation issues are being displayed"
+ );
+
+ // close the tab
+ info("Closing the tab.");
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function () {
+ info(
+ "Test that we are displaying correctly a manifest with validation warnings"
+ );
+ const url = URL_ROOT + "resources/manifest/load-ok-warnings.html";
+
+ await enableApplicationPanel();
+ const { panel, tab } = await openNewTabAndApplicationPanel(url);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "manifest");
+
+ info("Waiting for the manifest to be displayed");
+ await waitUntil(() => doc.querySelector(".js-manifest") !== null);
+ ok(true, "Manifest is being displayed");
+
+ // assert manifest members are being properly displayed
+ checkManifestMember(doc, "name", "Foo");
+ checkManifestMember(doc, "background_color", "");
+
+ const issuesEl = doc.querySelector(".js-manifest-issues");
+ ok(issuesEl !== null, "Validation issues are displayed");
+
+ const warningEl = [...issuesEl.querySelectorAll(".js-manifest-issue")].find(
+ x => x.textContent.includes("background_color")
+ );
+ ok(warningEl !== null, "A warning about background_color is displayed");
+
+ // close the tab
+ info("Closing the tab.");
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function () {
+ info("Test that we are displaying correctly a manifest with JSON errors");
+ const url = URL_ROOT + "resources/manifest/load-ok-json-error.html";
+
+ await enableApplicationPanel();
+ const { panel, tab } = await openNewTabAndApplicationPanel(url);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "manifest");
+
+ info("Waiting for the manifest to be displayed");
+ await waitUntil(() => doc.querySelector(".js-manifest") !== null);
+ ok(true, "Manifest is being displayed");
+
+ const issuesEl = doc.querySelector(".js-manifest-issues");
+ ok(issuesEl !== null, "Validation issues are displayed");
+
+ const errorEl = [...issuesEl.querySelectorAll(".js-manifest-issue")].find(x =>
+ x.textContent.includes("JSON")
+ );
+ ok(errorEl !== null, "An error about JSON parsing is displayed");
+
+ // close the tab
+ info("Closing the tab.");
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function () {
+ info("Test that we are displaying correctly a manifest with icons");
+ const url = URL_ROOT + "resources/manifest/load-ok-icons.html";
+
+ await enableApplicationPanel();
+ const { panel, tab } = await openNewTabAndApplicationPanel(url);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "manifest");
+
+ info("Waiting for the manifest to be displayed");
+ await waitUntil(() => doc.querySelector(".js-manifest") !== null);
+ ok(true, "Manifest is being displayed");
+
+ // assert manifest icon is being displayed
+ const iconEl = findMemberByLabel(doc, "128x128image/svg");
+ ok(iconEl !== null, "Icon label is being displayed with size and image type");
+ const imgEl = iconEl.querySelector(".js-manifest-item-content img");
+ ok(imgEl !== null, "An image is displayed for the icon");
+ is(
+ imgEl.src,
+ URL_ROOT + "resources/manifest/icon.svg",
+ "The icon image has the the icon url as source"
+ );
+ const iconTextContent = iconEl.querySelector(
+ ".js-manifest-item-content"
+ ).textContent;
+ ok(iconTextContent.includes("any"), "Purpose is being displayed");
+
+ // close the tab
+ info("Closing the tab.");
+ await BrowserTestUtils.removeTab(tab);
+});
+
+function findMemberByLabel(doc, member) {
+ return [...doc.querySelectorAll(".js-manifest-item")].find(x =>
+ x.querySelector(".js-manifest-item-label").textContent.startsWith(member)
+ );
+}
+
+function checkManifestMember(doc, member, expectedValue) {
+ const itemEl = findMemberByLabel(doc, member);
+ is(
+ itemEl.querySelector(".js-manifest-item-content").textContent,
+ expectedValue,
+ `Manifest member ${member} displays the correct value`
+ );
+}
diff --git a/devtools/client/application/test/browser/browser_application_panel_manifest-load.js b/devtools/client/application/test/browser/browser_application_panel_manifest-load.js
new file mode 100644
index 0000000000..dd89fbc1a6
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_manifest-load.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Check that the application panel fetches a manifest when in the Manifest Page
+ */
+
+add_task(async function () {
+ info("Test that manifest page loads the manifest successfully");
+ const url = URL_ROOT + "resources/manifest/load-ok.html";
+
+ await enableApplicationPanel();
+ const { panel, tab } = await openNewTabAndApplicationPanel(url);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "manifest");
+
+ info("Waiting for the manifest to load");
+ await waitUntil(() => doc.querySelector(".js-manifest") !== null);
+ ok(true, "Manifest loaded successfully");
+
+ // close the tab
+ info("Closing the tab.");
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function () {
+ info("Test that manifest page shows an error when failing to load");
+ const url = URL_ROOT + "resources/manifest/load-fail.html";
+
+ await enableApplicationPanel();
+ const { panel, tab } = await openNewTabAndApplicationPanel(url);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "manifest");
+
+ info("Waiting for the manifest to fail to load");
+ await waitUntil(
+ () => doc.querySelector(".js-manifest-loaded-error") !== null
+ );
+ ok(true, "Manifest page displays loading error");
+
+ // close the tab
+ info("Closing the tab.");
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function () {
+ info("Test that manifest page shows a message when there is no manifest");
+ const url = URL_ROOT + "resources/manifest/load-no-manifest.html";
+
+ await enableApplicationPanel();
+ const { panel, tab } = await openNewTabAndApplicationPanel(url);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "manifest");
+
+ info("Waiting for the 'no manifest' message to appear");
+ await waitUntil(() => doc.querySelector(".js-manifest-empty") !== null);
+ ok(true, "Manifest page displays a 'no manifest' message");
+
+ // close the tab
+ info("Closing the tab.");
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/devtools/client/application/test/browser/browser_application_panel_manifest-open-json.js b/devtools/client/application/test/browser/browser_application_panel_manifest-open-json.js
new file mode 100644
index 0000000000..7869502d4b
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_manifest-open-json.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Check that the application panel fetches a manifest when in the Manifest Page
+ */
+
+add_task(async function () {
+ info("Test that manifest page has a link that opens the manifest JSON file");
+ const url = URL_ROOT_SSL + "resources/manifest/load-ok-manifest-link.html";
+ const manifestUrl = URL_ROOT_SSL + "resources/manifest/manifest.json";
+
+ await enableApplicationPanel();
+ const { panel, tab } = await openNewTabAndApplicationPanel(url);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "manifest");
+
+ info("Waiting for the manifest JSON link");
+ await waitUntil(() => doc.querySelector(".js-manifest-json-link") !== null);
+ ok(true, "Link to JSON is displayed");
+
+ info("Click on link and wait till the JSON is opened in a new tab");
+ // click on the link and wait for the new tab to open
+ const onTabLoaded = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ `${manifestUrl}`
+ );
+ const link = doc.querySelector(".js-manifest-json-link");
+ link.click();
+ const jsonTab = await onTabLoaded;
+ ok(jsonTab, "The manifest JSON was opened in a new tab");
+
+ // close the tabs
+ info("Closing the page tab.");
+ await BrowserTestUtils.removeTab(tab);
+ info("Closing the manifest JSON tab.");
+ await BrowserTestUtils.removeTab(jsonTab);
+});
+
+add_task(async function () {
+ info(
+ "Test that manifest page does not show a link for manifests embedded in a data url"
+ );
+ const url = URL_ROOT_SSL + "resources/manifest/load-ok.html";
+
+ await enableApplicationPanel();
+ const { panel, tab } = await openNewTabAndApplicationPanel(url);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "manifest");
+
+ info("Waiting for the manifest to load");
+ await waitUntil(() => doc.querySelector(".js-manifest") !== null);
+ ok(true, "Manifest loaded successfully");
+ is(
+ doc.querySelector(".js-manifest-json-link"),
+ null,
+ "No JSON link is shown"
+ );
+
+ // close tab
+ info("Closing the tab");
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/devtools/client/application/test/browser/browser_application_panel_manifest-reload.js b/devtools/client/application/test/browser/browser_application_panel_manifest-reload.js
new file mode 100644
index 0000000000..4b8ab50985
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_manifest-reload.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Check that the application panel refetches the page manifest when reloading
+ * or navigating to a new page
+ */
+
+add_task(async function () {
+ await enableApplicationPanel();
+
+ info("Loading a page with no manifest");
+ let url = URL_ROOT_SSL + "resources/manifest/load-no-manifest.html";
+ const { panel, tab } = await openNewTabAndApplicationPanel(url);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "manifest");
+
+ info("Waiting for the 'no manifest' message to appear");
+ await waitFor(() => doc.querySelector(".js-manifest-empty") !== null);
+ ok(true, "Manifest page displays a 'no manifest' message");
+
+ info("Navigating to a page with a manifest");
+ url = URL_ROOT_SSL + "resources/manifest/load-ok.html";
+ await navigateTo(url);
+
+ info("Waiting for the manifest to show up");
+ await waitFor(() => doc.querySelector(".js-manifest") !== null);
+ ok(true, "Manifest displayed successfully");
+
+ info("Navigating to a page with a manifest that fails to load");
+ url = URL_ROOT_SSL + "resources/manifest/load-fail.html";
+ await navigateTo(url);
+
+ info("Waiting for the manifest to fail to load");
+ await waitFor(() => doc.querySelector(".js-manifest-loaded-error") !== null);
+ ok(true, "Manifest page displays loading error");
+
+ info("Reloading");
+ await navigateTo(url);
+
+ info("Waiting for the manifest to fail to load");
+ await waitFor(() => doc.querySelector(".js-manifest-loaded-error") !== null);
+ ok(true, "Manifest page displays loading error");
+
+ // close the tab
+ info("Closing the tab.");
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/devtools/client/application/test/browser/browser_application_panel_open-links.js b/devtools/client/application/test/browser/browser_application_panel_open-links.js
new file mode 100644
index 0000000000..d654873827
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_open-links.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { Toolbox } = require("resource://devtools/client/framework/toolbox.js");
+
+/**
+ * Check that links work when the devtools are detached in a separate window.
+ */
+
+const TAB_URL = URL_ROOT + "resources/service-workers/empty.html";
+
+add_task(async function () {
+ await enableApplicationPanel();
+
+ const { panel, toolbox } = await openNewTabAndApplicationPanel(TAB_URL);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "service-workers");
+
+ // detach devtools in a separate window
+ await toolbox.switchHost(Toolbox.HostType.WINDOW);
+
+ // click on the link and wait for the new tab to open
+ const onTabLoaded = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "about:debugging#workers",
+ true
+ );
+ doc.querySelector(".js-trusted-link").click();
+ info("Opening link in a new tab.");
+ const newTab = await onTabLoaded;
+
+ // We only need to check that newTab is truthy since
+ // BrowserTestUtils.waitForNewTab checks the URL.
+ ok(newTab, "The expected tab was opened.");
+
+ info("Wait until the main about debugging container is available");
+ await waitUntil(() => {
+ const aboutDebuggingDoc = newTab.linkedBrowser.contentDocument;
+ return aboutDebuggingDoc.querySelector(".app");
+ });
+
+ // close the tab
+ info("Closing the tab.");
+ await BrowserTestUtils.removeTab(newTab);
+});
diff --git a/devtools/client/application/test/browser/browser_application_panel_sidebar.js b/devtools/client/application/test/browser/browser_application_panel_sidebar.js
new file mode 100644
index 0000000000..80b2fbfe09
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_sidebar.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Check that the manifest is being properly shown
+ */
+
+add_task(async function () {
+ info("Test that we are displaying correctly the sidebar");
+
+ await enableApplicationPanel();
+ const { panel, tab, commands } = await openNewTabAndApplicationPanel();
+ const doc = panel.panelWin.document;
+
+ info("Waiting for the sidebar to be displayed");
+ await waitUntil(() => doc.querySelector(".js-sidebar") !== null);
+ ok(true, "Sidebar is being displayed");
+
+ await waitUntil(() => doc.querySelector(".js-service-workers-page") !== null);
+ ok(true, "Service Workers page was loaded per default.");
+
+ // close the tab
+ info("Closing the tab.");
+ await commands.client.waitForRequestsToSettle();
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function () {
+ info("Test that we are displaying correctly the selected page - manifest");
+
+ await enableApplicationPanel();
+ const { panel, tab, commands } = await openNewTabAndApplicationPanel();
+ const doc = panel.panelWin.document;
+
+ info("Select service worker page");
+ selectPage(panel, "service-workers");
+ await waitUntil(() => doc.querySelector(".js-service-workers-page") !== null);
+ await unregisterAllWorkers(commands.client, doc);
+
+ info("Select manifest page in the sidebar");
+ const link = doc.querySelector(".js-sidebar-manifest");
+ link.click();
+
+ await waitUntil(() => doc.querySelector(".js-manifest-page") !== null);
+ ok(true, "Manifest page was selected.");
+
+ // close the tab
+ info("Closing the tab.");
+ await commands.client.waitForRequestsToSettle();
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function () {
+ info(
+ "Test that we are displaying correctly the selected page - service workers"
+ );
+ const url = URL_ROOT + "resources/manifest/load-ok.html";
+
+ await enableApplicationPanel();
+ const { panel, tab, commands } = await openNewTabAndApplicationPanel(url);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "manifest");
+
+ info("Waiting for the manifest to load");
+ await waitUntil(() => doc.querySelector(".js-manifest-page") !== null);
+ ok(true, "Manifest page was selected.");
+
+ info("Select service worker page in the sidebar");
+ const link = doc.querySelector(".js-sidebar-service-workers");
+ link.click();
+
+ await waitUntil(() => doc.querySelector(".js-service-workers-page") !== null);
+ ok(true, "Service workers page was selected.");
+
+ // close the tab
+ info("Closing the tab.");
+ await commands.client.waitForRequestsToSettle();
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/devtools/client/application/test/browser/browser_application_panel_start-service-worker.js b/devtools/client/application/test/browser/browser_application_panel_start-service-worker.js
new file mode 100644
index 0000000000..1070f0f5af
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_start-service-worker.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TAB_URL = URL_ROOT + "resources/service-workers/simple.html";
+
+/**
+ * Tests that the Start button works for service workers who can be debugged
+ */
+add_task(async function () {
+ await enableApplicationPanel(); // this also enables SW debugging
+
+ // Setting a low idle_timeout and idle_extended_timeout will allow the service worker
+ // to reach the STOPPED state quickly, which will allow us to test the start button.
+ // The default value is 30000 milliseconds.
+ info("Set a low service worker idle timeout");
+ await pushPref("dom.serviceWorkers.idle_timeout", 1000);
+ await pushPref("dom.serviceWorkers.idle_extended_timeout", 1000);
+
+ const { panel, tab, commands } = await openNewTabAndApplicationPanel(TAB_URL);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "service-workers");
+
+ await waitForWorkerRegistration(tab);
+
+ info("Wait until the service worker appears in the application panel");
+ await waitUntil(() => getWorkerContainers(doc).length === 1);
+
+ info("Wait until the start button is displayed and enabled");
+ const container = getWorkerContainers(doc)[0];
+ await waitUntil(() => {
+ const button = container.querySelector(".js-start-button");
+ return button && !button.disabled;
+ });
+
+ info("Click the button and wait for the worker to start");
+ const button = container.querySelector(".js-start-button");
+ button.click();
+
+ info("Wait until status 'Running' is displayed");
+ await waitUntil(() => {
+ const statusEl = container.querySelector(".js-worker-status");
+ return statusEl && statusEl.textContent === "Running";
+ });
+ ok(true, "Worker status is 'Running'");
+
+ await unregisterAllWorkers(commands.client, doc);
+
+ // close the tab
+ info("Closing the tab.");
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/devtools/client/application/test/browser/browser_application_panel_target-switching.js b/devtools/client/application/test/browser/browser_application_panel_target-switching.js
new file mode 100644
index 0000000000..ff8d521e30
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_target-switching.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test switching for the top-level target.
+
+// We use about:robots, because this page will run in the parent process.
+// Navigating from about:robots to a regular content page will always trigger
+// a target switch, with or without fission.
+const PARENT_PROCESS_URI = "about:robots";
+const CONTENT_PROCESS_URI_WORKERS =
+ URL_ROOT + "resources/service-workers/simple.html";
+const CONTENT_PROCESS_URI_MANIFEST =
+ URL_ROOT + "resources/manifest/load-ok.html";
+
+// test workers when target switching
+add_task(async function () {
+ await enableApplicationPanel();
+
+ info("Open a page that runs in the parent process");
+ const { panel, commands, tab } = await openNewTabAndApplicationPanel(
+ PARENT_PROCESS_URI
+ );
+ const doc = panel.panelWin.document;
+
+ info("Check for non-existing service worker");
+ selectPage(panel, "service-workers");
+ const isWorkerListEmpty = !!doc.querySelector(".js-registration-list-empty");
+ ok(isWorkerListEmpty, "No Service Worker displayed");
+
+ info("Navigate to a page that runs in the child process");
+ await navigateTo(CONTENT_PROCESS_URI_WORKERS);
+
+ info("Wait until the service worker appears in the application panel");
+ await waitUntil(() => getWorkerContainers(doc).length === 1);
+
+ // close the tab
+ info("Closing the tab.");
+ await unregisterAllWorkers(commands.client, doc);
+ await BrowserTestUtils.removeTab(tab);
+});
+
+// test manifest when target switching
+add_task(async function () {
+ await enableApplicationPanel();
+
+ info("Open a page that runs in the parent process");
+ const { panel, tab } = await openNewTabAndApplicationPanel(
+ PARENT_PROCESS_URI
+ );
+ const doc = panel.panelWin.document;
+
+ info("Waiting for the 'no manifest' message to appear");
+ selectPage(panel, "manifest");
+ await waitUntil(() => doc.querySelector(".js-manifest-empty") !== null);
+
+ info("Navigate to a page that runs in the child process");
+ await navigateTo(CONTENT_PROCESS_URI_MANIFEST);
+
+ info("Waiting for the manifest to load");
+ selectPage(panel, "manifest");
+ await waitUntil(() => doc.querySelector(".js-manifest") !== null);
+ ok(true, "Manifest loaded successfully");
+
+ // close the tab
+ info("Closing the tab.");
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/devtools/client/application/test/browser/browser_application_panel_telemetry-debug-worker.js b/devtools/client/application/test/browser/browser_application_panel_telemetry-debug-worker.js
new file mode 100644
index 0000000000..fe95dabd5e
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_telemetry-debug-worker.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TAB_URL = URL_ROOT + "resources/service-workers/simple.html";
+
+// check telemetry for debugging a service worker
+add_task(async function () {
+ await enableApplicationPanel();
+
+ const { panel, tab, toolbox, commands } = await openNewTabAndApplicationPanel(
+ TAB_URL
+ );
+
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "service-workers");
+ setupTelemetryTest();
+
+ info("Wait until the service worker appears in the application panel");
+ await waitUntil(() => getWorkerContainers(doc).length === 1);
+
+ const container = getWorkerContainers(doc)[0];
+ info("Wait until the debug link is displayed");
+ await waitUntil(() => {
+ return container.querySelector(".js-inspect-link");
+ });
+
+ info("Click on the debug link and wait for debugger to be ready");
+ const debugLink = container.querySelector(".js-inspect-link");
+ debugLink.click();
+ await waitUntil(() => toolbox.getPanel("jsdebugger"));
+
+ const events = getTelemetryEvents("jsdebugger");
+ const openToolboxEvent = events.find(event => event.method == "enter");
+ ok(openToolboxEvent.session_id > 0, "Event has a valid session id");
+ is(
+ openToolboxEvent.start_state,
+ "application",
+ "Event has the 'application' start state"
+ );
+
+ // clean up and close the tab
+ await unregisterAllWorkers(commands.client, doc);
+ info("Closing the tab.");
+ await commands.client.waitForRequestsToSettle();
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/devtools/client/application/test/browser/browser_application_panel_telemetry-select-page.js b/devtools/client/application/test/browser/browser_application_panel_telemetry-select-page.js
new file mode 100644
index 0000000000..c200c38c17
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_telemetry-select-page.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function () {
+ await enableApplicationPanel();
+
+ const TAB_URL = URL_ROOT + "resources/service-workers/empty.html";
+ const { panel, tab, commands } = await openNewTabAndApplicationPanel(TAB_URL);
+ const doc = panel.panelWin.document;
+
+ setupTelemetryTest();
+
+ // make sure the default page is opened and then select a different one
+ await waitUntil(() => doc.querySelector(".js-service-workers-page") !== null);
+ ok(true, "Service Workers page was loaded per default.");
+ selectPage(panel, "manifest");
+
+ checkTelemetryEvent({ method: "select_page", page_type: "manifest" });
+
+ // close the tab
+ info("Closing the tab.");
+ await commands.client.waitForRequestsToSettle();
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/devtools/client/application/test/browser/browser_application_panel_telemetry-start-worker.js b/devtools/client/application/test/browser/browser_application_panel_telemetry-start-worker.js
new file mode 100644
index 0000000000..235ce3060d
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_telemetry-start-worker.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TAB_URL = URL_ROOT + "resources/service-workers/simple.html";
+
+// check telemetry for starting a service worker
+add_task(async function () {
+ info("Set a low service worker idle timeout");
+ await pushPref("dom.serviceWorkers.idle_timeout", 1000);
+ await pushPref("dom.serviceWorkers.idle_extended_timeout", 1000);
+
+ await enableApplicationPanel();
+
+ const { panel, tab, commands } = await openNewTabAndApplicationPanel(TAB_URL);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "service-workers");
+ await waitForWorkerRegistration(tab);
+
+ setupTelemetryTest();
+
+ info("Wait until the service worker appears in the application panel");
+ await waitUntil(() => getWorkerContainers(doc).length === 1);
+
+ info("Wait until the start button is displayed and enabled");
+ const container = getWorkerContainers(doc)[0];
+ await waitUntil(() => {
+ const button = container.querySelector(".js-start-button");
+ return button && !button.disabled;
+ });
+
+ info("Click the start button");
+ const button = container.querySelector(".js-start-button");
+ button.click();
+
+ checkTelemetryEvent({ method: "start_worker" });
+
+ // clean up and close the tab
+ await unregisterAllWorkers(commands.client, doc);
+ info("Closing the tab.");
+ await commands.client.waitForRequestsToSettle();
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/devtools/client/application/test/browser/browser_application_panel_telemetry-unregister-worker.js b/devtools/client/application/test/browser/browser_application_panel_telemetry-unregister-worker.js
new file mode 100644
index 0000000000..aa6d35f946
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_telemetry-unregister-worker.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TAB_URL = URL_ROOT + "resources/service-workers/simple.html";
+
+// check telemetry for unregistering a service worker
+add_task(async function () {
+ await enableApplicationPanel();
+
+ const { panel, tab, commands } = await openNewTabAndApplicationPanel(TAB_URL);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "service-workers");
+
+ setupTelemetryTest();
+
+ info("Wait until the service worker appears in the application panel");
+ await waitUntil(() => getWorkerContainers(doc).length === 1);
+
+ const workerContainer = getWorkerContainers(doc)[0];
+
+ info("Wait until the unregister button is displayed for the service worker");
+ await waitUntil(() => workerContainer.querySelector(".js-unregister-button"));
+ info("Click the unregister button");
+ const button = workerContainer.querySelector(".js-unregister-button");
+ button.click();
+
+ checkTelemetryEvent({ method: "unregister_worker" });
+
+ // clean up and close the tab
+ await unregisterAllWorkers(commands.client, doc);
+ info("Closing the tab.");
+ await commands.client.waitForRequestsToSettle();
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/devtools/client/application/test/browser/browser_application_panel_unregister-worker.js b/devtools/client/application/test/browser/browser_application_panel_unregister-worker.js
new file mode 100644
index 0000000000..8f6c4a5793
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_unregister-worker.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TAB_URL = URL_ROOT + "resources/service-workers/simple.html";
+
+add_task(async function () {
+ await enableApplicationPanel();
+
+ const { panel, tab, commands } = await openNewTabAndApplicationPanel(TAB_URL);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "service-workers");
+
+ info("Wait until the service worker appears in the application panel");
+ await waitUntil(() => getWorkerContainers(doc).length === 1);
+
+ const workerContainer = getWorkerContainers(doc)[0];
+
+ info("Wait until the unregister button is displayed for the service worker");
+ await waitUntil(() => workerContainer.querySelector(".js-unregister-button"));
+ info("Click the unregister button");
+ const button = workerContainer.querySelector(".js-unregister-button");
+ button.click();
+ info("Wait until the service worker is removed from the application panel");
+ await waitUntil(() => getWorkerContainers(doc).length === 0);
+ ok(true, "Service worker list is empty");
+
+ // just in case cleanup
+ await unregisterAllWorkers(commands.client, doc);
+
+ // close the tab
+ info("Closing the tab.");
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/devtools/client/application/test/browser/browser_application_panel_viewsource-service-worker.js b/devtools/client/application/test/browser/browser_application_panel_viewsource-service-worker.js
new file mode 100644
index 0000000000..fc6f0819b6
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_viewsource-service-worker.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TAB_URL = URL_ROOT + "resources/service-workers/debug.html";
+const SW_URL = URL_ROOT + "resources/service-workers/debug-sw.js";
+
+add_task(async function () {
+ await enableApplicationPanel();
+
+ // disable service worker debugging
+ await pushPref(
+ "devtools.debugger.features.windowless-service-workers",
+ false
+ );
+
+ const { panel, tab, commands } = await openNewTabAndApplicationPanel(TAB_URL);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "service-workers");
+
+ info("Wait until the service worker appears in the application panel");
+ await waitUntil(() => getWorkerContainers(doc).length === 1);
+
+ const container = getWorkerContainers(doc)[0];
+ info("Wait until the inspect link is displayed");
+ await waitUntil(() => {
+ return container.querySelector(".js-inspect-link");
+ });
+
+ info("Click on the inspect link and wait for a new view-source: tab open");
+ // click on the link and wait for the new tab to open
+ const onTabLoaded = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ `view-source:${SW_URL}`
+ );
+ const inspectLink = container.querySelector(".js-inspect-link");
+ inspectLink.click();
+
+ const sourceTab = await onTabLoaded;
+ ok(sourceTab, "The service worker source was opened in a new tab");
+
+ // clean up
+ await unregisterAllWorkers(commands.client, doc);
+ // close the tabs
+ info("Closing the tabs.");
+ await BrowserTestUtils.removeTab(sourceTab);
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/devtools/client/application/test/browser/browser_application_panel_worker-states.js b/devtools/client/application/test/browser/browser_application_panel_worker-states.js
new file mode 100644
index 0000000000..54446506fa
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_worker-states.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TAB_URL = URL_ROOT + "resources/service-workers/controlled-install.html";
+
+add_task(async function () {
+ await enableApplicationPanel();
+
+ const { panel, tab } = await openNewTabAndApplicationPanel(TAB_URL);
+ const doc = panel.panelWin.document;
+
+ selectPage(panel, "service-workers");
+
+ info("Check for non-existing service worker");
+ const isWorkerListEmpty = !!doc.querySelector(".js-registration-list-empty");
+ ok(isWorkerListEmpty, "No Service Worker displayed");
+
+ info("Register a service worker with a controlled install in the page.");
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ content.wrappedJSObject.registerServiceWorker();
+ });
+
+ info("Wait until the service worker appears in the application panel");
+ await waitUntil(() => !!getWorkerContainers(doc).length);
+ info("Wait until the 'Installing' state is displayed");
+ await waitUntil(() => {
+ const containers = getWorkerContainers(doc);
+ if (containers.length === 0) {
+ return false;
+ }
+
+ const stateEl = containers[0].querySelector(".js-worker-status");
+ return stateEl.textContent.toLowerCase() === "installing";
+ });
+
+ info("Allow the service worker to complete installation");
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ content.wrappedJSObject.installServiceWorker();
+ });
+
+ info("Wait until the 'running' state is displayed");
+ await waitUntil(() => {
+ const workerContainer = getWorkerContainers(doc)[0];
+ const stateEl = workerContainer.querySelector(".js-worker-status");
+ return stateEl.textContent.toLowerCase() === "running";
+ });
+
+ info("Unregister the service worker");
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const registration = await content.wrappedJSObject.sw;
+ registration.unregister();
+ });
+
+ info("Wait until the service worker is removed from the application panel");
+ await waitUntil(() => getWorkerContainers(doc).length === 0);
+
+ // close the tab
+ info("Closing the tab.");
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/devtools/client/application/test/browser/head.js b/devtools/client/application/test/browser/head.js
new file mode 100644
index 0000000000..6b7e67ff8d
--- /dev/null
+++ b/devtools/client/application/test/browser/head.js
@@ -0,0 +1,134 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-env browser */
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+
+"use strict";
+
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
+ this
+);
+
+/**
+ * Set all preferences needed to enable service worker debugging and testing.
+ */
+async function enableServiceWorkerDebugging() {
+ // Enable service workers.
+ await pushPref("dom.serviceWorkers.enabled", true);
+ // Accept workers from mochitest's http.
+ await pushPref("dom.serviceWorkers.testing.enabled", true);
+ // Force single content process, see Bug 1231208 for the SW refactor that should enable
+ // SW debugging in multi-e10s.
+ await pushPref("dom.ipc.processCount", 1);
+
+ // Enable service workers in the debugger
+ await pushPref("devtools.debugger.features.windowless-service-workers", true);
+ // Disable randomly spawning processes during tests
+ await pushPref("dom.ipc.processPrelaunch.enabled", false);
+
+ // Wait for dom.ipc.processCount to be updated before releasing processes.
+ Services.ppmm.releaseCachedProcesses();
+}
+
+async function enableApplicationPanel() {
+ // FIXME bug 1575427 this rejection is very common.
+ const { PromiseTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromiseTestUtils.sys.mjs"
+ );
+ PromiseTestUtils.allowMatchingRejectionsGlobally(
+ /this._frontCreationListeners is null/
+ );
+
+ // Enable all preferences related to service worker debugging.
+ await enableServiceWorkerDebugging();
+
+ // Enable web manifest processing.
+ Services.prefs.setBoolPref("dom.manifest.enabled", true);
+
+ // Enable application panel in DevTools.
+ await pushPref("devtools.application.enabled", true);
+}
+
+function setupTelemetryTest() {
+ // Reset all the counts
+ Services.telemetry.clearEvents();
+
+ // Ensure no events have been logged
+ const ALL_CHANNELS = Ci.nsITelemetry.DATASET_ALL_CHANNELS;
+ const snapshot = Services.telemetry.snapshotEvents(ALL_CHANNELS, true);
+ ok(!snapshot.parent, "No events have been logged for the main process");
+}
+
+function getTelemetryEvents(objectName) {
+ // read the requested events only
+ const ALL_CHANNELS = Ci.nsITelemetry.DATASET_ALL_CHANNELS;
+ const snapshot = Services.telemetry.snapshotEvents(ALL_CHANNELS, true);
+ // filter and transform the event data so the relevant info is in a single object:
+ // { method: "...", extraField: "...", anotherExtraField: "...", ... }
+ const events = snapshot.parent
+ .filter(event => event[1] === "devtools.main" && event[3] === objectName)
+ .map(event => ({ method: event[2], ...event[5] }));
+
+ return events;
+}
+
+function checkTelemetryEvent(expectedEvent, objectName = "application") {
+ info("Check telemetry event");
+ const events = getTelemetryEvents(objectName);
+
+ // assert we only got 1 event with a valid session ID
+ is(events.length, 1, "There was only 1 event logged");
+ const [event] = events;
+ ok(event.session_id > 0, "There is a valid session_id in the event");
+
+ // assert expected data
+ Assert.deepEqual(event, { ...expectedEvent, session_id: event.session_id });
+}
+
+function getWorkerContainers(doc) {
+ return doc.querySelectorAll(".js-sw-container");
+}
+
+async function openNewTabAndApplicationPanel(url) {
+ const tab = await addTab(url);
+
+ const toolbox = await gDevTools.showToolboxForTab(tab, {
+ toolId: "application",
+ });
+ const panel = toolbox.getCurrentPanel();
+ const target = toolbox.target;
+ const commands = toolbox.commands;
+ return { panel, tab, target, toolbox, commands };
+}
+
+async function unregisterAllWorkers(client, doc) {
+ // This method is declared in shared-head.js
+ await unregisterAllServiceWorkers(client);
+
+ info("Wait for service workers to disappear from the UI");
+ waitUntil(() => getWorkerContainers(doc).length === 0);
+}
+
+async function waitForWorkerRegistration(swTab) {
+ info("Wait until the registration appears on the window");
+ const swBrowser = swTab.linkedBrowser;
+ await asyncWaitUntil(async () =>
+ SpecialPowers.spawn(swBrowser, [], function () {
+ return !!content.wrappedJSObject.getRegistration();
+ })
+ );
+}
+
+function selectPage(panel, page) {
+ /**
+ * Select a page by simulating a user click in the sidebar.
+ * @param {string} page The page we want to select (see `PAGE_TYPES`)
+ **/
+ info(`Selecting application page: ${page}`);
+ const doc = panel.panelWin.document;
+ const navItem = doc.querySelector(`.js-sidebar-${page}`);
+ navItem.click();
+}
diff --git a/devtools/client/application/test/browser/resources/manifest/icon.svg b/devtools/client/application/test/browser/resources/manifest/icon.svg
new file mode 100644
index 0000000000..bfed2982bc
--- /dev/null
+++ b/devtools/client/application/test/browser/resources/manifest/icon.svg
@@ -0,0 +1,4 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 953.37 984"><defs><linearGradient id="linear-gradient" x1="-14706.28" y1="9250.14" x2="-14443.04" y2="9250.14" gradientTransform="matrix(0.76, 0.03, 0.05, -1.12, 11485.47, 11148)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0083ff"/><stop offset="0.1" stop-color="#0092f8"/><stop offset="0.31" stop-color="#00abeb"/><stop offset="0.52" stop-color="#00bee1"/><stop offset="0.75" stop-color="#00c8dc"/><stop offset="1" stop-color="#00ccda"/></linearGradient><radialGradient id="radial-gradient" cx="-7588.66" cy="8866.53" r="791.23" gradientTransform="matrix(1.23, 0, 0, -1.22, 9958.21, 11048.11)" gradientUnits="userSpaceOnUse"><stop offset="0.02" stop-color="#005fe7"/><stop offset="0.18" stop-color="#0042b4"/><stop offset="0.32" stop-color="#002989"/><stop offset="0.4" stop-color="#002079"/><stop offset="0.47" stop-color="#131d78"/><stop offset="0.66" stop-color="#3b1676"/><stop offset="0.75" stop-color="#4a1475"/></radialGradient><linearGradient id="linear-gradient-2" x1="539.64" y1="254.8" x2="348.2" y2="881.03" gradientTransform="matrix(1, 0, 0, -1, 1, 984)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#000f43" stop-opacity="0.4"/><stop offset="0.48" stop-color="#001962" stop-opacity="0.17"/><stop offset="1" stop-color="#002079" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-3" x1="540.64" y1="254.8" x2="349.2" y2="881.03" gradientTransform="matrix(1, 0, 0, -1, 0, 984)" href="#linear-gradient-2"/><linearGradient id="linear-gradient-4" x1="-8367.12" y1="7348.87" x2="-8482.36" y2="7357.76" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#812cc9"/><stop offset="1" stop-color="#005fe7"/></linearGradient><linearGradient id="linear-gradient-5" x1="-8449.89" y1="7496.97" x2="-8341.94" y2="7609.09" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0.05" stop-color="#005fe7"/><stop offset="0.18" stop-color="#065de6"/><stop offset="0.35" stop-color="#1856e1"/><stop offset="0.56" stop-color="#354adb"/><stop offset="0.78" stop-color="#5d3ad1"/><stop offset="0.95" stop-color="#812cc9"/></linearGradient><linearGradient id="linear-gradient-6" x1="-8653.41" y1="7245.3" x2="-8422.52" y2="7244.76" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#002079"/><stop offset="0.99" stop-color="#a238ff"/></linearGradient><radialGradient id="radial-gradient-2" cx="644.11" cy="599.83" fx="785.0454815336918" fy="470.6889181532662" r="793.95" gradientTransform="matrix(1, 0, 0, -1, 0, 984)" gradientUnits="userSpaceOnUse"><stop offset="0.2" stop-color="#00fdff"/><stop offset="0.26" stop-color="#0af1ff"/><stop offset="0.37" stop-color="#23d2ff"/><stop offset="0.52" stop-color="#4da0ff"/><stop offset="0.69" stop-color="#855bff"/><stop offset="0.77" stop-color="#a238ff"/><stop offset="0.81" stop-color="#a738fd"/><stop offset="0.86" stop-color="#b539f9"/><stop offset="0.9" stop-color="#cd39f1"/><stop offset="0.96" stop-color="#ee3ae6"/><stop offset="0.98" stop-color="#ff3be0"/></radialGradient><linearGradient id="linear-gradient-7" x1="-7458.97" y1="9093.17" x2="-7531.06" y2="8282.84" gradientTransform="matrix(1.23, 0, 0, -1.22, 9958.21, 11048.11)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#00ec00"/><stop offset="0.1" stop-color="#00e244"/><stop offset="0.22" stop-color="#00d694"/><stop offset="0.31" stop-color="#00cfc7"/><stop offset="0.35" stop-color="#00ccda"/><stop offset="0.42" stop-color="#0bc2dd" stop-opacity="0.92"/><stop offset="0.57" stop-color="#29a7e4" stop-opacity="0.72"/><stop offset="0.77" stop-color="#597df0" stop-opacity="0.4"/><stop offset="1" stop-color="#9448ff" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-8" x1="-8926.61" y1="7680.53" x2="-8790.14" y2="7680.53" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#005fe7"/><stop offset="0.46" stop-color="#0071f3" stop-opacity="0.51"/><stop offset="0.83" stop-color="#007efc" stop-opacity="0.14"/><stop offset="1" stop-color="#0083ff" stop-opacity="0"/></linearGradient><radialGradient id="radial-gradient-3" cx="-8914.62" cy="7721.05" r="165.97" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0.63" stop-color="#ffe302" stop-opacity="0"/><stop offset="0.67" stop-color="#ffe302" stop-opacity="0.05"/><stop offset="0.75" stop-color="#ffe302" stop-opacity="0.19"/><stop offset="0.86" stop-color="#ffe302" stop-opacity="0.4"/><stop offset="0.99" stop-color="#ffe302" stop-opacity="0.7"/></radialGradient><linearGradient id="linear-gradient-9" x1="214.02" y1="2032.47" x2="96.19" y2="2284.31" gradientTransform="matrix(0.99, 0.1, 0.1, -0.99, -250.1, 2306.29)" gradientUnits="userSpaceOnUse"><stop offset="0.19" stop-color="#4a1475" stop-opacity="0.5"/><stop offset="0.62" stop-color="#2277ac" stop-opacity="0.23"/><stop offset="0.94" stop-color="#00ccda" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-10" x1="-38.44" y1="278.18" x2="55.67" y2="171.29" gradientTransform="matrix(0.99, 0.1, 0.1, -0.99, 229.04, 745.87)" gradientUnits="userSpaceOnUse"><stop offset="0.01" stop-color="#002079" stop-opacity="0.5"/><stop offset="1" stop-color="#0083ff" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-11" x1="142.45" y1="96.25" x2="142.5" y2="149.68" gradientTransform="matrix(0.99, 0.1, 0.1, -0.99, 229.04, 745.87)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#4a1475" stop-opacity="0.9"/><stop offset="0.18" stop-color="#6720a2" stop-opacity="0.6"/><stop offset="0.38" stop-color="#812acb" stop-opacity="0.34"/><stop offset="0.57" stop-color="#9332e8" stop-opacity="0.15"/><stop offset="0.76" stop-color="#9e36f9" stop-opacity="0.04"/><stop offset="0.93" stop-color="#a238ff" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-12" x1="620.52" y1="947.88" x2="926.18" y2="264.39" gradientTransform="matrix(1, 0, 0, -1, 0, 984)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#00ec00" stop-opacity="0"/><stop offset="0.28" stop-color="#00dc6d" stop-opacity="0.5"/><stop offset="0.5" stop-color="#00d1bb" stop-opacity="0.86"/><stop offset="0.6" stop-color="#00ccda"/><stop offset="0.68" stop-color="#04c9db"/><stop offset="0.75" stop-color="#0fc1df"/><stop offset="0.83" stop-color="#23b2e6"/><stop offset="0.9" stop-color="#3e9ef0"/><stop offset="0.98" stop-color="#6184fc"/><stop offset="0.99" stop-color="#6680fe"/></linearGradient><linearGradient id="linear-gradient-13" x1="680.88" y1="554.79" x2="536.1" y2="166.04" gradientTransform="matrix(1, 0, 0, -1, 0, 984)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0083ff"/><stop offset="0.04" stop-color="#0083ff" stop-opacity="0.92"/><stop offset="0.14" stop-color="#0083ff" stop-opacity="0.71"/><stop offset="0.26" stop-color="#0083ff" stop-opacity="0.52"/><stop offset="0.37" stop-color="#0083ff" stop-opacity="0.36"/><stop offset="0.49" stop-color="#0083ff" stop-opacity="0.23"/><stop offset="0.61" stop-color="#0083ff" stop-opacity="0.13"/><stop offset="0.73" stop-color="#0083ff" stop-opacity="0.06"/><stop offset="0.86" stop-color="#0083ff" stop-opacity="0.01"/><stop offset="1" stop-color="#0083ff" stop-opacity="0"/></linearGradient></defs><title>firefox-logo-nightly</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><g id="Layer_2-2" data-name="Layer 2"><g id="Firefox"><path d="M770.28,91.56c-23.95,27.88-35.1,90.64-10.82,154.26s61.5,49.8,84.7,114.67c30.62,85.6,16.37,200.59,16.37,200.59s36.81,106.61,62.47-6.63C979.79,341.74,770.28,143.94,770.28,91.56Z" style="fill:url(#linear-gradient)"/><path id="_Path_" data-name=" Path " d="M476.92,972.83c245.24,0,443.9-199.74,443.9-446s-198.66-446-443.66-446S33.5,280.51,33.5,526.8C33,773.33,231.92,972.83,476.92,972.83Z" style="fill:url(#radial-gradient)"/><path d="M810.67,803.64a246.8,246.8,0,0,1-30.12,18.18,705.31,705.31,0,0,0,38.3-63c9.46-10.47,18.13-20.65,25.19-31.65,3.44-5.41,7.31-12.08,11.42-19.82,24.92-44.9,52.4-117.56,53.18-192.2v-5.66a257.25,257.25,0,0,0-5.71-55.75c.2,1.43.38,2.86.56,4.29-.22-1.1-.41-2.21-.64-3.31.37,2,.66,4,1,6,5.09,43.22,1.47,85.37-16.68,116.45-.29.45-.58.88-.87,1.32,9.41-47.23,12.56-99.39,2.09-151.6,0,0-4.19-25.38-35.38-102.44-18-44.35-49.83-80.72-78-107.21-24.69-30.55-47.11-51-59.47-64.06C689.72,126,678.9,105.61,674.45,92.31c-3.85-1.93-53.14-49.81-57.05-51.63-21.51,33.35-89.16,137.67-57,235.15,14.58,44.17,51.47,90,90.07,115.74,1.69,1.94,23,25,33.09,77.16,10.45,53.85,5,95.86-16.54,158C641.73,681.24,577,735.12,516.3,740.63c-129.67,11.78-177.15-65.11-177.15-65.11C385.49,694,436.72,690.17,467.87,671c31.4-19.43,50.39-33.83,65.81-28.15C548.86,648.43,561,632,550.1,615a78.5,78.5,0,0,0-79.4-34.57c-31.43,5.11-60.23,30-101.41,5.89a86.29,86.29,0,0,1-7.73-5.06c-2.71-1.79,8.83,2.72,6.13.69-8-4.35-22.2-13.84-25.88-17.22-.61-.56,6.22,2.18,5.61,1.62-38.51-31.71-33.7-53.13-32.49-66.57,1-10.75,8-24.52,19.75-30.11,5.69,3.11,9.24,5.48,9.24,5.48s-2.43-5-3.74-7.58c.46-.2.9-.15,1.36-.34,4.66,2.25,15,8.1,20.41,11.67,7.07,5,9.33,9.44,9.33,9.44s1.86-1,.48-5.37c-.5-1.78-2.65-7.45-9.65-13.17h.44A81.61,81.61,0,0,1,374.42,478c2-7.18,5.53-14.68,4.75-28.09-.48-9.43-.26-11.87-1.92-15.51-1.49-3.13.83-4.35,3.42-1.1a32.5,32.5,0,0,0-2.21-7.4v-.24c3.23-11.24,68.25-40.46,73-43.88A67.2,67.2,0,0,0,470.59,361c3.62-5.76,6.34-13.85,7-26.11.36-8.84-3.76-14.73-69.51-21.62-18-1.77-28.53-14.8-34.53-26.82-1.09-2.59-2.21-4.94-3.33-7.28a57.68,57.68,0,0,1-2.56-8.43c10.75-30.87,28.81-57,55.37-76.7,1.45-1.32-5.78.34-4.34-1,1.69-1.54,12.71-6,14.79-7,2.54-1.2-10.88-6.9-22.73-5.51-12.07,1.36-14.63,2.8-21.07,5.53,2.67-2.66,11.17-6.15,9.18-6.13-13,2-29.18,9.56-43,18.12a10.66,10.66,0,0,1,.83-4.35c-6.44,2.73-22.26,13.79-26.87,23.14a44.29,44.29,0,0,0,.27-5.4,84.17,84.17,0,0,0-13.19,13.82l-.24.22c-37.36-15-70.23-16-98.05-9.28-6.09-6.11-9.06-1.64-22.91-32.07-.94-1.83.72,1.81,0,0-2.28-5.9,1.39,7.87,0,0-23.28,18.37-53.92,39.19-68.63,53.89-.18.59,17.16-4.9,0,0-6,1.72-5.6,5.28-6.51,37.5-.22,2.44,0,5.18-.22,7.38-11.75,15-19.75,27.64-22.78,34.21-15.19,26.18-31.93,67-48.15,131.55A334.82,334.82,0,0,1,75.2,398.36C61.71,432.63,48.67,486.44,46.07,569.3A482.08,482.08,0,0,1,58.6,518.64,473,473,0,0,0,93.33,719.71c9.33,22.82,24.76,57.46,51,95.4C226.9,902,343.31,956,472.21,956,606.79,956,727.64,897.13,810.67,803.64Z" style="fill:url(#linear-gradient-2)"/><path d="M810.67,803.64a246.8,246.8,0,0,1-30.12,18.18,705.31,705.31,0,0,0,38.3-63c9.46-10.47,18.13-20.65,25.19-31.65,3.44-5.41,7.31-12.08,11.42-19.82,24.92-44.9,52.4-117.56,53.18-192.2v-5.66a257.25,257.25,0,0,0-5.71-55.75c.2,1.43.38,2.86.56,4.29-.22-1.1-.41-2.21-.64-3.31.37,2,.66,4,1,6,5.09,43.22,1.47,85.37-16.68,116.45-.29.45-.58.88-.87,1.32,9.41-47.23,12.56-99.39,2.09-151.6,0,0-4.19-25.38-35.38-102.44-18-44.35-49.83-80.72-78-107.21-24.69-30.55-47.11-51-59.47-64.06C689.72,126,678.9,105.61,674.45,92.31c-3.85-1.93-53.14-49.81-57.05-51.63-21.51,33.35-89.16,137.67-57,235.15,14.58,44.17,51.47,90,90.07,115.74,1.69,1.94,23,25,33.09,77.16,10.45,53.85,5,95.86-16.54,158C641.73,681.24,577,735.12,516.3,740.63c-129.67,11.78-177.15-65.11-177.15-65.11C385.49,694,436.72,690.17,467.87,671c31.4-19.43,50.39-33.83,65.81-28.15C548.86,648.43,561,632,550.1,615a78.5,78.5,0,0,0-79.4-34.57c-31.43,5.11-60.23,30-101.41,5.89a86.29,86.29,0,0,1-7.73-5.06c-2.71-1.79,8.83,2.72,6.13.69-8-4.35-22.2-13.84-25.88-17.22-.61-.56,6.22,2.18,5.61,1.62-38.51-31.71-33.7-53.13-32.49-66.57,1-10.75,8-24.52,19.75-30.11,5.69,3.11,9.24,5.48,9.24,5.48s-2.43-5-3.74-7.58c.46-.2.9-.15,1.36-.34,4.66,2.25,15,8.1,20.41,11.67,7.07,5,9.33,9.44,9.33,9.44s1.86-1,.48-5.37c-.5-1.78-2.65-7.45-9.65-13.17h.44A81.61,81.61,0,0,1,374.42,478c2-7.18,5.53-14.68,4.75-28.09-.48-9.43-.26-11.87-1.92-15.51-1.49-3.13.83-4.35,3.42-1.1a32.5,32.5,0,0,0-2.21-7.4v-.24c3.23-11.24,68.25-40.46,73-43.88A67.2,67.2,0,0,0,470.59,361c3.62-5.76,6.34-13.85,7-26.11.36-8.84-3.76-14.73-69.51-21.62-18-1.77-28.53-14.8-34.53-26.82-1.09-2.59-2.21-4.94-3.33-7.28a57.68,57.68,0,0,1-2.56-8.43c10.75-30.87,28.81-57,55.37-76.7,1.45-1.32-5.78.34-4.34-1,1.69-1.54,12.71-6,14.79-7,2.54-1.2-10.88-6.9-22.73-5.51-12.07,1.36-14.63,2.8-21.07,5.53,2.67-2.66,11.17-6.15,9.18-6.13-13,2-29.18,9.56-43,18.12a10.66,10.66,0,0,1,.83-4.35c-6.44,2.73-22.26,13.79-26.87,23.14a44.29,44.29,0,0,0,.27-5.4,84.17,84.17,0,0,0-13.19,13.82l-.24.22c-37.36-15-70.23-16-98.05-9.28-6.09-6.11-9.06-1.64-22.91-32.07-.94-1.83.72,1.81,0,0-2.28-5.9,1.39,7.87,0,0-23.28,18.37-53.92,39.19-68.63,53.89-.18.59,17.16-4.9,0,0-6,1.72-5.6,5.28-6.51,37.5-.22,2.44,0,5.18-.22,7.38-11.75,15-19.75,27.64-22.78,34.21-15.19,26.18-31.93,67-48.15,131.55A334.82,334.82,0,0,1,75.2,398.36C61.71,432.63,48.67,486.44,46.07,569.3A482.08,482.08,0,0,1,58.6,518.64,473,473,0,0,0,93.33,719.71c9.33,22.82,24.76,57.46,51,95.4C226.9,902,343.31,956,472.21,956,606.79,956,727.64,897.13,810.67,803.64Z" style="fill:url(#linear-gradient-3)"/><path d="M711.1,866.71c162.87-18.86,235-186.7,142.38-190C769.85,674,634,875.61,711.1,866.71Z" style="fill:url(#linear-gradient-4)"/><path d="M865.21,642.42C977.26,577.21,948,436.34,948,436.34s-43.25,50.24-72.62,130.32C846.4,646,797.84,681.81,865.21,642.42Z" style="fill:url(#linear-gradient-5)"/><path d="M509.47,950.06C665.7,999.91,800,876.84,717.21,835.74,642,798.68,435.32,926.49,509.47,950.06Z" style="fill:url(#linear-gradient-6)"/><path d="M638.58,21.42l.53-.57A1.7,1.7,0,0,0,638.58,21.42ZM876.85,702.23c3.8-5.36,8.94-22.53,13.48-30.21,27.58-44.52,27.78-80,27.78-80.84,16.66-83.22,15.15-117.2,4.9-180-8.25-50.6-44.32-123.09-75.57-158-32.2-36-9.51-24.25-40.69-50.52-27.33-30.29-53.82-60.29-68.25-72.36C634.22,43.09,636.57,24.58,638.58,21.42c-.34.37-.84.92-1.47,1.64C635.87,18.14,635,14,635,14s-57,57-69,152c-7.83,62,15.38,126.68,49,168a381.62,381.62,0,0,0,59,58h0c25.4,36.48,39.38,81.49,39.38,129.91,0,121.24-98.34,219.53-219.65,219.53a220.14,220.14,0,0,1-49.13-5.52c-57.24-10.92-90.3-39.8-106.78-59.41-9.45-11.23-13.46-19.42-13.46-19.42,51.28,18.37,108,14.53,142.47-4.52,34.75-19.26,55.77-33.55,72.84-27.92,16.82,5.61,30.21-10.67,18.2-27.54-11.77-16.85-42.4-41-87.88-34.29-34.79,5.07-66.66,29.76-112.24,5.84a97.34,97.34,0,0,1-8.55-5c-3-1.77,9.77,2.69,6.79.68-8.87-4.32-24.57-13.73-28.64-17.07-.68-.56,6.88,2.16,6.2,1.6-42.62-31.45-37.3-52.69-36-66,1.07-10.66,8.81-24.32,21.86-29.86,6.3,3.08,10.23,5.43,10.23,5.43s-2.69-4.92-4.14-7.51c.51-.19,1-.15,1.5-.34,5.16,2.23,16.58,8,22.59,11.57,7.83,4.95,10.32,9.36,10.32,9.36s2.06-1,.54-5.33c-.56-1.77-2.93-7.39-10.68-13.07h.48a91.65,91.65,0,0,1,13.13,8.17c2.19-7.12,6.12-14.56,5.25-27.86-.53-9.35-.28-11.78-2.12-15.39-1.65-3.1.92-4.31,3.78-1.09a29.73,29.73,0,0,0-2.44-7.34v-.24c3.57-11.14,75.53-40.12,80.77-43.51a70.24,70.24,0,0,0,21.17-20.63c4-5.72,7-13.73,7.75-25.89.25-5.48-1.44-9.82-20.5-14-11.44-2.49-29.14-4.91-56.43-7.47-19.9-1.76-31.58-14.68-38.21-26.6-1.21-2.57-2.45-4.9-3.68-7.22a53.41,53.41,0,0,1-2.83-8.36,158.47,158.47,0,0,1,61.28-76.06c1.6-1.31-6.4.33-4.8-1,1.87-1.52,14.06-5.93,16.37-6.92,2.81-1.19-12-6.84-25.16-5.47-13.36,1.35-16.19,2.78-23.32,5.49,3-2.64,12.37-6.1,10.16-6.08-14.4,2-32.3,9.48-47.6,18a9.72,9.72,0,0,1,.92-4.31c-7.13,2.71-24.64,13.67-29.73,23a39.79,39.79,0,0,0,.29-5.35,88.55,88.55,0,0,0-14.6,13.7l-.27.22C258.14,196,221.75,195,191,201.72c-6.74-6.06-17.57-15.23-32.89-45.4-1-1.82-1.6,3.75-2.4,2-6-13.81-9.55-36.44-9-52,0,0-12.32,5.61-22.51,29.06-1.89,4.21-3.11,6.54-4.32,8.87-.56.68,1.27-7.7,1-7.24-1.77,3-6.36,7.19-8.37,12.62-1.38,4-3.32,6.27-4.56,11.29l-.29.46c-.1-1.48.37-6.08,0-5.14A235.4,235.4,0,0,0,95.34,186c-5.49,18-11.88,42.61-12.89,74.57-.24,2.42,0,5.14-.25,7.32-13,14.83-21.86,27.39-25.2,33.91-16.81,26-35.33,66.44-53.29,130.46a319.35,319.35,0,0,1,28.54-50C17.32,416.25,2.89,469.62,0,551.8a436.92,436.92,0,0,1,13.87-50.24C11.29,556.36,17.68,624.3,52.32,701c20.57,45,67.92,136.6,183.62,208h0s39.36,29.3,107,51.26c5,1.81,10.06,3.6,15.23,5.33q-2.43-1-4.71-2A484.9,484.9,0,0,0,492.27,984c175.18.15,226.85-70.2,226.85-70.2l-.51.38q3.71-3.49,7.14-7.26c-27.64,26.08-90.75,27.84-114.3,26,40.22-11.81,66.69-21.81,118.17-41.52q9-3.36,18.48-7.64l2-.94c1.25-.58,2.49-1.13,3.75-1.74a349.3,349.3,0,0,0,70.26-44c51.7-41.3,63-81.56,68.83-108.1-.82,2.54-3.37,8.47-5.17,12.32-13.31,28.48-42.84,46-74.91,61a689.05,689.05,0,0,0,42.38-62.44C865.77,729.39,869,713.15,876.85,702.23Z" style="fill:url(#radial-gradient-2)"/><path d="M813.92,801c21.08-23.24,40-49.82,54.35-80,36.9-77.58,94-206.58,49-341.31C881.77,273.22,833,215,771.11,158.12,670.56,65.76,642.48,24.52,642.48,0c0,0-116.09,129.41-65.74,264.38s153.46,130,221.68,270.87c80.27,165.74-64.95,346.61-185,397.24,7.35-1.63,267-60.38,280.61-208.88C893.68,726.34,887.83,767.41,813.92,801Z" style="fill:url(#linear-gradient-7)"/><path d="M477.59,319.37c.39-8.77-4.16-14.66-76.68-21.46-29.84-2.76-41.26-30.33-44.75-41.94-10.61,27.56-15,56.49-12.64,91.48,1.61,22.92,17,47.52,24.37,62,0,0,1.64-2.13,2.39-2.91,13.86-14.43,71.94-36.42,77.39-39.54C453.69,363.16,476.58,346.44,477.59,319.37Z" style="fill:url(#linear-gradient-8)"/><path d="M477.59,319.37c.39-8.77-4.16-14.66-76.68-21.46-29.84-2.76-41.26-30.33-44.75-41.94-10.61,27.56-15,56.49-12.64,91.48,1.61,22.92,17,47.52,24.37,62,0,0,1.64-2.13,2.39-2.91,13.86-14.43,71.94-36.42,77.39-39.54C453.69,363.16,476.58,346.44,477.59,319.37Z" style="opacity:0.5;isolation:isolate;fill:url(#radial-gradient-3)"/><path d="M158.31,156.47c-1-1.82-1.6,3.75-2.4,2-6-13.81-9.58-36.2-8.72-52,0,0-12.32,5.61-22.51,29.06-1.89,4.21-3.11,6.54-4.32,8.86-.56.68,1.27-7.7,1-7.24-1.77,3-6.36,7.19-8.35,12.38-1.65,4.24-3.35,6.52-4.61,11.77-.39,1.43.39-6.32,0-5.38C84.72,201.68,80.19,271,82.69,268,133.17,214.14,191,201.36,191,201.36c-6.15-4.53-19.53-17.63-32.7-44.89Z" style="fill:url(#linear-gradient-9)"/><path d="M349.84,720.1c-69.72-29.77-149-71.75-146-167.14C207.92,427.35,321,452.18,321,452.18c-4.27,1-15.68,9.16-19.72,17.82-4.27,10.83-12.07,35.28,11.55,60.9,37.09,40.19-76.2,95.36,98.66,199.57,4.41,2.4-41-1.43-61.64-10.36Z" style="fill:url(#linear-gradient-10)"/><path d="M325.07,657.5c49.44,17.21,107,14.19,141.52-4.86,23.09-12.85,52.7-33.43,70.92-28.35-15.78-6.24-27.73-9.15-42.1-9.86-2.45,0-5.38,0-8-.32a136,136,0,0,0-15.76.86c-8.9.82-18.77,6.43-27.74,5.53-.48,0,8.7-3.77,8-3.61-4.75,1-9.92,1.21-15.37,1.88-3.47.39-6.45.82-9.89,1-103,8.73-190-55.81-190-55.81-7.41,25,33.17,74.3,88.52,93.57Z" style="opacity:0.5;isolation:isolate;fill:url(#linear-gradient-11)"/><path d="M813.74,801.65c104.16-102.27,156.86-226.58,134.58-366,0,0,8.9,71.5-24.85,144.63,16.21-71.39,18.1-160.11-25-252C841,205.64,746.45,141.11,710.35,114.19,655.66,73.4,633,31.87,632.57,23.3c-16.34,33.48-65.77,148.2-5.31,247,56.64,92.56,145.86,120,208.33,205C950.67,631.67,813.74,801.65,813.74,801.65Z" style="fill:url(#linear-gradient-12)"/><path d="M798.81,535.55C762.41,460.35,717,427.55,674,392c5,7,6.23,9.47,9,14,37.83,40.32,93.61,138.66,53.11,262.11C659.88,900.48,355,791.06,323,760.32,335.93,894.81,561,959.16,707.6,872,791,793,858.47,658.79,798.81,535.55Z" style="fill:url(#linear-gradient-13)"/></g></g></g></g></svg>
diff --git a/devtools/client/application/test/browser/resources/manifest/load-fail.html b/devtools/client/application/test/browser/resources/manifest/load-fail.html
new file mode 100644
index 0000000000..180c42a7b5
--- /dev/null
+++ b/devtools/client/application/test/browser/resources/manifest/load-fail.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<head>
+ <meta charset="utf-8">
+ <title>Manifest 404 not found</title>
+ <link rel="manifest" href='nowhere.json'>
+</head>
+<body>
+<h1>Manifest error 404 not found</h1>
+</body>
diff --git a/devtools/client/application/test/browser/resources/manifest/load-no-manifest.html b/devtools/client/application/test/browser/resources/manifest/load-no-manifest.html
new file mode 100644
index 0000000000..aeabc8a0cb
--- /dev/null
+++ b/devtools/client/application/test/browser/resources/manifest/load-no-manifest.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<head>
+ <meta charset="utf-8">
+ <title>No manifest link</title>
+</head>
+<body>
+<h1>No manifest <code>link</code> tag.</h1>
+</body>
diff --git a/devtools/client/application/test/browser/resources/manifest/load-ok-icons.html b/devtools/client/application/test/browser/resources/manifest/load-ok-icons.html
new file mode 100644
index 0000000000..539e5d2247
--- /dev/null
+++ b/devtools/client/application/test/browser/resources/manifest/load-ok-icons.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<head>
+ <meta charset="utf-8">
+ <title>Manifest successful load (with icons)</title>
+ <link rel="manifest" href='data:application/manifest+json,{"name": "Foo", "background_color": "%23ff0000", "icons": [{ "sizes": "128x128", "src": "http://example.com/browser/devtools/client/application/test/browser/resources/manifest/icon.svg", "type": "image/svg"}]}'>
+</head>
+<body>
+<h1>Manifest OK (with icons)</h1>
+</body>
diff --git a/devtools/client/application/test/browser/resources/manifest/load-ok-json-error.html b/devtools/client/application/test/browser/resources/manifest/load-ok-json-error.html
new file mode 100644
index 0000000000..95ad22b609
--- /dev/null
+++ b/devtools/client/application/test/browser/resources/manifest/load-ok-json-error.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<head>
+ <meta charset="utf-8">
+ <title>Manifest successful load with a warning</title>
+ <link rel="manifest" href='data:application/manifest+json,{"name": "Foo}'>
+</head>
+<body>
+<h1>Manifest OK with validation errors</h1>
+<p>The manifest has invalid JSON</p>
+</body>
diff --git a/devtools/client/application/test/browser/resources/manifest/load-ok-manifest-link.html b/devtools/client/application/test/browser/resources/manifest/load-ok-manifest-link.html
new file mode 100644
index 0000000000..f336f409e3
--- /dev/null
+++ b/devtools/client/application/test/browser/resources/manifest/load-ok-manifest-link.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<head>
+ <meta charset="utf-8">
+ <title>Successful load for a linked manifest</title>
+ <link rel="manifest" href='manifest.json'>
+</head>
+<body>
+<h1>Manifest OK (linked manifest)</h1>
+</body>
diff --git a/devtools/client/application/test/browser/resources/manifest/load-ok-warnings.html b/devtools/client/application/test/browser/resources/manifest/load-ok-warnings.html
new file mode 100644
index 0000000000..467d6c3e70
--- /dev/null
+++ b/devtools/client/application/test/browser/resources/manifest/load-ok-warnings.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<head>
+ <meta charset="utf-8">
+ <title>Manifest successful load with a warning</title>
+ <link rel="manifest" href='data:application/manifest+json,{"name": "Foo", "background_color": 42}'>
+</head>
+<body>
+<h1>Manifest OK with validation warnings</h1>
+<p><code>background_color</code> does not contain a valid CSS color.</p>
+</body>
diff --git a/devtools/client/application/test/browser/resources/manifest/load-ok.html b/devtools/client/application/test/browser/resources/manifest/load-ok.html
new file mode 100644
index 0000000000..1e6f5de59e
--- /dev/null
+++ b/devtools/client/application/test/browser/resources/manifest/load-ok.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<head>
+ <meta charset="utf-8">
+ <title>Manifest successful load</title>
+ <link rel="manifest" href='data:application/manifest+json,{"name": "Foo", "background_color": "%23ff0000"}'>
+</head>
+<body>
+<h1>Manifest OK</h1>
+</body>
diff --git a/devtools/client/application/test/browser/resources/manifest/manifest.json b/devtools/client/application/test/browser/resources/manifest/manifest.json
new file mode 100644
index 0000000000..0bc7bb50a3
--- /dev/null
+++ b/devtools/client/application/test/browser/resources/manifest/manifest.json
@@ -0,0 +1,3 @@
+{
+ "name": "Foo"
+}
diff --git a/devtools/client/application/test/browser/resources/service-workers/controlled-install-sw.js b/devtools/client/application/test/browser/resources/service-workers/controlled-install-sw.js
new file mode 100644
index 0000000000..d0a70a5312
--- /dev/null
+++ b/devtools/client/application/test/browser/resources/service-workers/controlled-install-sw.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Copied from shared-head.js
+function waitUntil(predicate, interval = 10) {
+ if (predicate()) {
+ return Promise.resolve(true);
+ }
+ return new Promise(resolve => {
+ setTimeout(function () {
+ waitUntil(predicate, interval).then(() => resolve(true));
+ }, interval);
+ });
+}
+
+// this flag will be flipped externally from controlled-install.html
+// by sending a message event to the worker
+let canInstall = false;
+self.addEventListener("message", event => {
+ if (event.data === "install-service-worker") {
+ canInstall = true;
+ }
+});
+
+self.addEventListener("install", event => {
+ event.waitUntil(waitUntil(() => canInstall));
+});
diff --git a/devtools/client/application/test/browser/resources/service-workers/controlled-install.html b/devtools/client/application/test/browser/resources/service-workers/controlled-install.html
new file mode 100644
index 0000000000..300ee1fde7
--- /dev/null
+++ b/devtools/client/application/test/browser/resources/service-workers/controlled-install.html
@@ -0,0 +1,27 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Service worker test</title>
+</head>
+<body>
+<script type="text/javascript">
+"use strict";
+
+let registration;
+
+window.registerServiceWorker = async function() {
+ registration = await navigator.serviceWorker.register(
+ "controlled-install-sw.js"
+ );
+ window.sw = registration;
+};
+
+window.installServiceWorker = function() {
+ registration.installing.postMessage("install-service-worker");
+};
+</script>
+</body>
+</html>
diff --git a/devtools/client/application/test/browser/resources/service-workers/debug-sw.js b/devtools/client/application/test/browser/resources/service-workers/debug-sw.js
new file mode 100644
index 0000000000..31f0b1bdd2
--- /dev/null
+++ b/devtools/client/application/test/browser/resources/service-workers/debug-sw.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+self.addEventListener("activate", event => {
+ event.waitUntil(self.clients.claim());
+});
+
+self.onfetch = function (event) {
+ const url = event.request.url;
+
+ const response = url.endsWith("test")
+ ? new Response("lorem ipsum", { statusText: "OK" })
+ : fetch(event.request);
+
+ event.respondWith(response);
+};
diff --git a/devtools/client/application/test/browser/resources/service-workers/debug.html b/devtools/client/application/test/browser/resources/service-workers/debug.html
new file mode 100644
index 0000000000..f0f16858fd
--- /dev/null
+++ b/devtools/client/application/test/browser/resources/service-workers/debug.html
@@ -0,0 +1,25 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Service worker test</title>
+</head>
+<body>
+<script type="text/javascript">
+"use strict";
+window.sw = navigator.serviceWorker.register("debug-sw.js");
+
+/* exported fetchFromWorker */
+async function fetchFromWorker() {
+ const response = await fetch("test");
+ const text = await response.text();
+ console.log(`Response from worker: ${text}`);
+}
+</script>
+
+<p>This page has a <code>fetchFromWorker()</code> function.</p>
+</body>
+</html>
diff --git a/devtools/client/application/test/browser/resources/service-workers/dynamic-registration.html b/devtools/client/application/test/browser/resources/service-workers/dynamic-registration.html
new file mode 100644
index 0000000000..def300da65
--- /dev/null
+++ b/devtools/client/application/test/browser/resources/service-workers/dynamic-registration.html
@@ -0,0 +1,19 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Service worker test</title>
+</head>
+<body>
+<script type="text/javascript">
+"use strict";
+
+window.registerServiceWorker = function() {
+ window.sw = navigator.serviceWorker.register("empty-sw.js");
+};
+
+</script>
+</body>
+</html>
diff --git a/devtools/client/application/test/browser/resources/service-workers/empty-sw.js b/devtools/client/application/test/browser/resources/service-workers/empty-sw.js
new file mode 100644
index 0000000000..5d33297056
--- /dev/null
+++ b/devtools/client/application/test/browser/resources/service-workers/empty-sw.js
@@ -0,0 +1,4 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Empty, just test registering.
diff --git a/devtools/client/application/test/browser/resources/service-workers/empty.html b/devtools/client/application/test/browser/resources/service-workers/empty.html
new file mode 100644
index 0000000000..02373ca02e
--- /dev/null
+++ b/devtools/client/application/test/browser/resources/service-workers/empty.html
@@ -0,0 +1,11 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Service worker test (no worker, empty page)</title>
+</head>
+<body></body>
+</html>
diff --git a/devtools/client/application/test/browser/resources/service-workers/scope-page.html b/devtools/client/application/test/browser/resources/service-workers/scope-page.html
new file mode 100644
index 0000000000..eed5bc82ed
--- /dev/null
+++ b/devtools/client/application/test/browser/resources/service-workers/scope-page.html
@@ -0,0 +1,19 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Service worker test</title>
+</head>
+<body>
+<script type="text/javascript">
+"use strict";
+
+window.sw = navigator.serviceWorker.register("empty-sw.js", {
+ scope: "./scope-page.html",
+});
+
+</script>
+</body>
+</html>
diff --git a/devtools/client/application/test/browser/resources/service-workers/simple-unicode.html b/devtools/client/application/test/browser/resources/service-workers/simple-unicode.html
new file mode 100644
index 0000000000..51e17b7fec
--- /dev/null
+++ b/devtools/client/application/test/browser/resources/service-workers/simple-unicode.html
@@ -0,0 +1,15 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Service worker test</title>
+</head>
+<body>
+<script type="text/javascript">
+"use strict";
+window.sw = navigator.serviceWorker.register("empty-sw.js?q=日本");
+</script>
+</body>
+</html>
diff --git a/devtools/client/application/test/browser/resources/service-workers/simple.html b/devtools/client/application/test/browser/resources/service-workers/simple.html
new file mode 100644
index 0000000000..88dc00aff0
--- /dev/null
+++ b/devtools/client/application/test/browser/resources/service-workers/simple.html
@@ -0,0 +1,32 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Service worker test</title>
+</head>
+<body>
+<script type="text/javascript">
+"use strict";
+
+let registration;
+
+const registerServiceWorker = async function() {
+ try {
+ registration = await navigator.serviceWorker.register("empty-sw.js");
+ dump("Empty service worker registered\n");
+ } catch (e) {
+ dump("Empty service worker not registered: " + e + "\n");
+ }
+};
+
+// Helper called from head.js to unregister the service worker.
+window.getRegistration = function() {
+ return registration;
+};
+// Register the service worker.
+registerServiceWorker();
+</script>
+</body>
+</html>