Stabilize docked devtools side resize
This commit is contained in:
parent
8b66628b19
commit
06c5cac4df
2 changed files with 449 additions and 22 deletions
|
|
@ -4162,7 +4162,7 @@ private extension BrowserPanel {
|
|||
}
|
||||
}
|
||||
|
||||
private extension WKWebView {
|
||||
extension WKWebView {
|
||||
func cmuxInspectorObject() -> NSObject? {
|
||||
let selector = NSSelectorFromString("_inspector")
|
||||
guard responds(to: selector),
|
||||
|
|
@ -4171,6 +4171,16 @@ private extension WKWebView {
|
|||
}
|
||||
return inspector
|
||||
}
|
||||
|
||||
func cmuxInspectorFrontendWebView() -> WKWebView? {
|
||||
guard let inspector = cmuxInspectorObject() else { return nil }
|
||||
let selector = NSSelectorFromString("inspectorWebView")
|
||||
guard inspector.responds(to: selector),
|
||||
let inspectorWebView = inspector.perform(selector)?.takeUnretainedValue() as? WKWebView else {
|
||||
return nil
|
||||
}
|
||||
return inspectorWebView
|
||||
}
|
||||
}
|
||||
|
||||
private extension NSObject {
|
||||
|
|
|
|||
|
|
@ -3579,6 +3579,10 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
}
|
||||
|
||||
final class HostContainerView: NSView {
|
||||
private final class HostedInspectorSideDockContainerView: NSView {
|
||||
override var isOpaque: Bool { false }
|
||||
}
|
||||
|
||||
var onDidMoveToWindow: (() -> Void)?
|
||||
var onGeometryChanged: (() -> Void)?
|
||||
private(set) var geometryRevision: UInt64 = 0
|
||||
|
|
@ -3587,6 +3591,9 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
private var hostedWebViewConstraints: [NSLayoutConstraint] = []
|
||||
private weak var localInlineSlotView: WindowBrowserSlotView?
|
||||
private var localInlineSlotConstraints: [NSLayoutConstraint] = []
|
||||
private weak var hostedInspectorSideDockContainerView: HostedInspectorSideDockContainerView?
|
||||
private var hostedInspectorSideDockConstraints: [NSLayoutConstraint] = []
|
||||
private weak var hostedInspectorFrontendWebView: WKWebView?
|
||||
private struct HostedInspectorDividerHit {
|
||||
let containerView: NSView
|
||||
let pageView: NSView
|
||||
|
|
@ -3617,7 +3624,7 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
var cursor: NSCursor { .resizeLeftRight }
|
||||
}
|
||||
|
||||
private static let hostedInspectorDividerHitExpansion: CGFloat = 6
|
||||
private static let hostedInspectorDividerHitExpansion: CGFloat = 10
|
||||
private static let minimumHostedInspectorWidth: CGFloat = 1
|
||||
private var trackingArea: NSTrackingArea?
|
||||
private var activeDividerCursorKind: DividerCursorKind?
|
||||
|
|
@ -3625,9 +3632,13 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
private var preferredHostedInspectorWidth: CGFloat?
|
||||
private var preferredHostedInspectorWidthFraction: CGFloat?
|
||||
var onPreferredHostedInspectorWidthChanged: ((CGFloat, CGFloat?) -> Void)?
|
||||
private weak var hostedInspectorSideDockPageView: NSView?
|
||||
private weak var hostedInspectorSideDockInspectorView: NSView?
|
||||
private var hostedInspectorSideDockDockSide: HostedInspectorDockSide?
|
||||
private var isHostedInspectorDividerDragActive = false
|
||||
private var isApplyingHostedInspectorLayout = false
|
||||
private var hostedInspectorReapplyWorkItem: DispatchWorkItem?
|
||||
private var hostedInspectorDockConfigurationSyncWorkItem: DispatchWorkItem?
|
||||
private var lastHostedInspectorLayoutBoundsSize: NSSize?
|
||||
#if DEBUG
|
||||
private var lastLoggedHostedInspectorFrames: (page: NSRect, inspector: NSRect)?
|
||||
|
|
@ -3636,6 +3647,7 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
|
||||
deinit {
|
||||
hostedInspectorReapplyWorkItem?.cancel()
|
||||
hostedInspectorDockConfigurationSyncWorkItem?.cancel()
|
||||
if let trackingArea {
|
||||
removeTrackingArea(trackingArea)
|
||||
}
|
||||
|
|
@ -3665,6 +3677,36 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
preferredHostedInspectorWidthFraction = widthFraction
|
||||
}
|
||||
|
||||
func containsManagedLocalInlineContent(_ view: NSView) -> Bool {
|
||||
if let localInlineSlotView,
|
||||
view === localInlineSlotView || view.isDescendant(of: localInlineSlotView) {
|
||||
return true
|
||||
}
|
||||
if let hostedInspectorSideDockContainerView,
|
||||
view === hostedInspectorSideDockContainerView || view.isDescendant(of: hostedInspectorSideDockContainerView) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func currentHostedWebViewContainer(preferredSlotView: WindowBrowserSlotView) -> NSView {
|
||||
if let hostedInspectorSideDockContainerView,
|
||||
let hostedInspectorSideDockPageView,
|
||||
hostedWebView?.isDescendant(of: hostedInspectorSideDockContainerView) == true,
|
||||
hostedInspectorSideDockPageView.isDescendant(of: hostedInspectorSideDockContainerView) {
|
||||
return hostedInspectorSideDockContainerView
|
||||
}
|
||||
return preferredSlotView
|
||||
}
|
||||
|
||||
func setHostedInspectorFrontendWebView(_ webView: WKWebView?) {
|
||||
hostedInspectorFrontendWebView = webView
|
||||
}
|
||||
|
||||
private var hasStoredHostedInspectorWidthPreference: Bool {
|
||||
preferredHostedInspectorWidth != nil || preferredHostedInspectorWidthFraction != nil
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
private static func shouldLogPointerEvent(_ event: NSEvent?) -> Bool {
|
||||
switch event?.type {
|
||||
|
|
@ -3812,6 +3854,13 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
localInlineSlotView?.onHostedInspectorLayout = nil
|
||||
}
|
||||
|
||||
func prepareForWindowPortalHosting() {
|
||||
hostedInspectorDockConfigurationSyncWorkItem?.cancel()
|
||||
hostedInspectorDockConfigurationSyncWorkItem = nil
|
||||
deactivateHostedInspectorSideDockIfNeeded(reparentTo: localInlineSlotView)
|
||||
hostedInspectorFrontendWebView = nil
|
||||
}
|
||||
|
||||
func releaseHostedWebViewConstraints() {
|
||||
NSLayoutConstraint.deactivate(hostedWebViewConstraints)
|
||||
hostedWebViewConstraints = []
|
||||
|
|
@ -3819,13 +3868,14 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
}
|
||||
|
||||
func pinHostedWebView(_ webView: WKWebView, in container: NSView) {
|
||||
guard webView.superview === container else { return }
|
||||
guard webView.superview === container || webView.isDescendant(of: container) else { return }
|
||||
|
||||
let hasCompanionWKSubviews = Self.hasWebKitCompanionSubview(
|
||||
in: container,
|
||||
primaryWebView: webView
|
||||
)
|
||||
let needsPlainWebViewFrameReset =
|
||||
webView.superview === container &&
|
||||
!hasCompanionWKSubviews &&
|
||||
Self.frameDiffersFromBounds(webView.frame, bounds: container.bounds)
|
||||
let needsFrameHosting =
|
||||
|
|
@ -3849,7 +3899,7 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
// preserve WebKit-managed split frames when docked DevTools siblings exist.
|
||||
webView.translatesAutoresizingMaskIntoConstraints = true
|
||||
webView.autoresizingMask = [.width, .height]
|
||||
if !hasCompanionWKSubviews {
|
||||
if webView.superview === container && !hasCompanionWKSubviews {
|
||||
webView.frame = container.bounds
|
||||
}
|
||||
needsLayout = true
|
||||
|
|
@ -3877,12 +3927,159 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
return false
|
||||
}
|
||||
|
||||
private func ensureHostedInspectorSideDockContainerView() -> HostedInspectorSideDockContainerView {
|
||||
if let hostedInspectorSideDockContainerView,
|
||||
hostedInspectorSideDockContainerView.superview === self {
|
||||
hostedInspectorSideDockContainerView.isHidden = false
|
||||
return hostedInspectorSideDockContainerView
|
||||
}
|
||||
|
||||
let containerView = HostedInspectorSideDockContainerView(frame: bounds)
|
||||
containerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(containerView, positioned: .above, relativeTo: localInlineSlotView)
|
||||
hostedInspectorSideDockConstraints = [
|
||||
containerView.topAnchor.constraint(equalTo: topAnchor),
|
||||
containerView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
containerView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
containerView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
]
|
||||
NSLayoutConstraint.activate(hostedInspectorSideDockConstraints)
|
||||
hostedInspectorSideDockContainerView = containerView
|
||||
return containerView
|
||||
}
|
||||
|
||||
private func moveHostedInspectorSubviewIfNeeded(_ view: NSView, to container: NSView) {
|
||||
guard view.superview !== container else { return }
|
||||
let frameInWindow = view.superview?.convert(view.frame, to: nil) ?? convert(view.frame, to: nil)
|
||||
view.removeFromSuperview()
|
||||
container.addSubview(view, positioned: .above, relativeTo: nil)
|
||||
view.frame = container.convert(frameInWindow, from: nil)
|
||||
}
|
||||
|
||||
private func isHostedInspectorSideDockActive() -> Bool {
|
||||
guard let hostedInspectorSideDockContainerView,
|
||||
let hostedInspectorSideDockPageView,
|
||||
let hostedInspectorSideDockInspectorView else {
|
||||
return false
|
||||
}
|
||||
return hostedInspectorSideDockPageView.superview === hostedInspectorSideDockContainerView &&
|
||||
hostedInspectorSideDockInspectorView.superview === hostedInspectorSideDockContainerView
|
||||
}
|
||||
|
||||
private func isHostedInspectorSideDockHit(_ hit: HostedInspectorDividerHit) -> Bool {
|
||||
guard let hostedInspectorSideDockContainerView else { return false }
|
||||
return hit.containerView === hostedInspectorSideDockContainerView
|
||||
}
|
||||
|
||||
private func activateHostedInspectorSideDockIfNeeded(using hit: HostedInspectorDividerHit) {
|
||||
let containerView = ensureHostedInspectorSideDockContainerView()
|
||||
moveHostedInspectorSubviewIfNeeded(hit.pageView, to: containerView)
|
||||
moveHostedInspectorSubviewIfNeeded(hit.inspectorView, to: containerView)
|
||||
hostedInspectorSideDockPageView = hit.pageView
|
||||
hostedInspectorSideDockInspectorView = hit.inspectorView
|
||||
hostedInspectorSideDockDockSide = hit.dockSide
|
||||
layoutHostedInspectorSideDockIfNeeded(reason: "sideDock.activate")
|
||||
}
|
||||
|
||||
private func deactivateHostedInspectorSideDockIfNeeded(reparentTo slotView: WindowBrowserSlotView?) {
|
||||
guard let slotView,
|
||||
let pageView = hostedInspectorSideDockPageView,
|
||||
let inspectorView = hostedInspectorSideDockInspectorView else {
|
||||
hostedInspectorSideDockPageView = nil
|
||||
hostedInspectorSideDockInspectorView = nil
|
||||
hostedInspectorSideDockDockSide = nil
|
||||
hostedInspectorSideDockContainerView?.isHidden = true
|
||||
return
|
||||
}
|
||||
|
||||
moveHostedInspectorSubviewIfNeeded(pageView, to: slotView)
|
||||
moveHostedInspectorSubviewIfNeeded(inspectorView, to: slotView)
|
||||
hostedInspectorSideDockPageView = nil
|
||||
hostedInspectorSideDockInspectorView = nil
|
||||
hostedInspectorSideDockDockSide = nil
|
||||
hostedInspectorSideDockContainerView?.isHidden = true
|
||||
}
|
||||
|
||||
private func layoutHostedInspectorSideDockIfNeeded(reason: String) {
|
||||
guard let containerView = hostedInspectorSideDockContainerView,
|
||||
let pageView = hostedInspectorSideDockPageView,
|
||||
let inspectorView = hostedInspectorSideDockInspectorView,
|
||||
let dockSide = hostedInspectorSideDockDockSide else {
|
||||
return
|
||||
}
|
||||
let preferredWidth = resolvedPreferredHostedInspectorWidth(in: containerView.bounds) ?? max(0, inspectorView.frame.width)
|
||||
_ = applyHostedInspectorDividerWidth(
|
||||
preferredWidth,
|
||||
to: HostedInspectorDividerHit(
|
||||
containerView: containerView,
|
||||
pageView: pageView,
|
||||
inspectorView: inspectorView,
|
||||
dockSide: dockSide
|
||||
),
|
||||
reason: reason
|
||||
)
|
||||
}
|
||||
|
||||
fileprivate func scheduleHostedInspectorDockConfigurationSync(reason: String) {
|
||||
hostedInspectorDockConfigurationSyncWorkItem?.cancel()
|
||||
guard hostedInspectorFrontendWebView != nil else { return }
|
||||
let workItem = DispatchWorkItem { [weak self] in
|
||||
self?.syncHostedInspectorDockConfiguration(reason: reason)
|
||||
}
|
||||
hostedInspectorDockConfigurationSyncWorkItem = workItem
|
||||
DispatchQueue.main.async(execute: workItem)
|
||||
}
|
||||
|
||||
private func syncHostedInspectorDockConfiguration(reason: String) {
|
||||
hostedInspectorDockConfigurationSyncWorkItem = nil
|
||||
guard let hostedInspectorFrontendWebView else { return }
|
||||
hostedInspectorFrontendWebView.evaluateJavaScript(
|
||||
"typeof WI === 'undefined' ? null : WI.dockConfiguration"
|
||||
) { [weak self] result, _ in
|
||||
self?.applyHostedInspectorDockConfiguration(result as? String, reason: reason)
|
||||
}
|
||||
}
|
||||
|
||||
private func applyHostedInspectorDockConfiguration(_ dockConfiguration: String?, reason: String) {
|
||||
switch dockConfiguration {
|
||||
case "left":
|
||||
hostedInspectorSideDockDockSide = .leading
|
||||
if isHostedInspectorSideDockActive() {
|
||||
layoutHostedInspectorSideDockIfNeeded(reason: "\(reason).dockLeft")
|
||||
} else if let slotView = localInlineSlotView,
|
||||
let hit = hostedInspectorDividerCandidate(in: slotView),
|
||||
hit.dockSide == .leading {
|
||||
activateHostedInspectorSideDockIfNeeded(using: hit)
|
||||
}
|
||||
case "right":
|
||||
hostedInspectorSideDockDockSide = .trailing
|
||||
if isHostedInspectorSideDockActive() {
|
||||
layoutHostedInspectorSideDockIfNeeded(reason: "\(reason).dockRight")
|
||||
} else if let slotView = localInlineSlotView,
|
||||
let hit = hostedInspectorDividerCandidate(in: slotView),
|
||||
hit.dockSide == .trailing {
|
||||
activateHostedInspectorSideDockIfNeeded(using: hit)
|
||||
}
|
||||
default:
|
||||
if isHostedInspectorSideDockActive() {
|
||||
deactivateHostedInspectorSideDockIfNeeded(reparentTo: localInlineSlotView)
|
||||
if dockConfiguration == "bottom" {
|
||||
hostedInspectorFrontendWebView?.evaluateJavaScript(
|
||||
"typeof WI !== 'undefined' ? WI._dockBottom() : null",
|
||||
completionHandler: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidMoveToWindow() {
|
||||
super.viewDidMoveToWindow()
|
||||
if window == nil {
|
||||
clearActiveDividerCursor(restoreArrow: false)
|
||||
} else {
|
||||
scheduleHostedInspectorDividerReapply(reason: "viewDidMoveToWindow")
|
||||
scheduleHostedInspectorDockConfigurationSync(reason: "viewDidMoveToWindow")
|
||||
}
|
||||
window?.invalidateCursorRects(for: self)
|
||||
onDidMoveToWindow?()
|
||||
|
|
@ -3895,6 +4092,7 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
override func viewDidMoveToSuperview() {
|
||||
super.viewDidMoveToSuperview()
|
||||
scheduleHostedInspectorDividerReapply(reason: "viewDidMoveToSuperview")
|
||||
scheduleHostedInspectorDockConfigurationSync(reason: "viewDidMoveToSuperview")
|
||||
notifyGeometryChangedIfNeeded()
|
||||
#if DEBUG
|
||||
debugLogHostedInspectorLayoutIfNeeded(reason: "viewDidMoveToSuperview")
|
||||
|
|
@ -3905,6 +4103,11 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
super.layout()
|
||||
if let previousSize = lastHostedInspectorLayoutBoundsSize,
|
||||
Self.sizeApproximatelyEqual(previousSize, bounds.size, epsilon: 0.5) {
|
||||
if isHostedInspectorSideDockActive() {
|
||||
layoutHostedInspectorSideDockIfNeeded(reason: "host.layout.sideDock.sameSize")
|
||||
} else if !isHostedInspectorDividerDragActive && !hasStoredHostedInspectorWidthPreference {
|
||||
captureHostedInspectorPreferredWidthFromCurrentLayout(reason: "host.layout.sameSize")
|
||||
}
|
||||
notifyGeometryChangedIfNeeded()
|
||||
#if DEBUG
|
||||
debugLogHostedInspectorLayoutIfNeeded(reason: "layout")
|
||||
|
|
@ -3912,7 +4115,12 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
return
|
||||
}
|
||||
lastHostedInspectorLayoutBoundsSize = bounds.size
|
||||
captureHostedInspectorPreferredWidthFromCurrentLayout(reason: "host.layout")
|
||||
if isHostedInspectorSideDockActive() {
|
||||
layoutHostedInspectorSideDockIfNeeded(reason: "host.layout.sideDock")
|
||||
} else if !hasStoredHostedInspectorWidthPreference {
|
||||
captureHostedInspectorPreferredWidthFromCurrentLayout(reason: "host.layout")
|
||||
}
|
||||
scheduleHostedInspectorDockConfigurationSync(reason: "layout")
|
||||
notifyGeometryChangedIfNeeded()
|
||||
#if DEBUG
|
||||
debugLogHostedInspectorLayoutIfNeeded(reason: "layout")
|
||||
|
|
@ -3921,6 +4129,9 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
|
||||
override func setFrameOrigin(_ newOrigin: NSPoint) {
|
||||
super.setFrameOrigin(newOrigin)
|
||||
if isHostedInspectorSideDockActive() {
|
||||
layoutHostedInspectorSideDockIfNeeded(reason: "setFrameOrigin.sideDock")
|
||||
}
|
||||
window?.invalidateCursorRects(for: self)
|
||||
notifyGeometryChangedIfNeeded()
|
||||
#if DEBUG
|
||||
|
|
@ -3930,6 +4141,9 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
|
||||
override func setFrameSize(_ newSize: NSSize) {
|
||||
super.setFrameSize(newSize)
|
||||
if isHostedInspectorSideDockActive() {
|
||||
layoutHostedInspectorSideDockIfNeeded(reason: "setFrameSize.sideDock")
|
||||
}
|
||||
window?.invalidateCursorRects(for: self)
|
||||
notifyGeometryChangedIfNeeded()
|
||||
#if DEBUG
|
||||
|
|
@ -3986,16 +4200,26 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
return nil
|
||||
}
|
||||
if let hostedInspectorHit {
|
||||
let isSideDockHit = isHostedInspectorSideDockHit(hostedInspectorHit)
|
||||
if let nativeHit = nativeHostedInspectorHit(at: point, hostedInspectorHit: hostedInspectorHit) {
|
||||
#if DEBUG
|
||||
debugLogHitTest(stage: "hitTest.hostedInspectorNative", point: point, passThrough: false, hitView: nativeHit)
|
||||
#endif
|
||||
return nativeHit
|
||||
if !isSideDockHit ||
|
||||
(nativeHit !== hostedInspectorHit.inspectorView &&
|
||||
!hostedInspectorHit.inspectorView.isDescendant(of: nativeHit)) {
|
||||
return nativeHit
|
||||
}
|
||||
}
|
||||
#if DEBUG
|
||||
debugLogHitTest(stage: "hitTest.hostedInspectorFallback", point: point, passThrough: false, hitView: hostedInspectorHit.inspectorView)
|
||||
debugLogHitTest(
|
||||
stage: isSideDockHit ? "hitTest.hostedInspectorManual" : "hitTest.hostedInspectorFallback",
|
||||
point: point,
|
||||
passThrough: false,
|
||||
hitView: hostedInspectorHit.inspectorView
|
||||
)
|
||||
#endif
|
||||
return hostedInspectorHit.inspectorView
|
||||
return isSideDockHit ? self : hostedInspectorHit.inspectorView
|
||||
}
|
||||
let hit = super.hitTest(point)
|
||||
#if DEBUG
|
||||
|
|
@ -4005,17 +4229,106 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
}
|
||||
|
||||
override func mouseDown(with event: NSEvent) {
|
||||
super.mouseDown(with: event)
|
||||
let point = convert(event.locationInWindow, from: nil)
|
||||
guard let hostedInspectorHit = hostedInspectorDividerHit(at: point),
|
||||
isHostedInspectorSideDockHit(hostedInspectorHit) else {
|
||||
super.mouseDown(with: event)
|
||||
return
|
||||
}
|
||||
|
||||
hostedInspectorReapplyWorkItem?.cancel()
|
||||
isHostedInspectorDividerDragActive = true
|
||||
hostedInspectorDividerDrag = HostedInspectorDividerDragState(
|
||||
containerView: hostedInspectorHit.containerView,
|
||||
pageView: hostedInspectorHit.pageView,
|
||||
inspectorView: hostedInspectorHit.inspectorView,
|
||||
dockSide: hostedInspectorHit.dockSide,
|
||||
initialWindowX: event.locationInWindow.x,
|
||||
initialPageFrame: hostedInspectorHit.pageView.frame,
|
||||
initialInspectorFrame: hostedInspectorHit.inspectorView.frame
|
||||
)
|
||||
#if DEBUG
|
||||
debugLogHostedInspectorFrames(stage: "drag.start", point: point, hit: hostedInspectorHit)
|
||||
#endif
|
||||
}
|
||||
|
||||
override func mouseDragged(with event: NSEvent) {
|
||||
super.mouseDragged(with: event)
|
||||
guard let dragState = hostedInspectorDividerDrag else {
|
||||
super.mouseDragged(with: event)
|
||||
return
|
||||
}
|
||||
|
||||
let containerBounds = dragState.containerView.bounds
|
||||
let minimumInspectorWidth = Self.minimumHostedInspectorWidth
|
||||
let initialDividerX = dragState.dockSide.dividerX(
|
||||
pageFrame: dragState.initialPageFrame,
|
||||
inspectorFrame: dragState.initialInspectorFrame
|
||||
)
|
||||
let proposedDividerX = initialDividerX + (event.locationInWindow.x - dragState.initialWindowX)
|
||||
let clampedDividerX = dragState.dockSide.clampedDividerX(
|
||||
proposedDividerX,
|
||||
containerBounds: containerBounds,
|
||||
pageFrame: dragState.initialPageFrame,
|
||||
minimumInspectorWidth: minimumInspectorWidth
|
||||
)
|
||||
let inspectorWidth = dragState.dockSide.inspectorWidth(
|
||||
forDividerX: clampedDividerX,
|
||||
in: containerBounds
|
||||
)
|
||||
recordPreferredHostedInspectorWidth(inspectorWidth, containerBounds: containerBounds)
|
||||
_ = applyHostedInspectorDividerWidth(
|
||||
inspectorWidth,
|
||||
to: HostedInspectorDividerHit(
|
||||
containerView: dragState.containerView,
|
||||
pageView: dragState.pageView,
|
||||
inspectorView: dragState.inspectorView,
|
||||
dockSide: dragState.dockSide
|
||||
),
|
||||
reason: "drag"
|
||||
)
|
||||
#if DEBUG
|
||||
debugLogHostedInspectorFrames(
|
||||
stage: "drag.update",
|
||||
point: convert(event.locationInWindow, from: nil),
|
||||
hit: HostedInspectorDividerHit(
|
||||
containerView: dragState.containerView,
|
||||
pageView: dragState.pageView,
|
||||
inspectorView: dragState.inspectorView,
|
||||
dockSide: dragState.dockSide
|
||||
)
|
||||
)
|
||||
#endif
|
||||
updateDividerCursor(
|
||||
at: convert(event.locationInWindow, from: nil),
|
||||
hostedInspectorHit: HostedInspectorDividerHit(
|
||||
containerView: dragState.containerView,
|
||||
pageView: dragState.pageView,
|
||||
inspectorView: dragState.inspectorView,
|
||||
dockSide: dragState.dockSide
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override func mouseUp(with event: NSEvent) {
|
||||
let finalDragState = hostedInspectorDividerDrag
|
||||
hostedInspectorDividerDrag = nil
|
||||
isHostedInspectorDividerDragActive = false
|
||||
updateDividerCursor(at: convert(event.locationInWindow, from: nil))
|
||||
if let finalDragState {
|
||||
#if DEBUG
|
||||
debugLogHostedInspectorFrames(
|
||||
stage: "drag.end",
|
||||
point: convert(event.locationInWindow, from: nil),
|
||||
hit: HostedInspectorDividerHit(
|
||||
containerView: finalDragState.containerView,
|
||||
pageView: finalDragState.pageView,
|
||||
inspectorView: finalDragState.inspectorView,
|
||||
dockSide: finalDragState.dockSide
|
||||
)
|
||||
)
|
||||
#endif
|
||||
layoutHostedInspectorSideDockIfNeeded(reason: "drag.end")
|
||||
}
|
||||
super.mouseUp(with: event)
|
||||
}
|
||||
|
||||
|
|
@ -4094,11 +4407,19 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
}
|
||||
|
||||
private func hostedInspectorDividerCandidate() -> HostedInspectorDividerHit? {
|
||||
let inspectorCandidates = Self.visibleDescendants(in: self)
|
||||
hostedInspectorDividerCandidate(in: self)
|
||||
}
|
||||
|
||||
private func hostedInspectorDividerCandidate(in root: NSView) -> HostedInspectorDividerHit? {
|
||||
if let preferredHit = hostedInspectorDividerCandidateUsingKnownWebViews(in: root) {
|
||||
return preferredHit
|
||||
}
|
||||
|
||||
let inspectorCandidates = Self.visibleDescendants(in: root)
|
||||
.filter { Self.isVisibleHostedInspectorCandidate($0) && Self.isInspectorView($0) }
|
||||
.sorted { lhs, rhs in
|
||||
let lhsFrame = convert(lhs.bounds, from: lhs)
|
||||
let rhsFrame = convert(rhs.bounds, from: rhs)
|
||||
let lhsFrame = root.convert(lhs.bounds, from: lhs)
|
||||
let rhsFrame = root.convert(rhs.bounds, from: rhs)
|
||||
return lhsFrame.minX < rhsFrame.minX
|
||||
}
|
||||
|
||||
|
|
@ -4106,7 +4427,7 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
var bestScore = -CGFloat.greatestFiniteMagnitude
|
||||
|
||||
for inspectorCandidate in inspectorCandidates {
|
||||
guard let candidate = hostedInspectorDividerCandidate(startingAt: inspectorCandidate) else {
|
||||
guard let candidate = hostedInspectorDividerCandidate(in: root, startingAt: inspectorCandidate) else {
|
||||
continue
|
||||
}
|
||||
let score = hostedInspectorDividerCandidateScore(candidate)
|
||||
|
|
@ -4119,6 +4440,59 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
return bestHit
|
||||
}
|
||||
|
||||
private func hostedInspectorDividerCandidateUsingKnownWebViews(in root: NSView) -> HostedInspectorDividerHit? {
|
||||
guard let pageLeaf = hostedWebView,
|
||||
let inspectorLeaf = hostedInspectorFrontendWebView,
|
||||
pageLeaf.isDescendant(of: root),
|
||||
inspectorLeaf.isDescendant(of: root),
|
||||
Self.isVisibleHostedInspectorCandidate(inspectorLeaf) else {
|
||||
return nil
|
||||
}
|
||||
return hostedInspectorDividerCandidate(
|
||||
in: root,
|
||||
pageLeaf: pageLeaf,
|
||||
inspectorLeaf: inspectorLeaf
|
||||
)
|
||||
}
|
||||
|
||||
private func hostedInspectorDividerCandidate(
|
||||
in root: NSView,
|
||||
pageLeaf: NSView,
|
||||
inspectorLeaf: NSView
|
||||
) -> HostedInspectorDividerHit? {
|
||||
var currentInspector: NSView? = inspectorLeaf
|
||||
|
||||
while let inspectorView = currentInspector, inspectorView !== root {
|
||||
guard let containerView = inspectorView.superview else { break }
|
||||
guard containerView === root || containerView.isDescendant(of: root) else {
|
||||
currentInspector = containerView
|
||||
continue
|
||||
}
|
||||
guard let pageView = Self.directChild(of: containerView, containing: pageLeaf) else {
|
||||
currentInspector = containerView
|
||||
continue
|
||||
}
|
||||
guard pageView !== inspectorView,
|
||||
Self.isVisibleHostedInspectorSiblingCandidate(pageView),
|
||||
Self.verticalOverlap(between: pageView.frame, and: inspectorView.frame) > 8,
|
||||
let dockSide = HostedInspectorDockSide.resolve(
|
||||
pageFrame: pageView.frame,
|
||||
inspectorFrame: inspectorView.frame
|
||||
) else {
|
||||
currentInspector = containerView
|
||||
continue
|
||||
}
|
||||
return HostedInspectorDividerHit(
|
||||
containerView: containerView,
|
||||
pageView: pageView,
|
||||
inspectorView: inspectorView,
|
||||
dockSide: dockSide
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
private func hostedInspectorDividerHitRect(for hit: HostedInspectorDividerHit) -> NSRect {
|
||||
let pageFrame = convert(hit.pageView.bounds, from: hit.pageView)
|
||||
let inspectorFrame = convert(hit.inspectorView.bounds, from: hit.inspectorView)
|
||||
|
|
@ -4130,11 +4504,11 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
)
|
||||
}
|
||||
|
||||
private func hostedInspectorDividerCandidate(startingAt inspectorLeaf: NSView) -> HostedInspectorDividerHit? {
|
||||
private func hostedInspectorDividerCandidate(in root: NSView, startingAt inspectorLeaf: NSView) -> HostedInspectorDividerHit? {
|
||||
var current: NSView? = inspectorLeaf
|
||||
var bestHit: HostedInspectorDividerHit?
|
||||
|
||||
while let inspectorView = current, inspectorView !== self {
|
||||
while let inspectorView = current, inspectorView !== root {
|
||||
guard let containerView = inspectorView.superview else { break }
|
||||
|
||||
let pageCandidates = containerView.subviews.compactMap { candidate -> (view: NSView, dockSide: HostedInspectorDockSide)? in
|
||||
|
|
@ -4189,7 +4563,11 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
let workItem = DispatchWorkItem { [weak self] in
|
||||
guard let self else { return }
|
||||
self.hostedInspectorReapplyWorkItem = nil
|
||||
self.captureHostedInspectorPreferredWidthFromCurrentLayout(reason: reason)
|
||||
if self.isHostedInspectorSideDockActive() {
|
||||
self.reapplyHostedInspectorDividerToStoredWidthIfNeeded(reason: reason)
|
||||
} else if !self.hasStoredHostedInspectorWidthPreference {
|
||||
self.captureHostedInspectorPreferredWidthFromCurrentLayout(reason: reason)
|
||||
}
|
||||
}
|
||||
hostedInspectorReapplyWorkItem = workItem
|
||||
DispatchQueue.main.async(execute: workItem)
|
||||
|
|
@ -4244,6 +4622,18 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
)
|
||||
}
|
||||
|
||||
private func reapplyHostedInspectorDividerToStoredWidthIfNeeded(reason: String) {
|
||||
guard !isApplyingHostedInspectorLayout else { return }
|
||||
guard let hit = hostedInspectorDividerCandidate() else { return }
|
||||
guard isHostedInspectorSideDockHit(hit) else { return }
|
||||
guard let preferredWidth = resolvedPreferredHostedInspectorWidth(in: hit.containerView.bounds) else {
|
||||
return
|
||||
}
|
||||
let currentInspectorWidth = max(0, hit.inspectorView.frame.width)
|
||||
guard abs(currentInspectorWidth - preferredWidth) > 0.5 else { return }
|
||||
_ = applyHostedInspectorDividerWidth(preferredWidth, to: hit, reason: reason)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func applyHostedInspectorDividerWidth(
|
||||
_ preferredWidth: CGFloat,
|
||||
|
|
@ -4302,6 +4692,17 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
return descendants
|
||||
}
|
||||
|
||||
private static func directChild(of container: NSView, containing descendant: NSView) -> NSView? {
|
||||
var current: NSView? = descendant
|
||||
var directChild: NSView?
|
||||
while let view = current, view !== container {
|
||||
directChild = view
|
||||
current = view.superview
|
||||
}
|
||||
guard current === container else { return nil }
|
||||
return directChild
|
||||
}
|
||||
|
||||
fileprivate static func isInspectorView(_ view: NSView) -> Bool {
|
||||
String(describing: type(of: view)).contains("WKInspector")
|
||||
}
|
||||
|
|
@ -4509,9 +4910,7 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
private func updateUsingLocalInlineHosting(_ nsView: NSView, context: Context, webView: WKWebView) -> Bool {
|
||||
guard let host = nsView as? HostContainerView else { return false }
|
||||
let slotView = host.ensureLocalInlineSlotView()
|
||||
let isAlreadyInLocalHost =
|
||||
webView.superview === slotView ||
|
||||
(webView.superview?.isDescendant(of: slotView) ?? false)
|
||||
let isAlreadyInLocalHost = host.containsManagedLocalInlineContent(webView)
|
||||
let didAttachWebViewToLocalHost = !isAlreadyInLocalHost
|
||||
|
||||
let coordinator = context.coordinator
|
||||
|
|
@ -4532,7 +4931,7 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
let shouldPreserveExistingExternalLocalHost =
|
||||
host.window == nil &&
|
||||
webView.superview != nil &&
|
||||
webView.superview !== slotView
|
||||
!host.containsManagedLocalInlineContent(webView)
|
||||
if shouldPreserveExistingExternalLocalHost {
|
||||
// Split zoom can instantiate a replacement local host before it joins a window.
|
||||
// Never let that off-window host steal the live page + inspector hierarchy away
|
||||
|
|
@ -4557,6 +4956,12 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
return false
|
||||
}
|
||||
|
||||
let preferredAttachedWidthState = panel.preferredAttachedDeveloperToolsWidthState()
|
||||
host.setPreferredHostedInspectorWidth(
|
||||
width: preferredAttachedWidthState.width,
|
||||
widthFraction: preferredAttachedWidthState.widthFraction
|
||||
)
|
||||
host.setHostedInspectorFrontendWebView(webView.cmuxInspectorFrontendWebView())
|
||||
host.onPreferredHostedInspectorWidthChanged = { [weak browserPanel = panel] width, _ in
|
||||
guard let browserPanel else { return }
|
||||
browserPanel.recordPreferredAttachedDeveloperToolsWidth(
|
||||
|
|
@ -4566,6 +4971,7 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
}
|
||||
slotView.onHostedInspectorLayout = { [weak host] _ in
|
||||
host?.scheduleHostedInspectorDividerReapply(reason: "slot.layout")
|
||||
host?.scheduleHostedInspectorDockConfigurationSync(reason: "slot.layout")
|
||||
}
|
||||
|
||||
if didAttachWebViewToLocalHost {
|
||||
|
|
@ -4582,7 +4988,10 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
}
|
||||
|
||||
slotView.isHidden = false
|
||||
host.pinHostedWebView(webView, in: slotView)
|
||||
host.pinHostedWebView(
|
||||
webView,
|
||||
in: host.currentHostedWebViewContainer(preferredSlotView: slotView)
|
||||
)
|
||||
coordinator.lastPortalHostId = nil
|
||||
coordinator.lastSynchronizedHostGeometryRevision = 0
|
||||
if didAttachWebViewToLocalHost {
|
||||
|
|
@ -4592,6 +5001,13 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
slotView.layoutSubtreeIfNeeded()
|
||||
host.layoutSubtreeIfNeeded()
|
||||
host.scheduleHostedInspectorDividerReapply(reason: "localInline.update.sync")
|
||||
DispatchQueue.main.async { [weak host, weak webView] in
|
||||
guard let host, let webView else { return }
|
||||
host.setHostedInspectorFrontendWebView(webView.cmuxInspectorFrontendWebView())
|
||||
host.scheduleHostedInspectorDockConfigurationSync(reason: "localInline.update.async")
|
||||
}
|
||||
} else {
|
||||
host.scheduleHostedInspectorDockConfigurationSync(reason: "localInline.update")
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
|
@ -4608,6 +5024,7 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
|
||||
private func updateUsingWindowPortal(_ nsView: NSView, context: Context, webView: WKWebView) -> Bool {
|
||||
guard let host = nsView as? HostContainerView else { return false }
|
||||
host.prepareForWindowPortalHosting()
|
||||
host.setLocalInlineSlotHidden(true)
|
||||
host.releaseHostedWebViewConstraints()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue