Bug 1144096 part 16 - [css-grid] Implement fragmentation. r=dholbert
authorMats Palmgren <mats@mozilla.com>
Fri, 11 Mar 2016 17:39:27 +0100
changeset 288308 40456ea738607ff6668d3c22531c682984ef6d68
parent 288307 c5fd95723f2e70d51a3638534d7039f353b45435
child 288309 b52d71c48531a2dc78d198dd99a23e79fbbd0a81
push id30079
push userryanvm@gmail.com
push dateSat, 12 Mar 2016 20:24:19 +0000
treeherdermozilla-central@d1d47ba19ce9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert
bugs1144096
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1144096 part 16 - [css-grid] Implement fragmentation. r=dholbert
layout/generic/nsGridContainerFrame.cpp
layout/generic/nsGridContainerFrame.h
--- a/layout/generic/nsGridContainerFrame.cpp
+++ b/layout/generic/nsGridContainerFrame.cpp
@@ -27,16 +27,17 @@
 #include "nsStyleContext.h"
 
 using namespace mozilla;
 typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags;
 typedef nsGridContainerFrame::TrackSize TrackSize;
 const uint32_t nsGridContainerFrame::kTranslatedMaxLine =
   uint32_t(nsStyleGridLine::kMaxLine - nsStyleGridLine::kMinLine);
 const uint32_t nsGridContainerFrame::kAutoLine = kTranslatedMaxLine + 3457U;
+typedef nsTHashtable< nsPtrHashKey<nsIFrame> > FrameHashtable;
 
 static void
 ReparentFrame(nsIFrame* aFrame, nsContainerFrame* aOldParent,
               nsContainerFrame* aNewParent)
 {
   NS_ASSERTION(aOldParent == aFrame->GetParent(),
                "Parent not consistent with expectations");
 
@@ -51,16 +52,49 @@ static void
 ReparentFrames(nsFrameList& aFrameList, nsContainerFrame* aOldParent,
                nsContainerFrame* aNewParent)
 {
   for (auto f : aFrameList) {
     ReparentFrame(f, aOldParent, aNewParent);
   }
 }
 
+static nscoord
+ClampToCSSMaxBSize(nscoord aSize, const nsHTMLReflowState* aReflowState)
+{
+  auto maxSize = aReflowState->ComputedMaxBSize();
+  if (MOZ_UNLIKELY(maxSize != NS_UNCONSTRAINEDSIZE)) {
+    MOZ_ASSERT(aReflowState->ComputedMinBSize() <= maxSize);
+    aSize = std::min(aSize, maxSize);
+  }
+  return aSize;
+}
+
+// Same as above and set aStatus INCOMPLETE if aSize wasn't clamped.
+// (If we clamp aSize it means our size is less than the break point,
+// i.e. we're effectively breaking in our overflow, so we should leave
+// aStatus as is (it will likely be set to OVERFLOW_INCOMPLETE later)).
+static nscoord
+ClampToCSSMaxBSize(nscoord aSize, const nsHTMLReflowState* aReflowState,
+                   nsReflowStatus* aStatus)
+{
+  auto maxSize = aReflowState->ComputedMaxBSize();
+  if (MOZ_UNLIKELY(maxSize != NS_UNCONSTRAINEDSIZE)) {
+    MOZ_ASSERT(aReflowState->ComputedMinBSize() <= maxSize);
+    if (aSize < maxSize) {
+      NS_FRAME_SET_INCOMPLETE(*aStatus);
+    } else {
+      aSize = maxSize;
+    }
+  } else {
+    NS_FRAME_SET_INCOMPLETE(*aStatus);
+  }
+  return aSize;
+}
+
 enum class GridLineSide
 {
   eBeforeGridGap,
   eAfterGridGap,
 };
 
 struct nsGridContainerFrame::TrackSize
 {
@@ -560,16 +594,21 @@ struct nsGridContainerFrame::GridItemInf
                         const GridArea& aArea)
     : mFrame(aFrame)
     , mArea(aArea)
   {
     mIsFlexing[0] = false;
     mIsFlexing[1] = false;
   }
 
+  static bool IsStartRowLessThan(const GridItemInfo* a, const GridItemInfo* b)
+  {
+    return a->mArea.mRows.mStart < b->mArea.mRows.mStart;
+  }
+
   nsIFrame* const mFrame;
   GridArea mArea;
   bool mIsFlexing[2]; // does the item span a flex track? (LogicalAxis index)
   static_assert(mozilla::eLogicalAxisBlock == 0, "unexpected index value");
   static_assert(mozilla::eLogicalAxisInline == 1, "unexpected index value");
 };
 
 /**
@@ -4178,17 +4217,498 @@ nsGridContainerFrame::ReflowInFlowChild(
     childPos -= padStart;
   }
   childRS->ApplyRelativePositioning(&childPos, aContainerSize);
   FinishReflowChild(aChild, pc, *childSize, childRS.ptr(), childWM, childPos,
                     aContainerSize, 0);
   ConsiderChildOverflow(aDesiredSize.mOverflowAreas, aChild);
 }
 
-void
+nscoord
+nsGridContainerFrame::ReflowInFragmentainer(GridReflowState&     aState,
+                                            const LogicalRect&   aContentArea,
+                                            nsHTMLReflowMetrics& aDesiredSize,
+                                            nsReflowStatus&      aStatus,
+                                            Fragmentainer&       aFragmentainer,
+                                            const nsSize&        aContainerSize)
+{
+  MOZ_ASSERT(aStatus == NS_FRAME_COMPLETE);
+  MOZ_ASSERT(aState.mReflowState);
+
+  // Collect our grid items and sort them in row order.  Collect placeholders
+  // and put them in a separate array.
+  nsTArray<const GridItemInfo*> sortedItems(aState.mGridItems.Length());
+  nsTArray<nsIFrame*> placeholders(aState.mAbsPosItems.Length());
+  aState.mIter.Reset(GridItemCSSOrderIterator::eIncludeAll);
+  for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
+    nsIFrame* child = *aState.mIter;
+    if (child->GetType() != nsGkAtoms::placeholderFrame) {
+      const GridItemInfo* info = &aState.mGridItems[aState.mIter.GridItemIndex()];
+      sortedItems.AppendElement(info);
+    } else {
+      placeholders.AppendElement(child);
+    }
+  }
+  // NOTE: no need to use stable_sort here, there are no dependencies on
+  // having content order between items on the same row in the code below.
+  std::sort(sortedItems.begin(), sortedItems.end(),
+            GridItemInfo::IsStartRowLessThan);
+
+  // Reflow our placeholder children; they must all be complete.
+  for (auto child : placeholders) {
+    nsReflowStatus childStatus;
+    ReflowInFlowChild(child, nullptr, aContainerSize, &aFragmentainer,
+                      aState, aContentArea, aDesiredSize, childStatus);
+    MOZ_ASSERT(NS_FRAME_IS_COMPLETE(childStatus),
+               "nsPlaceholderFrame should never need to be fragmented");
+  }
+
+  // The available size for children - we'll set this to the edge of the last
+  // row in most cases below, but for now use the full size.
+  nscoord childAvailableSize = aFragmentainer.mToFragmentainerEnd;
+  const uint32_t startRow = aState.mStartRow;
+  const uint32_t numRows = aState.mRows.mSizes.Length();
+  bool isBDBClone = aState.mReflowState->mStyleBorder->mBoxDecorationBreak ==
+                      NS_STYLE_BOX_DECORATION_BREAK_CLONE;
+  nscoord bpBEnd = aState.mBorderPadding.BEnd(aState.mWM);
+
+  // Set |endRow| to the first row that doesn't fit.
+  uint32_t endRow = numRows;
+  for (uint32_t row = startRow; row < numRows; ++row) {
+    auto& sz = aState.mRows.mSizes[row];
+    const nscoord bEnd = sz.mPosition + sz.mBase;
+    nscoord remainingAvailableSize = childAvailableSize - bEnd;
+    if (remainingAvailableSize < 0 ||
+        (isBDBClone && remainingAvailableSize < bpBEnd)) {
+      endRow = row;
+      break;
+    }
+  }
+
+  // Check for forced breaks on the items.
+  const bool isTopOfPage = aFragmentainer.mIsTopOfPage;
+  bool isForcedBreak = false;
+  const bool avoidBreakInside = ShouldAvoidBreakInside(*aState.mReflowState);
+  for (const GridItemInfo* info : sortedItems) {
+    uint32_t itemStartRow = info->mArea.mRows.mStart;
+    if (itemStartRow == endRow) {
+      break;
+    }
+    auto disp = info->mFrame->StyleDisplay();
+    if (disp->mBreakBefore) {
+      // Propagate break-before on the first row to the container unless we're
+      // already at top-of-page.
+      if ((itemStartRow == 0 && !isTopOfPage) || avoidBreakInside) {
+        aStatus = NS_INLINE_LINE_BREAK_BEFORE();
+        return aState.mFragBStart;
+      }
+      if ((itemStartRow > startRow ||
+           (itemStartRow == startRow && !isTopOfPage)) &&
+          itemStartRow < endRow) {
+        endRow = itemStartRow;
+        isForcedBreak = true;
+        // reset any BREAK_AFTER we found on an earlier item
+        aStatus = NS_FRAME_COMPLETE;
+        break;  // we're done since the items are sorted in row order
+      }
+    }
+    uint32_t itemEndRow = info->mArea.mRows.mEnd;
+    if (disp->mBreakAfter) {
+      if (itemEndRow != numRows) {
+        if (itemEndRow > startRow && itemEndRow < endRow) {
+          endRow = itemEndRow;
+          isForcedBreak = true;
+          // No "break;" here since later items with break-after may have
+          // a shorter span.
+        }
+      } else {
+        // Propagate break-after on the last row to the container, we may still
+        // find a break-before on this row though (and reset aStatus).
+        aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus); // tentative
+      }
+    }
+  }
+
+  // Consume at least one row in each fragment until we have consumed them all.
+  // Except for the first row if there's a break opportunity before it.
+  if (startRow == endRow && startRow != numRows &&
+      (startRow != 0 || !aFragmentainer.mCanBreakAtStart)) {
+    ++endRow;
+  }
+
+  // Honor break-inside:avoid if we can't fit all rows.
+  if (avoidBreakInside && endRow < numRows) {
+    aStatus = NS_INLINE_LINE_BREAK_BEFORE();
+    return aState.mFragBStart;
+  }
+
+  // Calculate the block-size including this fragment.
+  nscoord bEndRow =
+    aState.mRows.GridLineEdge(endRow, GridLineSide::eBeforeGridGap);
+  nscoord bSize;
+  if (aFragmentainer.mIsAutoBSize) {
+    // We only apply min-bsize once all rows are complete (when bsize is auto).
+    if (endRow < numRows) {
+      bSize = bEndRow;
+      auto clampedBSize = ClampToCSSMaxBSize(bSize, aState.mReflowState);
+      if (MOZ_UNLIKELY(clampedBSize != bSize)) {
+        // We apply max-bsize in all fragments though.
+        bSize = clampedBSize;
+      } else if (!isBDBClone) {
+        // The max-bsize won't make this fragment COMPLETE, so the block-end
+        // border will be in a later fragment.
+        bpBEnd = 0;
+      }
+    } else {
+      bSize = NS_CSS_MINMAX(bEndRow,
+                            aState.mReflowState->ComputedMinBSize(),
+                            aState.mReflowState->ComputedMaxBSize());
+    }
+  } else {
+    bSize = NS_CSS_MINMAX(aState.mReflowState->ComputedBSize(),
+                          aState.mReflowState->ComputedMinBSize(),
+                          aState.mReflowState->ComputedMaxBSize());
+  }
+
+  // Check for overflow and set aStatus INCOMPLETE if so.
+  bool overflow = bSize + bpBEnd > childAvailableSize;
+  if (overflow) {
+    if (avoidBreakInside) {
+      aStatus = NS_INLINE_LINE_BREAK_BEFORE();
+      return aState.mFragBStart;
+    }
+    bool breakAfterLastRow = endRow == numRows && aFragmentainer.mCanBreakAtEnd;
+    if (breakAfterLastRow) {
+      MOZ_ASSERT(bEndRow < bSize, "bogus aFragmentainer.mCanBreakAtEnd");
+      nscoord availableSize = childAvailableSize;
+      if (isBDBClone) {
+        availableSize -= bpBEnd;
+      }
+      // Pretend we have at least 1px available size, otherwise we'll never make
+      // progress in consuming our bSize.
+      availableSize = std::max(availableSize,
+                               aState.mFragBStart + AppUnitsPerCSSPixel());
+      // Fill the fragmentainer, but not more than our desired block-size and
+      // at least to the size of the last row (even if that overflows).
+      nscoord newBSize = std::min(bSize, availableSize);
+      newBSize = std::max(newBSize, bEndRow);
+      // If it's just the border+padding that is overflowing and we have
+      // box-decoration-break:clone then we are technically COMPLETE.  There's
+      // no point in creating another zero-bsize fragment in this case.
+      if (newBSize < bSize || !isBDBClone) {
+        NS_FRAME_SET_INCOMPLETE(aStatus);
+      }
+      bSize = newBSize;
+    } else if (bSize <= bEndRow && startRow + 1 < endRow) {
+      if (endRow == numRows) {
+        // We have more than one row in this fragment, so we can break before
+        // the last row instead.
+        --endRow;
+        bEndRow = aState.mRows.GridLineEdge(endRow, GridLineSide::eBeforeGridGap);
+        bSize = bEndRow;
+        if (aFragmentainer.mIsAutoBSize) {
+          bSize = ClampToCSSMaxBSize(bSize, aState.mReflowState);
+        }
+      }
+      NS_FRAME_SET_INCOMPLETE(aStatus);
+    } else if (endRow < numRows) {
+      bSize = ClampToCSSMaxBSize(bEndRow, aState.mReflowState, &aStatus);
+    } // else - no break opportunities.
+  } else {
+    // Even though our block-size fits we need to honor forced breaks, or if
+    // a row doesn't fit in an auto-sized container (unless it's constrained
+    // by a max-bsize which make us overflow-incomplete).
+    if (endRow < numRows && (isForcedBreak ||
+                             (aFragmentainer.mIsAutoBSize && bEndRow == bSize))) {
+      bSize = ClampToCSSMaxBSize(bEndRow, aState.mReflowState, &aStatus);
+    }
+  }
+
+  // If we can't fit all rows then we're at least overflow-incomplete.
+  if (endRow < numRows) {
+    childAvailableSize = bEndRow;
+    if (NS_FRAME_IS_COMPLETE(aStatus)) {
+      NS_FRAME_SET_OVERFLOW_INCOMPLETE(aStatus);
+      aStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
+    }
+  } else {
+    // Children always have the full size of the rows in this fragment.
+    childAvailableSize = std::max(childAvailableSize, bEndRow);
+  }
+
+  return ReflowRowsInFragmentainer(aState, aContentArea, aDesiredSize, aStatus,
+                                   aFragmentainer, aContainerSize, sortedItems,
+                                   startRow, endRow, bSize, childAvailableSize);
+}
+
+nscoord
+nsGridContainerFrame::ReflowRowsInFragmentainer(
+  GridReflowState&                     aState,
+  const LogicalRect&                   aContentArea,
+  nsHTMLReflowMetrics&                 aDesiredSize,
+  nsReflowStatus&                      aStatus,
+  Fragmentainer&                       aFragmentainer,
+  const nsSize&                        aContainerSize,
+  const nsTArray<const GridItemInfo*>& aSortedItems,
+  uint32_t                             aStartRow,
+  uint32_t                             aEndRow,
+  nscoord                              aBSize,
+  nscoord                              aAvailableSize)
+{
+  FrameHashtable pushedItems;
+  FrameHashtable incompleteItems;
+  FrameHashtable overflowIncompleteItems;
+  bool isBDBClone = aState.mReflowState->mStyleBorder->mBoxDecorationBreak ==
+                      NS_STYLE_BOX_DECORATION_BREAK_CLONE;
+  bool didGrowRow = false;
+  // As we walk across rows, we track whether the current row is at the top
+  // of its grid-fragment, to help decide whether we can break before it. When
+  // this function starts, our row is at the top of the current fragment if:
+  //  - we're starting with a nonzero row (i.e. we're a continuation)
+  // OR:
+  //  - we're starting with the first row, & we're not allowed to break before
+  //    it (which makes it effectively at the top of its grid-fragment).
+  bool isRowTopOfPage = aStartRow != 0 || !aFragmentainer.mCanBreakAtStart;
+  const bool isStartRowTopOfPage = isRowTopOfPage;
+  // Save our full available size for later.
+  const nscoord gridAvailableSize = aFragmentainer.mToFragmentainerEnd;
+  // Propagate the constrained size to our children.
+  aFragmentainer.mToFragmentainerEnd = aAvailableSize;
+  // Reflow the items in row order up to |aEndRow| and push items after that.
+  uint32_t row = 0;
+  // |i| is intentionally signed, so we can set it to -1 to restart the loop.
+  for (int32_t i = 0, len = aSortedItems.Length(); i < len; ++i) {
+    const GridItemInfo* const info = aSortedItems[i];
+    nsIFrame* child = info->mFrame;
+    row = info->mArea.mRows.mStart;
+    MOZ_ASSERT(child->GetPrevInFlow() ? row < aStartRow : row >= aStartRow,
+               "unexpected child start row");
+    if (row >= aEndRow) {
+      pushedItems.PutEntry(child);
+      continue;
+    }
+
+    bool rowCanGrow = false;
+    nscoord maxRowSize = 0;
+    if (row >= aStartRow) {
+      if (row > aStartRow) {
+        isRowTopOfPage = false;
+      }
+      // Can we grow this row?  Only consider span=1 items per spec...
+      rowCanGrow = !didGrowRow && info->mArea.mRows.Extent() == 1;
+      if (rowCanGrow) {
+        auto& sz = aState.mRows.mSizes[row];
+        // and only min-/max-content rows or flex rows in an auto-sized container
+        rowCanGrow = (sz.mState & TrackSize::eMinOrMaxContentMinSizing) ||
+                     ((sz.mState & TrackSize::eFlexMaxSizing) &&
+                      aFragmentainer.mIsAutoBSize);
+        if (rowCanGrow) {
+          if (isBDBClone) {
+            maxRowSize = gridAvailableSize -
+                         aState.mBorderPadding.BEnd(aState.mWM);
+          } else {
+            maxRowSize = gridAvailableSize;
+          }
+          maxRowSize -= sz.mPosition;
+          // ...and only if there is space for it to grow.
+          rowCanGrow = maxRowSize > sz.mBase;
+        }
+      }
+    }
+
+    // aFragmentainer.mIsTopOfPage is propagated to the child reflow state.
+    // When it's false the child can request BREAK_BEFORE.  We intentionally
+    // set it to false when the row is growable (as determined in CSS Grid
+    // Fragmentation) and there is a non-zero space between it and the
+    // fragmentainer end (that can be used to grow it).  If the child reports
+    // a forced break in this case, we grow this row to fill the fragment and
+    // restart the loop.  We also restart the loop with |aEndRow = row|
+    // (but without growing any row) for a BREAK_BEFORE child if it spans
+    // beyond the last row in this fragment.  This is to avoid fragmenting it.
+    // We only restart the loop once.
+    aFragmentainer.mIsTopOfPage = isRowTopOfPage && !rowCanGrow;
+    nsReflowStatus childStatus;
+    ReflowInFlowChild(child, info, aContainerSize, &aFragmentainer,
+                      aState, aContentArea, aDesiredSize, childStatus);
+    MOZ_ASSERT(!NS_FRAME_IS_FULLY_COMPLETE(childStatus) ||
+               !child->GetNextInFlow(),
+               "fully-complete reflow should destroy any NIFs");
+
+    if (NS_INLINE_IS_BREAK_BEFORE(childStatus)) {
+      MOZ_ASSERT(!child->GetPrevInFlow(),
+                 "continuations should never report BREAK_BEFORE status");
+      MOZ_ASSERT(!aFragmentainer.mIsTopOfPage,
+                 "got NS_INLINE_IS_BREAK_BEFORE at top of page");
+      if (!didGrowRow) {
+        if (rowCanGrow) {
+          // Grow this row and restart with the next row as |aEndRow|.
+          aState.mRows.ResizeRow(row, maxRowSize);
+          if (aState.mSharedGridData) {
+            aState.mSharedGridData->mRows.ResizeRow(row, maxRowSize);
+          }
+          didGrowRow = true;
+          aEndRow = row + 1;  // growing this row makes the next one not fit
+          i = -1;  // i == 0 after the next loop increment
+          isRowTopOfPage = isStartRowTopOfPage;
+          overflowIncompleteItems.Clear();
+          incompleteItems.Clear();
+          nscoord bEndRow =
+            aState.mRows.GridLineEdge(aEndRow, GridLineSide::eBeforeGridGap);
+          aFragmentainer.mToFragmentainerEnd = bEndRow;
+          if (aFragmentainer.mIsAutoBSize) {
+            aBSize = ClampToCSSMaxBSize(bEndRow, aState.mReflowState, &aStatus);
+          } else if (NS_FRAME_IS_NOT_COMPLETE(aStatus)) {
+            aBSize = NS_CSS_MINMAX(aState.mReflowState->ComputedBSize(),
+                                   aState.mReflowState->ComputedMinBSize(),
+                                   aState.mReflowState->ComputedMaxBSize());
+            aBSize = std::min(bEndRow, aBSize);
+          }
+          continue;
+        }
+
+        if (!isRowTopOfPage) {
+          // We can break before this row - restart with it as the new end row.
+          aEndRow = row;
+          aBSize = aState.mRows.GridLineEdge(aEndRow, GridLineSide::eBeforeGridGap);
+          i = -1;  // i == 0 after the next loop increment
+          isRowTopOfPage = isStartRowTopOfPage;
+          overflowIncompleteItems.Clear();
+          incompleteItems.Clear();
+          NS_FRAME_SET_INCOMPLETE(aStatus);
+          continue;
+        }
+        NS_ERROR("got BREAK_BEFORE at top-of-page");
+        childStatus = NS_FRAME_COMPLETE;
+      } else {
+        NS_ERROR("got BREAK_BEFORE again after growing the row?");
+        NS_FRAME_SET_INCOMPLETE(childStatus);
+      }
+    } else if (NS_INLINE_IS_BREAK_AFTER(childStatus)) {
+      MOZ_ASSERT_UNREACHABLE("unexpected child reflow status");
+    }
+
+    if (NS_FRAME_IS_NOT_COMPLETE(childStatus)) {
+      incompleteItems.PutEntry(child);
+    } else if (!NS_FRAME_IS_FULLY_COMPLETE(childStatus)) {
+      overflowIncompleteItems.PutEntry(child);
+    }
+  }
+
+  // Record a break before |aEndRow|.
+  if (aEndRow < aState.mRows.mSizes.Length()) {
+    aState.mRows.BreakBeforeRow(aEndRow);
+    if (aState.mSharedGridData) {
+      aState.mSharedGridData->mRows.BreakBeforeRow(aEndRow);
+    }
+  }
+
+  if (!pushedItems.IsEmpty() ||
+      !incompleteItems.IsEmpty() ||
+      !overflowIncompleteItems.IsEmpty()) {
+    if (NS_FRAME_IS_COMPLETE(aStatus)) {
+      NS_FRAME_SET_OVERFLOW_INCOMPLETE(aStatus);
+      aStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
+    }
+    // Iterate the children in normal document order and append them (or a NIF)
+    // to one of the following frame lists according to their status.
+    nsFrameList pushedList;
+    nsFrameList incompleteList;
+    nsFrameList overflowIncompleteList;
+    auto* pc = PresContext();
+    auto* fc = pc->PresShell()->FrameConstructor();
+    for (nsIFrame* child = GetChildList(kPrincipalList).FirstChild(); child; ) {
+      MOZ_ASSERT((pushedItems.Contains(child) ? 1 : 0) +
+                 (incompleteItems.Contains(child) ? 1 : 0) +
+                 (overflowIncompleteItems.Contains(child) ? 1 : 0) <= 1,
+                 "child should only be in one of these sets");
+      // Save the next-sibling so we can continue the loop if |child| is moved.
+      nsIFrame* next = child->GetNextSibling();
+      if (pushedItems.Contains(child)) {
+        MOZ_ASSERT(child->GetParent() == this);
+        StealFrame(child);
+        pushedList.AppendFrame(nullptr, child);
+      } else if (incompleteItems.Contains(child)) {
+        nsIFrame* childNIF = child->GetNextInFlow();
+        if (!childNIF) {
+          childNIF = fc->CreateContinuingFrame(pc, child, this);
+          incompleteList.AppendFrame(nullptr, childNIF);
+        } else {
+          MOZ_ASSERT(childNIF->GetParent() != this ||
+                     !mFrames.ContainsFrame(childNIF),
+                     "child's NIF shouldn't be in the same principal list");
+          // If child's existing NIF is an overflow container, convert it to an
+          // actual NIF, since now |child| has non-overflow stuff to give it.
+          if (childNIF->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) {
+            auto parent = childNIF->GetParent();
+            parent->StealFrame(childNIF);
+            if (parent != this) {
+              ReparentFrame(childNIF, parent, this);
+            }
+            childNIF->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
+            incompleteList.AppendFrame(nullptr, childNIF);
+          }
+        }
+      } else if (overflowIncompleteItems.Contains(child)) {
+        nsIFrame* childNIF = child->GetNextInFlow();
+        if (!childNIF) {
+          childNIF = fc->CreateContinuingFrame(pc, child, this);
+          childNIF->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
+          overflowIncompleteList.AppendFrame(nullptr, childNIF);
+        } else {
+          // If child has any non-overflow-container NIFs, convert them to
+          // overflow containers, since that's all |child| needs now.
+          while (childNIF &&
+                 !childNIF->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+            auto parent = childNIF->GetParent();
+            parent->StealFrame(childNIF);
+            if (parent != this) {
+              ReparentFrame(childNIF, parent, this);
+            }
+            childNIF->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
+            overflowIncompleteList.AppendFrame(nullptr, childNIF);
+            childNIF = childNIF->GetNextInFlow();
+          }
+        }
+      }
+      child = next;
+    }
+
+    // Merge the results into our respective overflow child lists.
+    if (!pushedList.IsEmpty()) {
+      nsFrameList* overflow = GetOverflowFrames();
+      if (overflow) {
+        ::MergeSortedFrameLists(*overflow, pushedList, GetContent());
+      } else {
+        SetOverflowFrames(pushedList);
+      }
+      AddStateBits(NS_STATE_GRID_DID_PUSH_ITEMS);
+    }
+    if (!incompleteList.IsEmpty()) {
+      nsFrameList* overflow = GetOverflowFrames();
+      if (overflow) {
+        ::MergeSortedFrameLists(*overflow, incompleteList, GetContent());
+      } else {
+        SetOverflowFrames(incompleteList);
+      }
+    }
+    if (!overflowIncompleteList.IsEmpty()) {
+      auto eoc = static_cast<nsFrameList*>(Properties().Get(
+                                           ExcessOverflowContainersProperty()));
+      if (eoc) {
+        ::MergeSortedFrameLists(*eoc, overflowIncompleteList, GetContent());
+      } else {
+        auto list = new (pc->PresShell()) nsFrameList(overflowIncompleteList);
+        SetPropTableFrames(list, ExcessOverflowContainersProperty());
+      }
+    }
+  }
+  return aBSize;
+}
+
+nscoord
 nsGridContainerFrame::ReflowChildren(GridReflowState&     aState,
                                      const LogicalRect&   aContentArea,
                                      nsHTMLReflowMetrics& aDesiredSize,
                                      nsReflowStatus&      aStatus)
 {
   MOZ_ASSERT(aState.mReflowState);
 
   aStatus = NS_FRAME_COMPLETE;
@@ -4199,18 +4719,20 @@ nsGridContainerFrame::ReflowChildren(Gri
                                     ocBounds, 0, ocStatus);
   }
 
   WritingMode wm = aState.mReflowState->GetWritingMode();
   const nsSize containerSize =
     (aContentArea.Size(wm) + aState.mBorderPadding.Size(wm)).GetPhysicalSize(wm);
 
   nscoord bSize = aContentArea.BSize(wm);
-  if (false) {
-    // XXX TBD: a later patch will add the fragmented reflow here...
+  Maybe<Fragmentainer> fragmentainer = GetNearestFragmentainer(aState);
+  if (MOZ_UNLIKELY(fragmentainer.isSome())) {
+    bSize = ReflowInFragmentainer(aState, aContentArea, aDesiredSize, aStatus,
+                                  *fragmentainer, containerSize);
   } else {
     aState.mIter.Reset(GridItemCSSOrderIterator::eIncludeAll);
     for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
       nsIFrame* child = *aState.mIter;
       const GridItemInfo* info = nullptr;
       if (child->GetType() != nsGkAtoms::placeholderFrame) {
         info = &aState.mGridItems[aState.mIter.GridItemIndex()];
       }
@@ -4262,16 +4784,17 @@ nsGridContainerFrame::ReflowChildren(Gri
       flags |= AbsPosReflowFlags::eConstrainHeight;
       flags |= AbsPosReflowFlags::eIsGridContainerCB;
       GetAbsoluteContainingBlock()->Reflow(this, PresContext(),
                                            *aState.mReflowState,
                                            aStatus, dummyRect, flags,
                                            &aDesiredSize.mOverflowAreas);
     }
   }
+  return bSize;
 }
 
 void
 nsGridContainerFrame::Reflow(nsPresContext*           aPresContext,
                              nsHTMLReflowMetrics&     aDesiredSize,
                              const nsHTMLReflowState& aReflowState,
                              nsReflowStatus&          aStatus)
 {
@@ -4505,18 +5028,18 @@ nsGridContainerFrame::Reflow(nsPresConte
                           computedISize, bSize);
 
   if (!prevInFlow) {
     // Apply 'align/justify-content' to the grid.
     gridReflowState.mCols.AlignJustifyContent(aReflowState, contentArea.Size(wm));
     gridReflowState.mRows.AlignJustifyContent(aReflowState, contentArea.Size(wm));
   }
 
-  gridReflowState.mIter.Reset(GridItemCSSOrderIterator::eIncludeAll);
-  ReflowChildren(gridReflowState, contentArea, aDesiredSize, aStatus);
+  bSize = ReflowChildren(gridReflowState, contentArea, aDesiredSize, aStatus);
+  bSize = std::max(bSize - consumedBSize, 0);
 
   // Skip our block-end border if we're INCOMPLETE.
   if (!NS_FRAME_IS_COMPLETE(aStatus) &&
       !gridReflowState.mSkipSides.BEnd() &&
       StyleBorder()->mBoxDecorationBreak !=
         NS_STYLE_BOX_DECORATION_BREAK_CLONE) {
     bp.BEnd(wm) = nscoord(0);
   }
--- a/layout/generic/nsGridContainerFrame.h
+++ b/layout/generic/nsGridContainerFrame.h
@@ -133,21 +133,23 @@ protected:
   void InitImplicitNamedAreas(const nsStylePosition* aStyle);
   void AddImplicitNamedAreas(const nsTArray<nsTArray<nsString>>& aLineNameLists);
   ImplicitNamedAreas* GetImplicitNamedAreas() const {
     return Properties().Get(ImplicitNamedAreasProperty());
   }
 
   /**
    * Reflow and place our children.
+   * @return the consumed size of all of this grid container's continuations
+   *         so far including this frame
    */
-  void ReflowChildren(GridReflowState&     aState,
-                      const LogicalRect&   aContentArea,
-                      nsHTMLReflowMetrics& aDesiredSize,
-                      nsReflowStatus&      aStatus);
+  nscoord ReflowChildren(GridReflowState&     aState,
+                         const LogicalRect&   aContentArea,
+                         nsHTMLReflowMetrics& aDesiredSize,
+                         nsReflowStatus&      aStatus);
 
   /**
    * Helper for GetMinISize / GetPrefISize.
    */
   nscoord IntrinsicISize(nsRenderingContext* aRenderingContext,
                          IntrinsicISizeType  aConstraint);
 
 #ifdef DEBUG
@@ -178,16 +180,38 @@ private:
      * Is the grid container's block-size unconstrained?
      */
     bool mIsAutoBSize;
   };
 
   Maybe<nsGridContainerFrame::Fragmentainer>
     GetNearestFragmentainer(const GridReflowState& aState) const;
 
+  // @return the consumed size of all continuations so far including this frame
+  nscoord ReflowInFragmentainer(GridReflowState&     aState,
+                                const LogicalRect&   aContentArea,
+                                nsHTMLReflowMetrics& aDesiredSize,
+                                nsReflowStatus&      aStatus,
+                                Fragmentainer&       aFragmentainer,
+                                const nsSize&        aContainerSize);
+
+  // Helper for ReflowInFragmentainer
+  // @return the consumed size of all continuations so far including this frame
+  nscoord ReflowRowsInFragmentainer(GridReflowState&     aState,
+                                    const LogicalRect&   aContentArea,
+                                    nsHTMLReflowMetrics& aDesiredSize,
+                                    nsReflowStatus&      aStatus,
+                                    Fragmentainer&       aFragmentainer,
+                                    const nsSize&        aContainerSize,
+                                    const nsTArray<const GridItemInfo*>& aItems,
+                                    uint32_t             aStartRow,
+                                    uint32_t             aEndRow,
+                                    nscoord              aBSize,
+                                    nscoord              aAvailableSize);
+
   // Helper for ReflowChildren / ReflowInFragmentainer
   void ReflowInFlowChild(nsIFrame*              aChild,
                          const GridItemInfo*    aGridItemInfo,
                          nsSize                 aContainerSize,
                          const Fragmentainer*   aFragmentainer,
                          const GridReflowState& aState,
                          const LogicalRect&     aContentArea,
                          nsHTMLReflowMetrics&   aDesiredSize,