• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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