Fix browser devtools X-close persistence

This commit is contained in:
austinpower1258 2026-03-17 16:28:19 -07:00
parent 66f8f8b022
commit fabcb06891
2 changed files with 62 additions and 0 deletions

View file

@ -2152,6 +2152,9 @@ final class BrowserPanel: Panel, ObservableObject {
private var pendingDeveloperToolsTransitionTargetVisible: Bool?
private var developerToolsTransitionSettleWorkItem: DispatchWorkItem?
private let developerToolsTransitionSettleDelay: TimeInterval = 0.15
private let developerToolsAttachedManualCloseDetectionDelay: TimeInterval = 0.35
private var developerToolsLastAttachedHostAt: Date?
private var developerToolsLastKnownVisibleAt: Date?
private var detachedDeveloperToolsWindowCloseObserver: NSObjectProtocol?
private var preferredAttachedDeveloperToolsWidth: CGFloat?
private var preferredAttachedDeveloperToolsWidthFraction: CGFloat?
@ -3971,6 +3974,7 @@ extension BrowserPanel {
let isVisibleSelector = NSSelectorFromString("isVisible")
if inspector.cmuxCallBool(selector: isVisibleSelector) ?? false {
developerToolsDetachedOpenGraceDeadline = nil
developerToolsLastKnownVisibleAt = Date()
return true
}
@ -3980,6 +3984,9 @@ extension BrowserPanel {
guard inspector.responds(to: showSelector) else { return false }
inspector.cmuxCallVoid(selector: showSelector)
let visibleAfterShow = inspector.cmuxCallBool(selector: isVisibleSelector) ?? false
if visibleAfterShow {
developerToolsLastKnownVisibleAt = Date()
}
if preferredDeveloperToolsPresentation == .detached {
developerToolsDetachedOpenGraceDeadline = visibleAfterShow
? nil
@ -4196,6 +4203,7 @@ extension BrowserPanel {
developerToolsDetachedOpenGraceDeadline = nil
syncDeveloperToolsPresentationPreferenceFromUI()
preferredDeveloperToolsVisible = true
developerToolsLastKnownVisibleAt = Date()
cancelDeveloperToolsRestoreRetry()
return
}
@ -4203,9 +4211,47 @@ extension BrowserPanel {
return
}
preferredDeveloperToolsVisible = false
developerToolsLastKnownVisibleAt = nil
cancelDeveloperToolsRestoreRetry()
}
func noteDeveloperToolsHostAttached() {
developerToolsLastAttachedHostAt = Date()
developerToolsLastKnownVisibleAt = isDeveloperToolsVisible() ? Date() : nil
}
@discardableResult
func consumeAttachedDeveloperToolsManualCloseIfNeeded(inspector: NSObject? = nil) -> Bool {
guard preferredDeveloperToolsVisible else { return false }
guard preferredDeveloperToolsPresentation != .detached else { return false }
guard !isDeveloperToolsTransitionInFlight else { return false }
guard webView.superview != nil, webView.window != nil else { return false }
guard let developerToolsLastAttachedHostAt else { return false }
guard Date().timeIntervalSince(developerToolsLastAttachedHostAt) >= developerToolsAttachedManualCloseDetectionDelay else {
return false
}
guard developerToolsLastKnownVisibleAt != nil else { return false }
guard let inspector = inspector ?? webView.cmuxInspectorObject() else { return false }
guard let visible = inspector.cmuxCallBool(selector: NSSelectorFromString("isVisible")) else { return false }
guard !visible else {
developerToolsLastKnownVisibleAt = Date()
return false
}
preferredDeveloperToolsVisible = false
developerToolsDetachedOpenGraceDeadline = nil
developerToolsLastKnownVisibleAt = nil
forceDeveloperToolsRefreshOnNextAttach = false
cancelDeveloperToolsRestoreRetry()
#if DEBUG
dlog(
"browser.devtools attachedClose.consume panel=\(id.uuidString.prefix(5)) " +
"\(debugDeveloperToolsStateSummary()) \(debugDeveloperToolsGeometrySummary())"
)
#endif
return true
}
/// Called after WKWebView reattaches to keep inspector stable across split/layout churn.
func restoreDeveloperToolsAfterAttachIfNeeded() {
guard preferredDeveloperToolsVisible else {
@ -4226,6 +4272,7 @@ extension BrowserPanel {
if visible {
developerToolsDetachedOpenGraceDeadline = nil
syncDeveloperToolsPresentationPreferenceFromUI()
developerToolsLastKnownVisibleAt = Date()
#if DEBUG
if shouldForceRefresh {
dlog("browser.devtools refresh.consumeVisible panel=\(id.uuidString.prefix(5)) \(debugDeveloperToolsStateSummary())")
@ -4249,6 +4296,10 @@ extension BrowserPanel {
return
}
if consumeAttachedDeveloperToolsManualCloseIfNeeded(inspector: inspector) {
return
}
#if DEBUG
if shouldForceRefresh {
dlog("browser.devtools refresh.forceShowWhenHidden panel=\(id.uuidString.prefix(5)) \(debugDeveloperToolsStateSummary())")
@ -4264,6 +4315,7 @@ extension BrowserPanel {
let visibleAfterShow = inspector.cmuxCallBool(selector: NSSelectorFromString("isVisible")) ?? false
if visibleAfterShow {
syncDeveloperToolsPresentationPreferenceFromUI()
developerToolsLastKnownVisibleAt = Date()
cancelDeveloperToolsRestoreRetry()
scheduleDetachedDeveloperToolsWindowDismissal()
} else {

View file

@ -513,6 +513,14 @@ struct BrowserPanelView: View {
refreshSuggestions()
}
}
.onChange(of: isVisibleInUI) { visibleInUI in
guard !visibleInUI else { return }
// The attached WebKit inspector close button can hide DevTools without
// touching BrowserPanel's persisted intent. Capture the actual inspector
// visibility before the surface detaches so switching away and back
// does not resurrect a manually closed inspector.
panel.syncDeveloperToolsPreferenceFromInspector()
}
.onChange(of: isFocused) { focused in
#if DEBUG
logBrowserFocusState(
@ -5689,6 +5697,7 @@ struct WebViewRepresentable: NSViewRepresentable {
coordinator.lastPortalHostId = nil
coordinator.lastSynchronizedHostGeometryRevision = 0
if didAttachWebViewToLocalHost {
panel.noteDeveloperToolsHostAttached()
panel.restoreDeveloperToolsAfterAttachIfNeeded()
webView.needsLayout = true
webView.layoutSubtreeIfNeeded()
@ -5702,6 +5711,7 @@ struct WebViewRepresentable: NSViewRepresentable {
host.scheduleHostedInspectorDockConfigurationSync(reason: "localInline.update.async")
}
} else {
panel.consumeAttachedDeveloperToolsManualCloseIfNeeded()
host.scheduleHostedInspectorDockConfigurationSync(reason: "localInline.update")
}