Implicit a11y scrolling for iOS (and caching in Viewports) (#17021)

diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart
index d654366..f49c46f 100644
--- a/packages/flutter/lib/src/rendering/object.dart
+++ b/packages/flutter/lib/src/rendering/object.dart
@@ -2106,6 +2106,27 @@
   /// that are not physically visible.
   Rect describeApproximatePaintClip(covariant RenderObject child) => null;
 
+  /// Returns a rect in this object's coordinate system that describes
+  /// which [SemanticsNode]s produced by the `child` should be included in the
+  /// semantics tree. [SemanticsNode]s from the `child` that are positioned
+  /// outside of this rect will be dropped. Child [SemanticsNode]s that are
+  /// positioned inside this rect, but outside of [describeApproximatePaintClip]
+  /// will be included in the tree marked as hidden. Child [SemanticsNode]s
+  /// that are inside of both rect will be included in the tree as regular
+  /// nodes.
+  ///
+  /// This method only returns a non-null value if the semantics clip rect
+  /// is different from the rect returned by [describeApproximatePaintClip].
+  /// If the semantics clip rect and the paint clip rect are the same, this
+  /// method returns null.
+  ///
+  /// A viewport would typically implement this method to include semantic nodes
+  /// in the semantics tree that are currently hidden just before the leading
+  /// or just after the trailing edge. These nodes have to be included in the
+  /// semantics tree to implement implicit accessibility scrolling on iOS where
+  /// the viewport scrolls implicitly when moving the accessibility focus from
+  /// a the last visible node in the viewport to the first hidden one.
+  Rect describeSemanticsClip(covariant RenderObject child) => null;
 
   // SEMANTICS
 
@@ -2279,7 +2300,10 @@
     );
     assert(fragment is _InterestingSemanticsFragment);
     final _InterestingSemanticsFragment interestingFragment = fragment;
-    final SemanticsNode node = interestingFragment.compileChildren(_semantics?.parentClipRect).single;
+    final SemanticsNode node = interestingFragment.compileChildren(
+      parentSemanticsClipRect: _semantics?.parentSemanticsClipRect,
+      parentPaintClipRect: _semantics?.parentPaintClipRect,
+    ).single;
     // Fragment only wants to add this node's SemanticsNode to the parent.
     assert(interestingFragment.config == null && node == _semantics);
   }
@@ -3025,7 +3049,10 @@
   final List<RenderObject> _ancestorChain;
 
   /// The children to be added to the parent.
-  Iterable<SemanticsNode> compileChildren(Rect parentClipRect);
+  Iterable<SemanticsNode> compileChildren({
+    @required Rect parentSemanticsClipRect,
+    @required Rect parentPaintClipRect
+  });
 
   /// The [SemanticsConfiguration] the child wants to merge into the parent's
   /// [SemanticsNode] or null if it doesn't want to merge anything.
@@ -3093,9 +3120,10 @@
   }) : super(owner: owner, dropsSemanticsOfPreviousSiblings: dropsSemanticsOfPreviousSiblings);
 
   @override
-  Iterable<SemanticsNode> compileChildren(Rect parentClipRect) sync* {
+  Iterable<SemanticsNode> compileChildren({Rect parentSemanticsClipRect, Rect parentPaintClipRect}) sync* {
     assert(_tagsForChildren == null || _tagsForChildren.isEmpty);
-    assert(parentClipRect == null);
+    assert(parentSemanticsClipRect == null);
+    assert(parentPaintClipRect == null);
     assert(_ancestorChain.length == 1);
 
     owner._semantics ??= new SemanticsNode.root(
@@ -3104,14 +3132,18 @@
     );
     final SemanticsNode node = owner._semantics;
     assert(MatrixUtils.matrixEquals(node.transform, new Matrix4.identity()));
-    assert(node.parentClipRect == null);
+    assert(node.parentSemanticsClipRect == null);
+    assert(node.parentPaintClipRect == null);
 
     node.rect = owner.semanticBounds;
 
     final List<SemanticsNode> children = <SemanticsNode>[];
     for (_InterestingSemanticsFragment fragment in _children) {
       assert(fragment.config == null);
-      children.addAll(fragment.compileChildren(parentClipRect));
+      children.addAll(fragment.compileChildren(
+        parentSemanticsClipRect: parentSemanticsClipRect,
+        parentPaintClipRect: parentPaintClipRect,
+      ));
     }
     node.updateWith(config: null, childrenInInversePaintOrder: children);
 
@@ -3171,22 +3203,22 @@
   final List<_InterestingSemanticsFragment> _children = <_InterestingSemanticsFragment>[];
 
   @override
-  Iterable<SemanticsNode> compileChildren(Rect parentClipRect) sync* {
+  Iterable<SemanticsNode> compileChildren({Rect parentSemanticsClipRect, Rect parentPaintClipRect}) sync* {
     if (!_isExplicit) {
       owner._semantics = null;
       for (_InterestingSemanticsFragment fragment in _children) {
         assert(_ancestorChain.first == fragment._ancestorChain.last);
         fragment._ancestorChain.addAll(_ancestorChain.sublist(1));
-        yield* fragment.compileChildren(parentClipRect);
+        yield* fragment.compileChildren(parentSemanticsClipRect: parentSemanticsClipRect, parentPaintClipRect: parentPaintClipRect);
       }
       return;
     }
 
     final _SemanticsGeometry geometry = _needsGeometryUpdate
-        ? new _SemanticsGeometry(parentClipRect: parentClipRect, ancestors: _ancestorChain)
+        ? new _SemanticsGeometry(parentSemanticsClipRect: parentSemanticsClipRect, parentPaintClipRect: parentPaintClipRect, ancestors: _ancestorChain)
         : null;
 
-    if (!_mergeIntoParent && (geometry?.isInvisible == true))
+    if (!_mergeIntoParent && (geometry?.dropFromTree == true))
       return;  // Drop the node, it's not going to be visible.
 
     owner._semantics ??= new SemanticsNode(showOnScreen: owner.showOnScreen);
@@ -3199,12 +3231,17 @@
       node
         ..rect = geometry.rect
         ..transform = geometry.transform
-        ..parentClipRect = geometry.clipRect;
+        ..parentSemanticsClipRect = geometry.semanticsClipRect
+        ..parentPaintClipRect = geometry.paintClipRect;
+      if (!_mergeIntoParent && geometry.markAsHidden) {
+        _ensureConfigIsWritable();
+        _config.isHidden = true;
+      }
     }
 
     final List<SemanticsNode> children = <SemanticsNode>[];
     for (_InterestingSemanticsFragment fragment in _children)
-      children.addAll(fragment.compileChildren(node.parentClipRect));
+      children.addAll(fragment.compileChildren(parentSemanticsClipRect: node.parentSemanticsClipRect, parentPaintClipRect: node.parentPaintClipRect));
 
     if (_config.isSemanticBoundary) {
       owner.assembleSemanticsNode(node, _config, children);
@@ -3226,14 +3263,18 @@
       _children.add(fragment);
       if (fragment.config == null)
         continue;
-      if (!_isConfigWritable) {
-        _config = _config.copy();
-        _isConfigWritable = true;
-      }
+      _ensureConfigIsWritable();
       _config.absorb(fragment.config);
     }
   }
 
+  void _ensureConfigIsWritable() {
+    if (!_isConfigWritable) {
+      _config = _config.copy();
+      _isConfigWritable = true;
+    }
+  }
+
   bool _isExplicit = false;
 
   @override
@@ -3257,61 +3298,97 @@
   /// (first [RenderObject] in the list) and its closest ancestor [RenderObject]
   /// that also owns its own [SemanticsNode] (last [RenderObject] in the list).
   _SemanticsGeometry({
-    @required Rect parentClipRect,
+    @required Rect parentSemanticsClipRect,
+    @required Rect parentPaintClipRect,
     @required List<RenderObject> ancestors,
   }) {
-    _computeValues(parentClipRect, ancestors);
+    _computeValues(parentSemanticsClipRect, parentPaintClipRect, ancestors);
   }
 
-  Rect _clipRect;
+  Rect _paintClipRect;
+  Rect _semanticsClipRect;
   Matrix4 _transform;
   Rect _rect;
 
   /// Value for [SemanticsNode.transform].
   Matrix4 get transform => _transform;
 
-  /// Value for [SemanticsNode.parentClipRect].
-  Rect get clipRect => _clipRect;
+  /// Value for [SemanticsNode.parentSemanticsClipRect].
+  Rect get semanticsClipRect => _semanticsClipRect;
+
+  /// Value for [SemanticsNode.parentPaintClipRect].
+  Rect get paintClipRect => _paintClipRect;
 
   /// Value for [SemanticsNode.rect].
   Rect get rect => _rect;
 
-  void _computeValues(Rect parentClipRect, List<RenderObject> ancestors) {
+  void _computeValues(Rect parentSemanticsClipRect, Rect parentPaintClipRect, List<RenderObject> ancestors) {
     assert(ancestors.length > 1);
 
     _transform = new Matrix4.identity();
-    _clipRect = parentClipRect;
+    _semanticsClipRect = parentSemanticsClipRect;
+    _paintClipRect = parentPaintClipRect;
     for (int index = ancestors.length-1; index > 0; index -= 1) {
       final RenderObject parent = ancestors[index];
       final RenderObject child = ancestors[index-1];
-      _clipRect = _intersectClipRect(parent.describeApproximatePaintClip(child));
-      if (_clipRect != null) {
-        if (_clipRect.isEmpty) {
-          _clipRect = Rect.zero;
-        } else {
-          final Matrix4 clipTransform = new Matrix4.identity();
-          parent.applyPaintTransform(child, clipTransform);
-          _clipRect = MatrixUtils.inverseTransformRect(clipTransform, _clipRect);
-        }
+      final Rect parentSemanticsClipRect = parent.describeSemanticsClip(child);
+      if (parentSemanticsClipRect != null) {
+        _semanticsClipRect = parentSemanticsClipRect;
+        _paintClipRect = _intersectRects(_paintClipRect, parent.describeApproximatePaintClip(child));
+      } else {
+        _semanticsClipRect = _intersectRects(_semanticsClipRect, parent.describeApproximatePaintClip(child));
       }
+      _semanticsClipRect = _transformRect(_semanticsClipRect, parent, child);
+      _paintClipRect = _transformRect(_paintClipRect, parent, child);
       parent.applyPaintTransform(child, _transform);
     }
 
     final RenderObject owner = ancestors.first;
-    _rect = _clipRect == null ? owner.semanticBounds : _clipRect.intersect(owner.semanticBounds);
+    _rect = _semanticsClipRect == null ? owner.semanticBounds : _semanticsClipRect.intersect(owner.semanticBounds);
+    if (_paintClipRect != null) {
+      final Rect paintRect = _paintClipRect.intersect(_rect);
+      _markAsHidden = paintRect.isEmpty && !_rect.isEmpty;
+      if (!_markAsHidden)
+        _rect = paintRect;
+    }
   }
 
-  Rect _intersectClipRect(Rect other) {
-    if (_clipRect == null)
-      return other;
-    if (other == null)
-      return _clipRect;
-    return _clipRect.intersect(other);
+  /// From parent to child coordinate system.
+  static Rect _transformRect(Rect rect, RenderObject parent, RenderObject child) {
+    if (rect == null)
+      return null;
+    if (rect.isEmpty)
+      return Rect.zero;
+    final Matrix4 transform = new Matrix4.identity();
+    parent.applyPaintTransform(child, transform);
+    return MatrixUtils.inverseTransformRect(transform, rect);
   }
 
-  /// Whether a [SemanticsNode] annotated with the geometric information tracked
-  /// by this object would be visible on screen.
-  bool get isInvisible {
+  static Rect _intersectRects(Rect a, Rect b) {
+    if (a == null)
+      return b;
+    if (b == null)
+      return a;
+    return a.intersect(b);
+  }
+
+  /// Whether the [SemanticsNode] annotated with the geometric information tracked
+  /// by this object can be dropped from the semantics tree without losing
+  /// semantics information.
+  bool get dropFromTree {
     return _rect.isEmpty;
   }
+
+  /// Whether the [SemanticsNode] annotated with the geometric information
+  /// tracked by this object should be marked as hidden because it is not
+  /// visible on screen.
+  ///
+  /// Hidden elements should still be included in the tree to work around
+  /// platform limitations (e.g. accessibility scrolling on iOS).
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsFlag.isHidden] for the purpose of marking a node as hidden.
+  bool get markAsHidden => _markAsHidden;
+  bool _markAsHidden = false;
 }
diff --git a/packages/flutter/lib/src/rendering/sliver.dart b/packages/flutter/lib/src/rendering/sliver.dart
index 1f65d06..1014bc7 100644
--- a/packages/flutter/lib/src/rendering/sliver.dart
+++ b/packages/flutter/lib/src/rendering/sliver.dart
@@ -104,6 +104,8 @@
     @required this.crossAxisExtent,
     @required this.crossAxisDirection,
     @required this.viewportMainAxisExtent,
+    @required this.remainingCacheExtent,
+    @required this.cacheOrigin,
   }) : assert(axisDirection != null),
        assert(growthDirection != null),
        assert(userScrollDirection != null),
@@ -112,7 +114,9 @@
        assert(remainingPaintExtent != null),
        assert(crossAxisExtent != null),
        assert(crossAxisDirection != null),
-       assert(viewportMainAxisExtent != null);
+       assert(viewportMainAxisExtent != null),
+       assert(remainingCacheExtent != null),
+       assert(cacheOrigin != null);
 
   /// Creates a copy of this object but with the given fields replaced with the
   /// new values.
@@ -126,6 +130,8 @@
     double crossAxisExtent,
     AxisDirection crossAxisDirection,
     double viewportMainAxisExtent,
+    double remainingCacheExtent,
+    double cacheOrigin,
   }) {
     return new SliverConstraints(
       axisDirection: axisDirection ?? this.axisDirection,
@@ -137,6 +143,8 @@
       crossAxisExtent: crossAxisExtent ?? this.crossAxisExtent,
       crossAxisDirection: crossAxisDirection ?? this.crossAxisDirection,
       viewportMainAxisExtent: viewportMainAxisExtent ?? this.viewportMainAxisExtent,
+      remainingCacheExtent: remainingCacheExtent ?? this.remainingCacheExtent,
+      cacheOrigin: cacheOrigin ?? this.cacheOrigin,
     );
   }
 
@@ -258,6 +266,47 @@
   /// For a vertical list, this is the height of the viewport.
   final double viewportMainAxisExtent;
 
+  /// Where the cache area starts relative to the [scrollOffset].
+  ///
+  /// Slivers that fall into the cache area located before the leading edge and
+  /// after the trailing edge of the viewport should still render content
+  /// because they are about to become visible when the user scrolls.
+  ///
+  /// The [cacheOrigin] describes where the [remainingCacheExtent] starts relative
+  /// to the [scrollOffset]. A cache origin of 0 means that the sliver does not
+  /// have to provide any content before the current [scrollOffset]. A
+  /// [cacheOrigin] of -250.0 means that even though the first visible part of
+  /// the sliver will be at the provided [scrollOffset], the sliver should
+  /// render content starting 250.0 before the [scrollOffset] to fill the
+  /// cache area of the viewport.
+  ///
+  /// The [cacheOrigin] is always negative or zero and will never exceed
+  /// -[scrollOffset]. In other words, a sliver is never asked to provide
+  /// content before its zero [scrollOffset].
+  ///
+  /// See also:
+  ///  * [RenderViewport.cacheExtent] for a description of a viewport's cache area.
+  final double cacheOrigin;
+
+
+  /// Describes how much content the sliver should provide starting from the
+  /// [cacheOrigin].
+  ///
+  /// Not all content in the [remainingCacheExtent] will be visible as some
+  /// of it might fall into the cache area of the viewport.
+  ///
+  /// Each sliver should start laying out content at the [cacheOrigin] and
+  /// try to provide as much content as the [remainingCacheExtent] allows.
+  ///
+  /// The [remainingCacheExtent] is always larger or equal to the
+  /// [remainingPaintExtent]. Content, that falls in the [remainingCacheExtent],
+  /// but is outside of the [remainingPaintExtent] is currently not visible
+  /// in the viewport.
+  ///
+  /// See also:
+  ///  * [RenderViewport.cacheExtent] for a description of a viewport's cache area.
+  final double remainingCacheExtent;
+
   /// The axis along which the [scrollOffset] and [remainingPaintExtent] are measured.
   Axis get axis => axisDirectionToAxis(axisDirection);
 
@@ -361,6 +410,8 @@
       verify(axisDirectionToAxis(axisDirection) != axisDirectionToAxis(crossAxisDirection), 'The "axisDirection" and the "crossAxisDirection" are along the same axis.');
       verify(viewportMainAxisExtent >= 0.0, 'The "viewportMainAxisExtent" is negative.');
       verify(remainingPaintExtent >= 0.0, 'The "remainingPaintExtent" is negative.');
+      verify(remainingCacheExtent >= 0.0, 'The "remainingCacheExtent" is negative.');
+      verify(cacheOrigin <= 0.0, 'The "cacheOrigin" is positive.');
       verify(isNormalized, 'The constraints are not normalized.'); // should be redundant with earlier checks
       return true;
     }());
@@ -382,7 +433,9 @@
         && typedOther.remainingPaintExtent == remainingPaintExtent
         && typedOther.crossAxisExtent == crossAxisExtent
         && typedOther.crossAxisDirection == crossAxisDirection
-        && typedOther.viewportMainAxisExtent == viewportMainAxisExtent;
+        && typedOther.viewportMainAxisExtent == viewportMainAxisExtent
+        && typedOther.remainingCacheExtent == remainingCacheExtent
+        && typedOther.cacheOrigin == cacheOrigin;
   }
 
   @override
@@ -396,6 +449,8 @@
       crossAxisExtent,
       crossAxisDirection,
       viewportMainAxisExtent,
+      remainingCacheExtent,
+      cacheOrigin,
     );
   }
 
@@ -408,9 +463,11 @@
              'scrollOffset: ${scrollOffset.toStringAsFixed(1)}, '
              'remainingPaintExtent: ${remainingPaintExtent.toStringAsFixed(1)}, ' +
              (overlap != 0.0 ? 'overlap: ${overlap.toStringAsFixed(1)}, ' : '') +
-             'crossAxisExtent: ${crossAxisExtent.toStringAsFixed(1)}, ' +
-             'crossAxisDirection: $crossAxisDirection, ' +
-             'viewportMainAxisExtent: ${viewportMainAxisExtent.toStringAsFixed(1)}' +
+             'crossAxisExtent: ${crossAxisExtent.toStringAsFixed(1)}, '
+             'crossAxisDirection: $crossAxisDirection, '
+             'viewportMainAxisExtent: ${viewportMainAxisExtent.toStringAsFixed(1)}, '
+             'remainingCacheExtent: ${remainingCacheExtent.toStringAsFixed(1)} '
+             'cacheOrigin: ${cacheOrigin.toStringAsFixed(1)} '
            ')';
   }
 }
@@ -440,6 +497,7 @@
     bool visible,
     this.hasVisualOverflow: false,
     this.scrollOffsetCorrection,
+    double cacheExtent,
   }) : assert(scrollExtent != null),
        assert(paintExtent != null),
        assert(paintOrigin != null),
@@ -448,6 +506,7 @@
        assert(scrollOffsetCorrection != 0.0),
        layoutExtent = layoutExtent ?? paintExtent,
        hitTestExtent = hitTestExtent ?? paintExtent,
+       cacheExtent = cacheExtent ?? layoutExtent ?? paintExtent,
        visible = visible ?? paintExtent > 0.0;
 
   /// A sliver that occupies no space at all.
@@ -585,6 +644,18 @@
   /// its offset based on this value.
   final double scrollOffsetCorrection;
 
+  /// How many pixels the sliver has consumed in the
+  /// [SliverConstraints.remainingCacheExtent].
+  ///
+  /// This value should be equal to or larger than the [layoutExtent] because
+  /// the sliver allways consumes at least the [layoutExtent] from the
+  /// [SliverConstraints.remainingCacheExtent] and possibly more if it falls
+  /// into the cache area of the viewport.
+  ///
+  /// See also:
+  ///  * [RenderViewport.cacheExtent] for a description of a viewport's cache area.
+  final double cacheExtent;
+
   /// Asserts that this geometry is internally consistent.
   ///
   /// Does nothing if asserts are disabled. Always returns true.
@@ -607,6 +678,7 @@
       verify(paintOrigin != null, 'The "paintOrigin" is null.');
       verify(layoutExtent != null, 'The "layoutExtent" is null.');
       verify(layoutExtent >= 0.0, 'The "layoutExtent" is negative.');
+      verify(cacheExtent >= 0.0, 'The "cacheExtent" is negative.');
       if (layoutExtent > paintExtent) {
         verify(false,
           'The "layoutExtent" exceeds the "paintExtent".\n' +
@@ -655,6 +727,7 @@
     properties.add(new DoubleProperty('hitTestExtent', hitTestExtent, defaultValue: paintExtent));
     properties.add(new DiagnosticsProperty<bool>('hasVisualOverflow', hasVisualOverflow, defaultValue: false));
     properties.add(new DoubleProperty('scrollOffsetCorrection', scrollOffsetCorrection, defaultValue: null));
+    properties.add(new DoubleProperty('cacheExtent', cacheExtent, defaultValue: 0.0));
   }
 }
 
@@ -1123,6 +1196,22 @@
     return (to.clamp(a, b) - from.clamp(a, b)).clamp(0.0, constraints.remainingPaintExtent);
   }
 
+  /// Computes the portion of the region from `from` to `to` that is within
+  /// the cache extent of the viewport, assuming that only the region from the
+  /// [SliverConstraints.cacheOrigin] that is
+  /// [SliverConstraints.remainingCacheExtent] high is visible, and that
+  /// the relationship between scroll offsets and paint offsets is linear.
+  ///
+  /// This method is not useful if there is not a 1:1 relationship between
+  /// consumed scroll offset and consumed cache extent.
+  double calculateCacheOffset(SliverConstraints constraints, { @required double from, @required double to }) {
+    assert(from <= to);
+    final double a = constraints.scrollOffset + constraints.cacheOrigin;
+    final double b = constraints.scrollOffset + constraints.remainingCacheExtent;
+    // the clamp on the next line is to avoid floating point rounding errors
+    return (to.clamp(a, b) - from.clamp(a, b)).clamp(0.0, constraints.remainingCacheExtent);
+  }
+
   /// Returns the distance from the leading _visible_ edge of the sliver to the
   /// side of the given child closest to that edge.
   ///
@@ -1537,11 +1626,14 @@
     }
     assert(childExtent != null);
     final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: childExtent);
+    final double cacheExtent = calculateCacheOffset(constraints, from: 0.0, to: childExtent);
+
     assert(paintedChildSize.isFinite);
     assert(paintedChildSize >= 0.0);
     geometry = new SliverGeometry(
       scrollExtent: childExtent,
       paintExtent: paintedChildSize,
+      cacheExtent: cacheExtent,
       maxPaintExtent: childExtent,
       hitTestExtent: paintedChildSize,
       hasVisualOverflow: childExtent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
diff --git a/packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart b/packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart
index c7a41cc..e29ba36 100644
--- a/packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart
+++ b/packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart
@@ -140,11 +140,11 @@
 
     final double itemExtent = this.itemExtent;
 
-    final double scrollOffset = constraints.scrollOffset;
+    final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin;
     assert(scrollOffset >= 0.0);
-    final double remainingPaintExtent = constraints.remainingPaintExtent;
-    assert(remainingPaintExtent >= 0.0);
-    final double targetEndScrollOffset = scrollOffset + remainingPaintExtent;
+    final double remainingExtent = constraints.remainingCacheExtent;
+    assert(remainingExtent >= 0.0);
+    final double targetEndScrollOffset = scrollOffset + remainingExtent;
 
     final BoxConstraints childConstraints = constraints.asBoxConstraints(
       minExtent: itemExtent,
@@ -242,9 +242,16 @@
       to: trailingScrollOffset,
     );
 
+    final double cacheExtent = calculateCacheOffset(
+      constraints,
+      from: leadingScrollOffset,
+      to: trailingScrollOffset,
+    );
+
     geometry = new SliverGeometry(
       scrollExtent: estimatedMaxScrollOffset,
       paintExtent: paintExtent,
+      cacheExtent: cacheExtent,
       maxPaintExtent: estimatedMaxScrollOffset,
       // Conservative to avoid flickering away the clip during scroll.
       hasVisualOverflow: (targetLastIndex != null && lastIndex >= targetLastIndex)
diff --git a/packages/flutter/lib/src/rendering/sliver_grid.dart b/packages/flutter/lib/src/rendering/sliver_grid.dart
index eae7a00..45f663c 100644
--- a/packages/flutter/lib/src/rendering/sliver_grid.dart
+++ b/packages/flutter/lib/src/rendering/sliver_grid.dart
@@ -513,11 +513,11 @@
     childManager.didStartLayout();
     childManager.setDidUnderflow(false);
 
-    final double scrollOffset = constraints.scrollOffset;
+    final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin;
     assert(scrollOffset >= 0.0);
-    final double remainingPaintExtent = constraints.remainingPaintExtent;
-    assert(remainingPaintExtent >= 0.0);
-    final double targetEndScrollOffset = scrollOffset + remainingPaintExtent;
+    final double remainingExtent = constraints.remainingCacheExtent;
+    assert(remainingExtent >= 0.0);
+    final double targetEndScrollOffset = scrollOffset + remainingExtent;
 
     final SliverGridLayout layout = _gridDelegate.getLayout(constraints);
 
@@ -617,11 +617,17 @@
       from: leadingScrollOffset,
       to: trailingScrollOffset,
     );
+    final double cacheExtent = calculateCacheOffset(
+      constraints,
+      from: leadingScrollOffset,
+      to: trailingScrollOffset,
+    );
 
     geometry = new SliverGeometry(
       scrollExtent: estimatedTotalExtent,
       paintExtent: paintExtent,
       maxPaintExtent: estimatedTotalExtent,
+      cacheExtent: cacheExtent,
       // Conservative to avoid complexity.
       hasVisualOverflow: true,
     );
diff --git a/packages/flutter/lib/src/rendering/sliver_list.dart b/packages/flutter/lib/src/rendering/sliver_list.dart
index 4abff38..2fd6569 100644
--- a/packages/flutter/lib/src/rendering/sliver_list.dart
+++ b/packages/flutter/lib/src/rendering/sliver_list.dart
@@ -47,11 +47,11 @@
     childManager.didStartLayout();
     childManager.setDidUnderflow(false);
 
-    final double scrollOffset = constraints.scrollOffset;
+    final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin;
     assert(scrollOffset >= 0.0);
-    final double remainingPaintExtent = constraints.remainingPaintExtent;
-    assert(remainingPaintExtent >= 0.0);
-    final double targetEndScrollOffset = scrollOffset + remainingPaintExtent;
+    final double remainingExtent = constraints.remainingCacheExtent;
+    assert(remainingExtent >= 0.0);
+    final double targetEndScrollOffset = scrollOffset + remainingExtent;
     final BoxConstraints childConstraints = constraints.asBoxConstraints();
     int leadingGarbage = 0;
     int trailingGarbage = 0;
@@ -269,9 +269,15 @@
       from: childScrollOffset(firstChild),
       to: endScrollOffset,
     );
+    final double cacheExtent = calculateCacheOffset(
+      constraints,
+      from: childScrollOffset(firstChild),
+      to: endScrollOffset,
+    );
     geometry = new SliverGeometry(
       scrollExtent: estimatedMaxScrollOffset,
       paintExtent: paintExtent,
+      cacheExtent: cacheExtent,
       maxPaintExtent: estimatedMaxScrollOffset,
       // Conservative to avoid flickering away the clip during scroll.
       hasVisualOverflow: endScrollOffset > targetEndScrollOffset || constraints.scrollOffset > 0.0,
diff --git a/packages/flutter/lib/src/rendering/sliver_multi_box_adaptor.dart b/packages/flutter/lib/src/rendering/sliver_multi_box_adaptor.dart
index 8663074..a4f0ec6 100644
--- a/packages/flutter/lib/src/rendering/sliver_multi_box_adaptor.dart
+++ b/packages/flutter/lib/src/rendering/sliver_multi_box_adaptor.dart
@@ -298,36 +298,6 @@
     _keepAliveBucket.values.forEach(visitor);
   }
 
