Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions Sources/BrowserWindowPortal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1489,6 +1489,12 @@ final class BrowserPaneDropTargetView: NSView {

final class WindowBrowserSlotView: NSView {
override var isOpaque: Bool { false }
override var isHidden: Bool {
didSet {
guard isHidden, !oldValue, let window else { return }
yieldOwnedFirstResponderIfNeeded(in: window, reason: "slotHidden")
}
}
private let paneDropTargetView = BrowserPaneDropTargetView(frame: .zero)
private let dropZoneOverlayView = BrowserDropZoneOverlayView(frame: .zero)
private var searchOverlayHostingView: NSHostingView<BrowserSearchOverlay>?
Expand Down Expand Up @@ -1527,6 +1533,13 @@ final class WindowBrowserSlotView: NSView {
nil
}

override func viewWillMove(toWindow newWindow: NSWindow?) {
if newWindow == nil, let currentWindow = window {
yieldOwnedFirstResponderIfNeeded(in: currentWindow, reason: "slotWillLeaveWindow")
}
super.viewWillMove(toWindow: newWindow)
}

override func layout() {
super.layout()
paneDropTargetView.frame = bounds
Expand Down Expand Up @@ -1669,6 +1682,23 @@ final class WindowBrowserSlotView: NSView {
return window.makeFirstResponder(nil)
}

@discardableResult
private func yieldOwnedFirstResponderIfNeeded(in window: NSWindow, reason: String) -> Bool {
guard let firstResponder = window.firstResponder,
let owningView = firstResponder.browserPortalOwningView,
owningView === self || owningView.isDescendant(of: self) else {
return false
}
#if DEBUG
dlog(
"browser.slot.firstResponder.yield reason=\(reason) " +
"slot=\(browserPortalDebugToken(self)) " +
"responder=\(String(describing: type(of: firstResponder)))"
)
#endif
return window.makeFirstResponder(nil)
}

func pinHostedWebView(_ webView: WKWebView) {
guard webView.superview === self else { return }

Expand Down
44 changes: 43 additions & 1 deletion cmuxTests/CmuxWebViewKeyEquivalentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2434,7 +2434,9 @@ final class BrowserSessionHistoryRestoreTests: XCTestCase {

@MainActor
final class BrowserDeveloperToolsVisibilityPersistenceTests: XCTestCase {
private final class WKInspectorProbeView: NSView {}
private final class WKInspectorProbeView: NSView {
override var acceptsFirstResponder: Bool { true }
}

private final class FakeInspector: NSObject {
private(set) var attachCount = 0
Expand Down Expand Up @@ -11468,6 +11470,46 @@ final class BrowserWindowPortalLifecycleTests: XCTestCase {
)
}

func testHidingBrowserSlotYieldsOwnedInspectorFirstResponder() {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 520, height: 320),
styleMask: [.titled, .closable],
backing: .buffered,
defer: false
)
defer { window.orderOut(nil) }
realizeWindowLayout(window)

guard let contentView = window.contentView else {
XCTFail("Expected content view")
return
}

let slot = WindowBrowserSlotView(frame: NSRect(x: 40, y: 24, width: 260, height: 180))
contentView.addSubview(slot)

let inspectorContainer = NSView(frame: slot.bounds)
inspectorContainer.autoresizingMask = [.width, .height]
let inspectorView = WKInspectorProbeView(frame: inspectorContainer.bounds)
inspectorView.autoresizingMask = [.width, .height]
inspectorContainer.addSubview(inspectorView)
slot.addSubview(inspectorContainer)
contentView.layoutSubtreeIfNeeded()

XCTAssertTrue(
window.makeFirstResponder(inspectorView),
Comment thread
lawrencecchen marked this conversation as resolved.
Resolved
Comment thread
lawrencecchen marked this conversation as resolved.
Resolved
"Precondition failed: inspector probe should become first responder"
Comment thread
lawrencecchen marked this conversation as resolved.
Resolved
)
XCTAssertTrue(window.firstResponder === inspectorView)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

slot.isHidden = true

XCTAssertNil(
window.firstResponder,
"Hiding a browser slot should yield any owned inspector responder before it goes off-screen"
)
Comment thread
lawrencecchen marked this conversation as resolved.
Resolved
}

func testHiddenPortalSyncDoesNotStealLocallyHostedDevToolsWebViewDuringResize() {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 520, height: 320),
Expand Down
Loading