| ... | ... | @@ -437,6 +437,12 @@ interface PickSession { |
| 437 | 437 | |
| 438 | 438 | interface BrowserControlSession { |
| 439 | 439 | readonly webContentsId: number; |
| 440 | // Pins the WebContents' Debugger wrapper for the session's lifetime. |
| 441 | // Electron's Debugger is GC-managed but registered with Chromium as a raw |
| 442 | // DevToolsAgentHostClient pointer; collecting it while attached crashes the |
| 443 | // browser process (electron/electron#53376). Detach must also go through |
| 444 | // this reference: `wc.debugger` throws once the WebContents is destroyed. |
| 445 | readonly debugger: Electron.Debugger; |
| 440 | 446 | readonly semaphore: Semaphore.Semaphore; |
| 441 | 447 | readonly scope: Scope.Closeable; |
| 442 | 448 | readonly onMessage: ( |
| ... | ... | @@ -1184,6 +1190,7 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function |
| 1184 | 1190 | const createControlSession = Effect.fn("PreviewManager.createControlSession")(function* () { |
| 1185 | 1191 | const semaphore = yield* Semaphore.make(1); |
| 1186 | 1192 | const scope = yield* Scope.fork(parentScope, "sequential"); |
| 1193 | const wcDebugger = wc.debugger; |
| 1187 | 1194 | const handleDebuggerMessage = Effect.fnUntraced(function* ( |
| 1188 | 1195 | method: string, |
| 1189 | 1196 | params: Record<string, unknown>, |
| ... | ... | @@ -1196,7 +1203,7 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function |
| 1196 | 1203 | operation: "ackScreencastFrame", |
| 1197 | 1204 | webContentsId: wc.id, |
| 1198 | 1205 | }, |
| 1199 | | () => wc.debugger.sendCommand("Page.screencastFrameAck", { sessionId }), |
| 1206 | () => wcDebugger.sendCommand("Page.screencastFrameAck", { sessionId }), |
| 1200 | 1207 | ).pipe(Effect.ignore); |
| 1201 | 1208 | } |
| 1202 | 1209 | const tabId = yield* tabIdForWebContents(wc.id); |
| ... | ... | @@ -1244,8 +1251,8 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function |
| 1244 | 1251 | }), |
| 1245 | 1252 | ), |
| 1246 | 1253 | attempt({ operation: "detachControlSession", webContentsId: wc.id }, () => { |
| 1247 | | wc.debugger.off("message", onMessage); |
| 1248 | | if (wc.debugger.isAttached()) wc.debugger.detach(); |
| 1254 | wcDebugger.off("message", onMessage); |
| 1255 | if (wcDebugger.isAttached()) wcDebugger.detach(); |
| 1249 | 1256 | }).pipe(Effect.ignore), |
| 1250 | 1257 | ], |
| 1251 | 1258 | { discard: true }, |
| ... | ... | @@ -1253,6 +1260,7 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function |
| 1253 | 1260 | ); |
| 1254 | 1261 | const control: BrowserControlSession = { |
| 1255 | 1262 | webContentsId: wc.id, |
| 1263 | debugger: wcDebugger, |
| 1256 | 1264 | semaphore, |
| 1257 | 1265 | scope, |
| 1258 | 1266 | onMessage, |
| ... | ... | @@ -1268,15 +1276,15 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function |
| 1268 | 1276 | }), |
| 1269 | 1277 | ); |
| 1270 | 1278 | yield* attempt({ operation: "attachDebuggerListeners", webContentsId: wc.id }, () => { |
| 1271 | | wc.debugger.on("message", onMessage); |
| 1272 | | wc.debugger.attach("1.3"); |
| 1279 | wcDebugger.on("message", onMessage); |
| 1280 | wcDebugger.attach("1.3"); |
| 1273 | 1281 | }); |
| 1274 | 1282 | yield* Effect.all( |
| 1275 | 1283 | ["Runtime.enable", "Accessibility.enable", "Network.enable", "Log.enable"].map( |
| 1276 | 1284 | (method) => |
| 1277 | 1285 | attemptPromise( |
| 1278 | 1286 | { operation: `initializeDebugger.${method}`, webContentsId: wc.id }, |
| 1279 | | () => wc.debugger.sendCommand(method), |
| 1287 | () => wcDebugger.sendCommand(method), |
| 1280 | 1288 | ), |
| 1281 | 1289 | ), |
| 1282 | 1290 | { concurrency: "unbounded", discard: true }, |
| ... | ... | @@ -1365,7 +1373,7 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function |
| 1365 | 1373 | } |
| 1366 | 1374 | const result = yield* attemptPromise( |
| 1367 | 1375 | { operation: `${action}.${method}`, tabId, webContentsId: wc.id }, |
| 1368 | | () => wc.debugger.sendCommand(method, commandParams), |
| 1376 | () => control.debugger.sendCommand(method, commandParams), |
| 1369 | 1377 | ); |
| 1370 | 1378 | const after = (yield* Ref.get(controlEpochRef)).get(tabId) ?? 0; |
| 1371 | 1379 | if (after !== epoch) { |
| ... | ... | @@ -1389,7 +1397,7 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function |
| 1389 | 1397 | tabId, |
| 1390 | 1398 | webContentsId: wc.id, |
| 1391 | 1399 | }, |
| 1392 | | () => wc.debugger.sendCommand(method, commandParams), |
| 1400 | () => control.debugger.sendCommand(method, commandParams), |
| 1393 | 1401 | ); |
| 1394 | 1402 | }, |
| 1395 | 1403 | ); |
| ... | ... | @@ -2578,9 +2586,9 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function |
| 2578 | 2586 | wc: Electron.WebContents, |
| 2579 | 2587 | colorScheme: DesktopPreviewColorScheme, |
| 2580 | 2588 | ) { |
| 2581 | | yield* ensureControlSession(wc); |
| 2589 | const control = yield* ensureControlSession(wc); |
| 2582 | 2590 | yield* attemptPromise({ operation: "applyColorScheme", tabId, webContentsId: wc.id }, () => |
| 2583 | | wc.debugger.sendCommand("Emulation.setEmulatedMedia", { |
| 2591 | control.debugger.sendCommand("Emulation.setEmulatedMedia", { |
| 2584 | 2592 | features: [ |
| 2585 | 2593 | { |
| 2586 | 2594 | name: "prefers-color-scheme", |
| ... | ... | @@ -2600,7 +2608,7 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function |
| 2600 | 2608 | Effect.gen(function* () { |
| 2601 | 2609 | const beforeAttach = (yield* SynchronizedRef.get(tabsRef)).get(tabId); |
| 2602 | 2610 | if (beforeAttach?.webContentsId !== wc.id) return; |
| 2603 | | yield* ensureControlSession(wc); |
| 2611 | const control = yield* ensureControlSession(wc); |
| 2604 | 2612 | const afterAttach = (yield* SynchronizedRef.get(tabsRef)).get(tabId); |
| 2605 | 2613 | if (afterAttach?.webContentsId !== wc.id) { |
| 2606 | 2614 | yield* detachControlSession(wc.id); |
| ... | ... | @@ -2608,7 +2616,7 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function |
| 2608 | 2616 | } |
| 2609 | 2617 | if (afterAttach.colorScheme !== "system") { |
| 2610 | 2618 | yield* attemptPromise({ operation: "applyColorScheme", tabId, webContentsId: wc.id }, () => |
| 2611 | | wc.debugger.sendCommand("Emulation.setEmulatedMedia", { |
| 2619 | control.debugger.sendCommand("Emulation.setEmulatedMedia", { |
| 2612 | 2620 | features: [ |
| 2613 | 2621 | { |
| 2614 | 2622 | name: "prefers-color-scheme", |