From 065c0feaaf412a51dcee470b6d2e352750f4de94 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Fri, 7 Jan 2022 16:25:20 -0800 Subject: [PATCH] Do not crash on LeaderLayer.applyTransform after retained rendering (#96144) --- packages/flutter/lib/src/rendering/layer.dart | 31 ++++++++++++------- .../flutter/test/rendering/layers_test.dart | 30 ++++++++++++++++++ 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/packages/flutter/lib/src/rendering/layer.dart b/packages/flutter/lib/src/rendering/layer.dart index 868bd5a625..ddf5e3a39a 100644 --- a/packages/flutter/lib/src/rendering/layer.dart +++ b/packages/flutter/lib/src/rendering/layer.dart @@ -2234,7 +2234,7 @@ class LeaderLayer extends ContainerLayer { void attach(Object owner) { super.attach(owner); assert(link._leader == null); - _lastOffset = null; + assert(_debugSetLastOffset(null)); link._leader = this; } @@ -2242,7 +2242,7 @@ class LeaderLayer extends ContainerLayer { void detach() { assert(link._leader == this); link._leader = null; - _lastOffset = null; + assert(_debugSetLastOffset(null)); super.detach(); } @@ -2251,7 +2251,17 @@ class LeaderLayer extends ContainerLayer { /// This is reset to null when the layer is attached or detached, to help /// catch cases where the follower layer ends up before the leader layer, but /// not every case can be detected. - Offset? _lastOffset; + Offset? _debugLastOffset; + + bool _debugSetLastOffset(Offset? offset) { + bool result = false; + assert(() { + _debugLastOffset = offset; + result = true; + return true; + }()); + return result; + } @override bool findAnnotations(AnnotationResult result, Offset localPosition, { required bool onlyFirst }) { @@ -2261,14 +2271,14 @@ class LeaderLayer extends ContainerLayer { @override void addToScene(ui.SceneBuilder builder) { assert(offset != null); - _lastOffset = offset; - if (_lastOffset != Offset.zero) + assert(_debugSetLastOffset(offset)); + if (offset != Offset.zero) engineLayer = builder.pushTransform( - Matrix4.translationValues(_lastOffset!.dx, _lastOffset!.dy, 0.0).storage, + Matrix4.translationValues(offset.dx, offset.dy, 0.0).storage, oldLayer: _engineLayer as ui.TransformEngineLayer?, ); addChildrenToScene(builder); - if (_lastOffset != Offset.zero) + if (offset != Offset.zero) builder.pop(); } @@ -2281,9 +2291,8 @@ class LeaderLayer extends ContainerLayer { /// children. @override void applyTransform(Layer? child, Matrix4 transform) { - assert(_lastOffset != null); - if (_lastOffset != Offset.zero) - transform.translate(_lastOffset!.dx, _lastOffset!.dy); + if (offset != Offset.zero) + transform.translate(offset.dx, offset.dy); } @override @@ -2499,7 +2508,7 @@ class FollowerLayer extends ContainerLayer { 'Linked LeaderLayer anchor is not in the same layer tree as the FollowerLayer.', ); assert( - leader._lastOffset != null, + leader._debugLastOffset != null, 'LeaderLayer anchor must come before FollowerLayer in paint order, but the reverse was true.', ); diff --git a/packages/flutter/test/rendering/layers_test.dart b/packages/flutter/test/rendering/layers_test.dart index 1d9aa4a816..8f477a88bf 100644 --- a/packages/flutter/test/rendering/layers_test.dart +++ b/packages/flutter/test/rendering/layers_test.dart @@ -218,6 +218,36 @@ void main() { expect(leaderLayer.debugSubtreeNeedsAddToScene, false); }); + test('LeaderLayer.applyTransform can be called after retained rendering', () { + void expectTransform(RenderObject leader) { + final LeaderLayer leaderLayer = leader.debugLayer! as LeaderLayer; + final Matrix4 expected = Matrix4.identity() + ..translate(leaderLayer.offset.dx, leaderLayer.offset.dy); + final Matrix4 transformed = Matrix4.identity(); + leaderLayer.applyTransform(null, transformed); + expect(transformed, expected); + } + + final LayerLink link = LayerLink(); + late RenderLeaderLayer leader; + final RenderRepaintBoundary root = RenderRepaintBoundary( + child:RenderRepaintBoundary( + child: leader = RenderLeaderLayer(link: link), + ), + ); + layout(root, phase: EnginePhase.composite); + + expectTransform(leader); + + // Causes a repaint, but the LeaderLayer of RenderLeaderLayer will be added + // as retained and LeaderLayer.addChildrenToScene will not be called. + root.markNeedsPaint(); + pumpFrame(phase: EnginePhase.composite); + + // The LeaderLayer.applyTransform call shouldn't crash. + expectTransform(leader); + }); + test('depthFirstIterateChildren', () { final ContainerLayer a = ContainerLayer(); final ContainerLayer b = ContainerLayer();