/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ let bc = new BroadcastChannel("inter-sw-postmessage"); let myId = /\/sw-(.)+$/.exec(registration.scope)[1]; // If we are being imported by the generated script from // `sw_always_updating_inter_sw_postmessage.sjs`, there will be a "version" // global and it starts counting from 1. let myVersion = "version" in globalThis ? globalThis.version : 0; let myFullId = `${myId}#${myVersion}`; onactivate = function () { bc.postMessage(`${myId}:version-activated:${myVersion}`); }; function extractId(urlStr) { if (!urlStr) { return urlStr; } const qIndex = urlStr.indexOf("?"); if (qIndex >= 0) { return urlStr.substring(qIndex + 1); } if (urlStr.endsWith("/empty_with_utils.html")) { return "helper"; } return urlStr; } function describeSource(source) { // Note that WindowProxy is impossible here, so we don't check it. if (source === null) { return "null"; } else if (source instanceof MessagePort) { return "port"; } else if (source instanceof WindowClient) { return `wc-${extractId(source.url)}`; } else if (source instanceof Client) { return `c-${extractId(source.url)}`; } else if (source instanceof ServiceWorker) { return `sw-${extractId(source.scriptURL)}`; } else { return "unexpected"; } } let lastPostMessageSource = null; globalThis.onmessage = async function handle_message(evt) { console.log(myId, "received postMessage"); lastPostMessageSource = evt.source; bc.postMessage( `${myId}:received-post-message-from:${describeSource(evt.source)}` ); }; /** * Map a target descriptor onto something we can postMessage. Possible options * and the resulting target: * - `last-source`: The `.source` property of the most recent event received via * `globalThis.onmessage`. * - `reg-sw-ID`: The active ServiceWorker found on a registration whose * scriptURL ends with `?ID`. This allows us to distinguish between multiple * (non-self-updating) ServiceWorkers on the same registration because each SW * can be given a distinct script path via the `?ID` suffix. But it does not * work for self-updating ServiceWorkers where the only difference is the * version identifier embedded in the script itself. */ async function resolveTarget(descriptor) { if (descriptor === "last-source") { return lastPostMessageSource; } else if (descriptor.startsWith("reg-")) { const registrations = await navigator.serviceWorker.getRegistrations(); let filterFunc; if (descriptor.startsWith("reg-sw-")) { const descriptorId = /^reg-sw-(.+)$/.exec(descriptor)[1]; console.log( "Looking for registration with id", descriptorId, "across", registrations.length, "registrations" ); filterFunc = sw => { if (sw) { console.log("checking SW", sw.scriptURL); } return extractId(sw?.scriptURL) === descriptorId; }; } else { throw new Error(`Target selector '${descriptor}' not understood`); } for (const reg of registrations) { console.log("Reg scriptURL", reg.active?.scriptURL); if (filterFunc(reg.active)) { return reg.active; } else if (filterFunc(reg.waiting)) { return reg.waiting; } else if (filterFunc(reg.installing)) { return reg.installing; } } throw new Error("No registration matches found!"); } throw new Error(`Target selector '${descriptor}' not understood`); } /** * Map a registration descriptor onto a registration. Options: * - `scope-ID`: The registration with a scope ending with `/sw-ID`. */ async function resolveRegistration(descriptor) { if (descriptor.startsWith("sw-")) { const registrations = await navigator.serviceWorker.getRegistrations(); const scopeSuffix = `/${descriptor}`; for (const reg of registrations) { if (reg.scope.endsWith(scopeSuffix)) { return reg; } } throw new Error("No registration matches found!"); } throw new Error(`Registration selector '${descriptor}' not understood`); } bc.onmessage = async function handle_bc(evt) { // Split the message into colon-delimited commands of the form: // :: if (typeof evt?.data !== "string") { return; } const pieces = evt?.data?.split(":"); if ( !pieces || pieces.length < 2 || (pieces[0] !== myId && pieces[0] !== myFullId) ) { return; } const cmd = pieces[1]; try { if (cmd === "post-message-to") { const target = await resolveTarget(pieces[2]); target.postMessage("yo!"); } else if (cmd === "update-reg") { const reg = await resolveRegistration(pieces[2]); reg.update(); } else if (cmd === "install-reg") { const installId = pieces[2]; const scope = `sw-${installId}`; const script = `sw_inter_sw_postmessage.js?${installId}`; await navigator.serviceWorker.register(script, { scope, }); bc.postMessage(`${myId}:registered:${installId}`); } else if (cmd === "workerref-hang") { const topic = pieces[2]; globalThis.WorkerTestUtils.holdStrongWorkerRefUntilMainThreadObserverNotified( topic ); bc.postMessage(`${myId}:workerref-hung:${topic}`); } else if (cmd === "block") { const topic = pieces[2]; globalThis.WorkerTestUtils.blockUntilMainThreadObserverNotified( topic, // This callback is invoked once the observer has been registered. () => { bc.postMessage(`${myId}:blocking:${topic}`); } ); } else if (cmd === "notify-observer") { const topic = pieces[2]; globalThis.WorkerTestUtils.notifyObserverOnMainThread(topic); bc.postMessage(`${myId}:notified-observer:${topic}`); } } catch (ex) { console.error(ex); bc.postMessage({ error: ex + "", myId, processing: evt?.data, }); } };