From 02d2cb4854c7d5bba8ac35ddf9cd054d1f36f8e3 Mon Sep 17 00:00:00 2001 From: David Mendes Date: Wed, 21 Oct 2026 18:17:58 +0100 Subject: [PATCH] fix(server): a preview dialog the browser already closed no longer blocks the tab --- apps/server/src/preview/ServerBrowser.test.ts | 41 +++++++++++++++++++ apps/server/src/preview/ServerBrowser.ts | 14 ++++--- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/apps/server/src/preview/ServerBrowser.test.ts b/apps/server/src/preview/ServerBrowser.test.ts index ac4a785f207f3..f3a178ef16052 100644 --- a/apps/server/src/preview/ServerBrowser.test.ts +++ b/apps/server/src/preview/ServerBrowser.test.ts @@ -673,6 +673,47 @@ it.live("reports a pending dialog without evaluating the page and resolves it ex ).pipe(Effect.provide(layer)), ); +it.live("a dialog the browser already closed stops blocking the tab", () => + Effect.scoped( + Effect.gen(function* () { + const { broker, tabId } = yield* ready; + const page = contexts[0]!.page; + yield* broker.invoke({ + scope, + tabId, + operation: "navigate", + input: { url: "http://localhost:5173" }, + }); + page.emit("dialog", { + type: () => "beforeunload", + message: () => "", + defaultValue: () => "", + accept: vi.fn(async () => { + throw new Error("Protocol error (Page.handleJavaScriptDialog): No dialog is showing"); + }), + dismiss: vi.fn(async () => {}), + }); + yield* broker + .invoke({ scope, tabId, operation: "dialog", input: { accept: true } }) + .pipe(Effect.flip); + const status = yield* broker.invoke({ + scope, + tabId, + operation: "status", + input: {}, + }); + expect(status.dialog).toBeNull(); + yield* broker.invoke({ + scope, + tabId, + operation: "navigate", + input: { url: "http://localhost:5173/other" }, + }); + expect(page.goto).toHaveBeenCalledTimes(2); + }), + ).pipe(Effect.provide(layer)), +); + it.live("a tab opened to a file the browser cannot show reports the file to download", () => Effect.scoped( Effect.gen(function* () { diff --git a/apps/server/src/preview/ServerBrowser.ts b/apps/server/src/preview/ServerBrowser.ts index 5b386e5f0797a..52605d352e7f9 100644 --- a/apps/server/src/preview/ServerBrowser.ts +++ b/apps/server/src/preview/ServerBrowser.ts @@ -537,11 +537,15 @@ const make = Effect.gen(function* () { const resolveDialog = async (tab: ServerTab, input: PreviewAutomationDialogInput) => { const dialog = tab.dialog; if (!dialog) throw new Error("No dialog is pending."); - if (input.accept) await dialog.accept(input.promptText); - else await dialog.dismiss(); - if (tab.dialog === dialog) tab.dialog = null; - ServerBrowserPage.invalidateRefs(tab.page); - broadcastControl(tab); + try { + if (input.accept) await dialog.accept(input.promptText); + else await dialog.dismiss(); + } finally { + // A failed answer means the browser already closed the dialog. Keeping it would block the tab. + if (tab.dialog === dialog) tab.dialog = null; + ServerBrowserPage.invalidateRefs(tab.page); + broadcastControl(tab); + } }; const report = (tab: ServerTab, navStatus: PreviewNavStatus) => {