macOS: fix intrinsicContentSize race in windowDidLoad (#11256)

Add initialContentSize fallback on TerminalViewContainer so
intrinsicContentSize returns the correct value immediately,
without waiting for @FocusedValue to propagate. This removes
the need for the DispatchQueue.main.asyncAfter 40ms delay.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-10 15:35:49 +01:00
parent cfedda1a0e
commit a6cd1b08af
3 changed files with 44 additions and 38 deletions

View File

@@ -1038,27 +1038,26 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
}
// Initialize our content view to the SwiftUI root
window.contentView = TerminalViewContainer {
let container = TerminalViewContainer {
TerminalView(ghostty: ghostty, viewModel: self, delegate: self)
}
// Set the initial content size on the container so that
// intrinsicContentSize returns the correct value immediately,
// without waiting for @FocusedValue to propagate through the
// SwiftUI focus chain.
container.initialContentSize = focusedSurface?.initialSize
window.contentView = container
// If we have a default size, we want to apply it.
if let defaultSize {
switch defaultSize {
case .frame:
// Frames can be applied immediately
defaultSize.apply(to: window)
defaultSize.apply(to: window)
case .contentIntrinsicSize:
// Content intrinsic size requires a short delay so that AppKit
// can layout our SwiftUI views.
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(40)) { [weak self, weak window] in
guard let self, let window else { return }
defaultSize.apply(to: window)
if let screen = window.screen ?? NSScreen.main {
let frame = self.adjustForWindowPosition(frame: window.frame, on: screen)
window.setFrameOrigin(frame.origin)
}
if case .contentIntrinsicSize = defaultSize {
if let screen = window.screen ?? NSScreen.main {
let frame = self.adjustForWindowPosition(frame: window.frame, on: screen)
window.setFrameOrigin(frame.origin)
}
}
}

View File

@@ -33,11 +33,23 @@ class TerminalViewContainer: NSView {
fatalError("init(coder:) has not been implemented")
}
/// To make ``TerminalController/DefaultSize/contentIntrinsicSize``
/// work in ``TerminalController/windowDidLoad()``,
/// we override this to provide the correct size.
/// The initial content size to use as a fallback before the SwiftUI
/// view hierarchy has completed layout (i.e. before @FocusedValue
/// propagates `lastFocusedSurface`). Once the hosting view reports
/// a valid intrinsic size, this fallback is no longer used.
var initialContentSize: NSSize?
override var intrinsicContentSize: NSSize {
terminalView.intrinsicContentSize
let hostingSize = terminalView.intrinsicContentSize
// The hosting view returns a valid size once SwiftUI has laid out
// with the correct idealWidth/idealHeight. Before that (when
// @FocusedValue hasn't propagated), it returns a tiny default.
// Fall back to initialContentSize in that case.
if let initialContentSize,
hostingSize.width < initialContentSize.width || hostingSize.height < initialContentSize.height {
return initialContentSize
}
return hostingSize
}
private func setup() {

View File

@@ -75,11 +75,11 @@ struct IntrinsicSizeTimingTests {
OptionalIdealSizeView(idealWidth: nil, idealHeight: nil, titlebarStyle: titlebarStyle)
}
// TODO: Fix #11256 set initialContentSize on the container so
// intrinsicContentSize returns the correct value immediately.
// await MainActor.run {
// container.initialContentSize = expectedSize
// }
// Set initialContentSize so intrinsicContentSize returns the
// correct value immediately, without waiting for @FocusedValue.
await MainActor.run {
container.initialContentSize = expectedSize
}
let window = await NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 800, height: 600),
@@ -111,10 +111,9 @@ struct IntrinsicSizeTimingTests {
OptionalIdealSizeView(idealWidth: nil, idealHeight: nil, titlebarStyle: titlebarStyle)
}
// TODO: Fix #11256 set initialContentSize on the container.
// await MainActor.run {
// container.initialContentSize = NSSize(width: 600, height: 400)
// }
await MainActor.run {
container.initialContentSize = NSSize(width: 600, height: 400)
}
let window = await NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 800, height: 600),
@@ -156,12 +155,9 @@ struct IntrinsicSizeTimingTests {
OptionalIdealSizeView(idealWidth: nil, idealHeight: nil, titlebarStyle: titlebarStyle)
}
// TODO: Fix #11256 set initialContentSize on the container so
// intrinsicContentSize returns the correct value immediately,
// eliminating the need for the async delay.
// await MainActor.run {
// container.initialContentSize = NSSize(width: 600, height: 400)
// }
await MainActor.run {
container.initialContentSize = NSSize(width: 600, height: 400)
}
let window = await NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 800, height: 600),
@@ -204,10 +200,9 @@ struct IntrinsicSizeTimingTests {
OptionalIdealSizeView(idealWidth: nil, idealHeight: nil, titlebarStyle: titlebarStyle)
}
// TODO: Fix #11256 set initialContentSize on the container.
// await MainActor.run {
// container.initialContentSize = NSSize(width: 600, height: 400)
// }
await MainActor.run {
container.initialContentSize = NSSize(width: 600, height: 400)
}
let window = await NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 800, height: 600),