From 6170143b6d9cc8a6532217ab3dbdfa910f1ad4c3 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 19 Feb 2026 20:41:22 -0800 Subject: [PATCH] Add deep split/devtools reparent diagnostics --- Sources/AppDelegate.swift | 40 +++++++++++-- Sources/Panels/BrowserPanelView.swift | 84 ++++++++++++++++++++++----- 2 files changed, 105 insertions(+), 19 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 1eb89d94..8091005e 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -2050,20 +2050,52 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent case .up: directionLabel = "up" case .down: directionLabel = "down" } + let keyWindow = NSApp.keyWindow + let firstResponder = keyWindow?.firstResponder + let firstResponderType = firstResponder.map { String(describing: type(of: $0)) } ?? "nil" + let firstResponderPtr = firstResponder.map { String(describing: Unmanaged.passUnretained($0).toOpaque()) } ?? "nil" + let firstResponderWindow: Int = { + if let v = firstResponder as? NSView { + return v.window?.windowNumber ?? -1 + } + if let w = firstResponder as? NSWindow { + return w.windowNumber + } + return -1 + }() + let splitContext = "keyWin=\(keyWindow?.windowNumber ?? -1) mainWin=\(NSApp.mainWindow?.windowNumber ?? -1) fr=\(firstResponderType)@\(firstResponderPtr) frWin=\(firstResponderWindow)" if let browser = tabManager?.focusedBrowserPanel { - dlog("split.shortcut dir=\(directionLabel) pre panel=\(browser.id.uuidString.prefix(5)) \(browser.debugDeveloperToolsStateSummary())") + let webWindow = browser.webView.window?.windowNumber ?? -1 + let webSuperview = browser.webView.superview.map { String(describing: Unmanaged.passUnretained($0).toOpaque()) } ?? "nil" + dlog("split.shortcut dir=\(directionLabel) pre panel=\(browser.id.uuidString.prefix(5)) \(browser.debugDeveloperToolsStateSummary()) webWin=\(webWindow) webSuper=\(webSuperview) \(splitContext)") } else { - dlog("split.shortcut dir=\(directionLabel) pre panel=nil") + dlog("split.shortcut dir=\(directionLabel) pre panel=nil \(splitContext)") } #endif tabManager?.createSplit(direction: direction) #if DEBUG DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { [weak self] in + let keyWindow = NSApp.keyWindow + let firstResponder = keyWindow?.firstResponder + let firstResponderType = firstResponder.map { String(describing: type(of: $0)) } ?? "nil" + let firstResponderPtr = firstResponder.map { String(describing: Unmanaged.passUnretained($0).toOpaque()) } ?? "nil" + let firstResponderWindow: Int = { + if let v = firstResponder as? NSView { + return v.window?.windowNumber ?? -1 + } + if let w = firstResponder as? NSWindow { + return w.windowNumber + } + return -1 + }() + let splitContext = "keyWin=\(keyWindow?.windowNumber ?? -1) mainWin=\(NSApp.mainWindow?.windowNumber ?? -1) fr=\(firstResponderType)@\(firstResponderPtr) frWin=\(firstResponderWindow)" if let browser = self?.tabManager?.focusedBrowserPanel { - dlog("split.shortcut dir=\(directionLabel) post panel=\(browser.id.uuidString.prefix(5)) \(browser.debugDeveloperToolsStateSummary())") + let webWindow = browser.webView.window?.windowNumber ?? -1 + let webSuperview = browser.webView.superview.map { String(describing: Unmanaged.passUnretained($0).toOpaque()) } ?? "nil" + dlog("split.shortcut dir=\(directionLabel) post panel=\(browser.id.uuidString.prefix(5)) \(browser.debugDeveloperToolsStateSummary()) webWin=\(webWindow) webSuper=\(webSuperview) \(splitContext)") } else { - dlog("split.shortcut dir=\(directionLabel) post panel=nil") + dlog("split.shortcut dir=\(directionLabel) post panel=nil \(splitContext)") } } recordGotoSplitSplitIfNeeded(direction: direction) diff --git a/Sources/Panels/BrowserPanelView.swift b/Sources/Panels/BrowserPanelView.swift index c0aeee61..954d3768 100644 --- a/Sources/Panels/BrowserPanelView.swift +++ b/Sources/Panels/BrowserPanelView.swift @@ -2566,11 +2566,31 @@ struct WebViewRepresentable: NSViewRepresentable { _ panel: BrowserPanel, event: String, generation: Int, - retryCount: Int + retryCount: Int, + details: String? = nil ) { - dlog( - "browser.devtools event=\(event) panel=\(panel.id.uuidString.prefix(5)) generation=\(generation) retry=\(retryCount) \(panel.debugDeveloperToolsStateSummary())" - ) + var line = "browser.devtools event=\(event) panel=\(panel.id.uuidString.prefix(5)) generation=\(generation) retry=\(retryCount) \(panel.debugDeveloperToolsStateSummary())" + if let details, !details.isEmpty { + line += " \(details)" + } + dlog(line) + } + + private static func objectID(_ object: AnyObject?) -> String { + guard let object else { return "nil" } + return String(describing: Unmanaged.passUnretained(object).toOpaque()) + } + + private static func responderDescription(_ responder: NSResponder?) -> String { + guard let responder else { return "nil" } + return "\(type(of: responder))@\(objectID(responder))" + } + + private static func attachContext(webView: WKWebView, host: NSView) -> String { + let hostWindow = host.window?.windowNumber ?? -1 + let webWindow = webView.window?.windowNumber ?? -1 + let firstResponder = (webView.window ?? host.window)?.firstResponder + return "host=\(objectID(host)) hostWin=\(hostWindow) hostInWin=\(host.window == nil ? 0 : 1) oldSuper=\(objectID(webView.superview)) webWin=\(webWindow) webInWin=\(webView.window == nil ? 0 : 1) fr=\(responderDescription(firstResponder))" } #endif @@ -2654,7 +2674,8 @@ struct WebViewRepresentable: NSViewRepresentable { panel, event: "retry.waitingForWindow", generation: generation, - retryCount: coordinator.attachRetryCount + retryCount: coordinator.attachRetryCount, + details: attachContext(webView: webView, host: host) ) } #endif @@ -2675,10 +2696,25 @@ struct WebViewRepresentable: NSViewRepresentable { } coordinator.attachRetryCount = 0 + #if DEBUG + logDevToolsState( + panel, + event: "retry.attach.begin", + generation: generation, + retryCount: 0, + details: attachContext(webView: webView, host: host) + ) + #endif attachWebView(webView, to: host) panel.restoreDeveloperToolsAfterAttachIfNeeded() #if DEBUG - logDevToolsState(panel, event: "retry.attached", generation: generation, retryCount: 0) + logDevToolsState( + panel, + event: "retry.attached", + generation: generation, + retryCount: 0, + details: attachContext(webView: webView, host: host) + ) #endif } @@ -2705,7 +2741,8 @@ struct WebViewRepresentable: NSViewRepresentable { panel, event: "detach.skipped.offWindowDevTools", generation: context.coordinator.attachGeneration, - retryCount: context.coordinator.attachRetryCount + retryCount: context.coordinator.attachRetryCount, + details: Self.attachContext(webView: webView, host: nsView) ) #endif return @@ -2716,7 +2753,8 @@ struct WebViewRepresentable: NSViewRepresentable { panel, event: "detach.beforeSync", generation: context.coordinator.attachGeneration, - retryCount: context.coordinator.attachRetryCount + retryCount: context.coordinator.attachRetryCount, + details: Self.attachContext(webView: webView, host: nsView) ) #endif panel.syncDeveloperToolsPreferenceFromInspector(preserveVisibleIntent: true) @@ -2725,7 +2763,8 @@ struct WebViewRepresentable: NSViewRepresentable { panel, event: "detach.afterSync", generation: context.coordinator.attachGeneration, - retryCount: context.coordinator.attachRetryCount + retryCount: context.coordinator.attachRetryCount, + details: Self.attachContext(webView: webView, host: nsView) ) #endif context.coordinator.attachRetryWorkItem?.cancel() @@ -2748,7 +2787,8 @@ struct WebViewRepresentable: NSViewRepresentable { panel, event: "detach.done", generation: context.coordinator.attachGeneration, - retryCount: context.coordinator.attachRetryCount + retryCount: context.coordinator.attachRetryCount, + details: Self.attachContext(webView: webView, host: nsView) ) #endif return @@ -2768,7 +2808,8 @@ struct WebViewRepresentable: NSViewRepresentable { panel, event: "attach.defer.offWindow", generation: context.coordinator.attachGeneration, - retryCount: context.coordinator.attachRetryCount + retryCount: context.coordinator.attachRetryCount, + details: Self.attachContext(webView: webView, host: nsView) ) #endif Self.scheduleAttachRetry( @@ -2779,6 +2820,15 @@ struct WebViewRepresentable: NSViewRepresentable { generation: context.coordinator.attachGeneration ) } else { + #if DEBUG + Self.logDevToolsState( + panel, + event: "attach.immediate.begin", + generation: context.coordinator.attachGeneration, + retryCount: context.coordinator.attachRetryCount, + details: Self.attachContext(webView: webView, host: nsView) + ) + #endif Self.attachWebView(webView, to: nsView) panel.restoreDeveloperToolsAfterAttachIfNeeded() #if DEBUG @@ -2786,7 +2836,8 @@ struct WebViewRepresentable: NSViewRepresentable { panel, event: "attach.immediate", generation: context.coordinator.attachGeneration, - retryCount: context.coordinator.attachRetryCount + retryCount: context.coordinator.attachRetryCount, + details: Self.attachContext(webView: webView, host: nsView) ) #endif } @@ -2802,7 +2853,8 @@ struct WebViewRepresentable: NSViewRepresentable { panel, event: "attach.alreadyAttached", generation: context.coordinator.attachGeneration, - retryCount: context.coordinator.attachRetryCount + retryCount: context.coordinator.attachRetryCount, + details: Self.attachContext(webView: webView, host: nsView) ) #endif } @@ -2853,7 +2905,8 @@ struct WebViewRepresentable: NSViewRepresentable { panel, event: "dismantle.skipDetach.devTools", generation: coordinator.attachGeneration, - retryCount: coordinator.attachRetryCount + retryCount: coordinator.attachRetryCount, + details: attachContext(webView: webView, host: nsView) ) #endif return @@ -2867,7 +2920,8 @@ struct WebViewRepresentable: NSViewRepresentable { panel, event: "dismantle.detached", generation: coordinator.attachGeneration, - retryCount: coordinator.attachRetryCount + retryCount: coordinator.attachRetryCount, + details: attachContext(webView: webView, host: nsView) ) } #endif