From c916341ec6b4d630e3774b62cbb92de45fcc7ae1 Mon Sep 17 00:00:00 2001 From: Ramavtar Yadav Date: Sat, 14 Nov 2026 01:09:34 +0530 Subject: [PATCH] fix(desktop): same-origin window.open keeps window.opener Featureless same-origin window.open is a Chromium tab, and T3 was opening it as a fresh URL tab, which dropped the opener HTML5 apps need. Co-authored-by: Cursor --- apps/desktop/src/preview/Manager.test.ts | 35 +++++++++++++++++++ apps/desktop/src/preview/Manager.ts | 43 ++++++++++++++++++------ 2 files changed, 67 insertions(+), 11 deletions(-) diff --git a/apps/desktop/src/preview/Manager.test.ts b/apps/desktop/src/preview/Manager.test.ts index 530a1200378b1..6344452ca408d 100644 --- a/apps/desktop/src/preview/Manager.test.ts +++ b/apps/desktop/src/preview/Manager.test.ts @@ -95,6 +95,29 @@ describe("previewWindowOpenAction", () => { } }); + it("keeps the opener for a same-origin window.open that Chromium reports as a tab", () => { + for (const disposition of ["foreground-tab", "background-tab"] as const) { + expect( + PreviewManager.previewWindowOpenAction( + details({ + url: "https://vdid.sbigeneral.in/appremote/#/client/CAMS_VDI-2", + disposition, + }), + "https://vdid.sbigeneral.in/", + ), + ).toBe("popup"); + } + }); + + it("still opens a cross-origin target=_blank link in a preview tab", () => { + expect( + PreviewManager.previewWindowOpenAction( + details({ disposition: "foreground-tab" }), + "https://vdid.sbigeneral.in/", + ), + ).toBe("new-tab"); + }); + it("keeps a form POST with a body on the in-place path", () => { const postBody = { data: [], contentType: "application/x-www-form-urlencoded" }; expect( @@ -103,6 +126,18 @@ describe("previewWindowOpenAction", () => { postBody, }), ).toBe("navigate"); + expect( + PreviewManager.previewWindowOpenAction( + { + ...details({ + url: "https://vdid.sbigeneral.in/submit", + disposition: "foreground-tab", + }), + postBody, + }, + "https://vdid.sbigeneral.in/", + ), + ).toBe("navigate"); }); it("does not hand a window to schemes that cannot be hardened", () => { diff --git a/apps/desktop/src/preview/Manager.ts b/apps/desktop/src/preview/Manager.ts index 49b61c73316f3..5fc469937c132 100644 --- a/apps/desktop/src/preview/Manager.ts +++ b/apps/desktop/src/preview/Manager.ts @@ -484,24 +484,44 @@ const POPUP_WINDOW_OPTIONS = { }, } satisfies Electron.BrowserWindowConstructorOptions; +const isSameOriginHttpUrl = (left: string, right: string): boolean => { + const from = URL.parse(left); + const to = URL.parse(right); + return ( + from !== null && + to !== null && + (from.protocol === "http:" || from.protocol === "https:") && + from.origin === to.origin + ); +}; + /** - * OAuth needs a surviving popup opener. `blob:`, app links (behind the session's `openExternal` - * prompt) and form POSTs stay in place. + * OAuth needs a surviving popup opener. Same-origin `window.open` without features is a Chromium + * foreground-tab; opening it as a T3 tab via URL navigation drops `window.opener`. + * `blob:`, app links (behind the session's `openExternal` prompt) and form POSTs stay in place. */ -export const previewWindowOpenAction = (details: { - readonly url: string; - readonly disposition: Electron.HandlerDetails["disposition"]; - readonly postBody?: Electron.PostBody; -}): "popup" | "new-tab" | "navigate" | "deny" => { +export const previewWindowOpenAction = ( + details: { + readonly url: string; + readonly disposition: Electron.HandlerDetails["disposition"]; + readonly postBody?: Electron.PostBody; + }, + openerUrl?: string, +): "popup" | "new-tab" | "navigate" | "deny" => { const protocol = URL.parse(details.url)?.protocol; // MSAL redirects on a denied blank popup; navigating the tab would destroy that fallback. if (protocol === undefined || IN_PLACE_DENIED_PROTOCOLS.has(protocol)) return "deny"; if (!POPUP_PROTOCOLS.has(protocol)) return "navigate"; if (details.disposition === "new-window") return "popup"; - return !details.postBody && + if ( + !details.postBody && (details.disposition === "foreground-tab" || details.disposition === "background-tab") - ? "new-tab" - : "navigate"; + ) { + return openerUrl !== undefined && isSameOriginHttpUrl(details.url, openerUrl) + ? "popup" + : "new-tab"; + } + return "navigate"; }; const isPreviewRefreshShortcut = (input: Electron.Input, platform: NodeJS.Platform): boolean => @@ -1646,7 +1666,8 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function wc.ipc.on(PAGE_UNHANDLED_SHORTCUT_CHANNEL, pageUnhandledShortcut); detachPasskeys = passkeys.attachGuest(wc); wc.setWindowOpenHandler((details) => { - const action = previewWindowOpenAction(details); + const openerUrl = wc.isDestroyed() ? undefined : wc.getURL(); + const action = previewWindowOpenAction(details, openerUrl); if (action === "popup") { return { action: "allow", overrideBrowserWindowOptions: POPUP_WINDOW_OPTIONS }; }