Skip to content

[Web/WASM] White flicker on resize in 3.41.0 due to async prepareToDraw and explicit canvas clear #182354

@synergyvs

Description

@synergyvs

Steps to reproduce

I encountered a regression in Flutter 3.41.0 (Web/WASM) where the background flashes white/transparent during resize.

Investigation Summary: The issue seems to be caused by the architectural changes in the rasterization pipeline:

Async Preparation: prepareToDraw is now async (await), introducing a gap between the resize event and the actual draw.

Explicit Clear: CkSurface.setSize calls _canvasProvider.resizeCanvas, which clears the HTML Canvas content (W3C spec) before the new frame is ready.

Missing Blit: There is no mechanism to preserve the previous frame's bitmap during this async gap.

Reproduction: Resize any app with a heavy widget (like a glass effect) on Web/WASM. The underlying Scaffold background (white) is exposed.

Workaround: I had to decouple the static background into a separate DOM layer (Stack bottom) to persist visibility during the canvas clear.

Please consider implementing a frame preservation mechanism during the setSize operation.

Detailed as below:
I am reporting a regression in Flutter 3.41.0 (Web/WASM) where the Scaffold background flashes white (or transparent) during window resize. This issue was not present in 3.38.9.

I have conducted a deep-dive investigation into the engine source code changes between 3.38.9 and 3.41.0. The root cause appears to be a specific combination of the new async rasterization pipeline and the HTML Canvas clearing behavior.

Below is the detailed technical analysis:

Technical Investigation Report
The white flash is caused by the Web Compositing pipeline re-architecture in 3.41.0. Specifically, it is the result of four compounding changes that leave the HTML Canvas in a cleared state during an asynchronous gap.

  1. prepareToDraw changed from Sync to Async
    In 3.38.9, prepareToDraw was synchronous, allowing the frame to start drawing immediately. In 3.41.0, it has become asynchronous, introducing a delay before drawing begins.

3.38.9 (offscreen_canvas_rasterizer.dart):

@override
void prepareToDraw() {
  rasterizer.offscreenSurface.createOrUpdateSurface(currentFrameSize);
}

3.41.0 (multi_surface_rasterizer.dart):

@override
Future<void> prepareToDraw() async {
  await rasterizer.offscreenSurface.setSize(currentFrameSize);
}

Impact: This await introduces at least one microtask tick of latency. During this gap, the canvas state changes described in point #2 become visible to the user.

  1. CkSurface.setSize triggers implicit Canvas Clear
    In 3.41.0, the new setSize implementation directly modifies the canvas dimensions, which triggers a browser-level clear.

3.41.0 (surface.dart):

@override
void setSize(BitmapSize size) {
  // ... check if size changed ...
  _canvasProvider.resizeCanvas(canvas, size); // <--- DESTRUCTIVE OPERATION
  _recreateSkSurface();
}

The Issue: _canvasProvider.resizeCanvas updates the HTML Canvas width and height attributes. Per the W3C HTML specification, setting these attributes clears the canvas content to transparent black.

Contrast with 3.38.9: The previous version's createOrUpdateSurface logic (specifically _createNewCanvas paths) handled surface updates without exposing this cleared state to the user, likely by swapping DOM elements or retaining the previous surface until the new one was ready.

  1. Increased Latency in Rasterization Pipeline
    The rasterization flow in 3.41.0 now involves multiple await steps that were previously synchronous or atomic:

prepareToDraw() (await)

setSize() -> Canvas is cleared here

rasterizeToImageBitmaps() (await)

rasterizeToCanvas() (await)

Because the Canvas is cleared at step 2, and the actual drawing doesn't happen until step 4, there is a visible window where the browser paints an empty (white/transparent) frame.

  1. Aggressive Metrics Firing via ResizeObserver
    The mechanism for detecting layout metrics changes has been made significantly more sensitive, triggering the above cycle more frequently.

3.38.9: Used DomMutationObserver watching only the style attribute of .

3.41.0: Uses DomResizeObserver watching an offscreen

element (_typographyMeasurementElement).

This observer fires on a wider range of changes (font loading, zoom levels, minor layout shifts), invoking invokeOnMetricsChanged() more often. This causes the framework to request new frames and trigger the destructive setSize flow more frequently during resize operations.

Conclusion & Suggestion
The issue is not just a performance regression but a visual correctness regression. The implementation of CkSurface.setSize in 3.41.0 lacks a mechanism to preserve the previous frame's content while the new surface is being prepared asynchronously.

Suggested Fix: Before calling _canvasProvider.resizeCanvas (which clears the canvas), the engine should ideally copy the current canvas content (e.g., to a temporary canvas or bitmap) and draw it back immediately after resize, or employ a double-buffering strategy at the DOM level to hide the cleared canvas until the new frame is fully rasterized.

Expected results

The application should resize smoothly. The content of the previous frame (or a stretched version of it) should remain visible until the new frame is fully rasterized and ready to be painted. There should be no visual gaps or flickering.

Actual results

The application canvas flashes white (or transparent) repeatedly during the resize drag operation. The underlying HTML page background is momentarily exposed because the Canvas element is explicitly cleared before the new frame is drawn.

Code sample

