fix(electron): close pages from the main process to avoid Target.closeTarget hang#41695
Conversation
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
13d604f to
7df6703
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…eTarget hang page.close() sends CDP Target.closeTarget and waits for the browser-level Target.detachedFromTarget event to resolve. On Electron, when the close races a navigation that is being committed, Electron acks Target.closeTarget with success but silently drops the actual close, so the target is never destroyed and page.close() hangs forever (Electron has no unload watchdog to recover, unlike Chrome). Close the page from the main process instead via a per-page custom close handler that calls webContents.close(). This is not subject to the CDP/ navigation race, and webContents.close() (without waitForBeforeUnload) closes without running beforeunload, matching page.close(runBeforeUnload: false) semantics.
7df6703 to
f17a470
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
- PLAYWRIGHT_ELECTRON_LEGACY_PAGE_CLOSE opts back into Target.closeTarget. - Fall back to the default close when the webContents cannot be found. - Clarify why we close the webContents rather than the BrowserWindow.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
page.close({ runBeforeUnload: true }) went through the CDP Page.close path,
which has the same problem as Target.closeTarget on Electron: it leaves a
window with a pending navigation open. Pass runBeforeUnload to the custom
close handler and map it to webContents.close({ waitForBeforeUnload }), so
both close variants close from the main process.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Test results for "MCP"3 failed 7757 passed, 1249 skipped Merge workflow run. |
This comment has been minimized.
This comment has been minimized.
Test results for "tests 1"8 flaky49516 passed, 1161 skipped Merge workflow run. |
Test results for "tests 2"1 failed 32 flaky105882 passed, 4474 skipped Merge workflow run. |
|
@yury-s I haven't managed to create a minimal reproduction yet (so haven't filed a bug yet), but I'm running into this on Chromium too. According to my testing the race also exists in Chromium and was introduced there before the version that Electron 42.4.1 is based on. I think this corresponds to https://issues.chromium.org/issues/536385539. |
Summary
page.close()sends CDPTarget.closeTargetand resolves only on the browser-levelTarget.detachedFromTargetevent. On Electron, when the close races a navigation that is being committed, Electron acksTarget.closeTargetwithsuccessbut silently drops the actual close, so the target is never destroyed andpage.close()hangs forever (Electron has no unload watchdog to recover, unlike Chrome). This surfaced as flaky Electron teardown timeouts in CI (e.g.page-autowaiting-no-hang.spec.ts"assigning location to about:blank").webContents.close(), which is not subject to the CDP/navigation race.webContents.close()(withoutwaitForBeforeUnload) closes without runningbeforeunload, matchingpage.close(runBeforeUnload: false)force-close semantics.