Commit 4caaf9ee authored by Adam Barth's avatar Adam Barth

Merge pull request #2617 from abarth/reparent_ordering

Handle the case of reparenting while updating children
parents f3dc82ab 9c088362
...@@ -1536,10 +1536,14 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab ...@@ -1536,10 +1536,14 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab
/// Attempts to update the given old children list using the given new /// Attempts to update the given old children list using the given new
/// widgets, removing obsolete elements and introducing new ones as necessary, /// widgets, removing obsolete elements and introducing new ones as necessary,
/// and then returns the new child list. /// and then returns the new child list.
List<Element> updateChildren(List<Element> oldChildren, List<Widget> newWidgets) { List<Element> updateChildren(List<Element> oldChildren, List<Widget> newWidgets, { Set<Element> detachedChildren }) {
assert(oldChildren != null); assert(oldChildren != null);
assert(newWidgets != null); assert(newWidgets != null);
Element replaceWithNullIfDetached(Element child) {
return detachedChildren != null && detachedChildren.contains(child) ? null : child;
}
// This attempts to diff the new child list (this.children) with // This attempts to diff the new child list (this.children) with
// the old child list (old.children), and update our renderObject // the old child list (old.children), and update our renderObject
// accordingly. // accordingly.
...@@ -1582,7 +1586,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab ...@@ -1582,7 +1586,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab
// Update the top of the list. // Update the top of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) { while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
Element oldChild = oldChildren[oldChildrenTop]; Element oldChild = replaceWithNullIfDetached(oldChildren[oldChildrenTop]);
Widget newWidget = newWidgets[newChildrenTop]; Widget newWidget = newWidgets[newChildrenTop];
assert(oldChild == null || oldChild._debugLifecycleState == _ElementLifecycle.active); assert(oldChild == null || oldChild._debugLifecycleState == _ElementLifecycle.active);
if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget)) if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
...@@ -1597,7 +1601,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab ...@@ -1597,7 +1601,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab
// Scan the bottom of the list. // Scan the bottom of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) { while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
Element oldChild = oldChildren[oldChildrenBottom]; Element oldChild = replaceWithNullIfDetached(oldChildren[oldChildrenBottom]);
Widget newWidget = newWidgets[newChildrenBottom]; Widget newWidget = newWidgets[newChildrenBottom];
assert(oldChild == null || oldChild._debugLifecycleState == _ElementLifecycle.active); assert(oldChild == null || oldChild._debugLifecycleState == _ElementLifecycle.active);
if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget)) if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
...@@ -1612,7 +1616,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab ...@@ -1612,7 +1616,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab
if (haveOldChildren) { if (haveOldChildren) {
oldKeyedChildren = new Map<Key, Element>(); oldKeyedChildren = new Map<Key, Element>();
while (oldChildrenTop <= oldChildrenBottom) { while (oldChildrenTop <= oldChildrenBottom) {
Element oldChild = oldChildren[oldChildrenTop]; Element oldChild = replaceWithNullIfDetached(oldChildren[oldChildrenTop]);
assert(oldChild == null || oldChild._debugLifecycleState == _ElementLifecycle.active); assert(oldChild == null || oldChild._debugLifecycleState == _ElementLifecycle.active);
if (oldChild != null) { if (oldChild != null) {
if (oldChild.widget.key != null) if (oldChild.widget.key != null)
...@@ -1663,7 +1667,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab ...@@ -1663,7 +1667,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab
// Update the bottom of the list. // Update the bottom of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) { while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
Element oldChild = oldChildren[oldChildrenTop]; Element oldChild = oldChildren[oldChildrenTop];
assert(oldChild != null); assert(replaceWithNullIfDetached(oldChild) != null);
assert(oldChild._debugLifecycleState == _ElementLifecycle.active); assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
Widget newWidget = newWidgets[newChildrenTop]; Widget newWidget = newWidgets[newChildrenTop];
assert(Widget.canUpdate(oldChild.widget, newWidget)); assert(Widget.canUpdate(oldChild.widget, newWidget));
...@@ -1678,9 +1682,11 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab ...@@ -1678,9 +1682,11 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab
// clean up any of the remaining middle nodes from the old list // clean up any of the remaining middle nodes from the old list
if (haveOldChildren && oldKeyedChildren.isNotEmpty) { if (haveOldChildren && oldKeyedChildren.isNotEmpty) {
for (Element oldChild in oldKeyedChildren.values) for (Element oldChild in oldKeyedChildren.values) {
if (detachedChildren == null || !detachedChildren.contains(oldChild))
_deactivateChild(oldChild); _deactivateChild(oldChild);
} }
}
return newChildren; return newChildren;
} }
...@@ -1808,19 +1814,9 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte ...@@ -1808,19 +1814,9 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
} }
List<Element> _children; List<Element> _children;
// We null out detached children lazily to avoid O(n^2) work walking _children // We keep a set of detached children to avoid O(n^2) work walking _children
// repeatedly to remove children. // repeatedly to remove children.
Set<Element> _detachedChildren; final Set<Element> _detachedChildren = new HashSet<Element>();
void _replaceDetachedChildrenWithNull() {
if (_detachedChildren != null && _detachedChildren.isNotEmpty) {
for (int i = 0; i < _children.length; ++i) {
if (_detachedChildren.contains(_children[i]))
_children[i] = null;
}
_detachedChildren.clear();
}
}
void insertChildRenderObject(RenderObject child, Element slot) { void insertChildRenderObject(RenderObject child, Element slot) {
final ContainerRenderObjectMixin renderObject = this.renderObject; final ContainerRenderObjectMixin renderObject = this.renderObject;
...@@ -1860,15 +1856,13 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte ...@@ -1860,15 +1856,13 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
} }
void visitChildren(ElementVisitor visitor) { void visitChildren(ElementVisitor visitor) {
_replaceDetachedChildrenWithNull();
for (Element child in _children) { for (Element child in _children) {
if (child != null) if (!_detachedChildren.contains(child))
visitor(child); visitor(child);
} }
} }
bool detachChild(Element child) { bool detachChild(Element child) {
_detachedChildren ??= new Set<Element>();
_detachedChildren.add(child); _detachedChildren.add(child);
_deactivateChild(child); _deactivateChild(child);
return true; return true;
...@@ -1888,8 +1882,8 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte ...@@ -1888,8 +1882,8 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
void update(T newWidget) { void update(T newWidget) {
super.update(newWidget); super.update(newWidget);
assert(widget == newWidget); assert(widget == newWidget);
_replaceDetachedChildrenWithNull(); _children = updateChildren(_children, widget.children, detachedChildren: _detachedChildren);
_children = updateChildren(_children, widget.children); _detachedChildren.clear();
} }
} }
......
...@@ -196,4 +196,99 @@ void main() { ...@@ -196,4 +196,99 @@ void main() {
expect(keyState.marker, equals("marked")); expect(keyState.marker, equals("marked"));
}); });
}); });
test('Reparent during update children', () {
testWidgets((WidgetTester tester) {
GlobalKey key = new GlobalKey();
tester.pumpWidget(new Stack(
children: <Widget>[
new StateMarker(key: key),
new Container(width: 100.0, height: 100.0),
]
));
StateMarkerState keyState = key.currentState;
keyState.marker = "marked";
tester.pumpWidget(new Stack(
children: <Widget>[
new Container(width: 100.0, height: 100.0),
new StateMarker(key: key),
]
));
expect(key.currentState, equals(keyState));
expect(keyState.marker, equals("marked"));
tester.pumpWidget(new Stack(
children: <Widget>[
new StateMarker(key: key),
new Container(width: 100.0, height: 100.0),
]
));
expect(key.currentState, equals(keyState));
expect(keyState.marker, equals("marked"));
});
});
test('Reparent to child during update children', () {
testWidgets((WidgetTester tester) {
GlobalKey key = new GlobalKey();
tester.pumpWidget(new Stack(
children: <Widget>[
new Container(width: 100.0, height: 100.0),
new StateMarker(key: key),
new Container(width: 100.0, height: 100.0),
]
));
StateMarkerState keyState = key.currentState;
keyState.marker = "marked";
tester.pumpWidget(new Stack(
children: <Widget>[
new Container(width: 100.0, height: 100.0, child: new StateMarker(key: key)),
new Container(width: 100.0, height: 100.0),
]
));
expect(key.currentState, equals(keyState));
expect(keyState.marker, equals("marked"));
tester.pumpWidget(new Stack(
children: <Widget>[
new Container(width: 100.0, height: 100.0),
new StateMarker(key: key),
new Container(width: 100.0, height: 100.0),
]
));
expect(key.currentState, equals(keyState));
expect(keyState.marker, equals("marked"));
tester.pumpWidget(new Stack(
children: <Widget>[
new Container(width: 100.0, height: 100.0),
new Container(width: 100.0, height: 100.0, child: new StateMarker(key: key)),
]
));
expect(key.currentState, equals(keyState));
expect(keyState.marker, equals("marked"));
tester.pumpWidget(new Stack(
children: <Widget>[
new Container(width: 100.0, height: 100.0),
new StateMarker(key: key),
new Container(width: 100.0, height: 100.0),
]
));
expect(key.currentState, equals(keyState));
expect(keyState.marker, equals("marked"));
});
});
} }
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