From 203ec67c6596d28081577a92eee0a5160f60ba7c Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 21 Sep 2023 10:43:07 -0700 Subject: [PATCH] Fix violation of the render rule in Windows Startup Test (#134245) This PR fixes yet another case in the windows startup test that violates the render rule, which caused https://github.com/flutter/engine/pull/45300 to be reverted. Although the `FlutterView.render` call is within `onBeginFrame`, there is an `await` before the call, causing the call to fall out of the synchronous scope. I've added this problem to the documentation of `FlutterView.render` in https://github.com/flutter/engine/pull/45555. --- .../windows_startup_test/lib/main.dart | 65 ++++++++++++++----- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/dev/integration_tests/windows_startup_test/lib/main.dart b/dev/integration_tests/windows_startup_test/lib/main.dart index 6917653db72e..f79473238a15 100644 --- a/dev/integration_tests/windows_startup_test/lib/main.dart +++ b/dev/integration_tests/windows_startup_test/lib/main.dart @@ -31,6 +31,25 @@ void drawHelloWorld(ui.FlutterView view) { view.render(sceneBuilder.build()); } +Future _waitUntilWindowVisible() async { + while (!await isWindowVisible()) { + await Future.delayed(const Duration(milliseconds: 100)); + } +} + +void _expectVisible(bool current, bool expect, Completer completer, int frameCount) { + if (current != expect) { + try { + throw 'Window should be ${expect ? 'visible' : 'hidden'} on frame $frameCount'; + } catch (e) { + if (!completer.isCompleted) { + completer.completeError(e); + } + rethrow; + } + } +} + void main() async { // TODO(goderbauer): Create a window if embedder doesn't provide an implicit view to draw into. assert(ui.PlatformDispatcher.instance.implicitView != null); @@ -70,27 +89,39 @@ void main() async { throw 'Window should be hidden at startup'; } - bool firstFrame = true; - ui.PlatformDispatcher.instance.onBeginFrame = (Duration duration) async { - if (await isWindowVisible()) { - if (firstFrame) { - throw 'Window should be hidden on first frame'; - } - - if (!visibilityCompleter.isCompleted) { - visibilityCompleter.complete('success'); - } + int frameCount = 0; + ui.PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + // Our goal is to verify that it's `drawHelloWorld` that makes the window + // appear, not anything else. This requires checking the visibility right + // before drawing, but since `isWindowVisible` has to be async, and + // `FlutterView.render` (in `drawHelloWorld`) forbids async before it, + // this can not be done during a single onBeginFrame. However, we can + // verify in separate frames to indirectly prove it, by ensuring that + // no other mechanism can affect isWindowVisible in the first frame at all. + frameCount += 1; + switch (frameCount) { + // The 1st frame: render nothing, just verify that the window is hidden. + case 1: + isWindowVisible().then((bool visible) { + _expectVisible(visible, false, visibilityCompleter, frameCount); + ui.PlatformDispatcher.instance.scheduleFrame(); + }); + // The 2nd frame: render, which makes the window appear. + case 2: + drawHelloWorld(view); + _waitUntilWindowVisible().then((_) { + if (!visibilityCompleter.isCompleted) { + visibilityCompleter.complete('success'); + } + }); + // Others, in case requested to render. + default: + drawHelloWorld(view); } - - // Draw something to trigger the first frame callback that displays the - // window. - drawHelloWorld(view); - firstFrame = false; }; - - ui.PlatformDispatcher.instance.scheduleFrame(); } catch (e) { visibilityCompleter.completeError(e); rethrow; } + ui.PlatformDispatcher.instance.scheduleFrame(); }