Keep browser omnibar visible across pane zoom

This commit is contained in:
austinpower1258 2026-03-10 00:41:00 -07:00
parent b99ec64ae3
commit 034f157feb
2 changed files with 114 additions and 3 deletions

View file

@ -1720,7 +1720,13 @@ final class BrowserPanel: Panel, ObservableObject {
let inWindow: Bool
let area: CGFloat
}
private struct PortalHostLock {
let hostId: ObjectIdentifier
let paneId: UUID
}
private var activePortalHostLease: PortalHostLease?
private var pendingDistinctPortalHostReplacementPaneId: UUID?
private var lockedPortalHost: PortalHostLock?
private var webViewCancellables = Set<AnyCancellable>()
private var navigationDelegate: BrowserNavigationDelegate?
private var uiDelegate: BrowserUIDelegate?
@ -1773,6 +1779,22 @@ final class BrowserPanel: Panel, ObservableObject {
lease.inWindow && lease.area > portalHostAreaThreshold
}
func preparePortalHostReplacementForNextDistinctClaim(
inPane paneId: PaneID,
reason: String
) {
pendingDistinctPortalHostReplacementPaneId = paneId.id
if lockedPortalHost?.paneId == paneId.id {
lockedPortalHost = nil
}
#if DEBUG
dlog(
"browser.portal.host.rearm panel=\(id.uuidString.prefix(5)) " +
"reason=\(reason) pane=\(paneId.id.uuidString.prefix(5))"
)
#endif
}
func claimPortalHost(
hostId: ObjectIdentifier,
paneId: PaneID,
@ -1788,6 +1810,11 @@ final class BrowserPanel: Panel, ObservableObject {
)
if let current = activePortalHostLease {
if let lock = lockedPortalHost,
(lock.hostId != current.hostId || lock.paneId != current.paneId) {
lockedPortalHost = nil
}
if current.hostId == hostId {
activePortalHostLease = next
return true
@ -1795,12 +1822,47 @@ final class BrowserPanel: Panel, ObservableObject {
let currentUsable = Self.portalHostIsUsable(current)
let nextUsable = Self.portalHostIsUsable(next)
let isSamePaneReplacement = current.paneId == paneId.id
let shouldForceDistinctReplacement =
isSamePaneReplacement &&
pendingDistinctPortalHostReplacementPaneId == paneId.id &&
inWindow
if shouldForceDistinctReplacement {
#if DEBUG
dlog(
"browser.portal.host.claim panel=\(id.uuidString.prefix(5)) " +
"reason=\(reason) host=\(hostId) pane=\(paneId.id.uuidString.prefix(5)) " +
"inWin=\(inWindow ? 1 : 0) size=\(String(format: "%.1fx%.1f", bounds.width, bounds.height)) " +
"replacingHost=\(current.hostId) replacingPane=\(current.paneId.uuidString.prefix(5)) " +
"replacingInWin=\(current.inWindow ? 1 : 0) replacingArea=\(String(format: "%.1f", current.area)) " +
"forced=1"
)
#endif
activePortalHostLease = next
pendingDistinctPortalHostReplacementPaneId = nil
lockedPortalHost = PortalHostLock(hostId: hostId, paneId: paneId.id)
return true
}
let lockBlocksSamePaneReplacement =
isSamePaneReplacement &&
currentUsable &&
lockedPortalHost?.hostId == current.hostId &&
lockedPortalHost?.paneId == current.paneId
let shouldReplace =
current.paneId != paneId.id ||
!currentUsable ||
(nextUsable && next.area > (current.area * Self.portalHostReplacementAreaGainRatio))
(
!lockBlocksSamePaneReplacement &&
nextUsable &&
next.area > (current.area * Self.portalHostReplacementAreaGainRatio)
)
if shouldReplace {
if lockedPortalHost?.hostId == current.hostId &&
lockedPortalHost?.paneId == current.paneId {
lockedPortalHost = nil
}
#if DEBUG
dlog(
"browser.portal.host.claim panel=\(id.uuidString.prefix(5)) " +
@ -1820,7 +1882,8 @@ final class BrowserPanel: Panel, ObservableObject {
"reason=\(reason) host=\(hostId) pane=\(paneId.id.uuidString.prefix(5)) " +
"inWin=\(inWindow ? 1 : 0) size=\(String(format: "%.1fx%.1f", bounds.width, bounds.height)) " +
"ownerHost=\(current.hostId) ownerPane=\(current.paneId.uuidString.prefix(5)) " +
"ownerInWin=\(current.inWindow ? 1 : 0) ownerArea=\(String(format: "%.1f", current.area))"
"ownerInWin=\(current.inWindow ? 1 : 0) ownerArea=\(String(format: "%.1f", current.area)) " +
"locked=\(lockBlocksSamePaneReplacement ? 1 : 0)"
)
#endif
return false
@ -1842,6 +1905,9 @@ final class BrowserPanel: Panel, ObservableObject {
func releasePortalHostIfOwned(hostId: ObjectIdentifier, reason: String) -> Bool {
guard let current = activePortalHostLease, current.hostId == hostId else { return false }
activePortalHostLease = nil
if lockedPortalHost?.hostId == hostId {
lockedPortalHost = nil
}
#if DEBUG
dlog(
"browser.portal.host.release panel=\(id.uuidString.prefix(5)) " +

View file

@ -3217,11 +3217,19 @@ final class Workspace: Identifiable, ObservableObject {
@discardableResult
func toggleSplitZoom(panelId: UUID) -> Bool {
let wasSplitZoomed = bonsplitController.isSplitZoomed
guard let paneId = paneId(forPanelId: panelId) else { return false }
guard bonsplitController.togglePaneZoom(inPane: paneId) else { return false }
focusPanel(panelId)
if browserPanel(for: panelId) != nil {
if let browserPanel = browserPanel(for: panelId) {
browserPanel.preparePortalHostReplacementForNextDistinctClaim(
inPane: paneId,
reason: "workspace.toggleSplitZoom"
)
scheduleBrowserPortalReconcileAfterSplitZoom(panelId: panelId, remainingPasses: 4)
if wasSplitZoomed && !bonsplitController.isSplitZoomed {
scheduleBrowserSplitZoomExitFocusReassert(panelId: panelId, remainingPasses: 4)
}
}
return true
}
@ -3525,6 +3533,43 @@ final class Workspace: Identifiable, ObservableObject {
}
}
// Browser panes can briefly keep the portal-hosted WKWebView visible while Bonsplit is
// still rebuilding the unzoomed pane host. Reassert pane/tab selection after layout settles
// so the SwiftUI chrome does not remain hidden until another browser focus command runs.
private func scheduleBrowserSplitZoomExitFocusReassert(panelId: UUID, remainingPasses: Int) {
guard remainingPasses > 0 else { return }
DispatchQueue.main.async { [weak self] in
guard let self, self.browserPanel(for: panelId) != nil else { return }
guard let paneId = self.paneId(forPanelId: panelId),
let tabId = self.surfaceIdFromPanelId(panelId) else { return }
let selectionConverged =
self.bonsplitController.focusedPaneId == paneId &&
self.bonsplitController.selectedTab(inPane: paneId)?.id == tabId
let anchorReady: Bool = {
guard let browserPanel = self.browserPanel(for: panelId) else { return false }
let anchorView = browserPanel.portalAnchorView
return
anchorView.window != nil &&
anchorView.superview != nil &&
anchorView.bounds.width > 1 &&
anchorView.bounds.height > 1
}()
if !selectionConverged {
self.focusPanel(panelId)
self.scheduleFocusReconcile()
}
if !selectionConverged || !anchorReady {
self.scheduleBrowserSplitZoomExitFocusReassert(
panelId: panelId,
remainingPasses: remainingPasses - 1
)
}
}
}
private func scheduleMovedTerminalRefresh(panelId: UUID) {
guard terminalPanel(for: panelId) != nil else { return }