diff --git a/emain/emain-tabview.ts b/emain/emain-tabview.ts index 753a53adec..b4746483e0 100644 --- a/emain/emain-tabview.ts +++ b/emain/emain-tabview.ts @@ -319,9 +319,26 @@ export async function getOrCreateWebViewForTab(waveWindowId: string, tabId: stri if (wc == null || wc.isDestroyed() || tabView.webContents == null || tabView.webContents.isDestroyed()) { return { action: "deny" }; } + // OAuth/SSO flows call window.open(url, name, "width=...,height=...") which Electron + // reports as disposition "new-window". They require a real popup that preserves the + // window.opener relationship (postMessage token return) and a valid Cross-Origin- + // Opener-Policy browsing context. Rerouting them into a new web block breaks both + // (results in ERR_BLOCKED_BY_RESPONSE). Allow a genuine popup window for these; it + // shares wc's session so any auth cookies/tokens remain visible to the webview. + // Gate on a non-empty features string: a shift+click on a plain link also reports + // disposition "new-window" but carries no features, and must keep routing to a web block. + if (details.disposition === "new-window" && details.features !== "") { + return { + action: "allow", + overrideBrowserWindowOptions: { width: 600, height: 700, autoHideMenuBar: true }, + }; + } + // Plain link clicks (target=_blank, foreground/background tab) keep existing behavior: + // open in a new Wave web block. tabView.webContents.send("webview-new-window", wc.id, details); return { action: "deny" }; }); + wc.on("did-create-window", (popup) => popup.setMenuBarVisibility(false)); }); tabView.webContents.on("before-input-event", (e, input) => { const waveEvent = adaptFromElectronKeyEvent(input); diff --git a/frontend/app/view/webview/webview.tsx b/frontend/app/view/webview/webview.tsx index f6d98b8f22..554c935827 100644 --- a/frontend/app/view/webview/webview.tsx +++ b/frontend/app/view/webview/webview.tsx @@ -1044,6 +1044,14 @@ const WebView = memo(({ model, onFailLoad, blockRef, initialSrc }: WebViewProps) const failLoadHandler = (e: any) => { if (e.errorCode === -3) { console.warn("Suppressed ERR_ABORTED error", e); + } else if (e.isMainFrame === false) { + // Sub-frame load failures are non-fatal. Web apps routinely load hidden iframes + // that are expected to fail — e.g. OAuth/OIDC silent-auth probes + // (response_mode=web_message, prompt=none), which get blocked by the embedder's + // Cross-Origin-Embedder-Policy and are then handled by the app's auth SDK (it + // falls back to interactive login). A normal browser surfaces these only to the + // SDK, not the user, so don't paint a fatal error over the whole block. + console.warn("Suppressed sub-frame load failure", e.validatedURL, e.errorDescription); } else { const errorMessage = `Failed to load ${e.validatedURL}: ${e.errorDescription}`; console.error(errorMessage);