blob: b5eac66aed490752c1764447d28be261c1f392a5 [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';
void main() {}
/// Mutiple tests use this to signal to the C++ side that they are ready for
/// validation.
void _finish() native '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();
}
_validateBuilderHasLayers(SceneBuilder builder) native 'ValidateBuilderHasLayers';
_validateBuilderHasNoLayers() native 'ValidateBuilderHasNoLayers';
_captureScene(Scene scene) native 'CaptureScene';
_validateSceneHasNoLayers() native '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();
}
_captureRootLayer(SceneBuilder sceneBuilder) native 'CaptureRootLayer';
_validateLayerTreeCounts() native 'ValidateLayerTreeCounts';
_validateEngineLayerDispose() native '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();
}
void _validateCodec(Codec codec) native 'ValidateCodec';
@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);
}
void _validateVertices(Vertices vertices) native 'ValidateVertices';
@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());
}
void _semanticsUpdate(SemanticsUpdate update) native 'SemanticsUpdate';
@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);
});
}
void _validatePath(Path path) native 'ValidatePath';
@pragma('vm:entry-point')
void frameCallback(_Image, int) {
print('called back');
}
@pragma('vm:entry-point')
void messageCallback(dynamic data) {}
@pragma('vm:entry-point')
void validateConfiguration() native '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);
});
}
void _encodeImage(Image i, int format, void Function(Uint8List result))
native 'EncodeImage';
void _validateExternal(Uint8List result) native 'ValidateExternal';
@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();
}
void _captureImageAndPicture(Image image, Picture picture) native 'CaptureImageAndPicture';
@pragma('vm:entry-point')
void hooksTests() {
void test(String name, VoidCallback testFunction) {
try {
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(Zone originalZone, Zone callbackZone) {
if (!identical(callbackZone, originalZone)) {
throw 'Callback called in wrong zone.';
}
}
void expectNotEquals(Object? value, Object? expected) {
if (value == expected) {
throw 'Expected $value to not be $expected.';
}
}
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',
17,
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
);
expectIdentical(originalZone, callbackZone);
if (devicePixelRatio != 0.1234) {
throw 'Expected devicePixelRatio to be 0.1234 but got $devicePixelRatio.';
}
});
test('updateUserSettings can handle an empty object', () {
_callHook('_updateUserSettingsData', 1, '{}');
});
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"';
}
});
test('Window padding/insets/viewPadding/systemGestureInsets', () {
_callHook(
'_updateWindowMetrics',
17,
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
);
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',
17,
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
);
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('Window physical touch slop', () {
_callHook(
'_updateWindowMetrics',
17,
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
);
expectEquals(window.viewConfiguration.gestureSettings,
GestureSettings(physicalTouchSlop: 11.0));
_callHook(
'_updateWindowMetrics',
17,
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
);
expectEquals(window.viewConfiguration.gestureSettings,
GestureSettings(physicalTouchSlop: null));
_callHook(
'_updateWindowMetrics',
17,
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
);
expectEquals(window.viewConfiguration.gestureSettings,
GestureSettings(physicalTouchSlop: 22.0));
});
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'));
});
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));
});
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);
});
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);
});
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);
});
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);
});
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);
});
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');
});
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);
});
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);
});
_finish();
}
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,
]) native 'CallHook';