Skip to content

[macOS] Window resize blocks the main thread for 1s per step when the frame's only layer is a platform view #191865

Open

Description

@edde746

Steps to reproduce

  1. flutter create bug, then replace lib/main.dart and macos/Runner/MainFlutterWindow.swift with the samples below.
  2. flutter run -d macos. Reproduces in debug, profile and release.
  3. 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.15ms

Flutter 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

  1. added
    platform-macosBuilding on or for macOS specifically
    team-macosOwned by the macOS platform team
    c: performanceRelates to speed or footprint issues (see "perf:" labels)
    a: platform-viewsEmbedding Android/iOS views in Flutter apps
    has reproducible stepsThe issue has been confirmed reproducible and is ready to work on
    on Aug 27, 2026
  2. mbcorona commented on Aug 27, 2026

    @mbcorona
    Member

    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.

  3. added
    P2Important issues not at the top of the work list
    triaged-macosTriaged by the macOS platform team
    on Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P2Important issues not at the top of the work lista: platform-viewsEmbedding Android/iOS views in Flutter appsc: performanceRelates to speed or footprint issues (see "perf:" labels)has reproducible stepsThe issue has been confirmed reproducible and is ready to work onplatform-macosBuilding on or for macOS specificallyteam-macosOwned by the macOS platform teamtriaged-macosTriaged by the macOS platform team

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions