| // 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.6 |
| import 'dart:typed_data' show Float64List; |
| import 'dart:ui'; |
| |
| import 'package:test/test.dart'; |
| |
| void main() { |
| test('pushTransform validates the matrix', () { |
| final SceneBuilder builder = SceneBuilder(); |
| final Float64List matrix4 = Float64List.fromList(<double>[ |
| 1, 0, 0, 0, |
| 0, 1, 0, 0, |
| 0, 0, 1, 0, |
| 0, 0, 0, 1, |
| ]); |
| expect(builder.pushTransform(matrix4), isNotNull); |
| |
| final Float64List matrix4WrongLength = Float64List.fromList(<double>[ |
| 1, 0, 0, 0, |
| 0, 1, 0, |
| 0, 0, 1, 0, |
| 0, 0, 0, |
| ]); |
| assert(() { |
| expect( |
| () => builder.pushTransform(matrix4WrongLength), |
| throwsA(const TypeMatcher<AssertionError>()), |
| ); |
| return true; |
| }()); |
| |
| final Float64List matrix4NaN = Float64List.fromList(<double>[ |
| 1, 0, 0, 0, |
| 0, 1, 0, 0, |
| 0, 0, 1, 0, |
| 0, 0, 0, double.nan, |
| ]); |
| assert(() { |
| expect( |
| () => builder.pushTransform(matrix4NaN), |
| throwsA(const TypeMatcher<AssertionError>()), |
| ); |
| return true; |
| }()); |
| |
| final Float64List matrix4Infinity = Float64List.fromList(<double>[ |
| 1, 0, 0, 0, |
| 0, 1, 0, 0, |
| 0, 0, 1, 0, |
| 0, 0, 0, double.infinity, |
| ]); |
| assert(() { |
| expect( |
| () => builder.pushTransform(matrix4Infinity), |
| throwsA(const TypeMatcher<AssertionError>()), |
| ); |
| return true; |
| }()); |
| }); |
| |
| test('SceneBuilder accepts typed layers', () { |
| final SceneBuilder builder1 = SceneBuilder(); |
| final OpacityEngineLayer opacity1 = builder1.pushOpacity(100); |
| expect(opacity1, isNotNull); |
| builder1.pop(); |
| builder1.build(); |
| |
| final SceneBuilder builder2 = SceneBuilder(); |
| final OpacityEngineLayer opacity2 = builder2.pushOpacity(200, oldLayer: opacity1); |
| expect(opacity2, isNotNull); |
| builder2.pop(); |
| builder2.build(); |
| }); |
| |
| // Attempts to use the same layer first as `oldLayer` then in `addRetained`. |
| void testPushThenIllegalRetain(_TestNoSharingFunction pushFunction) { |
| final SceneBuilder builder1 = SceneBuilder(); |
| final EngineLayer layer = pushFunction(builder1, null); |
| builder1.pop(); |
| builder1.build(); |
| |
| final SceneBuilder builder2 = SceneBuilder(); |
| pushFunction(builder2, layer); |
| builder2.pop(); |
| assert(() { |
| try { |
| builder2.addRetained(layer); |
| fail('Expected addRetained to throw AssertionError but it returned successully'); |
| } on AssertionError catch (error) { |
| expect(error.toString(), contains('The layer is already being used')); |
| } |
| return true; |
| }()); |
| builder2.build(); |
| } |
| |
| // Attempts to use the same layer first in `addRetained` then as `oldLayer`. |
| void testAddRetainedThenIllegalPush(_TestNoSharingFunction pushFunction) { |
| final SceneBuilder builder1 = SceneBuilder(); |
| final EngineLayer layer = pushFunction(builder1, null); |
| builder1.pop(); |
| builder1.build(); |
| |
| final SceneBuilder builder2 = SceneBuilder(); |
| builder2.addRetained(layer); |
| assert(() { |
| try { |
| pushFunction(builder2, layer); |
| fail('Expected push to throw AssertionError but it returned successully'); |
| } on AssertionError catch (error) { |
| expect(error.toString(), contains('The layer is already being used')); |
| } |
| return true; |
| }()); |
| builder2.build(); |
| } |
| |
| // Attempts to retain the same layer twice in the same scene. |
| void testDoubleAddRetained(_TestNoSharingFunction pushFunction) { |
| final SceneBuilder builder1 = SceneBuilder(); |
| final EngineLayer layer = pushFunction(builder1, null); |
| builder1.pop(); |
| builder1.build(); |
| |
| final SceneBuilder builder2 = SceneBuilder(); |
| builder2.addRetained(layer); |
| assert(() { |
| try { |
| builder2.addRetained(layer); |
| fail('Expected second addRetained to throw AssertionError but it returned successully'); |
| } on AssertionError catch (error) { |
| expect(error.toString(), contains('The layer is already being used')); |
| } |
| return true; |
| }()); |
| builder2.build(); |
| } |
| |
| // Attempts to use the same layer as `oldLayer` twice in the same scene. |
| void testPushOldLayerTwice(_TestNoSharingFunction pushFunction) { |
| final SceneBuilder builder1 = SceneBuilder(); |
| final EngineLayer layer = pushFunction(builder1, null); |
| builder1.pop(); |
| builder1.build(); |
| |
| final SceneBuilder builder2 = SceneBuilder(); |
| pushFunction(builder2, layer); |
| assert(() { |
| try { |
| pushFunction(builder2, layer); |
| fail('Expected push to throw AssertionError but it returned successully'); |
| } on AssertionError catch (error) { |
| expect(error.toString(), contains('was previously used as oldLayer')); |
| } |
| return true; |
| }()); |
| builder2.build(); |
| } |
| |
| // Attempts to use a child of a retained layer as an `oldLayer`. |
| void testPushChildLayerOfRetainedLayer(_TestNoSharingFunction pushFunction) { |
| final SceneBuilder builder1 = SceneBuilder(); |
| final EngineLayer layer = pushFunction(builder1, null); |
| final OpacityEngineLayer childLayer = builder1.pushOpacity(123); |
| builder1.pop(); |
| builder1.pop(); |
| builder1.build(); |
| |
| final SceneBuilder builder2 = SceneBuilder(); |
| builder2.addRetained(layer); |
| assert(() { |
| try { |
| builder2.pushOpacity(321, oldLayer: childLayer); |
| fail('Expected pushOpacity to throw AssertionError but it returned successully'); |
| } on AssertionError catch (error) { |
| expect(error.toString(), contains('The layer is already being used')); |
| } |
| return true; |
| }()); |
| builder2.build(); |
| } |
| |
| // Attempts to retain a layer whose child is already used as `oldLayer` elsewhere in the scene. |
| void testRetainParentLayerOfPushedChild(_TestNoSharingFunction pushFunction) { |
| final SceneBuilder builder1 = SceneBuilder(); |
| final EngineLayer layer = pushFunction(builder1, null); |
| final OpacityEngineLayer childLayer = builder1.pushOpacity(123); |
| builder1.pop(); |
| builder1.pop(); |
| builder1.build(); |
| |
| final SceneBuilder builder2 = SceneBuilder(); |
| builder2.pushOpacity(234, oldLayer: childLayer); |
| builder2.pop(); |
| assert(() { |
| try { |
| builder2.addRetained(layer); |
| fail('Expected addRetained to throw AssertionError but it returned successully'); |
| } on AssertionError catch (error) { |
| expect(error.toString(), contains('The layer is already being used')); |
| } |
| return true; |
| }()); |
| builder2.build(); |
| } |
| |
| // Attempts to retain a layer that has been used as `oldLayer` in a previous frame. |
| void testRetainOldLayer(_TestNoSharingFunction pushFunction) { |
| final SceneBuilder builder1 = SceneBuilder(); |
| final EngineLayer layer = pushFunction(builder1, null); |
| builder1.pop(); |
| builder1.build(); |
| |
| final SceneBuilder builder2 = SceneBuilder(); |
| pushFunction(builder2, layer); |
| builder2.pop(); |
| assert(() { |
| try { |
| final SceneBuilder builder3 = SceneBuilder(); |
| builder3.addRetained(layer); |
| fail('Expected addRetained to throw AssertionError but it returned successully'); |
| } on AssertionError catch (error) { |
| expect(error.toString(), contains('was previously used as oldLayer')); |
| } |
| return true; |
| }()); |
| builder2.build(); |
| } |
| |
| // Attempts to pass layer as `oldLayer` that has been used as `oldLayer` in a previous frame. |
| void testPushOldLayer(_TestNoSharingFunction pushFunction) { |
| final SceneBuilder builder1 = SceneBuilder(); |
| final EngineLayer layer = pushFunction(builder1, null); |
| builder1.pop(); |
| builder1.build(); |
| |
| final SceneBuilder builder2 = SceneBuilder(); |
| pushFunction(builder2, layer); |
| builder2.pop(); |
| assert(() { |
| try { |
| final SceneBuilder builder3 = SceneBuilder(); |
| pushFunction(builder3, layer); |
| fail('Expected addRetained to throw AssertionError but it returned successully'); |
| } on AssertionError catch (error) { |
| expect(error.toString(), contains('was previously used as oldLayer')); |
| } |
| return true; |
| }()); |
| builder2.build(); |
| } |
| |
| // Attempts to retain a parent of a layer used as `oldLayer` in a previous frame. |
| void testRetainsParentOfOldLayer(_TestNoSharingFunction pushFunction) { |
| final SceneBuilder builder1 = SceneBuilder(); |
| final EngineLayer parentLayer = pushFunction(builder1, null); |
| final OpacityEngineLayer childLayer = builder1.pushOpacity(123); |
| builder1.pop(); |
| builder1.pop(); |
| builder1.build(); |
| |
| final SceneBuilder builder2 = SceneBuilder(); |
| builder2.pushOpacity(321, oldLayer: childLayer); |
| builder2.pop(); |
| assert(() { |
| try { |
| final SceneBuilder builder3 = SceneBuilder(); |
| builder3.addRetained(parentLayer); |
| fail('Expected addRetained to throw AssertionError but it returned successully'); |
| } on AssertionError catch (error) { |
| expect(error.toString(), contains('was previously used as oldLayer')); |
| } |
| return true; |
| }()); |
| builder2.build(); |
| } |
| |
| void testNoSharing(_TestNoSharingFunction pushFunction) { |
| testPushThenIllegalRetain(pushFunction); |
| testAddRetainedThenIllegalPush(pushFunction); |
| testDoubleAddRetained(pushFunction); |
| testPushOldLayerTwice(pushFunction); |
| testPushChildLayerOfRetainedLayer(pushFunction); |
| testRetainParentLayerOfPushedChild(pushFunction); |
| testRetainOldLayer(pushFunction); |
| testPushOldLayer(pushFunction); |
| testRetainsParentOfOldLayer(pushFunction); |
| } |
| |
| test('SceneBuilder does not share a layer between addRetained and push*', () { |
| testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { |
| return builder.pushOffset(0, 0, oldLayer: oldLayer as OffsetEngineLayer); |
| }); |
| testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { |
| return builder.pushTransform(Float64List(16), oldLayer: oldLayer as TransformEngineLayer); |
| }); |
| testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { |
| return builder.pushClipRect(Rect.zero, oldLayer: oldLayer as ClipRectEngineLayer); |
| }); |
| testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { |
| return builder.pushClipRRect(RRect.zero, oldLayer: oldLayer as ClipRRectEngineLayer); |
| }); |
| testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { |
| return builder.pushClipPath(Path(), oldLayer: oldLayer as ClipPathEngineLayer); |
| }); |
| testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { |
| return builder.pushOpacity(100, oldLayer: oldLayer as OpacityEngineLayer); |
| }); |
| testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { |
| return builder.pushBackdropFilter(ImageFilter.blur(), oldLayer: oldLayer as BackdropFilterEngineLayer); |
| }); |
| testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { |
| return builder.pushShaderMask( |
| Gradient.radial( |
| const Offset(0, 0), |
| 10, |
| const <Color>[Color.fromARGB(0, 0, 0, 0), Color.fromARGB(0, 255, 255, 255)], |
| ), |
| Rect.zero, |
| BlendMode.color, |
| oldLayer: oldLayer as ShaderMaskEngineLayer, |
| ); |
| }); |
| testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { |
| return builder.pushPhysicalShape(path: Path(), color: const Color.fromARGB(0, 0, 0, 0), oldLayer: oldLayer as PhysicalShapeEngineLayer); |
| }); |
| testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { |
| return builder.pushColorFilter( |
| const ColorFilter.mode( |
| Color.fromARGB(0, 0, 0, 0), |
| BlendMode.color, |
| ), |
| oldLayer: oldLayer as ColorFilterEngineLayer, |
| ); |
| }); |
| testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { |
| return builder.pushColorFilter( |
| const ColorFilter.matrix(<double>[ |
| 1, 0, 0, 0, 0, |
| 0, 1, 0, 0, 0, |
| 0, 0, 1, 0, 0, |
| 0, 0, 0, 1, 0, |
| ]), |
| oldLayer: oldLayer as ColorFilterEngineLayer, |
| ); |
| }); |
| testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { |
| return builder.pushColorFilter( |
| const ColorFilter.linearToSrgbGamma(), |
| oldLayer: oldLayer as ColorFilterEngineLayer, |
| ); |
| }); |
| testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { |
| return builder.pushColorFilter( |
| const ColorFilter.srgbToLinearGamma(), |
| oldLayer: oldLayer as ColorFilterEngineLayer, |
| ); |
| }); |
| testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { |
| return builder.pushImageFilter( |
| ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), |
| oldLayer: oldLayer as ImageFilterEngineLayer, |
| ); |
| }); |
| testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { |
| return builder.pushImageFilter( |
| ImageFilter.matrix(Float64List.fromList(<double>[ |
| 1, 0, 0, 0, |
| 0, 1, 0, 0, |
| 0, 0, 1, 0, |
| 0, 0, 0, 1, |
| ])), |
| oldLayer: oldLayer as ImageFilterEngineLayer, |
| ); |
| }); |
| }); |
| } |
| |
| typedef _TestNoSharingFunction = EngineLayer Function(SceneBuilder builder, EngineLayer oldLayer); |