From cf360ad7b0695f15ef4e537f2bc281953772a6b6 Mon Sep 17 00:00:00 2001 From: murich Date: Tue, 1 Sep 2026 14:14:30 +0700 Subject: [PATCH] fix: destroy leaked fragment-capture window on failed loadURL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit captureStart() registered the offscreen capture BrowserWindow in the captures map before awaiting win.loadURL(url). If the load rejected (e.g. the fragment dev server was momentarily unavailable), the window was never destroyed or removed, and captureStart's own captures.has(id) early-return then permanently blocked any future retry for that fragment id. The leaked window keeps compositing on the GPU indefinitely. Enough of them accumulating (repeated capture attempts against a flaky dev server) can starve the single shared GPU process, which manifests as the preview going solid black with no exception and no crash — the existing webglcontextlost/webglcontextrestored recovery in player.ts never fires because the GPU process is starved, not reset. Wrap the loadURL await in try/catch and destroy+unregister the window on failure before rethrowing, so a failed load is fully cleaned up and can be retried. --- electron/fragments.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/electron/fragments.ts b/electron/fragments.ts index 98c6d84..fa2ad0b 100644 --- a/electron/fragments.ts +++ b/electron/fragments.ts @@ -639,7 +639,15 @@ async function captureStart( id, w: size.width, h: size.height, data: image.getBitmap() }) }) - await win.loadURL(url) + try { + await win.loadURL(url) + } catch (err) { + // a failed load must not leave a live, continuously-painting window behind: + // captures.has(id) short-circuits every future start for this id, so an + // orphan here is unrecoverable without restarting the app + captureStop(id) + throw err + } } function captureStop(id: string) {