demo · v132
Five real lifecycle traps
Calling showPopover() or showModal() on a popover or dialog inside an inactive document used to silently fail. v132 throws — loud failure beats silent buggy code. Five scenarios where this happens and how to guard.
1. Closing-tab race
User clicks an action just as their page is unloading.
window.addEventListener("beforeunload", () => {
dialog.showModal(); // tab is unloading
});
v132 throws InvalidStateError
2. bfcache restore
Page is in bfcache; an async callback fires before restoration.
// queued setTimeout, page enters bfcache, callback fires
pop.showPopover(); // doc is non-active
v132 throws InvalidStateError
3. Inactive iframe
Iframe was detached from DOM; cached reference still used.
const iframeDoc = iframe.contentDocument;
iframe.remove();
iframeDoc.getElementById("d").showModal();
v132 throws InvalidStateError
4. Top-level navigation
An async chain resolves after the page navigated away.
fetch("/data").then(() => {
// by the time this runs, location.href has changed
dialog.showModal();
});
v132 throws InvalidStateError
5. document.open() reset
Legacy script does document.open() — the original document goes inactive.
document.open(); // wipes the document
oldPop.showPopover(); // its document is gone
v132 throws InvalidStateError
6. how to guard (canonical pattern)
Always check the document is fully active before calling.
function safeShow(modal) {
if (!modal.isConnected) return;
if (modal.ownerDocument
.visibilityState === "hidden") return;
try { modal.showModal(); }
catch (e) { /* eaten */ }
}
always works
tldr: wrap any post-async showPopover / showModal in try/catch and feature-check
document.isConnected. The throw IS the bug surfacing — you weren't getting a working dialog before either, you just couldn't tell.see also
- Inactive document throw — feature index
- Inactive doc throw
- error matrix
- ChromeStatus entry