[eric] browser/apps: drop the iframe fallback for a 'run in the desktop app' message (keeps srcdoc + Windows-crash iframe)

This commit is contained in:
ciregenz
2026-06-29 16:38:03 -07:00
parent 6afbc5b849
commit 8784ca937c
3 changed files with 52 additions and 6 deletions
@@ -0,0 +1,38 @@
import React from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
interface Props {
kind?: 'browser' | 'app';
}
// Shown when a browser/app card is rendered OUTSIDE Electron (the dev URL opened directly in a web browser). The real <webview> only exists in the desktop app; rather than a crippled <iframe> the agent can't drive, tell the user to launch correctly.
const RunInDesktopMessage: React.FC<Props> = ({ kind = 'browser' }) => {
const noun = kind === 'app' ? 'Apps' : 'Browsers';
return (
<Box
sx={{
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 1,
px: 3,
textAlign: 'center',
color: '#888',
userSelect: 'none',
}}
>
<Typography sx={{ fontSize: '0.95rem', fontWeight: 600, color: '#bbb' }}>
Open this in the OpenSwarm desktop app
</Typography>
<Typography sx={{ fontSize: '0.82rem', lineHeight: 1.5, maxWidth: 360 }}>
{noun} run inside the OpenSwarm desktop window, not a regular web browser. It looks like you opened the dev URL directly in a browser; launch OpenSwarm (the Electron window from <code>bash run.sh</code>) and use it there instead.
</Typography>
</Box>
);
};
export default RunInDesktopMessage;
@@ -21,6 +21,7 @@ import AddIcon from '@mui/icons-material/Add';
import LockIcon from '@mui/icons-material/Lock';
import SearchIcon from '@mui/icons-material/Search';
import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined';
import RunInDesktopMessage from '@/app/components/RunInDesktopMessage';
import {
setBrowserCardPosition,
setBrowserCardSize,
@@ -120,7 +121,8 @@ function markWindowsWebviewSurvived(): void {
}
const isWindows = navigator.userAgent.includes('Windows');
const isElectron = navigator.userAgent.includes('Electron') && (!isWindows || windowsWebviewEnabled());
const inElectron = navigator.userAgent.includes('Electron');
const isElectron = inElectron && (!isWindows || windowsWebviewEnabled());
// Keep the openswarm/<ver> product token: Google's sign-in flags a BARE Chrome UA as not-genuine-Chrome and blocks it ("browser may not be secure"), but tolerates a UA carrying a product token. Only the Electron token must go (that one Google hard-blocks).
const chromeUserAgent = navigator.userAgent
@@ -1325,11 +1327,11 @@ const BrowserCard: React.FC<Props> = ({
</Button>
</DialogActions>
</Dialog>
{!isElectron && (
{!isElectron && (inElectron ? (
// inElectron but !isElectron = Windows after the webview crashed (windowsWebviewEnabled() false); keep the iframe as the crash safety net. No sandbox so sites render; the renderer's already isolated by Electron + the main.js XFO/CSP strip.
<Box sx={{ width: '100%', height: '100%', position: 'relative' }}>
<iframe
src={activeUrl}
// No sandbox: a restrictive sandbox blocks some sites from rendering, and our renderer is already isolated by Electron's contextIsolation + sub_frame XFO/CSP frame-ancestors strip in main.js. onLoad/onError add definitive instrumentation so we can tell whether the iframe loaded successfully (with empty body from anti-iframe JS) or genuinely failed (network error, CSP block, etc.).
style={{ width: '100%', height: '100%', border: 'none', pointerEvents: isElementSelectMode ? 'none' : 'auto' }}
title="Browser"
referrerPolicy="no-referrer-when-downgrade"
@@ -1339,7 +1341,9 @@ const BrowserCard: React.FC<Props> = ({
}}
/>
</Box>
)}
) : (
<RunInDesktopMessage kind="browser" />
))}
{/* Camera flash: screenshot */}
{(agentAction === 'screenshot' || lastAction === 'screenshot') && (
+6 -2
View File
@@ -7,8 +7,9 @@ import { useIframeElementSelector } from './useIframeElementSelector';
import { getAuthToken, ensureAuthToken } from '@/shared/config';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { registerViewWebview, unregisterViewWebview, type ViewWebview } from '@/shared/viewWebviewRegistry';
import RunInDesktopMessage from '@/app/components/RunInDesktopMessage';
// In Electron use <webview> to escape iframe restrictions (popups, mic/camera, WebAuthn, cookied fetch); outside Electron fall back to iframe.
// In Electron use <webview> to escape iframe restrictions (popups, mic/camera, WebAuthn, cookied fetch); a srcdoc app still uses an iframe (data:text/html breaks webview same-origin); outside Electron we show a launch-correctly message.
const isElectron = navigator.userAgent.includes('Electron');
// Card previews render small; downscale + JPEG so thumbnails don't bloat the output JSON or every list fetch.
@@ -411,7 +412,8 @@ const ViewPreview = forwardRef<ViewPreviewHandle, Props>(({
...style,
}}
/>
) : (
) : isElectron ? (
// In Electron but not useWebview = a srcdoc app (no serveUrl); the iframe is REQUIRED here (data:text/html breaks webview same-origin), not a fallback. Non-Electron falls through to the launch-correctly message.
<iframe
ref={iframeRef}
// Key only changes on mode switch (URL vs srcdoc); reloadKey updates the src attribute in place to avoid blank-flash on reload.
@@ -429,6 +431,8 @@ const ViewPreview = forwardRef<ViewPreviewHandle, Props>(({
}}
title="App Preview"
/>
) : (
<RunInDesktopMessage kind="app" />
)}
{restoring && (
<Box