Commit 0213b67e authored by krisgiesing's avatar krisgiesing

Handle dirty bits correctly when render objects are re-attached

Fixes #2855
parent 6fd68597
...@@ -107,10 +107,6 @@ class PaintingContext { ...@@ -107,10 +107,6 @@ class PaintingContext {
assert(child._layer != null); assert(child._layer != null);
assert(() { assert(() {
child.debugRegisterRepaintBoundaryPaint(includedParent: true, includedChild: false); child.debugRegisterRepaintBoundaryPaint(includedParent: true, includedChild: false);
return true;
});
child._layer.detach();
assert(() {
child._layer.debugCreator = child.debugCreator ?? child.runtimeType; child._layer.debugCreator = child.debugCreator ?? child.runtimeType;
return true; return true;
}); });
...@@ -121,6 +117,7 @@ class PaintingContext { ...@@ -121,6 +117,7 @@ class PaintingContext {
void _appendLayer(Layer layer) { void _appendLayer(Layer layer) {
assert(!_isRecording); assert(!_isRecording);
layer.detach();
_containerLayer.append(layer); _containerLayer.append(layer);
} }
...@@ -949,6 +946,30 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -949,6 +946,30 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
@override @override
void attach(PipelineOwner owner) { void attach(PipelineOwner owner) {
super.attach(owner); super.attach(owner);
// If the node was dirtied in some way while unattached, make sure to add
// it to the appropriate dirty list now that an owner is available
if (_needsLayout && _relayoutSubtreeRoot != null) {
// Don't enter this block if we've never laid out at all;
// scheduleInitialLayout() will handle it
_needsLayout = false;
markNeedsLayout();
}
if (_needsCompositingBitsUpdate) {
_needsCompositingBitsUpdate = false;
markNeedsCompositingBitsUpdate();
}
if (_needsPaint && _layer != null) {
// Don't enter this block if we've never painted at all;
// scheduleInitialPaint() will handle it
_needsPaint = false;
markNeedsPaint();
}
if (_needsSemanticsUpdate && hasSemantics) {
// Don't enter this block if we've never updated semantics at all;
// scheduleInitialSemantics() will handle it
_needsSemanticsUpdate = false;
markNeedsSemanticsUpdate();
}
} }
bool _needsLayout = true; bool _needsLayout = true;
...@@ -1396,8 +1417,6 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1396,8 +1417,6 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
/// writes are coalesced, removing redundant computation. /// writes are coalesced, removing redundant computation.
void markNeedsPaint() { void markNeedsPaint() {
assert(owner == null || !owner.debugDoingPaint); assert(owner == null || !owner.debugDoingPaint);
if (!attached)
return; // Don't try painting things that aren't in the hierarchy
if (_needsPaint) if (_needsPaint)
return; return;
_needsPaint = true; _needsPaint = true;
...@@ -1592,7 +1611,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1592,7 +1611,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
/// tree will be out of date. /// tree will be out of date.
void markNeedsSemanticsUpdate({ bool onlyChanges: false, bool noGeometry: false }) { void markNeedsSemanticsUpdate({ bool onlyChanges: false, bool noGeometry: false }) {
assert(!attached || !owner._debugDoingSemantics); assert(!attached || !owner._debugDoingSemantics);
if (!attached || !owner._semanticsEnabled || (_needsSemanticsUpdate && onlyChanges && (_needsSemanticsGeometryUpdate || noGeometry))) if ((attached && !owner._semanticsEnabled) || (_needsSemanticsUpdate && onlyChanges && (_needsSemanticsGeometryUpdate || noGeometry)))
return; return;
if (!noGeometry && (_semantics == null || (_semantics.hasChildren && _semantics.wasAffectedByClip))) { if (!noGeometry && (_semantics == null || (_semantics.hasChildren && _semantics.wasAffectedByClip))) {
// Since the geometry might have changed, we need to make sure to reapply any clips. // Since the geometry might have changed, we need to make sure to reapply any clips.
...@@ -1612,7 +1631,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1612,7 +1631,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
} }
if (!node._needsSemanticsUpdate) { if (!node._needsSemanticsUpdate) {
node._needsSemanticsUpdate = true; node._needsSemanticsUpdate = true;
owner._nodesNeedingSemantics.add(node); if (owner != null)
owner._nodesNeedingSemantics.add(node);
} }
} else { } else {
// The shape of the semantics tree around us may have changed. // The shape of the semantics tree around us may have changed.
...@@ -1631,7 +1651,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1631,7 +1651,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
node._semantics?.reset(); node._semantics?.reset();
if (!node._needsSemanticsUpdate) { if (!node._needsSemanticsUpdate) {
node._needsSemanticsUpdate = true; node._needsSemanticsUpdate = true;
owner._nodesNeedingSemantics.add(node); if (owner != null)
owner._nodesNeedingSemantics.add(node);
} }
} }
} }
......
// Copyright 2016 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 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:sky_services/semantics/semantics.mojom.dart' as mojom;
import 'package:test/test.dart';
import 'rendering_tester.dart';
class TestTree {
TestTree() {
// viewport incoming constraints are tight 800x600
// viewport is vertical by default
root = new RenderViewport(
// Place the child to be evaluated within both a repaint boundary and a
// layout-root element (in this case a tightly constrained box). Otherwise
// the act of transplanting the root into a new container will cause the
// relayout/repaint of the new parent node to satisfy the test.
child: new RenderRepaintBoundary(
child: new RenderConstrainedBox(
additionalConstraints: new BoxConstraints.tightFor(height: 20.0, width: 20.0),
child: new RenderRepaintBoundary(
child: new RenderCustomPaint(
painter: new TestCallbackPainter(
onPaint: () { painted = true; }
),
child: new RenderPositionedBox(
child: child = new RenderConstrainedBox(
additionalConstraints: new BoxConstraints.tightFor(height: 20.0, width: 20.0)
)
)
)
)
)
)
);
}
RenderObject root;
RenderConstrainedBox child;
bool painted = false;
}
class MutableCompositor extends RenderProxyBox {
MutableCompositor({ RenderBox child }) : super(child);
bool _alwaysComposite = false;
@override
bool get alwaysNeedsCompositing => _alwaysComposite;
}
class TestCompositingBitsTree {
TestCompositingBitsTree() {
// viewport incoming constraints are tight 800x600
// viewport is vertical by default
root = new RenderViewport(
// Place the child to be evaluated within a repaint boundary. Otherwise
// the act of transplanting the root into a new container will cause the
// repaint of the new parent node to satisfy the test.
child: new RenderRepaintBoundary(
child: compositor = new MutableCompositor(
child: new RenderCustomPaint(
painter: new TestCallbackPainter(
onPaint: () { painted = true; }
),
child: child = new RenderConstrainedBox(
additionalConstraints: new BoxConstraints.tightFor(height: 20.0, width: 20.0)
)
)
)
)
);
}
RenderObject root;
MutableCompositor compositor;
RenderConstrainedBox child;
bool painted = false;
}
class TestSemanticsListener implements mojom.SemanticsListener {
final List<mojom.SemanticsNode> updates = <mojom.SemanticsNode>[];
@override
void updateSemanticsTree(List<mojom.SemanticsNode> nodes) {
updates.addAll(nodes);
}
}
void main() {
test('objects can be detached and re-attached: layout', () {
TestTree testTree = new TestTree();
// Lay out
layout(testTree.root, phase: EnginePhase.layout);
expect(testTree.child.size, equals(const Size(20.0, 20.0)));
// Remove testTree from the custom render view
renderer.renderView.child = null;
expect(testTree.child.owner, isNull);
// Dirty one of the elements
testTree.child.additionalConstraints =
new BoxConstraints.tightFor(height: 5.0, width: 5.0);
// Lay out again
layout(testTree.root, phase: EnginePhase.layout);
expect(testTree.child.size, equals(const Size(5.0, 5.0)));
});
test('objects can be detached and re-attached: compositingBits', () {
TestCompositingBitsTree testTree = new TestCompositingBitsTree();
// Lay out, composite, and paint
layout(testTree.root, phase: EnginePhase.paint);
expect(testTree.painted, isTrue);
// Remove testTree from the custom render view
renderer.renderView.child = null;
expect(testTree.child.owner, isNull);
// Dirty one of the elements
testTree.compositor._alwaysComposite = true;
testTree.child.markNeedsCompositingBitsUpdate();
testTree.painted = false;
// Lay out, composite, and paint again
layout(testTree.root, phase: EnginePhase.paint);
expect(testTree.painted, isTrue);
});
test('objects can be detached and re-attached: paint', () {
TestTree testTree = new TestTree();
// Lay out, composite, and paint
layout(testTree.root, phase: EnginePhase.paint);
expect(testTree.painted, isTrue);
// Remove testTree from the custom render view
renderer.renderView.child = null;
expect(testTree.child.owner, isNull);
// Dirty one of the elements
testTree.child.markNeedsPaint();
testTree.painted = false;
// Lay out, composite, and paint again
layout(testTree.root, phase: EnginePhase.paint);
expect(testTree.painted, isTrue);
});
test('objects can be detached and re-attached: semantics', () {
TestTree testTree = new TestTree();
TestSemanticsListener listener = new TestSemanticsListener();
SemanticsNode.addListener(listener);
// Lay out, composite, paint, and update semantics
layout(testTree.root, phase: EnginePhase.sendSemanticsTree);
expect(listener.updates.length, equals(1));
// Remove testTree from the custom render view
renderer.renderView.child = null;
expect(testTree.child.owner, isNull);
// Dirty one of the elements
listener.updates.clear();
testTree.child.markNeedsSemanticsUpdate();
expect(listener.updates.length, equals(0));
// Lay out, composite, paint, and update semantics again
layout(testTree.root, phase: EnginePhase.sendSemanticsTree);
expect(listener.updates.length, equals(1));
});
}
...@@ -25,7 +25,9 @@ enum EnginePhase { ...@@ -25,7 +25,9 @@ enum EnginePhase {
layout, layout,
compositingBits, compositingBits,
paint, paint,
composite composite,
flushSemantics,
sendSemanticsTree
} }
class TestRenderingFlutterBinding extends BindingBase with Scheduler, Services, Renderer, Gesturer { class TestRenderingFlutterBinding extends BindingBase with Scheduler, Services, Renderer, Gesturer {
...@@ -51,6 +53,14 @@ class TestRenderingFlutterBinding extends BindingBase with Scheduler, Services, ...@@ -51,6 +53,14 @@ class TestRenderingFlutterBinding extends BindingBase with Scheduler, Services,
if (phase == EnginePhase.paint) if (phase == EnginePhase.paint)
return; return;
renderView.compositeFrame(); renderView.compositeFrame();
if (phase == EnginePhase.composite)
return;
if (SemanticsNode.hasListeners) {
pipelineOwner.flushSemantics();
if (phase == EnginePhase.flushSemantics)
return;
SemanticsNode.sendSemanticsTree();
}
} }
} }
......
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