Просмотр исходного кода

fix(desktop): guard destroyed recovery windows (#37406)

Luke Parker 1 месяц назад
Родитель
Сommit
4436678f7d

+ 2 - 1
packages/desktop/src/main/index.ts

@@ -33,6 +33,7 @@ import {
   type SidecarListener,
   type SidecarListener,
 } from "./server"
 } from "./server"
 import { setupAutoUpdater, showUpdaterDialog } from "./updater"
 import { setupAutoUpdater, showUpdaterDialog } from "./updater"
+import { safeWebContentsURL } from "./window-state"
 import {
 import {
   getLastFocusedWindow,
   getLastFocusedWindow,
   registerRendererProtocol,
   registerRendererProtocol,
@@ -233,7 +234,7 @@ const main = Effect.gen(function* () {
   })
   })
 
 
   app.on("render-process-gone", (_event, webContents, details) => {
   app.on("render-process-gone", (_event, webContents, details) => {
-    writeLog("window", "app render process gone", { url: webContents.getURL(), details }, "error")
+    writeLog("window", "app render process gone", { url: safeWebContentsURL(webContents), details }, "error")
   })
   })
 
 
   setRelaunchHandler(() => {
   setRelaunchHandler(() => {

+ 2 - 1
packages/desktop/src/main/unresponsive.ts

@@ -1,5 +1,6 @@
 import type { BrowserWindow } from "electron"
 import type { BrowserWindow } from "electron"
 import { write as writeLog } from "./logging"
 import { write as writeLog } from "./logging"
+import { safeWindowURL } from "./window-state"
 
 
 const sampleInterval = 1000
 const sampleInterval = 1000
 const samplePeriod = 15000
 const samplePeriod = 15000
@@ -46,7 +47,7 @@ export function createUnresponsiveSampler(win: BrowserWindow, name: string) {
     const message = [
     const message = [
       "renderer unresponsive samples",
       "renderer unresponsive samples",
       `Window: ${name}`,
       `Window: ${name}`,
-      `URL: ${win.isDestroyed() ? "<destroyed>" : win.webContents.getURL()}`,
+      `URL: ${safeWindowURL(win)}`,
       ...entries.map((entry) => `<${entry[1]}> ${entry[0]}`),
       ...entries.map((entry) => `<${entry[1]}> ${entry[0]}`),
       `Total Samples: ${total}`,
       `Total Samples: ${total}`,
     ].join("\n")
     ].join("\n")

+ 29 - 0
packages/desktop/src/main/window-state.ts

@@ -0,0 +1,29 @@
+export const destroyedWindowURL = "<destroyed>"
+
+type WebContentsURLState = {
+  isDestroyed(): boolean
+  getURL(): string
+}
+
+type WindowURLState = {
+  isDestroyed(): boolean
+  readonly webContents: WebContentsURLState
+}
+
+export function safeWebContentsURL(webContents: WebContentsURLState) {
+  try {
+    if (webContents.isDestroyed()) return destroyedWindowURL
+    return webContents.getURL()
+  } catch {
+    return destroyedWindowURL
+  }
+}
+
+export function safeWindowURL(win: WindowURLState) {
+  try {
+    if (win.isDestroyed()) return destroyedWindowURL
+    return safeWebContentsURL(win.webContents)
+  } catch {
+    return destroyedWindowURL
+  }
+}

+ 5 - 9
packages/desktop/src/main/windows.ts

@@ -13,6 +13,7 @@ import { getStore, removeStoreFile } from "./store"
 import { PINCH_ZOOM_ENABLED_KEY, WINDOW_IDS_KEY } from "./store-keys"
 import { PINCH_ZOOM_ENABLED_KEY, WINDOW_IDS_KEY } from "./store-keys"
 import { createUnresponsiveSampler } from "./unresponsive"
 import { createUnresponsiveSampler } from "./unresponsive"
 import { createWindowRegistry } from "./window-registry"
 import { createWindowRegistry } from "./window-registry"
+import { safeWindowURL } from "./window-state"
 
 
 const root = dirname(fileURLToPath(import.meta.url))
 const root = dirname(fileURLToPath(import.meta.url))
 const rendererRoot = join(root, "../renderer")
 const rendererRoot = join(root, "../renderer")
@@ -364,7 +365,7 @@ function wireWindowRecovery(win: BrowserWindow, name: string) {
         errorCode,
         errorCode,
         errorDescription,
         errorDescription,
         validatedURL,
         validatedURL,
-        currentURL: win.webContents.getURL(),
+        currentURL: safeWindowURL(win),
         isMainFrame,
         isMainFrame,
       },
       },
       "error",
       "error",
@@ -386,12 +387,7 @@ function wireWindowRecovery(win: BrowserWindow, name: string) {
   })
   })
   win.webContents.on("render-process-gone", (_event, details) => {
   win.webContents.on("render-process-gone", (_event, details) => {
     sampler.stopAndFlush()
     sampler.stopAndFlush()
-    writeLog(
-      "window",
-      "renderer process gone",
-      { window: name, currentURL: win.webContents.getURL(), details },
-      "error",
-    )
+    writeLog("window", "renderer process gone", { window: name, currentURL: safeWindowURL(win), details }, "error")
     void show(
     void show(
       "OpenCode window terminated unexpectedly",
       "OpenCode window terminated unexpectedly",
       [`Window: ${name}`, `Reason: ${details.reason}`, `Code: ${details.exitCode ?? "<unknown>"}`].join("\n"),
       [`Window: ${name}`, `Reason: ${details.reason}`, `Code: ${details.exitCode ?? "<unknown>"}`].join("\n"),
@@ -399,12 +395,12 @@ function wireWindowRecovery(win: BrowserWindow, name: string) {
     )
     )
   })
   })
   win.on("unresponsive", () => {
   win.on("unresponsive", () => {
-    writeLog("window", "renderer unresponsive", { window: name, currentURL: win.webContents.getURL() }, "error")
+    writeLog("window", "renderer unresponsive", { window: name, currentURL: safeWindowURL(win) }, "error")
     sampler.start()
     sampler.start()
     void show("OpenCode is not responding", "You can relaunch the app, open the logs, or keep waiting.", true)
     void show("OpenCode is not responding", "You can relaunch the app, open the logs, or keep waiting.", true)
   })
   })
   win.on("responsive", () => {
   win.on("responsive", () => {
-    writeLog("window", "renderer responsive", { window: name, currentURL: win.webContents.getURL() }, "error")
+    writeLog("window", "renderer responsive", { window: name, currentURL: safeWindowURL(win) }, "error")
     sampler.stopAndFlush()
     sampler.stopAndFlush()
   })
   })
   win.webContents.on("console-message", (_event, level, message, line, sourceId) => {
   win.webContents.on("console-message", (_event, level, message, line, sourceId) => {