blob: f1d213ec22cd8422aa3ab00ff6b072d15deb5f5d [file] [log] [blame]
// 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,
]);