Keep browser webviews alive during drag reparenting

This commit is contained in:
austinpower1258 2026-03-08 03:04:12 -07:00
parent a40c5af4b5
commit 52bd8cf16a
2 changed files with 39 additions and 16 deletions

View file

@ -2390,7 +2390,28 @@ final class WindowBrowserPortal: NSObject {
reason: reason
)
}
func preserveVisibleDuringTransientDetach(reason: String) -> Bool {
guard entry.visibleInUI, !containerView.isHidden else { return false }
let didScheduleTransientRecovery = scheduleTransientRecoveryRetryIfNeeded(
forWebViewId: webViewId,
entry: &entry,
webView: webView,
reason: reason
)
guard didScheduleTransientRecovery else { return false }
#if DEBUG
dlog(
"browser.portal.hidden.deferKeep web=\(browserPortalDebugToken(webView)) " +
"reason=\(reason) frame=\(browserPortalDebugFrame(containerView.frame))"
)
#endif
containerView.setDropZoneOverlay(zone: nil)
return true
}
guard let anchorView = entry.anchorView, let window else {
if preserveVisibleDuringTransientDetach(reason: "missingAnchorOrWindow") {
return
}
if scheduleTransientDetachRecovery(reason: "missingAnchorOrWindow") {
hideContainerView(reason: "missingAnchorOrWindow")
return
@ -2415,21 +2436,15 @@ final class WindowBrowserPortal: NSObject {
anchorView.window == nil &&
anchorView.superview != nil
if isOffWindowReparent {
let didScheduleTransientRecovery = scheduleTransientRecoveryRetryIfNeeded(
forWebViewId: webViewId,
entry: &entry,
webView: webView,
reason: "anchorWindowMismatch"
)
#if DEBUG
if didScheduleTransientRecovery && !containerView.isHidden {
dlog(
"browser.portal.hidden.deferKeep web=\(browserPortalDebugToken(webView)) " +
"reason=anchorWindowMismatch.offWindow frame=\(browserPortalDebugFrame(containerView.frame))"
)
if preserveVisibleDuringTransientDetach(reason: "anchorWindowMismatch.offWindow") {
return
}
#endif
containerView.setDropZoneOverlay(zone: nil)
if scheduleTransientDetachRecovery(reason: "anchorWindowMismatch") {
hideContainerView(reason: "anchorWindowMismatch")
return
}
}
if preserveVisibleDuringTransientDetach(reason: "anchorWindowMismatch") {
return
}
if scheduleTransientDetachRecovery(reason: "anchorWindowMismatch") {

View file

@ -4264,6 +4264,11 @@ struct WebViewRepresentable: NSViewRepresentable {
}
private static func installPortalAnchorView(_ anchorView: NSView, in host: NSView) {
// SwiftUI can keep transient replacement hosts alive off-window during split
// reparenting. Never let those hosts steal the shared portal anchor, or the
// portal will bind against an anchor with no real window and WKWebView will
// fall into a hidden/unrendered state.
guard host.window != nil else { return }
if anchorView.superview !== host {
anchorView.removeFromSuperview()
anchorView.frame = host.bounds
@ -4288,13 +4293,15 @@ struct WebViewRepresentable: NSViewRepresentable {
let paneDropContext = shouldAttachWebView ? currentPaneDropContext() : nil
let activeSearchOverlay = shouldAttachWebView ? searchOverlay : nil
let portalAnchorView = panel.portalAnchorView
Self.installPortalAnchorView(portalAnchorView, in: host)
if host.window != nil {
Self.installPortalAnchorView(portalAnchorView, in: host)
}
host.onDidMoveToWindow = { [weak host, weak webView, weak coordinator, weak portalAnchorView] in
guard let host, let webView, let coordinator, let portalAnchorView else { return }
guard coordinator.attachGeneration == generation else { return }
Self.installPortalAnchorView(portalAnchorView, in: host)
guard host.window != nil else { return }
Self.installPortalAnchorView(portalAnchorView, in: host)
BrowserWindowPortalRegistry.bind(
webView: webView,
to: portalAnchorView,
@ -4314,6 +4321,7 @@ struct WebViewRepresentable: NSViewRepresentable {
guard let host, let webView, let coordinator, let portalAnchorView else { return }
guard coordinator.attachGeneration == generation else { return }
guard coordinator.lastPortalHostId == ObjectIdentifier(host) else { return }
guard host.window != nil else { return }
Self.installPortalAnchorView(portalAnchorView, in: host)
if host.window != nil,
!BrowserWindowPortalRegistry.isWebView(webView, boundTo: portalAnchorView) {