mirror of
https://github.com/alibaba/flutter-go.git
synced 2025-07-14 09:54:27 +08:00
294 lines
9.5 KiB
Dart
294 lines
9.5 KiB
Dart
// Copyright 2019 The Chromium 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:html' as html;
|
|
|
|
import 'package:flutter_web_ui/src/engine.dart';
|
|
import 'package:flutter_web_ui/ui.dart';
|
|
|
|
import 'package:test/test.dart';
|
|
|
|
import 'matchers.dart';
|
|
|
|
void main() {
|
|
group('SceneBuilder', () {
|
|
test('pushOffset implements surface lifecycle', () {
|
|
testLayerLifeCycle((SceneBuilder sceneBuilder, Object paintedBy) {
|
|
return sceneBuilder.pushOffset(10, 20, webOnlyPaintedBy: paintedBy);
|
|
}, () {
|
|
return '''<s><flt-offset></flt-offset></s>''';
|
|
});
|
|
});
|
|
|
|
test('pushTransform implements surface lifecycle', () {
|
|
testLayerLifeCycle((SceneBuilder sceneBuilder, Object paintedBy) {
|
|
return sceneBuilder.pushTransform(
|
|
Matrix4.translationValues(10, 20, 0).storage,
|
|
webOnlyPaintedBy: paintedBy);
|
|
}, () {
|
|
return '''<s><flt-transform></flt-transform></s>''';
|
|
});
|
|
});
|
|
|
|
test('pushClipRect implements surface lifecycle', () {
|
|
testLayerLifeCycle((SceneBuilder sceneBuilder, Object paintedBy) {
|
|
return sceneBuilder.pushClipRect(const Rect.fromLTRB(10, 20, 30, 40),
|
|
webOnlyPaintedBy: paintedBy);
|
|
}, () {
|
|
return '''
|
|
<s>
|
|
<clip><clip-i></clip-i></clip>
|
|
</s>
|
|
''';
|
|
});
|
|
});
|
|
|
|
test('pushClipRRect implements surface lifecycle', () {
|
|
testLayerLifeCycle((SceneBuilder sceneBuilder, Object paintedBy) {
|
|
return sceneBuilder.pushClipRRect(
|
|
RRect.fromLTRBR(10, 20, 30, 40, const Radius.circular(3)),
|
|
webOnlyPaintedBy: paintedBy);
|
|
}, () {
|
|
return '''
|
|
<s>
|
|
<rclip><clip-i></clip-i></rclip>
|
|
</s>
|
|
''';
|
|
});
|
|
});
|
|
|
|
test('pushClipPath implements surface lifecycle', () {
|
|
testLayerLifeCycle((SceneBuilder sceneBuilder, Object paintedBy) {
|
|
final Path path = Path()..addRect(const Rect.fromLTRB(10, 20, 30, 40));
|
|
return sceneBuilder.pushClipPath(path, webOnlyPaintedBy: paintedBy);
|
|
}, () {
|
|
return '''
|
|
<s>
|
|
<flt-clippath>
|
|
<svg><defs><clipPath><path></path></clipPath></defs></svg>
|
|
</flt-clippath>
|
|
</s>
|
|
''';
|
|
});
|
|
});
|
|
|
|
test('pushOpacity implements surface lifecycle', () {
|
|
testLayerLifeCycle((SceneBuilder sceneBuilder, Object paintedBy) {
|
|
return sceneBuilder.pushOpacity(10, webOnlyPaintedBy: paintedBy);
|
|
}, () {
|
|
return '''<s><o></o></s>''';
|
|
});
|
|
});
|
|
|
|
test('pushPhysicalShape implements surface lifecycle', () {
|
|
testLayerLifeCycle((SceneBuilder sceneBuilder, Object paintedBy) {
|
|
final Path path = Path()..addRect(const Rect.fromLTRB(10, 20, 30, 40));
|
|
return sceneBuilder.pushPhysicalShape(
|
|
path: path,
|
|
elevation: 2,
|
|
color: const Color.fromRGBO(0, 0, 0, 1),
|
|
shadowColor: const Color.fromRGBO(0, 0, 0, 1),
|
|
webOnlyPaintedBy: paintedBy,
|
|
);
|
|
}, () {
|
|
return '''<s><pshape><clip-i></clip-i></pshape></s>''';
|
|
});
|
|
});
|
|
|
|
test('pushBackdropFilter implements surface lifecycle', () {
|
|
testLayerLifeCycle((SceneBuilder sceneBuilder, Object paintedBy) {
|
|
return sceneBuilder.pushBackdropFilter(
|
|
ImageFilter.blur(sigmaX: 1.0, sigmaY: 1.0),
|
|
webOnlyPaintedBy: paintedBy);
|
|
}, () {
|
|
return '<s><flt-backdrop>'
|
|
'<flt-backdrop-filter></flt-backdrop-filter>'
|
|
'<flt-backdrop-interior></flt-backdrop-interior>'
|
|
'</flt-backdrop></s>';
|
|
});
|
|
});
|
|
});
|
|
|
|
group('parent child lifecycle', () {
|
|
test(
|
|
'build, retain, update, and applyPaint are called the right number of times',
|
|
() {
|
|
final Object paintedBy = Object();
|
|
final PersistedScene scene1 = PersistedScene();
|
|
final PersistedClipRect clip1 =
|
|
PersistedClipRect(paintedBy, const Rect.fromLTRB(10, 10, 20, 20));
|
|
final PersistedOpacity opacity =
|
|
PersistedOpacity(paintedBy, 100, Offset.zero);
|
|
final MockPersistedPicture picture = MockPersistedPicture(paintedBy);
|
|
|
|
scene1.appendChild(clip1);
|
|
clip1.appendChild(opacity);
|
|
opacity.appendChild(picture);
|
|
|
|
expect(picture.retainCount, 0);
|
|
expect(picture.buildCount, 0);
|
|
expect(picture.updateCount, 0);
|
|
expect(picture.applyPaintCount, 0);
|
|
|
|
scene1.build();
|
|
expect(picture.retainCount, 0);
|
|
expect(picture.buildCount, 1);
|
|
expect(picture.updateCount, 0);
|
|
expect(picture.applyPaintCount, 1);
|
|
|
|
// The second scene graph retains the opacity, but not the clip. However,
|
|
// because the clip didn't change no repaints should happen.
|
|
final PersistedScene scene2 = PersistedScene();
|
|
final PersistedClipRect clip2 =
|
|
PersistedClipRect(paintedBy, const Rect.fromLTRB(10, 10, 20, 20));
|
|
scene2.appendChild(clip2);
|
|
opacity.reuseStrategy = PersistedSurfaceReuseStrategy.retain;
|
|
clip2.appendChild(opacity);
|
|
|
|
scene2.update(scene1);
|
|
expect(picture.retainCount, 1);
|
|
expect(picture.buildCount, 1);
|
|
expect(picture.updateCount, 0);
|
|
expect(picture.applyPaintCount, 1);
|
|
|
|
// The third scene graph retains the opacity, and produces a new clip.
|
|
// This should cause the picture to repaint despite being retained.
|
|
final PersistedScene scene3 = PersistedScene();
|
|
final PersistedClipRect clip3 =
|
|
PersistedClipRect(paintedBy, const Rect.fromLTRB(10, 10, 50, 50));
|
|
scene3.appendChild(clip3);
|
|
opacity.reuseStrategy = PersistedSurfaceReuseStrategy.retain;
|
|
clip3.appendChild(opacity);
|
|
|
|
scene3.update(scene2);
|
|
expect(picture.retainCount, 2);
|
|
expect(picture.buildCount, 1);
|
|
expect(picture.updateCount, 0);
|
|
expect(picture.applyPaintCount, 2);
|
|
});
|
|
});
|
|
}
|
|
|
|
typedef TestLayerBuilder = EngineLayer Function(
|
|
SceneBuilder sceneBuilder, Object paintedBy);
|
|
typedef ExpectedHtmlGetter = String Function();
|
|
|
|
void testLayerLifeCycle(
|
|
TestLayerBuilder layerBuilder, ExpectedHtmlGetter expectedHtmlGetter) {
|
|
// Force scene builder to start from scratch. This guarantees that the first
|
|
// scene starts from the "build" phase.
|
|
SceneBuilder.debugForgetFrameScene();
|
|
|
|
final Object paintedBy = Object();
|
|
|
|
// Build: builds a brand new layer.
|
|
SceneBuilder sceneBuilder = SceneBuilder();
|
|
final EngineLayer layer1 = layerBuilder(sceneBuilder, paintedBy);
|
|
final Type surfaceType = layer1.runtimeType;
|
|
sceneBuilder.pop();
|
|
|
|
SceneTester tester = SceneTester(sceneBuilder.build());
|
|
tester.expectSceneHtml(expectedHtmlGetter());
|
|
|
|
PersistedSurface findSurface() {
|
|
return enumerateSurfaces()
|
|
.where((PersistedSurface s) => s.runtimeType == surfaceType)
|
|
.single;
|
|
}
|
|
|
|
final PersistedSurface surface1 = findSurface();
|
|
final html.Element surfaceElement1 = surface1.rootElement;
|
|
|
|
// Retain: reuses a layer as is along with its DOM elements.
|
|
sceneBuilder = SceneBuilder();
|
|
sceneBuilder.addRetained(layer1);
|
|
|
|
tester = SceneTester(sceneBuilder.build());
|
|
tester.expectSceneHtml(expectedHtmlGetter());
|
|
|
|
final PersistedSurface surface2 = findSurface();
|
|
final html.Element surfaceElement2 = surface2.rootElement;
|
|
|
|
expect(surface2, same(surface1));
|
|
expect(surfaceElement2, same(surfaceElement1));
|
|
|
|
// Reuse: reuses a layer's DOM elements by matching it.
|
|
sceneBuilder = SceneBuilder();
|
|
final EngineLayer layer3 = layerBuilder(sceneBuilder, paintedBy);
|
|
sceneBuilder.pop();
|
|
expect(layer3, isNot(same(layer1)));
|
|
tester = SceneTester(sceneBuilder.build());
|
|
tester.expectSceneHtml(expectedHtmlGetter());
|
|
|
|
final PersistedSurface surface3 = findSurface();
|
|
expect(surface3, same(layer3));
|
|
final html.Element surfaceElement3 = surface3.rootElement;
|
|
expect(surface3, isNot(same(surface2)));
|
|
expect(surfaceElement3, isNotNull);
|
|
expect(surfaceElement3, same(surfaceElement2));
|
|
|
|
// Recycle: discards all the layers.
|
|
sceneBuilder = SceneBuilder();
|
|
tester = SceneTester(sceneBuilder.build());
|
|
tester.expectSceneHtml('<s></s>');
|
|
|
|
expect(surface3.rootElement, isNull); // offset3 should be recycled.
|
|
|
|
// Retain again: the framework should be able to request that a layer is added
|
|
// as retained even after it has been recycled. In this case the
|
|
// engine would "rehydrate" the layer with new DOM elements.
|
|
sceneBuilder = SceneBuilder();
|
|
sceneBuilder.addRetained(layer3);
|
|
tester = SceneTester(sceneBuilder.build());
|
|
tester.expectSceneHtml(expectedHtmlGetter());
|
|
expect(surface3.rootElement, isNotNull); // offset3 should be rehydrated.
|
|
|
|
// Make sure we clear retained surface list.
|
|
expect(debugRetainedSurfaces, isEmpty);
|
|
}
|
|
|
|
class MockPersistedPicture extends PersistedPicture {
|
|
factory MockPersistedPicture(Object paintedBy) {
|
|
final PictureRecorder recorder = PictureRecorder();
|
|
// Use the largest cull rect so that layer clips are effective. The tests
|
|
// rely on this.
|
|
recorder.beginRecording(Rect.largest)..drawPaint(Paint());
|
|
return MockPersistedPicture._(paintedBy, recorder.endRecording());
|
|
}
|
|
|
|
MockPersistedPicture._(Object paintedBy, Picture picture)
|
|
: super(paintedBy, 0, 0, picture, 0);
|
|
|
|
int retainCount = 0;
|
|
int buildCount = 0;
|
|
int updateCount = 0;
|
|
int applyPaintCount = 0;
|
|
|
|
@override
|
|
void build() {
|
|
super.build();
|
|
buildCount++;
|
|
}
|
|
|
|
@override
|
|
void retain() {
|
|
super.retain();
|
|
retainCount++;
|
|
}
|
|
|
|
@override
|
|
void applyPaint(EngineCanvas oldCanvas) {
|
|
applyPaintCount++;
|
|
}
|
|
|
|
@override
|
|
void update(PersistedPicture oldSurface) {
|
|
super.update(oldSurface);
|
|
updateCount++;
|
|
}
|
|
|
|
@override
|
|
int get bitmapPixelCount => 0;
|
|
}
|