Unverified Commit 025397ae authored by Dan Field's avatar Dan Field Committed by GitHub

Release retained resources from layers/dispose pictures (#84740)

parent 1977ee75
...@@ -524,12 +524,18 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox { ...@@ -524,12 +524,18 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox {
thumbCenterY + CupertinoThumbPainter.radius, thumbCenterY + CupertinoThumbPainter.radius,
); );
_clipRRectLayer = context.pushClipRRect(needsCompositing, Offset.zero, thumbBounds, trackRRect, (PaintingContext innerContext, Offset offset) { _clipRRectLayer.layer = context.pushClipRRect(needsCompositing, Offset.zero, thumbBounds, trackRRect, (PaintingContext innerContext, Offset offset) {
const CupertinoThumbPainter.switchThumb().paint(innerContext.canvas, thumbBounds); const CupertinoThumbPainter.switchThumb().paint(innerContext.canvas, thumbBounds);
}, oldLayer: _clipRRectLayer); }, oldLayer: _clipRRectLayer.layer);
} }
ClipRRectLayer? _clipRRectLayer; final LayerHandle<ClipRRectLayer> _clipRRectLayer = LayerHandle<ClipRRectLayer>();
@override
void dispose() {
_clipRRectLayer.layer = null;
super.dispose();
}
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder description) { void debugFillProperties(DiagnosticPropertiesBuilder description) {
......
...@@ -289,19 +289,25 @@ class _RenderCupertinoTextSelectionToolbarShape extends RenderShiftedBox { ...@@ -289,19 +289,25 @@ class _RenderCupertinoTextSelectionToolbarShape extends RenderShiftedBox {
} }
final BoxParentData childParentData = child!.parentData! as BoxParentData; final BoxParentData childParentData = child!.parentData! as BoxParentData;
_clipPathLayer = context.pushClipPath( _clipPathLayer.layer = context.pushClipPath(
needsCompositing, needsCompositing,
offset + childParentData.offset, offset + childParentData.offset,
Offset.zero & child!.size, Offset.zero & child!.size,
_clipPath(), _clipPath(),
(PaintingContext innerContext, Offset innerOffset) => innerContext.paintChild(child!, innerOffset), (PaintingContext innerContext, Offset innerOffset) => innerContext.paintChild(child!, innerOffset),
oldLayer: _clipPathLayer, oldLayer: _clipPathLayer.layer,
); );
} }
ClipPathLayer? _clipPathLayer; final LayerHandle<ClipPathLayer> _clipPathLayer = LayerHandle<ClipPathLayer>();
Paint? _debugPaint; Paint? _debugPaint;
@override
void dispose() {
_clipPathLayer.layer = null;
super.dispose();
}
@override @override
void debugPaintSize(PaintingContext context, Offset offset) { void debugPaintSize(PaintingContext context, Offset offset) {
assert(() { assert(() {
......
...@@ -1502,15 +1502,15 @@ class _RenderDecoration extends RenderBox { ...@@ -1502,15 +1502,15 @@ class _RenderDecoration extends RenderBox {
_labelTransform = Matrix4.identity() _labelTransform = Matrix4.identity()
..translate(dx, labelOffset.dy + dy) ..translate(dx, labelOffset.dy + dy)
..scale(scale); ..scale(scale);
_transformLayer = context.pushTransform( layer = context.pushTransform(
needsCompositing, needsCompositing,
offset, offset,
_labelTransform!, _labelTransform!,
_paintLabel, _paintLabel,
oldLayer: _transformLayer, oldLayer: layer as TransformLayer?,
); );
} else { } else {
_transformLayer = null; layer = null;
} }
doPaint(icon); doPaint(icon);
...@@ -1524,8 +1524,6 @@ class _RenderDecoration extends RenderBox { ...@@ -1524,8 +1524,6 @@ class _RenderDecoration extends RenderBox {
doPaint(counter); doPaint(counter);
} }
TransformLayer? _transformLayer;
@override @override
bool hitTestSelf(Offset position) => true; bool hitTestSelf(Offset position) => true;
......
...@@ -327,19 +327,25 @@ class RenderAnimatedSize extends RenderAligningShiftedBox { ...@@ -327,19 +327,25 @@ class RenderAnimatedSize extends RenderAligningShiftedBox {
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
if (child != null && _hasVisualOverflow && clipBehavior != Clip.none) { if (child != null && _hasVisualOverflow && clipBehavior != Clip.none) {
final Rect rect = Offset.zero & size; final Rect rect = Offset.zero & size;
_clipRectLayer = context.pushClipRect( _clipRectLayer.layer = context.pushClipRect(
needsCompositing, needsCompositing,
offset, offset,
rect, rect,
super.paint, super.paint,
clipBehavior: clipBehavior, clipBehavior: clipBehavior,
oldLayer: _clipRectLayer, oldLayer: _clipRectLayer.layer,
); );
} else { } else {
_clipRectLayer = null; _clipRectLayer.layer = null;
super.paint(context, offset); super.paint(context, offset);
} }
} }
ClipRectLayer? _clipRectLayer; final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
} }
...@@ -301,6 +301,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, ...@@ -301,6 +301,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
_foregroundRenderObject = null; _foregroundRenderObject = null;
_backgroundRenderObject?.dispose(); _backgroundRenderObject?.dispose();
_backgroundRenderObject = null; _backgroundRenderObject = null;
_clipRectLayer.layer = null;
_cachedBuiltInForegroundPainters?.dispose(); _cachedBuiltInForegroundPainters?.dispose();
_cachedBuiltInPainters?.dispose(); _cachedBuiltInPainters?.dispose();
super.dispose(); super.dispose();
...@@ -4007,22 +4008,22 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, ...@@ -4007,22 +4008,22 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
_computeTextMetricsIfNeeded(); _computeTextMetricsIfNeeded();
if (_hasVisualOverflow && clipBehavior != Clip.none) { if (_hasVisualOverflow && clipBehavior != Clip.none) {
_clipRectLayer = context.pushClipRect( _clipRectLayer.layer = context.pushClipRect(
needsCompositing, needsCompositing,
offset, offset,
Offset.zero & size, Offset.zero & size,
_paintContents, _paintContents,
clipBehavior: clipBehavior, clipBehavior: clipBehavior,
oldLayer: _clipRectLayer, oldLayer: _clipRectLayer.layer,
); );
} else { } else {
_clipRectLayer = null; _clipRectLayer.layer = null;
_paintContents(context, offset); _paintContents(context, offset);
} }
_paintHandleLayers(context, getEndpointsForSelection(selection!)); _paintHandleLayers(context, getEndpointsForSelection(selection!));
} }
ClipRectLayer? _clipRectLayer; final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override @override
Rect? describeApproximatePaintClip(RenderObject child) => _hasVisualOverflow ? Offset.zero & size : null; Rect? describeApproximatePaintClip(RenderObject child) => _hasVisualOverflow ? Offset.zero & size : null;
......
...@@ -1084,17 +1084,17 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -1084,17 +1084,17 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
return; return;
if (clipBehavior == Clip.none) { if (clipBehavior == Clip.none) {
_clipRectLayer = null; _clipRectLayer.layer = null;
defaultPaint(context, offset); defaultPaint(context, offset);
} else { } else {
// We have overflow and the clipBehavior isn't none. Clip it. // We have overflow and the clipBehavior isn't none. Clip it.
_clipRectLayer = context.pushClipRect( _clipRectLayer.layer = context.pushClipRect(
needsCompositing, needsCompositing,
offset, offset,
Offset.zero & size, Offset.zero & size,
defaultPaint, defaultPaint,
clipBehavior: clipBehavior, clipBehavior: clipBehavior,
oldLayer: _clipRectLayer, oldLayer: _clipRectLayer.layer,
); );
} }
...@@ -1141,7 +1141,13 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -1141,7 +1141,13 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
}()); }());
} }
ClipRectLayer? _clipRectLayer; final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
@override @override
Rect? describeApproximatePaintClip(RenderObject child) => _hasOverflow ? Offset.zero & size : null; Rect? describeApproximatePaintClip(RenderObject child) => _hasOverflow ? Offset.zero & size : null;
......
...@@ -389,21 +389,27 @@ class RenderFlow extends RenderBox ...@@ -389,21 +389,27 @@ class RenderFlow extends RenderBox
@override @override
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
if (clipBehavior == Clip.none) { if (clipBehavior == Clip.none) {
_clipRectLayer = null; _clipRectLayer.layer = null;
_paintWithDelegate(context, offset); _paintWithDelegate(context, offset);
} else { } else {
_clipRectLayer = context.pushClipRect( _clipRectLayer.layer = context.pushClipRect(
needsCompositing, needsCompositing,
offset, offset,
Offset.zero & size, Offset.zero & size,
_paintWithDelegate, _paintWithDelegate,
clipBehavior: clipBehavior, clipBehavior: clipBehavior,
oldLayer: _clipRectLayer, oldLayer: _clipRectLayer.layer,
); );
} }
} }
ClipRectLayer? _clipRectLayer; final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
@override @override
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) { bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
......
...@@ -785,22 +785,28 @@ class RenderListWheelViewport ...@@ -785,22 +785,28 @@ class RenderListWheelViewport
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
if (childCount > 0) { if (childCount > 0) {
if (_shouldClipAtCurrentOffset() && clipBehavior != Clip.none) { if (_shouldClipAtCurrentOffset() && clipBehavior != Clip.none) {
_clipRectLayer = context.pushClipRect( _clipRectLayer.layer = context.pushClipRect(
needsCompositing, needsCompositing,
offset, offset,
Offset.zero & size, Offset.zero & size,
_paintVisibleChildren, _paintVisibleChildren,
clipBehavior: clipBehavior, clipBehavior: clipBehavior,
oldLayer: _clipRectLayer, oldLayer: _clipRectLayer.layer,
); );
} else { } else {
_clipRectLayer = null; _clipRectLayer.layer = null;
_paintVisibleChildren(context, offset); _paintVisibleChildren(context, offset);
} }
} }
} }
ClipRectLayer? _clipRectLayer; final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
/// Paints all children visible in the current viewport. /// Paints all children visible in the current viewport.
void _paintVisibleChildren(PaintingContext context, Offset offset) { void _paintVisibleChildren(PaintingContext context, Offset offset) {
......
...@@ -117,30 +117,32 @@ class PaintingContext extends ClipContext { ...@@ -117,30 +117,32 @@ class PaintingContext extends ClipContext {
); );
return true; return true;
}()); }());
OffsetLayer? childLayer = child._layer as OffsetLayer?; OffsetLayer? childLayer = child._layerHandle.layer as OffsetLayer?;
if (childLayer == null) { if (childLayer == null) {
assert(debugAlsoPaintedParent); assert(debugAlsoPaintedParent);
assert(child._layerHandle.layer == null);
// Not using the `layer` setter because the setter asserts that we not // Not using the `layer` setter because the setter asserts that we not
// replace the layer for repaint boundaries. That assertion does not // replace the layer for repaint boundaries. That assertion does not
// apply here because this is exactly the place designed to create a // apply here because this is exactly the place designed to create a
// layer for repaint boundaries. // layer for repaint boundaries.
child._layer = childLayer = OffsetLayer(); final OffsetLayer layer = OffsetLayer();
child._layerHandle.layer = childLayer = layer;
} else { } else {
assert(debugAlsoPaintedParent || childLayer.attached); assert(debugAlsoPaintedParent || childLayer.attached);
childLayer.removeAllChildren(); childLayer.removeAllChildren();
} }
assert(identical(childLayer, child._layer)); assert(identical(childLayer, child._layerHandle.layer));
assert(child._layer is OffsetLayer); assert(child._layerHandle.layer is OffsetLayer);
assert(() { assert(() {
child._layer!.debugCreator = child.debugCreator ?? child.runtimeType; childLayer!.debugCreator = child.debugCreator ?? child.runtimeType;
return true; return true;
}()); }());
childContext ??= PaintingContext(child._layer!, child.paintBounds); childContext ??= PaintingContext(childLayer, child.paintBounds);
child._paintWithContext(childContext, Offset.zero); child._paintWithContext(childContext, Offset.zero);
// Double-check that the paint method did not replace the layer (the first // Double-check that the paint method did not replace the layer (the first
// check is done in the [layer] setter itself). // check is done in the [layer] setter itself).
assert(identical(childLayer, child._layer)); assert(identical(childLayer, child._layerHandle.layer));
childContext.stopRecordingIfNeeded(); childContext.stopRecordingIfNeeded();
} }
...@@ -209,14 +211,14 @@ class PaintingContext extends ClipContext { ...@@ -209,14 +211,14 @@ class PaintingContext extends ClipContext {
includedParent: true, includedParent: true,
includedChild: false, includedChild: false,
); );
child._layer!.debugCreator = child.debugCreator ?? child; child._layerHandle.layer!.debugCreator = child.debugCreator ?? child;
return true; return true;
}()); }());
} }
assert(child._layer is OffsetLayer); assert(child._layerHandle.layer is OffsetLayer);
final OffsetLayer childOffsetLayer = child._layer! as OffsetLayer; final OffsetLayer childOffsetLayer = child._layerHandle.layer! as OffsetLayer;
childOffsetLayer.offset = offset; childOffsetLayer.offset = offset;
appendLayer(child._layer!); appendLayer(childOffsetLayer);
} }
/// Adds a layer to the recording requiring that the recording is already /// Adds a layer to the recording requiring that the recording is already
...@@ -266,6 +268,7 @@ class PaintingContext extends ClipContext { ...@@ -266,6 +268,7 @@ class PaintingContext extends ClipContext {
Canvas get canvas { Canvas get canvas {
if (_canvas == null) if (_canvas == null)
_startRecording(); _startRecording();
assert(_currentLayer != null);
return _canvas!; return _canvas!;
} }
...@@ -391,6 +394,7 @@ class PaintingContext extends ClipContext { ...@@ -391,6 +394,7 @@ class PaintingContext extends ClipContext {
stopRecordingIfNeeded(); stopRecordingIfNeeded();
appendLayer(childLayer); appendLayer(childLayer);
final PaintingContext childContext = createChildContext(childLayer, childPaintBounds ?? estimatedBounds); final PaintingContext childContext = createChildContext(childLayer, childPaintBounds ?? estimatedBounds);
painter(childContext, offset); painter(childContext, offset);
childContext.stopRecordingIfNeeded(); childContext.stopRecordingIfNeeded();
} }
...@@ -969,9 +973,9 @@ class PipelineOwner { ...@@ -969,9 +973,9 @@ class PipelineOwner {
_nodesNeedingPaint = <RenderObject>[]; _nodesNeedingPaint = <RenderObject>[];
// Sort the dirty nodes in reverse order (deepest first). // Sort the dirty nodes in reverse order (deepest first).
for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) { for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
assert(node._layer != null); assert(node._layerHandle.layer != null);
if (node._needsPaint && node.owner == this) { if (node._needsPaint && node.owner == this) {
if (node._layer!.attached) { if (node._layerHandle.layer!.attached) {
PaintingContext.repaintCompositedChild(node); PaintingContext.repaintCompositedChild(node);
} else { } else {
node._skippedPaintingOnLayer(); node._skippedPaintingOnLayer();
...@@ -1274,7 +1278,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -1274,7 +1278,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
@mustCallSuper @mustCallSuper
void dispose() { void dispose() {
assert(!_debugDisposed); assert(!_debugDisposed);
_layer = null; _layerHandle.layer = null;
assert(() { assert(() {
// TODO(dnfield): Enable this assert once clients have had a chance to // TODO(dnfield): Enable this assert once clients have had a chance to
// migrate. // migrate.
...@@ -1478,7 +1482,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -1478,7 +1482,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
_needsCompositingBitsUpdate = false; _needsCompositingBitsUpdate = false;
markNeedsCompositingBitsUpdate(); markNeedsCompositingBitsUpdate();
} }
if (_needsPaint && _layer != null) { if (_needsPaint && _layerHandle.layer != null) {
// Don't enter this block if we've never painted at all; // Don't enter this block if we've never painted at all;
// scheduleInitialPaint() will handle it // scheduleInitialPaint() will handle it
_needsPaint = false; _needsPaint = false;
...@@ -2050,14 +2054,22 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -2050,14 +2054,22 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
/// never populating this field, or by setting it to null when the value of /// never populating this field, or by setting it to null when the value of
/// [needsCompositing] changes from true to false. /// [needsCompositing] changes from true to false.
/// ///
/// If a new layer is created and stored in some other field on the render
/// object, the render object must use a [LayerHandle] to store it. A layer
/// handle will prevent the layer from being disposed before the render
/// object is finished with it, and it will also make sure that the layer
/// gets appropriately disposed when the render object creates a replacement
/// or nulls it out. The render object must null out the [LayerHandle.layer]
/// in its [dispose] method.
///
/// If this render object is a repaint boundary, the framework automatically /// If this render object is a repaint boundary, the framework automatically
/// creates an [OffsetLayer] and populates this field prior to calling the /// creates an [OffsetLayer] and populates this field prior to calling the
/// [paint] method. The [paint] method must not replace the value of this /// [paint] method. The [paint] method must not replace the value of this
/// field. /// field.
@protected @protected
ContainerLayer? get layer { ContainerLayer? get layer {
assert(!isRepaintBoundary || (_layer == null || _layer is OffsetLayer)); assert(!isRepaintBoundary || _layerHandle.layer == null || _layerHandle.layer is OffsetLayer);
return _layer; return _layerHandle.layer;
} }
@protected @protected
...@@ -2068,9 +2080,10 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -2068,9 +2080,10 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
'The framework creates and assigns an OffsetLayer to a repaint ' 'The framework creates and assigns an OffsetLayer to a repaint '
'boundary automatically.', 'boundary automatically.',
); );
_layer = newLayer; _layerHandle.layer = newLayer;
} }
ContainerLayer? _layer;
final LayerHandle<ContainerLayer> _layerHandle = LayerHandle<ContainerLayer>();
/// In debug mode, the compositing layer that this render object uses to repaint. /// In debug mode, the compositing layer that this render object uses to repaint.
/// ///
...@@ -2082,7 +2095,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -2082,7 +2095,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
ContainerLayer? get debugLayer { ContainerLayer? get debugLayer {
ContainerLayer? result; ContainerLayer? result;
assert(() { assert(() {
result = _layer; result = _layerHandle.layer;
return true; return true;
}()); }());
return result; return result;
...@@ -2218,7 +2231,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -2218,7 +2231,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
}()); }());
// If we always have our own layer, then we can just repaint // If we always have our own layer, then we can just repaint
// ourselves without involving any other nodes. // ourselves without involving any other nodes.
assert(_layer is OffsetLayer); assert(_layerHandle.layer is OffsetLayer);
if (owner != null) { if (owner != null) {
owner!._nodesNeedingPaint.add(this); owner!._nodesNeedingPaint.add(this);
owner!.requestVisualUpdate(); owner!.requestVisualUpdate();
...@@ -2251,14 +2264,14 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -2251,14 +2264,14 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
assert(attached); assert(attached);
assert(isRepaintBoundary); assert(isRepaintBoundary);
assert(_needsPaint); assert(_needsPaint);
assert(_layer != null); assert(_layerHandle.layer != null);
assert(!_layer!.attached); assert(!_layerHandle.layer!.attached);
AbstractNode? node = parent; AbstractNode? node = parent;
while (node is RenderObject) { while (node is RenderObject) {
if (node.isRepaintBoundary) { if (node.isRepaintBoundary) {
if (node._layer == null) if (node._layerHandle.layer == null)
break; // looks like the subtree here has never been painted. let it handle itself. break; // looks like the subtree here has never been painted. let it handle itself.
if (node._layer!.attached) if (node._layerHandle.layer!.attached)
break; // it's the one that detached us, so it's the one that will decide to repaint us. break; // it's the one that detached us, so it's the one that will decide to repaint us.
node._needsPaint = true; node._needsPaint = true;
} }
...@@ -2278,8 +2291,8 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -2278,8 +2291,8 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
assert(parent is! RenderObject); assert(parent is! RenderObject);
assert(!owner!._debugDoingPaint); assert(!owner!._debugDoingPaint);
assert(isRepaintBoundary); assert(isRepaintBoundary);
assert(_layer == null); assert(_layerHandle.layer == null);
_layer = rootLayer; _layerHandle.layer = rootLayer;
assert(_needsPaint); assert(_needsPaint);
owner!._nodesNeedingPaint.add(this); owner!._nodesNeedingPaint.add(this);
} }
...@@ -2296,9 +2309,9 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -2296,9 +2309,9 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
assert(parent is! RenderObject); assert(parent is! RenderObject);
assert(!owner!._debugDoingPaint); assert(!owner!._debugDoingPaint);
assert(isRepaintBoundary); assert(isRepaintBoundary);
assert(_layer != null); // use scheduleInitialPaint the first time assert(_layerHandle.layer != null); // use scheduleInitialPaint the first time
_layer!.detach(); _layerHandle.layer!.detach();
_layer = rootLayer; _layerHandle.layer = rootLayer;
markNeedsPaint(); markNeedsPaint();
} }
...@@ -2388,7 +2401,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -2388,7 +2401,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
_debugDoingThisPaint = true; _debugDoingThisPaint = true;
debugLastActivePaint = _debugActivePaint; debugLastActivePaint = _debugActivePaint;
_debugActivePaint = this; _debugActivePaint = this;
assert(!isRepaintBoundary || _layer != null); assert(!isRepaintBoundary || _layerHandle.layer != null);
return true; return true;
}()); }());
_needsPaint = false; _needsPaint = false;
...@@ -2983,7 +2996,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -2983,7 +2996,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
properties.add(DiagnosticsProperty<ParentData>('parentData', parentData, tooltip: _debugCanParentUseSize == true ? 'can use size' : null, missingIfNull: true)); properties.add(DiagnosticsProperty<ParentData>('parentData', parentData, tooltip: _debugCanParentUseSize == true ? 'can use size' : null, missingIfNull: true));
properties.add(DiagnosticsProperty<Constraints>('constraints', _constraints, missingIfNull: true)); properties.add(DiagnosticsProperty<Constraints>('constraints', _constraints, missingIfNull: true));
// don't access it via the "layer" getter since that's only valid when we don't need paint // don't access it via the "layer" getter since that's only valid when we don't need paint
properties.add(DiagnosticsProperty<ContainerLayer>('layer', _layer, defaultValue: null)); properties.add(DiagnosticsProperty<ContainerLayer>('layer', _layerHandle.layer, defaultValue: null));
properties.add(DiagnosticsProperty<SemanticsNode>('semantics node', _semantics, defaultValue: null)); properties.add(DiagnosticsProperty<SemanticsNode>('semantics node', _semantics, defaultValue: null));
properties.add(FlagProperty( properties.add(FlagProperty(
'isBlockingSemanticsOfPreviouslyPaintedNodes', 'isBlockingSemanticsOfPreviouslyPaintedNodes',
......
...@@ -209,21 +209,27 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin { ...@@ -209,21 +209,27 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
// Clip the texture if it's going to paint out of the bounds of the renter box // Clip the texture if it's going to paint out of the bounds of the renter box
// (see comment in _paintTexture for an explanation of when this happens). // (see comment in _paintTexture for an explanation of when this happens).
if ((size.width < _currentAndroidViewSize.width || size.height < _currentAndroidViewSize.height) && clipBehavior != Clip.none) { if ((size.width < _currentAndroidViewSize.width || size.height < _currentAndroidViewSize.height) && clipBehavior != Clip.none) {
_clipRectLayer = context.pushClipRect( _clipRectLayer.layer = context.pushClipRect(
true, true,
offset, offset,
offset & size, offset & size,
_paintTexture, _paintTexture,
clipBehavior: clipBehavior, clipBehavior: clipBehavior,
oldLayer: _clipRectLayer, oldLayer: _clipRectLayer.layer,
); );
return; return;
} }
_clipRectLayer = null; _clipRectLayer.layer = null;
_paintTexture(context, offset); _paintTexture(context, offset);
} }
ClipRectLayer? _clipRectLayer; final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
void _paintTexture(PaintingContext context, Offset offset) { void _paintTexture(PaintingContext context, Offset offset) {
// As resizing the Android view happens asynchronously we don't know exactly when is a // As resizing the Android view happens asynchronously we don't know exactly when is a
......
...@@ -117,19 +117,25 @@ class RenderRotatedBox extends RenderBox with RenderObjectWithChildMixin<RenderB ...@@ -117,19 +117,25 @@ class RenderRotatedBox extends RenderBox with RenderObjectWithChildMixin<RenderB
@override @override
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
if (child != null) { if (child != null) {
_transformLayer = context.pushTransform( _transformLayer.layer = context.pushTransform(
needsCompositing, needsCompositing,
offset, offset,
_paintTransform!, _paintTransform!,
_paintChild, _paintChild,
oldLayer: _transformLayer, oldLayer: _transformLayer.layer,
); );
} else { } else {
_transformLayer = null; _transformLayer.layer = null;
} }
} }
TransformLayer? _transformLayer; final LayerHandle<TransformLayer> _transformLayer = LayerHandle<TransformLayer>();
@override
void dispose() {
_transformLayer.layer = null;
super.dispose();
}
@override @override
void applyPaintTransform(RenderBox child, Matrix4 transform) { void applyPaintTransform(RenderBox child, Matrix4 transform) {
......
...@@ -801,17 +801,17 @@ class RenderConstraintsTransformBox extends RenderAligningShiftedBox with DebugO ...@@ -801,17 +801,17 @@ class RenderConstraintsTransformBox extends RenderAligningShiftedBox with DebugO
} }
if (clipBehavior == Clip.none) { if (clipBehavior == Clip.none) {
_clipRectLayer = null; _clipRectLayer.layer = null;
super.paint(context, offset); super.paint(context, offset);
} else { } else {
// We have overflow and the clipBehavior isn't none. Clip it. // We have overflow and the clipBehavior isn't none. Clip it.
_clipRectLayer = context.pushClipRect( _clipRectLayer.layer = context.pushClipRect(
needsCompositing, needsCompositing,
offset, offset,
Offset.zero & size, Offset.zero & size,
super.paint, super.paint,
clipBehavior: clipBehavior, clipBehavior: clipBehavior,
oldLayer:_clipRectLayer, oldLayer: _clipRectLayer.layer,
); );
} }
...@@ -822,7 +822,13 @@ class RenderConstraintsTransformBox extends RenderAligningShiftedBox with DebugO ...@@ -822,7 +822,13 @@ class RenderConstraintsTransformBox extends RenderAligningShiftedBox with DebugO
}()); }());
} }
ClipRectLayer? _clipRectLayer; final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
@override @override
Rect? describeApproximatePaintClip(RenderObject child) { Rect? describeApproximatePaintClip(RenderObject child) {
......
...@@ -632,21 +632,27 @@ class RenderStack extends RenderBox ...@@ -632,21 +632,27 @@ class RenderStack extends RenderBox
@override @override
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
if (clipBehavior != Clip.none && _hasVisualOverflow) { if (clipBehavior != Clip.none && _hasVisualOverflow) {
_clipRectLayer = context.pushClipRect( _clipRectLayer.layer = context.pushClipRect(
needsCompositing, needsCompositing,
offset, offset,
Offset.zero & size, Offset.zero & size,
paintStack, paintStack,
clipBehavior: clipBehavior, clipBehavior: clipBehavior,
oldLayer: _clipRectLayer, oldLayer: _clipRectLayer.layer,
); );
} else { } else {
_clipRectLayer = null; _clipRectLayer.layer = null;
paintStack(context, offset); paintStack(context, offset);
} }
} }
ClipRectLayer? _clipRectLayer; final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
@override @override
Rect? describeApproximatePaintClip(RenderObject child) => _hasVisualOverflow ? Offset.zero & size : null; Rect? describeApproximatePaintClip(RenderObject child) => _hasVisualOverflow ? Offset.zero & size : null;
......
...@@ -632,21 +632,27 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix ...@@ -632,21 +632,27 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
if (firstChild == null) if (firstChild == null)
return; return;
if (hasVisualOverflow && clipBehavior != Clip.none) { if (hasVisualOverflow && clipBehavior != Clip.none) {
_clipRectLayer = context.pushClipRect( _clipRectLayer.layer = context.pushClipRect(
needsCompositing, needsCompositing,
offset, offset,
Offset.zero & size, Offset.zero & size,
_paintContents, _paintContents,
clipBehavior: clipBehavior, clipBehavior: clipBehavior,
oldLayer: _clipRectLayer, oldLayer: _clipRectLayer.layer,
); );
} else { } else {
_clipRectLayer = null; _clipRectLayer.layer = null;
_paintContents(context, offset); _paintContents(context, offset);
} }
} }
ClipRectLayer? _clipRectLayer; final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
void _paintContents(PaintingContext context, Offset offset) { void _paintContents(PaintingContext context, Offset offset) {
for (final RenderSliver child in childrenInPaintOrder) { for (final RenderSliver child in childrenInPaintOrder) {
......
...@@ -761,21 +761,27 @@ class RenderWrap extends RenderBox ...@@ -761,21 +761,27 @@ class RenderWrap extends RenderBox
// TODO(ianh): move the debug flex overflow paint logic somewhere common so // TODO(ianh): move the debug flex overflow paint logic somewhere common so
// it can be reused here // it can be reused here
if (_hasVisualOverflow && clipBehavior != Clip.none) { if (_hasVisualOverflow && clipBehavior != Clip.none) {
_clipRectLayer = context.pushClipRect( _clipRectLayer.layer = context.pushClipRect(
needsCompositing, needsCompositing,
offset, offset,
Offset.zero & size, Offset.zero & size,
defaultPaint, defaultPaint,
clipBehavior: clipBehavior, clipBehavior: clipBehavior,
oldLayer: _clipRectLayer, oldLayer: _clipRectLayer.layer,
); );
} else { } else {
_clipRectLayer = null; _clipRectLayer.layer = null;
defaultPaint(context, offset); defaultPaint(context, offset);
} }
} }
ClipRectLayer? _clipRectLayer; final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
......
...@@ -787,21 +787,27 @@ class _RenderTheatre extends RenderBox with ContainerRenderObjectMixin<RenderBox ...@@ -787,21 +787,27 @@ class _RenderTheatre extends RenderBox with ContainerRenderObjectMixin<RenderBox
@override @override
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
if (_hasVisualOverflow && clipBehavior != Clip.none) { if (_hasVisualOverflow && clipBehavior != Clip.none) {
_clipRectLayer = context.pushClipRect( _clipRectLayer.layer = context.pushClipRect(
needsCompositing, needsCompositing,
offset, offset,
Offset.zero & size, Offset.zero & size,
paintStack, paintStack,
clipBehavior: clipBehavior, clipBehavior: clipBehavior,
oldLayer: _clipRectLayer, oldLayer: _clipRectLayer.layer,
); );
} else { } else {
_clipRectLayer = null; _clipRectLayer.layer = null;
paintStack(context, offset); paintStack(context, offset);
} }
} }
ClipRectLayer? _clipRectLayer; final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
@override @override
void visitChildrenForSemantics(RenderObjectVisitor visitor) { void visitChildrenForSemantics(RenderObjectVisitor visitor) {
......
...@@ -620,22 +620,28 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix ...@@ -620,22 +620,28 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
} }
if (_shouldClipAtPaintOffset(paintOffset) && clipBehavior != Clip.none) { if (_shouldClipAtPaintOffset(paintOffset) && clipBehavior != Clip.none) {
_clipRectLayer = context.pushClipRect( _clipRectLayer.layer = context.pushClipRect(
needsCompositing, needsCompositing,
offset, offset,
Offset.zero & size, Offset.zero & size,
paintContents, paintContents,
clipBehavior: clipBehavior, clipBehavior: clipBehavior,
oldLayer: _clipRectLayer, oldLayer: _clipRectLayer.layer,
); );
} else { } else {
_clipRectLayer = null; _clipRectLayer.layer = null;
paintContents(context, offset); paintContents(context, offset);
} }
} }
} }
ClipRectLayer? _clipRectLayer; final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
@override @override
void applyPaintTransform(RenderBox child, Matrix4 transform) { void applyPaintTransform(RenderBox child, Matrix4 transform) {
......
...@@ -262,6 +262,7 @@ void main() { ...@@ -262,6 +262,7 @@ void main() {
r' owner: RenderView#[0-9a-f]{5}\n' r' owner: RenderView#[0-9a-f]{5}\n'
r' creator: RenderView\n' r' creator: RenderView\n'
r' engine layer: (TransformEngineLayer|PersistedTransform)#[0-9a-f]{5}\n' r' engine layer: (TransformEngineLayer|PersistedTransform)#[0-9a-f]{5}\n'
r' handles: 1\n'
r' offset: Offset\(0\.0, 0\.0\)\n' r' offset: Offset\(0\.0, 0\.0\)\n'
r' transform:\n' r' transform:\n'
r' \[0] 3\.0,0\.0,0\.0,0\.0\n' r' \[0] 3\.0,0\.0,0\.0,0\.0\n'
......
...@@ -648,7 +648,7 @@ void main() { ...@@ -648,7 +648,7 @@ void main() {
child: box200x200, child: box200x200,
); );
layout(defaultBox, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); layout(defaultBox, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
defaultBox.paint(context, Offset.zero); context.paintChild(defaultBox, Offset.zero);
expect(context.clipBehavior, equals(Clip.none)); expect(context.clipBehavior, equals(Clip.none));
for (final Clip clip in Clip.values) { for (final Clip clip in Clip.values) {
...@@ -659,7 +659,7 @@ void main() { ...@@ -659,7 +659,7 @@ void main() {
clipBehavior: clip, clipBehavior: clip,
); );
layout(box, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); layout(box, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
box.paint(context, Offset.zero); context.paintChild(box, Offset.zero);
expect(context.clipBehavior, equals(clip)); expect(context.clipBehavior, equals(clip));
} }
}); });
......
...@@ -163,9 +163,10 @@ void main() { ...@@ -163,9 +163,10 @@ void main() {
); );
layout(root); layout(root);
dynamic error; dynamic error;
final PaintingContext context = PaintingContext(ContainerLayer(), const Rect.fromLTRB(0.0, 0.0, 800.0, 600.0));
try { try {
s.debugPaint( s.debugPaint(
PaintingContext(ContainerLayer(), const Rect.fromLTRB(0.0, 0.0, 800.0, 600.0)), context,
const Offset(0.0, 500), const Offset(0.0, 500),
); );
} catch (e) { } catch (e) {
...@@ -195,9 +196,10 @@ void main() { ...@@ -195,9 +196,10 @@ void main() {
); );
layout(root); layout(root);
dynamic error; dynamic error;
final PaintingContext context = PaintingContext(ContainerLayer(), const Rect.fromLTRB(0.0, 0.0, 800.0, 600.0));
try { try {
s.debugPaint( s.debugPaint(
PaintingContext(ContainerLayer(), const Rect.fromLTRB(0.0, 0.0, 800.0, 600.0)), context,
const Offset(0.0, 500), const Offset(0.0, 500),
); );
} catch (e) { } catch (e) {
......
...@@ -47,7 +47,7 @@ void main() { ...@@ -47,7 +47,7 @@ void main() {
selection: const TextSelection(baseOffset: 0, extentOffset: 0), selection: const TextSelection(baseOffset: 0, extentOffset: 0),
); );
layout(defaultEditable, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); layout(defaultEditable, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
defaultEditable.paint(context, Offset.zero); context.paintChild(defaultEditable, Offset.zero);
expect(context.clipBehavior, equals(Clip.hardEdge)); expect(context.clipBehavior, equals(Clip.hardEdge));
context.clipBehavior = Clip.none; // Reset as Clip.none won't write into clipBehavior. context.clipBehavior = Clip.none; // Reset as Clip.none won't write into clipBehavior.
...@@ -63,7 +63,7 @@ void main() { ...@@ -63,7 +63,7 @@ void main() {
clipBehavior: clip, clipBehavior: clip,
); );
layout(editable, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); layout(editable, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
editable.paint(context, Offset.zero); context.paintChild(editable, Offset.zero);
expect(context.clipBehavior, equals(clip)); expect(context.clipBehavior, equals(clip));
} }
}); });
......
...@@ -62,13 +62,13 @@ void main() { ...@@ -62,13 +62,13 @@ void main() {
// By default, clipBehavior should be Clip.none // By default, clipBehavior should be Clip.none
final RenderFlex defaultFlex = RenderFlex(direction: Axis.vertical, children: <RenderBox>[box200x200]); final RenderFlex defaultFlex = RenderFlex(direction: Axis.vertical, children: <RenderBox>[box200x200]);
layout(defaultFlex, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); layout(defaultFlex, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
defaultFlex.paint(context, Offset.zero); context.paintChild(defaultFlex, Offset.zero);
expect(context.clipBehavior, equals(Clip.none)); expect(context.clipBehavior, equals(Clip.none));
for (final Clip clip in Clip.values) { for (final Clip clip in Clip.values) {
final RenderFlex flex = RenderFlex(direction: Axis.vertical, children: <RenderBox>[box200x200], clipBehavior: clip); final RenderFlex flex = RenderFlex(direction: Axis.vertical, children: <RenderBox>[box200x200], clipBehavior: clip);
layout(flex, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); layout(flex, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
flex.paint(context, Offset.zero); context.paintChild(flex, Offset.zero);
expect(context.clipBehavior, equals(clip)); expect(context.clipBehavior, equals(clip));
} }
}); });
......
...@@ -256,7 +256,7 @@ void main() { ...@@ -256,7 +256,7 @@ void main() {
); );
}); });
test('PictureLayer prints picture, engine layer, and raster cache hints in debug info', () { test('PictureLayer prints picture, raster cache hints in debug info', () {
final PictureRecorder recorder = PictureRecorder(); final PictureRecorder recorder = PictureRecorder();
final Canvas canvas = Canvas(recorder); final Canvas canvas = Canvas(recorder);
canvas.drawPaint(Paint()); canvas.drawPaint(Paint());
...@@ -267,10 +267,20 @@ void main() { ...@@ -267,10 +267,20 @@ void main() {
layer.willChangeHint = false; layer.willChangeHint = false;
final List<String> info = _getDebugInfo(layer); final List<String> info = _getDebugInfo(layer);
expect(info, contains('picture: ${describeIdentity(picture)}')); expect(info, contains('picture: ${describeIdentity(picture)}'));
expect(info, contains('engine layer: ${describeIdentity(null)}')); expect(info, isNot(contains('engine layer: ${describeIdentity(null)}')));
expect(info, contains('raster cache hints: isComplex = true, willChange = false')); expect(info, contains('raster cache hints: isComplex = true, willChange = false'));
}); });
test('Layer prints engineLayer if it is not null in debug info', () {
final ConcreteLayer layer = ConcreteLayer();
List<String> info = _getDebugInfo(layer);
expect(info, isNot(contains('engine layer: ${describeIdentity(null)}')));
layer.engineLayer = FakeEngineLayer();
info = _getDebugInfo(layer);
expect(info, contains('engine layer: ${describeIdentity(layer.engineLayer)}'));
});
test('mutating PictureLayer fields triggers needsAddToScene', () { test('mutating PictureLayer fields triggers needsAddToScene', () {
final PictureLayer pictureLayer = PictureLayer(Rect.zero); final PictureLayer pictureLayer = PictureLayer(Rect.zero);
checkNeedsAddToScene(pictureLayer, () { checkNeedsAddToScene(pictureLayer, () {
...@@ -591,6 +601,135 @@ void main() { ...@@ -591,6 +601,135 @@ void main() {
// layer. // layer.
parent.buildScene(SceneBuilder()); parent.buildScene(SceneBuilder());
}, skip: isBrowser); // TODO(yjbanov): `toImage` doesn't work on the Web: https://github.com/flutter/flutter/issues/42767 }, skip: isBrowser); // TODO(yjbanov): `toImage` doesn't work on the Web: https://github.com/flutter/flutter/issues/42767
// TODO(dnfield): remove this when https://github.com/flutter/flutter/issues/85066 is resolved.
const bool bug85066 = true;
test('PictureLayer does not let you call dispose unless refcount is 0', () {
PictureLayer layer = PictureLayer(Rect.zero);
expect(layer.debugHandleCount, 0);
layer.dispose();
expect(layer.debugDisposed, true, skip: bug85066);
layer = PictureLayer(Rect.zero);
final LayerHandle<PictureLayer> handle = LayerHandle<PictureLayer>(layer);
expect(layer.debugHandleCount, 1);
expect(() => layer.dispose(), throwsAssertionError);
handle.layer = null;
expect(layer.debugHandleCount, 0);
expect(layer.debugDisposed, true, skip: bug85066);
expect(() => layer.dispose(), throwsAssertionError, skip: bug85066); // already disposed.
});
test('Layer append/remove increases/decreases handle count', () {
final PictureLayer layer = PictureLayer(Rect.zero);
final ContainerLayer parent = ContainerLayer();
expect(layer.debugHandleCount, 0);
expect(layer.debugDisposed, false);
parent.append(layer);
expect(layer.debugHandleCount, 1);
expect(layer.debugDisposed, false);
layer.remove();
expect(layer.debugHandleCount, 0);
expect(layer.debugDisposed, true, skip: bug85066);
});
test('Layer.dispose disposes the engineLayer', () {
final Layer layer = ConcreteLayer();
final FakeEngineLayer engineLayer = FakeEngineLayer();
layer.engineLayer = engineLayer;
expect(engineLayer.disposed, false);
layer.dispose();
expect(engineLayer.disposed, true);
expect(layer.engineLayer, null);
});
test('Layer.engineLayer (set) disposes the engineLayer', () {
final Layer layer = ConcreteLayer();
final FakeEngineLayer engineLayer = FakeEngineLayer();
layer.engineLayer = engineLayer;
expect(engineLayer.disposed, false);
layer.engineLayer = null;
expect(engineLayer.disposed, true);
});
test('PictureLayer.picture (set) disposes the picture', () {
final PictureLayer layer = PictureLayer(Rect.zero);
final FakePicture picture = FakePicture();
layer.picture = picture;
expect(picture.disposed, false);
layer.picture = null;
expect(picture.disposed, true);
});
test('PictureLayer disposes the picture', () {
final PictureLayer layer = PictureLayer(Rect.zero);
final FakePicture picture = FakePicture();
layer.picture = picture;
expect(picture.disposed, false);
layer.dispose();
expect(picture.disposed, true);
});
test('LayerHandle disposes the layer', () {
final ConcreteLayer layer = ConcreteLayer();
final ConcreteLayer layer2 = ConcreteLayer();
expect(layer.debugHandleCount, 0);
expect(layer2.debugHandleCount, 0);
final LayerHandle<ConcreteLayer> holder = LayerHandle<ConcreteLayer>(layer);
expect(layer.debugHandleCount, 1);
expect(layer.debugDisposed, false);
expect(layer2.debugHandleCount, 0);
expect(layer2.debugDisposed, false);
holder.layer = layer;
expect(layer.debugHandleCount, 1);
expect(layer.debugDisposed, false);
expect(layer2.debugHandleCount, 0);
expect(layer2.debugDisposed, false);
holder.layer = layer2;
expect(layer.debugHandleCount, 0);
expect(layer.debugDisposed, true, skip: bug85066);
expect(layer2.debugHandleCount, 1);
expect(layer2.debugDisposed, false);
holder.layer = null;
expect(layer.debugHandleCount, 0);
expect(layer.debugDisposed, true, skip: bug85066);
expect(layer2.debugHandleCount, 0);
expect(layer2.debugDisposed, true, skip: bug85066);
expect(() => holder.layer = layer, throwsAssertionError, skip: bug85066);
});
}
class FakeEngineLayer extends Fake implements EngineLayer {
bool disposed = false;
@override
void dispose() {
assert(!disposed);
disposed = true;
}
}
class FakePicture extends Fake implements Picture {
bool disposed = false;
@override
void dispose() {
assert(!disposed);
disposed = true;
}
}
class ConcreteLayer extends Layer {
@override
void addToScene(SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {}
} }
class _TestAlwaysNeedsAddToSceneLayer extends ContainerLayer { class _TestAlwaysNeedsAddToSceneLayer extends ContainerLayer {
......
// Copyright 2014 The Flutter 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 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
// TODO(dnfield): remove when https://github.com/flutter/flutter/issues/85066 is resolved.
const bool bug85066 = true;
testWidgets('Tracks picture layers accurately when painting is interleaved with a pushLayer', (WidgetTester tester) async {
// Creates a RenderObject that will paint into multiple picture layers.
// Asserts that both layers get a handle, and that all layers get correctly
// released.
final GlobalKey key = GlobalKey();
await tester.pumpWidget(RepaintBoundary(
child: CustomPaint(
key: key,
painter: SimplePainter(),
child: const RepaintBoundary(child: Placeholder()),
foregroundPainter: SimplePainter(),
),
));
final List<Layer> layers = tester.binding.renderView.debugLayer!.depthFirstIterateChildren();
final RenderObject renderObject = key.currentContext!.findRenderObject()!;
for (final Layer layer in layers) {
expect(layer.debugDisposed, false);
}
await tester.pumpWidget(const SizedBox());
for (final Layer layer in layers) {
expect(layer.debugDisposed, true, skip: bug85066);
}
expect(renderObject.debugDisposed, true);
});
}
class SimplePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
canvas.drawPaint(Paint());
}
@override
bool shouldRepaint(SimplePainter oldDelegate) => true;
}
...@@ -305,7 +305,6 @@ class TestClipPaintingContext extends PaintingContext { ...@@ -305,7 +305,6 @@ class TestClipPaintingContext extends PaintingContext {
ClipRectLayer? oldLayer, ClipRectLayer? oldLayer,
}) { }) {
this.clipBehavior = clipBehavior; this.clipBehavior = clipBehavior;
return null;
} }
Clip clipBehavior = Clip.none; Clip clipBehavior = Clip.none;
......
...@@ -83,7 +83,7 @@ void main() { ...@@ -83,7 +83,7 @@ void main() {
parentData.left = parentData.right = 0; parentData.left = parentData.right = 0;
} }
layout(stack, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); layout(stack, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
stack.paint(context, Offset.zero); context.paintChild(stack, Offset.zero);
expect(context.clipBehavior, equals(clip)); expect(context.clipBehavior, equals(clip));
} }
}); });
......
...@@ -209,13 +209,13 @@ void main() { ...@@ -209,13 +209,13 @@ void main() {
// By default, clipBehavior should be Clip.none // By default, clipBehavior should be Clip.none
final RenderWrap defaultWrap = RenderWrap(textDirection: TextDirection.ltr, children: <RenderBox>[box200x200]); final RenderWrap defaultWrap = RenderWrap(textDirection: TextDirection.ltr, children: <RenderBox>[box200x200]);
layout(defaultWrap, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); layout(defaultWrap, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
defaultWrap.paint(context, Offset.zero); context.paintChild(defaultWrap, Offset.zero);
expect(context.clipBehavior, equals(Clip.none)); expect(context.clipBehavior, equals(Clip.none));
for (final Clip clip in Clip.values) { for (final Clip clip in Clip.values) {
final RenderWrap wrap = RenderWrap(textDirection: TextDirection.ltr, children: <RenderBox>[box200x200], clipBehavior: clip); final RenderWrap wrap = RenderWrap(textDirection: TextDirection.ltr, children: <RenderBox>[box200x200], clipBehavior: clip);
layout(wrap, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); layout(wrap, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
wrap.paint(context, Offset.zero); context.paintChild(wrap, Offset.zero);
expect(context.clipBehavior, equals(clip)); expect(context.clipBehavior, equals(clip));
} }
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment