helper_service_workers_navigation.js (3804B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 /* import-globals-from head.js */ 7 8 /* exported setupServiceWorkerNavigationTest */ 9 async function setupServiceWorkerNavigationTest() { 10 // Disable the preloaded process as it creates processes intermittently 11 // which forces the emission of RDP requests we aren't correctly waiting for. 12 await pushPref("dom.ipc.processPrelaunch.enabled", false); 13 } 14 15 /* exported watchServiceWorkerTargets */ 16 async function watchServiceWorkerTargets(tab) { 17 info("Create a target list for a tab target"); 18 const commands = await CommandsFactory.forTab(tab); 19 const targetCommand = commands.targetCommand; 20 21 // Enable Service Worker listening. 22 targetCommand.listenForServiceWorkers = true; 23 await targetCommand.startListening(); 24 25 // Setup onAvailable & onDestroyed callbacks so that we can check how many 26 // times they are called and with which targetFront. 27 const hooks = { 28 availableCount: 0, 29 destroyedCount: 0, 30 targets: [], 31 }; 32 33 const onAvailable = async ({ targetFront }) => { 34 info(` + Service worker target available for ${targetFront.url}\n`); 35 hooks.availableCount++; 36 hooks.targets.push(targetFront); 37 }; 38 39 const onDestroyed = ({ targetFront }) => { 40 info(` - Service worker target destroy for ${targetFront.url}\n`); 41 hooks.destroyedCount++; 42 hooks.targets.splice(hooks.targets.indexOf(targetFront), 1); 43 }; 44 45 await targetCommand.watchTargets({ 46 types: [targetCommand.TYPES.SERVICE_WORKER], 47 onAvailable, 48 onDestroyed, 49 }); 50 51 return { hooks, commands, targetCommand }; 52 } 53 54 /** 55 * Wait until the expected URL is loaded and win.registration has resolved. 56 */ 57 /* exported waitForRegistrationReady */ 58 async function waitForRegistrationReady(tab, expectedPageUrl, workerUrl) { 59 await asyncWaitUntil(() => 60 SpecialPowers.spawn( 61 tab.linkedBrowser, 62 [expectedPageUrl], 63 async function (_url) { 64 try { 65 const win = content.wrappedJSObject; 66 const isExpectedUrl = win.location.href === _url; 67 const hasRegistration = !!(await win.registrationPromise); 68 return isExpectedUrl && hasRegistration; 69 } catch (e) { 70 return false; 71 } 72 } 73 ) 74 ); 75 // On debug builds, the registration may not be yet ready in the parent process 76 // so we also need to ensure it is ready. 77 const swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService( 78 Ci.nsIServiceWorkerManager 79 ); 80 await waitFor(() => { 81 // Unfortunately we can't use swm.getRegistrationByPrincipal, as it requires a "scope", which doesn't seem to be the worker URL. 82 const registrations = swm.getAllRegistrations(); 83 for (let i = 0; i < registrations.length; i++) { 84 const info = registrations.queryElementAt( 85 i, 86 Ci.nsIServiceWorkerRegistrationInfo 87 ); 88 // Lookup for an exact URL match. 89 if (info.scriptSpec === workerUrl) { 90 return true; 91 } 92 } 93 return false; 94 }); 95 } 96 97 /** 98 * Assert helper for the `hooks` object, updated by the onAvailable and 99 * onDestroyed callbacks. Assert that the callbacks have been called the 100 * expected number of times, with the expected targets. 101 */ 102 /* exported checkHooks */ 103 async function checkHooks(hooks, { available, destroyed, targets }) { 104 await waitUntil( 105 () => hooks.availableCount == available && hooks.destroyedCount == destroyed 106 ); 107 is(hooks.availableCount, available, "onAvailable was called as expected"); 108 is(hooks.destroyedCount, destroyed, "onDestroyed was called as expected"); 109 110 is(hooks.targets.length, targets.length, "Expected number of targets"); 111 targets.forEach((url, i) => { 112 is(hooks.targets[i].url, url, `SW target ${i} has the expected url`); 113 }); 114 }