From 10c390f525888f076922942baab78fa80935ac86 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Mon, 18 Dec 2023 18:46:09 -0800 Subject: [PATCH 1/8] [web] Enforce onDrawFrame/onBeginFrame render rule Also update tests to conform to the render rule. --- .../lib/src/engine/platform_dispatcher.dart | 22 ++++++- .../backdrop_filter_golden_test.dart | 15 +++-- .../test/canvaskit/canvas_golden_test.dart | 13 ++-- lib/web_ui/test/canvaskit/common.dart | 12 ++-- .../test/canvaskit/embedded_views_test.dart | 59 +++++++++---------- .../test/canvaskit/image_golden_test.dart | 35 +++++------ lib/web_ui/test/canvaskit/layer_test.dart | 4 +- lib/web_ui/test/common/rendering.dart | 27 +++++++++ .../test/common/test_initialization.dart | 3 + lib/web_ui/test/ui/platform_view_test.dart | 10 ++-- lib/web_ui/test/ui/scene_builder_test.dart | 21 +++---- lib/web_ui/test/ui/utils.dart | 4 +- 12 files changed, 131 insertions(+), 94 deletions(-) create mode 100644 lib/web_ui/test/common/rendering.dart diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index ec17dee09334d..fb07541aa30d3 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -207,6 +207,10 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { } } + /// A set of views which have rendered in the current `onBeginFrame` or + /// `onDrawFrame` scope. + Set? _viewsRenderedInCurrentFrame; + /// A callback invoked when any window begins a frame. /// /// A callback that is invoked to notify the application that it is an @@ -229,7 +233,9 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// Engine code should use this method instead of the callback directly. /// Otherwise zones won't work properly. void invokeOnBeginFrame(Duration duration) { + _viewsRenderedInCurrentFrame = {}; invoke1(_onBeginFrame, _onBeginFrameZone, duration); + _viewsRenderedInCurrentFrame = null; } /// A callback that is invoked for each frame after [onBeginFrame] has @@ -250,7 +256,9 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// Engine code should use this method instead of the callback directly. /// Otherwise zones won't work properly. void invokeOnDrawFrame() { + _viewsRenderedInCurrentFrame = {}; invoke(_onDrawFrame, _onDrawFrameZone); + _viewsRenderedInCurrentFrame = null; } /// A callback that is invoked when pointer data is available. @@ -760,7 +768,16 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { // If there is no view to render into, then this is a no-op. return; } - renderer.renderScene(scene, view ?? implicitView!); + final ui.FlutterView viewToRender = view ?? implicitView!; + + // Only render in an `onDrawFrame` or `onBeginFrame` scope. This is checked + // by checking if the `_viewsRenderedInCurrentFrame` is non-null and this + // view hasn't been rendered already in this scope. + final bool shouldRender = + _viewsRenderedInCurrentFrame?.add(viewToRender) ?? false; + if (shouldRender) { + renderer.renderScene(scene, view ?? implicitView!); + } } /// Additional accessibility features that may be enabled by the platform. @@ -1275,7 +1292,8 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { String get defaultRouteName { // TODO(mdebbar): What should we do in multi-view mode? // https://github.com/flutter/flutter/issues/139174 - return _defaultRouteName ??= implicitView?.browserHistory.currentPath ?? '/'; + return _defaultRouteName ??= + implicitView?.browserHistory.currentPath ?? '/'; } /// Lazily initialized when the `defaultRouteName` getter is invoked. diff --git a/lib/web_ui/test/canvaskit/backdrop_filter_golden_test.dart b/lib/web_ui/test/canvaskit/backdrop_filter_golden_test.dart index cb0b178e0554f..29a252f1a4cca 100644 --- a/lib/web_ui/test/canvaskit/backdrop_filter_golden_test.dart +++ b/lib/web_ui/test/canvaskit/backdrop_filter_golden_test.dart @@ -7,8 +7,6 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; -import 'package:web_engine_tester/golden_tester.dart'; - import 'common.dart'; void main() { @@ -49,8 +47,8 @@ void testMain() { builder.pushOffset(0, 0); builder.addPicture(ui.Offset.zero, checkerboard); builder.pushBackdropFilter(ui.ImageFilter.blur(sigmaX: 10, sigmaY: 10)); - CanvasKitRenderer.instance.renderScene(builder.build(), implicitView); - await matchGoldenFile('canvaskit_backdropfilter_blur_edges.png', + await matchSceneGolden( + 'canvaskit_backdropfilter_blur_edges.png', builder.build(), region: region); }); test('ImageFilter with ColorFilter as child', () async { @@ -62,9 +60,7 @@ void testMain() { final CkPictureRecorder recorder = CkPictureRecorder(); final CkCanvas canvas = recorder.beginRecording(region); final ui.ColorFilter colorFilter = ui.ColorFilter.mode( - const ui.Color(0XFF00FF00).withOpacity(0.55), - ui.BlendMode.darken - ); + const ui.Color(0XFF00FF00).withOpacity(0.55), ui.BlendMode.darken); // using a colorFilter as an imageFilter for backDrop filter builder.pushBackdropFilter(colorFilter); @@ -75,7 +71,10 @@ void testMain() { ); final CkPicture redCircle1 = recorder.endRecording(); builder.addPicture(ui.Offset.zero, redCircle1); - await matchSceneGolden('canvaskit_red_circle_green_backdrop_colorFilter.png', builder.build(), region: region); + await matchSceneGolden( + 'canvaskit_red_circle_green_backdrop_colorFilter.png', + builder.build(), + region: region); }); // TODO(hterkelsen): https://github.com/flutter/flutter/issues/71520 }, skip: isSafari || isFirefox); diff --git a/lib/web_ui/test/canvaskit/canvas_golden_test.dart b/lib/web_ui/test/canvaskit/canvas_golden_test.dart index 3ada138eb3722..57ef4276cfc5a 100644 --- a/lib/web_ui/test/canvaskit/canvas_golden_test.dart +++ b/lib/web_ui/test/canvaskit/canvas_golden_test.dart @@ -11,8 +11,6 @@ import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; import 'package:ui/ui_web/src/ui_web.dart' as ui_web; -import 'package:web_engine_tester/golden_tester.dart'; - import 'common.dart'; void main() { @@ -147,16 +145,14 @@ void testMain() { builder.pushOffset(0, 0); builder.addPicture(ui.Offset.zero, picture); final LayerScene scene = builder.build(); - CanvasKitRenderer.instance.renderScene(scene, implicitView); + renderScene(scene); // Now draw an empty layer tree and confirm that the red rectangle is // no longer drawn. final LayerSceneBuilder emptySceneBuilder = LayerSceneBuilder(); emptySceneBuilder.pushOffset(0, 0); final LayerScene emptyScene = emptySceneBuilder.build(); - CanvasKitRenderer.instance.renderScene(emptyScene, implicitView); - - await matchGoldenFile('canvaskit_empty_scene.png', + await matchSceneGolden('canvaskit_empty_scene.png', emptyScene, region: const ui.Rect.fromLTRB(0, 0, 100, 100)); }); @@ -211,9 +207,8 @@ void testMain() { sb.pop(); // The below line should not throw an error. - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); - - await matchGoldenFile('cross_overlay_resources.png', region: const ui.Rect.fromLTRB(0, 0, 100, 100)); + await matchSceneGolden('cross_overlay_resources.png', sb.build(), + region: const ui.Rect.fromLTRB(0, 0, 100, 100)); }); }); } diff --git a/lib/web_ui/test/canvaskit/common.dart b/lib/web_ui/test/canvaskit/common.dart index 72f5e64e652cb..5649cee4e20fd 100644 --- a/lib/web_ui/test/canvaskit/common.dart +++ b/lib/web_ui/test/canvaskit/common.dart @@ -11,8 +11,11 @@ import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; import 'package:web_engine_tester/golden_tester.dart'; +import '../common/rendering.dart'; import '../common/test_initialization.dart'; +export '../common/rendering.dart' show renderScene; + const MethodCodec codec = StandardMethodCodec(); /// Common test setup for all CanvasKit unit-tests. @@ -44,13 +47,10 @@ CkPicture paintPicture( Future matchSceneGolden( String goldenFile, - LayerScene scene, { + ui.Scene scene, { required ui.Rect region, }) async { - // TODO(harryterkelsen): Enforce the render rule. Render can only be called in - // the scope of `onBeginFrame` or `onDrawFrame`, - // https://github.com/flutter/flutter/issues/137073. - CanvasKitRenderer.instance.renderScene(scene, implicitView); + renderScene(scene); await matchGoldenFile(goldenFile, region: region); } @@ -63,7 +63,7 @@ Future matchPictureGolden(String goldenFile, CkPicture picture, final LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.addPicture(ui.Offset.zero, picture); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); await matchGoldenFile(goldenFile, region: region); } diff --git a/lib/web_ui/test/canvaskit/embedded_views_test.dart b/lib/web_ui/test/canvaskit/embedded_views_test.dart index 1f225d9498eaa..cba461b36f036 100644 --- a/lib/web_ui/test/canvaskit/embedded_views_test.dart +++ b/lib/web_ui/test/canvaskit/embedded_views_test.dart @@ -41,7 +41,7 @@ void testMain() { final LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.addPlatformView(0, width: 10, height: 10); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); // The platform view is now split in two parts. The contents live // as a child of the glassPane, and the slot lives in the glassPane @@ -77,7 +77,7 @@ void testMain() { sb.pushClipRRect( ui.RRect.fromLTRBR(0, 0, 10, 10, const ui.Radius.circular(3))); sb.addPlatformView(0, width: 10, height: 10); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); expect( sceneHost.querySelectorAll('#sk_path_defs').single, @@ -122,7 +122,7 @@ void testMain() { sb.pushTransform(scaleMatrix.toFloat64()); sb.pushOffset(3, 3); sb.addPlatformView(0, width: 10, height: 10); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); // Transformations happen on the slot element. final DomElement slotHost = @@ -148,7 +148,7 @@ void testMain() { final LayerSceneBuilder sb = LayerSceneBuilder(); sb.addPlatformView(0, offset: const ui.Offset(3, 4), width: 5, height: 6); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); final DomElement slotHost = sceneHost.querySelector('flt-platform-view-slot')!; @@ -194,7 +194,7 @@ void testMain() { sb.pushOffset(6, 6); sb.addPlatformView(0, width: 10, height: 10); sb.pop(); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); // Transformations happen on the slot element. DomElement slotHost = sceneHost.querySelector('flt-platform-view-slot')!; @@ -214,7 +214,7 @@ void testMain() { sb.pushClipRect(ui.Rect.largest); sb.pushOffset(9, 9); sb.addPlatformView(0, width: 10, height: 10); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); // Transformations happen on the slot element. slotHost = sceneHost.querySelector('flt-platform-view-slot')!; @@ -244,7 +244,7 @@ void testMain() { sb.pushOffset(2, 2); sb.pushOffset(3, 3); sb.addPlatformView(0, width: 10, height: 10); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); // Transformations happen on the slot element. final DomElement slotHost = @@ -273,7 +273,7 @@ void testMain() { sb.pushClipRect(ui.Rect.largest); sb.pushOffset(9, 9); sb.addPlatformView(0, width: 10, height: 10); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); // Transformations happen on the slot element. final DomElement slotHost = @@ -315,7 +315,7 @@ void testMain() { sb.addPicture(ui.Offset.zero, testPicture); sb.addPlatformView(i, width: 10, height: 10); } - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); } // Frame 1: @@ -471,7 +471,7 @@ void testMain() { sb.addPicture(ui.Offset.zero, testPicture); sb.addPlatformView(view, width: 10, height: 10); } - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); } // Frame 1: @@ -580,7 +580,6 @@ void testMain() { for (int i = 0; i < 20; i++) { await disposePlatformView(i); } - }); test('embeds and disposes of a platform view', () async { @@ -593,7 +592,7 @@ void testMain() { LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.addPlatformView(0, width: 10, height: 10); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -606,7 +605,7 @@ void testMain() { sb = LayerSceneBuilder(); sb.pushOffset(0, 0); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, @@ -642,7 +641,7 @@ void testMain() { implicitView.debugPhysicalSizeOverride = const ui.Size(100, 100); implicitView.debugForceResize(); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -651,7 +650,7 @@ void testMain() { implicitView.debugPhysicalSizeOverride = const ui.Size(200, 200); implicitView.debugForceResize(); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -675,7 +674,7 @@ void testMain() { LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.addPlatformView(0, width: 10, height: 10); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -689,7 +688,7 @@ void testMain() { sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.addPlatformView(1, width: 10, height: 10); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -705,7 +704,7 @@ void testMain() { // the platform view. sb = LayerSceneBuilder(); sb.pushOffset(0, 0); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, ]); @@ -736,7 +735,7 @@ void testMain() { sb.pushClipRRect( ui.RRect.fromLTRBR(0, 0, 10, 10, const ui.Radius.circular(3))); sb.addPlatformView(0, width: 10, height: 10); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); } final DomNode skPathDefs = sceneHost.querySelector('#sk_path_defs')!; @@ -771,7 +770,7 @@ void testMain() { sb.addPlatformView(0, width: 10, height: 10); sb.pop(); // The below line should not throw an error. - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, ]); @@ -805,7 +804,7 @@ void testMain() { sb.pushOffset(0, 0); sb.addPlatformView(1, width: 10, height: 10); sb.pop(); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -816,7 +815,7 @@ void testMain() { sb.addPlatformView(0, width: 10, height: 10); sb.addPlatformView(1, width: 10, height: 10); sb.pop(); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -830,7 +829,7 @@ void testMain() { sb.addPlatformView(1, width: 10, height: 10); sb.addPlatformView(2, width: 10, height: 10); sb.pop(); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -849,7 +848,7 @@ void testMain() { sb.addPlatformView(2, width: 10, height: 10); sb.addPlatformView(3, width: 10, height: 10); sb.pop(); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -868,7 +867,7 @@ void testMain() { sb.addPlatformView(3, width: 10, height: 10); sb.addPlatformView(4, width: 10, height: 10); sb.pop(); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -889,7 +888,7 @@ void testMain() { sb.addPlatformView(4, width: 10, height: 10); sb.addPlatformView(5, width: 10, height: 10); sb.pop(); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -912,7 +911,7 @@ void testMain() { sb.addPlatformView(5, width: 10, height: 10); sb.addPlatformView(6, width: 10, height: 10); sb.pop(); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -934,7 +933,7 @@ void testMain() { sb.addPlatformView(5, width: 10, height: 10); sb.addPlatformView(6, width: 10, height: 10); sb.pop(); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -953,7 +952,7 @@ void testMain() { sb.addPlatformView(3, width: 10, height: 10); sb.addPlatformView(4, width: 10, height: 10); sb.pop(); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -970,7 +969,7 @@ void testMain() { sb.addPlatformView(2, width: 10, height: 10); sb.addPlatformView(1, width: 10, height: 10); sb.pop(); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); + renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, diff --git a/lib/web_ui/test/canvaskit/image_golden_test.dart b/lib/web_ui/test/canvaskit/image_golden_test.dart index a662b25ea0bfd..d622164c4ed96 100644 --- a/lib/web_ui/test/canvaskit/image_golden_test.dart +++ b/lib/web_ui/test/canvaskit/image_golden_test.dart @@ -10,7 +10,6 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; import 'package:ui/ui_web/src/ui_web.dart' as ui_web; -import 'package:web_engine_tester/golden_tester.dart'; import '../common/matchers.dart'; import 'common.dart'; @@ -637,11 +636,9 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) { canvas.drawImage(snapshot, ui.Offset.zero, CkPaint()); sb.addPicture(ui.Offset.zero, recorder.endRecording()); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); - await matchGoldenFile( - 'canvaskit_read_back_decoded_image_$mode.png', - region: const ui.Rect.fromLTRB(0, 0, 150, 150), - ); + await matchSceneGolden( + 'canvaskit_read_back_decoded_image_$mode.png', sb.build(), + region: const ui.Rect.fromLTRB(0, 0, 150, 150)); } image.dispose(); @@ -687,11 +684,10 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) { canvas.drawParagraph(makeSimpleText('2'), const ui.Offset(2, 2)); sb.addPicture(ui.Offset.zero, recorder.endRecording()); } - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); - await matchGoldenFile( - 'canvaskit_cross_gl_context_image_$mode.png', - region: const ui.Rect.fromLTRB(0, 0, 100, 100), - ); + + await matchSceneGolden( + 'canvaskit_cross_gl_context_image_$mode.png', sb.build(), + region: const ui.Rect.fromLTRB(0, 0, 100, 100)); await disposePlatformView(0); }); @@ -731,11 +727,10 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) { canvas.restore(); sb.addPicture(ui.Offset.zero, recorder.endRecording()); } - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); - await matchGoldenFile( - 'canvaskit_picture_texture_toimage.png', - region: const ui.Rect.fromLTRB(0, 0, 128, 128), - ); + + await matchSceneGolden( + 'canvaskit_picture_texture_toimage.png', sb.build(), + region: const ui.Rect.fromLTRB(0, 0, 128, 128)); mandrill.dispose(); codec.dispose(); }); @@ -774,11 +769,9 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) { canvas.drawImage(snapshot, ui.Offset.zero, CkPaint()); sb.addPicture(ui.Offset.zero, recorder.endRecording()); - CanvasKitRenderer.instance.renderScene(sb.build(), implicitView); - await matchGoldenFile( - 'canvaskit_read_back_decoded_image_$mode.png', - region: const ui.Rect.fromLTRB(0, 0, 150, 150), - ); + await matchSceneGolden( + 'canvaskit_read_back_decoded_image_$mode.png', sb.build(), + region: const ui.Rect.fromLTRB(0, 0, 150, 150)); } image.dispose(); diff --git a/lib/web_ui/test/canvaskit/layer_test.dart b/lib/web_ui/test/canvaskit/layer_test.dart index cf83e8abee889..a0820f1716bd0 100644 --- a/lib/web_ui/test/canvaskit/layer_test.dart +++ b/lib/web_ui/test/canvaskit/layer_test.dart @@ -39,7 +39,7 @@ void testMain() { sb.addPicture(ui.Offset.zero, picture); final LayerScene scene = sb.build(); final LayerTree layerTree = scene.layerTree; - CanvasKitRenderer.instance.renderScene(scene, implicitView); + renderScene(scene); final ClipRectEngineLayer clipRect = layerTree.rootLayer.debugLayers.single as ClipRectEngineLayer; expect(clipRect.paintBounds, const ui.Rect.fromLTRB(15, 15, 30, 30)); @@ -95,7 +95,7 @@ void testMain() { final LayerScene scene = sb.build(); final LayerTree layerTree = scene.layerTree; - CanvasKitRenderer.instance.renderScene(scene, implicitView); + renderScene(scene); final ImageFilterEngineLayer imageFilterLayer = layerTree.rootLayer.debugLayers.single as ImageFilterEngineLayer; diff --git a/lib/web_ui/test/common/rendering.dart b/lib/web_ui/test/common/rendering.dart new file mode 100644 index 0000000000000..077cdab9c10f1 --- /dev/null +++ b/lib/web_ui/test/common/rendering.dart @@ -0,0 +1,27 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:ui/src/engine.dart'; +import 'package:ui/ui.dart' as ui; + +// The scene that will be rendered in the next call to `onDrawFrame`. +ui.Scene? _sceneToRender; + +/// Sets up rendering so that `onDrawFrame` will render the last requested +/// scene. +void setUpRenderingForTests() { + // Set `onDrawFrame` to call `renderer.renderScene`. + EnginePlatformDispatcher.instance.onDrawFrame = () { + if (_sceneToRender != null) { + EnginePlatformDispatcher.instance.render(_sceneToRender!); + _sceneToRender = null; + } + }; +} + +/// Render the given [scene] in an `onDrawFrame` scope. +void renderScene(ui.Scene scene) { + _sceneToRender = scene; + EnginePlatformDispatcher.instance.invokeOnDrawFrame(); +} diff --git a/lib/web_ui/test/common/test_initialization.dart b/lib/web_ui/test/common/test_initialization.dart index b6359877d587f..0aa715232389c 100644 --- a/lib/web_ui/test/common/test_initialization.dart +++ b/lib/web_ui/test/common/test_initialization.dart @@ -11,6 +11,7 @@ import 'package:ui/ui.dart' as ui; import 'package:ui/ui_web/src/ui_web.dart' as ui_web; import 'fake_asset_manager.dart'; +import 'rendering.dart'; void setUpUnitTests({ bool emulateTesterEnvironment = true, @@ -44,6 +45,8 @@ void setUpUnitTests({ const ui.Size(800 * devicePixelRatio, 600 * devicePixelRatio); engine.scheduleFrameCallback = () {}; } + + setUpRenderingForTests(); }); tearDownAll(() async { diff --git a/lib/web_ui/test/ui/platform_view_test.dart b/lib/web_ui/test/ui/platform_view_test.dart index 7d0b01c2fbb82..01e37e5987504 100644 --- a/lib/web_ui/test/ui/platform_view_test.dart +++ b/lib/web_ui/test/ui/platform_view_test.dart @@ -11,8 +11,8 @@ import 'package:ui/ui.dart' as ui; import 'package:ui/ui_web/src/ui_web.dart' as ui_web; import 'package:web_engine_tester/golden_tester.dart'; +import '../common/rendering.dart'; import '../common/test_initialization.dart'; -import 'utils.dart'; void main() { internalBootstrapBrowserTest(() => testMain); @@ -65,7 +65,7 @@ Future testMain() async { width: 50, height: 50, ); - await renderer.renderScene(sb.build(), implicitView); + renderScene(sb.build()); await matchGoldenFile('picture_platformview_overlap.png', region: region); }); @@ -97,7 +97,7 @@ Future testMain() async { ); sb.addPicture(const ui.Offset(125, 125), picture); - await renderer.renderScene(sb.build(), implicitView); + renderScene(sb.build()); await matchGoldenFile('picture_platformview_sandwich.png', region: region); }); @@ -126,7 +126,7 @@ Future testMain() async { width: 50, height: 50, ); - await renderer.renderScene(sb.build(), implicitView); + renderScene(sb.build()); await matchGoldenFile('platformview_transformed.png', region: region); }); @@ -155,7 +155,7 @@ Future testMain() async { width: 50, height: 50, ); - await renderer.renderScene(sb.build(), implicitView); + renderScene(sb.build()); await matchGoldenFile('platformview_opacity.png', region: region); }); diff --git a/lib/web_ui/test/ui/scene_builder_test.dart b/lib/web_ui/test/ui/scene_builder_test.dart index 6ecc2f33f896e..36ef583e4b7c1 100644 --- a/lib/web_ui/test/ui/scene_builder_test.dart +++ b/lib/web_ui/test/ui/scene_builder_test.dart @@ -10,6 +10,7 @@ import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; import 'package:web_engine_tester/golden_tester.dart'; +import '../common/rendering.dart'; import '../common/test_initialization.dart'; import 'utils.dart'; @@ -36,7 +37,7 @@ Future testMain() async { ); })); - await renderer.renderScene(sceneBuilder.build(), implicitView); + renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_centered_circle.png', region: region); }); @@ -60,7 +61,7 @@ Future testMain() async { ); })); - await renderer.renderScene(sceneBuilder.build(), implicitView); + renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_rotated_rounded_square.png', region: region); }); @@ -75,7 +76,7 @@ Future testMain() async { ); })); - await renderer.renderScene(sceneBuilder.build(), implicitView); + renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_circle_clip_rect.png', region: region); }); @@ -93,7 +94,7 @@ Future testMain() async { ); })); - await renderer.renderScene(sceneBuilder.build(), implicitView); + renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_circle_clip_rrect.png', region: region); }); @@ -109,7 +110,7 @@ Future testMain() async { ); })); - await renderer.renderScene(sceneBuilder.build(), implicitView); + renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_rectangle_clip_circular_path.png', region: region); }); @@ -137,7 +138,7 @@ Future testMain() async { ); })); - await renderer.renderScene(sceneBuilder.build(), implicitView); + renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_opacity_circles_on_square.png', region: region); }); @@ -177,7 +178,7 @@ Future testMain() async { ); })); - await renderer.renderScene(sceneBuilder.build(), implicitView); + renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_shader_mask.png', region: region); }, skip: isFirefox && isHtml); // https://github.com/flutter/flutter/issues/86623 @@ -209,7 +210,7 @@ Future testMain() async { ); })); - await renderer.renderScene(sceneBuilder.build(), implicitView); + renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_backdrop_filter.png', region: region); }); @@ -228,7 +229,7 @@ Future testMain() async { ); })); - await renderer.renderScene(sceneBuilder.build(), implicitView); + renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_image_filter.png', region: region); }); @@ -250,7 +251,7 @@ Future testMain() async { ); })); - await renderer.renderScene(sceneBuilder.build(), implicitView); + renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_color_filter.png', region: region); }); }); diff --git a/lib/web_ui/test/ui/utils.dart b/lib/web_ui/test/ui/utils.dart index 24d8c148f240e..eaeaf2092280b 100644 --- a/lib/web_ui/test/ui/utils.dart +++ b/lib/web_ui/test/ui/utils.dart @@ -9,6 +9,8 @@ import 'package:ui/src/engine/skwasm/skwasm_stub.dart' if (dart.library.ffi) 'package:ui/src/engine/skwasm/skwasm_impl.dart'; import 'package:ui/ui.dart'; +import '../common/rendering.dart'; + Picture drawPicture(void Function(Canvas) drawCommands) { final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); @@ -21,7 +23,7 @@ Future drawPictureUsingCurrentRenderer(Picture picture) async { final SceneBuilder sb = SceneBuilder(); sb.pushOffset(0, 0); sb.addPicture(Offset.zero, picture); - await renderer.renderScene(sb.build(), implicitView); + renderScene(sb.build()); } /// Convenience getter for the implicit view. From d5e1fc1d91658aee724ae5df4ee72d79c7d223db Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 19 Dec 2023 11:10:19 -0800 Subject: [PATCH 2/8] Futurize EnginePlatformDispatcher.render --- lib/web_ui/lib/platform_dispatcher.dart | 2 +- .../lib/src/engine/platform_dispatcher.dart | 4 +- .../test/canvaskit/canvas_golden_test.dart | 2 +- lib/web_ui/test/canvaskit/common.dart | 4 +- .../test/canvaskit/embedded_views_test.dart | 101 ++++++++---------- lib/web_ui/test/canvaskit/layer_test.dart | 4 +- .../test/common/frame_timings_common.dart | 5 +- lib/web_ui/test/common/rendering.dart | 15 ++- .../test/engine/semantics/semantics_test.dart | 3 +- .../engine/surface/scene_builder_test.dart | 4 +- .../test/html/text/canvas_paragraph_test.dart | 5 +- lib/web_ui/test/ui/platform_view_test.dart | 8 +- lib/web_ui/test/ui/scene_builder_test.dart | 20 ++-- lib/web_ui/test/ui/utils.dart | 2 +- 14 files changed, 93 insertions(+), 86 deletions(-) diff --git a/lib/web_ui/lib/platform_dispatcher.dart b/lib/web_ui/lib/platform_dispatcher.dart index f463595697e88..7f0d66f7fc173 100644 --- a/lib/web_ui/lib/platform_dispatcher.dart +++ b/lib/web_ui/lib/platform_dispatcher.dart @@ -80,7 +80,7 @@ abstract class PlatformDispatcher { void scheduleFrame(); - void render(Scene scene, [FlutterView view]); + Future render(Scene scene, [FlutterView view]); AccessibilityFeatures get accessibilityFeatures; diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index fb07541aa30d3..905bca78fdc6c 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -761,7 +761,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// * [RendererBinding], the Flutter framework class which manages layout and /// painting. @override - void render(ui.Scene scene, [ui.FlutterView? view]) { + Future render(ui.Scene scene, [ui.FlutterView? view]) async { assert(view != null || implicitView != null, 'Calling render without a FlutterView'); if (view == null && implicitView == null) { @@ -776,7 +776,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { final bool shouldRender = _viewsRenderedInCurrentFrame?.add(viewToRender) ?? false; if (shouldRender) { - renderer.renderScene(scene, view ?? implicitView!); + await renderer.renderScene(scene, view ?? implicitView!); } } diff --git a/lib/web_ui/test/canvaskit/canvas_golden_test.dart b/lib/web_ui/test/canvaskit/canvas_golden_test.dart index 57ef4276cfc5a..4d3c7bc4cddcb 100644 --- a/lib/web_ui/test/canvaskit/canvas_golden_test.dart +++ b/lib/web_ui/test/canvaskit/canvas_golden_test.dart @@ -145,7 +145,7 @@ void testMain() { builder.pushOffset(0, 0); builder.addPicture(ui.Offset.zero, picture); final LayerScene scene = builder.build(); - renderScene(scene); + await renderScene(scene); // Now draw an empty layer tree and confirm that the red rectangle is // no longer drawn. diff --git a/lib/web_ui/test/canvaskit/common.dart b/lib/web_ui/test/canvaskit/common.dart index 5649cee4e20fd..8a545c2a079fd 100644 --- a/lib/web_ui/test/canvaskit/common.dart +++ b/lib/web_ui/test/canvaskit/common.dart @@ -50,7 +50,7 @@ Future matchSceneGolden( ui.Scene scene, { required ui.Rect region, }) async { - renderScene(scene); + await renderScene(scene); await matchGoldenFile(goldenFile, region: region); } @@ -63,7 +63,7 @@ Future matchPictureGolden(String goldenFile, CkPicture picture, final LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.addPicture(ui.Offset.zero, picture); - renderScene(sb.build()); + await renderScene(sb.build()); await matchGoldenFile(goldenFile, region: region); } diff --git a/lib/web_ui/test/canvaskit/embedded_views_test.dart b/lib/web_ui/test/canvaskit/embedded_views_test.dart index cba461b36f036..57ef4a8d74c31 100644 --- a/lib/web_ui/test/canvaskit/embedded_views_test.dart +++ b/lib/web_ui/test/canvaskit/embedded_views_test.dart @@ -41,7 +41,7 @@ void testMain() { final LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.addPlatformView(0, width: 10, height: 10); - renderScene(sb.build()); + await renderScene(sb.build()); // The platform view is now split in two parts. The contents live // as a child of the glassPane, and the slot lives in the glassPane @@ -77,7 +77,7 @@ void testMain() { sb.pushClipRRect( ui.RRect.fromLTRBR(0, 0, 10, 10, const ui.Radius.circular(3))); sb.addPlatformView(0, width: 10, height: 10); - renderScene(sb.build()); + await renderScene(sb.build()); expect( sceneHost.querySelectorAll('#sk_path_defs').single, @@ -122,7 +122,7 @@ void testMain() { sb.pushTransform(scaleMatrix.toFloat64()); sb.pushOffset(3, 3); sb.addPlatformView(0, width: 10, height: 10); - renderScene(sb.build()); + await renderScene(sb.build()); // Transformations happen on the slot element. final DomElement slotHost = @@ -148,7 +148,7 @@ void testMain() { final LayerSceneBuilder sb = LayerSceneBuilder(); sb.addPlatformView(0, offset: const ui.Offset(3, 4), width: 5, height: 6); - renderScene(sb.build()); + await renderScene(sb.build()); final DomElement slotHost = sceneHost.querySelector('flt-platform-view-slot')!; @@ -194,7 +194,7 @@ void testMain() { sb.pushOffset(6, 6); sb.addPlatformView(0, width: 10, height: 10); sb.pop(); - renderScene(sb.build()); + await renderScene(sb.build()); // Transformations happen on the slot element. DomElement slotHost = sceneHost.querySelector('flt-platform-view-slot')!; @@ -214,7 +214,7 @@ void testMain() { sb.pushClipRect(ui.Rect.largest); sb.pushOffset(9, 9); sb.addPlatformView(0, width: 10, height: 10); - renderScene(sb.build()); + await renderScene(sb.build()); // Transformations happen on the slot element. slotHost = sceneHost.querySelector('flt-platform-view-slot')!; @@ -244,7 +244,7 @@ void testMain() { sb.pushOffset(2, 2); sb.pushOffset(3, 3); sb.addPlatformView(0, width: 10, height: 10); - renderScene(sb.build()); + await renderScene(sb.build()); // Transformations happen on the slot element. final DomElement slotHost = @@ -273,7 +273,7 @@ void testMain() { sb.pushClipRect(ui.Rect.largest); sb.pushOffset(9, 9); sb.addPlatformView(0, width: 10, height: 10); - renderScene(sb.build()); + await renderScene(sb.build()); // Transformations happen on the slot element. final DomElement slotHost = @@ -308,20 +308,20 @@ void testMain() { platformViewIds.add(i); } - void renderTestScene({required int viewCount}) { + Future renderTestScene({required int viewCount}) async { final LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); for (int i = 0; i < viewCount; i++) { sb.addPicture(ui.Offset.zero, testPicture); sb.addPlatformView(i, width: 10, height: 10); } - renderScene(sb.build()); + await renderScene(sb.build()); } // Frame 1: // Render: up to cache size platform views. // Expect: main canvas plus platform view overlays. - renderTestScene(viewCount: 8); + await renderTestScene(viewCount: 8); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -344,15 +344,13 @@ void testMain() { // Frame 2: // Render: zero platform views. // Expect: main canvas, no overlays. - await Future.delayed(Duration.zero); - renderTestScene(viewCount: 0); + await renderTestScene(viewCount: 0); _expectSceneMatches(<_EmbeddedViewMarker>[_overlay]); // Frame 3: // Render: less than cache size platform views. // Expect: overlays reused. - await Future.delayed(Duration.zero); - renderTestScene(viewCount: 6); + await renderTestScene(viewCount: 6); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -372,8 +370,7 @@ void testMain() { // Frame 4: // Render: more platform views than max overlay count. // Expect: main canvas, backup overlay, maximum overlays. - await Future.delayed(Duration.zero); - renderTestScene(viewCount: 16); + await renderTestScene(viewCount: 16); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -404,8 +401,7 @@ void testMain() { // Frame 5: // Render: zero platform views. // Expect: main canvas, no overlays. - await Future.delayed(Duration.zero); - renderTestScene(viewCount: 0); + await renderTestScene(viewCount: 0); _expectSceneMatches(<_EmbeddedViewMarker>[_overlay]); // Frame 6: @@ -426,7 +422,7 @@ void testMain() { } try { - renderTestScene(viewCount: platformViewIds.length); + await renderTestScene(viewCount: platformViewIds.length); fail('Expected to throw'); } on AssertionError catch (error) { expect( @@ -439,7 +435,7 @@ void testMain() { // Render: a platform view after error. // Expect: success. Just checking the system is not left in a corrupted state. await createPlatformView(0, 'test-platform-view'); - renderTestScene(viewCount: 0); + await renderTestScene(viewCount: 0); _expectSceneMatches(<_EmbeddedViewMarker>[_overlay]); for (int i = 0; i < 16; i++) { @@ -464,20 +460,20 @@ void testMain() { platformViewIds.add(i); } - void renderTestScene(List views) { + Future renderTestScene(List views) async { final LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); for (final int view in views) { sb.addPicture(ui.Offset.zero, testPicture); sb.addPlatformView(view, width: 10, height: 10); } - renderScene(sb.build()); + await renderScene(sb.build()); } // Frame 1: // Render: Views 1-10 // Expect: main canvas plus platform view overlays; empty cache. - renderTestScene([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + await renderTestScene([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -502,8 +498,7 @@ void testMain() { // Frame 2: // Render: Views 2-11 // Expect: main canvas plus platform view overlays; empty cache. - await Future.delayed(Duration.zero); - renderTestScene([2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); + await renderTestScene([2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -528,8 +523,7 @@ void testMain() { // Frame 3: // Render: Views 3-12 // Expect: main canvas plus platform view overlays; empty cache. - await Future.delayed(Duration.zero); - renderTestScene([3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + await renderTestScene([3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -554,8 +548,7 @@ void testMain() { // Frame 4: // Render: Views 3-12 again (same as last frame) // Expect: main canvas plus platform view overlays; empty cache. - await Future.delayed(Duration.zero); - renderTestScene([3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + await renderTestScene([3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -592,7 +585,7 @@ void testMain() { LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.addPlatformView(0, width: 10, height: 10); - renderScene(sb.build()); + await renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -605,7 +598,7 @@ void testMain() { sb = LayerSceneBuilder(); sb.pushOffset(0, 0); - renderScene(sb.build()); + await renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, @@ -641,7 +634,7 @@ void testMain() { implicitView.debugPhysicalSizeOverride = const ui.Size(100, 100); implicitView.debugForceResize(); - renderScene(sb.build()); + await renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -650,7 +643,7 @@ void testMain() { implicitView.debugPhysicalSizeOverride = const ui.Size(200, 200); implicitView.debugForceResize(); - renderScene(sb.build()); + await renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -674,7 +667,7 @@ void testMain() { LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.addPlatformView(0, width: 10, height: 10); - renderScene(sb.build()); + await renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -688,7 +681,7 @@ void testMain() { sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.addPlatformView(1, width: 10, height: 10); - renderScene(sb.build()); + await renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -704,7 +697,7 @@ void testMain() { // the platform view. sb = LayerSceneBuilder(); sb.pushOffset(0, 0); - renderScene(sb.build()); + await renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, ]); @@ -729,28 +722,26 @@ void testMain() { ); await createPlatformView(0, 'test-platform-view'); - void renderTestScene() { + Future renderTestScene() async { final LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.pushClipRRect( ui.RRect.fromLTRBR(0, 0, 10, 10, const ui.Radius.circular(3))); sb.addPlatformView(0, width: 10, height: 10); - renderScene(sb.build()); + await renderScene(sb.build()); } final DomNode skPathDefs = sceneHost.querySelector('#sk_path_defs')!; expect(skPathDefs.childNodes, hasLength(0)); - renderTestScene(); + await renderTestScene(); expect(skPathDefs.childNodes, hasLength(1)); - await Future.delayed(Duration.zero); - renderTestScene(); + await renderTestScene(); expect(skPathDefs.childNodes, hasLength(1)); - await Future.delayed(Duration.zero); - renderTestScene(); + await renderTestScene(); expect(skPathDefs.childNodes, hasLength(1)); await disposePlatformView(0); @@ -770,7 +761,7 @@ void testMain() { sb.addPlatformView(0, width: 10, height: 10); sb.pop(); // The below line should not throw an error. - renderScene(sb.build()); + await renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, ]); @@ -804,7 +795,7 @@ void testMain() { sb.pushOffset(0, 0); sb.addPlatformView(1, width: 10, height: 10); sb.pop(); - renderScene(sb.build()); + await renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -815,7 +806,7 @@ void testMain() { sb.addPlatformView(0, width: 10, height: 10); sb.addPlatformView(1, width: 10, height: 10); sb.pop(); - renderScene(sb.build()); + await renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -829,7 +820,7 @@ void testMain() { sb.addPlatformView(1, width: 10, height: 10); sb.addPlatformView(2, width: 10, height: 10); sb.pop(); - renderScene(sb.build()); + await renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -848,7 +839,7 @@ void testMain() { sb.addPlatformView(2, width: 10, height: 10); sb.addPlatformView(3, width: 10, height: 10); sb.pop(); - renderScene(sb.build()); + await renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -867,7 +858,7 @@ void testMain() { sb.addPlatformView(3, width: 10, height: 10); sb.addPlatformView(4, width: 10, height: 10); sb.pop(); - renderScene(sb.build()); + await renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -888,7 +879,7 @@ void testMain() { sb.addPlatformView(4, width: 10, height: 10); sb.addPlatformView(5, width: 10, height: 10); sb.pop(); - renderScene(sb.build()); + await renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -911,7 +902,7 @@ void testMain() { sb.addPlatformView(5, width: 10, height: 10); sb.addPlatformView(6, width: 10, height: 10); sb.pop(); - renderScene(sb.build()); + await renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -933,7 +924,7 @@ void testMain() { sb.addPlatformView(5, width: 10, height: 10); sb.addPlatformView(6, width: 10, height: 10); sb.pop(); - renderScene(sb.build()); + await renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -952,7 +943,7 @@ void testMain() { sb.addPlatformView(3, width: 10, height: 10); sb.addPlatformView(4, width: 10, height: 10); sb.pop(); - renderScene(sb.build()); + await renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -969,7 +960,7 @@ void testMain() { sb.addPlatformView(2, width: 10, height: 10); sb.addPlatformView(1, width: 10, height: 10); sb.pop(); - renderScene(sb.build()); + await renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, diff --git a/lib/web_ui/test/canvaskit/layer_test.dart b/lib/web_ui/test/canvaskit/layer_test.dart index a0820f1716bd0..5b308ff8e6900 100644 --- a/lib/web_ui/test/canvaskit/layer_test.dart +++ b/lib/web_ui/test/canvaskit/layer_test.dart @@ -39,7 +39,7 @@ void testMain() { sb.addPicture(ui.Offset.zero, picture); final LayerScene scene = sb.build(); final LayerTree layerTree = scene.layerTree; - renderScene(scene); + await renderScene(scene); final ClipRectEngineLayer clipRect = layerTree.rootLayer.debugLayers.single as ClipRectEngineLayer; expect(clipRect.paintBounds, const ui.Rect.fromLTRB(15, 15, 30, 30)); @@ -95,7 +95,7 @@ void testMain() { final LayerScene scene = sb.build(); final LayerTree layerTree = scene.layerTree; - renderScene(scene); + await renderScene(scene); final ImageFilterEngineLayer imageFilterLayer = layerTree.rootLayer.debugLayers.single as ImageFilterEngineLayer; diff --git a/lib/web_ui/test/common/frame_timings_common.dart b/lib/web_ui/test/common/frame_timings_common.dart index 51087f3094b39..2bcc08438eb2a 100644 --- a/lib/web_ui/test/common/frame_timings_common.dart +++ b/lib/web_ui/test/common/frame_timings_common.dart @@ -21,8 +21,9 @@ Future runFrameTimingsTest() async { sceneBuilder ..pushOffset(0, 0) ..pop(); - ui.PlatformDispatcher.instance.render(sceneBuilder.build()); - frameDone.complete(); + ui.PlatformDispatcher.instance.render(sceneBuilder.build()).then((_) { + frameDone.complete(); + }); }; // Frame 1. diff --git a/lib/web_ui/test/common/rendering.dart b/lib/web_ui/test/common/rendering.dart index 077cdab9c10f1..89ecc319b19bb 100644 --- a/lib/web_ui/test/common/rendering.dart +++ b/lib/web_ui/test/common/rendering.dart @@ -2,26 +2,37 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; // The scene that will be rendered in the next call to `onDrawFrame`. ui.Scene? _sceneToRender; +// Completer that will complete when the call to render completes. +Completer? _sceneCompleter; + /// Sets up rendering so that `onDrawFrame` will render the last requested /// scene. void setUpRenderingForTests() { // Set `onDrawFrame` to call `renderer.renderScene`. EnginePlatformDispatcher.instance.onDrawFrame = () { if (_sceneToRender != null) { - EnginePlatformDispatcher.instance.render(_sceneToRender!); + EnginePlatformDispatcher.instance.render(_sceneToRender!).then((_) { + _sceneCompleter?.complete(); + }).catchError((Object error) { + _sceneCompleter?.completeError(error); + }); _sceneToRender = null; } }; } /// Render the given [scene] in an `onDrawFrame` scope. -void renderScene(ui.Scene scene) { +Future renderScene(ui.Scene scene) { _sceneToRender = scene; + _sceneCompleter = Completer(); EnginePlatformDispatcher.instance.invokeOnDrawFrame(); + return _sceneCompleter!.future; } diff --git a/lib/web_ui/test/engine/semantics/semantics_test.dart b/lib/web_ui/test/engine/semantics/semantics_test.dart index 4cc3c8bd60246..1dade2f885913 100644 --- a/lib/web_ui/test/engine/semantics/semantics_test.dart +++ b/lib/web_ui/test/engine/semantics/semantics_test.dart @@ -13,6 +13,7 @@ import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; import 'package:ui/ui_web/src/ui_web.dart' as ui_web; +import '../../common/rendering.dart'; import '../../common/test_initialization.dart'; import 'semantics_tester.dart'; @@ -2497,7 +2498,7 @@ void _testPlatformView() { width: 20, height: 30, ); - ui.PlatformDispatcher.instance.render(sceneBuilder.build()); + await renderScene(sceneBuilder.build()); final ui.SemanticsUpdateBuilder builder = ui.SemanticsUpdateBuilder(); final double dpr = EngineFlutterDisplay.instance.devicePixelRatio; diff --git a/lib/web_ui/test/engine/surface/scene_builder_test.dart b/lib/web_ui/test/engine/surface/scene_builder_test.dart index 3c893ddc2ec6f..bb6698d04104c 100644 --- a/lib/web_ui/test/engine/surface/scene_builder_test.dart +++ b/lib/web_ui/test/engine/surface/scene_builder_test.dart @@ -15,6 +15,7 @@ import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; import '../../common/matchers.dart'; +import '../../common/rendering.dart'; import '../../common/test_initialization.dart'; void main() { @@ -24,6 +25,7 @@ void main() { void testMain() { setUpAll(() async { await bootstrapAndRunApp(); + setUpRenderingForTests(); }); group('SceneBuilder', () { @@ -477,7 +479,7 @@ void testMain() { // Pump an empty scene to reset it, otherwise the first frame will attempt // to diff left-overs from a previous test, which results in unpredictable // DOM mutations. - ui.PlatformDispatcher.instance.render(SurfaceSceneBuilder().build()); + await renderScene(SurfaceSceneBuilder().build()); // Renders a `string` by breaking it up into individual characters and // rendering each character into its own layer. diff --git a/lib/web_ui/test/html/text/canvas_paragraph_test.dart b/lib/web_ui/test/html/text/canvas_paragraph_test.dart index 4a38f4f5cee1b..38b26ee898bc0 100644 --- a/lib/web_ui/test/html/text/canvas_paragraph_test.dart +++ b/lib/web_ui/test/html/text/canvas_paragraph_test.dart @@ -7,6 +7,7 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; +import '../../common/rendering.dart'; import '../../common/test_initialization.dart'; import '../paragraph/helper.dart'; @@ -791,7 +792,7 @@ Future testMain() async { expect(paragraph.height, 10); }); - test('Render after dispose', () { + test('Render after dispose', () async { final ui.Paragraph paragraph = plain(ahemStyle, 'abc'); paragraph.layout(const ui.ParagraphConstraints(width: 30.8)); @@ -806,7 +807,7 @@ Future testMain() async { builder.addPicture(ui.Offset.zero, picture); final ui.Scene scene = builder.build(); - ui.PlatformDispatcher.instance.render(scene); + await renderScene(scene); picture.dispose(); scene.dispose(); diff --git a/lib/web_ui/test/ui/platform_view_test.dart b/lib/web_ui/test/ui/platform_view_test.dart index 01e37e5987504..ccfc595e12ce1 100644 --- a/lib/web_ui/test/ui/platform_view_test.dart +++ b/lib/web_ui/test/ui/platform_view_test.dart @@ -65,7 +65,7 @@ Future testMain() async { width: 50, height: 50, ); - renderScene(sb.build()); + await renderScene(sb.build()); await matchGoldenFile('picture_platformview_overlap.png', region: region); }); @@ -97,7 +97,7 @@ Future testMain() async { ); sb.addPicture(const ui.Offset(125, 125), picture); - renderScene(sb.build()); + await renderScene(sb.build()); await matchGoldenFile('picture_platformview_sandwich.png', region: region); }); @@ -126,7 +126,7 @@ Future testMain() async { width: 50, height: 50, ); - renderScene(sb.build()); + await renderScene(sb.build()); await matchGoldenFile('platformview_transformed.png', region: region); }); @@ -155,7 +155,7 @@ Future testMain() async { width: 50, height: 50, ); - renderScene(sb.build()); + await renderScene(sb.build()); await matchGoldenFile('platformview_opacity.png', region: region); }); diff --git a/lib/web_ui/test/ui/scene_builder_test.dart b/lib/web_ui/test/ui/scene_builder_test.dart index 36ef583e4b7c1..b94e28c5c6920 100644 --- a/lib/web_ui/test/ui/scene_builder_test.dart +++ b/lib/web_ui/test/ui/scene_builder_test.dart @@ -37,7 +37,7 @@ Future testMain() async { ); })); - renderScene(sceneBuilder.build()); + await renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_centered_circle.png', region: region); }); @@ -61,7 +61,7 @@ Future testMain() async { ); })); - renderScene(sceneBuilder.build()); + await renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_rotated_rounded_square.png', region: region); }); @@ -76,7 +76,7 @@ Future testMain() async { ); })); - renderScene(sceneBuilder.build()); + await renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_circle_clip_rect.png', region: region); }); @@ -94,7 +94,7 @@ Future testMain() async { ); })); - renderScene(sceneBuilder.build()); + await renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_circle_clip_rrect.png', region: region); }); @@ -110,7 +110,7 @@ Future testMain() async { ); })); - renderScene(sceneBuilder.build()); + await renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_rectangle_clip_circular_path.png', region: region); }); @@ -138,7 +138,7 @@ Future testMain() async { ); })); - renderScene(sceneBuilder.build()); + await renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_opacity_circles_on_square.png', region: region); }); @@ -178,7 +178,7 @@ Future testMain() async { ); })); - renderScene(sceneBuilder.build()); + await renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_shader_mask.png', region: region); }, skip: isFirefox && isHtml); // https://github.com/flutter/flutter/issues/86623 @@ -210,7 +210,7 @@ Future testMain() async { ); })); - renderScene(sceneBuilder.build()); + await renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_backdrop_filter.png', region: region); }); @@ -229,7 +229,7 @@ Future testMain() async { ); })); - renderScene(sceneBuilder.build()); + await renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_image_filter.png', region: region); }); @@ -251,7 +251,7 @@ Future testMain() async { ); })); - renderScene(sceneBuilder.build()); + await renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_color_filter.png', region: region); }); }); diff --git a/lib/web_ui/test/ui/utils.dart b/lib/web_ui/test/ui/utils.dart index eaeaf2092280b..ec7eaf4c0aed8 100644 --- a/lib/web_ui/test/ui/utils.dart +++ b/lib/web_ui/test/ui/utils.dart @@ -23,7 +23,7 @@ Future drawPictureUsingCurrentRenderer(Picture picture) async { final SceneBuilder sb = SceneBuilder(); sb.pushOffset(0, 0); sb.addPicture(Offset.zero, picture); - renderScene(sb.build()); + await renderScene(sb.build()); } /// Convenience getter for the implicit view. From 0ec6420ef515b9749467da22ef28ff7b63889fa7 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 19 Dec 2023 11:41:29 -0800 Subject: [PATCH 3/8] Futurize more --- .../lib/src/engine/canvaskit/rasterizer.dart | 4 ++-- .../lib/src/engine/canvaskit/renderer.dart | 4 ++-- .../lib/src/engine/canvaskit/surface.dart | 22 ++++++++----------- lib/web_ui/lib/src/engine/dom.dart | 8 ++++--- lib/web_ui/lib/src/engine/html/renderer.dart | 2 +- lib/web_ui/lib/src/engine/renderer.dart | 2 +- .../engine/skwasm/skwasm_stub/renderer.dart | 2 +- .../test/engine/semantics/semantics_test.dart | 1 + 8 files changed, 22 insertions(+), 23 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart b/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart index d9280f28b6d7e..9d00cde516fbf 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart @@ -30,7 +30,7 @@ class Rasterizer { /// Creates a new frame from this rasterizer's surface, draws the given /// [LayerTree] into it, and then submits the frame. - void draw(LayerTree layerTree) { + Future draw(LayerTree layerTree) async { final ui.Size frameSize = view.physicalSize; if (frameSize.isEmpty) { // Available drawing area is empty. Skip drawing. @@ -49,7 +49,7 @@ class Rasterizer { compositorFrame.raster(layerTree, ignoreRasterCache: true); sceneHost.prepend(renderCanvasFactory.baseCanvas.htmlElement); - rasterizeToCanvas(renderCanvasFactory.baseCanvas, + await rasterizeToCanvas(renderCanvasFactory.baseCanvas, [pictureRecorder.endRecording()]); viewEmbedder.submitFrame(); diff --git a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart index 567fac6849883..deb63b4b159bf 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart @@ -402,7 +402,7 @@ class CanvasKitRenderer implements Renderer { CkParagraphBuilder(style); @override - void renderScene(ui.Scene scene, ui.FlutterView view) { + Future renderScene(ui.Scene scene, ui.FlutterView view) async { // "Build finish" and "raster start" happen back-to-back because we // render on the same thread, so there's no overhead from hopping to // another thread. @@ -417,7 +417,7 @@ class CanvasKitRenderer implements Renderer { "Unable to render to a view which hasn't been registered"); final Rasterizer rasterizer = _rasterizers[view.viewId]!; - rasterizer.draw((scene as LayerScene).layerTree); + await rasterizer.draw((scene as LayerScene).layerTree); frameTimingsOnRasterFinish(); } diff --git a/lib/web_ui/lib/src/engine/canvaskit/surface.dart b/lib/web_ui/lib/src/engine/canvaskit/surface.dart index f86998f43f421..d3e1b8f9f150a 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/surface.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/surface.dart @@ -115,22 +115,18 @@ class Surface { _surface!.flush(); if (browserSupportsCreateImageBitmap) { - DomImageBitmap bitmap; + JSObject bitmapSource; if (Surface.offscreenCanvasSupported) { - bitmap = (await createImageBitmap(_offscreenCanvas! as JSObject, ( - x: 0, - y: _pixelHeight - frameSize.height.toInt(), - width: frameSize.width.toInt(), - height: frameSize.height.toInt(), - )).toDart)! as DomImageBitmap; + bitmapSource = _offscreenCanvas! as JSObject; } else { - bitmap = (await createImageBitmap(_canvasElement! as JSObject, ( - x: 0, - y: _pixelHeight - frameSize.height.toInt(), - width: frameSize.width.toInt(), - height: frameSize.height.toInt() - )).toDart)! as DomImageBitmap; + bitmapSource = _canvasElement! as JSObject; } + final DomImageBitmap bitmap = await createImageBitmap(bitmapSource, ( + x: 0, + y: _pixelHeight - frameSize.height.toInt(), + width: frameSize.width.toInt(), + height: frameSize.height.toInt(), + )); canvas.render(bitmap); } else { // If the browser doesn't support `createImageBitmap` (e.g. Safari 14) diff --git a/lib/web_ui/lib/src/engine/dom.dart b/lib/web_ui/lib/src/engine/dom.dart index 21b2a8fe2a467..c5fda3fd35d43 100644 --- a/lib/web_ui/lib/src/engine/dom.dart +++ b/lib/web_ui/lib/src/engine/dom.dart @@ -210,14 +210,16 @@ external JSPromise _createImageBitmap2( JSNumber width, JSNumber height, ); -JSPromise createImageBitmap(JSAny source, +Future createImageBitmap(JSAny source, [({int x, int y, int width, int height})? bounds]) { + JSPromise jsPromise; if (bounds != null) { - return _createImageBitmap2(source, bounds.x.toJS, bounds.y.toJS, + jsPromise = _createImageBitmap2(source, bounds.x.toJS, bounds.y.toJS, bounds.width.toJS, bounds.height.toJS); } else { - return _createImageBitmap1(source); + jsPromise = _createImageBitmap1(source); } + return js_util.promiseToFuture(jsPromise); } @JS() diff --git a/lib/web_ui/lib/src/engine/html/renderer.dart b/lib/web_ui/lib/src/engine/html/renderer.dart index 7e74faefc09a0..0cf11314f8d74 100644 --- a/lib/web_ui/lib/src/engine/html/renderer.dart +++ b/lib/web_ui/lib/src/engine/html/renderer.dart @@ -323,7 +323,7 @@ class HtmlRenderer implements Renderer { CanvasParagraphBuilder(style as EngineParagraphStyle); @override - void renderScene(ui.Scene scene, ui.FlutterView view) { + Future renderScene(ui.Scene scene, ui.FlutterView view) async { final EngineFlutterView implicitView = EnginePlatformDispatcher.instance.implicitView!; implicitView.dom.setScene((scene as SurfaceScene).webOnlyRootElement!); frameTimingsOnRasterFinish(); diff --git a/lib/web_ui/lib/src/engine/renderer.dart b/lib/web_ui/lib/src/engine/renderer.dart index 9f39fcff9ea94..dbd7e633d2722 100644 --- a/lib/web_ui/lib/src/engine/renderer.dart +++ b/lib/web_ui/lib/src/engine/renderer.dart @@ -222,5 +222,5 @@ abstract class Renderer { ui.ParagraphBuilder createParagraphBuilder(ui.ParagraphStyle style); - FutureOr renderScene(ui.Scene scene, ui.FlutterView view); + Future renderScene(ui.Scene scene, ui.FlutterView view); } diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_stub/renderer.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_stub/renderer.dart index fe63d34ad0462..59f52969e3b74 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_stub/renderer.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_stub/renderer.dart @@ -150,7 +150,7 @@ class SkwasmRenderer implements Renderer { } @override - void renderScene(ui.Scene scene, ui.FlutterView view) { + Future renderScene(ui.Scene scene, ui.FlutterView view) { throw UnimplementedError('Skwasm not implemented on this platform.'); } diff --git a/lib/web_ui/test/engine/semantics/semantics_test.dart b/lib/web_ui/test/engine/semantics/semantics_test.dart index 1dade2f885913..b3b210614f335 100644 --- a/lib/web_ui/test/engine/semantics/semantics_test.dart +++ b/lib/web_ui/test/engine/semantics/semantics_test.dart @@ -33,6 +33,7 @@ void main() { Future testMain() async { await bootstrapAndRunApp(); + setUpRenderingForTests(); runSemanticsTests(); } From aaaeba0bce0dacd12fa82e8c80dc33d622765c04 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 19 Dec 2023 12:05:49 -0800 Subject: [PATCH 4/8] Fix analysis errors --- lib/web_ui/test/canvaskit/multi_view_test.dart | 4 ++-- .../test/canvaskit/render_canvas_test.dart | 4 ++-- lib/web_ui/test/engine/scene_view_test.dart | 18 ++++++++++++------ lib/web_ui/test/ui/image_golden_test.dart | 2 +- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/web_ui/test/canvaskit/multi_view_test.dart b/lib/web_ui/test/canvaskit/multi_view_test.dart index a5a67f473f4aa..dac4055c67518 100644 --- a/lib/web_ui/test/canvaskit/multi_view_test.dart +++ b/lib/web_ui/test/canvaskit/multi_view_test.dart @@ -33,13 +33,13 @@ void testMain() { }); test('can render into arbitrary views', () async { - CanvasKitRenderer.instance.renderScene(scene, implicitView); + await CanvasKitRenderer.instance.renderScene(scene, implicitView); final EngineFlutterView anotherView = EngineFlutterView( EnginePlatformDispatcher.instance, createDomElement('another-view')); EnginePlatformDispatcher.instance.viewManager.registerView(anotherView); - CanvasKitRenderer.instance.renderScene(scene, anotherView); + await CanvasKitRenderer.instance.renderScene(scene, anotherView); }); test('will error if trying to render into an unregistered view', () async { diff --git a/lib/web_ui/test/canvaskit/render_canvas_test.dart b/lib/web_ui/test/canvaskit/render_canvas_test.dart index dade8c2161d4d..f5a174e7c9a69 100644 --- a/lib/web_ui/test/canvaskit/render_canvas_test.dart +++ b/lib/web_ui/test/canvaskit/render_canvas_test.dart @@ -21,13 +21,13 @@ void testMain() { }); Future newBitmap(int width, int height) async { - return (await createImageBitmap( + return createImageBitmap( createBlankDomImageData(width, height) as JSAny, ( x: 0, y: 0, width: width, height: height, - )).toDart)! as DomImageBitmap; + )); } // Regression test for https://github.com/flutter/flutter/issues/75286 diff --git a/lib/web_ui/test/engine/scene_view_test.dart b/lib/web_ui/test/engine/scene_view_test.dart index 2580be479554a..185c1771e53d5 100644 --- a/lib/web_ui/test/engine/scene_view_test.dart +++ b/lib/web_ui/test/engine/scene_view_test.dart @@ -26,10 +26,13 @@ class StubPictureRenderer implements PictureRenderer { Future renderPicture(ScenePicture picture) async { renderedPictures.add(picture); final ui.Rect cullRect = picture.cullRect; - final DomImageBitmap bitmap = (await createImageBitmap( - scratchCanvasElement as JSObject, - (x: 0, y: 0, width: cullRect.width.toInt(), height: cullRect.height.toInt()) - ).toDart)! as DomImageBitmap; + final DomImageBitmap bitmap = + await createImageBitmap(scratchCanvasElement as JSObject, ( + x: 0, + y: 0, + width: cullRect.width.toInt(), + height: cullRect.height.toInt(), + )); return bitmap; } @@ -83,7 +86,8 @@ void testMain() { debugOverrideDevicePixelRatio(null); }); - test('SceneView places platform view according to device-pixel ratio', () async { + test('SceneView places platform view according to device-pixel ratio', + () async { debugOverrideDevicePixelRatio(2.0); final PlatformView platformView = PlatformView( @@ -113,7 +117,9 @@ void testMain() { debugOverrideDevicePixelRatio(null); }); - test('SceneView always renders most recent picture and skips intermediate pictures', () async { + test( + 'SceneView always renders most recent picture and skips intermediate pictures', + () async { final List pictures = []; final List> renderFutures = >[]; for (int i = 1; i < 20; i++) { diff --git a/lib/web_ui/test/ui/image_golden_test.dart b/lib/web_ui/test/ui/image_golden_test.dart index 8dff0b43f6db9..60b9810d1df62 100644 --- a/lib/web_ui/test/ui/image_golden_test.dart +++ b/lib/web_ui/test/ui/image_golden_test.dart @@ -318,7 +318,7 @@ Future testMain() async { image.src = url; await completer.future; - final DomImageBitmap bitmap = (await createImageBitmap(image as JSObject).toDart)! as DomImageBitmap; + final DomImageBitmap bitmap = await createImageBitmap(image as JSObject); expect(bitmap.width.toDartInt, 150); expect(bitmap.height.toDartInt, 150); From 7426b3b39ec57ee688b1b298213645379ac6cbcc Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 19 Dec 2023 12:39:22 -0800 Subject: [PATCH 5/8] Await for bitmaps to be created --- lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart | 4 ++-- lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart index cecfa354219c1..343894b1d8477 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart @@ -361,7 +361,7 @@ class HtmlViewEmbedder { sceneHost.append(_svgPathDefs!); } - void submitFrame() { + Future submitFrame() async { final ViewListDiffResult? diffResult = (_activeCompositionOrder.isEmpty || _compositionOrder.isEmpty) ? null @@ -385,7 +385,7 @@ class HtmlViewEmbedder { _context.pictureRecorders[pictureRecorderIndex].endRecording()); pictureRecorderIndex++; } - rasterizer.rasterizeToCanvas(overlay, pictures); + await rasterizer.rasterizeToCanvas(overlay, pictures); } for (final CkPictureRecorder recorder in _context.pictureRecordersCreatedDuringPreroll) { diff --git a/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart b/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart index 9d00cde516fbf..4e987d3a517f8 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart @@ -52,7 +52,7 @@ class Rasterizer { await rasterizeToCanvas(renderCanvasFactory.baseCanvas, [pictureRecorder.endRecording()]); - viewEmbedder.submitFrame(); + await viewEmbedder.submitFrame(); } /// Disposes of this rasterizer. From 25373201c92cff22d5dec1deb5d42d85c87f4fc9 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 19 Dec 2023 14:03:21 -0800 Subject: [PATCH 6/8] dry --- lib/web_ui/lib/src/engine/platform_dispatcher.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 905bca78fdc6c..f6bb44758d34b 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -776,7 +776,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { final bool shouldRender = _viewsRenderedInCurrentFrame?.add(viewToRender) ?? false; if (shouldRender) { - await renderer.renderScene(scene, view ?? implicitView!); + await renderer.renderScene(scene, viewToRender); } } From 88b41f5c8f02edbaf83cf02c84d20c75a41039e4 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Thu, 21 Dec 2023 11:42:41 -0800 Subject: [PATCH 7/8] Don't enforce the render rule on HTML renderer --- lib/web_ui/lib/src/engine/platform_dispatcher.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 7ca80db0fc439..04d9f98b8d3ae 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -775,7 +775,10 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { // view hasn't been rendered already in this scope. final bool shouldRender = _viewsRenderedInCurrentFrame?.add(viewToRender) ?? false; - if (shouldRender) { + // TODO(harryterkelsen): HTML renderer needs to violate the render rule in + // order to perform golden tests in Flutter framework because on the HTML + // renderer, golden tests render to DOM and then take a browser screenshot. + if (shouldRender || renderer.rendererTag == 'html') { await renderer.renderScene(scene, viewToRender); } } From 727312d0ccf0407aa622fd3b9a8c62686f854820 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Thu, 21 Dec 2023 11:50:18 -0800 Subject: [PATCH 8/8] Add issue to TODO --- lib/web_ui/lib/src/engine/platform_dispatcher.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 04d9f98b8d3ae..1453da94e0c3d 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -777,7 +777,8 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { _viewsRenderedInCurrentFrame?.add(viewToRender) ?? false; // TODO(harryterkelsen): HTML renderer needs to violate the render rule in // order to perform golden tests in Flutter framework because on the HTML - // renderer, golden tests render to DOM and then take a browser screenshot. + // renderer, golden tests render to DOM and then take a browser screenshot, + // https://github.com/flutter/flutter/issues/137073. if (shouldRender || renderer.rendererTag == 'html') { await renderer.renderScene(scene, viewToRender); }