fix: 🐛 Gerber demo: survive a stale cross-origin-isolation state
The demo showed "Live demo unavailable in this browser" on fully capable browsers (e.g. Chrome) whenever a page load wasn't cross-origin isolated — a soft SPA navigation, or a visit cached from before the COOP/COEP headers went live. The old reload guard used a sticky sessionStorage flag that, once set during a non-isolated load, never cleared, so it skipped the recovery reload and showed a misleading "unsupported browser" message. - Reload guard now uses a timestamp, not a sticky flag: a stale flag from an earlier visit no longer permanently strands the demo (reload loop still prevented within a 10s debounce). - Non-isolated pages now open the standalone viewer (/gerber-demo/, isolated on its own and always works) in a new tab instead of dead-ending. - The "needs a recent browser" note only shows when provably incapable (isolated but missing SharedArrayBuffer/WebGL2). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8b438b48b2
commit
429ac206f9
1 changed files with 41 additions and 28 deletions
|
|
@ -56,6 +56,7 @@ const caption =
|
|||
<script>
|
||||
const BOOT_SRC = "/gerber-demo/index.html";
|
||||
const RELOAD_KEY = "gerber-demo-coi-reload";
|
||||
const RELOAD_DEBOUNCE_MS = 10000;
|
||||
|
||||
function hasWebGL2(): boolean {
|
||||
try {
|
||||
|
|
@ -65,7 +66,8 @@ const caption =
|
|||
}
|
||||
}
|
||||
|
||||
function launch(root: HTMLElement): void {
|
||||
// Best path: embed the viewer inline (only when this page is cross-origin isolated).
|
||||
function launchInline(root: HTMLElement): void {
|
||||
if (root.dataset.state === "loading" || root.dataset.state === "ready") return;
|
||||
root.dataset.state = "loading";
|
||||
|
||||
|
|
@ -95,54 +97,65 @@ const caption =
|
|||
frame?.appendChild(iframe);
|
||||
}
|
||||
|
||||
function showFallback(root: HTMLElement): void {
|
||||
// Genuinely incapable browser: cross-origin isolated, yet no SharedArrayBuffer
|
||||
// or WebGL2. The only true dead-end — show the poster + the "recent browser"
|
||||
// note. (We can ONLY assert this when isolated; on a non-isolated page
|
||||
// SharedArrayBuffer is always undefined regardless of the browser's support.)
|
||||
function showUnsupported(root: HTMLElement): void {
|
||||
root.dataset.state = "unsupported";
|
||||
const launchEl = root.querySelector<HTMLElement>(".gerber-demo__launch");
|
||||
const fallback = root.querySelector<HTMLElement>(".gerber-demo__fallback");
|
||||
// Keep the poster (it's the <a> background); hide the play affordance, show note.
|
||||
launchEl?.querySelector(".gerber-demo__play")?.setAttribute("hidden", "");
|
||||
launchEl?.querySelector(".gerber-demo__cta")?.setAttribute("hidden", "");
|
||||
launchEl?.querySelector(".gerber-demo__sub")?.setAttribute("hidden", "");
|
||||
if (launchEl) {
|
||||
launchEl.removeAttribute("href"); // not clickable
|
||||
launchEl.removeAttribute("href");
|
||||
launchEl.style.cursor = "default";
|
||||
}
|
||||
const fallback = root.querySelector<HTMLElement>(".gerber-demo__fallback");
|
||||
if (fallback) fallback.hidden = false;
|
||||
}
|
||||
|
||||
function initDemo(): void {
|
||||
const root = document.querySelector<HTMLElement>(".gerber-demo");
|
||||
if (!root) return; // page without the demo
|
||||
const launchEl = root.querySelector<HTMLAnchorElement>(".gerber-demo__launch");
|
||||
|
||||
const isolated = window.crossOriginIsolated === true;
|
||||
|
||||
// Soft SPA navigation lands here NOT cross-origin isolated; force one real
|
||||
// load so the route's COOP/COEP headers apply. Guard against reload loops.
|
||||
if (!isolated) {
|
||||
if (!sessionStorage.getItem(RELOAD_KEY)) {
|
||||
sessionStorage.setItem(RELOAD_KEY, "1");
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
// Already retried and still not isolated → genuinely unsupported (or no
|
||||
// headers, e.g. a preview without vercel.json). Fall back.
|
||||
} else {
|
||||
if (window.crossOriginIsolated === true) {
|
||||
sessionStorage.removeItem(RELOAD_KEY);
|
||||
}
|
||||
|
||||
const supported =
|
||||
isolated && typeof SharedArrayBuffer !== "undefined" && hasWebGL2();
|
||||
|
||||
if (!supported) {
|
||||
showFallback(root);
|
||||
if (typeof SharedArrayBuffer !== "undefined" && hasWebGL2()) {
|
||||
root.dataset.state = "idle";
|
||||
launchEl?.addEventListener("click", (e) => {
|
||||
e.preventDefault(); // intercept the href; embed inline instead
|
||||
launchInline(root);
|
||||
});
|
||||
} else {
|
||||
showUnsupported(root);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const launchEl = root.querySelector<HTMLElement>(".gerber-demo__launch");
|
||||
launchEl?.addEventListener("click", (e) => {
|
||||
e.preventDefault(); // intercept the href; embed inline instead
|
||||
launch(root);
|
||||
});
|
||||
// NOT cross-origin isolated — usually because a soft SPA navigation delivered
|
||||
// this page (the route's COOP/COEP only take effect on a real load). Reload
|
||||
// ONCE to get the isolated version so we can embed inline. Guard with a
|
||||
// TIMESTAMP, not a sticky flag: a stale flag from an earlier visit must not
|
||||
// permanently strand the demo, but two reloads within the debounce window
|
||||
// would be a loop — so we only suppress a reload we *just* did.
|
||||
const last = Number(sessionStorage.getItem(RELOAD_KEY) || "0");
|
||||
if (Date.now() - last > RELOAD_DEBOUNCE_MS) {
|
||||
sessionStorage.setItem(RELOAD_KEY, String(Date.now()));
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
// Reloaded moments ago and STILL not isolated (stale cache, missing headers,
|
||||
// a preview without vercel.json…). Don't dead-end and don't blame the
|
||||
// browser: the standalone viewer page is isolated on its own, so let the
|
||||
// button open it in a new tab — capable browsers get the full viewer there.
|
||||
if (launchEl) {
|
||||
launchEl.target = "_blank";
|
||||
launchEl.rel = "noopener";
|
||||
root.dataset.state = "external";
|
||||
}
|
||||
}
|
||||
|
||||
// Run on first load and on every SPA navigation (ClientRouter).
|
||||
|
|
|
|||
Loading…
Reference in a new issue