Fix portal browser click focus after workspace switch (#908)

This commit is contained in:
Austin Wang 2026-03-04 18:36:26 -08:00 committed by GitHub
parent 895fb802c8
commit 34989e8ad0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 73 additions and 0 deletions

View file

@ -9147,6 +9147,12 @@ private extension NSWindow {
if let eventWindow = event.window, eventWindow !== window {
return nil
}
if let portalWebView = BrowserWindowPortalRegistry.webViewAtWindowPoint(
event.locationInWindow,
in: window
) as? CmuxWebView {
return portalWebView
}
guard let hitView = cmuxHitViewForCurrentEvent(in: window, event: event) else {
return nil
}

View file

@ -1196,6 +1196,12 @@ enum BrowserWindowPortalRegistry {
portalsByWindowId[windowId]?.detachWebView(withId: webViewId)
}
static func webViewAtWindowPoint(_ windowPoint: NSPoint, in window: NSWindow) -> WKWebView? {
let windowId = ObjectIdentifier(window)
guard let portal = portalsByWindowId[windowId] else { return nil }
return portal.webViewAtWindowPoint(windowPoint)
}
#if DEBUG
static func debugPortalCount() -> Int {
portalsByWindowId.count

View file

@ -411,6 +411,67 @@ final class CmuxWebViewKeyEquivalentTests: XCTestCase {
XCTAssertFalse(window.makeFirstResponder(descendant), "Expected pointer bypass to be limited to click context")
}
@MainActor
func testWindowFirstResponderGuardAllowsPointerInitiatedClickFocusForPortalHostedWebView() {
_ = NSApplication.shared
AppDelegate.installWindowResponderSwizzlesForTesting()
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 640, height: 420),
styleMask: [.titled, .closable],
backing: .buffered,
defer: false
)
let container = NSView(frame: window.contentRect(forFrameRect: window.frame))
window.contentView = container
let anchor = NSView(frame: NSRect(x: 80, y: 60, width: 240, height: 150))
container.addSubview(anchor)
let webView = CmuxWebView(frame: .zero, configuration: WKWebViewConfiguration())
let descendant = FirstResponderView(frame: NSRect(x: 0, y: 0, width: 10, height: 10))
webView.addSubview(descendant)
window.makeKeyAndOrderFront(nil)
container.layoutSubtreeIfNeeded()
RunLoop.current.run(until: Date().addingTimeInterval(0.05))
BrowserWindowPortalRegistry.bind(webView: webView, to: anchor, visibleInUI: true, zPriority: 1)
BrowserWindowPortalRegistry.synchronizeForAnchor(anchor)
defer {
BrowserWindowPortalRegistry.detach(webView: webView)
AppDelegate.clearWindowFirstResponderGuardTesting()
window.orderOut(nil)
}
webView.allowsFirstResponderAcquisition = false
_ = window.makeFirstResponder(nil)
XCTAssertFalse(window.makeFirstResponder(descendant), "Expected blocked focus without pointer click context")
let timestamp = ProcessInfo.processInfo.systemUptime
let pointerPointInContent = NSPoint(x: anchor.frame.midX, y: anchor.frame.midY)
let pointerPointInWindow = container.convert(pointerPointInContent, to: nil)
let pointerDownEvent = NSEvent.mouseEvent(
with: .leftMouseDown,
location: pointerPointInWindow,
modifierFlags: [],
timestamp: timestamp,
windowNumber: window.windowNumber,
context: nil,
eventNumber: 1,
clickCount: 1,
pressure: 1.0
)
XCTAssertNotNil(pointerDownEvent)
AppDelegate.setWindowFirstResponderGuardTesting(currentEvent: pointerDownEvent, hitView: nil)
_ = window.makeFirstResponder(nil)
XCTAssertTrue(
window.makeFirstResponder(descendant),
"Expected portal-hosted pointer click context to bypass blocked policy"
)
}
@MainActor
func testWindowFirstResponderGuardAvoidsTextViewDelegateLookupForWebViewResolution() {
_ = NSApplication.shared