| // 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. |
| |
| // @dart = 2.12 |
| |
| // HACK: pretend to be dart.ui in order to access its internals |
| library dart.ui; |
| |
| import 'dart:async'; |
| // this needs to be imported because painting.dart expects it this way |
| import 'dart:collection' as collection; |
| import 'dart:convert'; |
| import 'dart:developer' as developer; |
| import 'dart:math' as math; |
| import 'dart:nativewrappers'; // ignore: unused_import |
| import 'dart:typed_data'; |
| |
| |
| // HACK: these parts are to get access to private functions tested here. |
| part '../../lib/ui/annotations.dart'; |
| part '../../lib/ui/channel_buffers.dart'; |
| part '../../lib/ui/compositing.dart'; |
| part '../../lib/ui/geometry.dart'; |
| part '../../lib/ui/hash_codes.dart'; |
| part '../../lib/ui/hooks.dart'; |
| part '../../lib/ui/lerp.dart'; |
| part '../../lib/ui/natives.dart'; |
| part '../../lib/ui/painting.dart'; |
| part '../../lib/ui/platform_dispatcher.dart'; |
| part '../../lib/ui/pointer.dart'; |
| part '../../lib/ui/semantics.dart'; |
| part '../../lib/ui/text.dart'; |
| part '../../lib/ui/window.dart'; |
| |
| void main() { |
| VoidCallback? originalOnMetricsChanged; |
| VoidCallback? originalOnLocaleChanged; |
| FrameCallback? originalOnBeginFrame; |
| VoidCallback? originalOnDrawFrame; |
| TimingsCallback? originalOnReportTimings; |
| PointerDataPacketCallback? originalOnPointerDataPacket; |
| VoidCallback? originalOnSemanticsEnabledChanged; |
| SemanticsActionCallback? originalOnSemanticsAction; |
| PlatformMessageCallback? originalOnPlatformMessage; |
| VoidCallback? originalOnTextScaleFactorChanged; |
| |
| Object? oldWindowId; |
| double? oldDevicePixelRatio; |
| Rect? oldGeometry; |
| |
| WindowPadding? oldPadding; |
| WindowPadding? oldInsets; |
| WindowPadding? oldSystemGestureInsets; |
| |
| void setUp() { |
| PlatformDispatcher.instance._viewConfigurations.clear(); |
| PlatformDispatcher.instance._views.clear(); |
| PlatformDispatcher.instance._viewConfigurations[0] = const ViewConfiguration(); |
| PlatformDispatcher.instance._views[0] = FlutterWindow._(0, PlatformDispatcher.instance); |
| oldWindowId = window._windowId; |
| oldDevicePixelRatio = window.devicePixelRatio; |
| oldGeometry = window.viewConfiguration.geometry; |
| oldPadding = window.viewPadding; |
| oldInsets = window.viewInsets; |
| oldSystemGestureInsets = window.systemGestureInsets; |
| |
| originalOnMetricsChanged = window.onMetricsChanged; |
| originalOnLocaleChanged = window.onLocaleChanged; |
| originalOnBeginFrame = window.onBeginFrame; |
| originalOnDrawFrame = window.onDrawFrame; |
| originalOnReportTimings = window.onReportTimings; |
| originalOnPointerDataPacket = window.onPointerDataPacket; |
| originalOnSemanticsEnabledChanged = window.onSemanticsEnabledChanged; |
| originalOnSemanticsAction = window.onSemanticsAction; |
| originalOnPlatformMessage = window.onPlatformMessage; |
| originalOnTextScaleFactorChanged = window.onTextScaleFactorChanged; |
| } |
| |
| tearDown() { |
| _updateWindowMetrics( |
| oldWindowId!, // window id |
| oldDevicePixelRatio!, // device pixel ratio |
| oldGeometry!.width, // width |
| oldGeometry!.height, // height |
| oldPadding!.top, // padding top |
| oldPadding!.right, // padding right |
| oldPadding!.bottom, // padding bottom |
| oldPadding!.left, // padding left |
| oldInsets!.top, // inset top |
| oldInsets!.right, // inset right |
| oldInsets!.bottom, // inset bottom |
| oldInsets!.left, // inset left |
| oldSystemGestureInsets!.top, // system gesture inset top |
| oldSystemGestureInsets!.right, // system gesture inset right |
| oldSystemGestureInsets!.bottom, // system gesture inset bottom |
| oldSystemGestureInsets!.left, // system gesture inset left |
| ); |
| window.onMetricsChanged = originalOnMetricsChanged; |
| window.onLocaleChanged = originalOnLocaleChanged; |
| window.onBeginFrame = originalOnBeginFrame; |
| window.onDrawFrame = originalOnDrawFrame; |
| window.onReportTimings = originalOnReportTimings; |
| window.onPointerDataPacket = originalOnPointerDataPacket; |
| window.onSemanticsEnabledChanged = originalOnSemanticsEnabledChanged; |
| window.onSemanticsAction = originalOnSemanticsAction; |
| window.onPlatformMessage = originalOnPlatformMessage; |
| window.onTextScaleFactorChanged = originalOnTextScaleFactorChanged; |
| } |
| |
| void test(String description, void Function() testFunction) { |
| print(description); |
| setUp(); |
| testFunction(); |
| tearDown(); |
| } |
| |
| void expectEquals(dynamic actual, dynamic expected) { |
| if (actual != expected) { |
| throw Exception('Equality check failed:\n Expected: $expected\n Actual: $actual'); |
| } |
| } |
| |
| void expectIterablesEqual(Iterable<dynamic> actual, Iterable<dynamic> expected) { |
| expectEquals(actual.length, expected.length); |
| final Iterator<dynamic> actualIter = actual.iterator; |
| final Iterator<dynamic> expectedIter = expected.iterator; |
| while (expectedIter.moveNext()) { |
| expectEquals(actualIter.moveNext(), true); |
| expectEquals(actualIter.current, expectedIter.current); |
| } |
| expectEquals(actualIter.moveNext(), false); |
| } |
| |
| void expectNotEquals(dynamic actual, dynamic expected) { |
| if (actual == expected) { |
| throw Exception('Inequality check failed:\n Expected: $expected\n Actual: $actual'); |
| } |
| } |
| |
| void expectIdentical(dynamic actual, dynamic expected) { |
| if (!identical(actual, expected)) { |
| throw Exception('Identity check failed:\n Expected: $expected\n Actual: $actual'); |
| } |
| } |
| |
| test('updateUserSettings can handle an empty object', () { |
| // this should not throw. |
| _updateUserSettingsData('{}'); |
| }); |
| |
| test('onMetricsChanged preserves callback zone', () { |
| late Zone innerZone; |
| late Zone runZone; |
| late double devicePixelRatio; |
| |
| runZoned(() { |
| innerZone = Zone.current; |
| window.onMetricsChanged = () { |
| runZone = Zone.current; |
| devicePixelRatio = window.devicePixelRatio; |
| }; |
| }); |
| |
| window.onMetricsChanged!(); |
| _updateWindowMetrics( |
| 0, // window id |
| 0.1234, // device pixel ratio |
| 0.0, // width |
| 0.0, // height |
| 0.0, // padding top |
| 0.0, // padding right |
| 0.0, // padding bottom |
| 0.0, // padding left |
| 0.0, // inset top |
| 0.0, // inset right |
| 0.0, // inset bottom |
| 0.0, // inset left |
| 0.0, // system gesture inset top |
| 0.0, // system gesture inset right |
| 0.0, // system gesture inset bottom |
| 0.0, // system gesture inset left |
| ); |
| expectNotEquals(runZone, null); |
| expectIdentical(runZone, innerZone); |
| expectEquals(devicePixelRatio, 0.1234); |
| }); |
| |
| test('onLocaleChanged preserves callback zone', () { |
| late Zone innerZone; |
| late Zone runZone; |
| Locale? locale; |
| |
| runZoned(() { |
| innerZone = Zone.current; |
| window.onLocaleChanged = () { |
| runZone = Zone.current; |
| locale = window.locale; |
| }; |
| }); |
| |
| _updateLocales(<String>['en', 'US', '', '']); |
| expectNotEquals(runZone, null); |
| expectIdentical(runZone, innerZone); |
| expectEquals(locale, const Locale('en', 'US')); |
| }); |
| |
| test('onBeginFrame preserves callback zone', () { |
| late Zone innerZone; |
| late Zone runZone; |
| late Duration start; |
| |
| runZoned(() { |
| innerZone = Zone.current; |
| window.onBeginFrame = (Duration value) { |
| runZone = Zone.current; |
| start = value; |
| }; |
| }); |
| |
| _beginFrame(1234); |
| expectNotEquals(runZone, null); |
| expectIdentical(runZone, innerZone); |
| expectEquals(start, const Duration(microseconds: 1234)); |
| }); |
| |
| test('onDrawFrame preserves callback zone', () { |
| late Zone innerZone; |
| late Zone runZone; |
| |
| runZoned(() { |
| innerZone = Zone.current; |
| window.onDrawFrame = () { |
| runZone = Zone.current; |
| }; |
| }); |
| |
| _drawFrame(); |
| expectNotEquals(runZone, null); |
| expectIdentical(runZone, innerZone); |
| }); |
| |
| test('onReportTimings preserves callback zone', () { |
| late Zone innerZone; |
| late Zone runZone; |
| |
| PlatformDispatcher.instance._setNeedsReportTimings = (bool _) {}; |
| |
| runZoned(() { |
| innerZone = Zone.current; |
| window.onReportTimings = (List<FrameTiming> timings) { |
| runZone = Zone.current; |
| }; |
| }); |
| |
| _reportTimings(<int>[]); |
| expectNotEquals(runZone, null); |
| expectIdentical(runZone, innerZone); |
| }); |
| |
| test('onPointerDataPacket preserves callback zone', () { |
| late Zone innerZone; |
| late Zone runZone; |
| late PointerDataPacket data; |
| |
| runZoned(() { |
| innerZone = Zone.current; |
| window.onPointerDataPacket = (PointerDataPacket value) { |
| runZone = Zone.current; |
| data = value; |
| }; |
| }); |
| |
| final ByteData testData = ByteData.view(Uint8List(0).buffer); |
| _dispatchPointerDataPacket(testData); |
| expectNotEquals(runZone, null); |
| expectIdentical(runZone, innerZone); |
| expectIterablesEqual(data.data, PlatformDispatcher._unpackPointerDataPacket(testData).data); |
| }); |
| |
| test('onSemanticsEnabledChanged preserves callback zone', () { |
| late Zone innerZone; |
| late Zone runZone; |
| late bool enabled; |
| |
| runZoned(() { |
| innerZone = Zone.current; |
| window.onSemanticsEnabledChanged = () { |
| runZone = Zone.current; |
| enabled = window.semanticsEnabled; |
| }; |
| }); |
| |
| final bool newValue = !window.semanticsEnabled; // needed? |
| _updateSemanticsEnabled(newValue); |
| expectNotEquals(runZone, null); |
| expectIdentical(runZone, innerZone); |
| expectNotEquals(enabled, null); |
| expectEquals(enabled, newValue); |
| }); |
| |
| test('onSemanticsAction preserves callback zone', () { |
| late Zone innerZone; |
| late Zone runZone; |
| late int id; |
| late int action; |
| |
| runZoned(() { |
| innerZone = Zone.current; |
| window.onSemanticsAction = (int i, SemanticsAction a, ByteData? _) { |
| runZone = Zone.current; |
| action = a.index; |
| id = i; |
| }; |
| }); |
| |
| _dispatchSemanticsAction(1234, 4, null); |
| expectNotEquals(runZone, null); |
| expectIdentical(runZone, innerZone); |
| expectEquals(id, 1234); |
| expectEquals(action, 4); |
| }); |
| |
| test('onPlatformMessage preserves callback zone', () { |
| late Zone innerZone; |
| late Zone runZone; |
| late String name; |
| |
| runZoned(() { |
| innerZone = Zone.current; |
| window.onPlatformMessage = (String value, _, __) { |
| runZone = Zone.current; |
| name = value; |
| }; |
| }); |
| |
| _dispatchPlatformMessage('testName', null, 123456789); |
| expectNotEquals(runZone, null); |
| expectIdentical(runZone, innerZone); |
| expectEquals(name, 'testName'); |
| }); |
| |
| test('onTextScaleFactorChanged preserves callback zone', () { |
| late Zone innerZone; |
| late Zone runZoneTextScaleFactor; |
| late Zone runZonePlatformBrightness; |
| late double? textScaleFactor; |
| late Brightness? platformBrightness; |
| |
| runZoned(() { |
| innerZone = Zone.current; |
| window.onTextScaleFactorChanged = () { |
| runZoneTextScaleFactor = Zone.current; |
| textScaleFactor = window.textScaleFactor; |
| }; |
| window.onPlatformBrightnessChanged = () { |
| runZonePlatformBrightness = Zone.current; |
| platformBrightness = window.platformBrightness; |
| }; |
| }); |
| |
| window.onTextScaleFactorChanged!(); |
| |
| _updateUserSettingsData('{"textScaleFactor": 0.5, "platformBrightness": "light", "alwaysUse24HourFormat": true}'); |
| expectNotEquals(runZoneTextScaleFactor, null); |
| expectIdentical(runZoneTextScaleFactor, innerZone); |
| expectEquals(textScaleFactor, 0.5); |
| |
| textScaleFactor = null; |
| platformBrightness = null; |
| |
| window.onPlatformBrightnessChanged!(); |
| _updateUserSettingsData('{"textScaleFactor": 0.5, "platformBrightness": "dark", "alwaysUse24HourFormat": true}'); |
| expectNotEquals(runZonePlatformBrightness, null); |
| expectIdentical(runZonePlatformBrightness, innerZone); |
| expectEquals(platformBrightness, Brightness.dark); |
| |
| }); |
| |
| test('Window padding/insets/viewPadding/systemGestureInsets', () { |
| _updateWindowMetrics( |
| 0, // window id |
| 0, // screen id |
| 800.0, // width |
| 600.0, // height |
| 50.0, // padding top |
| 0.0, // padding right |
| 40.0, // padding bottom |
| 0.0, // padding left |
| 0.0, // inset top |
| 0.0, // inset right |
| 0.0, // inset bottom |
| 0.0, // inset left |
| 0.0, // system gesture inset top |
| 0.0, // system gesture inset right |
| 0.0, // system gesture inset bottom |
| 0.0, // system gesture inset left |
| ); |
| |
| expectEquals(window.viewInsets.bottom, 0.0); |
| expectEquals(window.viewPadding.bottom, 40.0); |
| expectEquals(window.padding.bottom, 40.0); |
| expectEquals(window.systemGestureInsets.bottom, 0.0); |
| |
| _updateWindowMetrics( |
| 0, // window id |
| 0, // screen id |
| 800.0, // width |
| 600.0, // height |
| 50.0, // padding top |
| 0.0, // padding right |
| 40.0, // padding bottom |
| 0.0, // padding left |
| 0.0, // inset top |
| 0.0, // inset right |
| 400.0, // inset bottom |
| 0.0, // inset left |
| 0.0, // system gesture inset top |
| 0.0, // system gesture inset right |
| 44.0, // system gesture inset bottom |
| 0.0, // system gesture inset left |
| ); |
| |
| expectEquals(window.viewInsets.bottom, 400.0); |
| expectEquals(window.viewPadding.bottom, 40.0); |
| expectEquals(window.padding.bottom, 0.0); |
| expectEquals(window.systemGestureInsets.bottom, 44.0); |
| }); |
| |
| test('PlatformDispatcher.locale returns unknown locale when locales is set to empty list', () { |
| late Locale locale; |
| runZoned(() { |
| window.onLocaleChanged = () { |
| locale = PlatformDispatcher.instance.locale; |
| }; |
| }); |
| |
| _updateLocales(<String>[]); |
| expectEquals(locale, const Locale.fromSubtags()); |
| expectEquals(locale.languageCode, 'und'); |
| }); |
| } |