1// Copyright 2013 The Flutter Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'dart:typed_data' show Float64List; 6import 'dart:ui'; 7 8import 'package:test/test.dart'; 9 10void main() { 11 test('pushTransform validates the matrix', () { 12 final SceneBuilder builder = SceneBuilder(); 13 final Float64List matrix4 = Float64List.fromList(<double>[ 14 1, 0, 0, 0, 15 0, 1, 0, 0, 16 0, 0, 1, 0, 17 0, 0, 0, 1, 18 ]); 19 expect(builder.pushTransform(matrix4), isNotNull); 20 21 final Float64List matrix4WrongLength = Float64List.fromList(<double>[ 22 1, 0, 0, 0, 23 0, 1, 0, 24 0, 0, 1, 0, 25 0, 0, 0, 26 ]); 27 assert(() { 28 expect( 29 () => builder.pushTransform(matrix4WrongLength), 30 throwsA(const TypeMatcher<AssertionError>()), 31 ); 32 return true; 33 }()); 34 35 final Float64List matrix4NaN = Float64List.fromList(<double>[ 36 1, 0, 0, 0, 37 0, 1, 0, 0, 38 0, 0, 1, 0, 39 0, 0, 0, double.nan, 40 ]); 41 assert(() { 42 expect( 43 () => builder.pushTransform(matrix4NaN), 44 throwsA(const TypeMatcher<AssertionError>()), 45 ); 46 return true; 47 }()); 48 49 final Float64List matrix4Infinity = Float64List.fromList(<double>[ 50 1, 0, 0, 0, 51 0, 1, 0, 0, 52 0, 0, 1, 0, 53 0, 0, 0, double.infinity, 54 ]); 55 assert(() { 56 expect( 57 () => builder.pushTransform(matrix4Infinity), 58 throwsA(const TypeMatcher<AssertionError>()), 59 ); 60 return true; 61 }()); 62 }); 63 64 test('SceneBuilder accepts typed layers', () { 65 final SceneBuilder builder1 = SceneBuilder(); 66 final OpacityEngineLayer opacity1 = builder1.pushOpacity(100); 67 expect(opacity1, isNotNull); 68 builder1.pop(); 69 builder1.build(); 70 71 final SceneBuilder builder2 = SceneBuilder(); 72 final OpacityEngineLayer opacity2 = builder2.pushOpacity(200, oldLayer: opacity1); 73 expect(opacity2, isNotNull); 74 builder2.pop(); 75 builder2.build(); 76 }); 77 78 // Attempts to use the same layer first as `oldLayer` then in `addRetained`. 79 void testPushThenIllegalRetain(_TestNoSharingFunction pushFunction) { 80 final SceneBuilder builder1 = SceneBuilder(); 81 final EngineLayer layer = pushFunction(builder1, null); 82 builder1.pop(); 83 builder1.build(); 84 85 final SceneBuilder builder2 = SceneBuilder(); 86 pushFunction(builder2, layer); 87 builder2.pop(); 88 assert(() { 89 try { 90 builder2.addRetained(layer); 91 fail('Expected addRetained to throw AssertionError but it returned successully'); 92 } on AssertionError catch (error) { 93 expect(error.toString(), contains('The layer is already being used')); 94 } 95 return true; 96 }()); 97 builder2.build(); 98 } 99 100 // Attempts to use the same layer first in `addRetained` then as `oldLayer`. 101 void testAddRetainedThenIllegalPush(_TestNoSharingFunction pushFunction) { 102 final SceneBuilder builder1 = SceneBuilder(); 103 final EngineLayer layer = pushFunction(builder1, null); 104 builder1.pop(); 105 builder1.build(); 106 107 final SceneBuilder builder2 = SceneBuilder(); 108 builder2.addRetained(layer); 109 assert(() { 110 try { 111 pushFunction(builder2, layer); 112 fail('Expected push to throw AssertionError but it returned successully'); 113 } on AssertionError catch (error) { 114 expect(error.toString(), contains('The layer is already being used')); 115 } 116 return true; 117 }()); 118 builder2.build(); 119 } 120 121 // Attempts to retain the same layer twice in the same scene. 122 void testDoubleAddRetained(_TestNoSharingFunction pushFunction) { 123 final SceneBuilder builder1 = SceneBuilder(); 124 final EngineLayer layer = pushFunction(builder1, null); 125 builder1.pop(); 126 builder1.build(); 127 128 final SceneBuilder builder2 = SceneBuilder(); 129 builder2.addRetained(layer); 130 assert(() { 131 try { 132 builder2.addRetained(layer); 133 fail('Expected second addRetained to throw AssertionError but it returned successully'); 134 } on AssertionError catch (error) { 135 expect(error.toString(), contains('The layer is already being used')); 136 } 137 return true; 138 }()); 139 builder2.build(); 140 } 141 142 // Attempts to use the same layer as `oldLayer` twice in the same scene. 143 void testPushOldLayerTwice(_TestNoSharingFunction pushFunction) { 144 final SceneBuilder builder1 = SceneBuilder(); 145 final EngineLayer layer = pushFunction(builder1, null); 146 builder1.pop(); 147 builder1.build(); 148 149 final SceneBuilder builder2 = SceneBuilder(); 150 pushFunction(builder2, layer); 151 assert(() { 152 try { 153 pushFunction(builder2, layer); 154 fail('Expected push to throw AssertionError but it returned successully'); 155 } on AssertionError catch (error) { 156 expect(error.toString(), contains('was previously used as oldLayer')); 157 } 158 return true; 159 }()); 160 builder2.build(); 161 } 162 163 // Attempts to use a child of a retained layer as an `oldLayer`. 164 void testPushChildLayerOfRetainedLayer(_TestNoSharingFunction pushFunction) { 165 final SceneBuilder builder1 = SceneBuilder(); 166 final EngineLayer layer = pushFunction(builder1, null); 167 final EngineLayer childLayer = builder1.pushOpacity(123); 168 builder1.pop(); 169 builder1.pop(); 170 builder1.build(); 171 172 final SceneBuilder builder2 = SceneBuilder(); 173 builder2.addRetained(layer); 174 assert(() { 175 try { 176 builder2.pushOpacity(321, oldLayer: childLayer); 177 fail('Expected pushOpacity to throw AssertionError but it returned successully'); 178 } on AssertionError catch (error) { 179 expect(error.toString(), contains('The layer is already being used')); 180 } 181 return true; 182 }()); 183 builder2.build(); 184 } 185 186 // Attempts to retain a layer whose child is already used as `oldLayer` elsewhere in the scene. 187 void testRetainParentLayerOfPushedChild(_TestNoSharingFunction pushFunction) { 188 final SceneBuilder builder1 = SceneBuilder(); 189 final EngineLayer layer = pushFunction(builder1, null); 190 final EngineLayer childLayer = builder1.pushOpacity(123); 191 builder1.pop(); 192 builder1.pop(); 193 builder1.build(); 194 195 final SceneBuilder builder2 = SceneBuilder(); 196 builder2.pushOpacity(234, oldLayer: childLayer); 197 builder2.pop(); 198 assert(() { 199 try { 200 builder2.addRetained(layer); 201 fail('Expected addRetained to throw AssertionError but it returned successully'); 202 } on AssertionError catch (error) { 203 expect(error.toString(), contains('The layer is already being used')); 204 } 205 return true; 206 }()); 207 builder2.build(); 208 } 209 210 // Attempts to retain a layer that has been used as `oldLayer` in a previous frame. 211 void testRetainOldLayer(_TestNoSharingFunction pushFunction) { 212 final SceneBuilder builder1 = SceneBuilder(); 213 final EngineLayer layer = pushFunction(builder1, null); 214 builder1.pop(); 215 builder1.build(); 216 217 final SceneBuilder builder2 = SceneBuilder(); 218 pushFunction(builder2, layer); 219 builder2.pop(); 220 assert(() { 221 try { 222 final SceneBuilder builder3 = SceneBuilder(); 223 builder3.addRetained(layer); 224 fail('Expected addRetained to throw AssertionError but it returned successully'); 225 } on AssertionError catch (error) { 226 expect(error.toString(), contains('was previously used as oldLayer')); 227 } 228 return true; 229 }()); 230 builder2.build(); 231 } 232 233 // Attempts to pass layer as `oldLayer` that has been used as `oldLayer` in a previous frame. 234 void testPushOldLayer(_TestNoSharingFunction pushFunction) { 235 final SceneBuilder builder1 = SceneBuilder(); 236 final EngineLayer layer = pushFunction(builder1, null); 237 builder1.pop(); 238 builder1.build(); 239 240 final SceneBuilder builder2 = SceneBuilder(); 241 pushFunction(builder2, layer); 242 builder2.pop(); 243 assert(() { 244 try { 245 final SceneBuilder builder3 = SceneBuilder(); 246 pushFunction(builder3, layer); 247 fail('Expected addRetained to throw AssertionError but it returned successully'); 248 } on AssertionError catch (error) { 249 expect(error.toString(), contains('was previously used as oldLayer')); 250 } 251 return true; 252 }()); 253 builder2.build(); 254 } 255 256 // Attempts to retain a parent of a layer used as `oldLayer` in a previous frame. 257 void testRetainsParentOfOldLayer(_TestNoSharingFunction pushFunction) { 258 final SceneBuilder builder1 = SceneBuilder(); 259 final EngineLayer parentLayer = pushFunction(builder1, null); 260 final OpacityEngineLayer childLayer = builder1.pushOpacity(123); 261 builder1.pop(); 262 builder1.pop(); 263 builder1.build(); 264 265 final SceneBuilder builder2 = SceneBuilder(); 266 builder2.pushOpacity(321, oldLayer: childLayer); 267 builder2.pop(); 268 assert(() { 269 try { 270 final SceneBuilder builder3 = SceneBuilder(); 271 builder3.addRetained(parentLayer); 272 fail('Expected addRetained to throw AssertionError but it returned successully'); 273 } on AssertionError catch (error) { 274 expect(error.toString(), contains('was previously used as oldLayer')); 275 } 276 return true; 277 }()); 278 builder2.build(); 279 } 280 281 void testNoSharing(_TestNoSharingFunction pushFunction) { 282 testPushThenIllegalRetain(pushFunction); 283 testAddRetainedThenIllegalPush(pushFunction); 284 testDoubleAddRetained(pushFunction); 285 testPushOldLayerTwice(pushFunction); 286 testPushChildLayerOfRetainedLayer(pushFunction); 287 testRetainParentLayerOfPushedChild(pushFunction); 288 testRetainOldLayer(pushFunction); 289 testPushOldLayer(pushFunction); 290 testRetainsParentOfOldLayer(pushFunction); 291 } 292 293 test('SceneBuilder does not share a layer between addRetained and push*', () { 294 testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { 295 return builder.pushOffset(0, 0, oldLayer: oldLayer); 296 }); 297 testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { 298 return builder.pushTransform(Float64List(16), oldLayer: oldLayer); 299 }); 300 testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { 301 return builder.pushClipRect(Rect.zero, oldLayer: oldLayer); 302 }); 303 testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { 304 return builder.pushClipRRect(RRect.zero, oldLayer: oldLayer); 305 }); 306 testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { 307 return builder.pushClipPath(Path(), oldLayer: oldLayer); 308 }); 309 testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { 310 return builder.pushOpacity(100, oldLayer: oldLayer); 311 }); 312 testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { 313 return builder.pushBackdropFilter(ImageFilter.blur(), oldLayer: oldLayer); 314 }); 315 testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { 316 return builder.pushShaderMask( 317 Gradient.radial( 318 const Offset(0, 0), 319 10, 320 const <Color>[Color.fromARGB(0, 0, 0, 0), Color.fromARGB(0, 255, 255, 255)], 321 ), 322 Rect.zero, 323 BlendMode.color, 324 oldLayer: oldLayer, 325 ); 326 }); 327 testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { 328 return builder.pushPhysicalShape(path: Path(), color: const Color.fromARGB(0, 0, 0, 0), oldLayer: oldLayer); 329 }); 330 testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { 331 return builder.pushColorFilter( 332 const ColorFilter.mode( 333 Color.fromARGB(0, 0, 0, 0), 334 BlendMode.color, 335 ), 336 oldLayer: oldLayer, 337 ); 338 }); 339 testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { 340 return builder.pushColorFilter( 341 const ColorFilter.matrix(<double>[ 342 1, 0, 0, 0, 0, 343 0, 1, 0, 0, 0, 344 0, 0, 1, 0, 0, 345 0, 0, 0, 1, 0, 346 ]), 347 oldLayer: oldLayer, 348 ); 349 }); 350 testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { 351 return builder.pushColorFilter( 352 const ColorFilter.linearToSrgbGamma(), 353 oldLayer: oldLayer, 354 ); 355 }); 356 testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { 357 return builder.pushColorFilter( 358 const ColorFilter.srgbToLinearGamma(), 359 oldLayer: oldLayer, 360 ); 361 }); 362 }); 363} 364 365typedef _TestNoSharingFunction = EngineLayer Function(SceneBuilder builder, EngineLayer oldLayer); 366