release previous canvas when picture is not painted (#18404)
diff --git a/lib/web_ui/lib/src/engine/surface/picture.dart b/lib/web_ui/lib/src/engine/surface/picture.dart
index 9ecb9cf..6e37f57 100644
--- a/lib/web_ui/lib/src/engine/surface/picture.dart
+++ b/lib/web_ui/lib/src/engine/surface/picture.dart
@@ -557,6 +557,7 @@
// that won't be useful for anything anyway.
_recycleCanvas(oldCanvas);
domRenderer.clearDom(rootElement);
+ _canvas = null;
return;
}
diff --git a/lib/web_ui/test/engine/surface/scene_builder_test.dart b/lib/web_ui/test/engine/surface/scene_builder_test.dart
index 6006b2f..79db571 100644
--- a/lib/web_ui/test/engine/surface/scene_builder_test.dart
+++ b/lib/web_ui/test/engine/surface/scene_builder_test.dart
@@ -239,6 +239,14 @@
});
});
+ PersistedPicture findPictureSurfaceChild(PersistedContainerSurface parent) {
+ PersistedPicture pictureSurface;
+ parent.visitChildren((PersistedSurface child) {
+ pictureSurface = child;
+ });
+ return pictureSurface;
+ }
+
test('skips painting picture when picture fully clipped out', () async {
final Picture picture = _drawPicture();
@@ -256,14 +264,65 @@
{
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
builder.pushOffset(0, 0);
- builder.pushClipRect(const Rect.fromLTRB(1000, 1000, 2000, 2000));
+ final PersistedContainerSurface clip = builder.pushClipRect(const Rect.fromLTRB(1000, 1000, 2000, 2000)) as PersistedContainerSurface;
builder.addPicture(Offset.zero, picture);
builder.pop();
builder.pop();
html.HtmlElement content = builder.build().webOnlyRootElement;
expect(content.querySelectorAll('flt-picture').single.children, isEmpty);
+ expect(findPictureSurfaceChild(clip).debugCanvas, isNull);
}
});
+
+ test('releases old canvas when picture is fully clipped out after addRetained', () async {
+ final Picture picture = _drawPicture();
+
+ // Frame 1: picture visible
+ final SurfaceSceneBuilder builder1 = SurfaceSceneBuilder();
+ final PersistedOffset offset1 = builder1.pushOffset(0, 0) as PersistedOffset;
+ builder1.addPicture(Offset.zero, picture);
+ builder1.pop();
+ html.HtmlElement content1 = builder1.build().webOnlyRootElement;
+ expect(content1.querySelectorAll('flt-picture').single.children, isNotEmpty);
+ expect(findPictureSurfaceChild(offset1).debugCanvas, isNotNull);
+
+ // Frame 2: picture is clipped out after an update
+ final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
+ final PersistedOffset offset2 = builder2.pushOffset(-10000, -10000, oldLayer: offset1);
+ builder2.addPicture(Offset.zero, picture);
+ builder2.pop();
+ html.HtmlElement content = builder2.build().webOnlyRootElement;
+ expect(content.querySelectorAll('flt-picture').single.children, isEmpty);
+ expect(findPictureSurfaceChild(offset2).debugCanvas, isNull);
+ });
+
+ test('releases old canvas when picture is fully clipped out after addRetained', () async {
+ final Picture picture = _drawPicture();
+
+ // Frame 1: picture visible
+ final SurfaceSceneBuilder builder1 = SurfaceSceneBuilder();
+ final PersistedOffset offset1 = builder1.pushOffset(0, 0) as PersistedOffset;
+ final PersistedOffset subOffset1 = builder1.pushOffset(0, 0) as PersistedOffset;
+ builder1.addPicture(Offset.zero, picture);
+ builder1.pop();
+ builder1.pop();
+ html.HtmlElement content1 = builder1.build().webOnlyRootElement;
+ expect(content1.querySelectorAll('flt-picture').single.children, isNotEmpty);
+ expect(findPictureSurfaceChild(subOffset1).debugCanvas, isNotNull);
+
+ // Frame 2: picture is clipped out after addRetained
+ final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
+ builder2.pushOffset(-10000, -10000, oldLayer: offset1);
+
+ // Even though the child offset is added as retained, the parent
+ // is updated with a value that causes the picture to move out of
+ // the clipped area. We should see the canvas being released.
+ builder2.addRetained(subOffset1);
+ builder2.pop();
+ html.HtmlElement content = builder2.build().webOnlyRootElement;
+ expect(content.querySelectorAll('flt-picture').single.children, isEmpty);
+ expect(findPictureSurfaceChild(subOffset1).debugCanvas, isNull);
+ });
}
typedef TestLayerBuilder = EngineLayer Function(