summaryrefslogtreecommitdiffstats
path: root/devtools/shared/commands/target/tests/browser_target_command_service_workers_navigation.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/shared/commands/target/tests/browser_target_command_service_workers_navigation.js')
-rw-r--r--devtools/shared/commands/target/tests/browser_target_command_service_workers_navigation.js358
1 files changed, 358 insertions, 0 deletions
diff --git a/devtools/shared/commands/target/tests/browser_target_command_service_workers_navigation.js b/devtools/shared/commands/target/tests/browser_target_command_service_workers_navigation.js
new file mode 100644
index 0000000000..7bf6c856c2
--- /dev/null
+++ b/devtools/shared/commands/target/tests/browser_target_command_service_workers_navigation.js
@@ -0,0 +1,358 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the TargetCommand API for service workers when navigating in content tabs.
+// When the top level target navigates, we manually call onTargetAvailable for
+// service workers which now match the page domain. We assert that the callbacks
+// will be called the expected number of times here.
+
+const COM_PAGE_URL = URL_ROOT_SSL + "test_sw_page.html";
+const COM_WORKER_URL = URL_ROOT_SSL + "test_sw_page_worker.js";
+const ORG_PAGE_URL = URL_ROOT_ORG_SSL + "test_sw_page.html";
+const ORG_WORKER_URL = URL_ROOT_ORG_SSL + "test_sw_page_worker.js";
+
+/**
+ * This test will navigate between two pages, both controlled by different
+ * service workers.
+ *
+ * The steps will be:
+ * - navigate to .com page
+ * - create target list
+ * -> onAvailable should be called for the .com worker
+ * - navigate to .org page
+ * -> onAvailable should be called for the .org worker
+ * - reload .org page
+ * -> nothing should happen
+ * - unregister .org worker
+ * -> onDestroyed should be called for the .org worker
+ * - navigate back to .com page
+ * -> nothing should happen
+ * - unregister .com worker
+ * -> onDestroyed should be called for the .com worker
+ */
+add_task(async function test_NavigationBetweenTwoDomains_NoDestroy() {
+ await setupServiceWorkerNavigationTest();
+
+ const tab = await addTab(COM_PAGE_URL);
+
+ const { hooks, commands, targetCommand } = await watchServiceWorkerTargets(
+ tab
+ );
+
+ // We expect onAvailable to have been called one time, for the only service
+ // worker target available in the test page.
+ await checkHooks(hooks, {
+ available: 1,
+ destroyed: 0,
+ targets: [COM_WORKER_URL],
+ });
+
+ info("Go to .org page, wait for onAvailable to be called");
+ BrowserTestUtils.startLoadingURIString(
+ gBrowser.selectedBrowser,
+ ORG_PAGE_URL
+ );
+ await checkHooks(hooks, {
+ available: 2,
+ destroyed: 0,
+ targets: [COM_WORKER_URL, ORG_WORKER_URL],
+ });
+
+ info("Reload .org page, onAvailable and onDestroyed should not be called");
+ await BrowserTestUtils.reloadTab(gBrowser.selectedTab);
+ await checkHooks(hooks, {
+ available: 2,
+ destroyed: 0,
+ targets: [COM_WORKER_URL, ORG_WORKER_URL],
+ });
+
+ info("Unregister .org service worker and wait until onDestroyed is called.");
+ await unregisterServiceWorker(ORG_WORKER_URL);
+ await checkHooks(hooks, {
+ available: 2,
+ destroyed: 1,
+ targets: [COM_WORKER_URL],
+ });
+
+ info("Go back to .com page");
+ const onBrowserLoaded = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser
+ );
+ BrowserTestUtils.startLoadingURIString(
+ gBrowser.selectedBrowser,
+ COM_PAGE_URL
+ );
+ await onBrowserLoaded;
+ await checkHooks(hooks, {
+ available: 2,
+ destroyed: 1,
+ targets: [COM_WORKER_URL],
+ });
+
+ info("Unregister .com service worker and wait until onDestroyed is called.");
+ await unregisterServiceWorker(COM_WORKER_URL);
+ await checkHooks(hooks, {
+ available: 2,
+ destroyed: 2,
+ targets: [],
+ });
+
+ // Stop listening to avoid worker related requests
+ targetCommand.destroy();
+
+ await commands.waitForRequestsToSettle();
+ await commands.destroy();
+ await removeTab(tab);
+});
+
+/**
+ * In this test we load a service worker in a page prior to starting the
+ * TargetCommand. We start the target list on another page, and then we go back to
+ * the first page. We want to check that we are correctly notified about the
+ * worker that was spawned before TargetCommand.
+ *
+ * Steps:
+ * - navigate to .com page
+ * - navigate to .org page
+ * - create target list
+ * -> onAvailable is called for the .org worker
+ * - unregister .org worker
+ * -> onDestroyed is called for the .org worker
+ * - navigate back to .com page
+ * -> onAvailable is called for the .com worker
+ * - unregister .com worker
+ * -> onDestroyed is called for the .com worker
+ */
+add_task(async function test_NavigationToPageWithExistingWorker() {
+ await setupServiceWorkerNavigationTest();
+
+ const tab = await addTab(COM_PAGE_URL);
+
+ info("Wait until the service worker registration is registered");
+ await waitForRegistrationReady(tab, COM_PAGE_URL, COM_WORKER_URL);
+
+ info("Navigate to another page");
+ let onBrowserLoaded = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser
+ );
+ BrowserTestUtils.startLoadingURIString(
+ gBrowser.selectedBrowser,
+ ORG_PAGE_URL
+ );
+
+ // Avoid TV failures, where target list still starts thinking that the
+ // current domain is .com .
+ info("Wait until we have fully navigated to the .org page");
+ // wait for the browser to be loaded otherwise the task spawned in waitForRegistrationReady
+ // might be destroyed (when it still belongs to the previous content process)
+ await onBrowserLoaded;
+ await waitForRegistrationReady(tab, ORG_PAGE_URL, ORG_WORKER_URL);
+
+ const { hooks, commands, targetCommand } = await watchServiceWorkerTargets(
+ tab
+ );
+
+ // We expect onAvailable to have been called one time, for the only service
+ // worker target available in the test page.
+ await checkHooks(hooks, {
+ available: 1,
+ destroyed: 0,
+ targets: [ORG_WORKER_URL],
+ });
+
+ info("Unregister .org service worker and wait until onDestroyed is called.");
+ await unregisterServiceWorker(ORG_WORKER_URL);
+ await checkHooks(hooks, {
+ available: 1,
+ destroyed: 1,
+ targets: [],
+ });
+
+ info("Go back .com page, wait for onAvailable to be called");
+ onBrowserLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ BrowserTestUtils.startLoadingURIString(
+ gBrowser.selectedBrowser,
+ COM_PAGE_URL
+ );
+ await onBrowserLoaded;
+
+ await checkHooks(hooks, {
+ available: 2,
+ destroyed: 1,
+ targets: [COM_WORKER_URL],
+ });
+
+ info("Unregister .com service worker and wait until onDestroyed is called.");
+ await unregisterServiceWorker(COM_WORKER_URL);
+ await checkHooks(hooks, {
+ available: 2,
+ destroyed: 2,
+ targets: [],
+ });
+
+ // Stop listening to avoid worker related requests
+ targetCommand.destroy();
+
+ await commands.waitForRequestsToSettle();
+ await commands.destroy();
+ await removeTab(tab);
+});
+
+add_task(async function test_NavigationToPageWithExistingStoppedWorker() {
+ await setupServiceWorkerNavigationTest();
+
+ const tab = await addTab(COM_PAGE_URL);
+
+ info("Wait until the service worker registration is registered");
+ await waitForRegistrationReady(tab, COM_PAGE_URL, COM_WORKER_URL);
+
+ await stopServiceWorker(COM_WORKER_URL);
+
+ const { hooks, commands, targetCommand } = await watchServiceWorkerTargets(
+ tab
+ );
+
+ // Let some time to watch target to eventually regress and revive the worker
+ await wait(1000);
+
+ // As the Service Worker doesn't have any active worker... it doesn't report any target.
+ info(
+ "Verify that no SW is reported after it has been stopped and we start watching for service workers"
+ );
+ await checkHooks(hooks, {
+ available: 0,
+ destroyed: 0,
+ targets: [],
+ });
+
+ info("Reload the worker module via the postMessage call");
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ const registration = await content.wrappedJSObject.registrationPromise;
+ // Force loading the worker again, even it has been stopped
+ registration.active.postMessage("");
+ });
+
+ info("Verify that the SW is notified");
+ await checkHooks(hooks, {
+ available: 1,
+ destroyed: 0,
+ targets: [COM_WORKER_URL],
+ });
+
+ await unregisterServiceWorker(COM_WORKER_URL);
+
+ await checkHooks(hooks, {
+ available: 1,
+ destroyed: 1,
+ targets: [],
+ });
+
+ // Stop listening to avoid worker related requests
+ targetCommand.destroy();
+
+ await commands.waitForRequestsToSettle();
+ await commands.destroy();
+ await removeTab(tab);
+});
+
+async function setupServiceWorkerNavigationTest() {
+ // Disable the preloaded process as it creates processes intermittently
+ // which forces the emission of RDP requests we aren't correctly waiting for.
+ await pushPref("dom.ipc.processPrelaunch.enabled", false);
+}
+
+async function watchServiceWorkerTargets(tab) {
+ info("Create a target list for a tab target");
+ const commands = await CommandsFactory.forTab(tab);
+ const targetCommand = commands.targetCommand;
+
+ // Enable Service Worker listening.
+ targetCommand.listenForServiceWorkers = true;
+ await targetCommand.startListening();
+
+ // Setup onAvailable & onDestroyed callbacks so that we can check how many
+ // times they are called and with which targetFront.
+ const hooks = {
+ availableCount: 0,
+ destroyedCount: 0,
+ targets: [],
+ };
+
+ const onAvailable = async ({ targetFront }) => {
+ info(` + Service worker target available for ${targetFront.url}\n`);
+ hooks.availableCount++;
+ hooks.targets.push(targetFront);
+ };
+
+ const onDestroyed = ({ targetFront }) => {
+ info(` - Service worker target destroy for ${targetFront.url}\n`);
+ hooks.destroyedCount++;
+ hooks.targets.splice(hooks.targets.indexOf(targetFront), 1);
+ };
+
+ await targetCommand.watchTargets({
+ types: [targetCommand.TYPES.SERVICE_WORKER],
+ onAvailable,
+ onDestroyed,
+ });
+
+ return { hooks, commands, targetCommand };
+}
+
+/**
+ * Wait until the expected URL is loaded and win.registration has resolved.
+ */
+async function waitForRegistrationReady(tab, expectedPageUrl, workerUrl) {
+ await asyncWaitUntil(() =>
+ SpecialPowers.spawn(tab.linkedBrowser, [expectedPageUrl], function (_url) {
+ try {
+ const win = content.wrappedJSObject;
+ const isExpectedUrl = win.location.href === _url;
+ const hasRegistration = !!win.registrationPromise;
+ return isExpectedUrl && hasRegistration;
+ } catch (e) {
+ return false;
+ }
+ })
+ );
+ // On debug builds, the registration may not be yet ready in the parent process
+ // so we also need to ensure it is ready.
+ const swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
+ Ci.nsIServiceWorkerManager
+ );
+ await waitFor(() => {
+ // Unfortunately we can't use swm.getRegistrationByPrincipal, as it requires a "scope", which doesn't seem to be the worker URL.
+ const registrations = swm.getAllRegistrations();
+ for (let i = 0; i < registrations.length; i++) {
+ const info = registrations.queryElementAt(
+ i,
+ Ci.nsIServiceWorkerRegistrationInfo
+ );
+ // Lookup for an exact URL match.
+ if (info.scriptSpec === workerUrl) {
+ return true;
+ }
+ }
+ return false;
+ });
+}
+
+/**
+ * Assert helper for the `hooks` object, updated by the onAvailable and
+ * onDestroyed callbacks. Assert that the callbacks have been called the
+ * expected number of times, with the expected targets.
+ */
+async function checkHooks(hooks, { available, destroyed, targets }) {
+ await waitUntil(
+ () => hooks.availableCount == available && hooks.destroyedCount == destroyed
+ );
+ is(hooks.availableCount, available, "onAvailable was called as expected");
+ is(hooks.destroyedCount, destroyed, "onDestroyed was called as expected");
+
+ is(hooks.targets.length, targets.length, "Expected number of targets");
+ targets.forEach((url, i) => {
+ is(hooks.targets[i].url, url, `SW target ${i} has the expected url`);
+ });
+}