[eric] browser: Ctrl/Cmd+R reloads the last-interacted browser, else the app

This commit is contained in:
ciregenz
2026-06-25 20:45:18 -07:00
parent 97a859e504
commit ccd0bc3971
6 changed files with 66 additions and 0 deletions
+12
View File
@@ -1959,6 +1959,17 @@ function swallowCloseWindowShortcut(event, input) {
}
}
// Cmd/Ctrl+R: the default menu's Reload accelerator reloads the WHOLE app even when a browser webview is focused (the "Ctrl+R reloads OpenSwarm, not the browser" complaint). preventDefault kills that accelerator (same electron#19279 path as Cmd+W, dispatched against whichever webContents is focused, hence both main window AND guests); the renderer then reloads the last-interacted browser, or the app if none. Shift+R (force reload) is left alone.
function routeReloadShortcut(event, input) {
if (input.type !== 'keyDown') return;
if (!(input.meta || input.control) || input.shift || input.alt) return;
if ((input.key || '').toLowerCase() !== 'r') return;
event.preventDefault();
try {
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send('openswarm:reload-shortcut');
} catch (_) {}
}
app.on('web-contents-created', (_event, contents) => {
// Block Cmd+W from closing the main window, whether the window chrome or one of
// its embedded webviews has focus. OAuth popups (their own 'window' contents,
@@ -1966,6 +1977,7 @@ app.on('web-contents-created', (_event, contents) => {
// still Cmd+W them shut.
if (isCreatingMainWindow || contents.getType() === 'webview') {
contents.on('before-input-event', swallowCloseWindowShortcut);
contents.on('before-input-event', routeReloadShortcut);
}
// Override the user-agent on popup BrowserWindows (i.e. anything created
+7
View File
@@ -108,6 +108,13 @@ contextBridge.exposeInMainWorld('openswarm', {
return () => ipcRenderer.removeListener('webview-new-window', listener);
},
// Cmd/Ctrl+R, intercepted in main (kills the default-menu reload), so the renderer can reload the focused browser instead of the whole app.
onReloadShortcut: (cb) => {
const listener = () => cb();
ipcRenderer.on('openswarm:reload-shortcut', listener);
return () => ipcRenderer.removeListener('openswarm:reload-shortcut', listener);
},
// Deep-link callback: fires when the OS opens the app with an
// openswarm://auth?token=... URL (after Stripe-hosted checkout).
onAuthUrl: (cb) => {
@@ -1,6 +1,8 @@
import React, { useState, useEffect, useRef, useCallback, startTransition, useMemo } from 'react';
import { NavLink, Outlet, useNavigate, useLocation } from 'react-router-dom';
import { openSettingsModal } from '@/shared/state/settingsSlice';
import { getLastInteractedBrowser, setLastInteractedBrowser, clearLastInteractedBrowser } from '@/shared/browserFocus';
import { getWebview } from '@/shared/browserRegistry';
import Box from '@mui/material/Box';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from '@mui/material/ListItemIcon';
@@ -323,6 +325,29 @@ const AppShell: React.FC = () => {
});
}, [openUrlInBrowser]);
// Track the browser card the user last touched. Chrome clicks land on this document; a webview PAGE click can't reach it, so BrowserCard reports those via the app-clicked IPC. Clearing on any non-browser-card click is what makes Ctrl+R fall back to reloading the app.
useEffect(() => {
const onPointerDown = (e: PointerEvent) => {
const card = (e.target as HTMLElement | null)?.closest?.('[data-select-type="browser-card"]') as HTMLElement | null;
if (card) setLastInteractedBrowser(card.getAttribute('data-select-id') || '');
else clearLastInteractedBrowser();
};
document.addEventListener('pointerdown', onPointerDown, true);
return () => document.removeEventListener('pointerdown', onPointerDown, true);
}, []);
// Cmd/Ctrl+R: main neutralizes the default-menu reload (which would always reload the whole app) and hands us the decision. Reload the browser you last interacted with; if that wasn't a live browser, reload the app, exactly as before.
useEffect(() => {
const w = window as any;
if (!w.openswarm?.onReloadShortcut) return;
return w.openswarm.onReloadShortcut(() => {
const id = getLastInteractedBrowser();
const wv = id ? getWebview(id) : undefined;
if (wv) { try { wv.reload(); return; } catch (_e) { /* torn-down webview; fall through to app reload */ } }
window.location.reload();
});
}, []);
useEffect(() => {
try { localStorage.setItem(SIDEBAR_WIDTH_KEY, String(sidebarWidth)); } catch {}
}, [sidebarWidth]);
@@ -46,6 +46,7 @@ import {
setActiveTab as setRegistryActiveTab,
type BrowserWebview,
} from '@/shared/browserRegistry';
import { setLastInteractedBrowser } from '@/shared/browserFocus';
import { useBrowserActivity } from '@/shared/useBrowserActivity';
import { getActionLabel } from '@/shared/browserCommandHandler';
import { resolveInput, isGoogleSearch } from '@/shared/resolveUrl';
@@ -325,6 +326,9 @@ const BrowserCard: React.FC<Props> = ({
},
}),
);
} else if (e?.channel === 'app-clicked') {
// First in-guest mousedown: a page click never reaches the host document, so this IPC is how a webview-content click marks this browser as last-interacted (drives Ctrl+R/zoom/tab targeting).
setLastInteractedBrowser(browserId);
}
};
+17
View File
@@ -0,0 +1,17 @@
// Tracks which browser card the user last interacted with (clicked into its page or its chrome),
// so global shortcuts (Ctrl+R reload, Ctrl +/- zoom, Ctrl+Tab) target THAT browser instead of a
// guess. Module-level and imperative on purpose: shortcut handlers read it on keydown, so no React
// re-render is needed. Cleared the moment the user clicks anything that isn't a browser card.
let lastInteractedBrowserId: string | null = null;
export function setLastInteractedBrowser(browserId: string): void {
lastInteractedBrowserId = browserId;
}
export function clearLastInteractedBrowser(): void {
lastInteractedBrowserId = null;
}
export function getLastInteractedBrowser(): string | null {
return lastInteractedBrowserId;
}
+1
View File
@@ -47,6 +47,7 @@ declare global {
onUpdateDownloaded: (cb: (info: OpenSwarmUpdateInfo) => void) => () => void;
onUpdateError: (cb: (message: string) => void) => () => void;
onWebviewNewWindow: (cb: (url: string, webContentsId: number) => void) => () => void;
onReloadShortcut?: (cb: () => void) => () => void;
openExternal: (url: string) => Promise<void>;
hardReset?: () => Promise<void>;
clearBrowserData?: () => Promise<{ ok: boolean }>;