Fix panel resize stuttering when tiled with browser panels (#1969)
* Fix panel resize stuttering when tiled with browser panels (#1968) During divider drag, the portal sync system was doing O(N²) work per frame: each geometry callback synced ALL web views, and multiple callbacks fired per layout pass (setFrameSize + setFrameOrigin + layout). Two changes: 1. synchronizeWebViewForAnchor now only syncs the primary web view and defers the all-sync. Each panel fires its own geometry callback, so secondary syncs are redundant on the hot path. 2. HostContainerView.setFrameOrigin/setFrameSize use markGeometryDirtyIfNeeded which defers the callback to layout(), coalescing 2-3 notifications per frame into one. An async fallback ensures origin-only changes (without a subsequent layout) are still delivered. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix premature geometryRevision increment in markGeometryDirtyIfNeeded Address reviewer feedback (Greptile, CodeRabbit): geometryRevision and lastReportedGeometryState are now only updated when the callback actually fires, not eagerly. This prevents updateNSView from seeing a premature revision delta and triggering a redundant synchronizeForAnchor before the coalesced notification arrives. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a395e8c343
commit
b919541044
2 changed files with 33 additions and 3 deletions
|
|
@ -2904,7 +2904,11 @@ final class WindowBrowserPortal: NSObject {
|
|||
synchronizeWebView(withId: primaryWebViewId, source: "anchorPrimary")
|
||||
}
|
||||
|
||||
synchronizeAllWebViews(excluding: primaryWebViewId, source: "anchorSecondary")
|
||||
// During rapid geometry changes (e.g. divider drag), syncing every web view
|
||||
// on every frame is expensive and causes stuttering. Each panel's
|
||||
// HostContainerView fires its own geometry callback, so secondary web views
|
||||
// will sync themselves. Defer the all-sync to coalesce with the next
|
||||
// run-loop turn instead.
|
||||
scheduleDeferredFullSynchronizeAll()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4313,6 +4313,7 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
var onGeometryChanged: (() -> Void)?
|
||||
private(set) var geometryRevision: UInt64 = 0
|
||||
private var lastReportedGeometryState: GeometryState?
|
||||
private var hasPendingGeometryNotification = false
|
||||
private weak var hostedWebView: WKWebView?
|
||||
private var hostedWebViewConstraints: [NSLayoutConstraint] = []
|
||||
private weak var localInlineSlotView: WindowBrowserSlotView?
|
||||
|
|
@ -4647,7 +4648,30 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
)
|
||||
}
|
||||
|
||||
/// Record that geometry changed without firing the callback immediately.
|
||||
/// `setFrameOrigin`/`setFrameSize` can fire multiple times before `layout()`;
|
||||
/// deferring avoids redundant portal-sync cascades during divider drag.
|
||||
/// A dispatch fallback ensures the callback fires even if `layout()` is not called.
|
||||
/// Note: `lastReportedGeometryState` and `geometryRevision` are only updated
|
||||
/// when the callback actually fires, so `updateNSView` sees a revision that
|
||||
/// is strictly tied to emitted callbacks (no premature increments).
|
||||
private func markGeometryDirtyIfNeeded() {
|
||||
let state = currentGeometryState()
|
||||
guard state != lastReportedGeometryState else { return }
|
||||
guard !hasPendingGeometryNotification else { return }
|
||||
hasPendingGeometryNotification = true
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.notifyGeometryChangedIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
/// Check for geometry changes and fire the callback. Also flushes any pending
|
||||
/// dirty state from `markGeometryDirtyIfNeeded` so `layout()` supersedes the
|
||||
/// async fallback. Only updates `lastReportedGeometryState` / `geometryRevision`
|
||||
/// when the callback is emitted, keeping the revision in sync with actual
|
||||
/// notifications.
|
||||
private func notifyGeometryChangedIfNeeded() {
|
||||
hasPendingGeometryNotification = false
|
||||
let state = currentGeometryState()
|
||||
guard state != lastReportedGeometryState else { return }
|
||||
lastReportedGeometryState = state
|
||||
|
|
@ -5070,7 +5094,8 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
override func setFrameOrigin(_ newOrigin: NSPoint) {
|
||||
super.setFrameOrigin(newOrigin)
|
||||
window?.invalidateCursorRects(for: self)
|
||||
notifyGeometryChangedIfNeeded()
|
||||
// Mark dirty; the callback fires from layout() with the settled geometry.
|
||||
markGeometryDirtyIfNeeded()
|
||||
#if DEBUG
|
||||
debugLogHostedInspectorLayoutIfNeeded(reason: "setFrameOrigin")
|
||||
#endif
|
||||
|
|
@ -5079,7 +5104,8 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
override func setFrameSize(_ newSize: NSSize) {
|
||||
super.setFrameSize(newSize)
|
||||
window?.invalidateCursorRects(for: self)
|
||||
notifyGeometryChangedIfNeeded()
|
||||
// Mark dirty; the callback fires from layout() with the settled geometry.
|
||||
markGeometryDirtyIfNeeded()
|
||||
#if DEBUG
|
||||
debugLogHostedInspectorLayoutIfNeeded(reason: "setFrameSize")
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue