diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index c7f2471bca..f9a3f03cc9 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -42,6 +42,7 @@ #include "nsLayoutUtils.h" #include "LayoutLogging.h" #include "mozilla/RestyleManager.h" +#include "nsGridContainerFrame.h" #include "nsIDOMNode.h" #include "nsISelection.h" @@ -4382,6 +4383,8 @@ nsFrame::MarkIntrinsicISizesDirty() if (GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) { nsFontInflationData::MarkFontInflationDataTextDirty(this); } + + RemoveProperty(nsGridContainerFrame::CachedBAxisMeasurement::Prop()); } /* virtual */ nscoord diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index 89352321c4..bbc28ecb4a 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -45,6 +45,8 @@ typedef nsTHashtable< nsPtrHashKey > FrameHashtable; typedef mozilla::CSSAlignUtils::AlignJustifyFlags AlignJustifyFlags; typedef nsLayoutUtils::IntrinsicISizeType IntrinsicISizeType; +using GridItemCachedBAxisMeasurement = nsGridContainerFrame::CachedBAxisMeasurement; + // https://drafts.csswg.org/css-sizing/#constraints enum class SizingConstraint { @@ -3793,6 +3795,17 @@ MeasuringReflow(nsIFrame* aChild, nsIFrame::ReflowChildFlags::NoMoveFrame | nsIFrame::ReflowChildFlags::NoSizeView | nsIFrame::ReflowChildFlags::NoDeleteNextInFlowChild; + + bool found; + GridItemCachedBAxisMeasurement cachedMeasurement = + aChild->GetProperty(GridItemCachedBAxisMeasurement::Prop(), &found); + if (found && cachedMeasurement.IsValidFor(aChild, aCBSize)) { + childSize.BSize(wm) = cachedMeasurement.BSize(); + nsContainerFrame::FinishReflowChild(aChild, pc, childSize, &childRI, wm, + LogicalPoint(wm), nsSize(), flags); + return cachedMeasurement.BSize(); + } + parent->ReflowChild(aChild, pc, childSize, childRI, wm, LogicalPoint(wm), nsSize(), flags, childStatus); parent->FinishReflowChild(aChild, pc, childSize, &childRI, wm, @@ -3800,6 +3813,22 @@ MeasuringReflow(nsIFrame* aChild, #ifdef DEBUG parent->DeleteProperty(nsContainerFrame::DebugReflowingWithInfiniteISize()); #endif + + if (!found && + GridItemCachedBAxisMeasurement::CanCacheMeasurement(aChild, aCBSize)) { + GridItemCachedBAxisMeasurement cachedMeasurement(aChild, aCBSize, + childSize.BSize(wm)); + aChild->SetProperty(GridItemCachedBAxisMeasurement::Prop(), + cachedMeasurement); + } else if (found) { + if (GridItemCachedBAxisMeasurement::CanCacheMeasurement(aChild, + aCBSize)) { + cachedMeasurement.Update(aChild, aCBSize, childSize.BSize(wm)); + } else { + aChild->RemoveProperty(GridItemCachedBAxisMeasurement::Prop()); + } + } + return childSize.BSize(wm); } @@ -5916,7 +5945,7 @@ nsGridContainerFrame::ReflowChildren(GridReflowInput& aState, if (child->GetType() != nsGkAtoms::placeholderFrame) { info = &aState.mGridItems[aState.mIter.GridItemIndex()]; } - ReflowInFlowChild(*aState.mIter, info, containerSize, Nothing(), nullptr, + ReflowInFlowChild(child, info, containerSize, Nothing(), nullptr, aState, aContentArea, aDesiredSize, aStatus); MOZ_ASSERT(NS_FRAME_IS_COMPLETE(aStatus), "child should be complete " "in unconstrained reflow"); diff --git a/layout/generic/nsGridContainerFrame.h b/layout/generic/nsGridContainerFrame.h index 1275305b65..ea9dae7033 100644 --- a/layout/generic/nsGridContainerFrame.h +++ b/layout/generic/nsGridContainerFrame.h @@ -8,6 +8,7 @@ #ifndef nsGridContainerFrame_h___ #define nsGridContainerFrame_h___ +#include "mozilla/MathAlgorithms.h" #include "mozilla/Maybe.h" #include "mozilla/TypeTraits.h" #include "nsContainerFrame.h" @@ -451,6 +452,104 @@ private: // in case RemoveFrame removed all associated child frames. bool mDidPushItemsBitMayLie; #endif + +public: + // This class caches the result for a grid item's block-axis measuring reflow. + // This cache prevents us from doing exponential reflows in cases of deeply + // nested grid frames. + // + // We store the cached value in the grid item's frame property table. + // + // We use the size of the grid area in the item's inline axis and + // the item's block axis baseline padding for lookups and a unique-enough + // set of properties. + // We store the item's border-box BSize as value to be retrieved. + class CachedBAxisMeasurement { + public: + NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(Prop, CachedBAxisMeasurement) + CachedBAxisMeasurement(const nsIFrame* aFrame, const LogicalSize& aCBSize, + const nscoord aBSize) + : mKey(aFrame, aCBSize), mBSize(aBSize) {} + + CachedBAxisMeasurement() = default; + + bool IsValidFor(const nsIFrame* aFrame, const LogicalSize& aCBSize) const { + if (!CanCacheMeasurement(aFrame, aCBSize)) { + return false; + } + + return mKey == Key(aFrame, aCBSize); + } + + static bool CanCacheMeasurement(const nsIFrame* aFrame, + const LogicalSize& aCBSize) { + return Key::CanHash(aFrame, aCBSize); + } + + nscoord BSize() const { return mBSize; } + + void Update(const nsIFrame* aFrame, const LogicalSize& aCBSize, + const nscoord aBSize) { + MOZ_ASSERT(CanCacheMeasurement(aFrame, aCBSize)); + mKey.mHashKey = Key::GenerateHash(aFrame, aCBSize); + mBSize = aBSize; + } + + private: + struct Key { + // mHashKey is generated by combining these 2 variables together + // 1. The containing block size in the item's inline axis used + // for measuring reflow + // 2. The item's baseline padding property + uint32_t mHashKey; + + Key() = default; + + Key(const nsIFrame* aFrame, const LogicalSize& aCBSize) { + MOZ_ASSERT(CanHash(aFrame, aCBSize)); + mHashKey = GenerateHash(aFrame, aCBSize); + } + + void UpdateHash(const nsIFrame* aFrame, const LogicalSize& aCBSize) { + MOZ_ASSERT(CanHash(aFrame, aCBSize)); + mHashKey = GenerateHash(aFrame, aCBSize); + } + + // Fast hash functions for this purpose only. + static uint32_t GenerateHash(const nsIFrame* aFrame, + const LogicalSize& aCBSize) { + MOZ_ASSERT(CanHash(aFrame, aCBSize)); + + nscoord gridAreaISize = aCBSize.ISize(aFrame->GetWritingMode()); + nscoord bBaselinePaddingProperty = + abs(aFrame->GetProperty(nsIFrame::BBaselinePadProperty())); + + uint_fast8_t bitsNeededForISize = mozilla::FloorLog2(gridAreaISize) + 1; + + return (gridAreaISize << (32 - bitsNeededForISize)) | + bBaselinePaddingProperty; + } + + static bool CanHash(const nsIFrame* aFrame, const LogicalSize& aCBSize) { + uint_fast8_t bitsNeededForISize = + mozilla::FloorLog2(aCBSize.ISize(aFrame->GetWritingMode())) + 1; + + uint_fast8_t bitsNeededForBBaselinePadding = + mozilla::FloorLog2( + abs(aFrame->GetProperty(nsIFrame::BBaselinePadProperty()))) + + 1; + + return bitsNeededForISize + bitsNeededForBBaselinePadding <= 32; + } + + bool operator==(const Key& aOther) const { + return mHashKey == aOther.mHashKey; + } + }; + + Key mKey; + nscoord mBSize; + }; }; #endif /* nsGridContainerFrame_h___ */