A lost stylesheet left the page unstyled until a reload, and every e2e step that finds elements by testid still passed on it. Only axe noticed: the one-off target-size failure on eight scattered controls. Losing the main stylesheet on purpose gives those same eight.
51 lines
2.0 KiB
TypeScript
51 lines
2.0 KiB
TypeScript
// 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<HTMLLinkElement>('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);
|
|
}
|
|
}
|