// A stylesheet can be lost on the way (a poor line, a loaded machine), and a browser never asks again // for one by itself: the page then stays unstyled until a reload, every control a bare browser button. // A module script waits for the document's stylesheets (measured: a sheet held 3 s held the module // too), so by the time this runs each link has either loaded or failed, and no event is left to wait // for. A lost one is asked for again in its own place, so the cascade's order stays as it was built. // // All of these sheets are this site's own: reading the rules of a loaded one is allowed, so a throw // says it failed (measured: Chromium, WebKit). Three tries, then the page stays as it is. A reload here // could loop, and the unstyled page still works. import { log } from "./log"; const RETRY_AFTER_MS = [0, 1000, 3000]; function holdsRules(link: HTMLLinkElement): boolean { try { return (link.sheet?.cssRules.length ?? 0) > 0; } catch { return false; } } function askAgain(link: HTMLLinkElement, attempt: number): void { const wait = RETRY_AFTER_MS[attempt]; if (wait === undefined) { log.error("app.stylesheet_gave_up", "a stylesheet could not be loaded", { href: link.href }); return; } window.setTimeout(() => { const next = link.cloneNode(false) as HTMLLinkElement; next.addEventListener("load", () => log.info("app.stylesheet_recovered", "a lost stylesheet loaded on a new request", { href: next.href, attempt: attempt + 1, }), ); next.addEventListener("error", () => askAgain(next, attempt + 1)); link.replaceWith(next); }, wait); } /** Run once, from a page's entry module. */ export function retryLostStylesheets(): void { for (const link of document.querySelectorAll('link[rel="stylesheet"]')) { if (holdsRules(link)) continue; log.warn("app.stylesheet_lost", "a stylesheet was lost on the way; asking again", { href: link.href, }); askAgain(link, 0); } }