Skip to content

WebDriver: Keep the window alive when a process swap loses its signal - #10730

Open
sideshowbarker wants to merge 2 commits into
LadybirdBrowser:masterfrom
sideshowbarker:webdriver-keep-window-across-swap
Open

WebDriver: Keep the window alive when a process swap loses its signal#10730
sideshowbarker wants to merge 2 commits into
LadybirdBrowser:masterfrom
sideshowbarker:webdriver-keep-window-across-swap

Conversation

@sideshowbarker

@sideshowbarker sideshowbarker commented Jul 17, 2026

Copy link
Copy Markdown
Member

Problem: TestWebDriverSessionHistory intermittently hangs during a UI-initiated history traversal that swaps the WebContent process. The traverse-history-from-ui request never returns — so the test’s HTTP client times out, and the harness kills WebDriver.

Cause: traverse_history_from_ui waits in perform_async_action for a driver-execution-complete signal from the current renderer. When the traversal replaces the process, that — and did_start_window_replacement preceding it — are fired by the old renderer from a callback that only runs after an async round-trip to the UI process. If the swap tears the old renderer down before that callback, both signals are lost: the Allow wait in perform_async_action can’t observe the loss, spins forever, and web_content_connection_closed sees no pending replacement. So it removes the window, clears the current window handle, and closes the session.

Fix: While perform_async_action awaits a replacement-capable action, remember the connection it’s waiting on. If that closes first, treat the close as the start of the swap: web_content_connection_closed keeps the window awaiting its replacement instead of removing it — letting the incoming renderer reclaim the window, and letting the wait observe the replacement and return. Mark the traverse_history_from_ui handler as replacement-capable — so it takes this path; and navigate_to and perform_actions, already replacement-capable, get the same protection. Fixes #10711.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4ff14657-3491-46e0-add5-5590e7e9a0c8

📥 Commits

Reviewing files that changed from the base of the PR and between 61e5862 and ee6d603.

📒 Files selected for processing (3)
  • Services/WebDriver/Session.cpp
  • Services/WebDriver/Session.h
  • Tests/LibWebView/test-webdriver-session-history.py

📝 Walkthrough

Walkthrough

WebDriver now tracks announced and inferred WebContent replacements during asynchronous actions. It bounds replacement waits, removes windows with failed replacements, returns distinct errors, and tests recovery after WebContent or browser termination.

Changes

WebDriver window replacement

Layer / File(s) Summary
Track replacement state and connection ownership
Services/WebDriver/Session.h, Services/WebDriver/Client.cpp
Window uses three replacement states. perform_async_action tracks the active replacement connection and updates its wait condition. History traversal enables replacement handling.
Handle replacement lifecycle events
Services/WebDriver/Session.cpp
Connection registration and window updates reset replacement state. Replacement announcements and closed connections set the corresponding replacement states.
Wait for replacement and report failures
Services/WebDriver/Session.h, Services/WebDriver/Session.cpp
Replacement waits use bounded timeouts. The session refetches windows, removes failed replacements, and returns distinct NoSuchWindow errors.
Validate process-death behavior
Tests/LibWebView/test-webdriver-session-history.py
Tests terminate WebContent or the browser during Actions commands. They verify WebContent recovery and browser-death session invalidation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WebDriverClient
  participant Session
  participant WebContentConnection
  participant Window
  WebDriverClient->>Session: Start async action with replacement allowed
  Session->>WebContentConnection: Track possible replacement connection
  WebContentConnection->>Session: Close during action
  Session->>Window: Mark replacement as inferred
  Session->>Session: Wait and refetch window
  Session-->>WebDriverClient: Return recovery or NoSuchWindow result
Loading

Possibly related PRs

Suggested reviewers: trflynn89, lubrsi

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the WebDriver history traversal hang, its process-replacement cause, and the corresponding code and test changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sideshowbarker
sideshowbarker force-pushed the webdriver-keep-window-across-swap branch from 1eb5b3b to 1f3d50f Compare July 17, 2026 08:01
@sideshowbarker

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@gmta

gmta commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

If an actual crash happens during traversal, aren't we now timing out instead of catching that immediately? Can we reduce the time it takes before we see that crash?

@sideshowbarker

Copy link
Copy Markdown
Member Author

If an actual crash happens during traversal, aren't we now timing out instead of catching that immediately?

hmm yeah, pretty much — it’s essentially a regression 😅

Can we reduce the time it takes before we see that crash?

