Repository navigation
Description
Steps to reproduce
flutter create bug, then replacelib/main.dartandmacos/Runner/MainFlutterWindow.swiftwith the samples below.flutter run -d macos. Reproduces in debug, profile and release.- Drag any window edge or corner to resize.
Expected results
Resizing is smooth. A frame whose content is an AppKit platform view should not make window resizing slower than a frame that rasterizes Flutter content.
Actual results
The window follows the cursor at roughly one step per second and the console fills with Resize timed out.
| scene | mean per resize step | Resize timed out |
|---|---|---|
| platform view only, nothing else paints | 1019.15 ms | 20 / 20 |
one opaque ColoredBox, no platform view |
8.82 ms | 0 / 20 |
Timings come from the same ResizeProbe used in #190075 (included below), which drives 20 setFrame steps through the same -[FlutterView setFrameSize:] path a live drag uses. 1019 ms is the ResizeSynchronizer timeout, so every step times out.
Measured with a local unoptimized engine (mac_debug_unopt_arm64) built from 58cc1b845d. The numbers above are from the unmodified engine; applying #191862 gives 1017.10 ms and 20/20 timeouts, i.e. no change, as expected from the analysis below.
Suspected cause
Not maintainer-confirmed; this is my reading of the source at 58cc1b845d plus the measurements above.
This is the same handshake failure as #190075, reached by a different route, and it is deliberately not fixed by #191862.
GetRequiredFrameSize() derives the committed frame size from the presented surfaces. FlutterCompositor::Present only collects surfaces from kFlutterLayerContentTypeBackingStore layers, so a frame whose sole layer is kFlutterLayerContentTypePlatformView presents zero surfaces and commits (0, 0). ResizeSynchronizer.beginResize waits for the committed size to equal the size being resized to, which (0, 0) never is, so the platform thread spins for the full timeoutDuration = 1.0.
#191862 fixes the zero-layer case by committing an explicit "empty frame" that satisfies any target size, and keys that on layers_count == 0 rather than on "no surfaces presented" precisely because it must not apply here: the platform view is on screen, has no autoresizing mask, and is positioned from the frame being committed by PresentPlatformViews. Unblocking on it would complete the resize with the platform view at its pre-resize geometry, which is the desynchronization ResizeSynchronizer exists to prevent.
Fixing this case properly needs the frame's real size, which the macOS compositor cannot currently obtain: FlutterPresentViewInfo carries layers and layers_count but no frame size, and no backing store is created for a platform-view-only frame, so FlutterBackingStoreConfig.size is never seen either. The value is already available one layer up — EmbedderLayers holds frame_size_ and is what invokes the present callback — so a plausible fix is to add the frame size to FlutterPresentViewInfo and have the macOS handshake commit that instead of a size derived from the presented surfaces. That would also make #191862's empty-frame mechanism unnecessary.
I have not checked whether the Windows or Linux embedders are affected; Windows keys the same handshake on layers_count == 0 and its compositor does not support platform views.
Code sample
lib/main.dart
import 'package:flutter/material.dart';
void main() {
runApp(
const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
// Nothing in the Flutter scene paints, so the only layer in the frame is
// the platform view.
backgroundColor: Colors.transparent,
body: AppKitView(viewType: 'red_box'),
),
),
);
}macos/Runner/MainFlutterWindow.swift
class RedBoxView: NSView {
override var isOpaque: Bool { true }
override func draw(_ dirtyRect: NSRect) {
NSColor.systemRed.setFill()
dirtyRect.fill()
}
}
class RedBoxFactory: NSObject, FlutterPlatformViewFactory {
func create(withViewIdentifier viewId: Int64, arguments args: Any?) -> NSView {
return RedBoxView()
}
}Registered in awakeFromNib(), before super.awakeFromNib():
let registrar = flutterViewController.registrar(forPlugin: "platform_view_probe")
registrar.register(RedBoxFactory(), withId: "red_box")Measurement probe (optional)
The ResizeProbe from #190075, unchanged. Call ResizeProbe.install(window: self) at the end of awakeFromNib(), then kill -USR1 <pid>.
enum ResizeProbe {
private static var source: DispatchSourceSignal?
static func install(window: NSWindow) {
signal(SIGUSR1, SIG_IGN)
let src = DispatchSource.makeSignalSource(signal: SIGUSR1, queue: .main)
src.setEventHandler { [weak window] in
guard let window else { return }
let base = window.frame
var samples: [Double] = []
for i in 0..<20 {
var f = window.frame
let d = CGFloat((i % 2 == 0) ? 6 : -6)
f.size.width += d; f.size.height += d
let t0 = CFAbsoluteTimeGetCurrent()
window.setFrame(f, display: true)
samples.append((CFAbsoluteTimeGetCurrent() - t0) * 1000.0)
}
window.setFrame(base, display: true)
let mean = samples.reduce(0, +) / Double(samples.count)
NSLog("[ResizeProbe] n=%d mean=%.2fms", samples.count, mean)
}
src.resume(); source = src
}
}Logs
Logs
Resize timed out
Resize timed out
Resize timed out
... (one per resize step; 20 of 20 in the measured run)
[ResizeProbe] n=20 mean=1019.15msFlutter Doctor output
Doctor output
This is an engine development checkout, since the measurements above use a locally built engine.
[!] Flutter (Channel [user-branch], 3.48.0-1.0.pre-466, on macOS 26.6.1 25G76 darwin-arm64, locale en-US)
! Flutter version 3.48.0-1.0.pre-466 on channel [user-branch] at ~/Documents/flutter-dev/flutter
Currently on an unknown channel.
! Upstream repository https://github.com/edde746/flutter.git is not a standard remote.
• Framework revision bd2a3b588e (2026-08-27), engine revision 58cc1b845d
• Dart version 3.14.0 (build 3.14.0-170.0.dev)
• DevTools version 2.61.0-dev.0
[✓] Android toolchain - develop for Android devices (Android SDK version 36.1.0)
[✓] Xcode - develop for iOS and macOS (Xcode 26.6)
• Build 17F113
• CocoaPods version 1.16.2
[✗] Chrome - develop for the web (Cannot find Chrome executable)
[✓] Connected device (2 available)
• <redacted-device> (mobile) • <redacted-udid> • ios • iOS 26.6 23G71
• macOS (desktop) • macos • darwin-arm64 • macOS 26.6.1 25G76 darwin-arm64
[✓] Network resources
Activity
mbcorona commentedon Aug 27, 2026
Hi @edde746, thanks for the detailed writeup and the measurements. Routing to the macOS team.
Clear enough on its own: a platform-view-only frame presents no backing-store surfaces, so the resize handshake commits (0,0) and ResizeSynchronizer spins to the 1s timeout. Distinct from #190075 and, as you note, out of scope for #191862.