Skip to content

fix: settle popup and download event handles without deadlock - #463

Merged
ankitranjan7 merged 3 commits into
mainfrom
fix/browser-run-event-wait
Aug 28, 2026
Merged

fix: settle popup and download event handles without deadlock#463
ankitranjan7 merged 3 commits into
mainfrom
fix/browser-run-event-wait

Conversation

@ankitranjan7

@ankitranjan7 ankitranjan7 commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Cause

Hosted page.waitForEvent('popup'|'download') honors its timeout when no event fires, but can hang until the outer browser-run ceiling when the event does fire. Tracing the QuickJS Playwright bridge: PlaywrightTransport.send dispatches into the Node dispatcher, which delivered __create__ Page/Download channels synchronously via onmessagedeliverTransport → reentrant callFunction. The waiter then never settles and the QuickJS timeout callback cannot run.

This is the shared event-to-proxy materialization path for both popup and download. It is not CDP acceptDownloads and not a broad bridge rewrite.

Inbound flush now catches a throwing deliver and routes it to onDeliveryError so it cannot become an uncaught microtask. The runner cancels the host if it already exists.

Contract

  • waitForEvent('popup', { timeout }) resolves to a Page whose url(), title(), and body can be read, or rejects at that timeout.
  • waitForEvent('download', { timeout }) resolves to a Download that can saveAs, or rejects at that timeout.
  • When neither event fires, the event-specific timeout wins before the outer run timeout.

Tests

Independent gates in src/browser/run/runner.test.ts:

  • Popup: click https://popup.example.test/, resolve waitForEvent('popup'), read url https://popup.example.test/, title Popup Title, body Popup Body.
  • Download: click <a download>, resolve waitForEvent('download'), saveAs.
  • Negative: it.each(['popup', 'download']) with 80ms timeout fails as BROWSER_RUN_TIMEOUT containing Timeout 80ms exceeded while waiting for event "…" in under 1s.
  • Throwing deferred delivery is reported to onDeliveryError (no uncaught microtask).

npx vitest run src/browser/run/runner.test.ts — 89 passed.

Anti-overfitting

No eval scenario IDs, CSV fixtures, or vendor-verification URLs. Any browser-run waiter for popup or download benefits.

Related

Closed webcmd-cloud#79: CDP download-behavior enablement is not this fix. If it is still required independently, it needs a per-session download directory and cleanup, not a global /tmp path.

Does not merge.

waitForEvent('popup'|'download') can deadlock past its own timeout when the
event fires: the Playwright dispatcher delivers the Page/Download channel
reentrantly while QuickJS is still inside transport send, so the waiter
never settles and the timer cannot run. Queue inbound protocol messages to
a microtask so handle materialization runs after send returns.
@github-actions

Copy link
Copy Markdown
Contributor

🟠 Maintainer review suggested — low confidence

The automated review could not reach a fully supported conclusion.

Limitations

  • The automated review returned an invalid structured result.

This review is advisory and does not block merging.

Catch throws from the queued inbound flush and cancel the host, and pin
popup/download waitForEvent tests to exact values and per-event timeouts.
Do not expose handleServerMessage on the public class; the delivery-error
test reaches it with a narrow cast. Drop the suite-wide mock restore.
@ankitranjan7
ankitranjan7 merged commit 09f9782 into main Aug 28, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant