Fix EXC_BREAKPOINT crash from re-entering sendEvent: during cursor movement - #69
Open
KJRRR wants to merge 1 commit into
Open
Fix EXC_BREAKPOINT crash from re-entering sendEvent: during cursor movement#69KJRRR wants to merge 1 commit into
KJRRR wants to merge 1 commit into
Conversation
Moving the cursor calls -[ViewController browserRemoteInputControllerHoverStateAtCursorPoint:]
from touchesMoved:, which queried the page synchronously. That evaluation spins the run loop
waiting on the out-of-process web content, and spinning it from inside event delivery
dispatches the next UIEvent, re-entering -[UIApplication sendEvent:] while the first call is
still on the stack. UIKit traps on that from tvOS 27 onwards, so the app dies with
EXC_BREAKPOINT during ordinary cursor movement.
Confirmed by device crash reports, which show the recursion directly:
UIApplication sendEvent: <- browser_sendEvent: <- updateCycleEntry
<- NSRunLoop runMode:beforeDate: <- BrowserPumpRunLoopUntil
<- stringByEvaluatingJavaScriptFromString: <- DOMPointForCursorOrigin:
<- hoverStateAtCursorPoint: <- touchesMoved: <- UIWindow sendEvent:
<- browser_sendEvent:
Track the depth of sendEvent: in the existing swizzle, refuse to pump the run loop while an
event is being delivered, and answer the hover query from the last known value, refreshing it
on a later turn of the run loop where blocking is safe. The refresh is throttled to ten a
second with one in flight, so the pointer cursor still tracks links.
Also bound the pump at two seconds, so a page that never calls back cannot hang the app.
The click path is unaffected: it already defers through performSelector:afterDelay:, so it
runs outside event delivery.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Problem
On tvOS 26/27 the app dies with
EXC_BREAKPOINTduring ordinary use — not on any particular page or action, but while simply moving the cursor. It looks like a random quit back to the Home Screen.Cause
-[ViewController browserRemoteInputControllerHoverStateAtCursorPoint:]is called fromtouchesMoved:to decide whether the cursor should show the arrow or the pointer. It answers by querying the page synchronously, via-[BrowserWebView stringByEvaluatingJavaScriptFromString:].That evaluation is only synchronous by virtue of
BrowserPumpRunLoopUntilspinning the run loop until WebKit calls back. WebKit runs the page out of process, so this blocks the main thread — and spinning the run loop from inside UIKit event delivery dispatches the nextUIEvent, re-entering-[UIApplication sendEvent:]while the first call is still on the stack.UIKit traps on that from tvOS 26 onwards. Earlier releases tolerated the re-entrancy, which is why this only shows up now.
The device crash report shows the recursion directly — note
browser_sendEvent:appearing twice in one stack:Fix
1. Never pump the run loop during event delivery. The existing
sendEvent:swizzle now tracks its own nesting depth, andstringByEvaluatingJavaScriptFromString:returnsnilinstead of pumping while an event is in flight. That closes the whole class of bug rather than this one call site.2. Keep the pointer cursor working. The hover query now answers from the last known value while an event is being delivered, and schedules a refresh on a later turn of the run loop, where blocking is safe. The refresh is throttled to ten per second with one in flight, so the cursor still tracks links — it can lag by a frame, which is not perceptible in use.
3. Bound the pump.
BrowserPumpRunLoopUntilhad no timeout at all, so a page that never calls back could spin the main thread indefinitely and get the app killed by the watchdog. It now gives up after two seconds.The click path is unaffected: it already defers through
performSelector:withObject:afterDelay:, so it runs outside event delivery. I audited the rest of the input path — the hover query was the only synchronous evaluation reachable fromsendEvent:.Testing
Reproduced and fixed on Apple TV 4K (3rd generation), tvOS 27.0 (24J5325d), built with Xcode 27.0 against the tvOS 27.0 SDK. Before the change, moving the cursor around a page reliably killed the app within a minute or two; ~15 crash reports pulled off the device all show the recursion above. After the change, the same usage no longer crashes. Also builds and runs clean in the tvOS 27 simulator.
Scope of what I actually verified: one device, one tvOS version, Debug configuration. I have not tested Release builds, older Apple TV hardware, or tvOS 26 — though the trapping behaviour is a UIKit change rather than anything device-specific, so I would expect tvOS 26 to be affected the same way.
Note this is independent of #68 and can be merged on its own, in either order.
🤖 Generated with Claude Code