From ad75228abeb6cb2b0f8737daa63f76a599f118f9 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Thu, 4 Jun 2026 11:26:50 -0700 Subject: [PATCH] [eric] browser: guard capture-page on churning webviews to dodge the V8 crash --- electron/main.js | 16 ++++++++++++++-- frontend/src/shared/browserRegistry.ts | 17 +++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/electron/main.js b/electron/main.js index 5923a9d3..c5cc70b4 100644 --- a/electron/main.js +++ b/electron/main.js @@ -2206,8 +2206,20 @@ ipcMain.handle('install-update', async () => { }); ipcMain.handle('capture-page', async (event, rect) => { - const image = await event.sender.capturePage(rect || undefined); - return image.toDataURL(); + // Capturing a webContents whose GPU surface is mid-recycle (a webview navigating + // a heavy SPA) can crash the renderer (SharedImage 'non-existent mailbox' -> + // V8 ToLocalChecked). The caller now waits for webviews to settle, but guard + // here too: skip a gone/crashed/loading sender and never encode an empty image, + // returning null so the dashboard keeps its last good preview instead of dying. + try { + const wc = event.sender; + if (!wc || wc.isDestroyed() || wc.isCrashed() || wc.isLoading()) return null; + const image = await wc.capturePage(rect || undefined); + if (!image || image.isEmpty()) return null; + return image.toDataURL(); + } catch { + return null; + } }); ipcMain.handle('open-external', (_event, url) => { diff --git a/frontend/src/shared/browserRegistry.ts b/frontend/src/shared/browserRegistry.ts index 0305989e..8a03e481 100644 --- a/frontend/src/shared/browserRegistry.ts +++ b/frontend/src/shared/browserRegistry.ts @@ -23,6 +23,7 @@ export interface BrowserWebview extends HTMLElement { canGoForward: () => boolean; getURL: () => string; getTitle: () => string; + isLoading: () => boolean; capturePage: (rect?: { x: number; y: number; width: number; height: number }) => Promise; executeJavaScript: (code: string) => Promise; sendInputEvent: (event: any) => void; @@ -64,3 +65,19 @@ export function findBrowserByWebContentsId(wcId: number): string | undefined { } return undefined; } + +// True if ANY registered webview is mid-navigation. Capturing the dashboard +// (which composites live webview pixels) while a webview's GPU surface is being +// recycled crashes the renderer (SharedImage 'non-existent mailbox' -> V8 +// ToLocalChecked), so the thumbnail capture must wait until they've settled. +export function anyWebviewLoading(): boolean { + for (const wv of registry.values()) { + try { + if (typeof wv.isLoading === 'function' && wv.isLoading()) return true; + } catch { + // a torn-down webview can throw; treat as "not safe to capture" + return true; + } + } + return false; +}