Issue #2694 - Improve nested grid layout performance by caching.

This improves performance by caching the grid items' block axis
measurement, avoiding potentially exponential calculations and reflows.
Resolves #2694
This commit is contained in:
Moonchild 2025-02-18 16:56:11 +01:00 committed by roytam1
commit 25570dc572
3 changed files with 132 additions and 1 deletions

View file

@ -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

View file

@ -45,6 +45,8 @@ typedef nsTHashtable< nsPtrHashKey<nsIFrame> > 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");

View file

@ -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___ */