You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(browser,js): enforce navigation deadline through synchronous V8 execution
`tokio::time::timeout` only fires at `.await` points. Any synchronous V8
work — script evaluation, module top-level code — holds the tokio executor
thread for its entire duration, making the outer async timeout invisible to
scripts that never yield. A page with heavy synchronous scripts could run
arbitrarily past `--timeout` with no way to interrupt it.
Fix: add `navigation_deadline: Option<Instant>` to `Page` and thread it
through every execution phase.
**Script phase** — each `execute_script_with_timeout` call receives the
remaining budget as its hard ceiling. When the budget expires a watchdog
thread fires `terminate_execution()` in a tight loop (every 10 ms) so that
scripts with `try-catch` error-recovery handlers are still eventually
terminated rather than absorbing a single termination call and continuing.
Scripts are also skipped entirely once the deadline has passed, cutting the
iteration short rather than starting work we know will be cancelled.
**Network fetch phase** — each parallel script fetch is wrapped in
`tokio::time::timeout(remaining_budget, ...)` so a slow CDN response cannot
by itself exhaust the navigation deadline; fetch failures are treated as
absent scripts rather than errors.
**ES module phase** — V8's `terminate_execution` is catchable by JavaScript
`try-catch`, and heavy modules with error-recovery paths run *longer* when
disturbed than when left to complete naturally. A threshold guard skips any
module when the remaining budget is below 15 s; the module would outlast the
deadline regardless, so it is better to skip it cleanly than to start work
that cannot be reliably stopped.
**Load-events / event-loop drain** — the DOMContentLoaded + load dispatch is
capped at the remaining budget (min 50 ms to allow basic event handling).
The idle event-loop drain is capped at min(500 ms, remaining) and now checks
the deadline on *every* iteration, not only in the timeout branch.
**Error surface** — a new `PageError::NavigationTimedOut` variant is
returned when `execute_scripts` exits because the deadline was reached,
letting the CLI distinguish a timeout from a genuine navigation failure and
produce an accurate "Timed out after Ns" message rather than silently
returning a partially-rendered page.
Also switches `eval_module_with_timeout` from `run_event_loop` to
`with_event_loop_promise`: the former waits for *all* pending work in the
runtime to drain (blocking forever on a page with a live `setInterval`),
while the latter resolves as soon as the module's top-level evaluation
completes.
0 commit comments