Avoid drag-handle key exclusivity conflicts
Fixes CMUXTERM-MACOS-CV
This commit is contained in:
parent
b75637530a
commit
f82dfa34e9
2 changed files with 58 additions and 10 deletions
|
|
@ -49,8 +49,13 @@ func performStandardTitlebarDoubleClick(window: NSWindow?) -> Bool {
|
|||
return true
|
||||
}
|
||||
|
||||
private var windowDragSuppressionDepthKey: UInt8 = 0
|
||||
private var windowDragTopHitResolutionDepthKey: UInt8 = 0
|
||||
private enum WindowDragHandleAssociatedObjectKeys {
|
||||
private static let suppressionDepthToken = NSObject()
|
||||
private static let topHitResolutionDepthToken = NSObject()
|
||||
|
||||
static let suppressionDepth = UnsafeRawPointer(Unmanaged.passUnretained(suppressionDepthToken).toOpaque())
|
||||
static let topHitResolutionDepth = UnsafeRawPointer(Unmanaged.passUnretained(topHitResolutionDepthToken).toOpaque())
|
||||
}
|
||||
|
||||
func beginWindowDragSuppression(window: NSWindow?) -> Int? {
|
||||
guard let window else { return nil }
|
||||
|
|
@ -58,7 +63,7 @@ func beginWindowDragSuppression(window: NSWindow?) -> Int? {
|
|||
let next = current + 1
|
||||
objc_setAssociatedObject(
|
||||
window,
|
||||
&windowDragSuppressionDepthKey,
|
||||
WindowDragHandleAssociatedObjectKeys.suppressionDepth,
|
||||
NSNumber(value: next),
|
||||
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
||||
)
|
||||
|
|
@ -71,11 +76,16 @@ func endWindowDragSuppression(window: NSWindow?) -> Int {
|
|||
let current = windowDragSuppressionDepth(window: window)
|
||||
let next = max(0, current - 1)
|
||||
if next == 0 {
|
||||
objc_setAssociatedObject(window, &windowDragSuppressionDepthKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
objc_setAssociatedObject(
|
||||
window,
|
||||
WindowDragHandleAssociatedObjectKeys.suppressionDepth,
|
||||
nil,
|
||||
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
||||
)
|
||||
} else {
|
||||
objc_setAssociatedObject(
|
||||
window,
|
||||
&windowDragSuppressionDepthKey,
|
||||
WindowDragHandleAssociatedObjectKeys.suppressionDepth,
|
||||
NSNumber(value: next),
|
||||
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
||||
)
|
||||
|
|
@ -85,7 +95,7 @@ func endWindowDragSuppression(window: NSWindow?) -> Int {
|
|||
|
||||
func windowDragSuppressionDepth(window: NSWindow?) -> Int {
|
||||
guard let window,
|
||||
let value = objc_getAssociatedObject(window, &windowDragSuppressionDepthKey) as? NSNumber else {
|
||||
let value = objc_getAssociatedObject(window, WindowDragHandleAssociatedObjectKeys.suppressionDepth) as? NSNumber else {
|
||||
return 0
|
||||
}
|
||||
return value.intValue
|
||||
|
|
@ -131,7 +141,10 @@ func withTemporaryWindowMovableEnabled(window: NSWindow?, _ body: () -> Void) ->
|
|||
private enum WindowDragHandleHitTestState {
|
||||
static func depth(window: NSWindow?) -> Int {
|
||||
guard let window,
|
||||
let value = objc_getAssociatedObject(window, &windowDragTopHitResolutionDepthKey) as? NSNumber else {
|
||||
let value = objc_getAssociatedObject(
|
||||
window,
|
||||
WindowDragHandleAssociatedObjectKeys.topHitResolutionDepth
|
||||
) as? NSNumber else {
|
||||
return 0
|
||||
}
|
||||
return value.intValue
|
||||
|
|
@ -142,7 +155,7 @@ private enum WindowDragHandleHitTestState {
|
|||
let next = depth(window: window) + 1
|
||||
objc_setAssociatedObject(
|
||||
window,
|
||||
&windowDragTopHitResolutionDepthKey,
|
||||
WindowDragHandleAssociatedObjectKeys.topHitResolutionDepth,
|
||||
NSNumber(value: next),
|
||||
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
||||
)
|
||||
|
|
@ -156,14 +169,14 @@ private enum WindowDragHandleHitTestState {
|
|||
if next == 0 {
|
||||
objc_setAssociatedObject(
|
||||
window,
|
||||
&windowDragTopHitResolutionDepthKey,
|
||||
WindowDragHandleAssociatedObjectKeys.topHitResolutionDepth,
|
||||
nil,
|
||||
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
||||
)
|
||||
} else {
|
||||
objc_setAssociatedObject(
|
||||
window,
|
||||
&windowDragTopHitResolutionDepthKey,
|
||||
WindowDragHandleAssociatedObjectKeys.topHitResolutionDepth,
|
||||
NSNumber(value: next),
|
||||
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5912,6 +5912,13 @@ final class WindowDragHandleHitTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
private final class ReentrantDragHandleView: NSView {
|
||||
override func hitTest(_ point: NSPoint) -> NSView? {
|
||||
let shouldCapture = windowDragHandleShouldCaptureHit(point, in: self, eventType: .leftMouseDown)
|
||||
return shouldCapture ? self : nil
|
||||
}
|
||||
}
|
||||
|
||||
func testDragHandleCapturesHitWhenNoSiblingClaimsPoint() {
|
||||
let container = NSView(frame: NSRect(x: 0, y: 0, width: 220, height: 36))
|
||||
let dragHandle = NSView(frame: container.bounds)
|
||||
|
|
@ -6079,6 +6086,34 @@ final class WindowDragHandleHitTests: XCTestCase {
|
|||
"Subview mutations during hit testing should not crash or break drag-handle capture"
|
||||
)
|
||||
}
|
||||
|
||||
func testDragHandleTopHitResolutionSurvivesSameWindowReentrancy() {
|
||||
let point = NSPoint(x: 180, y: 18)
|
||||
let window = NSWindow(
|
||||
contentRect: NSRect(x: 0, y: 0, width: 220, height: 36),
|
||||
styleMask: [.titled, .closable],
|
||||
backing: .buffered,
|
||||
defer: false
|
||||
)
|
||||
defer { window.orderOut(nil) }
|
||||
guard let contentView = window.contentView else {
|
||||
XCTFail("Expected content view")
|
||||
return
|
||||
}
|
||||
|
||||
let container = NSView(frame: contentView.bounds)
|
||||
container.autoresizingMask = [.width, .height]
|
||||
contentView.addSubview(container)
|
||||
|
||||
let dragHandle = ReentrantDragHandleView(frame: container.bounds)
|
||||
dragHandle.autoresizingMask = [.width, .height]
|
||||
container.addSubview(dragHandle)
|
||||
|
||||
XCTAssertTrue(
|
||||
windowDragHandleShouldCaptureHit(point, in: dragHandle, eventType: .leftMouseDown),
|
||||
"Reentrant same-window top-hit resolution should not trigger exclusivity crashes"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue