| ... | @@ -1303,6 +1303,17 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function | ... | @@ -1303,6 +1303,17 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function |
| 1303 | > => { | 1303 | > => { |
| 1304 | const existing = sessions.get(wc.id); | 1304 | const existing = sessions.get(wc.id); |
| 1305 | if (existing) return Effect.succeed([existing, sessions] as const); | 1305 | if (existing) return Effect.succeed([existing, sessions] as const); |
| | 1306 | // A guest can be destroyed while it waits for this lock, and its native |
| | 1307 | // methods throw once it is. |
| | 1308 | if (wc.isDestroyed()) { |
| | 1309 | return Effect.fail( |
| | 1310 | new PreviewOperationError({ |
| | 1311 | operation: "ensureControlSession", |
| | 1312 | webContentsId: wc.id, |
| | 1313 | cause: new Error("WebContents was destroyed"), |
| | 1314 | }), |
| | 1315 | ); |
| | 1316 | } |
| 1306 | if (wc.isDevToolsOpened()) { | 1317 | if (wc.isDevToolsOpened()) { |
| 1307 | return Effect.fail( | 1318 | return Effect.fail( |
| 1308 | new PreviewAutomationDevToolsOpenError({ | 1319 | new PreviewAutomationDevToolsOpenError({ |
| ... | @@ -1409,21 +1420,14 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function | ... | @@ -1409,21 +1420,14 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function |
| 1409 | wcDebugger.on("message", onMessage); | 1420 | wcDebugger.on("message", onMessage); |
| 1410 | wcDebugger.attach("1.3"); | 1421 | wcDebugger.attach("1.3"); |
| 1411 | }); | 1422 | }); |
| 1412 | yield* Effect.forEach( | | |
| 1413 | ["Runtime.enable", "Accessibility.enable", "Network.enable", "Log.enable"], | | |
| 1414 | (method) => | | |
| 1415 | attemptPromise( | | |
| 1416 | { operation: `initializeDebugger.${method}`, webContentsId: wc.id }, | | |
| 1417 | () => wcDebugger.sendCommand(method), | | |
| 1418 | ), | | |
| 1419 | { concurrency: "unbounded", discard: true }, | | |
| 1420 | ); | | |
| 1421 | // Electron gives `<webview>` guests a transparent base background, and | 1423 | // Electron gives `<webview>` guests a transparent base background, and |
| 1422 | // Chromium only paints a dark canvas for dark color-scheme pages over an | 1424 | // Chromium only paints a dark canvas for dark color-scheme pages over an |
| 1423 | // opaque base. Without this, dark-scheme pages with no background of | 1425 | // opaque base. Without this, dark-scheme pages with no background of |
| 1424 | // their own (text/plain, e.g. .md files) render white text on white. | 1426 | // their own (text/plain, e.g. .md files) render white text on white. |
| 1425 | // White matches the webview's existing white backing, so light pages look | 1427 | // White matches the webview's existing white backing, so light pages look |
| 1426 | // the same; Chromium still swaps in its dark canvas for dark-scheme pages. | 1428 | // the same; Chromium still swaps in its dark canvas for dark-scheme pages. |
| | 1429 | // Sent first because a document that paints before it arrives keeps the |
| | 1430 | // transparent base until its next load. |
| 1427 | yield* attemptPromise( | 1431 | yield* attemptPromise( |
| 1428 | { operation: "initializeDebugger.defaultBackground", webContentsId: wc.id }, | 1432 | { operation: "initializeDebugger.defaultBackground", webContentsId: wc.id }, |
| 1429 | () => | 1433 | () => |
| ... | @@ -1431,6 +1435,15 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function | ... | @@ -1431,6 +1435,15 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function |
| 1431 | color: { r: 255, g: 255, b: 255, a: 1 }, | 1435 | color: { r: 255, g: 255, b: 255, a: 1 }, |
| 1432 | }), | 1436 | }), |
| 1433 | ); | 1437 | ); |
| | 1438 | yield* Effect.forEach( |
| | 1439 | ["Runtime.enable", "Accessibility.enable", "Network.enable", "Log.enable"], |
| | 1440 | (method) => |
| | 1441 | attemptPromise( |
| | 1442 | { operation: `initializeDebugger.${method}`, webContentsId: wc.id }, |
| | 1443 | () => wcDebugger.sendCommand(method), |
| | 1444 | ), |
| | 1445 | { concurrency: "unbounded", discard: true }, |
| | 1446 | ); |
| 1434 | return [ | 1447 | return [ |
| 1435 | control, | 1448 | control, |
| 1436 | replaceMap(sessions, (copy) => { | 1449 | replaceMap(sessions, (copy) => { |
| ... | @@ -2415,6 +2428,30 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function | ... | @@ -2415,6 +2428,30 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function |
| 2415 | ); | 2428 | ); |
| 2416 | }); | 2429 | }); |
| 2417 | | 2430 | |
| | 2431 | // Called when a guest attaches to the window, before its first document |
| | 2432 | // paints. A tab opened straight to a URL often paints before the renderer |
| | 2433 | // gets to registerWebview, which would leave that page on the transparent |
| | 2434 | // base. registerWebview reuses the session opened here. |
| | 2435 | const prepareWebview = Effect.fn("PreviewManager.prepareWebview")(function* ( |
| | 2436 | wc: Electron.WebContents, |
| | 2437 | ) { |
| | 2438 | const webContentsId = wc.id; |
| | 2439 | // A guest destroyed before any tab claims it has no other cleanup path. |
| | 2440 | wc.once("destroyed", () => { |
| | 2441 | runFork(detachControlSession(webContentsId)); |
| | 2442 | }); |
| | 2443 | // Runs detached from the attach event, so nothing may escape. registerWebview |
| | 2444 | // opens the session again if this one did not. |
| | 2445 | yield* ensureControlSession(wc).pipe( |
| | 2446 | Effect.catchCause((cause) => |
| | 2447 | Effect.logDebug("Preview webview control session was not opened on attach.", { |
| | 2448 | webContentsId, |
| | 2449 | cause, |
| | 2450 | }), |
| | 2451 | ), |
| | 2452 | ); |
| | 2453 | }); |
| | 2454 | |
| 2418 | const navigate = Effect.fn("PreviewManager.navigate")(function* (tabId: string, rawUrl: string) { | 2455 | const navigate = Effect.fn("PreviewManager.navigate")(function* (tabId: string, rawUrl: string) { |
| 2419 | const url = yield* attempt({ operation: "navigate.normalizeUrl", tabId }, () => | 2456 | const url = yield* attempt({ operation: "navigate.normalizeUrl", tabId }, () => |
| 2420 | normalizePreviewUrl(rawUrl), | 2457 | normalizePreviewUrl(rawUrl), |
| ... | @@ -4675,6 +4712,7 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function | ... | @@ -4675,6 +4712,7 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function |
| 4675 | openPictureInPicture, | 4712 | openPictureInPicture, |
| 4676 | openDevTools, | 4713 | openDevTools, |
| 4677 | pickElement, | 4714 | pickElement, |
| | 4715 | prepareWebview, |
| 4678 | reapplyZoom, | 4716 | reapplyZoom, |
| 4679 | refresh, | 4717 | refresh, |
| 4680 | registerWebview, | 4718 | registerWebview, |
| ... | @@ -5011,6 +5049,7 @@ export class PreviewManager extends Context.Service< | ... | @@ -5011,6 +5049,7 @@ export class PreviewManager extends Context.Service< |
| 5011 | tabId: string, | 5049 | tabId: string, |
| 5012 | webContentsId: number, | 5050 | webContentsId: number, |
| 5013 | ) => Effect.Effect<void, PreviewManagerError>; | 5051 | ) => Effect.Effect<void, PreviewManagerError>; |
| | 5052 | readonly prepareWebview: (webContents: Electron.WebContents) => Effect.Effect<void>; |
| 5014 | readonly navigate: (tabId: string, url: string) => Effect.Effect<void, PreviewManagerError>; | 5053 | readonly navigate: (tabId: string, url: string) => Effect.Effect<void, PreviewManagerError>; |
| 5015 | readonly goBack: (tabId: string) => Effect.Effect<void, PreviewManagerError>; | 5054 | readonly goBack: (tabId: string) => Effect.Effect<void, PreviewManagerError>; |
| 5016 | readonly goForward: (tabId: string) => Effect.Effect<void, PreviewManagerError>; | 5055 | readonly goForward: (tabId: string) => Effect.Effect<void, PreviewManagerError>; |
| ... | @@ -5137,6 +5176,7 @@ export const make = Effect.gen(function* PreviewManagerMake() { | ... | @@ -5137,6 +5176,7 @@ export const make = Effect.gen(function* PreviewManagerMake() { |
| 5137 | createTab: operations.createTab, | 5176 | createTab: operations.createTab, |
| 5138 | closeTab: operations.closeTab, | 5177 | closeTab: operations.closeTab, |
| 5139 | registerWebview: operations.registerWebview, | 5178 | registerWebview: operations.registerWebview, |
| | 5179 | prepareWebview: operations.prepareWebview, |
| 5140 | navigate: operations.navigate, | 5180 | navigate: operations.navigate, |
| 5141 | goBack: operations.goBack, | 5181 | goBack: operations.goBack, |
| 5142 | goForward: operations.goForward, | 5182 | goForward: operations.goForward, |