Repository navigation
Fix wedged browser automation recovery #8094
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
2d86a54
test: cover browser automation watchdog recovery
austinywang 087d82f
fix: recover unresponsive browser automation
austinywang f761f82
Merge remote-tracking branch 'origin/main' into issue-8054-browser-pr…
austinywang 9c00737
fix: cover all browser automation callback channels
austinywang 72c969a
refactor: isolate browser watchdog signal
austinywang e29cafb
fix: preserve browser automation recovery state
austinywang cf69bd1
Merge remote-tracking branch 'origin/main' into issue-8054-browser-pr…
austinywang c1c3fbf
test: cover shared browser recovery
austinywang ea1ec4b
fix: share browser automation recovery
austinywang 961ed32
Merge remote-tracking branch 'origin/main' into issue-8054-browser-pr…
austinywang 7824157
test: cover superseded browser recovery
austinywang 7095778
Merge remote-tracking branch 'origin/main' into issue-8054-browser-pr…
austinywang f3f1f52
fix: preserve page-world automation state
austinywang 8307d58
fix: await recovered browser document readiness
austinywang c41152f
test: preserve browser consent during recovery
austinywang 6ff8adf
fix: preserve consent across browser recovery
austinywang 9000695
fix: preserve interactive prompts during browser recovery
austinywang 0cbd187
Merge remote-tracking branch 'origin/main' into issue-8054-browser-pr…
austinywang 905ac49
test: cover browser recovery lifecycle races
austinywang 39f7b8c
fix: guard browser automation recovery lifecycle
austinywang 056e6cf
test: remove browser readiness registration hook
austinywang d0a7fdb
fix: preserve browser recovery timeout headroom
austinywang 335c745
Merge remote-tracking branch 'origin/main' into issue-8054-browser-pr…
austinywang 6f2807c
test: cover browser omnibar draft isolation
austinywang 1f0ce4e
fix: isolate browser omnibar drafts by panel
austinywang 05fd181
Merge remote-tracking branch 'origin/main' into issue-8054-browser-pr…
austinywang c1c839b
ci: gate browser panel identity regression
austinywang b39ad9d
Merge remote-tracking branch 'origin/main' into issue-8054-browser-pr…
austinywang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
...es/macOS/CmuxBrowser/Sources/CmuxBrowser/Control/BrowserAutomationDocumentReadiness.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| public import Foundation | ||
|
|
||
| /// Tracks the first committed document for the browser instance owned by one panel. | ||
| /// | ||
| /// The owner calls ``bind(to:hasCommittedDocument:)`` whenever it replaces its WebView and | ||
| /// ``didCommit(instanceID:)`` from the corresponding navigation-delegate callback. Automation | ||
| /// callers can then await the real lifecycle signal without polling WebKit state. | ||
| @MainActor | ||
| public final class BrowserAutomationDocumentReadiness { | ||
| private var observedInstanceID: UUID? | ||
| private var committedInstanceID: UUID? | ||
| private var waiters: [UUID: AsyncStream<BrowserAutomationDocumentReadinessOutcome>.Continuation] = [:] | ||
|
|
||
| /// Creates an empty document-readiness owner. | ||
| public init() {} | ||
|
|
||
| /// Starts observing a browser instance and supersedes waits for any previous instance. | ||
| /// - Parameters: | ||
| /// - instanceID: Stable identity of the newly bound browser instance. | ||
| /// - hasCommittedDocument: Whether the instance committed before the owner attached its delegate. | ||
| public func bind(to instanceID: UUID, hasCommittedDocument: Bool) { | ||
| if observedInstanceID == instanceID { | ||
| if hasCommittedDocument, committedInstanceID != instanceID { | ||
| committedInstanceID = instanceID | ||
| finishWaiters(with: .committed) | ||
| } | ||
| return | ||
| } | ||
|
|
||
| finishWaiters(with: .superseded) | ||
| observedInstanceID = instanceID | ||
| committedInstanceID = hasCommittedDocument ? instanceID : nil | ||
| } | ||
|
|
||
| /// Records a navigation commit when it belongs to the currently bound browser instance. | ||
| /// - Parameter instanceID: Identity captured when the navigation delegate was bound. | ||
| public func didCommit(instanceID: UUID) { | ||
| guard observedInstanceID == instanceID else { return } | ||
| committedInstanceID = instanceID | ||
| finishWaiters(with: .committed) | ||
| } | ||
|
|
||
| /// Stops observing the current browser instance and cancels its pending waits. | ||
| public func invalidate() { | ||
| observedInstanceID = nil | ||
| committedInstanceID = nil | ||
| finishWaiters(with: .cancelled) | ||
| } | ||
|
|
||
| /// Returns whether the currently bound browser instance has committed a document. | ||
| /// - Parameter instanceID: Browser instance to inspect. | ||
| /// - Returns: `true` only after that exact instance produced a commit signal. | ||
| public func hasCommittedDocument(for instanceID: UUID) -> Bool { | ||
| observedInstanceID == instanceID && committedInstanceID == instanceID | ||
| } | ||
|
|
||
| /// Waits for a real commit signal from the specified browser instance. | ||
| /// - Parameter instanceID: Browser instance whose first document is required. | ||
| /// - Returns: Whether the instance committed, was superseded, or the wait was cancelled. | ||
| public func waitForCommit( | ||
| instanceID: UUID | ||
| ) async -> BrowserAutomationDocumentReadinessOutcome { | ||
| guard !Task.isCancelled else { return .cancelled } | ||
| guard observedInstanceID == instanceID else { return .superseded } | ||
| guard committedInstanceID != instanceID else { return .committed } | ||
|
|
||
| let waiterID = UUID() | ||
| let (events, continuation) = AsyncStream.makeStream( | ||
| of: BrowserAutomationDocumentReadinessOutcome.self, | ||
| bufferingPolicy: .bufferingNewest(1) | ||
| ) | ||
| waiters[waiterID] = continuation | ||
| defer { | ||
| waiters.removeValue(forKey: waiterID) | ||
| continuation.finish() | ||
| } | ||
| var iterator = events.makeAsyncIterator() | ||
| return await iterator.next() ?? .cancelled | ||
| } | ||
|
|
||
| private func finishWaiters(with outcome: BrowserAutomationDocumentReadinessOutcome) { | ||
| let pendingWaiters = Array(waiters.values) | ||
| waiters.removeAll() | ||
| for waiter in pendingWaiters { | ||
| waiter.yield(outcome) | ||
| waiter.finish() | ||
| } | ||
| } | ||
| } |
11 changes: 11 additions & 0 deletions
11
...S/CmuxBrowser/Sources/CmuxBrowser/Control/BrowserAutomationDocumentReadinessOutcome.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| /// Result of waiting for a browser instance to commit its first automation document. | ||
| public enum BrowserAutomationDocumentReadinessOutcome: Sendable, Equatable { | ||
| /// The observed browser instance committed a document and is ready for JavaScript automation. | ||
| case committed | ||
|
|
||
| /// A newer browser instance replaced the observed instance before it committed a document. | ||
| case superseded | ||
|
|
||
| /// The caller cancelled its wait before the observed instance committed a document. | ||
| case cancelled | ||
| } | ||
6 changes: 6 additions & 0 deletions
6
Packages/macOS/CmuxBrowser/Sources/CmuxBrowser/Control/BrowserAutomationProbeSignal.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| /// Internal result from racing browser automation liveness callbacks against their deadline. | ||
| enum BrowserAutomationProbeSignal: Sendable { | ||
| case responsive | ||
| case timedOut | ||
| case cancelled | ||
| } |
14 changes: 14 additions & 0 deletions
14
...ages/macOS/CmuxBrowser/Sources/CmuxBrowser/Control/BrowserAutomationRecoveryOutcome.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| /// Result of checking whether a browser automation callback pipeline is alive. | ||
| public enum BrowserAutomationRecoveryOutcome: Sendable, Equatable { | ||
| /// The liveness callback arrived before the deadline, so the current WebView remains authoritative. | ||
| case responsive | ||
|
|
||
| /// The callback missed its deadline and the owning browser surface replaced the unresponsive WebView. | ||
| case recovered | ||
|
|
||
| /// The callback missed its deadline, but another lifecycle path had already replaced the observed WebView. | ||
| case superseded | ||
|
|
||
| /// The check was cancelled before liveness or timeout produced an outcome. | ||
| case cancelled | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.