"use strict"; // This test registers a SW for a scope that will never control a document // and therefore never trigger a "fetch" functional event that would // automatically attempt to update the registration. The overlap of the // PAGE_URI and SCOPE is incidental. checkForUpdate is the only thing that // will trigger an update of the registration and so there is no need to // worry about Schedule Job races to coalesce an update job. const BASE_URI = "http://mochi.test:8888/browser/dom/serviceworkers/test/"; const PAGE_URI = BASE_URI + "empty.html"; const SCOPE = PAGE_URI + "?storage_recovery"; const SW_SCRIPT = BASE_URI + "storage_recovery_worker.sjs"; async function checkForUpdate(browser) { return SpecialPowers.spawn(browser, [SCOPE], async function (uri) { let reg = await content.navigator.serviceWorker.getRegistration(uri); await reg.update(); return !!reg.installing; }); } // Delete all of our chrome-namespace Caches for this origin, leaving any // content-owned caches in place. This is exclusively for simulating loss // of the origin's storage without loss of the registration and without // having to worry that future enhancements to QuotaClients/ServiceWorkerRegistrar // will break this test. If you want to wipe storage for an origin, use // QuotaManager APIs async function wipeStorage(u) { let uri = Services.io.newURI(u); let principal = Services.scriptSecurityManager.createContentPrincipal( uri, {} ); let caches = new CacheStorage("chrome", principal); let list = await caches.keys(); return Promise.all(list.map(c => caches.delete(c))); } add_setup(async function () { await SpecialPowers.pushPrefEnv({ set: [ ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ["dom.serviceWorkers.idle_timeout", 0], ], }); // Configure the server script to not redirect. await fetch(SW_SCRIPT + "?clear-redirect"); let tab = BrowserTestUtils.addTab(gBrowser, PAGE_URI); let browser = gBrowser.getBrowserForTab(tab); await BrowserTestUtils.browserLoaded(browser); await SpecialPowers.spawn( browser, [{ script: SW_SCRIPT, scope: SCOPE }], async function (opts) { let reg = await content.navigator.serviceWorker.register(opts.script, { scope: opts.scope, }); let worker = reg.installing || reg.waiting || reg.active; await new Promise(resolve => { if (worker.state === "activated") { resolve(); return; } worker.addEventListener("statechange", function onStateChange() { if (worker.state === "activated") { worker.removeEventListener("statechange", onStateChange); resolve(); } }); }); } ); BrowserTestUtils.removeTab(tab); }); // Verify that our service worker doesn't update normally. add_task(async function normal_update_check() { let tab = BrowserTestUtils.addTab(gBrowser, PAGE_URI); let browser = gBrowser.getBrowserForTab(tab); await BrowserTestUtils.browserLoaded(browser); let updated = await checkForUpdate(browser); ok(!updated, "normal update check should not trigger an update"); BrowserTestUtils.removeTab(tab); }); // Test what happens when we wipe the service worker scripts // out from under the site before triggering the update. This // should cause an update to occur. add_task(async function wiped_update_check() { // Wipe the backing cache storage, but leave the SW registered. await wipeStorage(PAGE_URI); let tab = BrowserTestUtils.addTab(gBrowser, PAGE_URI); let browser = gBrowser.getBrowserForTab(tab); await BrowserTestUtils.browserLoaded(browser); let updated = await checkForUpdate(browser); ok(updated, "wiping the service worker scripts should trigger an update"); BrowserTestUtils.removeTab(tab); }); // Test what happens when we wipe the service worker scripts // out from under the site before triggering the update. This // should cause an update to occur. add_task(async function wiped_and_failed_update_check() { // Wipe the backing cache storage, but leave the SW registered. await wipeStorage(PAGE_URI); // Configure the service worker script to redirect. This will // prevent the update from completing successfully. await fetch(SW_SCRIPT + "?set-redirect"); let tab = BrowserTestUtils.addTab(gBrowser, PAGE_URI); let browser = gBrowser.getBrowserForTab(tab); await BrowserTestUtils.browserLoaded(browser); // Attempt to update the service worker. This should throw // an error because the script is now redirecting. let updateFailed = false; try { await checkForUpdate(browser); } catch (e) { updateFailed = true; } ok(updateFailed, "redirecting service worker script should fail to update"); // Also, since the existing service worker's scripts are broken // we should also remove the registration completely when the // update fails. let exists = await SpecialPowers.spawn( browser, [SCOPE], async function (uri) { let reg = await content.navigator.serviceWorker.getRegistration(uri); return !!reg; } ); ok( !exists, "registration should be removed after scripts are wiped and update fails" ); // Note, we don't have to clean up the service worker registration // since its effectively been force-removed here. BrowserTestUtils.removeTab(tab); });