| // 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 'dart:async'; | |
| import 'dart:typed_data'; | |
| import 'dart:ui'; | |
| import 'dart:isolate'; | |
| import 'dart:ffi'; | |
| void main() {} | |
| /// Mutiple tests use this to signal to the C++ side that they are ready for | |
| /// validation. | |
| @pragma('vm:external-name', 'Finish') | |
| external void _finish(); | |
| @pragma('vm:entry-point') | |
| void customOnErrorTrue() { | |
| PlatformDispatcher.instance.onError = (Object error, StackTrace? stack) { | |
| _finish(); | |
| return true; | |
| }; | |
| throw Exception('true'); | |
| } | |
| @pragma('vm:entry-point') | |
| void customOnErrorFalse() { | |
| PlatformDispatcher.instance.onError = (Object error, StackTrace? stack) { | |
| _finish(); | |
| return false; | |
| }; | |
| throw Exception('false'); | |
| } | |
| @pragma('vm:entry-point') | |
| void customOnErrorThrow() { | |
| PlatformDispatcher.instance.onError = (Object error, StackTrace? stack) { | |
| _finish(); | |
| throw Exception('throw2'); | |
| }; | |
| throw Exception('throw1'); | |
| } | |
| @pragma('vm:entry-point') | |
| void setLatencyPerformanceMode() { | |
| PlatformDispatcher.instance.requestDartPerformanceMode(DartPerformanceMode.latency); | |
| _finish(); | |
| } | |
| @pragma('vm:entry-point') | |
| void validateSceneBuilderAndScene() { | |
| final SceneBuilder builder = SceneBuilder(); | |
| builder.pushOffset(10, 10); | |
| _validateBuilderHasLayers(builder); | |
| final Scene scene = builder.build(); | |
| _validateBuilderHasNoLayers(); | |
| _captureScene(scene); | |
| scene.dispose(); | |
| _validateSceneHasNoLayers(); | |
| } | |
| @pragma('vm:external-name', 'ValidateBuilderHasLayers') | |
| external _validateBuilderHasLayers(SceneBuilder builder); | |
| @pragma('vm:external-name', 'ValidateBuilderHasNoLayers') | |
| external _validateBuilderHasNoLayers(); | |
| @pragma('vm:external-name', 'CaptureScene') | |
| external _captureScene(Scene scene); | |
| @pragma('vm:external-name', 'ValidateSceneHasNoLayers') | |
| external _validateSceneHasNoLayers(); | |
| @pragma('vm:entry-point') | |
| void validateEngineLayerDispose() { | |
| final SceneBuilder builder = SceneBuilder(); | |
| final EngineLayer layer = builder.pushOffset(10, 10); | |
| _captureRootLayer(builder); | |
| final Scene scene = builder.build(); | |
| scene.dispose(); | |
| _validateLayerTreeCounts(); | |
| layer.dispose(); | |
| _validateEngineLayerDispose(); | |
| } | |
| @pragma('vm:external-name', 'CaptureRootLayer') | |
| external _captureRootLayer(SceneBuilder sceneBuilder); | |
| @pragma('vm:external-name', 'ValidateLayerTreeCounts') | |
| external _validateLayerTreeCounts(); | |
| @pragma('vm:external-name', 'ValidateEngineLayerDispose') | |
| external _validateEngineLayerDispose(); | |
| @pragma('vm:entry-point') | |
| Future<void> createSingleFrameCodec() async { | |
| final ImmutableBuffer buffer = await ImmutableBuffer.fromUint8List(Uint8List.fromList(List<int>.filled(4, 100))); | |
| final ImageDescriptor descriptor = ImageDescriptor.raw( | |
| buffer, | |
| width: 1, | |
| height: 1, | |
| pixelFormat: PixelFormat.rgba8888, | |
| ); | |
| final Codec codec = await descriptor.instantiateCodec(); | |
| _validateCodec(codec); | |
| final FrameInfo info = await codec.getNextFrame(); | |
| info.image.dispose(); | |
| _validateCodec(codec); | |
| codec.dispose(); | |
| descriptor.dispose(); | |
| buffer.dispose(); | |
| assert(buffer.debugDisposed); | |
| _finish(); | |
| } | |
| @pragma('vm:external-name', 'ValidateCodec') | |
| external void _validateCodec(Codec codec); | |
| @pragma('vm:entry-point') | |
| void createVertices() { | |
| const int uint16max = 65535; | |
| final Int32List colors = Int32List(uint16max); | |
| final Float32List coords = Float32List(uint16max * 2); | |
| final Uint16List indices = Uint16List(uint16max); | |
| final Float32List positions = Float32List(uint16max * 2); | |
| colors[0] = const Color(0xFFFF0000).value; | |
| colors[1] = const Color(0xFF00FF00).value; | |
| colors[2] = const Color(0xFF0000FF).value; | |
| colors[3] = const Color(0xFF00FFFF).value; | |
| indices[1] = indices[3] = 1; | |
| indices[2] = indices[5] = 3; | |
| indices[4] = 2; | |
| positions[2] = positions[4] = positions[5] = positions[7] = 250.0; | |
| final Vertices vertices = Vertices.raw( | |
| VertexMode.triangles, | |
| positions, | |
| textureCoordinates: coords, | |
| colors: colors, | |
| indices: indices, | |
| ); | |
| _validateVertices(vertices); | |
| } | |
| @pragma('vm:external-name', 'ValidateVertices') | |
| external void _validateVertices(Vertices vertices); | |
| @pragma('vm:entry-point') | |
| void sendSemanticsUpdate() { | |
| final SemanticsUpdateBuilder builder = SemanticsUpdateBuilder(); | |
| final String label = "label"; | |
| final List<StringAttribute> labelAttributes = <StringAttribute> [ | |
| SpellOutStringAttribute(range: TextRange(start: 1, end: 2)), | |
| ]; | |
| final String value = "value"; | |
| final List<StringAttribute> valueAttributes = <StringAttribute> [ | |
| SpellOutStringAttribute(range: TextRange(start: 2, end: 3)), | |
| ]; | |
| final String increasedValue = "increasedValue"; | |
| final List<StringAttribute> increasedValueAttributes = <StringAttribute> [ | |
| SpellOutStringAttribute(range: TextRange(start: 4, end: 5)), | |
| ]; | |
| final String decreasedValue = "decreasedValue"; | |
| final List<StringAttribute> decreasedValueAttributes = <StringAttribute> [ | |
| SpellOutStringAttribute(range: TextRange(start: 5, end: 6)), | |
| ]; | |
| final String hint = "hint"; | |
| final List<StringAttribute> hintAttributes = <StringAttribute> [ | |
| LocaleStringAttribute( | |
| locale: Locale('en', 'MX'), range: TextRange(start: 0, end: 1), | |
| ), | |
| ]; | |
| final Float64List transform = Float64List(16); | |
| final Int32List childrenInTraversalOrder = Int32List(0); | |
| final Int32List childrenInHitTestOrder = Int32List(0); | |
| final Int32List additionalActions = Int32List(0); | |
| transform[0] = 1; | |
| transform[1] = 0; | |
| transform[2] = 0; | |
| transform[3] = 0; | |
| transform[4] = 0; | |
| transform[5] = 1; | |
| transform[6] = 0; | |
| transform[7] = 0; | |
| transform[8] = 0; | |
| transform[9] = 0; | |
| transform[10] = 1; | |
| transform[11] = 0; | |
| transform[12] = 0; | |
| transform[13] = 0; | |
| transform[14] = 0; | |
| transform[15] = 0; | |
| builder.updateNode( | |
| id: 0, | |
| flags: 0, | |
| actions: 0, | |
| maxValueLength: 0, | |
| currentValueLength: 0, | |
| textSelectionBase: -1, | |
| textSelectionExtent: -1, | |
| platformViewId: -1, | |
| scrollChildren: 0, | |
| scrollIndex: 0, | |
| scrollPosition: 0, | |
| scrollExtentMax: 0, | |
| scrollExtentMin: 0, | |
| rect: Rect.fromLTRB(0, 0, 10, 10), | |
| elevation: 0, | |
| thickness: 0, | |
| label: label, | |
| labelAttributes: labelAttributes, | |
| value: value, | |
| valueAttributes: valueAttributes, | |
| increasedValue: increasedValue, | |
| increasedValueAttributes: increasedValueAttributes, | |
| decreasedValue: decreasedValue, | |
| decreasedValueAttributes: decreasedValueAttributes, | |
| hint: hint, | |
| hintAttributes: hintAttributes, | |
| textDirection: TextDirection.ltr, | |
| transform: transform, | |
| childrenInTraversalOrder: childrenInTraversalOrder, | |
| childrenInHitTestOrder: childrenInHitTestOrder, | |
| additionalActions: additionalActions); | |
| _semanticsUpdate(builder.build()); | |
| } | |
| @pragma('vm:external-name', 'SemanticsUpdate') | |
| external void _semanticsUpdate(SemanticsUpdate update); | |
| @pragma('vm:entry-point') | |
| void createPath() { | |
| final Path path = Path()..lineTo(10, 10); | |
| _validatePath(path); | |
| // Arbitrarily hold a reference to the path to make sure it does not get | |
| // garbage collected. | |
| Future<void>.delayed(const Duration(days: 100)).then((_) { | |
| path.lineTo(100, 100); | |
| }); | |
| } | |
| @pragma('vm:external-name', 'ValidatePath') | |
| external void _validatePath(Path path); | |
| @pragma('vm:entry-point') | |
| void frameCallback(_Image, int) { | |
| print('called back'); | |
| } | |
| @pragma('vm:entry-point') | |
| void platformMessagePortResponseTest() async { | |
| ReceivePort receivePort = ReceivePort(); | |
| _callPlatformMessageResponseDartPort(receivePort.sendPort.nativePort); | |
| List<dynamic> resultList = await receivePort.first; | |
| int identifier = resultList[0] as int; | |
| Uint8List? bytes = resultList[1] as Uint8List?; | |
| ByteData result = ByteData.sublistView(bytes!); | |
| if (result.lengthInBytes == 100) { | |
| _finishCallResponse(true); | |
| } else { | |
| _finishCallResponse(false); | |
| } | |
| } | |
| @pragma('vm:entry-point') | |
| void platformMessageResponseTest() { | |
| _callPlatformMessageResponseDart((ByteData? result) { | |
| if (result is UnmodifiableByteDataView && | |
| result.lengthInBytes == 100) { | |
| _finishCallResponse(true); | |
| } else { | |
| _finishCallResponse(false); | |
| } | |
| }); | |
| } | |
| @pragma('vm:external-name', 'CallPlatformMessageResponseDartPort') | |
| external void _callPlatformMessageResponseDartPort(int port); | |
| @pragma('vm:external-name', 'CallPlatformMessageResponseDart') | |
| external void _callPlatformMessageResponseDart(void Function(ByteData? result) callback); | |
| @pragma('vm:external-name', 'FinishCallResponse') | |
| external void _finishCallResponse(bool didPass); | |
| @pragma('vm:entry-point') | |
| void messageCallback(dynamic data) {} | |
| @pragma('vm:entry-point') | |
| @pragma('vm:external-name', 'ValidateConfiguration') | |
| external void validateConfiguration(); | |
| // Draw a circle on a Canvas that has a PictureRecorder. Take the image from | |
| // the PictureRecorder, and encode it as png. Check that the png data is | |
| // backed by an external Uint8List. | |
| @pragma('vm:entry-point') | |
| Future<void> encodeImageProducesExternalUint8List() async { | |
| final PictureRecorder pictureRecorder = PictureRecorder(); | |
| final Canvas canvas = Canvas(pictureRecorder); | |
| final Paint paint = Paint() | |
| ..color = Color.fromRGBO(255, 255, 255, 1.0) | |
| ..style = PaintingStyle.fill; | |
| final Offset c = Offset(50.0, 50.0); | |
| canvas.drawCircle(c, 25.0, paint); | |
| final Picture picture = pictureRecorder.endRecording(); | |
| final Image image = await picture.toImage(100, 100); | |
| _encodeImage(image, ImageByteFormat.png.index, (Uint8List result) { | |
| // The buffer should be non-null and writable. | |
| result[0] = 0; | |
| // The buffer should be external typed data. | |
| _validateExternal(result); | |
| }); | |
| } | |
| @pragma('vm:external-name', 'EncodeImage') | |
| external void _encodeImage(Image i, int format, void Function(Uint8List result)); | |
| @pragma('vm:external-name', 'ValidateExternal') | |
| external void _validateExternal(Uint8List result); | |
| @pragma('vm:entry-point') | |
| Future<void> pumpImage() async { | |
| const int width = 60; | |
| const int height = 60; | |
| final Completer<Image> completer = Completer<Image>(); | |
| decodeImageFromPixels( | |
| Uint8List.fromList(List<int>.filled(width * height * 4, 0xFF)), | |
| width, | |
| height, | |
| PixelFormat.rgba8888, | |
| (Image image) => completer.complete(image), | |
| ); | |
| final Image image = await completer.future; | |
| late Picture picture; | |
| late OffsetEngineLayer layer; | |
| void renderBlank(Duration duration) { | |
| image.dispose(); | |
| picture.dispose(); | |
| layer.dispose(); | |
| final PictureRecorder recorder = PictureRecorder(); | |
| final Canvas canvas = Canvas(recorder); | |
| canvas.drawPaint(Paint()); | |
| picture = recorder.endRecording(); | |
| final SceneBuilder builder = SceneBuilder(); | |
| layer = builder.pushOffset(0, 0); | |
| builder.addPicture(Offset.zero, picture); | |
| final Scene scene = builder.build(); | |
| window.render(scene); | |
| scene.dispose(); | |
| _finish(); | |
| } | |
| void renderImage(Duration duration) { | |
| final PictureRecorder recorder = PictureRecorder(); | |
| final Canvas canvas = Canvas(recorder); | |
| canvas.drawImage(image, Offset.zero, Paint()); | |
| picture = recorder.endRecording(); | |
| final SceneBuilder builder = SceneBuilder(); | |
| layer = builder.pushOffset(0, 0); | |
| builder.addPicture(Offset.zero, picture); | |
| _captureImageAndPicture(image, picture); | |
| final Scene scene = builder.build(); | |
| window.render(scene); | |
| scene.dispose(); | |
| window.onBeginFrame = renderBlank; | |
| window.scheduleFrame(); | |
| } | |
| window.onBeginFrame = renderImage; | |
| window.scheduleFrame(); | |
| } | |
| @pragma('vm:external-name', 'CaptureImageAndPicture') | |
| external void _captureImageAndPicture(Image image, Picture picture); | |
| @pragma('vm:entry-point') | |
| void convertPaintToDlPaint() { | |
| Paint paint = Paint(); | |
| paint.blendMode = BlendMode.modulate; | |
| paint.color = Color.fromARGB(0x11, 0x22, 0x33, 0x44); | |
| paint.colorFilter = ColorFilter.mode(Color.fromARGB(0x55, 0x66, 0x77, 0x88), BlendMode.xor); | |
| paint.maskFilter = MaskFilter.blur(BlurStyle.inner, .75); | |
| paint.style = PaintingStyle.stroke; | |
| _convertPaintToDlPaint(paint); | |
| } | |
| @pragma('vm:external-name', 'ConvertPaintToDlPaint') | |
| external void _convertPaintToDlPaint(Paint paint); | |
| @pragma('vm:entry-point') | |
| void hooksTests() async { | |
| Future<void> test(String name, FutureOr<void> Function() testFunction) async { | |
| try { | |
| await testFunction(); | |
| } catch (e) { | |
| print('Test "$name" failed!'); | |
| rethrow; | |
| } | |
| } | |
| void expectEquals(Object? value, Object? expected) { | |
| if (value != expected) { | |
| throw 'Expected $value to be $expected.'; | |
| } | |
| } | |
| void expectIdentical(Object a, Object b) { | |
| if (!identical(a, b)) { | |
| throw 'Expected $a to be identical to $b.'; | |
| } | |
| } | |
| void expectNotEquals(Object? value, Object? expected) { | |
| if (value == expected) { | |
| throw 'Expected $value to not be $expected.'; | |
| } | |
| } | |
| await test('onMetricsChanged preserves callback zone', () { | |
| late Zone originalZone; | |
| late Zone callbackZone; | |
| late double devicePixelRatio; | |
| runZoned(() { | |
| originalZone = Zone.current; | |
| window.onMetricsChanged = () { | |
| callbackZone = Zone.current; | |
| devicePixelRatio = window.devicePixelRatio; | |
| }; | |
| }); | |
| window.onMetricsChanged!(); | |
| _callHook( | |
| '_updateWindowMetrics', | |
| 20, | |
| 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 | |
| 22.0, // physicalTouchSlop | |
| <double>[], // display features bounds | |
| <int>[], // display features types | |
| <int>[], // display features states | |
| ); | |
| expectIdentical(originalZone, callbackZone); | |
| if (devicePixelRatio != 0.1234) { | |
| throw 'Expected devicePixelRatio to be 0.1234 but got $devicePixelRatio.'; | |
| } | |
| }); | |
| await test('onError preserves the callback zone', () { | |
| late Zone originalZone; | |
| late Zone callbackZone; | |
| final Object error = Exception('foo'); | |
| StackTrace? stackTrace; | |
| runZoned(() { | |
| originalZone = Zone.current; | |
| PlatformDispatcher.instance.onError = (Object exception, StackTrace? stackTrace) { | |
| callbackZone = Zone.current; | |
| expectIdentical(exception, error); | |
| expectNotEquals(stackTrace, null); | |
| return true; | |
| }; | |
| }); | |
| _callHook('_onError', 2, error, StackTrace.current); | |
| PlatformDispatcher.instance.onError = null; | |
| expectIdentical(originalZone, callbackZone); | |
| }); | |
| await test('updateUserSettings can handle an empty object', () { | |
| _callHook('_updateUserSettingsData', 1, '{}'); | |
| }); | |
| await test('PlatformDispatcher.locale returns unknown locale when locales is set to empty list', () { | |
| late Locale locale; | |
| int callCount = 0; | |
| runZoned(() { | |
| window.onLocaleChanged = () { | |
| locale = PlatformDispatcher.instance.locale; | |
| callCount += 1; | |
| }; | |
| }); | |
| const Locale fakeLocale = Locale.fromSubtags(languageCode: '1', countryCode: '2', scriptCode: '3'); | |
| _callHook('_updateLocales', 1, <String>[fakeLocale.languageCode, fakeLocale.countryCode!, fakeLocale.scriptCode!, '']); | |
| if (callCount != 1) { | |
| throw 'Expected 1 call, have $callCount'; | |
| } | |
| if (locale != fakeLocale) { | |
| throw 'Expected $locale to match $fakeLocale'; | |
| } | |
| _callHook('_updateLocales', 1, <String>[]); | |
| if (callCount != 2) { | |
| throw 'Expected 2 calls, have $callCount'; | |
| } | |
| if (locale != const Locale.fromSubtags()) { | |
| throw '$locale did not equal ${Locale.fromSubtags()}'; | |
| } | |
| if (locale.languageCode != 'und') { | |
| throw '${locale.languageCode} did not equal "und"'; | |
| } | |
| }); | |
| await test('deprecated region equals', () { | |
| // These are equal because ZR is deprecated and was mapped to CD. | |
| const Locale x = Locale('en', 'ZR'); | |
| const Locale y = Locale('en', 'CD'); | |
| expectEquals(x, y); | |
| expectEquals(x.countryCode, y.countryCode); | |
| }); | |
| await test('Window padding/insets/viewPadding/systemGestureInsets', () { | |
| _callHook( | |
| '_updateWindowMetrics', | |
| 20, | |
| 0, // window Id | |
| 1.0, // devicePixelRatio | |
| 800.0, // width | |
| 600.0, // height | |
| 50.0, // paddingTop | |
| 0.0, // paddingRight | |
| 40.0, // paddingBottom | |
| 0.0, // paddingLeft | |
| 0.0, // insetTop | |
| 0.0, // insetRight | |
| 0.0, // insetBottom | |
| 0.0, // insetLeft | |
| 0.0, // systemGestureInsetTop | |
| 0.0, // systemGestureInsetRight | |
| 0.0, // systemGestureInsetBottom | |
| 0.0, // systemGestureInsetLeft | |
| 22.0, // physicalTouchSlop | |
| <double>[], // display features bounds | |
| <int>[], // display features types | |
| <int>[], // display features states | |
| ); | |
| expectEquals(window.viewInsets.bottom, 0.0); | |
| expectEquals(window.viewPadding.bottom, 40.0); | |
| expectEquals(window.padding.bottom, 40.0); | |
| expectEquals(window.systemGestureInsets.bottom, 0.0); | |
| _callHook( | |
| '_updateWindowMetrics', | |
| 20, | |
| 0, // window Id | |
| 1.0, // devicePixelRatio | |
| 800.0, // width | |
| 600.0, // height | |
| 50.0, // paddingTop | |
| 0.0, // paddingRight | |
| 40.0, // paddingBottom | |
| 0.0, // paddingLeft | |
| 0.0, // insetTop | |
| 0.0, // insetRight | |
| 400.0, // insetBottom | |
| 0.0, // insetLeft | |
| 0.0, // systemGestureInsetTop | |
| 0.0, // systemGestureInsetRight | |
| 44.0, // systemGestureInsetBottom | |
| 0.0, // systemGestureInsetLeft | |
| 22.0, // physicalTouchSlop | |
| <double>[], // display features bounds | |
| <int>[], // display features types | |
| <int>[], // display features states | |
| ); | |
| expectEquals(window.viewInsets.bottom, 400.0); | |
| expectEquals(window.viewPadding.bottom, 40.0); | |
| expectEquals(window.padding.bottom, 0.0); | |
| expectEquals(window.systemGestureInsets.bottom, 44.0); | |
| }); | |
| await test('Window physical touch slop', () { | |
| _callHook( | |
| '_updateWindowMetrics', | |
| 20, | |
| 0, // window Id | |
| 1.0, // devicePixelRatio | |
| 800.0, // width | |
| 600.0, // height | |
| 50.0, // paddingTop | |
| 0.0, // paddingRight | |
| 40.0, // paddingBottom | |
| 0.0, // paddingLeft | |
| 0.0, // insetTop | |
| 0.0, // insetRight | |
| 0.0, // insetBottom | |
| 0.0, // insetLeft | |
| 0.0, // systemGestureInsetTop | |
| 0.0, // systemGestureInsetRight | |
| 0.0, // systemGestureInsetBottom | |
| 0.0, // systemGestureInsetLeft | |
| 11.0, // physicalTouchSlop | |
| <double>[], // display features bounds | |
| <int>[], // display features types | |
| <int>[], // display features states | |
| ); | |
| expectEquals(window.viewConfiguration.gestureSettings, | |
| GestureSettings(physicalTouchSlop: 11.0)); | |
| _callHook( | |
| '_updateWindowMetrics', | |
| 20, | |
| 0, // window Id | |
| 1.0, // devicePixelRatio | |
| 800.0, // width | |
| 600.0, // height | |
| 50.0, // paddingTop | |
| 0.0, // paddingRight | |
| 40.0, // paddingBottom | |
| 0.0, // paddingLeft | |
| 0.0, // insetTop | |
| 0.0, // insetRight | |
| 400.0, // insetBottom | |
| 0.0, // insetLeft | |
| 0.0, // systemGestureInsetTop | |
| 0.0, // systemGestureInsetRight | |
| 44.0, // systemGestureInsetBottom | |
| 0.0, // systemGestureInsetLeft | |
| -1.0, // physicalTouchSlop | |
| <double>[], // display features bounds | |
| <int>[], // display features types | |
| <int>[], // display features states | |
| ); | |
| expectEquals(window.viewConfiguration.gestureSettings, | |
| GestureSettings(physicalTouchSlop: null)); | |
| _callHook( | |
| '_updateWindowMetrics', | |
| 20, | |
| 0, // window Id | |
| 1.0, // devicePixelRatio | |
| 800.0, // width | |
| 600.0, // height | |
| 50.0, // paddingTop | |
| 0.0, // paddingRight | |
| 40.0, // paddingBottom | |
| 0.0, // paddingLeft | |
| 0.0, // insetTop | |
| 0.0, // insetRight | |
| 400.0, // insetBottom | |
| 0.0, // insetLeft | |
| 0.0, // systemGestureInsetTop | |
| 0.0, // systemGestureInsetRight | |
| 44.0, // systemGestureInsetBottom | |
| 0.0, // systemGestureInsetLeft | |
| 22.0, // physicalTouchSlop | |
| <double>[], // display features bounds | |
| <int>[], // display features types | |
| <int>[], // display features states | |
| ); | |
| expectEquals(window.viewConfiguration.gestureSettings, | |
| GestureSettings(physicalTouchSlop: 22.0)); | |
| }); | |
| await 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; | |
| }; | |
| }); | |
| _callHook('_updateLocales', 1, <String>['en', 'US', '', '']); | |
| expectIdentical(runZone, innerZone); | |
| expectEquals(locale, const Locale('en', 'US')); | |
| }); | |
| await 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; | |
| }; | |
| }); | |
| _callHook('_beginFrame', 2, 1234, 1); | |
| expectIdentical(runZone, innerZone); | |
| expectEquals(start, const Duration(microseconds: 1234)); | |
| }); | |
| await test('onDrawFrame preserves callback zone', () { | |
| late Zone innerZone; | |
| late Zone runZone; | |
| runZoned(() { | |
| innerZone = Zone.current; | |
| window.onDrawFrame = () { | |
| runZone = Zone.current; | |
| }; | |
| }); | |
| _callHook('_drawFrame'); | |
| expectIdentical(runZone, innerZone); | |
| }); | |
| await test('onReportTimings preserves callback zone', () { | |
| late Zone innerZone; | |
| late Zone runZone; | |
| runZoned(() { | |
| innerZone = Zone.current; | |
| window.onReportTimings = (List<FrameTiming> timings) { | |
| runZone = Zone.current; | |
| }; | |
| }); | |
| _callHook('_reportTimings', 1, <int>[]); | |
| expectIdentical(runZone, innerZone); | |
| }); | |
| await 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); | |
| _callHook('_dispatchPointerDataPacket', 1, testData); | |
| expectIdentical(runZone, innerZone); | |
| expectEquals(data.data.length, 0); | |
| }); | |
| await 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? | |
| _callHook('_updateSemanticsEnabled', 1, newValue); | |
| expectIdentical(runZone, innerZone); | |
| expectEquals(enabled, newValue); | |
| }); | |
| await 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; | |
| }; | |
| }); | |
| _callHook('_dispatchSemanticsAction', 3, 1234, 4, null); | |
| expectIdentical(runZone, innerZone); | |
| expectEquals(id, 1234); | |
| expectEquals(action, 4); | |
| }); | |
| await 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; | |
| }; | |
| }); | |
| _callHook('_dispatchPlatformMessage', 3, 'testName', null, 123456789); | |
| expectIdentical(runZone, innerZone); | |
| expectEquals(name, 'testName'); | |
| }); | |
| await 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!(); | |
| _callHook('_updateUserSettingsData', 1, '{"textScaleFactor": 0.5, "platformBrightness": "light", "alwaysUse24HourFormat": true}'); | |
| expectIdentical(runZoneTextScaleFactor, innerZone); | |
| expectEquals(textScaleFactor, 0.5); | |
| textScaleFactor = null; | |
| platformBrightness = null; | |
| window.onPlatformBrightnessChanged!(); | |
| _callHook('_updateUserSettingsData', 1, '{"textScaleFactor": 0.5, "platformBrightness": "dark", "alwaysUse24HourFormat": true}'); | |
| expectIdentical(runZonePlatformBrightness, innerZone); | |
| expectEquals(platformBrightness, Brightness.dark); | |
| }); | |
| await test('onFrameDataChanged preserves callback zone', () { | |
| late Zone innerZone; | |
| late Zone runZone; | |
| late int frameNumber; | |
| runZoned(() { | |
| innerZone = Zone.current; | |
| window.onFrameDataChanged = () { | |
| runZone = Zone.current; | |
| frameNumber = window.frameData.frameNumber; | |
| }; | |
| }); | |
| _callHook('_beginFrame', 2, 0, 2); | |
| expectNotEquals(runZone, null); | |
| expectIdentical(runZone, innerZone); | |
| expectEquals(frameNumber, 2); | |
| }); | |
| await test('_futureize handles callbacker sync error', () async { | |
| String? callbacker(void Function(Object? arg) cb) { | |
| return 'failure'; | |
| } | |
| Object? error; | |
| try { | |
| await _futurize(callbacker); | |
| } catch (err) { | |
| error = err; | |
| } | |
| expectNotEquals(error, null); | |
| }); | |
| await test('_futureize does not leak sync uncaught exceptions into the zone', () async { | |
| String? callbacker(void Function(Object? arg) cb) { | |
| cb(null); // indicates failure | |
| } | |
| Object? error; | |
| try { | |
| await _futurize(callbacker); | |
| } catch (err) { | |
| error = err; | |
| } | |
| expectNotEquals(error, null); | |
| }); | |
| await test('_futureize does not leak async uncaught exceptions into the zone', () async { | |
| String? callbacker(void Function(Object? arg) cb) { | |
| Timer.run(() { | |
| cb(null); // indicates failure | |
| }); | |
| } | |
| Object? error; | |
| try { | |
| await _futurize(callbacker); | |
| } catch (err) { | |
| error = err; | |
| } | |
| expectNotEquals(error, null); | |
| }); | |
| await test('_futureize successfully returns a value sync', () async { | |
| String? callbacker(void Function(Object? arg) cb) { | |
| cb(true); | |
| } | |
| final Object? result = await _futurize(callbacker); | |
| expectEquals(result, true); | |
| }); | |
| await test('_futureize successfully returns a value async', () async { | |
| String? callbacker(void Function(Object? arg) cb) { | |
| Timer.run(() { | |
| cb(true); | |
| }); | |
| } | |
| final Object? result = await _futurize(callbacker); | |
| expectEquals(result, true); | |
| }); | |
| await test('root isolate token', () async { | |
| if (RootIsolateToken.instance == null) { | |
| throw Exception('We should have a token on a root isolate.'); | |
| } | |
| ReceivePort receivePort = ReceivePort(); | |
| Isolate.spawn(_backgroundRootIsolateTestMain, receivePort.sendPort); | |
| bool didPass = await receivePort.first as bool; | |
| if (!didPass) { | |
| throw Exception('Background isolate found a root isolate id.'); | |
| } | |
| }); | |
| await test('send port message without registering', () async { | |
| ReceivePort receivePort = ReceivePort(); | |
| Isolate.spawn(_backgroundIsolateSendWithoutRegistering, receivePort.sendPort); | |
| bool didError = await receivePort.first as bool; | |
| if (!didError) { | |
| throw Exception('Expected an error when not registering a root isolate and sending port messages.'); | |
| } | |
| }); | |
| _finish(); | |
| } | |
| /// Sends `true` on [port] if the isolate executing the function is not a root | |
| /// isolate. | |
| void _backgroundRootIsolateTestMain(SendPort port) { | |
| port.send(RootIsolateToken.instance == null); | |
| } | |
| /// Sends `true` on [port] if [PlatformDispatcher.sendPortPlatformMessage] | |
| /// throws an exception without calling | |
| /// [PlatformDispatcher.registerBackgroundIsolate]. | |
| void _backgroundIsolateSendWithoutRegistering(SendPort port) { | |
| bool didError = false; | |
| ReceivePort messagePort = ReceivePort(); | |
| try { | |
| PlatformDispatcher.instance.sendPortPlatformMessage( | |
| 'foo', | |
| null, | |
| 1, | |
| messagePort.sendPort, | |
| ); | |
| } catch (_) { | |
| didError = true; | |
| } | |
| port.send(didError); | |
| } | |
| typedef _Callback<T> = void Function(T result); | |
| typedef _Callbacker<T> = String? Function(_Callback<T?> callback); | |
| // This is an exact copy of the function defined in painting.dart. If you change either | |
| // then you must change both. | |
| Future<T> _futurize<T>(_Callbacker<T> callbacker) { | |
| final Completer<T> completer = Completer<T>.sync(); | |
| // If the callback synchronously throws an error, then synchronously | |
| // rethrow that error instead of adding it to the completer. This | |
| // prevents the Zone from receiving an uncaught exception. | |
| bool sync = true; | |
| final String? error = callbacker((T? t) { | |
| if (t == null) { | |
| if (sync) { | |
| throw Exception('operation failed'); | |
| } else { | |
| completer.completeError(Exception('operation failed')); | |
| } | |
| } else { | |
| completer.complete(t); | |
| } | |
| }); | |
| sync = false; | |
| if (error != null) | |
| throw Exception(error); | |
| return completer.future; | |
| } | |
| @pragma('vm:external-name', 'CallHook') | |
| external void _callHook( | |
| String name, [ | |
| int argCount = 0, | |
| Object? arg0, | |
| Object? arg1, | |
| Object? arg2, | |
| Object? arg3, | |
| Object? arg4, | |
| Object? arg5, | |
| Object? arg6, | |
| Object? arg8, | |
| Object? arg9, | |
| Object? arg10, | |
| Object? arg11, | |
| Object? arg12, | |
| Object? arg13, | |
| Object? arg14, | |
| Object? arg15, | |
| Object? arg16, | |
| Object? arg17, | |
| Object? arg18, | |
| Object? arg19, | |
| Object? arg20, | |
| ]); |