From 2b4b963f24146033ecbd5813ac0eb82f0f0a32b6 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Thu, 28 May 2026 01:56:43 +0300 Subject: [PATCH] fix: open desktop browser popups in place Loads target=_blank links inside the context panel browser Handles webview popup navigation from Electron --- packages/electron/main.mjs | 23 ++++++++++ .../ui/src/components/layout/ContextPanel.tsx | 44 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/packages/electron/main.mjs b/packages/electron/main.mjs index 0ca0a89f..7d74f682 100644 --- a/packages/electron/main.mjs +++ b/packages/electron/main.mjs @@ -3062,6 +3062,29 @@ contextMenu({ showCopyLink: true, }); +const loadUrlInsideWebContents = (contents, rawUrl) => { + try { + const url = new URL(rawUrl); + if (url.protocol !== 'http:' && url.protocol !== 'https:') return false; + if (contents.isDestroyed()) return false; + void contents.loadURL(url.toString()).catch((error) => { + log.warn('[webview] failed to load popup URL in place:', error); + }); + return true; + } catch { + return false; + } +}; + +app.on('web-contents-created', (_event, contents) => { + if (contents.getType() !== 'webview') return; + + contents.setWindowOpenHandler(({ url }) => { + loadUrlInsideWebContents(contents, url); + return { action: 'deny' }; + }); +}); + // All desktop_* IPC and dialog:open run with full Electron main privileges // (fs access, shell.openPath, spawn, app.relaunch, …). The preload shim is // injected into every webContents in the window, including remote hosts the diff --git a/packages/ui/src/components/layout/ContextPanel.tsx b/packages/ui/src/components/layout/ContextPanel.tsx index 23368bc4..816bbbef 100644 --- a/packages/ui/src/components/layout/ContextPanel.tsx +++ b/packages/ui/src/components/layout/ContextPanel.tsx @@ -452,6 +452,40 @@ const DESKTOP_BROWSER_CANCEL_INSPECT_SCRIPT = `(() => { if (overlay) overlay.remove(); })()`; +const DESKTOP_BROWSER_SAME_WEBVIEW_NAVIGATION_SCRIPT = `(() => { + if (window.__openchamberSameWebviewNavigationInstalled) return; + window.__openchamberSameWebviewNavigationInstalled = true; + + const navigate = (rawUrl) => { + if (typeof rawUrl !== 'string' || rawUrl.length === 0) return false; + try { + const url = new URL(rawUrl, window.location.href); + if (url.protocol !== 'http:' && url.protocol !== 'https:') return false; + window.location.assign(url.href); + return true; + } catch (_error) { + return false; + } + }; + + const originalOpen = window.open.bind(window); + window.open = (url, target, features) => { + if (navigate(url)) return null; + return originalOpen(url, target, features); + }; + + document.addEventListener('click', (event) => { + if (event.defaultPrevented) return; + const target = event.target; + if (!(target instanceof Element)) return; + const anchor = target.closest('a[target="_blank"][href]'); + if (!(anchor instanceof HTMLAnchorElement)) return; + if (!navigate(anchor.href)) return; + event.preventDefault(); + event.stopPropagation(); + }, true); +})()`; + const normalizeBrowserUrl = (value: string): string => { const trimmed = value.trim(); if (!trimmed) return 'about:blank'; @@ -1377,11 +1411,18 @@ const DesktopBrowserPane: React.FC = ({ initialUrl, dir } }; + const installSameWebviewNavigation = () => { + try { + webview.executeJavaScript?.(DESKTOP_BROWSER_SAME_WEBVIEW_NAVIGATION_SCRIPT, true).catch(() => {}); + } catch { /* webview not ready */ } + }; + webview.addEventListener('did-navigate', onNavigate); webview.addEventListener('did-navigate-in-page', onNavigate); webview.addEventListener('did-start-loading', onStartLoading); webview.addEventListener('did-stop-loading', onStopLoading); webview.addEventListener('new-window', onNewWindow); + webview.addEventListener('dom-ready', installSameWebviewNavigation); // Check current loading state imperatively — we may have missed the event try { @@ -1390,6 +1431,7 @@ const DesktopBrowserPane: React.FC = ({ initialUrl, dir syncUrl(); } } catch { /* webview not ready */ } + installSameWebviewNavigation(); return () => { if (loadingTimerRef.current) clearTimeout(loadingTimerRef.current); @@ -1398,6 +1440,7 @@ const DesktopBrowserPane: React.FC = ({ initialUrl, dir webview.removeEventListener('did-start-loading', onStartLoading); webview.removeEventListener('did-stop-loading', onStopLoading); webview.removeEventListener('new-window', onNewWindow); + webview.removeEventListener('dom-ready', installSameWebviewNavigation); }; }, [persistUrl]); @@ -1547,6 +1590,7 @@ const DesktopBrowserPane: React.FC = ({ initialUrl, dir ref={webviewRef} src={normalizeBrowserUrl(initialUrl)} partition="persist:openchamber-browser" + allowpopups style={{ width: '100%', height: '100%', border: 'none' }} /> {(!currentUrl || currentUrl === 'about:blank') && !isLoading ? (