Bug 1675376 - Cache consumed BSize in a frame property for non-first continuations. r=mats
authorEmilio Cobos Álvarez <emilio@crisal.io>
Wed, 18 Nov 2020 11:04:35 +0000
changeset 557792 54da9db54cbef62e84c722dba6d65943af8d9e58
parent 557791 6cde833264983df1d8bb92f4051cf8ece18ec221
child 557793 3abee7381c9a35ede4693e3d7c8c3264d7858ca5
push id37962
push userapavel@mozilla.com
push dateWed, 18 Nov 2020 21:51:58 +0000
treeherdermozilla-central@9d797387f57c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmats
bugs1675376
milestone85.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 1675376 - Cache consumed BSize in a frame property for non-first continuations. r=mats This removes virtually all the time under ConsumedBSize. See the comment for what ensures the correctness of the cache: Basically, we refresh the cache for a frame continuation every time we reflow it, which means that when next continuations go look for it it should be up-to-date (we rely on that already because we're looking at the content rect). Differential Revision: https://phabricator.services.mozilla.com/D97357
layout/generic/nsBlockFrame.cpp
layout/generic/nsFlexContainerFrame.cpp
layout/generic/nsFlexContainerFrame.h
layout/generic/nsGridContainerFrame.cpp
layout/generic/nsSplittableFrame.cpp
layout/generic/nsSplittableFrame.h
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -1246,17 +1246,17 @@ void nsBlockFrame::Reflow(nsPresContext*
   // ColumnSetWrapper's children depend on ColumnSetWrapper's block-size or
   // max-block-size because both affect the children's available block-size.
   if (IsColumnSetWrapperFrame()) {
     AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
   }
 
   const ReflowInput* reflowInput = &aReflowInput;
   WritingMode wm = aReflowInput.GetWritingMode();
-  nscoord consumedBSize = ConsumedBSize(wm);
+  nscoord consumedBSize = CalcAndCacheConsumedBSize(wm);
   nscoord effectiveComputedBSize =
       GetEffectiveComputedBSize(aReflowInput, consumedBSize);
   Maybe<ReflowInput> mutableReflowInput;
   // If we have non-auto block size, we're clipping our kids and we fit,
   // make sure our kids fit too.
   const PhysicalAxes physicalBlockAxis =
       wm.IsVertical() ? PhysicalAxes::Horizontal : PhysicalAxes::Vertical;
   if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
--- a/layout/generic/nsFlexContainerFrame.cpp
+++ b/layout/generic/nsFlexContainerFrame.cpp
@@ -4095,29 +4095,30 @@ void nsFlexContainerFrame::GenerateFlexL
     }
   }
 }
 
 // Retrieves the content-box main-size of our flex container from the
 // reflow input (specifically, the main-size of *this continuation* of the
 // flex container).
 nscoord nsFlexContainerFrame::GetMainSizeFromReflowInput(
-    const ReflowInput& aReflowInput, const FlexboxAxisTracker& aAxisTracker) {
+    const ReflowInput& aReflowInput, const FlexboxAxisTracker& aAxisTracker,
+    nscoord aConsumedBSize) {
   if (aAxisTracker.IsRowOriented()) {
     // Row-oriented --> our main axis is the inline axis, so our main size
     // is our inline size (which should already be resolved).
     NS_WARNING_ASSERTION(
         aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE,
         "Unconstrained inline size; this should only result from huge sizes "
         "(not intrinsic sizing w/ orthogonal flows)");
     return aReflowInput.ComputedISize();
   }
 
   // Note: This may be unconstrained, if our block size is "auto":
-  return GetEffectiveComputedBSize(aReflowInput);
+  return GetEffectiveComputedBSize(aReflowInput, aConsumedBSize);
 }
 
 // Returns the largest outer hypothetical main-size of any line in |aLines|.
 // (i.e. the hypothetical main-size of the largest line)
 static nscoord GetLargestLineMainSize(nsTArray<FlexLine>& aLines) {
   nscoord largestLineOuterSize = 0;
   for (const FlexLine& line : aLines) {
     largestLineOuterSize =
@@ -4154,17 +4155,18 @@ nscoord nsFlexContainerFrame::ComputeMai
   // computed min/max main-size properties.
   nscoord largestLineOuterSize = GetLargestLineMainSize(aLines);
   return NS_CSS_MINMAX(largestLineOuterSize, aReflowInput.ComputedMinBSize(),
                        aReflowInput.ComputedMaxBSize());
 }
 
 nscoord nsFlexContainerFrame::ComputeCrossSize(
     const ReflowInput& aReflowInput, const FlexboxAxisTracker& aAxisTracker,
-    nscoord aSumLineCrossSizes, bool* aIsDefinite) const {
+    nscoord aSumLineCrossSizes, nscoord aConsumedBSize,
+    bool* aIsDefinite) const {
   MOZ_ASSERT(aIsDefinite, "outparam pointer must be non-null");
 
   if (aAxisTracker.IsColumnOriented()) {
     // Column-oriented --> our cross axis is the inline axis, so our cross size
     // is our inline size (which should already be resolved).
     NS_WARNING_ASSERTION(
         aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE,
         "Unconstrained inline size; this should only result from huge sizes "
@@ -4175,17 +4177,18 @@ nscoord nsFlexContainerFrame::ComputeCro
     // size is an intrinsic size, and we have flex items that are flexible and
     // have intrinsic aspect ratios, then we may need to take their post-flexing
     // main sizes into account (multiplied through their aspect ratios to get
     // their cross sizes), in order to determine their flex line's size & the
     // flex container's cross size (e.g. as `aSumLineCrossSizes`).
     return aReflowInput.ComputedISize();
   }
 
-  nscoord effectiveComputedBSize = GetEffectiveComputedBSize(aReflowInput);
+  nscoord effectiveComputedBSize =
+      GetEffectiveComputedBSize(aReflowInput, aConsumedBSize);
   if (effectiveComputedBSize != NS_UNCONSTRAINEDSIZE) {
     // Row-oriented case (cross axis is block-axis), with fixed BSize:
     *aIsDefinite = true;
 
     // Just use our fixed block-size because we always assume the available
     // block-size is unconstrained, and the reflow input has already done the
     // appropriate min/max-BSize clamping.
     return effectiveComputedBSize;
@@ -4396,29 +4399,32 @@ void nsFlexContainerFrame::Reflow(nsPres
   // border/padding when we know our content-box size after DoFlexLayout.
   LogicalMargin borderPadding =
       aReflowInput.ComputedLogicalBorderPadding(wm).ApplySkipSides(
           PreReflowBlockLevelLogicalSkipSides());
 
   const LogicalSize availableSizeForItems =
       ComputeAvailableSizeForItems(aReflowInput, borderPadding);
 
+  const nscoord consumedBSize =
+      CalcAndCacheConsumedBSize(aReflowInput.GetWritingMode());
   nscoord contentBoxMainSize =
-      GetMainSizeFromReflowInput(aReflowInput, axisTracker);
+      GetMainSizeFromReflowInput(aReflowInput, axisTracker, consumedBSize);
   nscoord contentBoxCrossSize;
   nscoord flexContainerAscent;
 
   // Calculate gap size for main and cross axis
   nscoord mainGapSize;
   nscoord crossGapSize;
   if (axisTracker.IsRowOriented()) {
     mainGapSize = nsLayoutUtils::ResolveGapToLength(stylePos->mColumnGap,
                                                     contentBoxMainSize);
     crossGapSize = nsLayoutUtils::ResolveGapToLength(
-        stylePos->mRowGap, GetEffectiveComputedBSize(aReflowInput));
+        stylePos->mRowGap,
+        GetEffectiveComputedBSize(aReflowInput, consumedBSize));
   } else {
     mainGapSize = nsLayoutUtils::ResolveGapToLength(stylePos->mRowGap,
                                                     contentBoxMainSize);
     NS_WARNING_ASSERTION(aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE,
                          "Unconstrained inline size; this should only result "
                          "from huge sizes (not intrinsic sizing w/ orthogonal "
                          "flows)");
     crossGapSize = nsLayoutUtils::ResolveGapToLength(
@@ -4435,41 +4441,40 @@ void nsFlexContainerFrame::Reflow(nsPres
     // content-box size. https://drafts.csswg.org/css-flexbox-1/#pagination-algo
     //
     // Note: For a multi-line column-oriented flex container, the sample
     // algorithm suggests we wrap the flex line at the block-end edge of a
     // column/page, but we do not implement it intentionally. This brings the
     // layout result closer to the one as if there's no fragmentation.
     DoFlexLayout(aReflowInput, contentBoxMainSize, contentBoxCrossSize,
                  flexContainerAscent, lines, struts, placeholders, axisTracker,
-                 mainGapSize, crossGapSize, hasLineClampEllipsis,
+                 mainGapSize, crossGapSize, consumedBSize, hasLineClampEllipsis,
                  containerInfo);
 
     if (!struts.IsEmpty()) {
       // We're restarting flex layout, with new knowledge of collapsed items.
       lines.Clear();
       placeholders.Clear();
       DoFlexLayout(aReflowInput, contentBoxMainSize, contentBoxCrossSize,
                    flexContainerAscent, lines, struts, placeholders,
-                   axisTracker, mainGapSize, crossGapSize, hasLineClampEllipsis,
-                   containerInfo);
+                   axisTracker, mainGapSize, crossGapSize, consumedBSize,
+                   hasLineClampEllipsis, containerInfo);
     }
   } else {
     auto* data = FirstInFlow()->GetProperty(SharedFlexData::Prop());
     MOZ_ASSERT(data, "SharedFlexData should be set by our first-in-flow!");
 
     GenerateFlexLines(*data, lines);
     contentBoxMainSize = data->mContentBoxMainSize;
     contentBoxCrossSize = data->mContentBoxCrossSize;
   }
 
   const LogicalSize contentBoxSize =
       axisTracker.LogicalSizeFromFlexRelativeSizes(contentBoxMainSize,
                                                    contentBoxCrossSize);
-  const nscoord consumedBSize = ConsumedBSize(wm);
   const nscoord effectiveContentBSize =
       contentBoxSize.BSize(wm) - consumedBSize;
 
   // Check if we may need a next-in-flow. If so, we'll need to skip block-end
   // border and padding.
   const bool mayNeedNextInFlow =
       effectiveContentBSize > availableSizeForItems.BSize(wm);
   if (mayNeedNextInFlow) {
@@ -4870,17 +4875,18 @@ bool nsFlexContainerFrame::IsUsedFlexBas
   return aFlexBasis.IsAuto() && aMainSize.IsAuto();
 }
 
 void nsFlexContainerFrame::DoFlexLayout(
     const ReflowInput& aReflowInput, nscoord& aContentBoxMainSize,
     nscoord& aContentBoxCrossSize, nscoord& aFlexContainerAscent,
     nsTArray<FlexLine>& aLines, nsTArray<StrutInfo>& aStruts,
     nsTArray<nsIFrame*>& aPlaceholders, const FlexboxAxisTracker& aAxisTracker,
-    nscoord aMainGapSize, nscoord aCrossGapSize, bool aHasLineClampEllipsis,
+    nscoord aMainGapSize, nscoord aCrossGapSize, nscoord aConsumedBSize,
+    bool aHasLineClampEllipsis,
     ComputedFlexContainerInfo* const aContainerInfo) {
   MOZ_ASSERT(aLines.IsEmpty(), "Caller should pass an empty array for lines!");
   MOZ_ASSERT(aPlaceholders.IsEmpty(),
              "Caller should pass an empty array for placeholders!");
 
   GenerateFlexLines(aReflowInput, aContentBoxMainSize, aStruts, aAxisTracker,
                     aMainGapSize, aHasLineClampEllipsis, aPlaceholders, aLines);
 
@@ -4973,18 +4979,19 @@ void nsFlexContainerFrame::DoFlexLayout(
       }
     }
     // Now that we've finished with this line's items, size the line itself:
     line.ComputeCrossSizeAndBaseline(aAxisTracker);
     sumLineCrossSizes += line.LineCrossSize();
   }
 
   bool isCrossSizeDefinite;
-  aContentBoxCrossSize = ComputeCrossSize(
-      aReflowInput, aAxisTracker, sumLineCrossSizes, &isCrossSizeDefinite);
+  aContentBoxCrossSize =
+      ComputeCrossSize(aReflowInput, aAxisTracker, sumLineCrossSizes,
+                       aConsumedBSize, &isCrossSizeDefinite);
 
   // Set up state for cross-axis alignment, at a high level (outside the
   // scope of a particular flex line)
   CrossAxisPositionTracker crossAxisPosnTracker(
       aLines, aReflowInput, aContentBoxCrossSize, isCrossSizeDefinite,
       aAxisTracker, aCrossGapSize);
 
   // Now that we know the cross size of each line (including
--- a/layout/generic/nsFlexContainerFrame.h
+++ b/layout/generic/nsFlexContainerFrame.h
@@ -316,17 +316,17 @@ class nsFlexContainerFrame final : publi
    */
   void DoFlexLayout(const ReflowInput& aReflowInput,
                     nscoord& aContentBoxMainSize, nscoord& aContentBoxCrossSize,
                     nscoord& aFlexContainerAscent, nsTArray<FlexLine>& aLines,
                     nsTArray<StrutInfo>& aStruts,
                     nsTArray<nsIFrame*>& aPlaceholders,
                     const FlexboxAxisTracker& aAxisTracker,
                     nscoord aMainGapSize, nscoord aCrossGapSize,
-                    bool aHasLineClampEllipsis,
+                    nscoord aConsumedBSize, bool aHasLineClampEllipsis,
                     ComputedFlexContainerInfo* const aContainerInfo);
 
   /**
    * If our devtools have requested a ComputedFlexContainerInfo for this flex
    * container, this method ensures that we have one (and if one already exists,
    * this method reinitializes it to look like a freshly-created one).
    *
    * @return the pointer to a freshly created or reinitialized
@@ -442,17 +442,18 @@ class nsFlexContainerFrame final : publi
    * This method creates FlexLines and FlexItems for children in flex
    * container's next-in-flows by using the SharedFlexData stored in flex
    * container's first-in-flow. Returns FlexLines in the outparam |aLines|.
    */
   void GenerateFlexLines(const SharedFlexData& aData,
                          nsTArray<FlexLine>& aLines);
 
   nscoord GetMainSizeFromReflowInput(const ReflowInput& aReflowInput,
-                                     const FlexboxAxisTracker& aAxisTracker);
+                                     const FlexboxAxisTracker& aAxisTracker,
+                                     nscoord aConsumedBSize);
 
   /**
    * Resolves the content-box main-size of a flex container frame,
    * primarily based on:
    * - the "tentative" main size, taken from the reflow input ("tentative"
    *   because it may be unconstrained or may run off the page).
    * - the sizes of our lines of flex items.
    *
@@ -472,17 +473,18 @@ class nsFlexContainerFrame final : publi
    */
   nscoord ComputeMainSize(const ReflowInput& aReflowInput,
                           const FlexboxAxisTracker& aAxisTracker,
                           nscoord aTentativeMainSize,
                           nsTArray<FlexLine>& aLines) const;
 
   nscoord ComputeCrossSize(const ReflowInput& aReflowInput,
                            const FlexboxAxisTracker& aAxisTracker,
-                           nscoord aSumLineCrossSizes, bool* aIsDefinite) const;
+                           nscoord aSumLineCrossSizes, nscoord aConsumedBSize,
+                           bool* aIsDefinite) const;
 
   /**
    * Compute the size of the available space that we'll give to our children to
    * reflow into. In particular, compute the available size that we would give
    * to a hypothetical child placed at the IStart/BStart corner of this flex
    * container's content-box.
    *
    * @param aReflowInput the flex container's reflow input.
--- a/layout/generic/nsGridContainerFrame.cpp
+++ b/layout/generic/nsGridContainerFrame.cpp
@@ -8527,17 +8527,17 @@ void nsGridContainerFrame::Reflow(nsPres
           bSize += gridReflowInput.mRows.SumOfGridGaps();
         } else if (computedBSize == NS_UNCONSTRAINEDSIZE) {
           bSize = gridReflowInput.mRows.GridLineEdge(
               rowSizes.Length(), GridLineSide::BeforeGridGap);
         }
       }
     }
   } else {
-    consumedBSize = ConsumedBSize(wm);
+    consumedBSize = CalcAndCacheConsumedBSize(wm);
     gridReflowInput.InitializeForContinuation(this, consumedBSize);
     // XXX Technically incorrect: We're ignoring our row sizes, when really
     // we should use them but *they* should be computed as if we had no
     // children. To be fixed in bug 1488878.
     if (!aReflowInput.mStyleDisplay->IsContainSize()) {
       const uint32_t numRows = gridReflowInput.mRows.mSizes.Length();
       bSize = gridReflowInput.mRows.GridLineEdge(numRows,
                                                  GridLineSide::AfterGridGap);
--- a/layout/generic/nsSplittableFrame.cpp
+++ b/layout/generic/nsSplittableFrame.cpp
@@ -179,37 +179,47 @@ void nsSplittableFrame::RemoveFromFlow(n
       nextContinuation->SetPrevContinuation(prevContinuation);
     }
   }
 
   aFrame->SetPrevInFlow(nullptr);
   aFrame->SetNextInFlow(nullptr);
 }
 
-nscoord nsSplittableFrame::ConsumedBSize(WritingMode aWM) const {
-  nscoord bSize = 0;
+NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(ConsumedBSizeProperty, nscoord);
 
-  for (nsIFrame* prev = GetPrevContinuation(); prev;
-       prev = prev->GetPrevContinuation()) {
+nscoord nsSplittableFrame::CalcAndCacheConsumedBSize(WritingMode aWM) {
+  nsIFrame* prev = GetPrevContinuation();
+  if (!prev) {
+    return 0;
+  }
+  nscoord bSize = 0;
+  for (; prev; prev = prev->GetPrevContinuation()) {
     bSize += prev->ContentSize(aWM).BSize(aWM);
+    bool found = false;
+    nscoord consumed = prev->GetProperty(ConsumedBSizeProperty(), &found);
+    if (found) {
+      bSize += consumed;
+      break;
+    }
+    MOZ_ASSERT(!prev->GetPrevContinuation(),
+               "Property should always be set on prev continuation if not "
+               "the first continuation");
   }
+  SetProperty(ConsumedBSizeProperty(), bSize);
   return bSize;
 }
 
 nscoord nsSplittableFrame::GetEffectiveComputedBSize(
     const ReflowInput& aReflowInput, nscoord aConsumedBSize) const {
   nscoord bSize = aReflowInput.ComputedBSize();
   if (bSize == NS_UNCONSTRAINEDSIZE) {
     return NS_UNCONSTRAINEDSIZE;
   }
 
-  if (aConsumedBSize == NS_UNCONSTRAINEDSIZE) {
-    aConsumedBSize = ConsumedBSize(aReflowInput.GetWritingMode());
-  }
-
   bSize -= aConsumedBSize;
 
   // nsFieldSetFrame's inner frames are special since some of their content-box
   // BSize may be consumed by positioning it below the legend.  So we always
   // report zero for true overflow containers here.
   // XXXmats: hmm, can we fix this so that the sizes actually adds up instead?
   if (IsTrueOverflowContainer() &&
       Style()->GetPseudoType() == PseudoStyleType::fieldsetContent) {
--- a/layout/generic/nsSplittableFrame.h
+++ b/layout/generic/nsSplittableFrame.h
@@ -82,32 +82,34 @@ class nsSplittableFrame : public nsIFram
       : nsIFrame(aStyle, aPresContext, aID),
         mPrevContinuation(nullptr),
         mNextContinuation(nullptr) {}
 
   /**
    * Return the sum of the block-axis content size of our previous
    * continuations.
    *
-   * @param aWM a writing-mode to determine the block-axis
+   * Classes that call this are _required_ to call this at least once for each
+   * reflow (unless you're the first continuation, in which case you can skip
+   * it, because as an optimization we don't cache it there).
    *
-   * @note (bz) This makes laying out a splittable frame with N continuations
-   *       O(N^2)! So, use this function with caution and minimize the number
-   *       of calls to this method.
+   * This guarantees that the internal cache works, by refreshing it. Calling it
+   * multiple times in the same reflow is wasteful, but not an error.
+   *
+   * @param aWM a writing-mode to determine the block-axis
    */
-  nscoord ConsumedBSize(mozilla::WritingMode aWM) const;
+  nscoord CalcAndCacheConsumedBSize(mozilla::WritingMode aWM);
 
   /**
    * Retrieve the effective computed block size of this frame, which is the
    * computed block size, minus the block size consumed by any previous
    * continuations.
    */
   nscoord GetEffectiveComputedBSize(
-      const ReflowInput& aReflowInput,
-      nscoord aConsumed = NS_UNCONSTRAINEDSIZE) const;
+      const ReflowInput& aReflowInput, nscoord aConsumed) const;
 
   /**
    * @see nsIFrame::GetLogicalSkipSides()
    */
   LogicalSides GetLogicalSkipSides(
       const Maybe<SkipSidesDuringReflow>& = Nothing()) const override;
 
   /**