diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 4a545e365ec8e9a9a1a8eefa860353287d951538..719737c8aa6924485462b7c0bf412d00c5721b5c 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -135,7 +135,7 @@ class BitmapCanvas extends EngineCanvas { } /// Setup cache for reusing DOM elements across frames. - void setElementCache(CrossFrameCache cache) { + void setElementCache(CrossFrameCache? cache) { _elementCache = cache; } diff --git a/lib/web_ui/lib/src/engine/html/picture.dart b/lib/web_ui/lib/src/engine/html/picture.dart index 2c030ea68b18601e2af00a70af62880200fbba3e..f81395f7e4a7859f827db4bc43da8970d726bd7e 100644 --- a/lib/web_ui/lib/src/engine/html/picture.dart +++ b/lib/web_ui/lib/src/engine/html/picture.dart @@ -52,6 +52,7 @@ List<_PaintRequest> _paintQueue = <_PaintRequest>[]; void _recycleCanvas(EngineCanvas? canvas) { if (canvas is BitmapCanvas) { + canvas.setElementCache(null); if (canvas.isReusable()) { _recycledCanvases.add(canvas); if (_recycledCanvases.length > _kCanvasCacheSize) { @@ -91,7 +92,7 @@ class PersistedPicture extends PersistedLeafSurface { final int hints; /// Cache for reusing elements such as images across picture updates. - CrossFrameCache _elementCache = + CrossFrameCache? _elementCache = CrossFrameCache(); @override @@ -386,6 +387,7 @@ class PersistedPicture extends PersistedLeafSurface { if (_debugShowCanvasReuseStats) { DebugCanvasReuseOverlay.instance.keptCount++; } + // Re-use old bitmap canvas. oldCanvas.bounds = _optimalLocalCullRect!; _canvas = oldCanvas; oldCanvas.setElementCache(_elementCache); @@ -395,6 +397,10 @@ class PersistedPicture extends PersistedLeafSurface { // We can't use the old canvas because the size has changed, so we put // it in a cache for later reuse. _recycleCanvas(oldCanvas); + if (_canvas is BitmapCanvas) { + (_canvas as BitmapCanvas).setElementCache(null); + } + _canvas = null; // We cannot paint immediately because not all canvases that we may be // able to reuse have been released yet. So instead we enqueue this // picture to be painted after the update cycle is done syncing the layer @@ -403,8 +409,9 @@ class PersistedPicture extends PersistedLeafSurface { canvasSize: _optimalLocalCullRect!.size, paintCallback: () { _canvas = _findOrCreateCanvas(_optimalLocalCullRect!); - assert(_canvas is BitmapCanvas && - (_canvas as BitmapCanvas?)!._elementCache == _elementCache); + if (_canvas is BitmapCanvas) { + (_canvas as BitmapCanvas).setElementCache(_elementCache); + } if (_debugExplainSurfaceStats) { final BitmapCanvas bitmapCanvas = _canvas as BitmapCanvas; _surfaceStatsFor(this).paintPixelCount += @@ -518,6 +525,9 @@ class PersistedPicture extends PersistedLeafSurface { super.update(oldSurface); // Transfer element cache over. _elementCache = oldSurface._elementCache; + if (oldSurface != this) { + oldSurface._elementCache = null; + } if (dx != oldSurface.dx || dy != oldSurface.dy) { _applyTranslate(); 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 89b358e35f7f67990e90e52f802c196324ae5439..fca7829288da2d3955f3b1e0766a56c6ffbfe95b 100644 --- a/lib/web_ui/test/engine/surface/scene_builder_test.dart +++ b/lib/web_ui/test/engine/surface/scene_builder_test.dart @@ -258,6 +258,42 @@ void testMain() { }); }); + /// Verify elementCache is passed during update to reuse existing + /// image elements. + test('Should retain same image element', () async { + final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); + final Picture picture1 = _drawPathImagePath(); + EngineLayer oldLayer = builder.pushClipRect( + const Rect.fromLTRB(10, 10, 300, 300), + ); + builder.addPicture(Offset.zero, picture1); + builder.pop(); + + html.HtmlElement content = builder.build().webOnlyRootElement; + html.document.body.append(content); + List list = content.querySelectorAll('img'); + for (html.ImageElement image in list) { + image.alt = 'marked'; + } + + // Force update to scene which will utilize reuse code path. + final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder(); + builder2.pushClipRect( + const Rect.fromLTRB(5, 10, 300, 300), + oldLayer: oldLayer + ); + final Picture picture2 = _drawPathImagePath(); + builder2.addPicture(Offset.zero, picture2); + builder2.pop(); + + html.HtmlElement contentAfterReuse = builder2.build().webOnlyRootElement; + list = contentAfterReuse.querySelectorAll('img'); + for (html.ImageElement image in list) { + expect(image.alt, 'marked'); + } + expect(list.length, 1); + }); + PersistedPicture findPictureSurfaceChild(PersistedContainerSurface parent) { PersistedPicture pictureSurface; parent.visitChildren((PersistedSurface child) {