Technical Investigation Report
The white flash is caused by the Web Compositing pipeline re-architecture in 3.41.0. Specifically, it is the result of four compounding changes that leave the HTML Canvas in a cleared state during an asynchronous gap.

  1. prepareToDraw changed from Sync to Async
    In 3.38.9, prepareToDraw was synchronous, allowing the frame to start drawing immediately. In 3.41.0, it has become asynchronous, introducing a delay before drawing begins.

3.38.9 (offscreen_canvas_rasterizer.dart):

@override
void prepareToDraw() {
  rasterizer.offscreenSurface.createOrUpdateSurface(currentFrameSize);
}

3.41.0 (multi_surface_rasterizer.dart):

@override
Future<void> prepareToDraw() async {
  await rasterizer.offscreenSurface.setSize(currentFrameSize);
}

Impact: This await introduces at least one microtask tick of latency. During this gap, the canvas state changes described in point #2 become visible to the user.

  1. CkSurface.setSize triggers implicit Canvas Clear
    In 3.41.0, the new setSize implementation directly modifies the canvas dimensions, which triggers a browser-level clear.

3.41.0 (surface.dart):

@override
void setSize(BitmapSize size) {
  // ... check if size changed ...
  _canvasProvider.resizeCanvas(canvas, size); // <--- DESTRUCTIVE OPERATION
  _recreateSkSurface();
}

The Issue: _canvasProvider.resizeCanvas updates the HTML Canvas width and height attributes. Per the W3C HTML specification, setting these attributes clears the canvas content to transparent black.

Contrast with 3.38.9: The previous version's createOrUpdateSurface logic (specifically _createNewCanvas paths) handled surface updates without exposing this cleared state to the user, likely by swapping DOM elements or retaining the previous surface until the new one was ready.

  1. Increased Latency in Rasterization Pipeline
    The rasterization flow in 3.41.0 now involves multiple await steps that were previously synchronous or atomic:
prepareToDraw() (await)

setSize() -> Canvas is cleared here

rasterizeToImageBitmaps() (await)

rasterizeToCanvas() (await)

Because the Canvas is cleared at step 2, and the actual drawing doesn't happen until step 4, there is a visible window where the browser paints an empty (white/transparent) frame.

  1. Aggressive Metrics Firing via ResizeObserver
    The mechanism for detecting layout metrics changes has been made significantly more sensitive, triggering the above cycle more frequently.

3.38.9: Used DomMutationObserver watching only the style attribute of .

3.41.0: Uses DomResizeObserver watching an offscreen

element (_typographyMeasurementElement).

This observer fires on a wider range of changes (font loading, zoom levels, minor layout shifts), invoking invokeOnMetricsChanged() more often. This causes the framework to request new frames and trigger the destructive setSize flow more frequently during resize operations.

Conclusion & Suggestion
The issue is not just a performance regression but a visual correctness regression. The implementation of CkSurface.setSize in 3.41.0 lacks a mechanism to preserve the previous frame's content while the new surface is being prepared asynchronously.

Suggested Fix: Before calling _canvasProvider.resizeCanvas (which clears the canvas), the engine should ideally copy the current canvas content (e.g., to a temporary canvas or bitmap) and draw it back immediately after resize, or employ a double-buffering strategy at the DOM level to hide the cleared canvas until the new frame is fully rasterized.

Screenshots or Video

Screenshots / Video demonstration

[Upload media here]

Logs

Logs
[Paste your logs here]

Flutter Doctor output

Doctor output
[2026/02/13 14:45:28] (121s) /c/0/w/a/sr_v3_3 >>>fvm flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 3.41.0, on Microsoft Windows [Version
    10.0.26200.7705], locale ja-JP)
[√] Windows Version (11 Pro 64-bit, 25H2, 2009)
[X] Android toolchain - develop for Android devices
    X Unable to locate Android SDK.
      Install Android Studio from:
      https://developer.android.com/studio/index.html
      On first launch it will assist you in installing the Android
      SDK components.
      (or visit https://flutter.dev/to/windows-android-setup for  
      detailed instructions).
      If the Android SDK has been installed to a custom location, 
      please use
      `flutter config --android-sdk` to update to that location.  

[√] Chrome - develop for the web
[!] Visual Studio - develop Windows apps (Visual Studio Build
    Tools 2022 17.14.15 (September 2025))
    X Visual Studio is missing necessary components. Please re-run
      the Visual Studio installer for the "Desktop development    
      with C++" workload, and include these components:
        MSVC v142 - VS 2019 C++ x64/x86 build tools
         - If there are multiple build tool versions available,   
         install the latest
        C++ CMake tools for Windows
        Windows 10 SDK
[√] Connected device (3 available)
[√] Network resources

! Doctor found issues in 2 categories.
[2026/02/13 15:45:12] (5s) /c/0/w/a/sr_v3_3 >>>    

Metadata

Metadata

Labels

P1High-priority issues at the top of the work listc: regressionIt was better in the past than it is nowe: wasmIssues related to the wasm build of Flutter Web.engineflutter/engine related. See also e: labels.found in release: 3.41Found to occur in 3.41platform-webWeb applications specificallyteam-webOwned by Web platform team

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions