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