| // Copyright 2014 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 'dart:async'; |
| import 'dart:typed_data'; |
| import 'dart:ui' as ui; |
| |
| import 'package:flutter_driver/driver_extension.dart'; |
| |
| import 'windows.dart'; |
| |
| void drawHelloWorld(ui.FlutterView view) { |
| final style = ui.ParagraphStyle(); |
| final paragraphBuilder = ui.ParagraphBuilder(style)..addText('Hello world'); |
| final ui.Paragraph paragraph = paragraphBuilder.build(); |
| |
| paragraph.layout(const ui.ParagraphConstraints(width: 100.0)); |
| |
| final recorder = ui.PictureRecorder(); |
| final canvas = ui.Canvas(recorder); |
| |
| canvas.drawParagraph(paragraph, ui.Offset.zero); |
| |
| final ui.Picture picture = recorder.endRecording(); |
| final sceneBuilder = ui.SceneBuilder() |
| ..addPicture(ui.Offset.zero, picture) |
| ..pop(); |
| |
| view.render(sceneBuilder.build()); |
| } |
| |
| Future<void> _waitUntilWindowVisible() async { |
| while (!await isWindowVisible()) { |
| await Future<void>.delayed(const Duration(milliseconds: 100)); |
| } |
| } |
| |
| void _expectVisible(bool current, bool expect, Completer<String> 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); |
| final ui.FlutterView view = ui.PlatformDispatcher.instance.implicitView!; |
| |
| // Create a completer to send the window visibility result back to the |
| // integration test. |
| final visibilityCompleter = Completer<String>(); |
| enableFlutterDriverExtension( |
| handler: (String? message) async { |
| if (message == 'verifyWindowVisibility') { |
| return visibilityCompleter.future; |
| } else if (message == 'verifyTheme') { |
| final bool app = await isAppDarkModeEnabled(); |
| final bool system = await isSystemDarkModeEnabled(); |
| |
| return (app == system) |
| ? 'success' |
| : 'error: app dark mode ($app) does not match system dark mode ($system)'; |
| } else if (message == 'verifyStringConversion') { |
| // Use a test string that contains code points that fit in both 8 and 16 bits. |
| // The code points are passed a list of integers through the method channel, |
| // which will use the UTF16 to UTF8 utility function to convert them to a |
| // std::string, which should equate to the original expected string. |
| const expected = 'ABCℵ'; |
| final codePoints = Int32List.fromList(expected.codeUnits); |
| final String converted = await testStringConversion(codePoints); |
| return (converted == expected) |
| ? 'success' |
| : 'error: conversion of UTF16 string to UTF8 failed, expected "${expected.codeUnits}" but got "${converted.codeUnits}"'; |
| } else if (message == 'verifyNullStringConversion') { |
| // Test that Utf8FromUtf16 handles nullptr gracefully by returning empty string. |
| final String converted = await testNullStringConversion(); |
| return converted.isEmpty |
| ? 'success' |
| : 'error: nullptr conversion should return empty string, got "${converted.codeUnits}"'; |
| } else if (message == 'verifyEmptyStringConversion') { |
| // Test that Utf8FromUtf16 handles empty string gracefully. |
| final String converted = await testEmptyStringConversion(); |
| return converted.isEmpty |
| ? 'success' |
| : 'error: empty string conversion should return empty string, got "${converted.codeUnits}"'; |
| } else if (message == 'verifyInvalidUtf16Conversion') { |
| // Test that Utf8FromUtf16 handles invalid UTF-16 (unpaired surrogate) gracefully. |
| // With WC_ERR_INVALID_CHARS flag, WideCharToMultiByte returns 0 for invalid input, |
| // so Utf8FromUtf16 should return an empty string. |
| final String converted = await testInvalidUtf16Conversion(); |
| return converted.isEmpty |
| ? 'success' |
| : 'error: invalid UTF-16 conversion should return empty string, got "${converted.codeUnits}"'; |
| } |
| |
| throw 'Unrecognized message: $message'; |
| }, |
| ); |
| |
| try { |
| if (await isWindowVisible()) { |
| throw 'Window should be hidden at startup'; |
| } |
| |
| var 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); |
| } |
| }; |
| } catch (e) { |
| visibilityCompleter.completeError(e); |
| rethrow; |
| } |
| ui.PlatformDispatcher.instance.scheduleFrame(); |
| } |