Fix omnibar tracking timeout using background queue (#46)

The 3-second safety net that posts a synthetic mouseUp to break out of
NSTextView's stuck tracking loop was dispatched on the main queue. Since
super.mouseDown blocks the main thread in the tracking loop, the timeout
could never fire. Use a background queue instead (NSApp.postEvent is
thread-safe). Use DispatchWorkItem.isCancelled for atomic cancellation.
This commit is contained in:
Lawrence Chen 2026-02-17 03:35:49 -08:00 committed by GitHub
parent 100575a63e
commit 2678606a20
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1742,19 +1742,20 @@ private final class OmnibarNativeTextField: NSTextField {
} else {
// Already editing allow normal click-to-place-cursor and drag-to-select.
// Guard against a stuck tracking loop by posting a synthetic mouseUp after
// a timeout so the main thread can't be blocked indefinitely.
var trackingFinished = false
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { [weak self] in
guard !trackingFinished, let self, let window = self.window else { return }
#if DEBUG
dlog("browser.omnibarTrackingTimeout — forcing mouseUp")
#endif
// a timeout. IMPORTANT: must use a background queue because super.mouseDown
// blocks the main thread in NSTextView's tracking loop, so
// DispatchQueue.main.asyncAfter would never fire.
let cancelled = DispatchWorkItem { /* sentinel */ }
let windowNumber = window?.windowNumber ?? 0
let location = event.locationInWindow
DispatchQueue.global(qos: .userInteractive).asyncAfter(deadline: .now() + 3.0) {
guard !cancelled.isCancelled else { return }
if let fakeUp = NSEvent.mouseEvent(
with: .leftMouseUp,
location: event.locationInWindow,
location: location,
modifierFlags: [],
timestamp: ProcessInfo.processInfo.systemUptime,
windowNumber: window.windowNumber,
windowNumber: windowNumber,
context: nil,
eventNumber: 0,
clickCount: 1,
@ -1764,7 +1765,7 @@ private final class OmnibarNativeTextField: NSTextField {
}
}
super.mouseDown(with: event)
trackingFinished = true
cancelled.cancel()
}
}