-  @override
-  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
-    switch (constraints.normalizedGrowthDirection) {
-      case GrowthDirection.forward:
-        super.visitChildrenForSemantics((RenderObject child) {
-          // The sliver is overlapped at the leading edge; check if trailing edge is visible.
-          final Offset bottomRightInViewport = MatrixUtils.transformPoint(
-              child.getTransformTo(parent), child.semanticBounds.bottomRight
-          );
-          final double endOverlap = constraints.overlap;
-          if ((constraints.axis == Axis.vertical && bottomRightInViewport.dy > endOverlap) ||
-              (constraints.axis == Axis.horizontal && bottomRightInViewport.dx > endOverlap))
-            visitor(child);
-        });
-        break;
-      case GrowthDirection.reverse:
-        super.visitChildrenForSemantics((RenderObject child) {
-          // The sliver is overlapped at the trailing edge; check if leading edge is visible.
-          final Offset topLeftInViewport = MatrixUtils.transformPoint(
-              child.getTransformTo(parent), child.semanticBounds.topLeft
-          );
-          final double startOverlap = constraints.remainingPaintExtent - constraints.overlap;
-          if ((constraints.axis == Axis.vertical && topLeftInViewport.dy < startOverlap) ||
-              (constraints.axis == Axis.horizontal && topLeftInViewport.dx < startOverlap))
-            visitor(child);
-        });
-        break;
-    }
-  }
-
   /// Called during layout to create and add the child with the given index and
   /// scroll offset.
   ///
diff --git a/packages/flutter/lib/src/rendering/sliver_padding.dart b/packages/flutter/lib/src/rendering/sliver_padding.dart
index 0220715..fb0e7ee 100644
--- a/packages/flutter/lib/src/rendering/sliver_padding.dart
+++ b/packages/flutter/lib/src/rendering/sliver_padding.dart
@@ -182,8 +182,10 @@
     child.layout(
       constraints.copyWith(
         scrollOffset: math.max(0.0, constraints.scrollOffset - beforePadding),
+        cacheOrigin: math.min(0.0, constraints.cacheOrigin + beforePadding),
         overlap: 0.0,
         remainingPaintExtent: constraints.remainingPaintExtent - calculatePaintOffset(constraints, from: 0.0, to: beforePadding),
+        remainingCacheExtent: constraints.remainingCacheExtent - calculateCacheOffset(constraints, from: 0.0, to: beforePadding),
         crossAxisExtent: math.max(0.0, constraints.crossAxisExtent - crossAxisPadding),
       ),
       parentUsesSize: true,
@@ -206,6 +208,17 @@
       to: mainAxisPadding + childLayoutGeometry.scrollExtent,
     );
     final double mainAxisPaddingPaintExtent = beforePaddingPaintExtent + afterPaddingPaintExtent;
+    final double beforePaddingCacheExtent = calculateCacheOffset(
+      constraints,
+      from: 0.0,
+      to: beforePadding,
+    );
+    final double afterPaddingCacheExtent = calculateCacheOffset(
+      constraints,
+      from: beforePadding + childLayoutGeometry.scrollExtent,
+      to: mainAxisPadding + childLayoutGeometry.scrollExtent,
+    );
+    final double mainAxisPaddingCacheExtent = afterPaddingCacheExtent + beforePaddingCacheExtent;
     final double paintExtent = math.min(
       beforePaddingPaintExtent + math.max(childLayoutGeometry.paintExtent, childLayoutGeometry.layoutExtent + afterPaddingPaintExtent),
       constraints.remainingPaintExtent,
@@ -214,6 +227,7 @@
       scrollExtent: mainAxisPadding + childLayoutGeometry.scrollExtent,
       paintExtent: paintExtent,
       layoutExtent: math.min(mainAxisPaddingPaintExtent + childLayoutGeometry.layoutExtent, paintExtent),
+      cacheExtent: math.min(mainAxisPaddingCacheExtent + childLayoutGeometry.cacheExtent, constraints.remainingCacheExtent),
       maxPaintExtent: mainAxisPadding + childLayoutGeometry.maxPaintExtent,
       hitTestExtent: math.max(
         mainAxisPaddingPaintExtent + childLayoutGeometry.paintExtent,
diff --git a/packages/flutter/lib/src/rendering/sliver_persistent_header.dart b/packages/flutter/lib/src/rendering/sliver_persistent_header.dart
index b6ac9a6..325fa93 100644
--- a/packages/flutter/lib/src/rendering/sliver_persistent_header.dart
+++ b/packages/flutter/lib/src/rendering/sliver_persistent_header.dart
@@ -292,13 +292,15 @@
     final bool overlapsContent = constraints.overlap > 0.0;
     excludeFromSemanticsScrolling = overlapsContent || (constraints.scrollOffset > maxExtent - minExtent);
     layoutChild(constraints.scrollOffset, maxExtent, overlapsContent: overlapsContent);
+    final double layoutExtent = (maxExtent - constraints.scrollOffset).clamp(0.0, constraints.remainingPaintExtent);
     geometry = new SliverGeometry(
       scrollExtent: maxExtent,
       paintOrigin: constraints.overlap,
       paintExtent: math.min(childExtent, constraints.remainingPaintExtent),
-      layoutExtent: (maxExtent - constraints.scrollOffset).clamp(0.0, constraints.remainingPaintExtent),
+      layoutExtent: layoutExtent,
       maxPaintExtent: maxExtent,
       maxScrollObstructionExtent: minExtent,
+      cacheExtent: layoutExtent > 0.0 ? -constraints.cacheOrigin + layoutExtent : layoutExtent,
       hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
     );
   }
diff --git a/packages/flutter/lib/src/rendering/viewport.dart b/packages/flutter/lib/src/rendering/viewport.dart
index b236860..6d943ce 100644
--- a/packages/flutter/lib/src/rendering/viewport.dart
+++ b/packages/flutter/lib/src/rendering/viewport.dart
@@ -53,6 +53,14 @@
   /// descendant of the viewport and there must not be any other
   /// [RenderAbstractViewport] objects between the target and this object.
   double getOffsetToReveal(RenderObject target, double alignment);
+
+  /// The default value for the cache extent of the viewport.
+  ///
+  /// See also:
+  ///
+  ///  * [RenderViewportBase.cacheExtent] for a definition of the cache extent.
+  @protected
+  static const double defaultCacheExtent = 250.0;
 }
 
 /// A base class for render objects that are bigger on the inside.
@@ -82,13 +90,15 @@
     AxisDirection axisDirection: AxisDirection.down,
     @required AxisDirection crossAxisDirection,
     @required ViewportOffset offset,
+    double cacheExtent,
   }) : assert(axisDirection != null),
        assert(crossAxisDirection != null),
        assert(offset != null),
        assert(axisDirectionToAxis(axisDirection) != axisDirectionToAxis(crossAxisDirection)),
        _axisDirection = axisDirection,
        _crossAxisDirection = crossAxisDirection,
-       _offset = offset;
+       _offset = offset,
+       _cacheExtent = cacheExtent ?? RenderAbstractViewport.defaultCacheExtent;
 
   @override
   void describeSemanticsConfiguration(SemanticsConfiguration config) {
@@ -99,11 +109,9 @@
 
   @override
   void visitChildrenForSemantics(RenderObjectVisitor visitor) {
-    for (RenderSliver sliver in childrenInPaintOrder) {
-      if (sliver.geometry.paintExtent != 0 &&
-          sliver.constraints.overlap < sliver.geometry.paintOrigin + sliver.geometry.paintExtent)
-        visitor(sliver);
-    }
+    childrenInPaintOrder
+        .where((RenderSliver sliver) => sliver.geometry.visible || sliver.geometry.cacheExtent > 0.0)
+        .forEach(visitor);
   }
 
   /// The direction in which the [SliverConstraints.scrollOffset] increases.
@@ -166,6 +174,35 @@
     markNeedsLayout();
   }
 
+  /// {@template flutter.rendering.viewport.cacheExtent}
+  /// The viewport has an area before and after the visible area to cache items
+  /// that are about to become visible when the user scrolls.
+  ///
+  /// Items that fall in this cache area are laid out even though they are not
+  /// (yet) visible on screen. The [cacheExtent] describes how many pixels
+  /// the cache area extends before the leading edge and after the trailing edge
+  /// of the viewport.
+  ///
+  /// The total extent, which the viewport will try to cover with children, is
+  /// [cacheExtent] before the leading edge + extent of the main axis +
+  /// [cacheExtent] after the trailing edge.
+  ///
+  /// The cache area is also used to implement implicit accessibility scrolling
+  /// on iOS: When the accessibility focus moves from an item in the visible
+  /// viewport to an invisible item in the cache area, the framework will bring
+  /// that item into view with an (implicit) scroll action.
+  /// {@endtemplate}
+  double get cacheExtent => _cacheExtent;
+  double _cacheExtent;
+  set cacheExtent(double value) {
+    value = value ?? RenderAbstractViewport.defaultCacheExtent;
+    assert(value != null);
+    if (value == _cacheExtent)
+      return;
+    _cacheExtent = value;
+    markNeedsLayout();
+  }
+
   @override
   void attach(PipelineOwner owner) {
     super.attach(owner);
@@ -262,17 +299,19 @@
   /// encountered, if any. Otherwise returns 0.0. Typical callers will call this
   /// function repeatedly until it returns 0.0.
   @protected
-  double layoutChildSequence(
-    RenderSliver child,
-    double scrollOffset,
-    double overlap,
-    double layoutOffset,
-    double remainingPaintExtent,
-    double mainAxisExtent,
-    double crossAxisExtent,
-    GrowthDirection growthDirection,
-    RenderSliver advance(RenderSliver child),
-  ) {
+  double layoutChildSequence({
+    @required RenderSliver child,
+    @required double scrollOffset,
+    @required double overlap,
+    @required double layoutOffset,
+    @required double remainingPaintExtent,
+    @required double mainAxisExtent,
+    @required double crossAxisExtent,
+    @required GrowthDirection growthDirection,
+    @required RenderSliver advance(RenderSliver child),
+    @required double remainingCacheExtent,
+    @required double cacheOrigin,
+  }) {
     assert(scrollOffset.isFinite);
     assert(scrollOffset >= 0.0);
     final double initialLayoutOffset = layoutOffset;
@@ -280,18 +319,32 @@
         applyGrowthDirectionToScrollDirection(offset.userScrollDirection, growthDirection);
     assert(adjustedUserScrollDirection != null);
     double maxPaintOffset = layoutOffset + overlap;
+
     while (child != null) {
-      assert(scrollOffset >= 0.0);
+      final double sliverScrollOffset = scrollOffset <= 0.0 ? 0.0 : scrollOffset;
+      // If the scrollOffset is too small we adjust the paddedOrigin because it
+      // doesn't make sense to ask a sliver for content before its scroll
+      // offset.
+      final double corectedCacheOrigin = math.max(cacheOrigin, -sliverScrollOffset);
+      final double cacheExtentCorrection = cacheOrigin - corectedCacheOrigin;
+
+      assert(sliverScrollOffset >= corectedCacheOrigin.abs());
+      assert(corectedCacheOrigin <= 0.0);
+      assert(sliverScrollOffset >= 0.0);
+      assert(cacheExtentCorrection <= 0.0);
+
       child.layout(new SliverConstraints(
         axisDirection: axisDirection,
         growthDirection: growthDirection,
         userScrollDirection: adjustedUserScrollDirection,
-        scrollOffset: scrollOffset,
+        scrollOffset: sliverScrollOffset,
         overlap: maxPaintOffset - layoutOffset,
         remainingPaintExtent: math.max(0.0, remainingPaintExtent - layoutOffset + initialLayoutOffset),
         crossAxisExtent: crossAxisExtent,
         crossAxisDirection: crossAxisDirection,
         viewportMainAxisExtent: mainAxisExtent,
+        remainingCacheExtent: math.max(0.0, remainingCacheExtent + cacheExtentCorrection),
+        cacheOrigin: corectedCacheOrigin,
       ), parentUsesSize: true);
 
       final SliverGeometry childLayoutGeometry = child.geometry;
@@ -304,13 +357,23 @@
       // We use the child's paint origin in our coordinate system as the
       // layoutOffset we store in the child's parent data.
       final double effectiveLayoutOffset = layoutOffset + childLayoutGeometry.paintOrigin;
-      updateChildLayoutOffset(child, effectiveLayoutOffset, growthDirection);
+
+      // `effectiveLayoutOffset` becomes meaningless once we moved past the trailing edge
+      // because `childLayoutGeometry.layoutExtent` is zero. Using the still increasing
+      // 'scrollOffset` to roughly position these invisible slivers in the right order.
+      if (childLayoutGeometry.visible || scrollOffset > 0) {
+        updateChildLayoutOffset(child, effectiveLayoutOffset, growthDirection);
+      } else {
+        updateChildLayoutOffset(child, -scrollOffset + initialLayoutOffset, growthDirection);
+      }
+
       maxPaintOffset = math.max(effectiveLayoutOffset + childLayoutGeometry.paintExtent, maxPaintOffset);
       scrollOffset -= childLayoutGeometry.scrollExtent;
       layoutOffset += childLayoutGeometry.layoutExtent;
-
-      if (scrollOffset <= 0.0)
-        scrollOffset = 0.0;
+      if (childLayoutGeometry.cacheExtent != 0.0) {
+        remainingCacheExtent -= childLayoutGeometry.cacheExtent - cacheExtentCorrection;
+        cacheOrigin = math.min(corectedCacheOrigin + childLayoutGeometry.cacheExtent, 0.0);
+      }
 
       updateOutOfBandData(growthDirection, childLayoutGeometry);
 
@@ -323,6 +386,59 @@
   }
 
   @override
+  Rect describeApproximatePaintClip(RenderSliver child) {
+    final Rect viewportClip = Offset.zero & size;
+    if (child.constraints.overlap == 0) {
+      return viewportClip;
+    }
+
+    // Adjust the clip rect for this sliver by the overlap from the previous sliver.
+    double left = viewportClip.left;
+    double right = viewportClip.right;
+    double top = viewportClip.top;
+    double bottom = viewportClip.bottom;
+    final double startOfOverlap = child.constraints.viewportMainAxisExtent - child.constraints.remainingPaintExtent;
+    final double overlapCorrection = startOfOverlap + child.constraints.overlap;
+    switch (applyGrowthDirectionToAxisDirection(axisDirection, child.constraints.growthDirection)) {
+      case AxisDirection.down:
+        top += overlapCorrection;
+        break;
+      case AxisDirection.up:
+        bottom -= overlapCorrection;
+        break;
+      case AxisDirection.right:
+        left += overlapCorrection;
+        break;
+      case AxisDirection.left:
+        right -= overlapCorrection;
+        break;
+    }
+    return new Rect.fromLTRB(left, top, right, bottom);
+  }
+
+  @override
+  Rect describeSemanticsClip(RenderSliver child) {
+    assert (axis != null);
+    switch (axis) {
+      case Axis.vertical:
+        return new Rect.fromLTRB(
+          semanticBounds.left,
+          semanticBounds.top - cacheExtent,
+          semanticBounds.right,
+          semanticBounds.bottom + cacheExtent,
+        );
+      case Axis.horizontal:
+        return new Rect.fromLTRB(
+          semanticBounds.left - cacheExtent,
+          semanticBounds.top,
+          semanticBounds.right + cacheExtent,
+          semanticBounds.bottom,
+        );
+    }
+    return null;
+  }
+
+  @override
   void paint(PaintingContext context, Offset offset) {
     if (firstChild == null)
       return;
@@ -734,11 +850,12 @@
     double anchor: 0.0,
     List<RenderSliver> children,
     RenderSliver center,
+    double cacheExtent,
   }) : assert(anchor != null),
        assert(anchor >= 0.0 && anchor <= 1.0),
        _anchor = anchor,
        _center = center,
-       super(axisDirection: axisDirection, crossAxisDirection: crossAxisDirection, offset: offset) {
+       super(axisDirection: axisDirection, crossAxisDirection: crossAxisDirection, offset: offset, cacheExtent: cacheExtent) {
     addAll(children);
     if (center == null && firstChild != null)
       _center = firstChild;
@@ -979,26 +1096,32 @@
 
     // centerOffset is the offset from the leading edge of the RenderViewport
     // to the zero scroll offset (the line between the forward slivers and the
-    // reverse slivers). The other two are that, but clamped to the visible
-    // region of the viewport.
+    // reverse slivers).
     final double centerOffset = mainAxisExtent * anchor - correctedOffset;
-    final double clampedForwardCenter = math.max(0.0, math.min(mainAxisExtent, centerOffset));
-    final double clampedReverseCenter = math.max(0.0, math.min(mainAxisExtent, mainAxisExtent - centerOffset));
+    final double reverseDirectionRemainingPaintExtent = centerOffset.clamp(0.0, mainAxisExtent);
+    final double forwardDirectionRemainingPaintExtent = (mainAxisExtent - centerOffset).clamp(0.0, mainAxisExtent);
+
+    final double fullCacheExtent = mainAxisExtent + 2 * cacheExtent;
+    final double centerCacheOffset = centerOffset + cacheExtent;
+    final double reverseDirectionRemainingCacheExtent = centerCacheOffset.clamp(0.0, fullCacheExtent);
+    final double forwardDirectionRemainingCacheExtent = (fullCacheExtent - centerCacheOffset).clamp(0.0, fullCacheExtent);
 
     final RenderSliver leadingNegativeChild = childBefore(center);
 
     if (leadingNegativeChild != null) {
       // negative scroll offsets
       final double result = layoutChildSequence(
-        leadingNegativeChild,
-        math.max(mainAxisExtent, centerOffset) - mainAxisExtent,
-        0.0,
-        clampedReverseCenter,
-        clampedForwardCenter,
-        mainAxisExtent,
-        crossAxisExtent,
-        GrowthDirection.reverse,
-        childBefore,
+        child: leadingNegativeChild,
+        scrollOffset: math.max(mainAxisExtent, centerOffset) - mainAxisExtent,
+        overlap: 0.0,
+        layoutOffset: forwardDirectionRemainingPaintExtent,
+        remainingPaintExtent: reverseDirectionRemainingPaintExtent,
+        mainAxisExtent: mainAxisExtent,
+        crossAxisExtent: crossAxisExtent,
+        growthDirection: GrowthDirection.reverse,
+        advance: childBefore,
+        remainingCacheExtent: reverseDirectionRemainingCacheExtent,
+        cacheOrigin: (mainAxisExtent - centerOffset).clamp(-cacheExtent, 0.0),
       );
       if (result != 0.0)
         return -result;
@@ -1006,15 +1129,17 @@
 
     // positive scroll offsets
     return layoutChildSequence(
-      center,
-      math.max(0.0, -centerOffset),
-      leadingNegativeChild == null ? math.min(0.0, -centerOffset) : 0.0,
-      clampedForwardCenter,
-      clampedReverseCenter,
-      mainAxisExtent,
-      crossAxisExtent,
-      GrowthDirection.forward,
-      childAfter,
+      child: center,
+      scrollOffset: math.max(0.0, -centerOffset),
+      overlap: leadingNegativeChild == null ? math.min(0.0, -centerOffset) : 0.0,
+      layoutOffset: centerOffset >= mainAxisExtent ? centerOffset: reverseDirectionRemainingPaintExtent,
+      remainingPaintExtent: forwardDirectionRemainingPaintExtent,
+      mainAxisExtent: mainAxisExtent,
+      crossAxisExtent: crossAxisExtent,
+      growthDirection: GrowthDirection.forward,
+      advance: childAfter,
+      remainingCacheExtent: forwardDirectionRemainingCacheExtent,
+      cacheOrigin: centerOffset.clamp(-cacheExtent, 0.0),
     );
   }
 
@@ -1333,15 +1458,17 @@
     _shrinkWrapExtent = 0.0;
     _hasVisualOverflow = false;
     return layoutChildSequence(
-      firstChild,
-      math.max(0.0, correctedOffset),
-      math.min(0.0, correctedOffset),
-      0.0,
-      mainAxisExtent,
-      mainAxisExtent,
-      crossAxisExtent,
-      GrowthDirection.forward,
-      childAfter,
+      child: firstChild,
+      scrollOffset: math.max(0.0, correctedOffset),
+      overlap: math.min(0.0, correctedOffset),
+      layoutOffset: 0.0,
+      remainingPaintExtent: mainAxisExtent,
+      mainAxisExtent: mainAxisExtent,
+      crossAxisExtent: crossAxisExtent,
+      growthDirection: GrowthDirection.forward,
+      advance: childAfter,
+      remainingCacheExtent: mainAxisExtent + 2 * cacheExtent,
+      cacheOrigin: -cacheExtent,
     );
   }
 
diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart
index a8ff461..4504a85 100644
--- a/packages/flutter/lib/src/semantics/semantics.dart
+++ b/packages/flutter/lib/src/semantics/semantics.dart
@@ -794,11 +794,39 @@
     }
   }
 
-  /// The clip rect from an ancestor that was applied to this node.
+  /// The semantic clip from an ancestor that was applied to this node.
   ///
   /// Expressed in the coordinate system of the node. May be null if no clip has
   /// been applied.
-  Rect parentClipRect;
+  ///
+  /// Descendant [SemanticsNode]s that are positioned outside of this rect will
+  /// be excluded from the semantics tree. Descendant [SemanticsNode]s that are
+  /// overlapping with this rect, but are outside of [parentPaintClipRect] will
+  /// be included in the tree, but they will be marked as hidden because they
+  /// are assumed to be not visible on screen.
+  ///
+  /// If this rect is null, all descendant [SemanticsNode]s outside of
+  /// [parentPaintClipRect] will be excluded from the tree.
+  ///
+  /// If this rect is non-null it has to completely enclose
+  /// [parentPaintClipRect]. If [parentPaintClipRect] is null this property is
+  /// also null.
+  Rect parentSemanticsClipRect;
+
+  /// The paint clip from an ancestor that was applied to this node.
+  ///
+  /// Expressed in the coordinate system of the node. May be null if no clip has
+  /// been applied.
+  ///
+  /// Descendant [SemanticsNode]s that are positioned outside of this rect will
+  /// either be excluded from the semantics tree (if they have no overlap with
+  /// [parentSemanticsClipRect]) or they will be included and marked as hidden
+  /// (if they are overlapping with [parentSemanticsClipRect]).
+  ///
+  /// This rect is completely enclosed by [parentSemanticsClipRect].
+  ///
+  /// If this rect is null [parentSemanticsClipRect] also has to be null.
+  Rect parentPaintClipRect;
 
   /// Whether the node is invisible.
   ///
diff --git a/packages/flutter/lib/src/widgets/page_view.dart b/packages/flutter/lib/src/widgets/page_view.dart
index 485a7ba..4ea9a93 100644
--- a/packages/flutter/lib/src/widgets/page_view.dart
+++ b/packages/flutter/lib/src/widgets/page_view.dart
@@ -564,6 +564,7 @@
         physics: physics,
         viewportBuilder: (BuildContext context, ViewportOffset position) {
           return new Viewport(
+            cacheExtent: 0.0,
             axisDirection: axisDirection,
             offset: position,
             slivers: <Widget>[
diff --git a/packages/flutter/lib/src/widgets/scroll_view.dart b/packages/flutter/lib/src/widgets/scroll_view.dart
index 9d67371..0e0c88f 100644
--- a/packages/flutter/lib/src/widgets/scroll_view.dart
+++ b/packages/flutter/lib/src/widgets/scroll_view.dart
@@ -56,6 +56,7 @@
     bool primary,
     ScrollPhysics physics,
     this.shrinkWrap: false,
+    this.cacheExtent,
   }) : assert(reverse != null),
        assert(shrinkWrap != null),
        assert(!(controller != null && primary == true),
@@ -165,6 +166,9 @@
   /// Defaults to false.
   final bool shrinkWrap;
 
+  /// {@macro flutter.rendering.viewport.cacheExtent}
+  final double cacheExtent;
+
   /// Returns the [AxisDirection] in which the scroll view scrolls.
   ///
   /// Combines the [scrollDirection] with the [reverse] boolean to obtain the
@@ -211,6 +215,7 @@
       axisDirection: axisDirection,
       offset: offset,
       slivers: slivers,
+      cacheExtent: cacheExtent,
     );
   }
 
@@ -333,6 +338,7 @@
     bool primary,
     ScrollPhysics physics,
     bool shrinkWrap: false,
+    double cacheExtent,
     this.slivers: const <Widget>[],
   }) : super(
     key: key,
@@ -342,6 +348,7 @@
     primary: primary,
     physics: physics,
     shrinkWrap: shrinkWrap,
+    cacheExtent: cacheExtent,
   );
 
   /// The slivers to place inside the viewport.
@@ -372,6 +379,7 @@
     ScrollPhysics physics,
     bool shrinkWrap: false,
     this.padding,
+    double cacheExtent,
   }) : super(
     key: key,
     scrollDirection: scrollDirection,
@@ -380,6 +388,7 @@
     primary: primary,
     physics: physics,
     shrinkWrap: shrinkWrap,
+    cacheExtent: cacheExtent,
   );
 
   /// The amount of space by which to inset the children.
@@ -594,6 +603,7 @@
     this.itemExtent,
     bool addAutomaticKeepAlives: true,
     bool addRepaintBoundaries: true,
+    double cacheExtent,
     List<Widget> children: const <Widget>[],
   }) : childrenDelegate = new SliverChildListDelegate(
          children,
@@ -608,6 +618,7 @@
     physics: physics,
     shrinkWrap: shrinkWrap,
     padding: padding,
+    cacheExtent: cacheExtent,
   );
 
   /// Creates a scrollable, linear array of widgets that are created on demand.
@@ -648,6 +659,7 @@
     int itemCount,
     bool addAutomaticKeepAlives: true,
     bool addRepaintBoundaries: true,
+    double cacheExtent,
   }) : childrenDelegate = new SliverChildBuilderDelegate(
          itemBuilder,
          childCount: itemCount,
@@ -662,6 +674,7 @@
     physics: physics,
     shrinkWrap: shrinkWrap,
     padding: padding,
+    cacheExtent: cacheExtent
   );
 
   /// Creates a scrollable, linear array of widgets with a custom child model.
@@ -679,6 +692,7 @@
     EdgeInsetsGeometry padding,
     this.itemExtent,
     @required this.childrenDelegate,
+    double cacheExtent,
   }) : assert(childrenDelegate != null),
        super(
          key: key,
@@ -689,6 +703,7 @@
          physics: physics,
          shrinkWrap: shrinkWrap,
          padding: padding,
+        cacheExtent: cacheExtent,
        );
 
   /// If non-null, forces the children to have the given extent in the scroll
@@ -878,6 +893,7 @@
     @required this.gridDelegate,
     bool addAutomaticKeepAlives: true,
     bool addRepaintBoundaries: true,
+    double cacheExtent,
     List<Widget> children: const <Widget>[],
   }) : assert(gridDelegate != null),
        childrenDelegate = new SliverChildListDelegate(
@@ -894,6 +910,7 @@
          physics: physics,
          shrinkWrap: shrinkWrap,
          padding: padding,
+        cacheExtent: cacheExtent,
        );
 
   /// Creates a scrollable, 2D array of widgets that are created on demand.
@@ -929,6 +946,7 @@
     int itemCount,
     bool addAutomaticKeepAlives: true,
     bool addRepaintBoundaries: true,
+    double cacheExtent,
   }) : assert(gridDelegate != null),
        childrenDelegate = new SliverChildBuilderDelegate(
          itemBuilder,
@@ -945,6 +963,7 @@
          physics: physics,
          shrinkWrap: shrinkWrap,
          padding: padding,
+        cacheExtent: cacheExtent,
        );
 
   /// Creates a scrollable, 2D array of widgets with both a custom
@@ -965,6 +984,7 @@
     EdgeInsetsGeometry padding,
     @required this.gridDelegate,
     @required this.childrenDelegate,
+    double cacheExtent,
   }) : assert(gridDelegate != null),
        assert(childrenDelegate != null),
        super(
@@ -976,6 +996,7 @@
          physics: physics,
          shrinkWrap: shrinkWrap,
          padding: padding,
+        cacheExtent: cacheExtent,
        );
 
   /// Creates a scrollable, 2D array of widgets with a fixed number of tiles in
@@ -1007,6 +1028,7 @@
     double childAspectRatio: 1.0,
     bool addAutomaticKeepAlives: true,
     bool addRepaintBoundaries: true,
+    double cacheExtent,
     List<Widget> children: const <Widget>[],
   }) : gridDelegate = new SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: crossAxisCount,
@@ -1027,6 +1049,7 @@
     physics: physics,
     shrinkWrap: shrinkWrap,
     padding: padding,
+    cacheExtent: cacheExtent,
   );
 
   /// Creates a scrollable, 2D array of widgets with tiles that each have a
diff --git a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart
index 278448a..f23e9bb 100644
--- a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart
+++ b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart
@@ -323,11 +323,14 @@
   _RenderSingleChildViewport({
     AxisDirection axisDirection: AxisDirection.down,
     @required ViewportOffset offset,
+    double cacheExtent: RenderAbstractViewport.defaultCacheExtent,
     RenderBox child,
   }) : assert(axisDirection != null),
        assert(offset != null),
+       assert(cacheExtent != null),
        _axisDirection = axisDirection,
-       _offset = offset {
+       _offset = offset,
+       _cacheExtent = cacheExtent {
     this.child = child;
   }
 
@@ -357,6 +360,17 @@
     markNeedsLayout();
   }
 
+  /// {@macro flutter.rendering.viewport.cacheExtent}
+  double get cacheExtent => _cacheExtent;
+  double _cacheExtent;
+  set cacheExtent(double value) {
+    assert(value != null);
+    if (value == _cacheExtent)
+      return;
+    _cacheExtent = value;
+    markNeedsLayout();
+  }
+
   void _hasScrolled() {
     markNeedsPaint();
     markNeedsSemanticsUpdate();
@@ -588,4 +602,26 @@
     // Make sure the viewport itself is on screen.
     super.showOnScreen();
   }
+
+  @override
+  Rect describeSemanticsClip(RenderObject child) {
+    assert(axis != null);
+    switch (axis) {
+      case Axis.vertical:
+        return new Rect.fromLTRB(
+          semanticBounds.left,
+          semanticBounds.top - cacheExtent,
+          semanticBounds.right,
+          semanticBounds.bottom + cacheExtent,
+        );
+      case Axis.horizontal:
+        return new Rect.fromLTRB(
+          semanticBounds.left - cacheExtent,
+          semanticBounds.top,
+          semanticBounds.right + cacheExtent,
+          semanticBounds.bottom,
+        );
+    }
+    return null;
+  }
 }
diff --git a/packages/flutter/lib/src/widgets/sliver.dart b/packages/flutter/lib/src/widgets/sliver.dart
index 9074396..38a3cba 100644
--- a/packages/flutter/lib/src/widgets/sliver.dart
+++ b/packages/flutter/lib/src/widgets/sliver.dart
@@ -888,6 +888,25 @@
    assert(!_childElements.values.any((Element child) => child == null));
     _childElements.values.toList().forEach(visitor);
   }
+
+  @override
+  void debugVisitOnstageChildren(ElementVisitor visitor) {
+    _childElements.values.where((Element child) {
+      final SliverMultiBoxAdaptorParentData parentData = child.renderObject.parentData;
+      double itemExtent;
+      switch (renderObject.constraints.axis) {
+        case Axis.horizontal:
+          itemExtent = child.renderObject.paintBounds.width;
+          break;
+        case Axis.vertical:
+          itemExtent = child.renderObject.paintBounds.height;
+          break;
+      }
+
+      return parentData.layoutOffset < renderObject.constraints.scrollOffset + renderObject.constraints.remainingPaintExtent &&
+          parentData.layoutOffset + itemExtent > renderObject.constraints.scrollOffset;
+    }).forEach(visitor);
+  }
 }
 
 /// A sliver that contains a single box child that fills the remaining space in
diff --git a/packages/flutter/lib/src/widgets/viewport.dart b/packages/flutter/lib/src/widgets/viewport.dart
index cb508cc..16f1ea8 100644
--- a/packages/flutter/lib/src/widgets/viewport.dart
+++ b/packages/flutter/lib/src/widgets/viewport.dart
@@ -56,6 +56,7 @@
     this.anchor: 0.0,
     @required this.offset,
     this.center,
+    this.cacheExtent,
     List<Widget> slivers: const <Widget>[],
   }) : assert(offset != null),
        assert(slivers != null),
@@ -108,6 +109,9 @@
   /// The [center] must be the key of a child of the viewport.
   final Key center;
 
+  /// {@macro flutter.rendering.viewport.cacheExtent}
+  final double cacheExtent;
+
   /// Given a [BuildContext] and an [AxisDirection], determine the correct cross
   /// axis direction.
   ///
@@ -135,6 +139,7 @@
       crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection),
       anchor: anchor,
       offset: offset,
+      cacheExtent: cacheExtent,
     );
   }
 
@@ -144,7 +149,8 @@
       ..axisDirection = axisDirection
       ..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection)
       ..anchor = anchor
-      ..offset = offset;
+      ..offset = offset
+      ..cacheExtent = cacheExtent;
   }
 
   @override
@@ -199,6 +205,14 @@
       renderObject.center = null;
     }
   }
+
+  @override
+  void debugVisitOnstageChildren(ElementVisitor visitor) {
+    children.where((Element e) {
+      final RenderSliver renderSliver = e.renderObject;
+      return renderSliver.geometry.visible;
+    }).forEach(visitor);
+  }
 }
 
 /// A widget that is bigger on the inside and shrink wraps its children in the
diff --git a/packages/flutter/test/cupertino/refresh_test.dart b/packages/flutter/test/cupertino/refresh_test.dart
index c91c039..ef6317d 100644
--- a/packages/flutter/test/cupertino/refresh_test.dart
+++ b/packages/flutter/test/cupertino/refresh_test.dart
@@ -430,11 +430,11 @@
 
       // Now the sliver is scrolled off screen.
       expect(
-        tester.getTopLeft(find.widgetWithText(Center, '-1')).dy,
+        tester.getTopLeft(find.widgetWithText(Center, '-1', skipOffstage: false)).dy,
         moreOrLessEquals(-175.38461538461536),
       );
       expect(
-        tester.getBottomLeft(find.widgetWithText(Center, '-1')).dy,
+        tester.getBottomLeft(find.widgetWithText(Center, '-1', skipOffstage: false)).dy,
         moreOrLessEquals(-115.38461538461536),
       );
       expect(
@@ -720,11 +720,11 @@
 
         // Now the sliver is scrolled off screen.
         expect(
-          tester.getTopLeft(find.widgetWithText(Center, '-1')).dy,
+          tester.getTopLeft(find.widgetWithText(Center, '-1', skipOffstage: false)).dy,
           moreOrLessEquals(-175.38461538461536),
         );
         expect(
-          tester.getBottomLeft(find.widgetWithText(Center, '-1')).dy,
+          tester.getBottomLeft(find.widgetWithText(Center, '-1', skipOffstage: false)).dy,
           moreOrLessEquals(-115.38461538461536),
         );
 
@@ -872,7 +872,7 @@
       );
 
       expect(
-        CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))),
+        CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
         RefreshIndicatorMode.inactive,
       );
 
@@ -907,7 +907,7 @@
       await tester.pump(const Duration(seconds: 2));
 
       expect(
-        CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))),
+        CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
         RefreshIndicatorMode.inactive,
       );
 
@@ -1147,7 +1147,7 @@
           moreOrLessEquals(-145.0332383665717),
         );
         expect(
-          CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))),
+          CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
           RefreshIndicatorMode.refresh,
         );
 
@@ -1155,7 +1155,7 @@
         // The sliver layout extent is removed on next frame.
         await tester.pump();
         expect(
-          CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))),
+          CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
           RefreshIndicatorMode.inactive,
         );
         // Nothing moved.
@@ -1208,7 +1208,7 @@
         await tester.pump(const Duration(seconds: 5));
         // In refresh mode but has no UI.
         expect(
-          CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))),
+          CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
           RefreshIndicatorMode.refresh,
         );
         expect(
@@ -1221,7 +1221,7 @@
         await tester.pump();
         // Goes to inactive right away since the sliver is already collapsed.
         expect(
-          CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))),
+          CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
           RefreshIndicatorMode.inactive,
         );
 
diff --git a/packages/flutter/test/material/app_bar_test.dart b/packages/flutter/test/material/app_bar_test.dart
index 54b9a84..927134c 100644
--- a/packages/flutter/test/material/app_bar_test.dart
+++ b/packages/flutter/test/material/app_bar_test.dart
@@ -54,16 +54,11 @@
   return PrimaryScrollController.of(tester.element(find.byType(CustomScrollView)));
 }
 
-bool appBarIsVisible(WidgetTester tester) {
-  final RenderSliver sliver = tester.element(find.byType(SliverAppBar)).findRenderObject();
-  return sliver.geometry.visible;
-}
+double appBarHeight(WidgetTester tester) => tester.getSize(find.byType(AppBar, skipOffstage: false)).height;
+double appBarTop(WidgetTester tester) => tester.getTopLeft(find.byType(AppBar, skipOffstage: false)).dy;
+double appBarBottom(WidgetTester tester) => tester.getBottomLeft(find.byType(AppBar, skipOffstage: false)).dy;
 
-double appBarHeight(WidgetTester tester) => tester.getSize(find.byType(AppBar)).height;
-double appBarTop(WidgetTester tester) => tester.getTopLeft(find.byType(AppBar)).dy;
-double appBarBottom(WidgetTester tester) => tester.getBottomLeft(find.byType(AppBar)).dy;
-
-double tabBarHeight(WidgetTester tester) => tester.getSize(find.byType(TabBar)).height;
+double tabBarHeight(WidgetTester tester) => tester.getSize(find.byType(TabBar, skipOffstage: false)).height;
 
 void main() {
   setUp(() {
@@ -592,7 +587,7 @@
 
     final ScrollController controller = primaryScrollController(tester);
     expect(controller.offset, 0.0);
-    expect(appBarIsVisible(tester), true);
+    expect(find.byType(SliverAppBar), findsOneWidget);
 
     final double initialAppBarHeight = appBarHeight(tester);
     final double initialTabBarHeight = tabBarHeight(tester);
@@ -600,21 +595,21 @@
     // Scroll the not-pinned appbar partially out of view
     controller.jumpTo(50.0);
     await tester.pump();
-    expect(appBarIsVisible(tester), true);
+    expect(find.byType(SliverAppBar), findsOneWidget);
     expect(appBarHeight(tester), initialAppBarHeight);
     expect(tabBarHeight(tester), initialTabBarHeight);
 
     // Scroll the not-pinned appbar out of view
     controller.jumpTo(600.0);
     await tester.pump();
-    expect(appBarIsVisible(tester), false);
+    expect(find.byType(SliverAppBar), findsNothing);
     expect(appBarHeight(tester), initialAppBarHeight);
     expect(tabBarHeight(tester), initialTabBarHeight);
 
     // Scroll the not-pinned appbar back into view
     controller.jumpTo(0.0);
     await tester.pump();
-    expect(appBarIsVisible(tester), true);
+    expect(find.byType(SliverAppBar), findsOneWidget);
     expect(appBarHeight(tester), initialAppBarHeight);
     expect(tabBarHeight(tester), initialTabBarHeight);
   });
@@ -629,7 +624,7 @@
 
     final ScrollController controller = primaryScrollController(tester);
     expect(controller.offset, 0.0);
-    expect(appBarIsVisible(tester), true);
+    expect(find.byType(SliverAppBar), findsOneWidget);
     expect(appBarHeight(tester), 128.0);
 
     const double initialAppBarHeight = 128.0;
@@ -639,7 +634,7 @@
     // point both the toolbar and the tabbar are visible.
     controller.jumpTo(600.0);
     await tester.pump();
-    expect(appBarIsVisible(tester), true);
+    expect(find.byType(SliverAppBar), findsOneWidget);
     expect(tabBarHeight(tester), initialTabBarHeight);
     expect(appBarHeight(tester), lessThan(initialAppBarHeight));
     expect(appBarHeight(tester), greaterThan(initialTabBarHeight));
@@ -647,7 +642,7 @@
     // Scroll the not-pinned appbar back into view
     controller.jumpTo(0.0);
     await tester.pump();
-    expect(appBarIsVisible(tester), true);
+    expect(find.byType(SliverAppBar), findsOneWidget);
     expect(appBarHeight(tester), initialAppBarHeight);
     expect(tabBarHeight(tester), initialTabBarHeight);
   });
@@ -662,7 +657,7 @@
 
     final ScrollController controller = primaryScrollController(tester);
     expect(controller.offset, 0.0);
-    expect(appBarIsVisible(tester), true);
+    expect(find.byType(SliverAppBar), findsOneWidget);
     expect(appBarHeight(tester), 128.0);
 
     const double initialAppBarHeight = 128.0;
@@ -672,7 +667,7 @@
     // point only the tabBar is visible.
     controller.jumpTo(600.0);
     await tester.pump();
-    expect(appBarIsVisible(tester), true);
+    expect(find.byType(SliverAppBar), findsOneWidget);
     expect(tabBarHeight(tester), initialTabBarHeight);
     expect(appBarHeight(tester), lessThan(initialAppBarHeight));
     expect(appBarHeight(tester), initialTabBarHeight);
@@ -680,7 +675,7 @@
     // Scroll the floating-pinned appbar back into view
     controller.jumpTo(0.0);
     await tester.pump();
-    expect(appBarIsVisible(tester), true);
+    expect(find.byType(SliverAppBar), findsOneWidget);
     expect(appBarHeight(tester), initialAppBarHeight);
     expect(tabBarHeight(tester), initialTabBarHeight);
   });
@@ -692,7 +687,7 @@
       snap: true,
       expandedHeight: 128.0,
     ));
-    expect(appBarIsVisible(tester), true);
+    expect(find.byType(SliverAppBar), findsOneWidget);
     expect(appBarTop(tester), 0.0);
     expect(appBarHeight(tester), 128.0);
     expect(appBarBottom(tester), 128.0);
@@ -701,7 +696,7 @@
     final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
     position.jumpTo(256.00);
     await tester.pumpAndSettle();
-    expect(appBarIsVisible(tester), false);
+    expect(find.byType(SliverAppBar), findsNothing);
     expect(appBarTop(tester), lessThanOrEqualTo(-128.0));
 
     // Drag the scrollable up and down. The app bar should not snap open, its
@@ -773,7 +768,7 @@
       snap: true,
       expandedHeight: 128.0,
     ));
-    expect(appBarIsVisible(tester), true);
+    expect(find.byType(SliverAppBar), findsOneWidget);
     expect(appBarTop(tester), 0.0);
     expect(appBarHeight(tester), 128.0);
     expect(appBarBottom(tester), 128.0);
@@ -783,7 +778,7 @@
     final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
     position.jumpTo(256.0);
     await tester.pumpAndSettle();
-    expect(appBarIsVisible(tester), true);
+    expect(find.byType(SliverAppBar), findsOneWidget);
     expect(appBarTop(tester), 0.0);
     expect(appBarHeight(tester), kTextTabBarHeight);
 
diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart
index 918376a..87fa409 100644
--- a/packages/flutter/test/material/tabs_test.dart
+++ b/packages/flutter/test/material/tabs_test.dart
@@ -289,7 +289,7 @@
     }
 
     StateMarkerState findStateMarkerState(String name) {
-      return tester.state(find.widgetWithText(StateMarker, name));
+      return tester.state(find.widgetWithText(StateMarker, name, skipOffstage: false));
     }
 
     await tester.pumpWidget(builder());
diff --git a/packages/flutter/test/material/text_field_focus_test.dart b/packages/flutter/test/material/text_field_focus_test.dart
index 8d4ee76..98e7145 100644
--- a/packages/flutter/test/material/text_field_focus_test.dart
+++ b/packages/flutter/test/material/text_field_focus_test.dart
@@ -162,7 +162,7 @@
 
     await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0));
     await tester.pump();
-    expect(find.byType(TextField), findsOneWidget);
+    expect(find.byType(TextField, skipOffstage: false), findsOneWidget);
     expect(tester.testTextInput.isVisible, isTrue);
 
     focusNode.unfocus();
@@ -201,10 +201,10 @@
     expect(find.byType(TextField), findsOneWidget);
     await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0));
     await tester.pump();
-    expect(find.byType(TextField), findsOneWidget);
+    expect(find.byType(TextField, skipOffstage: false), findsOneWidget);
     await tester.pumpWidget(makeTest('test'));
     await tester.pump(); // in case the AutomaticKeepAlive widget thinks it needs a cleanup frame
-    expect(find.byType(TextField), findsOneWidget);
+    expect(find.byType(TextField, skipOffstage: false), findsOneWidget);
   });
 
   testWidgets('TextField with decoration:null', (WidgetTester tester) async {
diff --git a/packages/flutter/test/rendering/sliver_cache_test.dart b/packages/flutter/test/rendering/sliver_cache_test.dart
new file mode 100644
index 0000000..91ca7a8
--- /dev/null
+++ b/packages/flutter/test/rendering/sliver_cache_test.dart
@@ -0,0 +1,971 @@
+// Copyright 2018 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/foundation.dart';
+import 'package:flutter/rendering.dart';
+import 'package:test/test.dart';
+
+import 'rendering_tester.dart';
+
+void main() {
+  test('RenderViewport calculates correct constraints, RenderSliverToBoxAdapter calculates correct geometry', () {
+    final List<RenderSliver> children = new List<RenderSliver>.generate(30, (int index) {
+      return new RenderSliverToBoxAdapter(
+        child: new RenderSizedBox(const Size(400.0, 100.0)),
+      );
+    });
+
+    // Viewport is 800x600, can show 6 children at a time.
+
+    final RenderViewport root = new RenderViewport(
+      axisDirection: AxisDirection.down,
+      crossAxisDirection: AxisDirection.right,
+      offset: new ViewportOffset.zero(),
+      cacheExtent: 250.0,
+      children: children,
+    );
+    layout(root);
+
+    RenderSliver firstVisible = children[0];
+    expectSliverConstraints(
+      sliver: firstVisible,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 600.0 + 250.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: firstVisible,
+      paintExtent: 100.0,
+      cacheExtent: 100.0,
+      visible: true,
+    );
+
+    RenderSliver lastVisible = children[5];
+    expectSliverConstraints(
+      sliver: lastVisible,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 100.0,
+      remainingCacheExtent: 350.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: lastVisible,
+      paintExtent: 100.0,
+      cacheExtent: 100.0,
+      visible: true,
+    );
+
+    RenderSliver firstInCache = children[6];
+    expectSliverConstraints(
+      sliver: firstInCache,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 0.0,
+      remainingCacheExtent: 250.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: firstInCache,
+      paintExtent: 0.0,
+      cacheExtent: 100.0,
+      visible: false,
+    );
+
+    RenderSliver lastInCache = children[8];
+    expectSliverConstraints(
+      sliver: lastInCache,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 0.0,
+      remainingCacheExtent: 50.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: lastInCache,
+      paintExtent: 0.0,
+      cacheExtent: 50.0,
+      visible: false,
+    );
+
+    RenderSliver outsideCache = children[9];
+    expectSliverConstraints(
+      sliver: outsideCache,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 0.0,
+      remainingCacheExtent: 0.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: outsideCache,
+      paintExtent: 0.0,
+      cacheExtent: 0.0,
+      visible: false,
+    );
+
+    // scroll down half a sliver
+    root.offset = new ViewportOffset.fixed(50.0);
+    pumpFrame();
+
+    firstVisible = children[0];
+    expectSliverConstraints(
+      sliver: firstVisible,
+      cacheOrigin: -50.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 50.0 + 600.0 + 250.0,
+      scrollOffset: 50.0,
+    );
+    expectSliverGeometry(
+      sliver: firstVisible,
+      paintExtent: 50.0,
+      cacheExtent: 100.0,
+      visible: true,
+    );
+
+    lastVisible = children[6];
+    expectSliverConstraints(
+      sliver: lastVisible,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 50.0,
+      remainingCacheExtent: 300.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: lastVisible,
+      paintExtent: 50.0,
+      cacheExtent: 100.0,
+      visible: true,
+    );
+
+    firstInCache = children[7];
+    expectSliverConstraints(
+      sliver: firstInCache,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 0.0,
+      remainingCacheExtent: 200.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: firstInCache,
+      paintExtent: 0.0,
+      cacheExtent: 100.0,
+      visible: false,
+    );
+
+    lastInCache = children[8];
+    expectSliverConstraints(
+      sliver: lastInCache,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 0.0,
+      remainingCacheExtent: 100.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: lastInCache,
+      paintExtent: 0.0,
+      cacheExtent: 100.0,
+      visible: false,
+    );
+
+    outsideCache = children[9];
+    expectSliverConstraints(
+      sliver: outsideCache,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 0.0,
+      remainingCacheExtent: 0.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: outsideCache,
+      paintExtent: 0.0,
+      cacheExtent: 0.0,
+      visible: false,
+    );
+
+    // scroll down 1.5 slivers
+    root.offset = new ViewportOffset.fixed(150.0);
+    pumpFrame();
+
+    RenderSliver firstInPreCache = children[0];
+    expectSliverConstraints(
+      sliver: firstInPreCache,
+      cacheOrigin: -150.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 150.0 + 600.0 + 250.0,
+      scrollOffset: 150.0,
+    );
+    expectSliverGeometry(
+      sliver: firstInPreCache,
+      paintExtent: 0.0,
+      cacheExtent: 100.0,
+      visible: false,
+    );
+
+    firstVisible = children[1];
+    expectSliverConstraints(
+      sliver: firstVisible,
+      cacheOrigin: -50.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 50.0 + 600.0 + 250.0,
+      scrollOffset: 50.0,
+    );
+    expectSliverGeometry(
+      sliver: firstVisible,
+      paintExtent: 50.0,
+      cacheExtent: 100.0,
+      visible: true,
+    );
+
+    // scroll down 10 slivers
+    root.offset = new ViewportOffset.fixed(1000.0);
+    pumpFrame();
+
+    final RenderSliver first = children[0];
+    expectSliverConstraints(
+      sliver: first,
+      cacheOrigin: -250.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 250.0 + 600.0 + 250.0,
+      scrollOffset: 1000.0,
+    );
+    expectSliverGeometry(
+      sliver: first,
+      paintExtent: 0.0,
+      cacheExtent: 0.0,
+      visible: false,
+    );
+
+    firstInPreCache = children[7];
+    expectSliverConstraints(
+      sliver: firstInPreCache,
+      cacheOrigin: -250.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 250.0 + 600.0 + 250.0,
+      scrollOffset: 300.0,
+    );
+    expectSliverGeometry(
+      sliver: firstInPreCache,
+      paintExtent: 0.0,
+      cacheExtent: 50.0,
+      visible: false,
+    );
+
+    final RenderSliver lastInPreCache = children[9];
+    expectSliverConstraints(
+      sliver: lastInPreCache,
+      cacheOrigin: -100.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 100.0 + 600.0 + 250.0,
+      scrollOffset: 100.0,
+    );
+    expectSliverGeometry(
+      sliver: lastInPreCache,
+      paintExtent: 0.0,
+      cacheExtent: 100.0,
+      visible: false,
+    );
+
+    firstVisible = children[10];
+    expectSliverConstraints(
+      sliver: firstVisible,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 600.0 + 250.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: firstVisible,
+      paintExtent: 100.0,
+      cacheExtent: 100.0,
+      visible: true,
+    );
+  });
+
+  test('RenderSliverFixedExtentList calculates correct geometry', () {
+    // Viewport is 800x600, can show 6 full children at a time
+    final List<RenderBox> children = new List<RenderBox>.generate(30, (int index) {
+      return new RenderSizedBox(const Size(400.0, 100.0));
+    });
+    final TestRenderSliverBoxChildManager childManager = new TestRenderSliverBoxChildManager(
+      children: children,
+    );
+    RenderSliverFixedExtentList inner;
+    final RenderViewport root = new RenderViewport(
+      axisDirection: AxisDirection.down,
+      crossAxisDirection: AxisDirection.right,
+      offset: new ViewportOffset.zero(),
+      cacheExtent: 250.0,
+      children: <RenderSliver>[
+        inner = childManager.createRenderSliverFixedExtentList(),
+      ],
+    );
+    layout(root);
+
+    expectSliverConstraints(
+      sliver: inner,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 600.0 + 250.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: inner,
+      paintExtent: 600.0,
+      cacheExtent: 850.0,
+      visible: true,
+    );
+    expect(children.sublist(0, 9).every((RenderBox r) => r.attached), true);
+    expect(children.sublist(9, 30).any((RenderBox r) => r.attached), false);
+
+    // scroll half an item down
+    root.offset = new ViewportOffset.fixed(50.0);
+    pumpFrame();
+
+    expectSliverConstraints(
+      sliver: inner,
+      cacheOrigin: -50.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 50.0 + 600.0 + 250.0,
+      scrollOffset: 50.0,
+    );
+    expectSliverGeometry(
+      sliver: inner,
+      paintExtent: 600.0,
+      cacheExtent: 900.0,
+      visible: true,
+    );
+    expect(children.sublist(0, 9).every((RenderBox r) => r.attached), true);
+    expect(children.sublist(9, 30).any((RenderBox r) => r.attached), false);
+
+
+    // scroll to the middle
+    root.offset = new ViewportOffset.fixed(1500.0);
+    pumpFrame();
+
+    expectSliverConstraints(
+      sliver: inner,
+      cacheOrigin: -250.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 250.0 + 600.0 + 250.0,
+      scrollOffset: 1500.0,
+    );
+    expectSliverGeometry(
+      sliver: inner,
+      paintExtent: 600.0,
+      cacheExtent: 1100.0,
+      visible: true,
+    );
+
+    expect(children.sublist(0, 12).any((RenderBox r) => r.attached), false);
+    expect(children.sublist(12, 24).every((RenderBox r) => r.attached), true);
+    expect(children.sublist(24, 30).any((RenderBox r) => r.attached), false);
+
+    // scroll to the end
+    root.offset = new ViewportOffset.fixed(2400.0);
+    pumpFrame();
+
+    expectSliverConstraints(
+      sliver: inner,
+      cacheOrigin: -250.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 250.0 + 600.0 + 250.0,
+      scrollOffset: 2400.0,
+    );
+    expectSliverGeometry(
+      sliver: inner,
+      paintExtent: 600.0,
+      cacheExtent: 850.0,
+      visible: true,
+    );
+    expect(children.sublist(0, 21).any((RenderBox r) => r.attached), false);
+    expect(children.sublist(21, 30).every((RenderBox r) => r.attached), true);
+  });
+
+  test('RenderSliverList calculates correct geometry', () {
+    // Viewport is 800x600, can show 6 full children at a time
+    final List<RenderBox> children = new List<RenderBox>.generate(30, (int index) {
+      return new RenderSizedBox(const Size(400.0, 100.0));
+    });
+    final TestRenderSliverBoxChildManager childManager = new TestRenderSliverBoxChildManager(
+      children: children,
+    );
+    RenderSliverList inner;
+    final RenderViewport root = new RenderViewport(
+      axisDirection: AxisDirection.down,
+      crossAxisDirection: AxisDirection.right,
+      offset: new ViewportOffset.zero(),
+      cacheExtent: 250.0,
+      children: <RenderSliver>[
+        inner = childManager.createRenderSliverList(),
+      ],
+    );
+    layout(root);
+
+    expectSliverConstraints(
+      sliver: inner,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 600.0 + 250.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: inner,
+      paintExtent: 600.0,
+      cacheExtent: 850.0,
+      visible: true,
+    );
+    expect(children.sublist(0, 9).every((RenderBox r) => r.attached), true);
+    expect(children.sublist(9, 30).any((RenderBox r) => r.attached), false);
+
+    // scroll half an item down
+    root.offset = new ViewportOffset.fixed(50.0);
+    pumpFrame();
+
+    expectSliverConstraints(
+      sliver: inner,
+      cacheOrigin: -50.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 50.0 + 600.0 + 250.0,
+      scrollOffset: 50.0,
+    );
+    expectSliverGeometry(
+      sliver: inner,
+      paintExtent: 600.0,
+      cacheExtent: 900.0,
+      visible: true,
+    );
+    expect(children.sublist(0, 9).every((RenderBox r) => r.attached), true);
+    expect(children.sublist(9, 30).any((RenderBox r) => r.attached), false);
+
+
+    // scroll to the middle
+    root.offset = new ViewportOffset.fixed(1500.0);
+    pumpFrame();
+
+    expectSliverConstraints(
+      sliver: inner,
+      cacheOrigin: -250.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 250.0 + 600.0 + 250.0,
+      scrollOffset: 1500.0,
+    );
+    expectSliverGeometry(
+      sliver: inner,
+      paintExtent: 600.0,
+      cacheExtent: 1100.0,
+      visible: true,
+    );
+
+    expect(children.sublist(0, 12).any((RenderBox r) => r.attached), false);
+    expect(children.sublist(12, 24).every((RenderBox r) => r.attached), true);
+    expect(children.sublist(24, 30).any((RenderBox r) => r.attached), false);
+
+    // scroll to the end
+    root.offset = new ViewportOffset.fixed(2400.0);
+    pumpFrame();
+
+    expectSliverConstraints(
+      sliver: inner,
+      cacheOrigin: -250.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 250.0 + 600.0 + 250.0,
+      scrollOffset: 2400.0,
+    );
+    expectSliverGeometry(
+      sliver: inner,
+      paintExtent: 600.0,
+      cacheExtent: 850.0,
+      visible: true,
+    );
+    expect(children.sublist(0, 21).any((RenderBox r) => r.attached), false);
+    expect(children.sublist(21, 30).every((RenderBox r) => r.attached), true);
+  });
+
+  test('RenderSliverGrid calculates correct geometry', () {
+    // Viewport is 800x600, each grid element is 400x100, giving us space for 12 visible children
+    final List<RenderBox> children = new List<RenderBox>.generate(60, (int index) {
+      return new RenderSizedBox(const Size(400.0, 100.0));
+    });
+    final TestRenderSliverBoxChildManager childManager = new TestRenderSliverBoxChildManager(
+      children: children,
+    );
+    RenderSliverGrid inner;
+    final RenderViewport root = new RenderViewport(
+      axisDirection: AxisDirection.down,
+      crossAxisDirection: AxisDirection.right,
+      offset: new ViewportOffset.zero(),
+      cacheExtent: 250.0,
+      children: <RenderSliver>[
+        inner = childManager.createRenderSliverGrid(),
+      ],
+    );
+    layout(root);
+
+    expectSliverConstraints(
+      sliver: inner,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 600.0 + 250.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: inner,
+      paintExtent: 600.0,
+      cacheExtent: 850.0,
+      visible: true,
+    );
+    expect(children.sublist(0, 18).every((RenderBox r) => r.attached), true);
+    expect(children.sublist(18, 60).any((RenderBox r) => r.attached), false);
+
+    // scroll half an item down
+    root.offset = new ViewportOffset.fixed(50.0);
+    pumpFrame();
+
+    expectSliverConstraints(
+      sliver: inner,
+      cacheOrigin: -50.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 50.0 + 600.0 + 250.0,
+      scrollOffset: 50.0,
+    );
+    expectSliverGeometry(
+      sliver: inner,
+      paintExtent: 600.0,
+      cacheExtent: 900.0,
+      visible: true,
+    );
+    expect(children.sublist(0, 18).every((RenderBox r) => r.attached), true);
+    expect(children.sublist(18, 60).any((RenderBox r) => r.attached), false);
+
+
+    // scroll to the middle
+    root.offset = new ViewportOffset.fixed(1500.0);
+    pumpFrame();
+
+    expectSliverConstraints(
+      sliver: inner,
+      cacheOrigin: -250.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 250.0 + 600.0 + 250.0,
+      scrollOffset: 1500.0,
+    );
+    expectSliverGeometry(
+      sliver: inner,
+      paintExtent: 600.0,
+      cacheExtent: 1100.0,
+      visible: true,
+    );
+
+    expect(children.sublist(0, 24).any((RenderBox r) => r.attached), false);
+    expect(children.sublist(24, 48).every((RenderBox r) => r.attached), true);
+    expect(children.sublist(48, 60).any((RenderBox r) => r.attached), false);
+
+    // scroll to the end
+    root.offset = new ViewportOffset.fixed(2400.0);
+    pumpFrame();
+
+    expectSliverConstraints(
+      sliver: inner,
+      cacheOrigin: -250.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 250.0 + 600.0 + 250.0,
+      scrollOffset: 2400.0,
+    );
+    expectSliverGeometry(
+      sliver: inner,
+      paintExtent: 600.0,
+      cacheExtent: 850.0,
+      visible: true,
+    );
+    expect(children.sublist(0, 42).any((RenderBox r) => r.attached), false);
+    expect(children.sublist(42, 60).every((RenderBox r) => r.attached), true);
+  });
+
+  test('RenderSliverPadding calculates correct geometry', () {
+    // Viewport is 800x600, each item is 100px high with 50px before and after = 200px
+
+    final List<RenderSliverToBoxAdapter> adapters = <RenderSliverToBoxAdapter>[];
+    final List<RenderSliverPadding> paddings = new List<RenderSliverPadding>.generate(30, (int index) {
+      RenderSliverToBoxAdapter adapter;
+      final RenderSliverPadding padding = new RenderSliverPadding(
+        padding: const EdgeInsets.symmetric(vertical: 50.0),
+        child: adapter = new RenderSliverToBoxAdapter(
+          child: new RenderSizedBox(const Size(400.0, 100.0)),
+        ),
+      );
+      adapters.add(adapter);
+      return padding;
+    });
+
+
+    final RenderViewport root = new RenderViewport(
+      axisDirection: AxisDirection.down,
+      crossAxisDirection: AxisDirection.right,
+      offset: new ViewportOffset.zero(),
+      cacheExtent: 250.0,
+      children: paddings,
+    );
+    layout(root);
+
+    RenderSliverPadding firstVisiblePadding = paddings[0];
+    expectSliverConstraints(
+      sliver: firstVisiblePadding,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 600.0 + 250.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: firstVisiblePadding,
+      paintExtent: 200.0,
+      cacheExtent: 200.0,
+      visible: true,
+    );
+    RenderSliverToBoxAdapter firstVisiblePadded = adapters[0];
+    expectSliverConstraints(
+      sliver: firstVisiblePadded,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 550.0,
+      remainingCacheExtent: 600.0 + 250.0 - 50.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: firstVisiblePadded,
+      paintExtent: 100.0,
+      cacheExtent: 100.0,
+      visible: true,
+    );
+
+    RenderSliverPadding lastVisiblePadding = paddings[2];
+    expectSliverConstraints(
+      sliver: lastVisiblePadding,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 200.0,
+      remainingCacheExtent: 200.0 + 250.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: lastVisiblePadding,
+      paintExtent: 200.0,
+      cacheExtent: 200.0,
+      visible: true,
+    );
+    RenderSliverToBoxAdapter lastVisiblePadded = adapters[2];
+    expectSliverConstraints(
+      sliver: lastVisiblePadded,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 150.0,
+      remainingCacheExtent: 200.0 + 250.0 - 50.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: lastVisiblePadded,
+      paintExtent: 100.0,
+      cacheExtent: 100.0,
+      visible: true,
+    );
+
+    final RenderSliverPadding firstCachePadding = paddings[3];
+    expectSliverConstraints(
+      sliver: firstCachePadding,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 0.0,
+      remainingCacheExtent: 250.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: firstCachePadding,
+      paintExtent: 0.0,
+      cacheExtent: 200.0,
+      visible: false,
+    );
+    final RenderSliverToBoxAdapter firstCachePadded = adapters[3];
+    expectSliverConstraints(
+      sliver: firstCachePadded,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 0.0,
+      remainingCacheExtent: 250.0 - 50.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: firstCachePadded,
+      paintExtent: 0.0,
+      cacheExtent: 100.0,
+      visible: false,
+    );
+
+    final RenderSliverPadding lastCachePadding = paddings[4];
+    expectSliverConstraints(
+      sliver: lastCachePadding,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 0.0,
+      remainingCacheExtent: 50.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: lastCachePadding,
+      paintExtent: 0.0,
+      cacheExtent: 50.0,
+      visible: false,
+    );
+    final RenderSliverToBoxAdapter lastCachePadded = adapters[4];
+    expectSliverConstraints(
+      sliver: lastCachePadded,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 0.0,
+      remainingCacheExtent: 0.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: lastCachePadded,
+      paintExtent: 0.0,
+      cacheExtent: 0.0,
+      visible: false,
+    );
+
+    // scroll first padding off screen
+    root.offset = new ViewportOffset.fixed(50.0);
+    pumpFrame();
+
+    firstVisiblePadding = paddings[0];
+    expectSliverConstraints(
+      sliver: firstVisiblePadding,
+      cacheOrigin: -50.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 50.0 + 600.0 + 250.0,
+      scrollOffset: 50.0,
+    );
+    expectSliverGeometry(
+      sliver: firstVisiblePadding,
+      paintExtent: 150.0,
+      cacheExtent: 200.0,
+      visible: true,
+    );
+    firstVisiblePadded = adapters[0];
+    expectSliverConstraints(
+      sliver: firstVisiblePadded,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 600.0 + 250.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: firstVisiblePadded,
+      paintExtent: 100.0,
+      cacheExtent: 100.0,
+      visible: true,
+    );
+
+    // scroll to the end
+    root.offset = new ViewportOffset.fixed(5400.0);
+    pumpFrame();
+
+
+    final RenderSliverPadding firstPadding = paddings[0];
+    expectSliverConstraints(
+      sliver: firstPadding,
+      cacheOrigin: -250.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 250.0 + 600.0 + 250.0,
+      scrollOffset: 5400.0,
+    );
+    expectSliverGeometry(
+      sliver: firstPadding,
+      paintExtent: 0.0,
+      cacheExtent: 0.0,
+      visible: false,
+    );
+    final RenderSliverToBoxAdapter firstPadded = adapters[0];
+    expectSliverConstraints(
+      sliver: firstPadded,
+      cacheOrigin: -200.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 250.0 + 600.0 + 250.0,
+      scrollOffset: 5350.0,
+    );
+    expectSliverGeometry(
+      sliver: firstPadded,
+      paintExtent: 0.0,
+      cacheExtent: 0.0,
+      visible: false,
+    );
+
+    final RenderSliverPadding firstPreCachePadding = paddings[25];
+    expectSliverConstraints(
+      sliver: firstPreCachePadding,
+      cacheOrigin: -250.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 250.0 + 600.0 + 250.0,
+      scrollOffset: 400.0,
+    );
+    expectSliverGeometry(
+      sliver: firstPreCachePadding,
+      paintExtent: 0.0,
+      cacheExtent: 50.0,
+      visible: false,
+    );
+    final RenderSliverToBoxAdapter firstPreCachePadded = adapters[25];
+    expectSliverConstraints(
+      sliver: firstPreCachePadded,
+      cacheOrigin: -200.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 250.0 + 600.0 + 250.0,
+      scrollOffset: 350.0,
+    );
+    expectSliverGeometry(
+      sliver: firstPreCachePadded,
+      paintExtent: 0.0,
+      cacheExtent: 0.0,
+      visible: false,
+    );
+
+    final RenderSliverPadding lastPreCachePadding = paddings[26];
+    expectSliverConstraints(
+      sliver: lastPreCachePadding,
+      cacheOrigin: -200.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 200.0 + 600.0 + 250.0,
+      scrollOffset: 200.0,
+    );
+    expectSliverGeometry(
+      sliver: lastPreCachePadding,
+      paintExtent: 0.0,
+      cacheExtent: 200.0,
+      visible: false,
+    );
+    final RenderSliverToBoxAdapter lastPreCachePadded = adapters[26];
+    expectSliverConstraints(
+      sliver: lastPreCachePadded,
+      cacheOrigin: -150.0,
+      remainingPaintExtent: 600.0,
+      remainingCacheExtent: 150.0 + 600.0 + 250.0,
+      scrollOffset: 150.0,
+    );
+    expectSliverGeometry(
+      sliver: lastPreCachePadded,
+      paintExtent: 0.0,
+      cacheExtent: 100.0,
+      visible: false,
+    );
+
+    lastVisiblePadding = paddings[29];
+    expectSliverConstraints(
+      sliver: lastVisiblePadding,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 200.0,
+      remainingCacheExtent: 200.0 + 250.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: lastVisiblePadding,
+      paintExtent: 200.0,
+      cacheExtent: 200.0,
+      visible: true,
+    );
+    lastVisiblePadded = adapters[29];
+    expectSliverConstraints(
+      sliver: lastVisiblePadded,
+      cacheOrigin: 0.0,
+      remainingPaintExtent: 150.0,
+      remainingCacheExtent: 150.0 + 250.0,
+      scrollOffset: 0.0,
+    );
+    expectSliverGeometry(
+      sliver: lastVisiblePadded,
+      paintExtent: 100.0,
+      cacheExtent: 100.0,
+      visible: true,
+    );
+  });
+}
+
+void expectSliverConstraints({RenderSliver sliver, double cacheOrigin, double remainingPaintExtent, double remainingCacheExtent, double scrollOffset}) {
+  expect(sliver.constraints.cacheOrigin, cacheOrigin, reason: 'cacheOrigin');
+  expect(sliver.constraints.remainingPaintExtent, remainingPaintExtent, reason: 'remainingPaintExtent');
+  expect(sliver.constraints.remainingCacheExtent, remainingCacheExtent, reason: 'remainingCacheExtent');
+  expect(sliver.constraints.scrollOffset, scrollOffset, reason: 'scrollOffset');
+}
+
+void expectSliverGeometry({RenderSliver sliver, double paintExtent, double cacheExtent, bool visible}) {
+  expect(sliver.geometry.paintExtent, paintExtent, reason: 'paintExtent');
+  expect(sliver.geometry.cacheExtent, cacheExtent, reason: 'cacheExtent');
+  expect(sliver.geometry.visible, visible, reason: 'visible');
+}
+
+class TestRenderSliverBoxChildManager extends RenderSliverBoxChildManager {
+  TestRenderSliverBoxChildManager({
+    this.children,
+  });
+
+  RenderSliverMultiBoxAdaptor _renderObject;
+  List<RenderBox> children;
+
+  RenderSliverList createRenderSliverList() {
+    assert(_renderObject == null);
+    _renderObject = new RenderSliverList(childManager: this);
+    return _renderObject;
+  }
+
+  RenderSliverFixedExtentList createRenderSliverFixedExtentList() {
+    assert(_renderObject == null);
+    _renderObject = new RenderSliverFixedExtentList(
+      childManager: this,
+      itemExtent: 100.0,
+    );
+    return _renderObject;
+  }
+
+  RenderSliverGrid createRenderSliverGrid() {
+    assert(_renderObject == null);
+    _renderObject = new RenderSliverGrid(
+      childManager: this,
+      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
+        crossAxisCount: 2,
+        childAspectRatio: 4.0,
+      ),
+    );
+    return _renderObject;
+  }
+
+  int _currentlyUpdatingChildIndex;
+
+  @override
+  void createChild(int index, { @required RenderBox after }) {
+    if (index < 0 || index >= children.length)
+      return null;
+    try {
+      _currentlyUpdatingChildIndex = index;
+      _renderObject.insert(children[index], after: after);
+    } finally {
+      _currentlyUpdatingChildIndex = null;
+    }
+  }
+
+  @override
+  void removeChild(RenderBox child) {
+    _renderObject.remove(child);
+  }
+
+  @override
+  double estimateMaxScrollOffset(SliverConstraints constraints, {
+    int firstIndex,
+    int lastIndex,
+    double leadingScrollOffset,
+    double trailingScrollOffset,
+  }) {
+    assert(lastIndex >= firstIndex);
+    return children.length * (trailingScrollOffset - leadingScrollOffset) / (lastIndex - firstIndex + 1);
+  }
+
+  @override
+  int get childCount => children.length;
+
+  @override
+  void didAdoptChild(RenderBox child) {
+    assert(_currentlyUpdatingChildIndex != null);
+    final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
+    childParentData.index = _currentlyUpdatingChildIndex;
+  }
+
+  @override
+  void setDidUnderflow(bool value) { }
+}
diff --git a/packages/flutter/test/rendering/slivers_block_test.dart b/packages/flutter/test/rendering/slivers_block_test.dart
index db28533..f815cd5 100644
--- a/packages/flutter/test/rendering/slivers_block_test.dart
+++ b/packages/flutter/test/rendering/slivers_block_test.dart
@@ -83,6 +83,7 @@
       axisDirection: AxisDirection.down,
       crossAxisDirection: AxisDirection.right,
       offset: new ViewportOffset.zero(),
+      cacheExtent: 0.0,
       children: <RenderSliver>[
         inner = childManager.createRenderObject(),
       ],
@@ -161,6 +162,7 @@
       children: <RenderSliver>[
         inner = childManager.createRenderObject(),
       ],
+      cacheExtent: 0.0,
     );
     layout(root);
 
diff --git a/packages/flutter/test/rendering/slivers_helpers_test.dart b/packages/flutter/test/rendering/slivers_helpers_test.dart
index 230e24c..6efe4f1 100644
--- a/packages/flutter/test/rendering/slivers_helpers_test.dart
+++ b/packages/flutter/test/rendering/slivers_helpers_test.dart
@@ -27,6 +27,8 @@
       crossAxisExtent: 0.0,
       crossAxisDirection: AxisDirection.right,
       viewportMainAxisExtent: 0.0,
+      cacheOrigin: 0.0,
+      remainingCacheExtent: 0.0,
     );
     final SliverConstraints b = a.copyWith();
     expect(a, equals(b));
@@ -55,6 +57,8 @@
       crossAxisExtent: 40.0,
       crossAxisDirection: AxisDirection.right,
       viewportMainAxisExtent: 30.0,
+      cacheOrigin: 0.0,
+      remainingCacheExtent: 0.0,
     );
     expect(c, equals(d));
     expect(c.normalizedGrowthDirection, equals(GrowthDirection.forward));
diff --git a/packages/flutter/test/rendering/slivers_test.dart b/packages/flutter/test/rendering/slivers_test.dart
index 550ef31..7fa4f0e 100644
--- a/packages/flutter/test/rendering/slivers_test.dart
+++ b/packages/flutter/test/rendering/slivers_test.dart
@@ -86,9 +86,10 @@
         ' │ │   GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
         ' │ │   0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
         ' │ │   crossAxisDirection: AxisDirection.right,\n'
-        ' │ │   viewportMainAxisExtent: 600.0)\n'
+        ' │ │   viewportMainAxisExtent: 600.0, remainingCacheExtent: 850.0\n'
+        ' │ │   cacheOrigin: 0.0 )\n'
         ' │ │ geometry: SliverGeometry(scrollExtent: 400.0, paintExtent: 400.0,\n'
-        ' │ │   maxPaintExtent: 400.0)\n'
+        ' │ │   maxPaintExtent: 400.0, cacheExtent: 400.0)\n'
         ' │ │\n'
         ' │ └─child: RenderSizedBox#00000 NEEDS-PAINT\n'
         ' │     parentData: paintOffset=Offset(0.0, -0.0) (can use size)\n'
@@ -101,9 +102,11 @@
         ' │ │   GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
         ' │ │   0.0, remainingPaintExtent: 200.0, crossAxisExtent: 800.0,\n'
         ' │ │   crossAxisDirection: AxisDirection.right,\n'
-        ' │ │   viewportMainAxisExtent: 600.0)\n'
+        ' │ │   viewportMainAxisExtent: 600.0, remainingCacheExtent: 450.0\n'
+        ' │ │   cacheOrigin: 0.0 )\n'
         ' │ │ geometry: SliverGeometry(scrollExtent: 400.0, paintExtent: 200.0,\n'
-        ' │ │   maxPaintExtent: 400.0, hasVisualOverflow: true)\n'
+        ' │ │   maxPaintExtent: 400.0, hasVisualOverflow: true, cacheExtent:\n'
+        ' │ │   400.0)\n'
         ' │ │\n'
         ' │ └─child: RenderSizedBox#00000 NEEDS-PAINT\n'
         ' │     parentData: paintOffset=Offset(0.0, -0.0) (can use size)\n'
@@ -111,14 +114,16 @@
         ' │     size: Size(800.0, 400.0)\n'
         ' │\n'
         ' ├─child 2: RenderSliverToBoxAdapter#00000 relayoutBoundary=up1 NEEDS-PAINT\n'
-        ' │ │ parentData: paintOffset=Offset(0.0, 600.0) (can use size)\n'
+        ' │ │ parentData: paintOffset=Offset(0.0, 800.0) (can use size)\n'
         ' │ │ constraints: SliverConstraints(AxisDirection.down,\n'
         ' │ │   GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
         ' │ │   0.0, remainingPaintExtent: 0.0, crossAxisExtent: 800.0,\n'
         ' │ │   crossAxisDirection: AxisDirection.right,\n'
-        ' │ │   viewportMainAxisExtent: 600.0)\n'
+        ' │ │   viewportMainAxisExtent: 600.0, remainingCacheExtent: 50.0\n'
+        ' │ │   cacheOrigin: 0.0 )\n'
         ' │ │ geometry: SliverGeometry(scrollExtent: 400.0, hidden,\n'
-        ' │ │   maxPaintExtent: 400.0, hasVisualOverflow: true)\n'
+        ' │ │   maxPaintExtent: 400.0, hasVisualOverflow: true, cacheExtent:\n'
+        ' │ │   50.0)\n'
         ' │ │\n'
         ' │ └─child: RenderSizedBox#00000 NEEDS-PAINT\n'
         ' │     parentData: paintOffset=Offset(0.0, -0.0) (can use size)\n'
@@ -126,12 +131,13 @@
         ' │     size: Size(800.0, 400.0)\n'
         ' │\n'
         ' ├─child 3: RenderSliverToBoxAdapter#00000 relayoutBoundary=up1 NEEDS-PAINT\n'
-        ' │ │ parentData: paintOffset=Offset(0.0, 600.0) (can use size)\n'
+        ' │ │ parentData: paintOffset=Offset(0.0, 1200.0) (can use size)\n'
         ' │ │ constraints: SliverConstraints(AxisDirection.down,\n'
         ' │ │   GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
         ' │ │   0.0, remainingPaintExtent: 0.0, crossAxisExtent: 800.0,\n'
         ' │ │   crossAxisDirection: AxisDirection.right,\n'
-        ' │ │   viewportMainAxisExtent: 600.0)\n'
+        ' │ │   viewportMainAxisExtent: 600.0, remainingCacheExtent: 0.0\n'
+        ' │ │   cacheOrigin: 0.0 )\n'
         ' │ │ geometry: SliverGeometry(scrollExtent: 400.0, hidden,\n'
         ' │ │   maxPaintExtent: 400.0, hasVisualOverflow: true)\n'
         ' │ │\n'
@@ -141,12 +147,13 @@
         ' │     size: Size(800.0, 400.0)\n'
         ' │\n'
         ' └─child 4: RenderSliverToBoxAdapter#00000 relayoutBoundary=up1 NEEDS-PAINT\n'
-        '   │ parentData: paintOffset=Offset(0.0, 600.0) (can use size)\n'
+        '   │ parentData: paintOffset=Offset(0.0, 1600.0) (can use size)\n'
         '   │ constraints: SliverConstraints(AxisDirection.down,\n'
         '   │   GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
         '   │   0.0, remainingPaintExtent: 0.0, crossAxisExtent: 800.0,\n'
         '   │   crossAxisDirection: AxisDirection.right,\n'
-        '   │   viewportMainAxisExtent: 600.0)\n'
+        '   │   viewportMainAxisExtent: 600.0, remainingCacheExtent: 0.0\n'
+        '   │   cacheOrigin: 0.0 )\n'
         '   │ geometry: SliverGeometry(scrollExtent: 400.0, hidden,\n'
         '   │   maxPaintExtent: 400.0, hasVisualOverflow: true)\n'
         '   │\n'
@@ -158,23 +165,23 @@
     );
     expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0));
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0));
-    expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
-    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
+    expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 800.0));
+    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 1200.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 1600.0));
 
     expect(a.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 400.0));
     expect(b.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 800.0));
-    expect(c.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 1000.0));
-    expect(d.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 1000.0));
-    expect(e.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 1000.0));
+    expect(c.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 1200.0));
+    expect(d.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 1600.0));
+    expect(e.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 2000.0));
 
     root.offset = new ViewportOffset.fixed(200.0);
     pumpFrame();
     expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0));
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
-    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
+    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 1000.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 1400.0));
 
     root.offset = new ViewportOffset.fixed(600.0);
     pumpFrame();
@@ -182,7 +189,7 @@
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0));
     expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 1000.0));
 
     root.offset = new ViewportOffset.fixed(900.0);
     pumpFrame();
@@ -190,7 +197,7 @@
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -500.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -100.0));
     expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 700.0));
 
     final HitTestResult result = new HitTestResult();
     root.hitTest(result, position: const Offset(130.0, 150.0));
@@ -218,17 +225,17 @@
 
     expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0));
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0));
-    expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
-    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
+    expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -600.0));
+    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -1000.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -1400.0));
 
     root.offset = new ViewportOffset.fixed(200.0);
     pumpFrame();
     expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0));
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
-    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
+    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -800.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -1200.0));
 
     root.offset = new ViewportOffset.fixed(600.0);
     pumpFrame();
@@ -236,7 +243,7 @@
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0));
     expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -800.0));
 
     root.offset = new ViewportOffset.fixed(900.0);
     pumpFrame();
@@ -244,7 +251,7 @@
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 700.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0));
     expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -100.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -500.0));
 
     final HitTestResult result = new HitTestResult();
     root.hitTest(result, position: const Offset(150.0, 350.0));
@@ -284,28 +291,28 @@
     expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0));
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(400.0, 0.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0));
-    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0));
+    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(1200.0, 0.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(1600.0, 0.0));
 
     expect(_getPaintOrigin(sliverA), const Offset(0.0, 0.0));
     expect(_getPaintOrigin(sliverB), const Offset(400.0, 0.0));
     expect(_getPaintOrigin(sliverC), const Offset(800.0, 0.0));
-    expect(_getPaintOrigin(sliverD), const Offset(800.0, 0.0));
-    expect(_getPaintOrigin(sliverE), const Offset(800.0, 0.0));
+    expect(_getPaintOrigin(sliverD), const Offset(1200.0, 0.0));
+    expect(_getPaintOrigin(sliverE), const Offset(1600.0, 0.0));
 
     root.offset = new ViewportOffset.fixed(200.0);
     pumpFrame();
     expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0));
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0));
-    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0));
+    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(1000.0, 0.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(1400.0, 0.0));
 
     expect(_getPaintOrigin(sliverA), const Offset(000.0, 0.0));
     expect(_getPaintOrigin(sliverB), const Offset(200.0, 0.0));
     expect(_getPaintOrigin(sliverC), const Offset(600.0, 0.0));
-    expect(_getPaintOrigin(sliverD), const Offset(800.0, 0.0));
-    expect(_getPaintOrigin(sliverE), const Offset(800.0, 0.0));
+    expect(_getPaintOrigin(sliverD), const Offset(1000.0, 0.0));
+    expect(_getPaintOrigin(sliverE), const Offset(1400.0, 0.0));
 
     root.offset = new ViewportOffset.fixed(600.0);
     pumpFrame();
@@ -313,13 +320,13 @@
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0));
     expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(1000.0, 0.0));
 
     expect(_getPaintOrigin(sliverA), const Offset(000.0, 0.0));
     expect(_getPaintOrigin(sliverB), const Offset(000.0, 0.0));
     expect(_getPaintOrigin(sliverC), const Offset(200.0, 0.0));
     expect(_getPaintOrigin(sliverD), const Offset(600.0, 0.0));
-    expect(_getPaintOrigin(sliverE), const Offset(800.0, 0.0));
+    expect(_getPaintOrigin(sliverE), const Offset(1000.0, 0.0));
 
     root.offset = new ViewportOffset.fixed(900.0);
     pumpFrame();
@@ -362,16 +369,16 @@
     expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(400.0, 0.0));
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0));
-    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0));
+    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-800.0, 0.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-1200.0, 0.0));
 
     root.offset = new ViewportOffset.fixed(200.0);
     pumpFrame();
     expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0));
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0));
-    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0));
+    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-600.0, 0.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-1000.0, 0.0));
 
     root.offset = new ViewportOffset.fixed(600.0);
     pumpFrame();
@@ -379,7 +386,7 @@
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0));
     expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-600.0, 0.0));
 
     root.offset = new ViewportOffset.fixed(900.0);
     pumpFrame();
@@ -429,23 +436,23 @@
 
     expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0));
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0));
-    expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
-    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
+    expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 800.0));
+    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 1200.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 1600.0));
 
     expect(a.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 400.0));
     expect(b.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 800.0));
-    expect(c.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 1000.0));
-    expect(d.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 1000.0));
-    expect(e.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 1000.0));
+    expect(c.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 1200.0));
+    expect(d.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 1600.0));
+    expect(e.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 2000.0));
 
     root.offset = new ViewportOffset.fixed(200.0);
     pumpFrame();
     expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0));
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
-    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
+    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 1000.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 1400.0));
 
     root.offset = new ViewportOffset.fixed(600.0);
     pumpFrame();
@@ -453,7 +460,7 @@
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0));
     expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 1000.0));
 
     root.offset = new ViewportOffset.fixed(900.0);
     pumpFrame();
@@ -461,7 +468,7 @@
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -500.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -100.0));
     expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 700.0));
 
     final HitTestResult result = new HitTestResult();
     root.hitTest(result, position: const Offset(130.0, 150.0));
@@ -489,17 +496,17 @@
 
     expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0));
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0));
-    expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
-    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
+    expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -600.0));
+    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -1000.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -1400.0));
 
     root.offset = new ViewportOffset.fixed(200.0);
     pumpFrame();
     expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0));
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
-    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
+    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -800.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -1200.0));
 
     root.offset = new ViewportOffset.fixed(600.0);
     pumpFrame();
@@ -507,7 +514,7 @@
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0));
     expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -800.0));
 
     root.offset = new ViewportOffset.fixed(900.0);
     pumpFrame();
@@ -515,7 +522,7 @@
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 700.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0));
     expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -100.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -500.0));
 
     final HitTestResult result = new HitTestResult();
     root.hitTest(result, position: const Offset(150.0, 350.0));
@@ -544,16 +551,16 @@
     expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0));
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(400.0, 0.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0));
-    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0));
+    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(1200.0, 0.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(1600.0, 0.0));
 
     root.offset = new ViewportOffset.fixed(200.0);
     pumpFrame();
     expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0));
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0));
-    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0));
+    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(1000.0, 0.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(1400.0, 0.0));
 
     root.offset = new ViewportOffset.fixed(600.0);
     pumpFrame();
@@ -561,7 +568,7 @@
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0));
     expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(1000.0, 0.0));
 
     root.offset = new ViewportOffset.fixed(900.0);
     pumpFrame();
@@ -598,16 +605,16 @@
     expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(400.0, 0.0));
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0));
-    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0));
+    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-800.0, 0.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-1200.0, 0.0));
 
     root.offset = new ViewportOffset.fixed(200.0);
     pumpFrame();
     expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0));
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0));
-    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0));
+    expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-600.0, 0.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-1000.0, 0.0));
 
     root.offset = new ViewportOffset.fixed(600.0);
     pumpFrame();
@@ -615,7 +622,7 @@
     expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0));
     expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0));
     expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0));
-    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0));
+    expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-600.0, 0.0));
 
     root.offset = new ViewportOffset.fixed(900.0);
     pumpFrame();
@@ -684,7 +691,7 @@
         visible: true,
       ).toString(),
       equals(
-        'SliverGeometry(scrollExtent: 100.0, paintExtent: 50.0, layoutExtent: 20.0, maxPaintExtent: 0.0)',
+        'SliverGeometry(scrollExtent: 100.0, paintExtent: 50.0, layoutExtent: 20.0, maxPaintExtent: 0.0, cacheExtent: 20.0)',
       ),
     );
     expect(
@@ -694,7 +701,7 @@
         layoutExtent: 20.0,
       ).toString(),
       equals(
-        'SliverGeometry(scrollExtent: 100.0, hidden, layoutExtent: 20.0, maxPaintExtent: 0.0)',
+        'SliverGeometry(scrollExtent: 100.0, hidden, layoutExtent: 20.0, maxPaintExtent: 0.0, cacheExtent: 20.0)',
       ),
     );
   });
diff --git a/packages/flutter/test/widgets/animated_list_test.dart b/packages/flutter/test/widgets/animated_list_test.dart
index 08bde08..ffd43ed 100644
--- a/packages/flutter/test/widgets/animated_list_test.dart
+++ b/packages/flutter/test/widgets/animated_list_test.dart
@@ -60,9 +60,9 @@
       ),
     );
 
-    double itemHeight(int index) => tester.getSize(find.byKey(new ValueKey<int>(index))).height;
-    double itemTop(int index) => tester.getTopLeft(find.byKey(new ValueKey<int>(index))).dy;
-    double itemBottom(int index) => tester.getBottomLeft(find.byKey(new ValueKey<int>(index))).dy;
+    double itemHeight(int index) => tester.getSize(find.byKey(new ValueKey<int>(index), skipOffstage: false)).height;
+    double itemTop(int index) => tester.getTopLeft(find.byKey(new ValueKey<int>(index), skipOffstage: false)).dy;
+    double itemBottom(int index) => tester.getBottomLeft(find.byKey(new ValueKey<int>(index), skipOffstage: false)).dy;
 
     listKey.currentState.insertItem(0, duration: const Duration(milliseconds: 100));
     await tester.pump();
diff --git a/packages/flutter/test/widgets/automatic_keep_alive_test.dart b/packages/flutter/test/widgets/automatic_keep_alive_test.dart
index 9fb9b04..1d46c12 100644
--- a/packages/flutter/test/widgets/automatic_keep_alive_test.dart
+++ b/packages/flutter/test/widgets/automatic_keep_alive_test.dart
@@ -71,41 +71,43 @@
           addAutomaticKeepAlives: impliedMode,
           addRepaintBoundaries: impliedMode,
           itemExtent: 12.3, // about 50 widgets visible
+          cacheExtent: 0.0,
           children: generateList(const Placeholder(), impliedMode: impliedMode),
         ),
       ),
     );
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
     await tester.drag(find.byType(ListView), const Offset(0.0, -300.0)); // about 25 widgets' worth
     await tester.pump();
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
     const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(true);
     await tester.drag(find.byType(ListView), const Offset(0.0, 300.0)); // back to top
     await tester.pump();
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
     const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(false);
     await tester.pump();
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
   });
 
   testWidgets('AutomaticKeepAlive with ListView without itemExtent', (WidgetTester tester) async {
@@ -115,6 +117,7 @@
         child: new ListView(
           addAutomaticKeepAlives: impliedMode,
           addRepaintBoundaries: impliedMode,
+          cacheExtent: 0.0,
           children: generateList(
             new Container(height: 12.3, child: const Placeholder()), // about 50 widgets visible
             impliedMode: impliedMode,
@@ -124,35 +127,36 @@
     );
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
     await tester.drag(find.byType(ListView), const Offset(0.0, -300.0)); // about 25 widgets' worth
     await tester.pump();
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
     const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(true);
     await tester.drag(find.byType(ListView), const Offset(0.0, 300.0)); // back to top
     await tester.pump();
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
     const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(false);
     await tester.pump();
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
   });
 
   testWidgets('AutomaticKeepAlive with GridView', (WidgetTester tester) async {
@@ -164,6 +168,7 @@
           addRepaintBoundaries: impliedMode,
           crossAxisCount: 2,
           childAspectRatio: 400.0 / 24.6, // about 50 widgets visible
+          cacheExtent: 0.0,
           children: generateList(
             new Container(child: const Placeholder()),
             impliedMode: impliedMode,
@@ -173,35 +178,36 @@
     );
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
     await tester.drag(find.byType(GridView), const Offset(0.0, -300.0)); // about 25 widgets' worth
     await tester.pump();
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
     const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(true);
     await tester.drag(find.byType(GridView), const Offset(0.0, 300.0)); // back to top
     await tester.pump();
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
     const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(false);
     await tester.pump();
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
   });
 }
 
@@ -216,6 +222,7 @@
         child: new ListView(
           addAutomaticKeepAlives: false,
           addRepaintBoundaries: false,
+          cacheExtent: 0.0,
           children: <Widget>[
             new AutomaticKeepAlive(
               child: new Container(
@@ -245,11 +252,11 @@
     expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
     await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); // move to bottom
     await tester.pump();
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsNothing);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     await tester.drag(find.byType(ListView), const Offset(0.0, 1000.0)); // move to top
@@ -257,41 +264,48 @@
     expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
     const GlobalObjectKey<_LeafState>(0).currentState.setKeepAlive(true);
     await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); // move to bottom
     await tester.pump();
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
-    const GlobalObjectKey<_LeafState>(1).currentState.setKeepAlive(true);
-    await tester.pump();
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
-    const GlobalObjectKey<_LeafState>(0).currentState.setKeepAlive(false);
-    await tester.pump();
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
-    const GlobalObjectKey<_LeafState>(1).currentState.setKeepAlive(false);
-    await tester.pump();
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsNothing);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
+    const GlobalObjectKey<_LeafState>(1).currentState.setKeepAlive(true);
+    await tester.pump();
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
+    const GlobalObjectKey<_LeafState>(0).currentState.setKeepAlive(false);
+    await tester.pump();
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
+    const GlobalObjectKey<_LeafState>(1).currentState.setKeepAlive(false);
+    await tester.pump();
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
   });
 
-  testWidgets('AutomaticKeepAlive double', (WidgetTester tester) async {
+  testWidgets('AutomaticKeepAlive double 2', (WidgetTester tester) async {
     await tester.pumpWidget(
       new Directionality(
         textDirection: TextDirection.ltr,
         child: new ListView(
           addAutomaticKeepAlives: false,
           addRepaintBoundaries: false,
+          cacheExtent: 0.0,
           children: <Widget>[
             new AutomaticKeepAlive(
               child: new Container(
@@ -328,13 +342,15 @@
     expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(5)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(4), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(5), skipOffstage: false), findsNothing);
     const GlobalObjectKey<_LeafState>(0).currentState.setKeepAlive(true);
     await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); // move to bottom
     await tester.pump();
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsOneWidget);
@@ -344,6 +360,7 @@
       child: new ListView(
         addAutomaticKeepAlives: false,
         addRepaintBoundaries: false,
+        cacheExtent: 0.0,
         children: <Widget>[
           new AutomaticKeepAlive(
             child: new Container(
@@ -376,7 +393,7 @@
       ),
     ));
     await tester.pump(); // Sometimes AutomaticKeepAlive needs an extra pump to clean things up.
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsNothing);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsOneWidget);
@@ -387,22 +404,26 @@
     expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(5)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(4), skipOffstage: false), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(5), skipOffstage: false), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(5)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing);
     const GlobalObjectKey<_LeafState>(0).currentState.setKeepAlive(false);
     await tester.pump();
     expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(5)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(4), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(5), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsNothing);
     await tester.pumpWidget(new Directionality(
       textDirection: TextDirection.ltr,
       child: new ListView(
         addAutomaticKeepAlives: false,
         addRepaintBoundaries: false,
+        cacheExtent: 0.0,
         children: <Widget>[
           new AutomaticKeepAlive(
             child: new Container(
@@ -437,10 +458,10 @@
     await tester.pump(); // Sometimes AutomaticKeepAlive needs an extra pump to clean things up.
     expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(5)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(4), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(5), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsNothing);
   });
 
   testWidgets('AutomaticKeepAlive with keepAlive set to true before initState', (WidgetTester tester) async {
@@ -469,9 +490,9 @@
     expect(find.byKey(const GlobalObjectKey<_AlwaysKeepAliveState>(0)), findsOneWidget);
     await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); // move to bottom
     await tester.pump();
-    expect(find.byKey(const GlobalObjectKey<_AlwaysKeepAliveState>(0)), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_AlwaysKeepAliveState>(0), skipOffstage: false), findsOneWidget);
 
-    expect(find.text('keep me alive'), findsOneWidget);
+    expect(find.text('keep me alive', skipOffstage: false), findsOneWidget);
     expect(find.text('FooBar 1'), findsNothing);
     expect(find.text('FooBar 2'), findsNothing);
   });
diff --git a/packages/flutter/test/widgets/grid_view_test.dart b/packages/flutter/test/widgets/grid_view_test.dart
index c40e083..6e8641a 100644
--- a/packages/flutter/test/widgets/grid_view_test.dart
+++ b/packages/flutter/test/widgets/grid_view_test.dart
@@ -168,9 +168,17 @@
       0, 1, 2, // col 0
       3, 4, 5, // col 1
       6, 7, 8, // col 2
+      9, 10, 11, // col 3 (in cached area)
     ]));
     log.clear();
 
+    for (int i = 0; i < 9; i++) {
+      expect(find.text('$i'), findsOneWidget);
+    }
+    for (int i = 9; i < 80; i++) {
+      expect(find.text('$i'), findsNothing);
+    }
+
     final ScrollableState state = tester.state(find.byType(Scrollable));
     final ScrollPosition position = state.position;
     position.jumpTo(3025.0);
@@ -179,25 +187,49 @@
     await tester.pump();
 
     expect(log, equals(<int>[
+      30, 31, 32, // col 10 (in cached area)
       33, 34, 35, // col 11
       36, 37, 38, // col 12
       39, 40, 41, // col 13
       42, 43, 44, // col 14
+      45, 46, 47, // col 15 (in cached area)
     ]));
     log.clear();
 
+    for (int i = 0; i < 33; i++) {
+      expect(find.text('$i'), findsNothing);
+    }
+    for (int i = 33; i < 45; i++) {
+      expect(find.text('$i'), findsOneWidget);
+    }
+    for (int i = 45; i < 80; i++) {
+      expect(find.text('$i'), findsNothing);
+    }
+
     position.jumpTo(975.0);
 
     expect(log, isEmpty);
     await tester.pump();
 
     expect(log, equals(<int>[
+      6, 7, 8, // col2 (in cached area)
       9, 10, 11, // col 3
       12, 13, 14, // col 4
       15, 16, 17, // col 5
       18, 19, 20, // col 6
+      21, 22, 23, // col 7 (in cached area)
     ]));
     log.clear();
+
+    for (int i = 0; i < 9; i++) {
+      expect(find.text('$i'), findsNothing);
+    }
+    for (int i = 9; i < 21; i++) {
+      expect(find.text('$i'), findsOneWidget);
+    }
+    for (int i = 21; i < 80; i++) {
+      expect(find.text('$i'), findsNothing);
+    }
   });
 
   testWidgets('GridView - change crossAxisCount', (WidgetTester tester) async {
@@ -230,7 +262,15 @@
       0, 1, 2, 3, // row 0
       4, 5, 6, 7, // row 1
       8, 9, 10, 11, // row 2
+      12, 13, 14, 15, // row 3 (in cached area)
+      16, 17, 18, 19, // row 4 (in cached area)
     ]));
+    for (int i = 0; i < 12; i++) {
+      expect(find.text('$i'), findsOneWidget);
+    }
+    for (int i = 12; i < 40; i++) {
+      expect(find.text('$i'), findsNothing);
+    }
     log.clear();
 
     await tester.pumpWidget(
@@ -258,6 +298,8 @@
       0, 1, 2, 3, // row 0
       4, 5, 6, 7, // row 1
       8, 9, 10, 11, // row 2
+      12, 13, 14, 15, // row 3 (in cached area)
+      16, 17, 18, 19, // row 4 (in cached area)
     ]));
     log.clear();
 
@@ -295,7 +337,15 @@
       0, 1, 2, 3, // row 0
       4, 5, 6, 7, // row 1
       8, 9, 10, 11, // row 2
+      12, 13, 14, 15, // row 3 (in cached area)
+      16, 17, 18, 19, // row 4 (in cached area)
     ]));
+    for (int i = 0; i < 12; i++) {
+      expect(find.text('$i'), findsOneWidget);
+    }
+    for (int i = 12; i < 40; i++) {
+      expect(find.text('$i'), findsNothing);
+    }
     log.clear();
 
     await tester.pumpWidget(
@@ -323,6 +373,8 @@
       0, 1, 2, 3, // row 0
       4, 5, 6, 7, // row 1
       8, 9, 10, 11, // row 2
+      12, 13, 14, 15, // row 3 (in cached area)
+      16, 17, 18, 19, // row 4 (in cached area)
     ]));
     log.clear();
 
@@ -346,6 +398,7 @@
           child: new SizedBox(
             height: 200.0,
             child: new GridView.count(
+              cacheExtent: 0.0,
               crossAxisCount: 2,
               children: <Widget>[ container, container, container, container ],
             ),
@@ -379,7 +432,7 @@
       ),
     );
 
-    expect(find.text('0'), findsOneWidget);
+    expect(find.text('0'), findsNothing);
     expect(find.text('1'), findsNothing);
   });
 
diff --git a/packages/flutter/test/widgets/heroes_test.dart b/packages/flutter/test/widgets/heroes_test.dart
index 85a2afc..7ed838d 100644
--- a/packages/flutter/test/widgets/heroes_test.dart
+++ b/packages/flutter/test/widgets/heroes_test.dart
@@ -781,6 +781,7 @@
       builder: (BuildContext context) {
         return new Material(
           child: new ListView(
+            cacheExtent: 0.0,
             children: <Widget>[
               const SizedBox(height: 100.0),
               // This container will appear at Y=100
diff --git a/packages/flutter/test/widgets/keep_alive_test.dart b/packages/flutter/test/widgets/keep_alive_test.dart
index fb4d8a1..566fa0e 100644
--- a/packages/flutter/test/widgets/keep_alive_test.dart
+++ b/packages/flutter/test/widgets/keep_alive_test.dart
@@ -47,6 +47,7 @@
       new Directionality(
         textDirection: TextDirection.ltr,
         child: new ListView(
+          cacheExtent: 0.0,
           addAutomaticKeepAlives: false,
           addRepaintBoundaries: false,
           itemExtent: 12.3, // about 50 widgets visible
@@ -56,35 +57,36 @@
     );
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
     await tester.drag(find.byType(ListView), const Offset(0.0, -300.0)); // about 25 widgets' worth
     await tester.pump();
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
     const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(true);
     await tester.drag(find.byType(ListView), const Offset(0.0, 300.0)); // back to top
     await tester.pump();
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
     const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(false);
     await tester.pump();
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
   });
 
   testWidgets('KeepAlive with ListView without itemExtent', (WidgetTester tester) async {
@@ -92,6 +94,7 @@
       new Directionality(
         textDirection: TextDirection.ltr,
         child: new ListView(
+          cacheExtent: 0.0,
           addAutomaticKeepAlives: false,
           addRepaintBoundaries: false,
           children: generateList(new Container(height: 12.3, child: const Placeholder())), // about 50 widgets visible
@@ -100,35 +103,36 @@
     );
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
     await tester.drag(find.byType(ListView), const Offset(0.0, -300.0)); // about 25 widgets' worth
     await tester.pump();
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
     const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(true);
     await tester.drag(find.byType(ListView), const Offset(0.0, 300.0)); // back to top
     await tester.pump();
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
     const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(false);
     await tester.pump();
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
   });
 
   testWidgets('KeepAlive with GridView', (WidgetTester tester) async {
@@ -136,6 +140,7 @@
       new Directionality(
         textDirection: TextDirection.ltr,
         child: new GridView.count(
+          cacheExtent: 0.0,
           addAutomaticKeepAlives: false,
           addRepaintBoundaries: false,
           crossAxisCount: 2,
@@ -146,35 +151,36 @@
     );
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
     await tester.drag(find.byType(GridView), const Offset(0.0, -300.0)); // about 25 widgets' worth
     await tester.pump();
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
     const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(true);
     await tester.drag(find.byType(GridView), const Offset(0.0, 300.0)); // back to top
     await tester.pump();
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsOneWidget);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
     const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(false);
     await tester.pump();
     expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
     expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing);
-    expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
+    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
   });
 
   testWidgets('KeepAlive render tree description', (WidgetTester tester) async {
@@ -270,10 +276,12 @@
       '                     │   GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
       '                     │   0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
       '                     │   crossAxisDirection: AxisDirection.right,\n'
-      '                     │   viewportMainAxisExtent: 600.0)\n'
+      '                     │   viewportMainAxisExtent: 600.0, remainingCacheExtent: 850.0\n'
+      '                     │   cacheOrigin: 0.0 )\n'
       '                     │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n'
-      '                     │   600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true)\n'
-      '                     │ currently live children: 0 to 1\n'
+      '                     │   600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true,\n'
+      '                     │   cacheExtent: 850.0)\n'
+      '                     │ currently live children: 0 to 2\n'
       '                     │\n'
       '                     ├─child with index 0: RenderLimitedBox#00000\n'
       '                     │ │ parentData: index=0; layoutOffset=0.0\n'
@@ -287,8 +295,20 @@
       '                     │     constraints: BoxConstraints(w=800.0, h=400.0)\n'
       '                     │     size: Size(800.0, 400.0)\n'
       '                     │\n'
-      '                     └─child with index 1: RenderLimitedBox#00000\n'                                       // <----- no dashed line starts here
-      '                       │ parentData: index=1; layoutOffset=400.0\n'
+      '                     ├─child with index 1: RenderLimitedBox#00000\n'                                       // <----- no dashed line starts here
+      '                     │ │ parentData: index=1; layoutOffset=400.0\n'
+      '                     │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
+      '                     │ │ size: Size(800.0, 400.0)\n'
+      '                     │ │ maxWidth: 400.0\n'
+      '                     │ │ maxHeight: 400.0\n'
+      '                     │ │\n'
+      '                     │ └─child: RenderCustomPaint#00000\n'
+      '                     │     parentData: <none> (can use size)\n'
+      '                     │     constraints: BoxConstraints(w=800.0, h=400.0)\n'
+      '                     │     size: Size(800.0, 400.0)\n'
+      '                     │\n'
+      '                     └─child with index 2: RenderLimitedBox#00000\n'
+      '                       │ parentData: index=2; layoutOffset=800.0\n'
       '                       │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
       '                       │ size: Size(800.0, 400.0)\n'
       '                       │ maxWidth: 400.0\n'
@@ -385,10 +405,24 @@
       '                     │   GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
       '                     │   2000.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
       '                     │   crossAxisDirection: AxisDirection.right,\n'
-      '                     │   viewportMainAxisExtent: 600.0)\n'
+      '                     │   viewportMainAxisExtent: 600.0, remainingCacheExtent: 1100.0\n'
+      '                     │   cacheOrigin: -250.0 )\n'
       '                     │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n'
-      '                     │   600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true)\n'
-      '                     │ currently live children: 5 to 6\n'
+      '                     │   600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true,\n'
+      '                     │   cacheExtent: 1100.0)\n'
+      '                     │ currently live children: 4 to 7\n'
+      '                     │\n'
+      '                     ├─child with index 4: RenderLimitedBox#00000\n'
+      '                     │ │ parentData: index=4; layoutOffset=1600.0\n'
+      '                     │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
+      '                     │ │ size: Size(800.0, 400.0)\n'
+      '                     │ │ maxWidth: 400.0\n'
+      '                     │ │ maxHeight: 400.0\n'
+      '                     │ │\n'
+      '                     │ └─child: RenderCustomPaint#00000\n'
+      '                     │     parentData: <none> (can use size)\n'
+      '                     │     constraints: BoxConstraints(w=800.0, h=400.0)\n'
+      '                     │     size: Size(800.0, 400.0)\n'
       '                     │\n'
       '                     ├─child with index 5: RenderLimitedBox#00000\n'                                       // <----- this is index 5, not 0
       '                     │ │ parentData: index=5; layoutOffset=2000.0\n'
@@ -403,7 +437,19 @@
       '                     │     size: Size(800.0, 400.0)\n'
       '                     │\n'
       '                     ├─child with index 6: RenderLimitedBox#00000\n'
-      '                     ╎ │ parentData: index=6; layoutOffset=2400.0\n'
+      '                     │ │ parentData: index=6; layoutOffset=2400.0\n'
+      '                     │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
+      '                     │ │ size: Size(800.0, 400.0)\n'
+      '                     │ │ maxWidth: 400.0\n'
+      '                     │ │ maxHeight: 400.0\n'
+      '                     │ │\n'
+      '                     │ └─child: RenderCustomPaint#00000\n'
+      '                     │     parentData: <none> (can use size)\n'
+      '                     │     constraints: BoxConstraints(w=800.0, h=400.0)\n'
+      '                     │     size: Size(800.0, 400.0)\n'
+      '                     │\n'
+      '                     ├─child with index 7: RenderLimitedBox#00000\n'
+      '                     ╎ │ parentData: index=7; layoutOffset=2800.0\n'
       '                     ╎ │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
       '                     ╎ │ size: Size(800.0, 400.0)\n'
       '                     ╎ │ maxWidth: 400.0\n'
diff --git a/packages/flutter/test/widgets/list_view_builder_test.dart b/packages/flutter/test/widgets/list_view_builder_test.dart
index 26e7e78..384de2a 100644
--- a/packages/flutter/test/widgets/list_view_builder_test.dart
+++ b/packages/flutter/test/widgets/list_view_builder_test.dart
@@ -38,7 +38,11 @@
 
     final FlipWidgetState testWidget = tester.state(find.byType(FlipWidget));
 
-    expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4, 5]));
+    expect(callbackTracker, equals(<int>[
+      0, 1, 2, 3, 4, 5, // visible in viewport
+      6, 7, 8, // in caching area
+    ]));
+    check(visible: <int>[0, 1, 2, 3, 4, 5], hidden: <int>[ 6, 7, 8]);
 
     callbackTracker.clear();
     testWidget.flip();
@@ -50,7 +54,11 @@
     testWidget.flip();
     await tester.pump();
 
-    expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4, 5]));
+    expect(callbackTracker, equals(<int>[
+      0, 1, 2, 3, 4, 5,
+      6, 7, 8, // in caching area
+    ]));
+    check(visible: <int>[0, 1, 2, 3, 4, 5], hidden: <int>[ 6, 7, 8]);
   });
 
   testWidgets('ListView.builder vertical', (WidgetTester tester) async {
@@ -91,7 +99,12 @@
 
     await tester.pumpWidget(buildWidget());
 
-    expect(callbackTracker, equals(<int>[1, 2, 3, 4]));
+    expect(callbackTracker, equals(<int>[
+      0, // in caching area
+      1, 2, 3, 4,
+      5, // in caching area
+    ]));
+    check(visible: <int>[1, 2, 3, 4], hidden: <int>[0, 5]);
     callbackTracker.clear();
 
     jumpTo(400.0);
@@ -99,12 +112,12 @@
 
     await tester.pumpWidget(buildWidget());
 
-    expect(callbackTracker, equals(<int>[1, 2, 3, 4]));
-    callbackTracker.clear();
-
-    await tester.pumpWidget(buildWidget());
-
-    expect(callbackTracker, equals(<int>[2, 3, 4]));
+    expect(callbackTracker, equals(<int>[
+      0, 1, // in caching area
+      2, 3, 4,
+      5, 6, // in caching area
+    ]));
+    check(visible: <int>[2, 3, 4], hidden: <int>[0, 1, 5, 6]);
     callbackTracker.clear();
 
     jumpTo(500.0);
@@ -112,7 +125,12 @@
 
     await tester.pumpWidget(buildWidget());
 
-    expect(callbackTracker, equals(<int>[2, 3, 4, 5]));
+    expect(callbackTracker, equals(<int>[
+      0, 1, // in caching area
+      2, 3, 4, 5,
+      6, // in caching area
+    ]));
+    check(visible: <int>[2, 3, 4, 5], hidden: <int>[0, 1, 6]);
     callbackTracker.clear();
   });
 
@@ -155,8 +173,12 @@
 
     await tester.pumpWidget(buildWidget());
 
-    expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5]));
-
+    expect(callbackTracker, equals(<int>[
+      0, // in caching area
+      1, 2, 3, 4, 5,
+      6, // in caching area
+    ]));
+    check(visible: <int>[1, 2, 3, 4, 5], hidden: <int>[0, 6]);
     callbackTracker.clear();
 
     jumpTo(400.0);
@@ -164,12 +186,12 @@
 
     await tester.pumpWidget(buildWidget());
 
-    expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5]));
-    callbackTracker.clear();
-
-    await tester.pumpWidget(buildWidget());
-
-    expect(callbackTracker, equals(<int>[2, 3, 4, 5]));
+    expect(callbackTracker, equals(<int>[
+      0, 1, // in caching area
+      2, 3, 4, 5,
+      6, 7, // in caching area
+    ]));
+    check(visible: <int>[2, 3, 4, 5], hidden: <int>[0, 1, 6, 7]);
     callbackTracker.clear();
 
     jumpTo(500.0);
@@ -177,7 +199,12 @@
 
     await tester.pumpWidget(buildWidget());
 
-    expect(callbackTracker, equals(<int>[2, 3, 4, 5, 6]));
+    expect(callbackTracker, equals(<int>[
+      0, 1, // in caching area
+      2, 3, 4, 5, 6,
+      7, // in caching area
+    ]));
+    check(visible: <int>[2, 3, 4, 5, 6], hidden: <int>[0, 1, 7]);
     callbackTracker.clear();
   });
 
@@ -208,26 +235,39 @@
     }
 
     await tester.pumpWidget(testWidget);
-    expect(callbackTracker, equals(<int>[0, 1]));
+    expect(callbackTracker, equals(<int>[0, 1, 2]));
+    check(visible: <int>[0, 1], hidden: <int>[2]);
     callbackTracker.clear();
 
     jumpTo(150.0);
     await tester.pump();
 
-    expect(callbackTracker, equals(<int>[2]));
+    expect(callbackTracker, equals(<int>[3]));
+    check(visible: <int>[0, 1, 2], hidden: <int>[3]);
     callbackTracker.clear();
 
     jumpTo(600.0);
     await tester.pump();
 
-    expect(callbackTracker, equals(<int>[3]));
+    expect(callbackTracker, equals(<int>[4]));
+    check(visible: <int>[2, 3], hidden: <int>[0, 1, 4]);
     callbackTracker.clear();
 
     jumpTo(750.0);
     await tester.pump();
 
-    expect(callbackTracker, equals(<int>[4]));
+    expect(callbackTracker, equals(<int>[5]));
+    check(visible: <int>[2, 3, 4], hidden: <int>[0, 1, 5]);
     callbackTracker.clear();
   });
 
 }
+
+void check({List<int> visible: const <int>[], List<int> hidden: const <int>[]}) {
+  for (int i in visible) {
+    expect(find.text('$i'), findsOneWidget);
+  }
+  for (int i in hidden) {
+    expect(find.text('$i'), findsNothing);
+  }
+}
diff --git a/packages/flutter/test/widgets/list_view_correction_test.dart b/packages/flutter/test/widgets/list_view_correction_test.dart
index 7f9c239..cb5c777 100644
--- a/packages/flutter/test/widgets/list_view_correction_test.dart
+++ b/packages/flutter/test/widgets/list_view_correction_test.dart
@@ -12,6 +12,65 @@
       new Directionality(
         textDirection: TextDirection.ltr,
         child: new ListView(
+          cacheExtent: 0.0,
+          controller: controller,
+          children: <Widget>[
+            new Container(height: 400.0, child: const Text('1')),
+            new Container(height: 400.0, child: const Text('2')),
+            new Container(height: 400.0, child: const Text('3')),
+            new Container(height: 400.0, child: const Text('4')),
+            new Container(height: 400.0, child: const Text('5')),
+            new Container(height: 400.0, child: const Text('6')),
+          ],
+        ),
+      ),
+    );
+
+    controller.jumpTo(1000.0);
+    await tester.pump();
+
+    expect(tester.getTopLeft(find.text('4')).dy, equals(200.0));
+
+    await tester.pumpWidget(
+      new Directionality(
+        textDirection: TextDirection.ltr,
+        child: new ListView(
+          cacheExtent: 0.0,
+          controller: controller,
+          children: <Widget>[
+            new Container(height: 200.0, child: const Text('1')),
+            new Container(height: 400.0, child: const Text('2')),
+            new Container(height: 400.0, child: const Text('3')),
+            new Container(height: 400.0, child: const Text('4')),
+            new Container(height: 400.0, child: const Text('5')),
+            new Container(height: 400.0, child: const Text('6')),
+          ],
+        ),
+      ),
+    );
+
+    expect(controller.offset, equals(1000.0));
+    expect(tester.getTopLeft(find.text('4')).dy, equals(200.0));
+
+    controller.jumpTo(300.0);
+    await tester.pump();
+
+    expect(controller.offset, equals(300.0));
+    expect(tester.getTopLeft(find.text('2')).dy, equals(100.0));
+
+    controller.jumpTo(50.0);
+    await tester.pump();
+
+    expect(controller.offset, equals(0.0));
+    expect(tester.getTopLeft(find.text('2')).dy, equals(200.0));
+  });
+
+  testWidgets('ListView can handle shrinking top elements with cache extent', (WidgetTester tester) async {
+    final ScrollController controller = new ScrollController();
+    await tester.pumpWidget(
+      new Directionality(
+        textDirection: TextDirection.ltr,
+        child: new ListView(
           controller: controller,
           children: <Widget>[
             new Container(height: 400.0, child: const Text('1')),
@@ -53,14 +112,14 @@
     controller.jumpTo(300.0);
     await tester.pump();
 
-    expect(tester.getTopLeft(find.text('2')).dy, equals(100.0));
+    expect(controller.offset, equals(250.0));
+    expect(tester.getTopLeft(find.text('2')).dy, equals(-50.0));
 
     controller.jumpTo(50.0);
-
     await tester.pump();
 
-    expect(controller.offset, equals(0.0));
-    expect(tester.getTopLeft(find.text('2')).dy, equals(200.0));
+    expect(controller.offset, equals(50.0));
+    expect(tester.getTopLeft(find.text('2')).dy, equals(150.0));
   });
 
   testWidgets('ListView can handle inserts at 0', (WidgetTester tester) async {
diff --git a/packages/flutter/test/widgets/list_view_misc_test.dart b/packages/flutter/test/widgets/list_view_misc_test.dart
index c3898d6..d3ed59a 100644
--- a/packages/flutter/test/widgets/list_view_misc_test.dart
+++ b/packages/flutter/test/widgets/list_view_misc_test.dart
@@ -150,7 +150,7 @@
       ),
     );
 
-    final SliverMultiBoxAdaptorElement element = tester.element(find.byType(SliverList));
+    final SliverMultiBoxAdaptorElement element = tester.element(find.byType(SliverList, skipOffstage: false));
 
     final double maxScrollOffset = element.estimateMaxScrollOffset(
       null,
diff --git a/packages/flutter/test/widgets/list_view_test.dart b/packages/flutter/test/widgets/list_view_test.dart
index 3836199..4fbb483 100644
--- a/packages/flutter/test/widgets/list_view_test.dart
+++ b/packages/flutter/test/widgets/list_view_test.dart
@@ -96,7 +96,7 @@
       ),
     );
 
-    expect(log, equals(<int>[0, 1, 2]));
+    expect(log, equals(<int>[0, 1, 2, 3, 4]));
     log.clear();
 
     final ScrollableState state = tester.state(find.byType(Scrollable));
@@ -106,7 +106,7 @@
     expect(log, isEmpty);
     await tester.pump();
 
-    expect(log, equals(<int>[10, 11, 12, 13]));
+    expect(log, equals(<int>[8, 9, 10, 11, 12, 13, 14]));
     log.clear();
 
     position.jumpTo(975.0);
@@ -114,7 +114,7 @@
     expect(log, isEmpty);
     await tester.pump();
 
-    expect(log, equals(<int>[4, 5, 6, 7]));
+    expect(log, equals(<int>[7, 6, 5, 4, 3]));
     log.clear();
   });
 
@@ -196,7 +196,7 @@
         ),
       ),
     );
-    expect(find.text('padded'), findsOneWidget);
+    expect(find.text('padded', skipOffstage: false), findsOneWidget);
   });
 
   testWidgets('ListView with itemExtent in unbounded context', (WidgetTester tester) async {
@@ -240,7 +240,7 @@
       ),
     );
 
-    expect(delegate.log, equals(<String>['didFinishLayout firstIndex=0 lastIndex=5']));
+    expect(delegate.log, equals(<String>['didFinishLayout firstIndex=0 lastIndex=7']));
     delegate.log.clear();
 
     await tester.pumpWidget(
@@ -253,7 +253,7 @@
       ),
     );
 
-    expect(delegate.log, equals(<String>['didFinishLayout firstIndex=0 lastIndex=2']));
+    expect(delegate.log, equals(<String>['didFinishLayout firstIndex=0 lastIndex=4']));
     delegate.log.clear();
 
     await tester.drag(find.byType(ListView), const Offset(0.0, -600.0));
@@ -262,7 +262,7 @@
 
     await tester.pump();
 
-    expect(delegate.log, equals(<String>['didFinishLayout firstIndex=2 lastIndex=5']));
+    expect(delegate.log, equals(<String>['didFinishLayout firstIndex=1 lastIndex=6']));
     delegate.log.clear();
   });
 
diff --git a/packages/flutter/test/widgets/list_view_viewporting_test.dart b/packages/flutter/test/widgets/list_view_viewporting_test.dart
index 518695e..fc8fbf7 100644
--- a/packages/flutter/test/widgets/list_view_viewporting_test.dart
+++ b/packages/flutter/test/widgets/list_view_viewporting_test.dart
@@ -38,7 +38,10 @@
 
     final FlipWidgetState testWidget = tester.state(find.byType(FlipWidget));
 
-    expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4, 5]));
+    expect(callbackTracker, equals(<int>[
+      0, 1, 2, 3, 4, 5, // visible
+      6, 7, 8 // in cached area
+    ]));
 
     callbackTracker.clear();
     testWidget.flip();
@@ -50,7 +53,10 @@
     testWidget.flip();
     await tester.pump();
 
-    expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4, 5]));
+    expect(callbackTracker, equals(<int>[
+      0, 1, 2, 3, 4, 5, // visible
+      6, 7, 8, // in cached area
+    ]));
   });
 
   testWidgets('ListView vertical', (WidgetTester tester) async {
@@ -86,22 +92,34 @@
     await tester.pumpWidget(builder());
 
     // 0 is built to find its height
-    expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4]));
+    expect(callbackTracker, equals(<int>[
+      0, 1, 2, 3, 4,
+      5, // in cached area
+    ]));
     callbackTracker.clear();
 
     final ScrollableState scrollable = tester.state(find.byType(Scrollable));
-    scrollable.position.jumpTo(400.0); // now only 3 should fit, numbered 2-4.
+    scrollable.position.jumpTo(600.0); // now only 3 should fit, numbered 3-5.
 
     await tester.pumpWidget(builder());
 
     // We build the visible children to find their new size.
-    expect(callbackTracker, equals(<int>[1, 2, 3, 4]));
+    expect(callbackTracker, equals(<int>[
+      0, 1, 2,
+      3, 4, 5, //visible
+      6, 7
+    ]));
     callbackTracker.clear();
 
     await tester.pumpWidget(builder());
 
     // 0 isn't built because they're not visible.
-    expect(callbackTracker, equals(<int>[1, 2, 3, 4]));
+    expect(callbackTracker, equals(<int>[
+      1, 2,
+      3, 4, 5, // visible
+      6, 7,
+    ]
+    ));
     callbackTracker.clear();
   });
 
@@ -128,7 +146,7 @@
         child: new FlipWidget(
           left: new ListView.builder(
             scrollDirection: Axis.horizontal,
-            controller: new ScrollController(initialScrollOffset: 300.0),
+            controller: new ScrollController(initialScrollOffset: 500.0),
             itemBuilder: itemBuilder,
           ),
           right: const Text('Not Today'),
@@ -139,23 +157,23 @@
     await tester.pumpWidget(builder());
 
     // 0 is built to find its width
-    expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4, 5]));
+    expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4, 5, 6, 7]));
 
     callbackTracker.clear();
 
     final ScrollableState scrollable = tester.state(find.byType(Scrollable));
-    scrollable.position.jumpTo(400.0); // now only 4 should fit, numbered 2-5.
+    scrollable.position.jumpTo(600.0); // now only 4 should fit, numbered 2-5.
 
     await tester.pumpWidget(builder());
 
     // We build the visible children to find their new size.
-    expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5]));
+    expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5, 6, 7, 8]));
     callbackTracker.clear();
 
     await tester.pumpWidget(builder());
 
     // 0 isn't built because they're not visible.
-    expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5]));
+    expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5, 6, 7, 8]));
     callbackTracker.clear();
   });
 
@@ -189,18 +207,24 @@
 
     await tester.pumpWidget(builder());
 
-    expect(callbackTracker, equals(<int>[0, 1, 2]));
+    expect(callbackTracker, equals(<int>[
+      0, 1, 2,
+      3, // in cached area
+    ]));
     callbackTracker.clear();
     tester.allWidgets.forEach(collectText);
-    expect(text, equals(<String>['0', '1', '2']));
+    expect(text, equals(<String>['0', '1', '2', '3']));
     text.clear();
 
     await tester.pumpWidget(builder());
 
-    expect(callbackTracker, equals(<int>[0, 1, 2]));
+    expect(callbackTracker, equals(<int>[
+      0, 1, 2,
+      3, // in cached area
+    ]));
     callbackTracker.clear();
     tester.allWidgets.forEach(collectText);
-    expect(text, equals(<String>['0', '1', '2']));
+    expect(text, equals(<String>['0', '1', '2', '3']));
     text.clear();
   });
 
@@ -308,9 +332,10 @@
         ' │   GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
         ' │   0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
         ' │   crossAxisDirection: AxisDirection.right,\n'
-        ' │   viewportMainAxisExtent: 600.0)\n'
+        ' │   viewportMainAxisExtent: 600.0, remainingCacheExtent: 850.0\n'
+        ' │   cacheOrigin: 0.0 )\n'
         ' │ geometry: SliverGeometry(scrollExtent: 300.0, paintExtent: 300.0,\n'
-        ' │   maxPaintExtent: 300.0)\n'
+        ' │   maxPaintExtent: 300.0, cacheExtent: 300.0)\n'
         ' │ currently live children: 0 to 2\n'
         ' │\n'
         ' ├─child with index 0: RenderRepaintBoundary#00000 relayoutBoundary=up2\n'
diff --git a/packages/flutter/test/widgets/nested_scroll_view_test.dart b/packages/flutter/test/widgets/nested_scroll_view_test.dart
index b879116..a08e742 100644
--- a/packages/flutter/test/widgets/nested_scroll_view_test.dart
+++ b/packages/flutter/test/widgets/nested_scroll_view_test.dart
@@ -339,7 +339,7 @@
     final Offset point1 = tester.getCenter(find.text('AA'));
     await tester.dragFrom(point1, const Offset(0.0, 200.0));
     await tester.pump(const Duration(milliseconds: 20));
-    final Offset point2 = tester.getCenter(find.text('AA'));
+    final Offset point2 = tester.getCenter(find.text('AA', skipOffstage: false));
     expect(point1.dy, greaterThan(point2.dy));
   });
 
@@ -646,4 +646,4 @@
   }
   @override
   bool shouldRebuild(TestHeader oldDelegate) => false;
-}
\ No newline at end of file
+}
diff --git a/packages/flutter/test/widgets/page_view_test.dart b/packages/flutter/test/widgets/page_view_test.dart
index 6a4fbe8..ead8fab 100644
--- a/packages/flutter/test/widgets/page_view_test.dart
+++ b/packages/flutter/test/widgets/page_view_test.dart
@@ -257,7 +257,7 @@
       ),
     ));
 
-    expect(find.text('Alabama'), findsOneWidget);
+    expect(find.text('Alabama', skipOffstage: false), findsOneWidget);
 
     await tester.pumpWidget(new Directionality(
       textDirection: TextDirection.ltr,
diff --git a/packages/flutter/test/widgets/scrollable_semantics_test.dart b/packages/flutter/test/widgets/scrollable_semantics_test.dart
index a2ca2cd..f3a458f 100644
--- a/packages/flutter/test/widgets/scrollable_semantics_test.dart
+++ b/packages/flutter/test/widgets/scrollable_semantics_test.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:ui';
+
 import 'package:flutter/material.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:flutter/rendering.dart';
@@ -362,6 +364,12 @@
                   label: r'item 1',
                   textDirection: TextDirection.ltr,
                 ),
+                new TestSemantics(
+                  flags: <SemanticsFlag>[
+                    SemanticsFlag.isHidden,
+                  ],
+                  label: r'item 2',
+                ),
               ],
             ),
           ],
diff --git a/packages/flutter/test/widgets/scrollable_semantics_traversal_order_test.dart b/packages/flutter/test/widgets/scrollable_semantics_traversal_order_test.dart
new file mode 100644
index 0000000..4211575
--- /dev/null
+++ b/packages/flutter/test/widgets/scrollable_semantics_traversal_order_test.dart
@@ -0,0 +1,813 @@
+// Copyright 2018 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 'dart:ui';
+
+import 'package:flutter/rendering.dart';
+import 'package:flutter/semantics.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter/widgets.dart';
+
+import 'semantics_tester.dart';
+
+void main() {
+  testWidgets('Traversal Order of SliverList', (WidgetTester tester) async {
+    final SemanticsTester semantics = new SemanticsTester(tester);
+
+    final List<Widget> listChildren = new List<Widget>.generate(30, (int i) {
+      return new Container(
+        height: 200.0,
+        child: new Row(
+          crossAxisAlignment: CrossAxisAlignment.stretch,
+          children: <Widget>[
+            new Semantics(
+              container: true,
+              child: new Text('Item ${i}a'),
+            ),
+            new Semantics(
+              container: true,
+              child: new Text('item ${i}b'),
+            ),
+          ],
+        ),
+      );
+    });
+    await tester.pumpWidget(
+      new Semantics(
+        textDirection: TextDirection.ltr,
+        child: new Directionality(
+          textDirection: TextDirection.ltr,
+          child: new MediaQuery(
+            data: const MediaQueryData(),
+            child: new CustomScrollView(
+              controller: new ScrollController(initialScrollOffset: 3000.0),
+              slivers: <Widget>[
+                new SliverList(
+                  delegate: new SliverChildListDelegate(listChildren),
+                ),
+              ],
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(semantics, hasSemantics(
+      new TestSemantics.root(
+        children: <TestSemantics>[
+          new TestSemantics(
+            textDirection: TextDirection.ltr,
+            children: <TestSemantics>[
+              new TestSemantics(
+                children: <TestSemantics>[
+                  new TestSemantics(
+                    actions: <SemanticsAction>[
+                      SemanticsAction.scrollUp,
+                      SemanticsAction.scrollDown,
+                    ],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 13a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'item 13b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 14a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'item 14b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 15a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'item 15b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 16a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'item 16b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 17a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'item 17b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 18a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'item 18b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 19a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'item 19b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
+                  ),
+                ],
+              ),
+            ],
+          ),
+        ],
+      ),
+      childOrder: DebugSemanticsDumpOrder.traversalOrder,
+      ignoreId: true,
+      ignoreTransform: true,
+      ignoreRect: true,
+    ));
+
+    semantics.dispose();
+  });
+
+  testWidgets('Traversal Order of SliverFixedExtentList', (WidgetTester tester) async {
+    final SemanticsTester semantics = new SemanticsTester(tester);
+
+    final List<Widget> listChildren = new List<Widget>.generate(30, (int i) {
+      return new Container(
+        height: 200.0,
+        child: new Row(
+          crossAxisAlignment: CrossAxisAlignment.stretch,
+          children: <Widget>[
+            new Semantics(
+              container: true,
+              child: new Text('Item ${i}a'),
+            ),
+            new Semantics(
+              container: true,
+              child: new Text('item ${i}b'),
+            ),
+          ],
+        ),
+      );
+    });
+    await tester.pumpWidget(
+      new Semantics(
+        textDirection: TextDirection.ltr,
+        child: new Directionality(
+          textDirection: TextDirection.ltr,
+          child: new MediaQuery(
+            data: const MediaQueryData(),
+            child: new CustomScrollView(
+              controller: new ScrollController(initialScrollOffset: 3000.0),
+              slivers: <Widget>[
+                new SliverFixedExtentList(
+                  itemExtent: 200.0,
+                  delegate: new SliverChildListDelegate(listChildren),
+                ),
+              ],
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(semantics, hasSemantics(
+      new TestSemantics.root(
+        children: <TestSemantics>[
+          new TestSemantics(
+            textDirection: TextDirection.ltr,
+            children: <TestSemantics>[
+              new TestSemantics(
+                children: <TestSemantics>[
+                  new TestSemantics(
+                    actions: <SemanticsAction>[
+                      SemanticsAction.scrollUp,
+                      SemanticsAction.scrollDown,
+                    ],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 13a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'item 13b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 14a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'item 14b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 15a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'item 15b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 16a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'item 16b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 17a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'item 17b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 18a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'item 18b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 19a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'item 19b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
+                  ),
+                ],
+              ),
+            ],
+          ),
+        ],
+      ),
+      childOrder: DebugSemanticsDumpOrder.traversalOrder,
+      ignoreId: true,
+      ignoreTransform: true,
+      ignoreRect: true,
+    ));
+
+    semantics.dispose();
+  });
+
+  testWidgets('Traversal Order of SliverGrid', (WidgetTester tester) async {
+    final SemanticsTester semantics = new SemanticsTester(tester);
+
+    final List<Widget> listChildren = new List<Widget>.generate(30, (int i) {
+      return new Container(
+        height: 200.0,
+        child: new Text('Item $i'),
+      );
+    });
+    await tester.pumpWidget(
+      new Semantics(
+        textDirection: TextDirection.ltr,
+        child: new Directionality(
+          textDirection: TextDirection.ltr,
+          child: new MediaQuery(
+            data: const MediaQueryData(),
+            child: new CustomScrollView(
+              controller: new ScrollController(initialScrollOffset: 1600.0),
+              slivers: <Widget>[
+                new SliverGrid.count(
+                  crossAxisCount: 2,
+                  crossAxisSpacing: 400.0,
+                  children: listChildren,
+                ),
+              ],
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(semantics, hasSemantics(
+      new TestSemantics.root(
+        children: <TestSemantics>[
+          new TestSemantics(
+            textDirection: TextDirection.ltr,
+            children: <TestSemantics>[
+              new TestSemantics(
+                children: <TestSemantics>[
+                  new TestSemantics(
+                    actions: <SemanticsAction>[SemanticsAction.scrollUp,
+                    SemanticsAction.scrollDown],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 12',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 13',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 14',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 15',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 16',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 17',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 18',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 19',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 20',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 21',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 22',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 23',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 24',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 25',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
+                  ),
+                ],
+              ),
+            ],
+          ),
+        ],
+      ),
+      childOrder: DebugSemanticsDumpOrder.traversalOrder,
+      ignoreId: true,
+      ignoreTransform: true,
+      ignoreRect: true,
+    ));
+
+    semantics.dispose();
+  });
+
+  testWidgets('Traversal Order of List of individual slivers', (WidgetTester tester) async {
+    final SemanticsTester semantics = new SemanticsTester(tester);
+
+    final List<Widget> listChildren = new List<Widget>.generate(30, (int i) {
+      return new SliverToBoxAdapter(
+        child: new Container(
+          height: 200.0,
+          child: new Row(
+            crossAxisAlignment: CrossAxisAlignment.stretch,
+            children: <Widget>[
+              new Semantics(
+                container: true,
+                child: new Text('Item ${i}a'),
+              ),
+              new Semantics(
+                container: true,
+                child: new Text('item ${i}b'),
+              ),
+            ],
+          ),
+        ),
+      );
+    });
+    await tester.pumpWidget(
+      new Semantics(
+        textDirection: TextDirection.ltr,
+        child: new Directionality(
+          textDirection: TextDirection.ltr,
+          child: new MediaQuery(
+            data: const MediaQueryData(),
+            child: new CustomScrollView(
+              controller: new ScrollController(initialScrollOffset: 3000.0),
+              slivers: listChildren,
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(semantics, hasSemantics(
+      new TestSemantics.root(
+        children: <TestSemantics>[
+          new TestSemantics(
+            textDirection: TextDirection.ltr,
+            children: <TestSemantics>[
+              new TestSemantics(
+                children: <TestSemantics>[
+                  new TestSemantics(
+                    actions: <SemanticsAction>[
+                      SemanticsAction.scrollUp,
+                      SemanticsAction.scrollDown,
+                    ],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 13a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'item 13b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 14a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'item 14b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 15a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'item 15b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 16a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'item 16b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 17a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'item 17b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 18a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'item 18b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 19a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'item 19b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
+                  ),
+                ],
+              ),
+            ],
+          ),
+        ],
+      ),
+      childOrder: DebugSemanticsDumpOrder.traversalOrder,
+      ignoreId: true,
+      ignoreTransform: true,
+      ignoreRect: true,
+    ));
+
+    semantics.dispose();
+  });
+
+  testWidgets('Traversal Order of in a SingleChildScrollView', (WidgetTester tester) async {
+    final SemanticsTester semantics = new SemanticsTester(tester);
+
+    final List<Widget> listChildren = new List<Widget>.generate(30, (int i) {
+      return new Container(
+        height: 200.0,
+        child: new Row(
+          crossAxisAlignment: CrossAxisAlignment.stretch,
+          children: <Widget>[
+            new Semantics(
+              container: true,
+              child: new Text('Item ${i}a'),
+            ),
+            new Semantics(
+              container: true,
+              child: new Text('item ${i}b'),
+            ),
+          ],
+        ),
+      );
+    });
+    await tester.pumpWidget(
+      new Semantics(
+        textDirection: TextDirection.ltr,
+        child: new Directionality(
+          textDirection: TextDirection.ltr,
+          child: new MediaQuery(
+            data: const MediaQueryData(),
+            child: new SingleChildScrollView(
+              controller: new ScrollController(initialScrollOffset: 3000.0),
+              child: new Column(
+                children: listChildren,
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(semantics, hasSemantics(
+      new TestSemantics.root(
+        children: <TestSemantics>[
+          new TestSemantics(
+            textDirection: TextDirection.ltr,
+            children: <TestSemantics>[
+              new TestSemantics(
+                actions: <SemanticsAction>[
+                  SemanticsAction.scrollUp,
+                  SemanticsAction.scrollDown,
+                ],
+                children: <TestSemantics>[
+                  new TestSemantics(
+                    flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                    label: 'Item 13a',
+                    textDirection: TextDirection.ltr,
+                  ),
+                  new TestSemantics(
+                    flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                    label: 'item 13b',
+                    textDirection: TextDirection.ltr,
+                  ),
+                  new TestSemantics(
+                    flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                    label: 'Item 14a',
+                    textDirection: TextDirection.ltr,
+                  ),
+                  new TestSemantics(
+                    flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                    label: 'item 14b',
+                    textDirection: TextDirection.ltr,
+                  ),
+                  new TestSemantics(
+                    label: 'Item 15a',
+                    textDirection: TextDirection.ltr,
+                  ),
+                  new TestSemantics(
+                    label: 'item 15b',
+                    textDirection: TextDirection.ltr,
+                  ),
+                  new TestSemantics(
+                    label: 'Item 16a',
+                    textDirection: TextDirection.ltr,
+                  ),
+                  new TestSemantics(
+                    label: 'item 16b',
+                    textDirection: TextDirection.ltr,
+                  ),
+                  new TestSemantics(
+                    label: 'Item 17a',
+                    textDirection: TextDirection.ltr,
+                  ),
+                  new TestSemantics(
+                    label: 'item 17b',
+                    textDirection: TextDirection.ltr,
+                  ),
+                  new TestSemantics(
+                    flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                    label: 'Item 18a',
+                    textDirection: TextDirection.ltr,
+                  ),
+                  new TestSemantics(
+                    flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                    label: 'item 18b',
+                    textDirection: TextDirection.ltr,
+                  ),
+                  new TestSemantics(
+                    flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                    label: 'Item 19a',
+                    textDirection: TextDirection.ltr,
+                  ),
+                  new TestSemantics(
+                    flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                    label: 'item 19b',
+                    textDirection: TextDirection.ltr,
+                  ),
+                ],
+              ),
+            ],
+          ),
+        ],
+      ),
+      childOrder: DebugSemanticsDumpOrder.traversalOrder,
+      ignoreId: true,
+      ignoreTransform: true,
+      ignoreRect: true,
+    ));
+
+    semantics.dispose();
+  });
+
+  testWidgets('Traversal Order with center child', (WidgetTester tester) async {
+    final SemanticsTester semantics = new SemanticsTester(tester);
+
+    await tester.pumpWidget(new Semantics(
+      textDirection: TextDirection.ltr,
+      child: new Directionality(
+        textDirection: TextDirection.ltr,
+        child: new MediaQuery(
+          data: const MediaQueryData(),
+          child: new Scrollable(
+            viewportBuilder: (BuildContext context, ViewportOffset offset) {
+              return new Viewport(
+                offset: offset,
+                center: const ValueKey<int>(0),
+                slivers: new List<Widget>.generate(30, (int i) {
+                  final int item = i - 15;
+                  return new SliverToBoxAdapter(
+                    key: new ValueKey<int>(item),
+                    child: new Container(
+                      height: 200.0,
+                      child: new Row(
+                        crossAxisAlignment: CrossAxisAlignment.stretch,
+                        children: <Widget>[
+                          new Semantics(
+                            container: true,
+                            child: new Text('${item}a'),
+                          ),
+                          new Semantics(
+                            container: true,
+                            child: new Text('${item}b'),
+                          ),
+                        ],
+                      ),
+                    ),
+                  );
+                }),
+              );
+            },
+          ),
+        ),
+      ),
+    ));
+
+    expect(semantics, hasSemantics(
+      new TestSemantics.root(
+        children: <TestSemantics>[
+          new TestSemantics(
+            textDirection: TextDirection.ltr,
+            children: <TestSemantics>[
+              new TestSemantics(
+                children: <TestSemantics>[
+                  new TestSemantics(
+                    actions: <SemanticsAction>[
+                      SemanticsAction.scrollUp,
+                      SemanticsAction.scrollDown,
+                    ],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: '-2a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: '-2b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: '-1a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: '-1b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: '0a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: '0b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: '1a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: '1b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: '2a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: '2b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: '3a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: '3b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: '4a',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: '4b',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
+                  ),
+                ],
+              ),
+            ],
+          ),
+        ],
+      ),
+      ignoreRect: true,
+      ignoreTransform: true,
+      ignoreId: true,
+    ));
+
+    semantics.dispose();
+
+  });
+}
diff --git a/packages/flutter/test/widgets/single_child_scroll_view_test.dart b/packages/flutter/test/widgets/single_child_scroll_view_test.dart
index f3d937b..126d3de 100644
--- a/packages/flutter/test/widgets/single_child_scroll_view_test.dart
+++ b/packages/flutter/test/widgets/single_child_scroll_view_test.dart
@@ -2,9 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:ui';
+
 import 'package:flutter_test/flutter_test.dart';
 import 'package:flutter/widgets.dart';
 
+import 'semantics_tester.dart';
+
 class TestScrollPosition extends ScrollPositionWithSingleContext {
   TestScrollPosition({
     ScrollPhysics physics,
@@ -170,4 +174,171 @@
     );
     expect(innerScrollable.controller, isNull);
   });
+
+  testWidgets('SingleChildScrollView semantics', (WidgetTester tester) async {
+    final SemanticsTester semantics = new SemanticsTester(tester);
+    final ScrollController controller = new ScrollController();
+
+    await tester.pumpWidget(
+      new Directionality(
+        textDirection: TextDirection.ltr,
+        child: new SingleChildScrollView(
+          controller: controller,
+          child: new Column(
+            children: new List<Widget>.generate(30, (int i) {
+              return new Container(
+                height: 200.0,
+                child: new Text('Tile $i'),
+              );
+            }),
+          ),
+        ),
+      ),
+    );
+
+    expect(semantics, hasSemantics(
+      new TestSemantics(
+        children: <TestSemantics>[
+          new TestSemantics(
+            actions: <SemanticsAction>[
+              SemanticsAction.scrollUp,
+            ],
+            children: <TestSemantics>[
+              new TestSemantics(
+                label: r'Tile 0',
+                textDirection: TextDirection.ltr,
+              ),
+              new TestSemantics(
+                label: r'Tile 1',
+                textDirection: TextDirection.ltr,
+              ),
+              new TestSemantics(
+                label: r'Tile 2',
+                textDirection: TextDirection.ltr,
+              ),
+              new TestSemantics(
+                flags: <SemanticsFlag>[
+                  SemanticsFlag.isHidden,
+                ],
+                label: r'Tile 3',
+                textDirection: TextDirection.ltr,
+              ),
+              new TestSemantics(
+                flags: <SemanticsFlag>[
+                  SemanticsFlag.isHidden,],
+                label: r'Tile 4',
+                textDirection: TextDirection.ltr,
+              ),
+            ],
+          ),
+        ],
+      ),
+      ignoreRect: true, ignoreTransform: true, ignoreId: true,
+    ));
+
+    controller.jumpTo(3000.0);
+    await tester.pumpAndSettle();
+
+    expect(semantics, hasSemantics(
+      new TestSemantics(
+        children: <TestSemantics>[
+          new TestSemantics(
+            actions: <SemanticsAction>[
+              SemanticsAction.scrollUp,
+              SemanticsAction.scrollDown,
+            ],
+            children: <TestSemantics>[
+              new TestSemantics(
+                flags: <SemanticsFlag>[
+                  SemanticsFlag.isHidden,
+                ],
+                label: r'Tile 13',
+                textDirection: TextDirection.ltr,
+              ),
+              new TestSemantics(
+                flags: <SemanticsFlag>[
+                  SemanticsFlag.isHidden,
+                ],
+                label: r'Tile 14',
+                textDirection: TextDirection.ltr,
+              ),
+              new TestSemantics(
+                label: r'Tile 15',
+                textDirection: TextDirection.ltr,
+              ),
+              new TestSemantics(
+                label: r'Tile 16',
+                textDirection: TextDirection.ltr,
+              ),
+              new TestSemantics(
+                label: r'Tile 17',
+                textDirection: TextDirection.ltr,
+              ),
+              new TestSemantics(
+                flags: <SemanticsFlag>[
+                  SemanticsFlag.isHidden,
+                ],
+                label: r'Tile 18',
+                textDirection: TextDirection.ltr,
+              ),
+              new TestSemantics(
+                flags: <SemanticsFlag>[
+                  SemanticsFlag.isHidden,
+                ],
+                label: r'Tile 19',
+                textDirection: TextDirection.ltr,
+              ),
+            ],
+          ),
+        ],
+      ),
+      ignoreRect: true, ignoreTransform: true, ignoreId: true,
+    ));
+
+    controller.jumpTo(6000.0);
+    await tester.pumpAndSettle();
+
+    expect(semantics, hasSemantics(
+      new TestSemantics(
+        children: <TestSemantics>[
+          new TestSemantics(
+            actions: <SemanticsAction>[
+              SemanticsAction.scrollDown,
+            ],
+            children: <TestSemantics>[
+              new TestSemantics(
+                flags: <SemanticsFlag>[
+                  SemanticsFlag.isHidden,
+                ],
+                label: r'Tile 25',
+                textDirection: TextDirection.ltr,
+              ),
+              new TestSemantics(
+                flags: <SemanticsFlag>[
+                  SemanticsFlag.isHidden,
+                ],
+                label: r'Tile 26',
+                textDirection: TextDirection.ltr,
+              ),
+              new TestSemantics(
+                label: r'Tile 27',
+                textDirection: TextDirection.ltr,
+              ),
+              new TestSemantics(
+                label: r'Tile 28',
+                textDirection: TextDirection.ltr,
+              ),
+              new TestSemantics(
+                label: r'Tile 29',
+                textDirection: TextDirection.ltr,
+              ),
+            ],
+          ),
+        ],
+      ),
+      ignoreRect: true, ignoreTransform: true, ignoreId: true,
+    ));
+
+    semantics.dispose();
+  });
 }
diff --git a/packages/flutter/test/widgets/sliver_fill_viewport_test.dart b/packages/flutter/test/widgets/sliver_fill_viewport_test.dart
index ae0850d..3ba4b77 100644
--- a/packages/flutter/test/widgets/sliver_fill_viewport_test.dart
+++ b/packages/flutter/test/widgets/sliver_fill_viewport_test.dart
@@ -70,17 +70,42 @@
         ' │   GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
         ' │   0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
         ' │   crossAxisDirection: AxisDirection.right,\n'
-        ' │   viewportMainAxisExtent: 600.0)\n'
+        ' │   viewportMainAxisExtent: 600.0, remainingCacheExtent: 850.0\n'
+        ' │   cacheOrigin: 0.0 )\n'
         ' │ geometry: SliverGeometry(scrollExtent: 12000.0, paintExtent:\n'
-        ' │   600.0, maxPaintExtent: 12000.0, hasVisualOverflow: true)\n'
-        ' │ currently live children: 0 to 0\n'
+        ' │   600.0, maxPaintExtent: 12000.0, hasVisualOverflow: true,\n'
+        ' │   cacheExtent: 850.0)\n'
+        ' │ currently live children: 0 to 1\n'
         ' │\n'
-        ' └─child with index 0: RenderRepaintBoundary#00000\n'
-        '   │ parentData: index=0; layoutOffset=0.0\n'
+        ' ├─child with index 0: RenderRepaintBoundary#00000\n'
+        ' │ │ parentData: index=0; layoutOffset=0.0\n'
+        ' │ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
+        ' │ │ layer: OffsetLayer#00000\n'
+        ' │ │ size: Size(800.0, 600.0)\n'
+        ' │ │ metrics: 75.0% useful (1 bad vs 3 good)\n'
+        ' │ │ diagnosis: insufficient data to draw conclusion (less than five\n'
+        ' │ │   repaints)\n'
+        ' │ │\n'
+        ' │ └─child: RenderParagraph#00000\n'
+        ' │   │ parentData: <none> (can use size)\n'
+        ' │   │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
+        ' │   │ size: Size(800.0, 600.0)\n'
+        ' │   │ textAlign: start\n'
+        ' │   │ textDirection: ltr\n'
+        ' │   │ softWrap: wrapping at box width\n'
+        ' │   │ overflow: clip\n'
+        ' │   │ maxLines: unlimited\n'
+        ' │   ╘═╦══ text ═══\n'
+        ' │     ║ TextSpan:\n'
+        ' │     ║   <all styles inherited>\n'
+        ' │     ║   "0"\n'
+        ' │     ╚═══════════\n'
+        ' └─child with index 1: RenderRepaintBoundary#00000\n'
+        '   │ parentData: index=1; layoutOffset=600.0\n'
         '   │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
         '   │ layer: OffsetLayer#00000\n'
         '   │ size: Size(800.0, 600.0)\n'
-        '   │ metrics: 50.0% useful (1 bad vs 1 good)\n'
+        '   │ metrics: 75.0% useful (1 bad vs 3 good)\n'
         '   │ diagnosis: insufficient data to draw conclusion (less than five\n'
         '   │   repaints)\n'
         '   │\n'
@@ -96,7 +121,7 @@
         '     ╘═╦══ text ═══\n'
         '       ║ TextSpan:\n'
         '       ║   <all styles inherited>\n'
-        '       ║   "0"\n'
+        '       ║   "1"\n'
         '       ╚═══════════\n'
       ),
     );
diff --git a/packages/flutter/test/widgets/sliver_prototype_item_extent_test.dart b/packages/flutter/test/widgets/sliver_prototype_item_extent_test.dart
index 113f80d..8be2d77 100644
--- a/packages/flutter/test/widgets/sliver_prototype_item_extent_test.dart
+++ b/packages/flutter/test/widgets/sliver_prototype_item_extent_test.dart
@@ -131,7 +131,7 @@
     );
 
     // Item 0 exists in the list and as the prototype item.
-    expect(tester.widgetList(find.text('Item 0')).length, 2);
+    expect(tester.widgetList(find.text('Item 0', skipOffstage: false)).length, 2);
 
     for (int i = 1; i < 10; i += 1)
       expect(find.text('Item $i'), findsOneWidget);
diff --git a/packages/flutter/test/widgets/sliver_semantics_test.dart b/packages/flutter/test/widgets/sliver_semantics_test.dart
index 8825058..9ff1110 100644
--- a/packages/flutter/test/widgets/sliver_semantics_test.dart
+++ b/packages/flutter/test/widgets/sliver_semantics_test.dart
@@ -8,7 +8,6 @@
 import 'package:flutter/rendering.dart';
 import 'package:flutter/widgets.dart';
 import 'package:flutter/material.dart';
-import 'package:vector_math/vector_math_64.dart';
 
 import 'semantics_tester.dart';
 
@@ -36,22 +35,25 @@
       );
     });
     await tester.pumpWidget(
-      new Directionality(
+      new Semantics(
         textDirection: TextDirection.ltr,
-        child: new MediaQuery(
-          data: const MediaQueryData(),
-          child: new CustomScrollView(
-            controller: scrollController,
-            slivers: <Widget>[
-              const SliverAppBar(
-                pinned: true,
-                expandedHeight: appBarExpandedHeight,
-                title: const Text('Semantics Test with Slivers'),
-              ),
-              new SliverList(
-                delegate: new SliverChildListDelegate(listChildren),
-              ),
-            ],
+        child: new Directionality(
+          textDirection: TextDirection.ltr,
+          child: new MediaQuery(
+            data: const MediaQueryData(),
+            child: new CustomScrollView(
+              controller: scrollController,
+              slivers: <Widget>[
+                const SliverAppBar(
+                  pinned: true,
+                  expandedHeight: appBarExpandedHeight,
+                  title: const Text('Semantics Test with Slivers'),
+                ),
+                new SliverList(
+                  delegate: new SliverChildListDelegate(listChildren),
+                ),
+              ],
+            ),
           ),
         ),
       ),
@@ -61,29 +63,51 @@
     expect(semantics, hasSemantics(
       new TestSemantics.root(
         children: <TestSemantics>[
-          new TestSemantics.rootChild(
+          new TestSemantics(
             id: 1,
-            tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
+            textDirection: TextDirection.ltr,
             children: <TestSemantics>[
               new TestSemantics(
-                id: 6,
-                actions: <SemanticsAction>[SemanticsAction.scrollUp],
+                id: 2,
                 children: <TestSemantics>[
                   new TestSemantics(
-                    id: 2,
-                    label: r'Item 0',
-                    textDirection: TextDirection.ltr,
-                  ),
-                  new TestSemantics(
-                    id: 3,
-                    label: r'Item 1',
-                    textDirection: TextDirection.ltr,
-                  ),
-                  new TestSemantics(
-                    id: 4,
-                    flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
-                    label: r'Semantics Test with Slivers',
-                    textDirection: TextDirection.ltr,
+                    id: 9,
+                    actions: <SemanticsAction>[SemanticsAction.scrollUp],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        id: 7,
+                        children: <TestSemantics>[
+                          new TestSemantics(
+                            id: 8,
+                            flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
+                            label: 'Semantics Test with Slivers',
+                            textDirection: TextDirection.ltr,
+                          ),
+                        ],
+                      ),
+                      new TestSemantics(
+                        id: 3,
+                        label: 'Item 0',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        id: 4,
+                        label: 'Item 1',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        id: 5,
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 2',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        id: 6,
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 3',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
                   ),
                 ],
               ),
@@ -103,37 +127,63 @@
     expect(semantics, hasSemantics(
       new TestSemantics.root(
         children: <TestSemantics>[
-          new TestSemantics.rootChild(
+          new TestSemantics(
             id: 1,
-            tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
+            textDirection: TextDirection.ltr,
             children: <TestSemantics>[
               new TestSemantics(
-                id: 6,
-                actions: <SemanticsAction>[SemanticsAction.scrollUp, SemanticsAction.scrollDown],
+                id: 2,
                 children: <TestSemantics>[
                   new TestSemantics(
-                    id: 2,
-                    label: r'Item 0',
-                    textDirection: TextDirection.ltr,
-                  ),
-                  new TestSemantics(
-                    id: 3,
-                    label: r'Item 1',
-                    textDirection: TextDirection.ltr,
-                  ),
-                  new TestSemantics(
                     id: 7,
-                    label: r'Item 2',
-                    textDirection: TextDirection.ltr,
+                    tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        id: 8,
+                        flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
+                        label: 'Semantics Test with Slivers',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
+                  ),
+                  new TestSemantics(
+                    id: 9,
+                    actions: <SemanticsAction>[
+                      SemanticsAction.scrollUp,
+                      SemanticsAction.scrollDown,
+                    ],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        id: 3,
+                        label: 'Item 0',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        id: 4,
+                        label: 'Item 1',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        id: 5,
+                        label: 'Item 2',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        id: 6,
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 3',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        id: 10,
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 4',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
                   ),
                 ],
               ),
-              new TestSemantics(
-                id: 4,
-                flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
-                label: r'Semantics Test with Slivers',
-                textDirection: TextDirection.ltr,
-              ),
             ],
           ),
         ],
@@ -150,34 +200,53 @@
     expect(semantics, hasSemantics(
       new TestSemantics.root(
         children: <TestSemantics>[
-          new TestSemantics.rootChild(
+          new TestSemantics(
             id: 1,
-            tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
+            textDirection: TextDirection.ltr,
             children: <TestSemantics>[
               new TestSemantics(
-                id: 6,
-                actions: <SemanticsAction>[SemanticsAction.scrollUp, SemanticsAction.scrollDown],
+                id: 2,
                 children: <TestSemantics>[
                   new TestSemantics(
-                    id: 2,
-                    label: r'Item 0',
-                    textDirection: TextDirection.ltr,
-                  ),
-                  new TestSemantics(
-                    id: 3,
-                    label: r'Item 1',
-                    textDirection: TextDirection.ltr,
-                  ),
-                  new TestSemantics(
-                    id: 7,
-                    label: r'Item 2',
-                    textDirection: TextDirection.ltr,
-                  ),
-                  new TestSemantics(
-                    id: 4,
-                    flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
-                    label: r'Semantics Test with Slivers',
-                    textDirection: TextDirection.ltr,
+                    id: 9,
+                    actions: <SemanticsAction>[
+                      SemanticsAction.scrollUp,
+                      SemanticsAction.scrollDown,
+                    ],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        id: 7,
+                        children: <TestSemantics>[
+                          new TestSemantics(
+                            id: 8,
+                            flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
+                            label: 'Semantics Test with Slivers',
+                            textDirection: TextDirection.ltr,
+                          ),
+                        ],
+                      ),
+                      new TestSemantics(
+                        id: 3,
+                        label: 'Item 0',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        id: 4,
+                        label: 'Item 1',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        id: 5,
+                        label: 'Item 2',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        id: 6,
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 3',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
                   ),
                 ],
               ),
@@ -192,7 +261,7 @@
     semantics.dispose();
   });
 
-  testWidgets('Offscreen sliver are not included in semantics tree', (WidgetTester tester) async {
+  testWidgets('Offscreen sliver are hidden in semantics tree', (WidgetTester tester) async {
     final SemanticsTester semantics = new SemanticsTester(tester);
 
     const double containerHeight = 200.0;
@@ -209,14 +278,17 @@
       );
     });
     await tester.pumpWidget(
-      new Directionality(
+      new Semantics(
         textDirection: TextDirection.ltr,
-        child: new Center(
-          child: new SizedBox(
-            height: containerHeight,
-            child: new CustomScrollView(
-              controller: scrollController,
-              slivers: slivers,
+        child: new Directionality(
+          textDirection: TextDirection.ltr,
+          child: new Center(
+            child: new SizedBox(
+              height: containerHeight,
+              child: new CustomScrollView(
+                controller: scrollController,
+                slivers: slivers,
+              ),
             ),
           ),
         ),
@@ -226,23 +298,36 @@
     expect(semantics, hasSemantics(
       new TestSemantics.root(
         children: <TestSemantics>[
-          new TestSemantics.rootChild(
-            id: 1,
-            tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
+          new TestSemantics(
+            textDirection: TextDirection.ltr,
             children: <TestSemantics>[
               new TestSemantics(
-                id: 4,
-                actions: <SemanticsAction>[SemanticsAction.scrollUp, SemanticsAction.scrollDown],
                 children: <TestSemantics>[
                   new TestSemantics(
-                    id: 2,
-                    label: 'Item 2',
-                    textDirection: TextDirection.ltr,
-                  ),
-                  new TestSemantics(
-                    id: 3,
-                    label: 'Item 1',
-                    textDirection: TextDirection.ltr,
+                    actions: <SemanticsAction>[
+                      SemanticsAction.scrollUp,
+                      SemanticsAction.scrollDown,
+                    ],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 0',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 1',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 2',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 3',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
                   ),
                 ],
               ),
@@ -252,6 +337,7 @@
       ),
       ignoreRect: true,
       ignoreTransform: true,
+      ignoreId: true,
     ));
 
     semantics.dispose();
@@ -269,10 +355,13 @@
       );
     });
     await tester.pumpWidget(
-      new Directionality(
+      new Semantics(
         textDirection: TextDirection.ltr,
-        child: new CustomScrollView(
-          slivers: slivers,
+        child: new Directionality(
+          textDirection: TextDirection.ltr,
+          child: new CustomScrollView(
+            slivers: slivers,
+          ),
         ),
       ),
     );
@@ -280,37 +369,34 @@
     expect(semantics, hasSemantics(
       new TestSemantics.root(
         children: <TestSemantics>[
-          new TestSemantics.rootChild(
-            id: 1,
-            tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
+          new TestSemantics(
+            textDirection: TextDirection.ltr,
             children: <TestSemantics>[
               new TestSemantics(
-                id: 7,
                 children: <TestSemantics>[
                   new TestSemantics(
-                    id: 2,
-                    label: 'Item 4',
-                    textDirection: TextDirection.ltr,
-                  ),
-                  new TestSemantics(
-                    id: 3,
-                    label: 'Item 3',
-                    textDirection: TextDirection.ltr,
-                  ),
-                  new TestSemantics(
-                    id: 4,
-                    label: 'Item 2',
-                    textDirection: TextDirection.ltr,
-                  ),
-                  new TestSemantics(
-                    id: 5,
-                    label: 'Item 1',
-                    textDirection: TextDirection.ltr,
-                  ),
-                  new TestSemantics(
-                    id: 6,
-                    label: 'Item 0',
-                    textDirection: TextDirection.ltr,
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        label: 'Item 4',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 3',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 2',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 1',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 0',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
                   ),
                 ],
               ),
@@ -320,6 +406,8 @@
       ),
       ignoreRect: true,
       ignoreTransform: true,
+      ignoreId: true,
+      childOrder: DebugSemanticsDumpOrder.inverseHitTest,
     ));
 
     semantics.dispose();
@@ -335,89 +423,97 @@
       );
     });
     final ScrollController controller = new ScrollController(initialScrollOffset: 280.0);
-    await tester.pumpWidget(new Directionality(
+    await tester.pumpWidget(new Semantics(
       textDirection: TextDirection.ltr,
-      child: new MediaQuery(
-        data: const MediaQueryData(),
-        child: new CustomScrollView(
-          slivers: <Widget>[
-            const SliverAppBar(
-              pinned: true,
-              expandedHeight: 100.0,
-              title: const Text('AppBar'),
-            ),
-            new SliverList(
-              delegate: new SliverChildListDelegate(listChildren),
-            ),
-          ],
-          controller: controller,
+      child: new Directionality(
+        textDirection: TextDirection.ltr,
+        child: new MediaQuery(
+          data: const MediaQueryData(),
+          child: new CustomScrollView(
+            slivers: <Widget>[
+              const SliverAppBar(
+                pinned: true,
+                expandedHeight: 100.0,
+                title: const Text('AppBar'),
+              ),
+              new SliverList(
+                delegate: new SliverChildListDelegate(listChildren),
+              ),
+            ],
+            controller: controller,
+          ),
         ),
       ),
     ));
 
-    // 'Item 0' is covered by app bar.
-    expect(semantics, isNot(includesNodeWith(label: 'Item 0')));
-
     expect(semantics, hasSemantics(
       new TestSemantics.root(
         children: <TestSemantics>[
-          new TestSemantics.rootChild(
-            id: 1,
-            rect: TestSemantics.fullScreen,
-            tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
+          new TestSemantics(
+            textDirection: TextDirection.ltr,
             children: <TestSemantics>[
               new TestSemantics(
-                id: 7,
-                actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
-                rect: TestSemantics.fullScreen,
-                children: <TestSemantics>[
-                  // Item 0 is missing because its covered by the app bar.
-                  new TestSemantics(
-                    id: 2,
-                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
-                    // Item 1 starts 20.0dp below edge, so there would be room for Item 0.
-                    transform: new Matrix4.translation(new Vector3(0.0, 20.0, 0.0)),
-                    label: 'Item 1',
-                  ),
-                  new TestSemantics(
-                    id: 3,
-                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
-                    transform: new Matrix4.translation(new Vector3(0.0, 220.0, 0.0)),
-                    label: 'Item 2',
-                  ),
-                  new TestSemantics(
-                    id: 4,
-                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
-                    transform: new Matrix4.translation(new Vector3(0.0, 420.0, 0.0)),
-                    label: 'Item 3',
-                  ),
-                ],
-              ),
-              new TestSemantics(
-                id: 5,
-                rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
-                flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
-                tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
                 children: <TestSemantics>[
                   new TestSemantics(
-                    id: 6,
-                    label: 'AppBar',
-                    rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
-                    textDirection: TextDirection.ltr,
+                    tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
+                        label: 'AppBar',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
+                  ),
+                  new TestSemantics(
+                    actions: <SemanticsAction>[
+                      SemanticsAction.scrollUp,
+                      SemanticsAction.scrollDown,
+                    ],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 0',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 1',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 2',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 3',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 4',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 5',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
                   ),
                 ],
               ),
             ],
-          )
+          ),
         ],
       ),
       ignoreTransform: true,
+      ignoreId: true,
+      ignoreRect: true,
     ));
 
     semantics.dispose();
   });
 
-  testWidgets('Slivers fully covered by another overlapping sliver are excluded', (WidgetTester tester) async {
+  testWidgets('Slivers fully covered by another overlapping sliver are hidden', (WidgetTester tester) async {
     final SemanticsTester semantics = new SemanticsTester(tester);
 
     final ScrollController controller = new ScrollController(initialScrollOffset: 280.0);
@@ -429,79 +525,88 @@
         ),
       );
     });
-    await tester.pumpWidget(new Directionality(
+    await tester.pumpWidget(new Semantics(
       textDirection: TextDirection.ltr,
-      child: new MediaQuery(
-        data: const MediaQueryData(),
-        child: new CustomScrollView(
-          controller: controller,
-          slivers: <Widget>[
-            const SliverAppBar(
-              pinned: true,
-              expandedHeight: 100.0,
-              title: const Text('AppBar'),
-            ),
-          ]..addAll(slivers),
+      child: new Directionality(
+        textDirection: TextDirection.ltr,
+        child: new MediaQuery(
+          data: const MediaQueryData(),
+          child: new CustomScrollView(
+            controller: controller,
+            slivers: <Widget>[
+              const SliverAppBar(
+                pinned: true,
+                expandedHeight: 100.0,
+                title: const Text('AppBar'),
+              ),
+            ]..addAll(slivers),
+          ),
         ),
       ),
     ));
 
-    // 'Item 0' is covered by app bar.
-    expect(semantics, isNot(includesNodeWith(label: 'Item 0')));
-
     expect(semantics, hasSemantics(
       new TestSemantics.root(
         children: <TestSemantics>[
-          new TestSemantics.rootChild(
-            id: 1,
-            rect: TestSemantics.fullScreen,
-            tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
+          new TestSemantics(
+            textDirection: TextDirection.ltr,
             children: <TestSemantics>[
               new TestSemantics(
-                id: 7,
-                actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
-                rect: TestSemantics.fullScreen,
                 children: <TestSemantics>[
                   new TestSemantics(
-                    id: 2,
-                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
-                    transform: new Matrix4.translation(new Vector3(0.0, 420.0, 0.0)),
-                    label: 'Item 3',
+                    tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
+                        label: 'AppBar',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
                   ),
                   new TestSemantics(
-                    id: 3,
-                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
-                    transform: new Matrix4.translation(new Vector3(0.0, 220.0, 0.0)),
-                    label: 'Item 2',
-                  ),
-                  new TestSemantics(
-                    id: 4,
-                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
-                    // Item 1 starts 20.0dp below edge, so there would be room for Item 0.
-                    transform: new Matrix4.translation(new Vector3(0.0, 20.0, 0.0)),
-                    label: 'Item 1',
-                  ),
-                  // Item 0 is missing because its covered by the app bar.
-                ],
-              ),
-              new TestSemantics(
-                id: 5,
-                rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
-                flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
-                tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
-                children: <TestSemantics>[
-                  new TestSemantics(
-                    id: 6,
-                    rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
-                    label: 'AppBar',
+                    actions: <SemanticsAction>[
+                      SemanticsAction.scrollUp,
+                      SemanticsAction.scrollDown,
+                    ],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 0',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 1',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 2',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 3',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 4',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 5',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
                   ),
                 ],
               ),
             ],
-          )
+          ),
         ],
       ),
       ignoreTransform: true,
+      ignoreRect: true,
+      ignoreId: true,
     ));
 
     semantics.dispose();
@@ -517,88 +622,98 @@
       );
     });
     final ScrollController controller = new ScrollController(initialScrollOffset: 280.0);
-    await tester.pumpWidget(new Directionality(
+    await tester.pumpWidget(new Semantics(
       textDirection: TextDirection.ltr,
-      child: new MediaQuery(
-        data: const MediaQueryData(),
-        child: new CustomScrollView(
-          reverse: true, // This is the important setting for this test.
-          slivers: <Widget>[
-            const SliverAppBar(
-              pinned: true,
-              expandedHeight: 100.0,
-              title: const Text('AppBar'),
-            ),
-            new SliverList(
-              delegate: new SliverChildListDelegate(listChildren),
-            ),
-          ],
-          controller: controller,
+      child: new Directionality(
+        textDirection: TextDirection.ltr,
+        child: new MediaQuery(
+          data: const MediaQueryData(),
+          child: new CustomScrollView(
+            reverse: true, // This is the important setting for this test.
+            slivers: <Widget>[
+              const SliverAppBar(
+                pinned: true,
+                expandedHeight: 100.0,
+                title: const Text('AppBar'),
+              ),
+              new SliverList(
+                delegate: new SliverChildListDelegate(listChildren),
+              ),
+            ],
+            controller: controller,
+          ),
         ),
       ),
     ));
 
-    // 'Item 0' is covered by app bar.
-    expect(semantics, isNot(includesNodeWith(label: 'Item 0')));
-
     expect(semantics, hasSemantics(
       new TestSemantics.root(
         children: <TestSemantics>[
-          new TestSemantics.rootChild(
-            id: 1,
-            rect: TestSemantics.fullScreen,
-            tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
+          new TestSemantics(
+            textDirection: TextDirection.ltr,
             children: <TestSemantics>[
               new TestSemantics(
-                id: 7,
-                actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
-                rect: TestSemantics.fullScreen,
-                children: <TestSemantics>[
-                  // Item 0 is missing because its covered by the app bar.
-                  new TestSemantics(
-                    id: 2,
-                    // Item 1 ends at 580dp, so there would be 20dp space for Item 0.
-                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
-                    label: 'Item 1',
-                  ),
-                  new TestSemantics(
-                    id: 3,
-                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
-                    label: 'Item 2',
-                  ),
-                  new TestSemantics(
-                    id: 4,
-                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
-                    label: 'Item 3',
-                  ),
-                ],
-              ),
-              new TestSemantics(
-                id: 5,
-                rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
-                transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)),
-                flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
-                tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
                 children: <TestSemantics>[
                   new TestSemantics(
-                    id: 6,
-                    rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
-                    label: 'AppBar',
-                    textDirection: TextDirection.ltr,
+                    actions: <SemanticsAction>[
+                      SemanticsAction.scrollUp,
+                      SemanticsAction.scrollDown,
+                    ],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 5',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 4',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 3',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 2',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 1',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 0',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
+                  ),
+                  new TestSemantics(
+                    tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
+                        label: 'AppBar',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
                   ),
                 ],
               ),
             ],
-          )
+          ),
         ],
       ),
       ignoreTransform: true,
+      ignoreId: true,
+      ignoreRect: true,
     ));
 
     semantics.dispose();
   });
 
-  testWidgets('Slivers fully covered by another overlapping sliver are excluded (reverse)', (WidgetTester tester) async {
+  testWidgets('Slivers fully covered by another overlapping sliver are hidden (reverse)', (WidgetTester tester) async {
     final SemanticsTester semantics = new SemanticsTester(tester);
 
     final ScrollController controller = new ScrollController(initialScrollOffset: 280.0);
@@ -610,89 +725,93 @@
         ),
       );
     });
-    await tester.pumpWidget(new Directionality(
+    await tester.pumpWidget(new Semantics(
       textDirection: TextDirection.ltr,
-      child: new MediaQuery(
-        data: const MediaQueryData(),
-        child: new CustomScrollView(
-          reverse: true, // This is the important setting for this test.
-          controller: controller,
-          slivers: <Widget>[
-            const SliverAppBar(
-              pinned: true,
-              expandedHeight: 100.0,
-              title: const Text('AppBar'),
-            ),
-          ]..addAll(slivers),
+      child: new Directionality(
+        textDirection: TextDirection.ltr,
+        child: new MediaQuery(
+          data: const MediaQueryData(),
+          child: new CustomScrollView(
+            reverse: true, // This is the important setting for this test.
+            controller: controller,
+            slivers: <Widget>[
+              const SliverAppBar(
+                pinned: true,
+                expandedHeight: 100.0,
+                title: const Text('AppBar'),
+              ),
+            ]..addAll(slivers),
+          ),
         ),
       ),
     ));
 
-    // 'Item 0' is covered by app bar.
-    expect(semantics, isNot(includesNodeWith(label: 'Item 0')));
-
     expect(semantics, hasSemantics(
       new TestSemantics.root(
         children: <TestSemantics>[
-          new TestSemantics.rootChild(
-            id: 1,
-            rect: TestSemantics.fullScreen,
-            tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
+          new TestSemantics(
+            textDirection: TextDirection.ltr,
             children: <TestSemantics>[
               new TestSemantics(
-                id: 7,
-                actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
-                rect: TestSemantics.fullScreen,
                 children: <TestSemantics>[
                   new TestSemantics(
-                    id: 2,
-                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
-                    transform: new Matrix4.translation(new Vector3(0.0, -20.0, 0.0)),
-                    label: 'Item 3',
+                    actions: <SemanticsAction>[SemanticsAction.scrollUp,
+                    SemanticsAction.scrollDown],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 5',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 4',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 3',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 2',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Item 1',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Item 0',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
                   ),
                   new TestSemantics(
-                    id: 3,
-                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
-                    transform: new Matrix4.translation(new Vector3(0.0, 180.0, 0.0)),
-                    label: 'Item 2',
-                  ),
-                  new TestSemantics(
-                    id: 4,
-                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
-                    // Item 1 ends at 580dp, so there would be 20dp space for Item 0.
-                    transform: new Matrix4.translation(new Vector3(0.0, 380.0, 0.0)),
-                    label: 'Item 1',
-                  ),
-                  // Item 0 is missing because its covered by the app bar.
-                ],
-              ),
-              new TestSemantics(
-                id: 5,
-                rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
-                transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)),
-                flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
-                tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
-                children: <TestSemantics>[
-                  new TestSemantics(
-                    id: 6,
-                    rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
-                    transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)),
-                    label: 'AppBar',
-                    textDirection: TextDirection.ltr,
+                    tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
+                        label: 'AppBar',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
                   ),
                 ],
               ),
             ],
-          )
+          ),
         ],
       ),
       ignoreTransform: true,
+      ignoreId: true,
+      ignoreRect: true,
     ));
 
     semantics.dispose();
   });
 
-  testWidgets('Slivers fully covered by another overlapping sliver are excluded (with center sliver)', (WidgetTester tester) async {
+  testWidgets('Slivers fully covered by another overlapping sliver are hidden (with center sliver)', (WidgetTester tester) async {
     final SemanticsTester semantics = new SemanticsTester(tester);
 
     final ScrollController controller = new ScrollController(initialScrollOffset: 280.0);
@@ -709,53 +828,177 @@
         child: new Text('Backward Item $i', textDirection: TextDirection.ltr),
       );
     });
-    await tester.pumpWidget(new Directionality(
+    await tester.pumpWidget(new Semantics(
       textDirection: TextDirection.ltr,
-      child: new MediaQuery(
-        data: const MediaQueryData(),
-        child: new Scrollable(
-          controller: controller,
-          viewportBuilder: (BuildContext context, ViewportOffset offset) {
-            return new Viewport(
-              offset: offset,
-              center: forwardAppBarKey,
-              slivers: <Widget>[
-                new SliverList(
-                  delegate: new SliverChildListDelegate(backwardChildren),
-                ),
-                const SliverAppBar(
-                  pinned: true,
-                  expandedHeight: 100.0,
-                  flexibleSpace: const FlexibleSpaceBar(
-                    title: const Text('Backward app bar', textDirection: TextDirection.ltr),
+      child: new Directionality(
+        textDirection: TextDirection.ltr,
+        child: new MediaQuery(
+          data: const MediaQueryData(),
+          child: new Scrollable(
+            controller: controller,
+            viewportBuilder: (BuildContext context, ViewportOffset offset) {
+              return new Viewport(
+                offset: offset,
+                center: forwardAppBarKey,
+                slivers: <Widget>[
+                  new SliverList(
+                    delegate: new SliverChildListDelegate(backwardChildren),
                   ),
-                ),
-                new SliverAppBar(
-                  pinned: true,
-                  key: forwardAppBarKey,
-                  expandedHeight: 100.0,
-                  flexibleSpace: const FlexibleSpaceBar(
-                    title: const Text('Forward app bar', textDirection: TextDirection.ltr),
+                  const SliverAppBar(
+                    pinned: true,
+                    expandedHeight: 100.0,
+                    flexibleSpace: const FlexibleSpaceBar(
+                      title: const Text('Backward app bar', textDirection: TextDirection.ltr),
+                    ),
                   ),
-                ),
-                new SliverList(
-                  delegate: new SliverChildListDelegate(forwardChildren),
-                ),
-              ],
-            );
-          },
+                  new SliverAppBar(
+                    pinned: true,
+                    key: forwardAppBarKey,
+                    expandedHeight: 100.0,
+                    flexibleSpace: const FlexibleSpaceBar(
+                      title: const Text('Forward app bar', textDirection: TextDirection.ltr),
+                    ),
+                  ),
+                  new SliverList(
+                    delegate: new SliverChildListDelegate(forwardChildren),
+                  ),
+                ],
+              );
+            },
+          ),
         ),
       ),
     ));
 
     // 'Forward Item 0' is covered by app bar.
-    expect(semantics, isNot(includesNodeWith(label: 'Forward Item 0')));
-    expect(semantics, includesNodeWith(label: 'Forward Item 1'));
+    expect(semantics, hasSemantics(
+      new TestSemantics.root(
+        children: <TestSemantics>[
+          new TestSemantics(
+            textDirection: TextDirection.ltr,
+            children: <TestSemantics>[
+              new TestSemantics(
+                children: <TestSemantics>[
+                  new TestSemantics(
+                    tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
+                        label: 'Forward app bar',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
+                  ),
+                  new TestSemantics(
+                    actions: <SemanticsAction>[
+                      SemanticsAction.scrollUp,
+                      SemanticsAction.scrollDown,
+                    ],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Forward Item 0',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Forward Item 1',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Forward Item 2',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Forward Item 3',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Forward Item 4',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Forward Item 5',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
+                  ),
+                ],
+              ),
+            ],
+          ),
+        ],
+      ),
+      ignoreTransform: true,
+      ignoreRect: true,
+      ignoreId: true,
+    ));
 
     controller.jumpTo(-880.0);
     await tester.pumpAndSettle();
-    expect(semantics, isNot(includesNodeWith(label: 'Backward Item 0')));
-    expect(semantics, includesNodeWith(label: 'Backward Item 1'));
+
+    // 'Backward Item 0' is covered by app bar.
+    expect(semantics, hasSemantics(
+      new TestSemantics.root(
+        children: <TestSemantics>[
+          new TestSemantics(
+            textDirection: TextDirection.ltr,
+            children: <TestSemantics>[
+              new TestSemantics(
+                children: <TestSemantics>[
+                  new TestSemantics(
+                    actions: <SemanticsAction>[
+                      SemanticsAction.scrollUp,
+                      SemanticsAction.scrollDown,
+                    ],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Backward Item 5',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Backward Item 4',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Backward Item 3',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Backward Item 2',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        label: 'Backward Item 1',
+                        textDirection: TextDirection.ltr,
+                      ),
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
+                        label: 'Backward Item 0',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
+                  ),
+                  new TestSemantics(
+                    tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
+                    children: <TestSemantics>[
+                      new TestSemantics(
+                        flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
+                        label: 'Backward app bar',
+                        textDirection: TextDirection.ltr,
+                      ),
+                    ],
+                  ),
+                ],
+              ),
+            ],
+          ),
+        ],
+      ), ignoreTransform: true, ignoreRect: true, ignoreId: true,
+    ));
 
     semantics.dispose();
   });
diff --git a/packages/flutter/test/widgets/slivers_appbar_floating_test.dart b/packages/flutter/test/widgets/slivers_appbar_floating_test.dart
index a35fb8b..651d273 100644
--- a/packages/flutter/test/widgets/slivers_appbar_floating_test.dart
+++ b/packages/flutter/test/widgets/slivers_appbar_floating_test.dart
@@ -70,8 +70,8 @@
     final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
 
     verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
-    verifyPaintPosition(key2, const Offset(0.0, 600.0), false);
-    verifyPaintPosition(key3, const Offset(0.0, 600.0), false);
+    verifyPaintPosition(key2, const Offset(0.0, 1000.0), false);
+    verifyPaintPosition(key3, const Offset(0.0, 1200.0), false);
 
     position.animateTo(bigHeight - 600.0 + delegate.maxExtent, curve: Curves.linear, duration: const Duration(minutes: 1));
     await tester.pumpAndSettle(const Duration(milliseconds: 1000));
@@ -142,8 +142,8 @@
     final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
 
     verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
-    verifyPaintPosition(key2, const Offset(0.0, 600.0), false);
-    verifyPaintPosition(key3, const Offset(0.0, 600.0), false);
+    verifyPaintPosition(key2, const Offset(0.0, 1000.0), false);
+    verifyPaintPosition(key3, const Offset(0.0, 1200.0), false);
 
     position.animateTo(bigHeight + delegate.maxExtent * 2.0, curve: Curves.linear, duration: const Duration(minutes: 1));
     await tester.pumpAndSettle(const Duration(milliseconds: 1000));
@@ -177,8 +177,8 @@
     final ScrollPositionWithSingleContext position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
 
     verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
-    verifyPaintPosition(key2, const Offset(0.0, 600.0), false);
-    verifyPaintPosition(key3, const Offset(0.0, 600.0), false);
+    verifyPaintPosition(key2, const Offset(0.0, 1000.0), false);
+    verifyPaintPosition(key3, const Offset(0.0, 1200.0), false);
 
     position.animateTo(bigHeight + delegate.maxExtent * 2.0, curve: Curves.linear, duration: const Duration(minutes: 1));
     await tester.pumpAndSettle(const Duration(milliseconds: 1000));
diff --git a/packages/flutter/test/widgets/slivers_appbar_pinned_test.dart b/packages/flutter/test/widgets/slivers_appbar_pinned_test.dart
index 1eba52c..24ff76f 100644
--- a/packages/flutter/test/widgets/slivers_appbar_pinned_test.dart
+++ b/packages/flutter/test/widgets/slivers_appbar_pinned_test.dart
@@ -90,9 +90,11 @@
         ' │   GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
         ' │   0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
         ' │   crossAxisDirection: AxisDirection.right,\n'
-        ' │   viewportMainAxisExtent: 600.0)\n'
+        ' │   viewportMainAxisExtent: 600.0, remainingCacheExtent: 850.0\n'
+        ' │   cacheOrigin: 0.0 )\n'
         ' │ geometry: SliverGeometry(scrollExtent: 200.0, paintExtent: 200.0,\n'
-        ' │   maxPaintExtent: 200.0, hasVisualOverflow: true)\n'
+        ' │   maxPaintExtent: 200.0, hasVisualOverflow: true, cacheExtent:\n'
+        ' │   200.0)\n'
         ' │ maxExtent: EXCEPTION (FlutterError)\n'
         ' │ child position: 0.0\n'
         ' │\n'
@@ -140,23 +142,23 @@
     final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
     verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
     verifyPaintPosition(key2, const Offset(0.0, 550.0), true);
-    verifyPaintPosition(key3, const Offset(0.0, 600.0), false);
-    verifyPaintPosition(key4, const Offset(0.0, 600.0), false);
-    verifyPaintPosition(key5, const Offset(0.0, 600.0), false);
+    verifyPaintPosition(key3, const Offset(0.0, 750.0), false);
+    verifyPaintPosition(key4, const Offset(0.0, 950.0), false);
+    verifyPaintPosition(key5, const Offset(0.0, 1500.0), false);
     position.animateTo(550.0, curve: Curves.linear, duration: const Duration(minutes: 1));
     await tester.pumpAndSettle(const Duration(milliseconds: 100));
     verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
     verifyPaintPosition(key2, const Offset(0.0, 0.0), true);
     verifyPaintPosition(key3, const Offset(0.0, 200.0), true);
     verifyPaintPosition(key4, const Offset(0.0, 400.0), true);
-    verifyPaintPosition(key5, const Offset(0.0, 600.0), false);
+    verifyPaintPosition(key5, const Offset(0.0, 950.0), false);
     position.animateTo(600.0, curve: Curves.linear, duration: const Duration(minutes: 1));
     await tester.pumpAndSettle(const Duration(milliseconds: 200));
     verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
     verifyPaintPosition(key2, const Offset(0.0, 0.0), true);
     verifyPaintPosition(key3, const Offset(0.0, 150.0), true);
     verifyPaintPosition(key4, const Offset(0.0, 350.0), true);
-    verifyPaintPosition(key5, const Offset(0.0, 600.0), false);
+    verifyPaintPosition(key5, const Offset(0.0, 900.0), false);
     position.animateTo(650.0, curve: Curves.linear, duration: const Duration(minutes: 1));
     await tester.pumpAndSettle(const Duration(milliseconds: 300));
     verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
@@ -164,7 +166,7 @@
     verifyPaintPosition(key3, const Offset(0.0, 100.0), true);
     verifyActualBoxPosition(tester, find.byType(Container), 1, new Rect.fromLTWH(0.0, 100.0, 800.0, 200.0));
     verifyPaintPosition(key4, const Offset(0.0, 300.0), true);
-    verifyPaintPosition(key5, const Offset(0.0, 600.0), false);
+    verifyPaintPosition(key5, const Offset(0.0, 850.0), false);
     position.animateTo(700.0, curve: Curves.linear, duration: const Duration(minutes: 1));
     await tester.pumpAndSettle(const Duration(milliseconds: 400));
     verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
@@ -172,7 +174,7 @@
     verifyPaintPosition(key3, const Offset(0.0, 100.0), true);
     verifyActualBoxPosition(tester, find.byType(Container), 1, new Rect.fromLTWH(0.0, 100.0, 800.0, 200.0));
     verifyPaintPosition(key4, const Offset(0.0, 250.0), true);
-    verifyPaintPosition(key5, const Offset(0.0, 600.0), false);
+    verifyPaintPosition(key5, const Offset(0.0, 800.0), false);
     position.animateTo(750.0, curve: Curves.linear, duration: const Duration(minutes: 1));
     await tester.pumpAndSettle(const Duration(milliseconds: 500));
     verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
@@ -180,21 +182,21 @@
     verifyPaintPosition(key3, const Offset(0.0, 100.0), true);
     verifyActualBoxPosition(tester, find.byType(Container), 1, new Rect.fromLTWH(0.0, 100.0, 800.0, 200.0));
     verifyPaintPosition(key4, const Offset(0.0, 200.0), true);
-    verifyPaintPosition(key5, const Offset(0.0, 600.0), false);
+    verifyPaintPosition(key5, const Offset(0.0, 750.0), false);
     position.animateTo(800.0, curve: Curves.linear, duration: const Duration(minutes: 1));
     await tester.pumpAndSettle(const Duration(milliseconds: 60));
     verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
     verifyPaintPosition(key2, const Offset(0.0, 0.0), true);
     verifyPaintPosition(key3, const Offset(0.0, 100.0), true);
     verifyPaintPosition(key4, const Offset(0.0, 150.0), true);
-    verifyPaintPosition(key5, const Offset(0.0, 600.0), false);
+    verifyPaintPosition(key5, const Offset(0.0, 700.0), false);
     position.animateTo(850.0, curve: Curves.linear, duration: const Duration(minutes: 1));
     await tester.pumpAndSettle(const Duration(milliseconds: 70));
     verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
     verifyPaintPosition(key2, const Offset(0.0, 0.0), true);
     verifyPaintPosition(key3, const Offset(0.0, 100.0), true);
     verifyPaintPosition(key4, const Offset(0.0, 100.0), true);
-    verifyPaintPosition(key5, const Offset(0.0, 600.0), false);
+    verifyPaintPosition(key5, const Offset(0.0, 650.0), false);
     position.animateTo(900.0, curve: Curves.linear, duration: const Duration(minutes: 1));
     await tester.pumpAndSettle(const Duration(milliseconds: 80));
     verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
diff --git a/packages/flutter/test/widgets/slivers_block_global_key_test.dart b/packages/flutter/test/widgets/slivers_block_global_key_test.dart
index 696d984..afcbb99 100644
--- a/packages/flutter/test/widgets/slivers_block_global_key_test.dart
+++ b/packages/flutter/test/widgets/slivers_block_global_key_test.dart
@@ -22,12 +22,15 @@
   Widget build(BuildContext context) => new Text('${widget.value}:$generation ', textDirection: TextDirection.ltr);
 }
 
+// Creates a SliverList with `keys.length` children and each child having a key from `keys` and a text of `key:generation`.
+// The generation is increased with every call to this method.
 Future<Null> test(WidgetTester tester, double offset, List<int> keys) {
   globalGeneration += 1;
   return tester.pumpWidget(
     new Directionality(
       textDirection: TextDirection.ltr,
       child: new Viewport(
+        cacheExtent: 0.0,
         offset: new ViewportOffset.fixed(offset),
         slivers: <Widget>[
           new SliverList(
@@ -41,6 +44,8 @@
   );
 }
 
+// `answerKey`: Expected offsets of visible SliverList children in global coordinate system.
+// `text`: A space-separated list of expected `key:generation` pairs for the visible SliverList children.
 void verify(WidgetTester tester, List<Offset> answerKey, String text) {
   final List<Offset> testAnswers = tester.renderObjectList<RenderBox>(find.byType(SizedBox)).map<Offset>(
     (RenderBox target) => target.localToGlobal(const Offset(0.0, 0.0))
@@ -48,8 +53,8 @@
   expect(testAnswers, equals(answerKey));
   final String foundText =
     tester.widgetList<Text>(find.byType(Text))
-    .map<String>((Text widget) => widget.data)
-    .reduce((String value, String element) => value + element);
+        .map<String>((Text widget) => widget.data)
+        .reduce((String value, String element) => value + element);
   expect(foundText, equals(text));
 }
 
diff --git a/packages/flutter/test/widgets/slivers_evil_test.dart b/packages/flutter/test/widgets/slivers_evil_test.dart
index 214031e..3443e0e 100644
--- a/packages/flutter/test/widgets/slivers_evil_test.dart
+++ b/packages/flutter/test/widgets/slivers_evil_test.dart
@@ -198,6 +198,7 @@
   testWidgets('Removing offscreen items above and rescrolling does not crash', (WidgetTester tester) async {
     await tester.pumpWidget(new MaterialApp(
       home: new CustomScrollView(
+        cacheExtent: 0.0,
         slivers: <Widget>[
           new SliverFixedExtentList(
             itemExtent: 100.0,
@@ -225,6 +226,7 @@
     // Stop returning the first 3 items.
     await tester.pumpWidget(new MaterialApp(
       home: new CustomScrollView(
+        cacheExtent: 0.0,
         slivers: <Widget>[
           new SliverFixedExtentList(
             itemExtent: 100.0,
diff --git a/packages/flutter/test/widgets/slivers_padding_test.dart b/packages/flutter/test/widgets/slivers_padding_test.dart
index e566999..d016e10 100644
--- a/packages/flutter/test/widgets/slivers_padding_test.dart
+++ b/packages/flutter/test/widgets/slivers_padding_test.dart
@@ -27,7 +27,7 @@
 }
 
 void verify(WidgetTester tester, List<Rect> answerKey) {
-  final List<Rect> testAnswers = tester.renderObjectList<RenderBox>(find.byType(SizedBox)).map<Rect>(
+  final List<Rect> testAnswers = tester.renderObjectList<RenderBox>(find.byType(SizedBox, skipOffstage: false)).map<Rect>(
     (RenderBox target) {
       final Offset topLeft = target.localToGlobal(Offset.zero);
       final Offset bottomRight = target.localToGlobal(target.size.bottomRight(Offset.zero));
@@ -45,14 +45,14 @@
     verify(tester, <Rect>[
       new Rect.fromLTWH(0.0, 0.0, 800.0, 400.0),
       new Rect.fromLTWH(25.0, 420.0, 760.0, 400.0),
-      new Rect.fromLTWH(0.0, 600.0, 800.0, 400.0),
+      new Rect.fromLTWH(0.0, 855.0, 800.0, 400.0),
     ]);
 
     await test(tester, 200.0, padding, AxisDirection.down, TextDirection.ltr);
     verify(tester, <Rect>[
       new Rect.fromLTWH(0.0, -200.0, 800.0, 400.0),
       new Rect.fromLTWH(25.0, 220.0, 760.0, 400.0),
-      new Rect.fromLTWH(0.0, 600.0, 800.0, 400.0),
+      new Rect.fromLTWH(0.0, 655.0, 800.0, 400.0),
     ]);
 
     await test(tester, 390.0, padding, AxisDirection.down, TextDirection.ltr);
@@ -84,14 +84,14 @@
     verify(tester, <Rect>[
       new Rect.fromLTWH(0.0, 0.0, 800.0, 400.0),
       new Rect.fromLTWH(25.0, 420.0, 760.0, 400.0),
-      new Rect.fromLTWH(0.0, 600.0, 800.0, 400.0),
+      new Rect.fromLTWH(0.0, 855.0, 800.0, 400.0),
     ]);
 
     await test(tester, 200.0, padding, AxisDirection.down, TextDirection.ltr);
     verify(tester, <Rect>[
       new Rect.fromLTWH(0.0, -200.0, 800.0, 400.0),
       new Rect.fromLTWH(25.0, 220.0, 760.0, 400.0),
-      new Rect.fromLTWH(0.0, 600.0, 800.0, 400.0),
+      new Rect.fromLTWH(0.0, 655.0, 800.0, 400.0),
     ]);
 
     await test(tester, 390.0, padding, AxisDirection.down, TextDirection.ltr);
@@ -123,14 +123,14 @@
     verify(tester, <Rect>[
       new Rect.fromLTWH(0.0, 0.0, 800.0, 400.0),
       new Rect.fromLTWH(15.0, 420.0, 760.0, 400.0),
-      new Rect.fromLTWH(0.0, 600.0, 800.0, 400.0),
+      new Rect.fromLTWH(0.0, 855.0, 800.0, 400.0),
     ]);
 
     await test(tester, 200.0, padding, AxisDirection.down, TextDirection.rtl);
     verify(tester, <Rect>[
       new Rect.fromLTWH(0.0, -200.0, 800.0, 400.0),
       new Rect.fromLTWH(15.0, 220.0, 760.0, 400.0),
-      new Rect.fromLTWH(0.0, 600.0, 800.0, 400.0),
+      new Rect.fromLTWH(0.0, 655.0, 800.0, 400.0),
     ]);
 
     await test(tester, 390.0, padding, AxisDirection.down, TextDirection.rtl);
@@ -363,6 +363,7 @@
       return new Directionality(
         textDirection: TextDirection.ltr,
         child: new CustomScrollView(
+          cacheExtent: 0.0,
           slivers: <Widget>[
             new SliverPadding(
               padding: EdgeInsets.zero,
diff --git a/packages/flutter/test/widgets/slivers_test.dart b/packages/flutter/test/widgets/slivers_test.dart
index c7ce26b..7fafa8c 100644
--- a/packages/flutter/test/widgets/slivers_test.dart
+++ b/packages/flutter/test/widgets/slivers_test.dart
@@ -26,10 +26,10 @@
 }
 
 void verify(WidgetTester tester, List<Offset> idealPositions, List<bool> idealVisibles) {
-  final List<Offset> actualPositions = tester.renderObjectList<RenderBox>(find.byType(SizedBox)).map<Offset>(
+  final List<Offset> actualPositions = tester.renderObjectList<RenderBox>(find.byType(SizedBox, skipOffstage: false)).map<Offset>(
     (RenderBox target) => target.localToGlobal(const Offset(0.0, 0.0))
   ).toList();
-  final List<bool> actualVisibles = tester.renderObjectList<RenderSliverToBoxAdapter>(find.byType(SliverToBoxAdapter)).map<bool>(
+  final List<bool> actualVisibles = tester.renderObjectList<RenderSliverToBoxAdapter>(find.byType(SliverToBoxAdapter, skipOffstage: false)).map<bool>(
     (RenderSliverToBoxAdapter target) => target.geometry.visible
   ).toList();
   expect(actualPositions, equals(idealPositions));
@@ -43,9 +43,9 @@
     verify(tester, <Offset>[
       const Offset(0.0, 0.0),
       const Offset(0.0, 400.0),
-      const Offset(0.0, 600.0),
-      const Offset(0.0, 600.0),
-      const Offset(0.0, 600.0),
+      const Offset(0.0, 800.0),
+      const Offset(0.0, 1200.0),
+      const Offset(0.0, 1600.0),
     ], <bool>[true, true, false, false, false]);
 
     await test(tester, 200.0);
@@ -53,8 +53,8 @@
       const Offset(0.0, -200.0),
       const Offset(0.0, 200.0),
       const Offset(0.0, 600.0),
-      const Offset(0.0, 600.0),
-      const Offset(0.0, 600.0),
+      const Offset(0.0, 1000.0),
+      const Offset(0.0, 1400.0),
     ], <bool>[true, true, false, false, false]);
 
     await test(tester, 600.0);
@@ -63,7 +63,7 @@
       const Offset(0.0, -200.0),
       const Offset(0.0, 200.0),
       const Offset(0.0, 600.0),
-      const Offset(0.0, 600.0),
+      const Offset(0.0, 1000.0),
     ], <bool>[false, true, true, false, false]);
 
     await test(tester, 900.0);
@@ -72,7 +72,7 @@
       const Offset(0.0, -500.0),
       const Offset(0.0, -100.0),
       const Offset(0.0, 300.0),
-      const Offset(0.0, 600.0),
+      const Offset(0.0, 700.0),
     ], <bool>[false, false, true, true, false]);
   });
 
@@ -82,18 +82,18 @@
     verify(tester, <Offset>[
       const Offset(0.0, 100.0),
       const Offset(0.0, 500.0),
-      const Offset(0.0, 600.0),
-      const Offset(0.0, 600.0),
-      const Offset(0.0, 600.0),
+      const Offset(0.0, 900.0),
+      const Offset(0.0, 1300.0),
+      const Offset(0.0, 1700.0),
     ], <bool>[true, true, false, false, false]);
 
     await test(tester, 200.0, anchor: 100.0);
     verify(tester, <Offset>[
       const Offset(0.0, -100.0),
       const Offset(0.0, 300.0),
-      const Offset(0.0, 600.0),
-      const Offset(0.0, 600.0),
-      const Offset(0.0, 600.0),
+      const Offset(0.0, 700.0),
+      const Offset(0.0, 1100.0),
+      const Offset(0.0, 1500.0),
     ], <bool>[true, true, false, false, false]);
 
     await test(tester, 600.0, anchor: 100.0);
@@ -101,8 +101,8 @@
       const Offset(0.0, -500.0),
       const Offset(0.0, -100.0),
       const Offset(0.0, 300.0),
-      const Offset(0.0, 600.0),
-      const Offset(0.0, 600.0),
+      const Offset(0.0, 700.0),
+      const Offset(0.0, 1100.0),
     ], <bool>[false, true, true, false, false]);
 
     await test(tester, 900.0, anchor: 100.0);
@@ -111,7 +111,7 @@
       const Offset(0.0, -400.0),
       const Offset(0.0, 0.0),
       const Offset(0.0, 400.0),
-      const Offset(0.0, 600.0),
+      const Offset(0.0, 800.0),
     ], <bool>[false, false, true, true, false]);
   });
 
diff --git a/packages/flutter_localizations/test/basics_test.dart b/packages/flutter_localizations/test/basics_test.dart
index 18c41a0..20a4518 100644
--- a/packages/flutter_localizations/test/basics_test.dart
+++ b/packages/flutter_localizations/test/basics_test.dart
@@ -21,9 +21,9 @@
       ),
     ));
 
-    final LocalizationTrackerState outerTracker = tester.state(find.byKey(const ValueKey<String>('outer')));
+    final LocalizationTrackerState outerTracker = tester.state(find.byKey(const ValueKey<String>('outer'), skipOffstage: false));
     expect(outerTracker.captionFontSize, 12.0);
-    final LocalizationTrackerState innerTracker = tester.state(find.byKey(const ValueKey<String>('inner')));
+    final LocalizationTrackerState innerTracker = tester.state(find.byKey(const ValueKey<String>('inner'), skipOffstage: false));
     expect(innerTracker.captionFontSize, 13.0);
   });
 
diff --git a/packages/flutter_test/lib/src/finders.dart b/packages/flutter_test/lib/src/finders.dart
index 615c905..ed65a00 100644
--- a/packages/flutter_test/lib/src/finders.dart
+++ b/packages/flutter_test/lib/src/finders.dart
@@ -55,8 +55,8 @@
   /// nodes that are [Offstage] or that are from inactive [Route]s.
   Finder widgetWithText(Type widgetType, String text, { bool skipOffstage: true }) {
     return find.ancestor(
-      of: find.text(text),
-      matching: find.byType(widgetType),
+      of: find.text(text, skipOffstage: skipOffstage),
+      matching: find.byType(widgetType, skipOffstage: skipOffstage),
     );
   }