Revert "Do not rebuild Routes when a new opaque Route is pushed on top (#48900)" (#49366)

This reverts commit 8eecdbe823ea09dd723a8af34c7c275e5536d728.
diff --git a/packages/flutter/lib/src/rendering/stack.dart b/packages/flutter/lib/src/rendering/stack.dart
index c72c1d1..502588b 100644
--- a/packages/flutter/lib/src/rendering/stack.dart
+++ b/packages/flutter/lib/src/rendering/stack.dart
@@ -425,8 +425,7 @@
     }
   }
 
-  /// Helper function for calculating the intrinsics metrics of a Stack.
-  static double getIntrinsicDimension(RenderBox firstChild, double mainChildSizeGetter(RenderBox child)) {
+  double _getIntrinsicDimension(double mainChildSizeGetter(RenderBox child)) {
     double extent = 0.0;
     RenderBox child = firstChild;
     while (child != null) {
@@ -441,22 +440,22 @@
 
   @override
   double computeMinIntrinsicWidth(double height) {
-    return getIntrinsicDimension(firstChild, (RenderBox child) => child.getMinIntrinsicWidth(height));
+    return _getIntrinsicDimension((RenderBox child) => child.getMinIntrinsicWidth(height));
   }
 
   @override
   double computeMaxIntrinsicWidth(double height) {
-    return getIntrinsicDimension(firstChild, (RenderBox child) => child.getMaxIntrinsicWidth(height));
+    return _getIntrinsicDimension((RenderBox child) => child.getMaxIntrinsicWidth(height));
   }
 
   @override
   double computeMinIntrinsicHeight(double width) {
-    return getIntrinsicDimension(firstChild, (RenderBox child) => child.getMinIntrinsicHeight(width));
+    return _getIntrinsicDimension((RenderBox child) => child.getMinIntrinsicHeight(width));
   }
 
   @override
   double computeMaxIntrinsicHeight(double width) {
-    return getIntrinsicDimension(firstChild, (RenderBox child) => child.getMaxIntrinsicHeight(width));
+    return _getIntrinsicDimension((RenderBox child) => child.getMaxIntrinsicHeight(width));
   }
 
   @override
@@ -464,57 +463,6 @@
     return defaultComputeDistanceToHighestActualBaseline(baseline);
   }
 
-  /// Lays out the positioned `child` according to `alignment` within a Stack of `size`.
-  ///
-  /// Returns true when the child has visual overflow.
-  static bool layoutPositionedChild(RenderBox child, StackParentData childParentData, Size size, Alignment alignment) {
-    assert(childParentData.isPositioned);
-    assert(child.parentData == childParentData);
-
-    bool hasVisualOverflow = false;
-    BoxConstraints childConstraints = const BoxConstraints();
-
-    if (childParentData.left != null && childParentData.right != null)
-      childConstraints = childConstraints.tighten(width: size.width - childParentData.right - childParentData.left);
-    else if (childParentData.width != null)
-      childConstraints = childConstraints.tighten(width: childParentData.width);
-
-    if (childParentData.top != null && childParentData.bottom != null)
-      childConstraints = childConstraints.tighten(height: size.height - childParentData.bottom - childParentData.top);
-    else if (childParentData.height != null)
-      childConstraints = childConstraints.tighten(height: childParentData.height);
-
-    child.layout(childConstraints, parentUsesSize: true);
-
-    double x;
-    if (childParentData.left != null) {
-      x = childParentData.left;
-    } else if (childParentData.right != null) {
-      x = size.width - childParentData.right - child.size.width;
-    } else {
-      x = alignment.alongOffset(size - child.size as Offset).dx;
-    }
-
-    if (x < 0.0 || x + child.size.width > size.width)
-      hasVisualOverflow = true;
-
-    double y;
-    if (childParentData.top != null) {
-      y = childParentData.top;
-    } else if (childParentData.bottom != null) {
-      y = size.height - childParentData.bottom - child.size.height;
-    } else {
-      y = alignment.alongOffset(size - child.size as Offset).dy;
-    }
-
-    if (y < 0.0 || y + child.size.height > size.height)
-      hasVisualOverflow = true;
-
-    childParentData.offset = Offset(x, y);
-
-    return hasVisualOverflow;
-  }
-
   @override
   void performLayout() {
     _resolve();
@@ -579,7 +527,45 @@
       if (!childParentData.isPositioned) {
         childParentData.offset = _resolvedAlignment.alongOffset(size - child.size as Offset);
       } else {
-        _hasVisualOverflow = layoutPositionedChild(child, childParentData, size, _resolvedAlignment) || _hasVisualOverflow;
+        BoxConstraints childConstraints = const BoxConstraints();
+
+        if (childParentData.left != null && childParentData.right != null)
+          childConstraints = childConstraints.tighten(width: size.width - childParentData.right - childParentData.left);
+        else if (childParentData.width != null)
+          childConstraints = childConstraints.tighten(width: childParentData.width);
+
+        if (childParentData.top != null && childParentData.bottom != null)
+          childConstraints = childConstraints.tighten(height: size.height - childParentData.bottom - childParentData.top);
+        else if (childParentData.height != null)
+          childConstraints = childConstraints.tighten(height: childParentData.height);
+
+        child.layout(childConstraints, parentUsesSize: true);
+
+        double x;
+        if (childParentData.left != null) {
+          x = childParentData.left;
+        } else if (childParentData.right != null) {
+          x = size.width - childParentData.right - child.size.width;
+        } else {
+          x = _resolvedAlignment.alongOffset(size - child.size as Offset).dx;
+        }
+
+        if (x < 0.0 || x + child.size.width > size.width)
+          _hasVisualOverflow = true;
+
+        double y;
+        if (childParentData.top != null) {
+          y = childParentData.top;
+        } else if (childParentData.bottom != null) {
+          y = size.height - childParentData.bottom - child.size.height;
+        } else {
+          y = _resolvedAlignment.alongOffset(size - child.size as Offset).dy;
+        }
+
+        if (y < 0.0 || y + child.size.height > size.height)
+          _hasVisualOverflow = true;
+
+        childParentData.offset = Offset(x, y);
       }
 
       assert(child.parentData == childParentData);
diff --git a/packages/flutter/lib/src/widgets/overlay.dart b/packages/flutter/lib/src/widgets/overlay.dart
index 3ea523c..609ad2f 100644
--- a/packages/flutter/lib/src/widgets/overlay.dart
+++ b/packages/flutter/lib/src/widgets/overlay.dart
@@ -4,13 +4,13 @@
 
 import 'dart:async';
 import 'dart:collection';
-import 'dart:math' as math;
 
 import 'package:flutter/foundation.dart';
 import 'package:flutter/rendering.dart';
 import 'package:flutter/scheduler.dart';
 
 import 'basic.dart';
+import 'debug.dart';
 import 'framework.dart';
 import 'ticker_provider.dart';
 
@@ -115,7 +115,7 @@
   }
 
   OverlayState _overlay;
-  final GlobalKey<_OverlayEntryWidgetState> _key = GlobalKey<_OverlayEntryWidgetState>();
+  final GlobalKey<_OverlayEntryState> _key = GlobalKey<_OverlayEntryState>();
 
   /// Remove this entry from the overlay.
   ///
@@ -152,30 +152,21 @@
   String toString() => '${describeIdentity(this)}(opaque: $opaque; maintainState: $maintainState)';
 }
 
-class _OverlayEntryWidget extends StatefulWidget {
-  const _OverlayEntryWidget({
-    @required Key key,
-    @required this.entry,
-    this.tickerEnabled = true,
-  }) : assert(key != null),
-       assert(entry != null),
-       assert(tickerEnabled != null),
-       super(key: key);
+class _OverlayEntry extends StatefulWidget {
+  _OverlayEntry(this.entry)
+    : assert(entry != null),
+      super(key: entry._key);
 
   final OverlayEntry entry;
-  final bool tickerEnabled;
 
   @override
-  _OverlayEntryWidgetState createState() => _OverlayEntryWidgetState();
+  _OverlayEntryState createState() => _OverlayEntryState();
 }
 
-class _OverlayEntryWidgetState extends State<_OverlayEntryWidget> {
+class _OverlayEntryState extends State<_OverlayEntry> {
   @override
   Widget build(BuildContext context) {
-    return TickerMode(
-      enabled: widget.tickerEnabled,
-      child: widget.entry.builder(context),
-    );
+    return widget.entry.builder(context);
   }
 
   void _markNeedsBuild() {
@@ -461,32 +452,28 @@
 
   @override
   Widget build(BuildContext context) {
-    // This list is filled backwards and then reversed below before
-    // it is added to the tree.
-    final List<Widget> children = <Widget>[];
+    // These lists are filled backwards. For the offstage children that
+    // does not matter since they aren't rendered, but for the onstage
+    // children we reverse the list below before adding it to the tree.
+    final List<Widget> onstageChildren = <Widget>[];
+    final List<Widget> offstageChildren = <Widget>[];
     bool onstage = true;
-    int onstageCount = 0;
     for (int i = _entries.length - 1; i >= 0; i -= 1) {
       final OverlayEntry entry = _entries[i];
       if (onstage) {
-        onstageCount += 1;
-        children.add(_OverlayEntryWidget(
-          key: entry._key,
-          entry: entry,
-        ));
+        onstageChildren.add(_OverlayEntry(entry));
         if (entry.opaque)
           onstage = false;
       } else if (entry.maintainState) {
-        children.add(_OverlayEntryWidget(
-          key: entry._key,
-          entry: entry,
-          tickerEnabled: false,
-        ));
+        offstageChildren.add(TickerMode(enabled: false, child: _OverlayEntry(entry)));
       }
     }
     return _Theatre(
-      skipCount: children.length - onstageCount,
-      children: children.reversed.toList(growable: false),
+      onstage: Stack(
+        fit: StackFit.expand,
+        children: onstageChildren.reversed.toList(growable: false),
+      ),
+      offstage: offstageChildren,
     );
   }
 
@@ -499,50 +486,36 @@
   }
 }
 
-/// Special version of a [Stack], that doesn't layout and render the first
-/// [skipCount] children.
+/// A widget that has one [onstage] child which is visible, and one or more
+/// [offstage] widgets which are kept alive, and are built, but are not laid out
+/// or painted.
 ///
-/// The first [skipCount] children are considered "offstage".
-class _Theatre extends MultiChildRenderObjectWidget {
+/// The onstage widget must be a [Stack].
+///
+/// For convenience, it is legal to use [Positioned] widgets around the offstage
+/// widgets.
+class _Theatre extends RenderObjectWidget {
   _Theatre({
-    Key key,
-    this.skipCount = 0,
-    List<Widget> children = const <Widget>[],
-  }) : assert(skipCount != null),
-       assert(skipCount >= 0),
-       assert(children != null),
-       assert(children.length >= skipCount),
-       super(key: key, children: children);
+    this.onstage,
+    @required this.offstage,
+  }) : assert(offstage != null),
+       assert(!offstage.any((Widget child) => child == null));
 
-  final int skipCount;
+  final Stack onstage;
+
+  final List<Widget> offstage;
 
   @override
   _TheatreElement createElement() => _TheatreElement(this);
 
   @override
-  _RenderTheatre createRenderObject(BuildContext context) {
-    return _RenderTheatre(
-      skipCount: skipCount,
-      textDirection: Directionality.of(context),
-    );
-  }
-
-  @override
-  void updateRenderObject(BuildContext context, _RenderTheatre renderObject) {
-    renderObject
-      ..skipCount = skipCount
-      ..textDirection = Directionality.of(context);
-  }
-
-  @override
-  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
-    super.debugFillProperties(properties);
-    properties.add(IntProperty('skipCount', skipCount));
-  }
+  _RenderTheatre createRenderObject(BuildContext context) => _RenderTheatre();
 }
 
-class _TheatreElement extends MultiChildRenderObjectElement {
-  _TheatreElement(_Theatre widget) : super(widget);
+class _TheatreElement extends RenderObjectElement {
+  _TheatreElement(_Theatre widget)
+    : assert(!debugChildrenHaveDuplicateKeys(widget, widget.offstage)),
+      super(widget);
 
   @override
   _Theatre get widget => super.widget as _Theatre;
@@ -550,268 +523,186 @@
   @override
   _RenderTheatre get renderObject => super.renderObject as _RenderTheatre;
 
+  Element _onstage;
+  static final Object _onstageSlot = Object();
+
+  List<Element> _offstage;
+  final Set<Element> _forgottenOffstageChildren = HashSet<Element>();
+
+  @override
+  void insertChildRenderObject(RenderBox child, dynamic slot) {
+    assert(renderObject.debugValidateChild(child));
+    if (slot == _onstageSlot) {
+      assert(child is RenderStack);
+      renderObject.child = child as RenderStack;
+    } else {
+      assert(slot == null || slot is Element);
+      renderObject.insert(child, after: slot?.renderObject as RenderBox);
+    }
+  }
+
+  @override
+  void moveChildRenderObject(RenderBox child, dynamic slot) {
+    if (slot == _onstageSlot) {
+      renderObject.remove(child);
+      assert(child is RenderStack);
+      renderObject.child = child as RenderStack;
+    } else {
+      assert(slot == null || slot is Element);
+      if (renderObject.child == child) {
+        renderObject.child = null;
+        renderObject.insert(child, after: slot?.renderObject as RenderBox);
+      } else {
+        renderObject.move(child, after: slot?.renderObject as RenderBox);
+      }
+    }
+  }
+
+  @override
+  void removeChildRenderObject(RenderBox child) {
+    if (renderObject.child == child) {
+      renderObject.child = null;
+    } else {
+      renderObject.remove(child);
+    }
+  }
+
+  @override
+  void visitChildren(ElementVisitor visitor) {
+    if (_onstage != null)
+      visitor(_onstage);
+    for (final Element child in _offstage) {
+      if (!_forgottenOffstageChildren.contains(child))
+        visitor(child);
+    }
+  }
+
   @override
   void debugVisitOnstageChildren(ElementVisitor visitor) {
-    assert(children.length >= widget.skipCount);
-    children.skip(widget.skipCount).forEach(visitor);
+    if (_onstage != null)
+      visitor(_onstage);
+  }
+
+  @override
+  bool forgetChild(Element child) {
+    if (child == _onstage) {
+      _onstage = null;
+    } else {
+      assert(_offstage.contains(child));
+      assert(!_forgottenOffstageChildren.contains(child));
+      _forgottenOffstageChildren.add(child);
+    }
+    return true;
+  }
+
+  @override
+  void mount(Element parent, dynamic newSlot) {
+    super.mount(parent, newSlot);
+    _onstage = updateChild(_onstage, widget.onstage, _onstageSlot);
+    _offstage = List<Element>(widget.offstage.length);
+    Element previousChild;
+    for (int i = 0; i < _offstage.length; i += 1) {
+      final Element newChild = inflateWidget(widget.offstage[i], previousChild);
+      _offstage[i] = newChild;
+      previousChild = newChild;
+    }
+  }
+
+  @override
+  void update(_Theatre newWidget) {
+    super.update(newWidget);
+    assert(widget == newWidget);
+    _onstage = updateChild(_onstage, widget.onstage, _onstageSlot);
+    _offstage = updateChildren(_offstage, widget.offstage, forgottenChildren: _forgottenOffstageChildren);
+    _forgottenOffstageChildren.clear();
   }
 }
 
-class _RenderTheatre extends RenderBox with ContainerRenderObjectMixin<RenderBox, StackParentData> {
-  _RenderTheatre({
-    List<RenderBox> children,
-    @required TextDirection textDirection,
-    int skipCount = 0,
-  }) : assert(skipCount != null),
-       assert(skipCount >= 0),
-       assert(textDirection != null),
-       _textDirection = textDirection,
-       _skipCount = skipCount {
-    addAll(children);
-  }
-
-  bool _hasVisualOverflow = false;
+// A render object which lays out and paints one subtree while keeping a list
+// of other subtrees alive but not laid out or painted (the "zombie" children).
+//
+// The subtree that is laid out and painted must be a [RenderStack].
+//
+// This class uses [StackParentData] objects for its parent data so that the
+// children of its primary subtree's stack can be moved to this object's list
+// of zombie children without changing their parent data objects.
+class _RenderTheatre extends RenderBox
+  with RenderObjectWithChildMixin<RenderStack>, RenderProxyBoxMixin<RenderStack>,
+       ContainerRenderObjectMixin<RenderBox, StackParentData> {
 
   @override
-  void setupParentData(RenderBox child) {
+  void setupParentData(RenderObject child) {
     if (child.parentData is! StackParentData)
       child.parentData = StackParentData();
   }
 
-  Alignment _resolvedAlignment;
-
-  void _resolve() {
-    if (_resolvedAlignment != null)
-      return;
-    _resolvedAlignment = AlignmentDirectional.topStart.resolve(textDirection);
-  }
-
-  void _markNeedResolution() {
-    _resolvedAlignment = null;
-    markNeedsLayout();
-  }
-
-  TextDirection get textDirection => _textDirection;
-  TextDirection _textDirection;
-  set textDirection(TextDirection value) {
-    if (_textDirection == value)
-      return;
-    _textDirection = value;
-    _markNeedResolution();
-  }
-
-  int get skipCount => _skipCount;
-  int _skipCount;
-  set skipCount(int value) {
-    assert(value != null);
-    if (_skipCount != value) {
-      _skipCount = value;
-      markNeedsLayout();
-    }
-  }
-
-  RenderBox get _firstOnstageChild {
-    if (skipCount == super.childCount) {
-      return null;
-    }
-    RenderBox child = super.firstChild;
-    for (int toSkip = skipCount; toSkip > 0; toSkip--) {
-      final StackParentData childParentData = child.parentData as StackParentData;
-      child = childParentData.nextSibling;
-      assert(child != null);
-    }
-    return child;
-  }
-
-  RenderBox get _lastOnstageChild => skipCount == super.childCount ? null : lastChild;
-
-  int get _onstageChildCount => childCount - skipCount;
+  // Because both RenderObjectWithChildMixin and ContainerRenderObjectMixin
+  // define redepthChildren, visitChildren and debugDescribeChildren and don't
+  // call super, we have to define them again here to make sure the work of both
+  // is done.
+  //
+  // We chose to put ContainerRenderObjectMixin last in the inheritance chain so
+  // that we can call super to hit its more complex definitions of
+  // redepthChildren and visitChildren, and then duplicate the more trivial
+  // definition from RenderObjectWithChildMixin inline in our version here.
+  //
+  // This code duplication is suboptimal.
+  // TODO(ianh): Replace this with a better solution once https://github.com/dart-lang/sdk/issues/27100 is fixed
+  //
+  // For debugDescribeChildren we just roll our own because otherwise the line
+  // drawings won't really work as well.
 
   @override
-  double computeMinIntrinsicWidth(double height) {
-    return RenderStack.getIntrinsicDimension(_firstOnstageChild, (RenderBox child) => child.getMinIntrinsicWidth(height));
+  void redepthChildren() {
+    if (child != null)
+      redepthChild(child);
+    super.redepthChildren();
   }
 
   @override
-  double computeMaxIntrinsicWidth(double height) {
-    return RenderStack.getIntrinsicDimension(_firstOnstageChild, (RenderBox child) => child.getMaxIntrinsicWidth(height));
-  }
-
-  @override
-  double computeMinIntrinsicHeight(double width) {
-    return RenderStack.getIntrinsicDimension(_firstOnstageChild, (RenderBox child) => child.getMinIntrinsicHeight(width));
-  }
-
-  @override
-  double computeMaxIntrinsicHeight(double width) {
-    return RenderStack.getIntrinsicDimension(_firstOnstageChild, (RenderBox child) => child.getMaxIntrinsicHeight(width));
-  }
-
-  @override
-  double computeDistanceToActualBaseline(TextBaseline baseline) {
-    assert(!debugNeedsLayout);
-    double result;
-    RenderBox child = _firstOnstageChild;
-    while (child != null) {
-      assert(!child.debugNeedsLayout);
-      final StackParentData childParentData = child.parentData as StackParentData;
-      double candidate = child.getDistanceToActualBaseline(baseline);
-      if (candidate != null) {
-        candidate += childParentData.offset.dy;
-        if (result != null) {
-          result = math.min(result, candidate);
-        } else {
-          result = candidate;
-        }
-      }
-      child = childParentData.nextSibling;
-    }
-    return result;
-  }
-
-  @override
-  bool get sizedByParent => true;
-
-  @override
-  void performResize() {
-    size = constraints.biggest;
-    assert(size.isFinite);
-  }
-
-  @override
-  void performLayout() {
-    _hasVisualOverflow = false;
-
-    if (_onstageChildCount == 0) {
-      return;
-    }
-
-    _resolve();
-    assert(_resolvedAlignment != null);
-
-    // Same BoxConstraints as used by RenderStack for StackFit.expand.
-    final BoxConstraints nonPositionedConstraints = BoxConstraints.tight(constraints.biggest);
-
-    RenderBox child = _firstOnstageChild;
-    while (child != null) {
-      final StackParentData childParentData = child.parentData as StackParentData;
-
-      if (!childParentData.isPositioned) {
-        child.layout(nonPositionedConstraints, parentUsesSize: true);
-        childParentData.offset = _resolvedAlignment.alongOffset(size - child.size as Offset);
-      } else {
-        _hasVisualOverflow = RenderStack.layoutPositionedChild(child, childParentData, size, _resolvedAlignment) || _hasVisualOverflow;
-      }
-
-      assert(child.parentData == childParentData);
-      child = childParentData.nextSibling;
-    }
-  }
-
-  @override
-  bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
-    RenderBox child = _lastOnstageChild;
-    for (int i = 0; i < _onstageChildCount; i++) {
-      assert(child != null);
-      final StackParentData childParentData = child.parentData as StackParentData;
-      final bool isHit = result.addWithPaintOffset(
-        offset: childParentData.offset,
-        position: position,
-        hitTest: (BoxHitTestResult result, Offset transformed) {
-          assert(transformed == position - childParentData.offset);
-          return child.hitTest(result, position: transformed);
-        },
-      );
-      if (isHit)
-        return true;
-      child = childParentData.previousSibling;
-    }
-    return false;
-  }
-
-  @protected
-  void paintStack(PaintingContext context, Offset offset) {
-    RenderBox child = _firstOnstageChild;
-    while (child != null) {
-      final StackParentData childParentData = child.parentData as StackParentData;
-      context.paintChild(child, childParentData.offset + offset);
-      child = childParentData.nextSibling;
-    }
-  }
-
-  @override
-  void paint(PaintingContext context, Offset offset) {
-    if (_hasVisualOverflow) {
-      context.pushClipRect(needsCompositing, offset, Offset.zero & size, paintStack);
-    } else {
-      paintStack(context, offset);
-    }
-  }
-
-  @override
-  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
-    RenderBox child = _firstOnstageChild;
-    while (child != null) {
+  void visitChildren(RenderObjectVisitor visitor) {
+    if (child != null)
       visitor(child);
-      final StackParentData childParentData = child.parentData as StackParentData;
-      child = childParentData.nextSibling;
-    }
-  }
-
-  @override
-  Rect describeApproximatePaintClip(RenderObject child) => _hasVisualOverflow ? Offset.zero & size : null;
-
-  @override
-  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
-    super.debugFillProperties(properties);
-    properties.add(IntProperty('skipCount', skipCount));
-    properties.add(EnumProperty<TextDirection>('textDirection', textDirection));
+    super.visitChildren(visitor);
   }
 
   @override
   List<DiagnosticsNode> debugDescribeChildren() {
-    final List<DiagnosticsNode> offstageChildren = <DiagnosticsNode>[];
-    final List<DiagnosticsNode> onstageChildren = <DiagnosticsNode>[];
+    final List<DiagnosticsNode> children = <DiagnosticsNode>[
+      if (child != null) child.toDiagnosticsNode(name: 'onstage'),
+    ];
 
-    int count = 1;
-    bool onstage = false;
-    RenderBox child = firstChild;
-    final RenderBox firstOnstageChild = _firstOnstageChild;
-    while (child != null) {
-      if (child == firstOnstageChild) {
-        onstage = true;
-        count = 1;
-      }
+    if (firstChild != null) {
+      RenderBox child = firstChild;
 
-      if (onstage) {
-        onstageChildren.add(
-          child.toDiagnosticsNode(
-            name: 'onstage $count',
-          ),
-        );
-      } else {
-        offstageChildren.add(
+      int count = 1;
+      while (true) {
+        children.add(
           child.toDiagnosticsNode(
             name: 'offstage $count',
             style: DiagnosticsTreeStyle.offstage,
           ),
         );
+        if (child == lastChild)
+          break;
+        final StackParentData childParentData = child.parentData as StackParentData;
+        child = childParentData.nextSibling;
+        count += 1;
       }
-
-      final StackParentData childParentData = child.parentData as StackParentData;
-      child = childParentData.nextSibling;
-      count += 1;
-    }
-
-    return <DiagnosticsNode>[
-      ...onstageChildren,
-      if (offstageChildren.isNotEmpty)
-        ...offstageChildren
-      else
+    } else {
+      children.add(
         DiagnosticsNode.message(
           'no offstage children',
           style: DiagnosticsTreeStyle.offstage,
         ),
-    ];
+      );
+    }
+    return children;
+  }
+
+  @override
+  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
+    if (child != null)
+      visitor(child);
   }
 }
diff --git a/packages/flutter/test/cupertino/nav_bar_transition_test.dart b/packages/flutter/test/cupertino/nav_bar_transition_test.dart
index f65d4b0..dda551e 100644
--- a/packages/flutter/test/cupertino/nav_bar_transition_test.dart
+++ b/packages/flutter/test/cupertino/nav_bar_transition_test.dart
@@ -72,9 +72,12 @@
 }
 
 Finder flying(WidgetTester tester, Finder finder) {
-  final ContainerRenderObjectMixin<RenderBox, StackParentData> theater = tester.renderObject(find.byType(Overlay));
+  final RenderObjectWithChildMixin<RenderStack> theater =
+      tester.renderObject(find.byType(Overlay));
+  final RenderStack theaterStack = theater.child;
   final Finder lastOverlayFinder = find.byElementPredicate((Element element) {
-    return element is RenderObjectElement && element.renderObject == theater.lastChild;
+    return element is RenderObjectElement &&
+        element.renderObject == theaterStack.lastChild;
   });
 
   assert(
diff --git a/packages/flutter/test/material/debug_test.dart b/packages/flutter/test/material/debug_test.dart
index 8ab0c9a..0874371 100644
--- a/packages/flutter/test/material/debug_test.dart
+++ b/packages/flutter/test/material/debug_test.dart
@@ -132,8 +132,8 @@
       '     Offstage\n'
       '     _ModalScopeStatus\n'
       '     _ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#969b7]\n'
-      '     TickerMode\n'
-      '     _OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#545d0]\n'
+      '     _OverlayEntry-[LabeledGlobalKey<_OverlayEntryState>#7a3ae]\n'
+      '     Stack\n'
       '     _Theatre\n'
       '     Overlay-[LabeledGlobalKey<OverlayState>#31a52]\n'
       '     _FocusMarker\n'
diff --git a/packages/flutter/test/material/stepper_test.dart b/packages/flutter/test/material/stepper_test.dart
index bd6266b..13ee688 100644
--- a/packages/flutter/test/material/stepper_test.dart
+++ b/packages/flutter/test/material/stepper_test.dart
@@ -529,13 +529,12 @@
     // which will change depending on where the test is run.
     expect(lines.length, greaterThan(7));
     expect(
-      lines.take(9).join('\n'),
+      lines.take(8).join('\n'),
       equalsIgnoringHashCodes(
         '══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞════════════════════════\n'
         'The following assertion was thrown building Stepper(dirty,\n'
-        'dependencies: [TickerMode,\n'
-        '_LocalizationsScope-[GlobalKey#6b31b]], state:\n'
-        '_StepperState#1bf00):\n'
+        'dependencies: [_LocalizationsScope-[GlobalKey#00000]], state:\n'
+        '_StepperState#00000):\n'
         'Steppers must not be nested.\n'
         'The material specification advises that one should avoid\n'
         'embedding steppers within steppers.\n'
diff --git a/packages/flutter/test/widgets/navigator_test.dart b/packages/flutter/test/widgets/navigator_test.dart
index aace92d..5522c11 100644
--- a/packages/flutter/test/widgets/navigator_test.dart
+++ b/packages/flutter/test/widgets/navigator_test.dart
@@ -1186,33 +1186,6 @@
     expect(find.byKey(const ValueKey<String>('/A/B')), findsNothing); // popped
     expect(find.byKey(const ValueKey<String>('/C')), findsOneWidget);
   });
-
-  testWidgets('Pushing opaque Route does not rebuild routes below', (WidgetTester tester) async {
-    // Regression test for https://github.com/flutter/flutter/issues/45797.
-
-    final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
-    final Key bottomRoute = UniqueKey();
-    final Key topRoute = UniqueKey();
-    await tester.pumpWidget(
-      MaterialApp(
-        navigatorKey: navigator,
-        routes: <String, WidgetBuilder>{
-          '/' : (BuildContext context) => StatefulTestWidget(key: bottomRoute),
-          '/a': (BuildContext context) => StatefulTestWidget(key: topRoute),
-        },
-      ),
-    );
-    expect(tester.state<StatefulTestState>(find.byKey(bottomRoute)).rebuildCount, 1);
-
-    navigator.currentState.pushNamed('/a');
-    await tester.pumpAndSettle();
-
-    // Bottom route is offstage and did not rebuild.
-    expect(find.byKey(bottomRoute), findsNothing);
-    expect(tester.state<StatefulTestState>(find.byKey(bottomRoute, skipOffstage: false)).rebuildCount, 1);
-
-    expect(tester.state<StatefulTestState>(find.byKey(topRoute)).rebuildCount, 1);
-  });
 }
 
 class NoAnimationPageRoute extends PageRouteBuilder<void> {
@@ -1226,20 +1199,3 @@
     return super.createAnimationController()..value = 1.0;
   }
 }
-
-class StatefulTestWidget extends StatefulWidget {
-  const StatefulTestWidget({Key key}) : super(key: key);
-
-  @override
-  State<StatefulTestWidget> createState() => StatefulTestState();
-}
-
-class StatefulTestState extends State<StatefulTestWidget> {
-  int rebuildCount = 0;
-
-  @override
-  Widget build(BuildContext context) {
-    rebuildCount += 1;
-    return Container();
-  }
-}
diff --git a/packages/flutter/test/widgets/overlay_test.dart b/packages/flutter/test/widgets/overlay_test.dart
index 21b60d8..0453fcd 100644
--- a/packages/flutter/test/widgets/overlay_test.dart
+++ b/packages/flutter/test/widgets/overlay_test.dart
@@ -6,8 +6,6 @@
 import 'package:flutter_test/flutter_test.dart';
 import 'package:flutter/widgets.dart';
 
-import 'semantics_tester.dart';
-
 void main() {
   testWidgets('OverflowEntries context contains Overlay', (WidgetTester tester) async {
     final GlobalKey overlayKey = GlobalKey();
@@ -27,9 +25,6 @@
                 return Container();
               },
             ),
-            OverlayEntry(
-              builder: (BuildContext context) => Container(),
-            )
           ],
         ),
       ),
@@ -41,42 +36,36 @@
     expect(
       theater.toStringDeep(minLevel: DiagnosticLevel.info),
       equalsIgnoringHashCodes(
-        '_RenderTheatre#744c9\n'
-        ' │ parentData: <none>\n'
-        ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
-        ' │ size: Size(800.0, 600.0)\n'
-        ' │ skipCount: 0\n'
-        ' │ textDirection: ltr\n'
-        ' │\n'
-        ' ├─onstage 1: RenderLimitedBox#bb803\n'
-        ' │ │ parentData: not positioned; offset=Offset(0.0, 0.0) (can use\n'
-        ' │ │   size)\n'
-        ' │ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
-        ' │ │ size: Size(800.0, 600.0)\n'
-        ' │ │ maxWidth: 0.0\n'
-        ' │ │ maxHeight: 0.0\n'
-        ' │ │\n'
-        ' │ └─child: RenderConstrainedBox#62707\n'
-        ' │     parentData: <none> (can use size)\n'
-        ' │     constraints: BoxConstraints(w=800.0, h=600.0)\n'
-        ' │     size: Size(800.0, 600.0)\n'
-        ' │     additionalConstraints: BoxConstraints(biggest)\n'
-        ' │\n'
-        ' ├─onstage 2: RenderLimitedBox#af5f1\n'
-        ' ╎ │ parentData: not positioned; offset=Offset(0.0, 0.0) (can use\n'
-        ' ╎ │   size)\n'
-        ' ╎ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
-        ' ╎ │ size: Size(800.0, 600.0)\n'
-        ' ╎ │ maxWidth: 0.0\n'
-        ' ╎ │ maxHeight: 0.0\n'
-        ' ╎ │\n'
-        ' ╎ └─child: RenderConstrainedBox#69c48\n'
-        ' ╎     parentData: <none> (can use size)\n'
-        ' ╎     constraints: BoxConstraints(w=800.0, h=600.0)\n'
-        ' ╎     size: Size(800.0, 600.0)\n'
-        ' ╎     additionalConstraints: BoxConstraints(biggest)\n'
-        ' ╎\n'
-        ' └╌no offstage children\n'
+        '_RenderTheatre#f5cf2\n'
+            ' │ parentData: <none>\n'
+            ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
+            ' │ size: Size(800.0, 600.0)\n'
+            ' │\n'
+            ' ├─onstage: RenderStack#39819\n'
+            ' ╎ │ parentData: not positioned; offset=Offset(0.0, 0.0) (can use\n'
+            ' ╎ │   size)\n'
+            ' ╎ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
+            ' ╎ │ size: Size(800.0, 600.0)\n'
+            ' ╎ │ alignment: AlignmentDirectional.topStart\n'
+            ' ╎ │ textDirection: ltr\n'
+            ' ╎ │ fit: expand\n'
+            ' ╎ │ overflow: clip\n'
+            ' ╎ │\n'
+            ' ╎ └─child 1: RenderLimitedBox#d1448\n'
+            ' ╎   │ parentData: not positioned; offset=Offset(0.0, 0.0) (can use\n'
+            ' ╎   │   size)\n'
+            ' ╎   │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
+            ' ╎   │ size: Size(800.0, 600.0)\n'
+            ' ╎   │ maxWidth: 0.0\n'
+            ' ╎   │ maxHeight: 0.0\n'
+            ' ╎   │\n'
+            ' ╎   └─child: RenderConstrainedBox#e8b87\n'
+            ' ╎       parentData: <none> (can use size)\n'
+            ' ╎       constraints: BoxConstraints(w=800.0, h=600.0)\n'
+            ' ╎       size: Size(800.0, 600.0)\n'
+            ' ╎       additionalConstraints: BoxConstraints(biggest)\n'
+            ' ╎\n'
+            ' └╌no offstage children\n'
       ),
     );
   });
@@ -114,52 +103,60 @@
     expect(
       theater.toStringDeep(minLevel: DiagnosticLevel.info),
       equalsIgnoringHashCodes(
-        '_RenderTheatre#385b3\n'
+        '_RenderTheatre#b22a8\n'
         ' │ parentData: <none>\n'
         ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
         ' │ size: Size(800.0, 600.0)\n'
-        ' │ skipCount: 2\n'
-        ' │ textDirection: ltr\n'
         ' │\n'
-        ' ├─onstage 1: RenderLimitedBox#0a77a\n'
+        ' ├─onstage: RenderStack#eab87\n'
         ' ╎ │ parentData: not positioned; offset=Offset(0.0, 0.0) (can use\n'
         ' ╎ │   size)\n'
         ' ╎ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
         ' ╎ │ size: Size(800.0, 600.0)\n'
-        ' ╎ │ maxWidth: 0.0\n'
-        ' ╎ │ maxHeight: 0.0\n'
+        ' ╎ │ alignment: AlignmentDirectional.topStart\n'
+        ' ╎ │ textDirection: ltr\n'
+        ' ╎ │ fit: expand\n'
+        ' ╎ │ overflow: clip\n'
         ' ╎ │\n'
-        ' ╎ └─child: RenderConstrainedBox#21f3a\n'
-        ' ╎     parentData: <none> (can use size)\n'
-        ' ╎     constraints: BoxConstraints(w=800.0, h=600.0)\n'
-        ' ╎     size: Size(800.0, 600.0)\n'
-        ' ╎     additionalConstraints: BoxConstraints(biggest)\n'
+        ' ╎ └─child 1: RenderLimitedBox#ca15b\n'
+        ' ╎   │ parentData: not positioned; offset=Offset(0.0, 0.0) (can use\n'
+        ' ╎   │   size)\n'
+        ' ╎   │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
+        ' ╎   │ size: Size(800.0, 600.0)\n'
+        ' ╎   │ maxWidth: 0.0\n'
+        ' ╎   │ maxHeight: 0.0\n'
+        ' ╎   │\n'
+        ' ╎   └─child: RenderConstrainedBox#dffe5\n'
+        ' ╎       parentData: <none> (can use size)\n'
+        ' ╎       constraints: BoxConstraints(w=800.0, h=600.0)\n'
+        ' ╎       size: Size(800.0, 600.0)\n'
+        ' ╎       additionalConstraints: BoxConstraints(biggest)\n'
         ' ╎\n'
-        ' ╎╌offstage 1: RenderLimitedBox#62c8c NEEDS-LAYOUT NEEDS-PAINT\n'
+        ' ╎╌offstage 1: RenderLimitedBox#b6f09 NEEDS-LAYOUT NEEDS-PAINT\n'
         ' ╎ │ parentData: not positioned; offset=Offset(0.0, 0.0)\n'
         ' ╎ │ constraints: MISSING\n'
         ' ╎ │ size: MISSING\n'
         ' ╎ │ maxWidth: 0.0\n'
         ' ╎ │ maxHeight: 0.0\n'
         ' ╎ │\n'
-        ' ╎ └─child: RenderConstrainedBox#425fa NEEDS-LAYOUT NEEDS-PAINT\n'
+        ' ╎ └─child: RenderConstrainedBox#5a057 NEEDS-LAYOUT NEEDS-PAINT\n'
         ' ╎     parentData: <none>\n'
         ' ╎     constraints: MISSING\n'
         ' ╎     size: MISSING\n'
         ' ╎     additionalConstraints: BoxConstraints(biggest)\n'
         ' ╎\n'
-        ' └╌offstage 2: RenderLimitedBox#03ae2 NEEDS-LAYOUT NEEDS-PAINT\n'
+        ' └╌offstage 2: RenderLimitedBox#f689e NEEDS-LAYOUT NEEDS-PAINT\n'
         '   │ parentData: not positioned; offset=Offset(0.0, 0.0)\n'
         '   │ constraints: MISSING\n'
         '   │ size: MISSING\n'
         '   │ maxWidth: 0.0\n'
         '   │ maxHeight: 0.0\n'
         '   │\n'
-        '   └─child: RenderConstrainedBox#b4d48 NEEDS-LAYOUT NEEDS-PAINT\n'
+        '   └─child: RenderConstrainedBox#c15f0 NEEDS-LAYOUT NEEDS-PAINT\n'
         '       parentData: <none>\n'
         '       constraints: MISSING\n'
         '       size: MISSING\n'
-        '       additionalConstraints: BoxConstraints(biggest)\n',
+        '       additionalConstraints: BoxConstraints(biggest)\n'
       ),
     );
   });
@@ -701,261 +698,4 @@
     expect(find.byKey(root), findsNothing);
     expect(find.byKey(top), findsOneWidget);
   });
-
-  testWidgets('OverlayEntries do not rebuild when opaqueness changes', (WidgetTester tester) async {
-    // Regression test for https://github.com/flutter/flutter/issues/45797.
-
-    final GlobalKey<OverlayState> overlayKey = GlobalKey<OverlayState>();
-    final Key bottom = UniqueKey();
-    final Key middle = UniqueKey();
-    final Key top = UniqueKey();
-    final Widget bottomWidget = StatefulTestWidget(key: bottom);
-    final Widget middleWidget = StatefulTestWidget(key: middle);
-    final Widget topWidget = StatefulTestWidget(key: top);
-
-    final OverlayEntry bottomEntry = OverlayEntry(
-      maintainState: true,
-      builder: (BuildContext context) {
-        return bottomWidget;
-      },
-    );
-    final OverlayEntry middleEntry = OverlayEntry(
-      maintainState: true,
-      builder: (BuildContext context) {
-        return middleWidget;
-      },
-    );
-    final OverlayEntry topEntry = OverlayEntry(
-      maintainState: true,
-      builder: (BuildContext context) {
-        return topWidget;
-      },
-    );
-
-    await tester.pumpWidget(
-      Directionality(
-        textDirection: TextDirection.ltr,
-        child: Overlay(
-          key: overlayKey,
-          initialEntries: <OverlayEntry>[
-            bottomEntry,
-            middleEntry,
-            topEntry,
-          ],
-        ),
-      ),
-    );
-
-    // All widgets are onstage.
-    expect(tester.state<StatefulTestState>(find.byKey(bottom)).rebuildCount, 1);
-    expect(tester.state<StatefulTestState>(find.byKey(middle)).rebuildCount, 1);
-    expect(tester.state<StatefulTestState>(find.byKey(top)).rebuildCount, 1);
-
-    middleEntry.opaque = true;
-    await tester.pump();
-
-    // Bottom widget is offstage and did not rebuild.
-    expect(find.byKey(bottom), findsNothing);
-    expect(tester.state<StatefulTestState>(find.byKey(bottom, skipOffstage: false)).rebuildCount, 1);
-    expect(tester.state<StatefulTestState>(find.byKey(middle)).rebuildCount, 1);
-    expect(tester.state<StatefulTestState>(find.byKey(top)).rebuildCount, 1);
-  });
-
-  testWidgets('OverlayEntries do not rebuild when opaque entry is added', (WidgetTester tester) async {
-    // Regression test for https://github.com/flutter/flutter/issues/45797.
-
-    final GlobalKey<OverlayState> overlayKey = GlobalKey<OverlayState>();
-    final Key bottom = UniqueKey();
-    final Key middle = UniqueKey();
-    final Key top = UniqueKey();
-    final Widget bottomWidget = StatefulTestWidget(key: bottom);
-    final Widget middleWidget = StatefulTestWidget(key: middle);
-    final Widget topWidget = StatefulTestWidget(key: top);
-
-    final OverlayEntry bottomEntry = OverlayEntry(
-      maintainState: true,
-      builder: (BuildContext context) {
-        return bottomWidget;
-      },
-    );
-    final OverlayEntry middleEntry = OverlayEntry(
-      opaque: true,
-      maintainState: true,
-      builder: (BuildContext context) {
-        return middleWidget;
-      },
-    );
-    final OverlayEntry topEntry = OverlayEntry(
-      maintainState: true,
-      builder: (BuildContext context) {
-        return topWidget;
-      },
-    );
-
-    await tester.pumpWidget(
-      Directionality(
-        textDirection: TextDirection.ltr,
-        child: Overlay(
-          key: overlayKey,
-          initialEntries: <OverlayEntry>[
-            bottomEntry,
-            topEntry,
-          ],
-        ),
-      ),
-    );
-
-    // Both widgets are onstage.
-    expect(tester.state<StatefulTestState>(find.byKey(bottom)).rebuildCount, 1);
-    expect(tester.state<StatefulTestState>(find.byKey(top)).rebuildCount, 1);
-
-    overlayKey.currentState.rearrange(<OverlayEntry>[
-      bottomEntry, middleEntry, topEntry,
-    ]);
-    await tester.pump();
-
-    // Bottom widget is offstage and did not rebuild.
-    expect(find.byKey(bottom), findsNothing);
-    expect(tester.state<StatefulTestState>(find.byKey(bottom, skipOffstage: false)).rebuildCount, 1);
-    expect(tester.state<StatefulTestState>(find.byKey(middle)).rebuildCount, 1);
-    expect(tester.state<StatefulTestState>(find.byKey(top)).rebuildCount, 1);
-  });
-
-  testWidgets('entries below opaque entries are ignored for hit testing', (WidgetTester tester) async {
-    final GlobalKey<OverlayState> overlayKey = GlobalKey<OverlayState>();
-    int bottomTapCount = 0;
-    await tester.pumpWidget(
-      Directionality(
-        textDirection: TextDirection.ltr,
-        child: Overlay(
-          key: overlayKey,
-          initialEntries: <OverlayEntry>[
-            OverlayEntry(
-              maintainState: true,
-              builder: (BuildContext context) {
-                return GestureDetector(
-                  onTap: () {
-                    bottomTapCount++;
-                  },
-                );
-              },
-            ),
-          ],
-        ),
-      ),
-    );
-
-    expect(bottomTapCount, 0);
-    await tester.tap(find.byKey(overlayKey));
-    expect(bottomTapCount, 1);
-
-    overlayKey.currentState.insert(OverlayEntry(
-      maintainState: true,
-      opaque: true,
-      builder: (BuildContext context) {
-        return Container();
-      },
-    ));
-    await tester.pump();
-
-    // Bottom is offstage and does not receive tap events.
-    expect(find.byType(GestureDetector), findsNothing);
-    expect(find.byType(GestureDetector, skipOffstage: false), findsOneWidget);
-    await tester.tap(find.byKey(overlayKey));
-    expect(bottomTapCount, 1);
-
-    int topTapCount = 0;
-    overlayKey.currentState.insert(OverlayEntry(
-      maintainState: true,
-      opaque: true,
-      builder: (BuildContext context) {
-        return GestureDetector(
-          onTap: () {
-            topTapCount++;
-          },
-        );
-      },
-    ));
-    await tester.pump();
-
-    expect(topTapCount, 0);
-    await tester.tap(find.byKey(overlayKey));
-    expect(topTapCount, 1);
-    expect(bottomTapCount, 1);
-  });
-
-  testWidgets('Semantics of entries below opaque entries are ignored', (WidgetTester tester) async {
-    final SemanticsTester semantics = SemanticsTester(tester);
-    final GlobalKey<OverlayState> overlayKey = GlobalKey<OverlayState>();
-    await tester.pumpWidget(
-      Directionality(
-        textDirection: TextDirection.ltr,
-        child: Overlay(
-          key: overlayKey,
-          initialEntries: <OverlayEntry>[
-            OverlayEntry(
-              maintainState: true,
-              builder: (BuildContext context) {
-                return const Text('bottom');
-              },
-            ),
-            OverlayEntry(
-              maintainState: true,
-              opaque: true,
-              builder: (BuildContext context) {
-                return const Text('top');
-              },
-            ),
-          ],
-        ),
-      ),
-    );
-    expect(find.text('bottom'), findsNothing);
-    expect(find.text('bottom', skipOffstage: false), findsOneWidget);
-    expect(find.text('top'), findsOneWidget);
-    expect(semantics, includesNodeWith(label: 'top'));
-    expect(semantics, isNot(includesNodeWith(label: 'bottom')));
-
-    semantics.dispose();
-  });
-
-  testWidgets('Can used Positioned within OverlayEntry', (WidgetTester tester) async {
-    await tester.pumpWidget(
-      Directionality(
-        textDirection: TextDirection.ltr,
-        child: Overlay(
-          initialEntries: <OverlayEntry>[
-            OverlayEntry(
-              builder: (BuildContext context) {
-                return const Positioned(
-                  left: 145,
-                  top: 123,
-                  child: Text('positioned child'),
-                );
-              },
-            ),
-          ],
-        ),
-      ),
-    );
-
-    expect(tester.getTopLeft(find.text('positioned child')), const Offset(145, 123));
-  });
-}
-
-class StatefulTestWidget extends StatefulWidget {
-  const StatefulTestWidget({Key key}) : super(key: key);
-
-  @override
-  State<StatefulTestWidget> createState() => StatefulTestState();
-}
-
-class StatefulTestState extends State<StatefulTestWidget> {
-  int rebuildCount = 0;
-
-  @override
-  Widget build(BuildContext context) {
-    rebuildCount += 1;
-    return Container();
-  }
 }