Yeah, I’ll go back and work on (re)implementing a proper fix for the original problem.

@sideshowbarker
sideshowbarker force-pushed the webdriver-keep-window-across-swap branch from ad216ba to ad3ee1d Compare July 27, 2026 12:10
@sideshowbarker

Copy link
Copy Markdown
Member Author

@gmta OK, pushed an update to get things working right.

Problem: TestWebDriverSessionHistory intermittently hangs during a
UI-initiated history traversal that swaps the WebContent process. The
traverse-history-from-ui request never returns — so the test’s HTTP
client times out, and the harness kills WebDriver.

Cause: traverse_history_from_ui waits in perform_async_action for a
driver-execution-complete signal from the current renderer. When the
traversal replaces the process, that — and did_start_window_replacement
preceding it — are fired by the old renderer from a callback that only
runs after an async round-trip to the UI process. If the swap tears the
old renderer down before that callback, both signals are lost: the Allow
wait in perform_async_action can’t observe the loss, spins forever, and
web_content_connection_closed sees no pending replacement. So it removes
the window, clears the current window handle, and closes the session.

Fix: While perform_async_action awaits a replacement-capable action,
remember the connection it’s waiting on. If that closes first, treat the
close as the start of the swap: web_content_connection_closed keeps the
window awaiting its replacement instead of removing it — letting the
incoming renderer reclaim the window, and letting the wait observe the
replacement and return. Mark the traverse_history_from_ui handler as
replacement-capable — so it takes this path; and navigate_to and
perform_actions, already replacement-capable, get the same protection.

Fixes LadybirdBrowser#10711
@sideshowbarker
sideshowbarker force-pushed the webdriver-keep-window-across-swap branch from ad3ee1d to 61e5862 Compare August 5, 2026 08:18
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Services/WebDriver/Session.cpp`:
- Around line 316-319: Update the inferred replacement timeout handling in the
window-replacement flow around the connection-close branch and its
five-second/page-load timeout wait so that an expired replacement transitions to
a terminal closed/error state or removes the affected window before returning
ErrorCode::Timeout. Ensure subsequent find_session() calls cannot rediscover the
window as awaiting replacement and repeat the timeout indefinitely.

In `@Services/WebDriver/Session.h`:
- Line 119: Update the replacement predicate in Session’s perform_async_action
flow to compare the current window’s WebContentConnection identity with the
captured connection, treating any non-null different connection as replacement;
preserve the existing window-removal and awaiting-replacement checks.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7628f609-7e26-48f0-beed-5f9c6ee907b8

📥 Commits

Reviewing files that changed from the base of the PR and between bba04b3 and 61e5862.

📒 Files selected for processing (3)
  • Services/WebDriver/Client.cpp
  • Services/WebDriver/Session.cpp
  • Services/WebDriver/Session.h

Comment thread Services/WebDriver/Session.cpp
Comment thread Services/WebDriver/Session.h Outdated
Problem: A WebContent process swap can strand WebDriver in two ways. A
command awaiting a replacement-capable action can spin forever after its
renderer dies. And if a renderer dies with no replacement ever arriving,
the command fails with “timeout” — and every later command stalls five
seconds to fail the same way, for the life of the session.

Cause: Window::AwaitingReplacement has no terminal transitions. If the
replacement registers itself before the event loop dispatches the old
connection’s close, that close matches no window — so the awaiting-
replacement state watched for by the wait in perform_async_action is
never entered, no response can arrive from the dead renderer, and the
wait has no other exit. And the wait returns “timeout” but leaves the
window awaiting its replacement with no connection when it’s in
wait_for_current_window_to_have_web_content_connection and times out —
So every later command re-enters the same wait, repeating the timeout.

Fix: Give each wait a terminal outcome, with a single owner for each
transition. The wait in perform_async_action now also exits when the
current window is connected to a different connection than the one its
action was sent to — and the command then completes the way an announced
swap does. The replacement-wait timeout, as sole owner of the failure
transition, removes the window — converging on the end state of an un-
announced connection close: The waiting command fails with “no such
window”, the spec’s error for a torn-down browsing context, and truthful
for a crash that “timeout” misreported; the session closes when that
window was its last — and later commands fail immediately, instead of
repeating the wait. A replacement that registers in the same event-loop
batch firing the timer still wins: The wait rechecks the window before
finalizing. The now-unused ensure_current_window_handle_is_valid: gone.
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.

CI failure in TestWebDriverSessionHistory

2 participants