mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-28 01:57:30 +09:00
Bug 1209697: Cache flex measuring reflows to avoid exponential behavior
This commit is contained in:
parent
b706df90da
commit
95b1132e04
2 changed files with 114 additions and 39 deletions
|
|
@ -33,6 +33,8 @@ typedef nsFlexContainerFrame::FlexItem FlexItem;
|
||||||
typedef nsFlexContainerFrame::FlexLine FlexLine;
|
typedef nsFlexContainerFrame::FlexLine FlexLine;
|
||||||
typedef nsFlexContainerFrame::FlexboxAxisTracker FlexboxAxisTracker;
|
typedef nsFlexContainerFrame::FlexboxAxisTracker FlexboxAxisTracker;
|
||||||
typedef nsFlexContainerFrame::StrutInfo StrutInfo;
|
typedef nsFlexContainerFrame::StrutInfo StrutInfo;
|
||||||
|
typedef nsFlexContainerFrame::CachedMeasuringReflowResult
|
||||||
|
CachedMeasuringReflowResult;
|
||||||
|
|
||||||
static mozilla::LazyLogModule gFlexContainerLog("nsFlexContainerFrame");
|
static mozilla::LazyLogModule gFlexContainerLog("nsFlexContainerFrame");
|
||||||
|
|
||||||
|
|
@ -1756,6 +1758,91 @@ nsFlexContainerFrame::
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cached result for a measuring reflow.
|
||||||
|
*
|
||||||
|
* Right now we only need to cache the available size, the height and the
|
||||||
|
* ascent. This can be extended later if needed.
|
||||||
|
*
|
||||||
|
* The assumption here is that a given flex item measurement won't change until
|
||||||
|
* either the available size changes, or the flex container intrinsic size is
|
||||||
|
* marked as dirty (due to a style or DOM change).
|
||||||
|
*
|
||||||
|
* Caching it prevents us from doing exponential reflows in cases of deeply
|
||||||
|
* nested flex and scroll frames.
|
||||||
|
*
|
||||||
|
* We store them in the frame property table for simplicity.
|
||||||
|
*/
|
||||||
|
struct nsFlexContainerFrame::CachedMeasuringReflowResult
|
||||||
|
{
|
||||||
|
LogicalSize mAvailableSize;
|
||||||
|
const nscoord mHeight;
|
||||||
|
const nscoord mAscent;
|
||||||
|
|
||||||
|
CachedMeasuringReflowResult(const LogicalSize& aAvailableSize,
|
||||||
|
nscoord aHeight,
|
||||||
|
nscoord aAscent)
|
||||||
|
: mAvailableSize(aAvailableSize)
|
||||||
|
, mHeight(aHeight)
|
||||||
|
, mAscent(aAscent)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
NS_DECLARE_FRAME_PROPERTY_DELETABLE(CachedFlexMeasuringReflow,
|
||||||
|
CachedMeasuringReflowResult);
|
||||||
|
|
||||||
|
const CachedMeasuringReflowResult&
|
||||||
|
nsFlexContainerFrame::MeasureAscentAndHeightForFlexItem(
|
||||||
|
FlexItem& aItem,
|
||||||
|
nsPresContext* aPresContext,
|
||||||
|
ReflowInput& aChildReflowInput)
|
||||||
|
{
|
||||||
|
const auto availableSize = aChildReflowInput.AvailableSize();
|
||||||
|
const FrameProperties props = aItem.Frame()->Properties();
|
||||||
|
if (const auto* cachedResult = props.Get(CachedFlexMeasuringReflow())) {
|
||||||
|
if (cachedResult->mAvailableSize == availableSize) {
|
||||||
|
return *cachedResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReflowOutput childDesiredSize(aChildReflowInput);
|
||||||
|
nsReflowStatus childReflowStatus;
|
||||||
|
|
||||||
|
const uint32_t flags = NS_FRAME_NO_MOVE_FRAME;
|
||||||
|
ReflowChild(aItem.Frame(), aPresContext,
|
||||||
|
childDesiredSize, aChildReflowInput,
|
||||||
|
0, 0, flags, childReflowStatus);
|
||||||
|
aItem.SetHadMeasuringReflow();
|
||||||
|
|
||||||
|
// XXXdholbert Once we do pagination / splitting, we'll need to actually
|
||||||
|
// handle incomplete childReflowStatuses. But for now, we give our kids
|
||||||
|
// unconstrained available height, which means they should always complete.
|
||||||
|
MOZ_ASSERT(NS_FRAME_IS_COMPLETE(childReflowStatus),
|
||||||
|
"We gave flex item unconstrained available height, so it "
|
||||||
|
"should be complete");
|
||||||
|
|
||||||
|
// Tell the child we're done with its initial reflow.
|
||||||
|
// (Necessary for e.g. GetBaseline() to work below w/out asserting)
|
||||||
|
FinishReflowChild(aItem.Frame(), aPresContext,
|
||||||
|
childDesiredSize, &aChildReflowInput, 0, 0, flags);
|
||||||
|
|
||||||
|
auto result =
|
||||||
|
new CachedMeasuringReflowResult(availableSize,
|
||||||
|
childDesiredSize.Height(),
|
||||||
|
childDesiredSize.BlockStartAscent());
|
||||||
|
|
||||||
|
props.Set(CachedFlexMeasuringReflow(), result);
|
||||||
|
return *result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* virtual */ void
|
||||||
|
nsFlexContainerFrame::MarkIntrinsicISizesDirty()
|
||||||
|
{
|
||||||
|
for (nsIFrame* childFrame : mFrames) {
|
||||||
|
childFrame->Properties().Delete(CachedFlexMeasuringReflow());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nscoord
|
nscoord
|
||||||
nsFlexContainerFrame::
|
nsFlexContainerFrame::
|
||||||
MeasureFlexItemContentHeight(nsPresContext* aPresContext,
|
MeasureFlexItemContentHeight(nsPresContext* aPresContext,
|
||||||
|
|
@ -1783,27 +1870,15 @@ nsFlexContainerFrame::
|
||||||
childRIForMeasuringHeight.SetVResize(true);
|
childRIForMeasuringHeight.SetVResize(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReflowOutput childDesiredSize(childRIForMeasuringHeight);
|
const CachedMeasuringReflowResult& reflowResult =
|
||||||
nsReflowStatus childReflowStatus;
|
MeasureAscentAndHeightForFlexItem(aFlexItem, aPresContext,
|
||||||
const uint32_t flags = NS_FRAME_NO_MOVE_FRAME;
|
childRIForMeasuringHeight);
|
||||||
ReflowChild(aFlexItem.Frame(), aPresContext,
|
|
||||||
childDesiredSize, childRIForMeasuringHeight,
|
|
||||||
0, 0, flags, childReflowStatus);
|
|
||||||
|
|
||||||
MOZ_ASSERT(NS_FRAME_IS_COMPLETE(childReflowStatus),
|
aFlexItem.SetAscent(reflowResult.mAscent);
|
||||||
"We gave flex item unconstrained available height, so it "
|
|
||||||
"should be complete");
|
|
||||||
|
|
||||||
FinishReflowChild(aFlexItem.Frame(), aPresContext,
|
|
||||||
childDesiredSize, &childRIForMeasuringHeight,
|
|
||||||
0, 0, flags);
|
|
||||||
|
|
||||||
aFlexItem.SetHadMeasuringReflow();
|
|
||||||
aFlexItem.SetAscent(childDesiredSize.BlockStartAscent());
|
|
||||||
|
|
||||||
// Subtract border/padding in vertical axis, to get _just_
|
// Subtract border/padding in vertical axis, to get _just_
|
||||||
// the effective computed value of the "height" property.
|
// the effective computed value of the "height" property.
|
||||||
nscoord childDesiredHeight = childDesiredSize.Height() -
|
nscoord childDesiredHeight = reflowResult.mHeight -
|
||||||
childRIForMeasuringHeight.ComputedPhysicalBorderPadding().TopBottom();
|
childRIForMeasuringHeight.ComputedPhysicalBorderPadding().TopBottom();
|
||||||
|
|
||||||
return std::max(0, childDesiredHeight);
|
return std::max(0, childDesiredHeight);
|
||||||
|
|
@ -3959,25 +4034,10 @@ nsFlexContainerFrame::SizeItemInCrossAxis(
|
||||||
// whether any of its ancestors are being resized).
|
// whether any of its ancestors are being resized).
|
||||||
aChildReflowInput.SetVResize(true);
|
aChildReflowInput.SetVResize(true);
|
||||||
}
|
}
|
||||||
ReflowOutput childDesiredSize(aChildReflowInput);
|
|
||||||
nsReflowStatus childReflowStatus;
|
|
||||||
const uint32_t flags = NS_FRAME_NO_MOVE_FRAME;
|
|
||||||
ReflowChild(aItem.Frame(), aPresContext,
|
|
||||||
childDesiredSize, aChildReflowInput,
|
|
||||||
0, 0, flags, childReflowStatus);
|
|
||||||
aItem.SetHadMeasuringReflow();
|
|
||||||
|
|
||||||
// XXXdholbert Once we do pagination / splitting, we'll need to actually
|
// Potentially reflow the item, and get the sizing info.
|
||||||
// handle incomplete childReflowStatuses. But for now, we give our kids
|
const CachedMeasuringReflowResult& reflowResult =
|
||||||
// unconstrained available height, which means they should always complete.
|
MeasureAscentAndHeightForFlexItem(aItem, aPresContext, aChildReflowInput);
|
||||||
MOZ_ASSERT(NS_FRAME_IS_COMPLETE(childReflowStatus),
|
|
||||||
"We gave flex item unconstrained available height, so it "
|
|
||||||
"should be complete");
|
|
||||||
|
|
||||||
// Tell the child we're done with its initial reflow.
|
|
||||||
// (Necessary for e.g. GetBaseline() to work below w/out asserting)
|
|
||||||
FinishReflowChild(aItem.Frame(), aPresContext,
|
|
||||||
childDesiredSize, &aChildReflowInput, 0, 0, flags);
|
|
||||||
|
|
||||||
// Save the sizing info that we learned from this reflow
|
// Save the sizing info that we learned from this reflow
|
||||||
// -----------------------------------------------------
|
// -----------------------------------------------------
|
||||||
|
|
@ -3989,7 +4049,7 @@ nsFlexContainerFrame::SizeItemInCrossAxis(
|
||||||
// so we don't bother with making aAxisTracker pick the cross-axis component
|
// so we don't bother with making aAxisTracker pick the cross-axis component
|
||||||
// for us.)
|
// for us.)
|
||||||
nscoord crossAxisBorderPadding = aItem.GetBorderPadding().TopBottom();
|
nscoord crossAxisBorderPadding = aItem.GetBorderPadding().TopBottom();
|
||||||
if (childDesiredSize.Height() < crossAxisBorderPadding) {
|
if (reflowResult.mHeight < crossAxisBorderPadding) {
|
||||||
// Child's requested size isn't large enough for its border/padding!
|
// Child's requested size isn't large enough for its border/padding!
|
||||||
// This is OK for the trivial nsFrame::Reflow() impl, but other frame
|
// This is OK for the trivial nsFrame::Reflow() impl, but other frame
|
||||||
// classes should know better. So, if we get here, the child had better be
|
// classes should know better. So, if we get here, the child had better be
|
||||||
|
|
@ -4002,10 +4062,10 @@ nsFlexContainerFrame::SizeItemInCrossAxis(
|
||||||
aItem.SetCrossSize(0);
|
aItem.SetCrossSize(0);
|
||||||
} else {
|
} else {
|
||||||
// (normal case)
|
// (normal case)
|
||||||
aItem.SetCrossSize(childDesiredSize.Height() - crossAxisBorderPadding);
|
aItem.SetCrossSize(reflowResult.mHeight - crossAxisBorderPadding);
|
||||||
}
|
}
|
||||||
|
|
||||||
aItem.SetAscent(childDesiredSize.BlockStartAscent());
|
aItem.SetAscent(reflowResult.mAscent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
@ -4295,7 +4355,7 @@ nsFlexContainerFrame::DoFlexLayout(nsPresContext* aPresContext,
|
||||||
LogicalSize availSize = aReflowInput.ComputedSize(wm);
|
LogicalSize availSize = aReflowInput.ComputedSize(wm);
|
||||||
availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
|
availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
|
||||||
ReflowInput childReflowInput(aPresContext, aReflowInput,
|
ReflowInput childReflowInput(aPresContext, aReflowInput,
|
||||||
item->Frame(), availSize);
|
item->Frame(), availSize);
|
||||||
if (!sizeOverride) {
|
if (!sizeOverride) {
|
||||||
// Directly override the computed main-size, by tweaking reflow state:
|
// Directly override the computed main-size, by tweaking reflow state:
|
||||||
if (aAxisTracker.IsMainAxisHorizontal()) {
|
if (aAxisTracker.IsMainAxisHorizontal()) {
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ public:
|
||||||
class FlexLine;
|
class FlexLine;
|
||||||
class FlexboxAxisTracker;
|
class FlexboxAxisTracker;
|
||||||
struct StrutInfo;
|
struct StrutInfo;
|
||||||
|
struct CachedMeasuringReflowResult;
|
||||||
|
|
||||||
// nsIFrame overrides
|
// nsIFrame overrides
|
||||||
void Init(nsIContent* aContent,
|
void Init(nsIContent* aContent,
|
||||||
|
|
@ -66,6 +67,8 @@ public:
|
||||||
const nsRect& aDirtyRect,
|
const nsRect& aDirtyRect,
|
||||||
const nsDisplayListSet& aLists) override;
|
const nsDisplayListSet& aLists) override;
|
||||||
|
|
||||||
|
void MarkIntrinsicISizesDirty() override;
|
||||||
|
|
||||||
virtual void Reflow(nsPresContext* aPresContext,
|
virtual void Reflow(nsPresContext* aPresContext,
|
||||||
ReflowOutput& aDesiredSize,
|
ReflowOutput& aDesiredSize,
|
||||||
const ReflowInput& aReflowInput,
|
const ReflowInput& aReflowInput,
|
||||||
|
|
@ -194,6 +197,18 @@ protected:
|
||||||
const ReflowInput& aParentReflowInput,
|
const ReflowInput& aParentReflowInput,
|
||||||
const FlexboxAxisTracker& aAxisTracker);
|
const FlexboxAxisTracker& aAxisTracker);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method gets a cached measuring reflow for a flex item, or does it and
|
||||||
|
* caches it.
|
||||||
|
*
|
||||||
|
* This avoids exponential reflows, see the comment on
|
||||||
|
* CachedMeasuringReflowResult.
|
||||||
|
*/
|
||||||
|
const CachedMeasuringReflowResult& MeasureAscentAndHeightForFlexItem(
|
||||||
|
FlexItem& aItem,
|
||||||
|
nsPresContext* aPresContext,
|
||||||
|
ReflowInput& aChildReflowInput);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method performs a "measuring" reflow to get the content height of
|
* This method performs a "measuring" reflow to get the content height of
|
||||||
* aFlexItem.Frame() (treating it as if it had auto-height), & returns the
|
* aFlexItem.Frame() (treating it as if it had auto-height), & returns the
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue