diff --git a/src/web-ui/src/app/scenes/browser/BrowserPanel.tsx b/src/web-ui/src/app/scenes/browser/BrowserPanel.tsx index 30b920a719..b82fc81be0 100644 --- a/src/web-ui/src/app/scenes/browser/BrowserPanel.tsx +++ b/src/web-ui/src/app/scenes/browser/BrowserPanel.tsx @@ -70,6 +70,17 @@ function isWebviewNotFoundError(error: unknown): boolean { return formatUnknownError(error).toLowerCase().includes('webview not found'); } +function isWebviewApiUnavailable(error: unknown): boolean { + const message = formatUnknownError(error); + const lowered = message.toLowerCase(); + return ( + lowered.includes('webview api not available') || + lowered.includes('webview is not supported') || + lowered.includes('webview unavailable') || + lowered.includes('no webview') + ); +} + async function waitForWebviewCreated(handle: BrowserWebviewHandle): Promise { await new Promise((resolve, reject) => { let settled = false; @@ -135,6 +146,7 @@ const BrowserPanel: React.FC = ({ isActive, initialUrl }) => const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [isInspectorActive, setIsInspectorActive] = useState(false); + const [webviewUnavailable, setWebviewUnavailable] = useState(false); const addContext = useContextStore((s) => s.addContext); @@ -239,7 +251,7 @@ const BrowserPanel: React.FC = ({ isActive, initialUrl }) => } setIsInspectorActive(false); - if (!isTauri) { + if (!isTauri || webviewUnavailable) { setIsLoading(false); return; } @@ -278,13 +290,18 @@ const BrowserPanel: React.FC = ({ isActive, initialUrl }) => .catch(() => {}); }, 500); } catch (loadError) { - const message = formatUnknownError(loadError); - log.error('Load browser panel url failed', loadError); - setError(message); + if (isWebviewApiUnavailable(loadError)) { + log.warn('Webview API not available, falling back to iframe'); + setWebviewUnavailable(true); + } else { + const message = formatUnknownError(loadError); + log.error('Load browser panel url failed', loadError); + setError(message); + } } finally { setIsLoading(false); } - }, [isTauri, recreateWebview, shouldShowWebview, syncWebviewBounds]); + }, [isTauri, recreateWebview, shouldShowWebview, syncWebviewBounds, webviewUnavailable]); const queueSync = useCallback(() => { if (resizeFrameRef.current !== null) window.cancelAnimationFrame(resizeFrameRef.current); @@ -296,7 +313,7 @@ const BrowserPanel: React.FC = ({ isActive, initialUrl }) => // Activate / deactivate webview based on shouldShowWebview useEffect(() => { - if (!isTauri) return; + if (!isTauri || webviewUnavailable) return; if (shouldShowWebview) { if (!webviewRef.current) { @@ -565,7 +582,7 @@ const BrowserPanel: React.FC = ({ isActive, initialUrl }) => ) : null}
- {!isTauri ? ( + {!isTauri || webviewUnavailable ? (