Implementing align-content for blocks
This documents the principal patch for Gecko bug 1684236, “Implement align-content for block containers”.
- Trigger an independent formatting context
- Align the Contents of nsBlockFrame
- Avoid complications due to fragmentation
- Cache the Alignment Shift
Trigger an independent formatting context
The first, and simplest, thing that This segment adds a test for whether This segment in To support this, the And so is the template in align-content does
is enforce an independent formatting context
by turning the block, if it is not already, into a block formatting context root
so that it fully contains its descendant margins and floats.
Make
nsCSSFrameConstructor Construct a Flow Rootalign-content is normal
to the part of nsCSSFrameConstructor that flags an nsBlockFrame
as being a BFC root.
nsCSSFrameConstructor::ConstructNonScrollableBlock()
diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -4582,19 +4582,21 @@ nsIFrame* nsCSSFrameConstructor::Constru
ComputedStyle* const computedStyle = aItem.mComputedStyle;
// We want a block formatting context root in paginated contexts for
// every block that would be scrollable in a non-paginated context.
// We mark our blocks with a bit here if this condition is true, so
// we can check it later in nsIFrame::ApplyPaginatedOverflowClipping.
bool clipPaginatedOverflow =
(aItem.mFCData->mBits & FCDATA_FORCED_NON_SCROLLABLE_BLOCK) != 0;
+ bool isAligned = computedStyle->StylePosition()->mAlignContent.primary !=
+ mozilla::StyleAlignFlags::NORMAL;
nsFrameState flags = nsFrameState(0);
if ((aDisplay->IsAbsolutelyPositionedStyle() || aDisplay->IsFloatingStyle() ||
- aDisplay->DisplayInside() == StyleDisplayInside::FlowRoot ||
+ aDisplay->DisplayInside() == StyleDisplayInside::FlowRoot || isAligned ||
clipPaginatedOverflow) &&
!aParentFrame->IsInSVGTextSubtree()) {
flags = NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS;
if (clipPaginatedOverflow) {
flags |= NS_BLOCK_CLIP_PAGINATED_OVERFLOW;
}
}
Make Style Changes Trigger Frame Reconstruction
nsStyleStruct.cpp
updates CalcDifference()
to trigger frame reconstruction whenever a block frame
switches between normal and non-normal align-content values,
to support switching between a BFC root or not.
To do this, it needs to also pass in nsStyleDisplay,
to detect whether the frame is a block.
It also tries to skip reconstruction in cases where the block frame
is already forced to be a BFC root by a different property.
nsStyleStruct::CalcDifference()
diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1142,25 +1142,47 @@ static bool IsAutonessEqual(const StyleR
return false;
}
}
return true;
}
nsChangeHint nsStylePosition::CalcDifference(
const nsStylePosition& aNewData,
- const nsStyleVisibility& aOldStyleVisibility) const {
+ const nsStyleVisibility& aOldStyleVisibility,
+ const nsStyleDisplay& aOldStyleDisplay) const {
if (mGridTemplateColumns.IsMasonry() !=
aNewData.mGridTemplateColumns.IsMasonry() ||
mGridTemplateRows.IsMasonry() != aNewData.mGridTemplateRows.IsMasonry()) {
// XXXmats this could be optimized to AllReflowHints with a bit of work,
// but I'll assume this is a very rare use case in practice. (bug 1623886)
return nsChangeHint_ReconstructFrame;
}
+ if ( // alignment switched between normal / non-normal
+ ((mAlignContent.primary == mozilla::StyleAlignFlags::NORMAL) !=
+ (aNewData.mAlignContent.primary == mozilla::StyleAlignFlags::NORMAL)) &&
+ // is a block box
+ aOldStyleDisplay.IsBlockOutsideStyle() &&
+ aOldStyleDisplay.DisplayInside() == mozilla::StyleDisplayInside::Flow &&
+ // not a scroll container (which is always a flow-root anyway)
+ (aOldStyleDisplay.mOverflowX == mozilla::StyleOverflow::Visible ||
+ aOldStyleDisplay.mOverflowX == mozilla::StyleOverflow::Clip) &&
+ (aOldStyleDisplay.mOverflowY == mozilla::StyleOverflow::Visible ||
+ aOldStyleDisplay.mOverflowY == mozilla::StyleOverflow::Clip) &&
+ // not out of flow (which is always a flow-root anyway)
+ aOldStyleDisplay.mFloat == mozilla::StyleFloat::None &&
+ aOldStyleDisplay.mPosition != mozilla::StylePositionProperty::Absolute &&
+ aOldStyleDisplay.mPosition != mozilla::StylePositionProperty::Fixed) {
+ // Change in align-content causes switch between block flow / flow-root
+ return nsChangeHint_ReconstructFrame;
+ // XXX It would be best if we could not do this if we're a flex / grid item,
+ // but we can't tell because that depends on the parent display. :(
+ }
+
nsChangeHint hint = nsChangeHint(0);
// Changes to "z-index" require a repaint.
if (mZIndex != aNewData.mZIndex) {
hint |= nsChangeHint_RepaintFrame;
}
// Changes to "object-fit" & "object-position" require a repaint. They
nsStyleStruct.h is also altered
to pass nsStyleDisplay:
nsStyleStruct.h/CalcDifference()
diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -713,19 +713,19 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
using StyleAlignSelf = mozilla::StyleAlignSelf;
using StyleJustifySelf = mozilla::StyleJustifySelf;
explicit nsStylePosition(const mozilla::dom::Document&);
nsStylePosition(const nsStylePosition& aOther);
~nsStylePosition();
static constexpr bool kHasTriggerImageLoads = false;
- nsChangeHint CalcDifference(
- const nsStylePosition& aNewData,
- const nsStyleVisibility& aOldStyleVisibility) const;
+ nsChangeHint CalcDifference(const nsStylePosition& aNewData,
+ const nsStyleVisibility& aOldStyleVisibility,
+ const nsStyleDisplay& aStyleDisplay) const;
// Returns whether we need to compute an hypothetical position if we were
// absolutely positioned.
bool NeedsHypotheticalPositionIfAbsPos() const {
return (mOffset.Get(mozilla::eSideRight).IsAuto() &&
mOffset.Get(mozilla::eSideLeft).IsAuto()) ||
(mOffset.Get(mozilla::eSideTop).IsAuto() &&
mOffset.Get(mozilla::eSideBottom).IsAuto());
ComputedStyle.cpp:
ComputedStyle.cpp
diff --git a/layout/style/ComputedStyle.cpp b/layout/style/ComputedStyle.cpp
--- a/layout/style/ComputedStyle.cpp
+++ b/layout/style/ComputedStyle.cpp
@@ -162,17 +162,18 @@ nsChangeHint ComputedStyle::CalcStyleDif
DO_STRUCT_DIFFERENCE(Outline);
DO_STRUCT_DIFFERENCE(TableBorder);
DO_STRUCT_DIFFERENCE(Table);
DO_STRUCT_DIFFERENCE(UIReset);
DO_STRUCT_DIFFERENCE(Text);
DO_STRUCT_DIFFERENCE_WITH_ARGS(List, (, *StyleDisplay()));
DO_STRUCT_DIFFERENCE(SVGReset);
DO_STRUCT_DIFFERENCE(SVG);
- DO_STRUCT_DIFFERENCE_WITH_ARGS(Position, (, *StyleVisibility()));
+ DO_STRUCT_DIFFERENCE_WITH_ARGS(Position,
+ (, *StyleVisibility(), *StyleDisplay()));
DO_STRUCT_DIFFERENCE(Font);
DO_STRUCT_DIFFERENCE(Margin);
DO_STRUCT_DIFFERENCE(Padding);
DO_STRUCT_DIFFERENCE(Border);
DO_STRUCT_DIFFERENCE(TextReset);
DO_STRUCT_DIFFERENCE(Effects);
DO_STRUCT_DIFFERENCE(Background);
DO_STRUCT_DIFFERENCE(Page);
Align the Contents of nsBlockFrame
The core of the patch is the This segment adds the call to This segment inserts the definition of The premise of this function is very simple:
There's some fancy caching happening through And of course, we also need to declare the new function in AlignContent() function,
which is called at the end of nsBlockFame::Reflow,
after calculating the final size of the block.
It shifts the contents of the block,
including floats and list marker boxes,
but not absolutely-positioned elements.
nsBlockFrame::Reflow() calling AlignContent()
AlignContent()
right after ComputeFinalSize,
and before we reflow any absolutely-positioned boxes
for which this block is a containing block
(so that their static positions will be correct).
nsBlockFrame::Reflow()
diff --git a/layout/generic/nsBlockFrame.cpp b/layout/generic/nsBlockFrame.cpp
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -1608,16 +1608,19 @@ void nsBlockFrame::Reflow(nsPresContext*
}
CheckFloats(state);
// Compute our final size
nscoord blockEndEdgeOfChildren;
ComputeFinalSize(aReflowInput, state, aMetrics, &blockEndEdgeOfChildren);
+ // Align content
+ AlignContent(state, aMetrics, &blockEndEdgeOfChildren);
+
// If the block direction is right-to-left, we need to update the bounds of
// lines that were placed relative to mContainerSize during reflow, as
// we typically do not know the true container size until we've reflowed all
// its children. So we use a dummy mContainerSize during reflow (see
// BlockReflowState's constructor) and then fix up the positions of the
// lines here, once the final block size is known.
//
// Note that writing-mode:vertical-rl is the only case where the block
nsBlockFrame::AlignContent()
nsBlockFrame::AlignContent()
right after the definition of ComputeFinalSize()
(upon whose calculations it depends).
aState.mAlignContentShift;
ignore this value (assume it’s zero) on your first read-through.
We’ll get back to it later.
nsBlockFrame::AlignContent()
@@ -2159,16 +2167,101 @@ void nsBlockFrame::ComputeFinalSize(cons
if ((ABSURD_SIZE(aMetrics.Width()) || ABSURD_SIZE(aMetrics.Height())) &&
!GetParent()->IsAbsurdSizeAssertSuppressed()) {
ListTag(stdout);
printf(": WARNING: desired:%d,%d\n", aMetrics.Width(), aMetrics.Height());
}
#endif
}
+void nsBlockFrame::AlignContent(BlockReflowState& aState,
+ ReflowOutput& aMetrics,
+ nscoord* aBEndEdgeOfChildren) {
+ if (!HasAllStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS))
+ return; // non-BFC can't have been aligned
+
+ StyleAlignFlags alignment = StylePosition()->mAlignContent.primary;
+ alignment &= ~StyleAlignFlags::FLAG_BITS;
+
+ // Short circuit
+ bool isCentered = alignment == mozilla::StyleAlignFlags::CENTER ||
+ alignment == mozilla::StyleAlignFlags::SPACE_AROUND ||
+ alignment == mozilla::StyleAlignFlags::SPACE_EVENLY;
+ bool isEndAlign = alignment == mozilla::StyleAlignFlags::END ||
+ alignment == mozilla::StyleAlignFlags::FLEX_END ||
+ alignment == mozilla::StyleAlignFlags::LAST_BASELINE;
+ if (!(isEndAlign) && !isCentered // desired shift = 0
+ && !aState.mAlignContentShift) // no cached shift to undo
+ return;
+
+ // NOTE: ComputeFinalSize already called aState.UndoAlignContentShift(),
+ // so metrics no longer include cached shift.
+ // NOTE: Content is currently positioned at cached shift
+ // NOTE: Content has been fragmented against 0-shift assumption.
+
+ // Calculate shift
+ nscoord shift = 0;
+ WritingMode wm = aState.mReflowInput.GetWritingMode();
+ if ((isCentered || isEndAlign) && !mLines.empty() &&
+ aState.mReflowStatus.IsFullyComplete() && !GetPrevInFlow()) {
+ nscoord availB = aState.mReflowInput.AvailableBSize();
+ nscoord endB = aMetrics.BSize(wm) - aState.BorderPadding().BEnd(wm);
+ shift = std::min(availB, endB) - (*aBEndEdgeOfChildren);
+
+ // note: these measures all include start BP, so it subtracts out
+ if (!(StylePosition()->mAlignContent.primary &
+ mozilla::StyleAlignFlags::UNSAFE)) {
+ shift = std::max(0, shift);
+ }
+ if (isCentered) {
+ shift = shift / 2;
+ }
+
+ // Handle limitations due to fragmentation
+ if (availB != NS_UNCONSTRAINEDSIZE) {
+ nscoord maxShift = availB - aState.mOverflowBCoord;
+ if (shift > maxShift) {
+ // avoid needing overflow fragmentation
+ shift = maxShift;
+ // trigger reflow if availB increases
+ aState.mFlags.mHasAlignContentOverflow = true;
+ }
+ }
+ // XXX Should also floor negative shift by distance to top of page
+ // so it doesn't get clipped when printing.
+ }
+ // else: zero shift if start-aligned or if fragmented
+
+ nscoord delta = shift - aState.mAlignContentShift;
+ if (delta) {
+ // Shift children
+ LogicalPoint translation(wm, 0, delta);
+ for (LineIterator line = LinesBegin(), line_end = LinesEnd();
+ line != line_end; ++line) {
+ SlideLine(aState, line, delta);
+ }
+ for (nsIFrame* kid = GetChildList(FrameChildListID::Float).FirstChild();
+ kid; kid = kid->GetNextSibling()) {
+ kid->MovePositionBy(wm, translation);
+ nsContainerFrame::PlaceFrameView(kid);
+ }
+ if (HasOutsideMarker() && !mLines.empty()) {
+ nsIFrame* marker = GetOutsideMarker();
+ marker->MovePositionBy(wm, translation);
+ }
+
+ // Cache shift
+ SetProperty(AlignContentShift(), shift);
+ }
+
+ if (!shift) {
+ RemoveProperty(AlignContentShift());
+ }
+}
+
void nsBlockFrame::ConsiderBlockEndEdgeOfChildren(
OverflowAreas& aOverflowAreas, nscoord aBEndEdgeOfChildren,
const nsStyleDisplay* aDisplay) const {
const auto wm = GetWritingMode();
// Factor in the block-end edge of the children. Child frames will be added
// to the overflow area as we iterate through the lines, but their margins
// won't, so we need to account for block-end margins here.
nsBlockFrame.h.
(Ignore the property declarations for now, we’ll get back to them later.)
nsBlockFrame.h/AlignContent()
diff --git a/layout/generic/nsBlockFrame.h b/layout/generic/nsBlockFrame.h
--- a/layout/generic/nsBlockFrame.h
+++ b/layout/generic/nsBlockFrame.h
@@ -481,16 +481,26 @@ class nsBlockFrame : public nsContainerF
// helper for SlideLine and UpdateLineContainerSize
void MoveChildFramesOfLine(nsLineBox* aLine, nscoord aDeltaBCoord);
void ComputeFinalSize(const ReflowInput& aReflowInput,
BlockReflowState& aState, ReflowOutput& aMetrics,
nscoord* aBEndEdgeOfChildren);
/**
+ * Calculates the necessary shift to honor 'align-content' and applies it.
+ */
+ void AlignContent(BlockReflowState& aState, ReflowOutput& aMetrics,
+ nscoord* aBEndEdgeOfChildren);
+ // Stash the effective align-content shift value between reflows
+ NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(AlignContentShift, nscoord)
+ // Trigger reflow when alignment was limited due to limited AvailableBSize
+ NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(AlignContentOverflow, bool)
+
+ /**
* Helper method for Reflow(). Computes the overflow areas created by our
* children, and includes them into aOverflowAreas.
*/
void ComputeOverflowAreas(mozilla::OverflowAreas& aOverflowAreas,
nscoord aBEndEdgeOfChildren,
const nsStyleDisplay* aDisplay) const;
/**
Avoid Complications Due to Fragmentation
This patch is fragmentation-aware, in that it can cope with fragmentation even in dynamic contexts like resizes of or within a multi-column element. However, it’s approach to fragmentation is very dumb:
- It bails out of alignment if the block or its contents are fragmented
(
aState.mReflowStatus.IsFullyComplete() && !GetPrevInFlow()). - It limits any shift values that would push its contents to overflow the fragmentainer boundary (and thus require them to be fragmented).
nsBlockFrame::AlignContent() Review
+ // Calculate shift
+ nscoord shift = 0;
+ WritingMode wm = aState.mReflowInput.GetWritingMode();
+ if ((isCentered || isEndAlign) && !mLines.empty() &&
+ aState.mReflowStatus.IsFullyComplete() && !GetPrevInFlow()) {
...
+ // Handle limitations due to fragmentation
+ if (availB != NS_UNCONSTRAINEDSIZE) {
+ nscoord maxShift = availB - aState.mOverflowBCoord;
+ if (shift > maxShift) {
+ // avoid needing overflow fragmentation
+ shift = maxShift;
+ // trigger reflow if availB increases
+ aState.mFlags.mHasAlignContentOverflow = true;
+ }
+ }
+ // XXX Should also floor negative shift by distance to top of page
+ // so it doesn't get clipped when printing.
+ }
+ // else: zero shift if start-aligned or if fragmented
Tracking overflow bounds with BlockReflowState.mOverflowBCoord
In order to reference the limits of the block’s potentially overflowing content,
we introduce BlockReflowState.mOverflowBCoord
to track the bottommost limit of its scrollable overflow.
This segment declares mOverflowBCoord as a member of BlockReflowState:
nsBlockReflowState.h/mOverflowBCoord
@@ -338,16 +357,19 @@ class BlockReflowState {
// which we know is adjacent to the top of the block (in other words,
// all lines before it are empty and do not have clearance. This line is
// always before the current line.
nsLineList::iterator mLineAdjacentToTop;
// The current block-direction coordinate in the block
nscoord mBCoord;
+ // The farthest child overflow coordinate in the block so far
+ nscoord mOverflowBCoord;
+
// mBlock's computed logical border+padding with pre-reflow skip sides applied
// (See the constructor and nsIFrame::PreReflowBlockLevelLogicalSkipSides).
LogicalMargin mBorderPadding;
// The overflow areas of all floats placed so far
OverflowAreas mFloatOverflowAreas;
// Previous child. This is used when pulling up a frame to update
This segment increases mOverflowBCoord
to include child scrollable overflow bounds on a line
whose contents are not being reflowed this time:
nsBlockFrame::ReflowDirtyLines()
@@ -3022,16 +3107,22 @@ void nsBlockFrame::ReflowDirtyLines(Bloc
lastLineMovedUp = deltaBCoord < 0;
if (deltaBCoord != 0) {
SlideLine(aState, line, deltaBCoord);
} else {
repositionViews = true;
}
+ mozilla::LogicalRect overflowArea =
+ line->GetOverflowArea(mozilla::OverflowType::Scrollable,
+ line->mWritingMode, aState.mContainerSize);
+ aState.mOverflowBCoord = std::max(aState.mOverflowBCoord,
+ overflowArea.BEnd(line->mWritingMode));
+
NS_ASSERTION(!line->IsDirty() || !line->HasFloats(),
"Possibly stale float cache here!");
if (willReflowAgain && line->IsBlock()) {
// If we're going to reflow everything again, and this line is a block,
// then there is no need to recover float state. The line may contain
// other lines with floats, but in that case RecoverStateFrom would only
// add floats to the float manager. We don't need to do that because
// everything's going to get reflowed again "for real". Calling
This segment increases mOverflowBCoord
to include child scrollable overflow bounds on a line
whose contents are being reflowed this time:
nsBlockFrame::ReflowLine()
@@ -3444,16 +3525,22 @@ void nsBlockFrame::ReflowLine(BlockReflo
// This line is overlapping a float - store the edges marking the area
// between the floats for text-overflow analysis.
aLine->SetFloatEdges(s, e);
}
}
}
}
+ mozilla::LogicalRect overflowArea =
+ aLine->GetOverflowArea(mozilla::OverflowType::Scrollable,
+ aLine->mWritingMode, aState.mContainerSize);
+ aState.mOverflowBCoord =
+ std::max(aState.mOverflowBCoord, overflowArea.BEnd(aLine->mWritingMode));
+
aLine->ClearMovedFragments();
}
nsIFrame* nsBlockFrame::PullFrame(BlockReflowState& aState,
LineIterator aLine) {
// First check our remaining lines.
if (LinesEnd() != aLine.next()) {
return PullFrameFrom(aLine, this, aLine.next());
Tracking reflow needs with AlignContentOverflow
Remember that in AlignContent() we are limiting
the amount of shift in order to prevent overflowing content
from overflowing the edge of the fragmentainer:
nsBlockFrame::AlignContent() Review
+ // Handle limitations due to fragmentation
+ if (availB != NS_UNCONSTRAINEDSIZE) {
+ nscoord maxShift = availB - aState.mOverflowBCoord;
+ if (shift > maxShift) {
+ // avoid needing overflow fragmentation
+ shift = maxShift;
+ // trigger reflow if availB increases
+ aState.mFlags.mHasAlignContentOverflow = true;
+ }
+ }
For this to work correctly, we need to trigger reflow
not only when ReflowInput.AvailableBSize decreases
such that the box’s scrollable overflow no longer fits,
but also when ReflowInput.AvailableBSize increases
such that we need to adjust how much we limit the alignment shift!
To do this, we track boxes that might need this special reflow
by flagging them similar to how we flag boxes that need special reflow
due to containing descendants with clear.
In fact, we re-use the exact same mechanism for this.
This segment renames the relevant frame state bit:
nsFrameStatebits.h
diff --git a/layout/generic/nsFrameStateBits.h b/layout/generic/nsFrameStateBits.h
--- a/layout/generic/nsFrameStateBits.h
+++ b/layout/generic/nsFrameStateBits.h
@@ -536,20 +536,18 @@ FRAME_STATE_BIT(Block, 23, NS_BLOCK_FLOA
(NS_BLOCK_FLOAT_MGR | NS_BLOCK_MARGIN_ROOT)
FRAME_STATE_BIT(Block, 24, NS_BLOCK_HAS_LINE_CURSOR)
FRAME_STATE_BIT(Block, 25, NS_BLOCK_HAS_OVERFLOW_LINES)
FRAME_STATE_BIT(Block, 26, NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)
-// Set on any block that has descendant frames in the normal
-// flow with 'clear' set to something other than 'none'
-// (including <BR CLEAR="..."> frames)
-FRAME_STATE_BIT(Block, 27, NS_BLOCK_HAS_CLEAR_CHILDREN)
+// Set on any block that needs unoptimized reflow for itself or a descendant
+FRAME_STATE_BIT(Block, 27, NS_BLOCK_HAS_SPECIAL_CHILDREN)
// NS_BLOCK_CLIP_PAGINATED_OVERFLOW is only set in paginated prescontexts, on
// blocks which were forced to not have scrollframes but still need to clip
// the display of their kids.
FRAME_STATE_BIT(Block, 28, NS_BLOCK_CLIP_PAGINATED_OVERFLOW)
// NS_BLOCK_HAS_FIRST_LETTER_STYLE means that the block has first-letter style,
// even if it has no actual first-letter frame among its descendants.
Because it's now serving two purposes, we need more information to distinguish them, so we declare two distinct frame properties for this purpose:
This segment declares the frame property HasClearChildren:
nsBlockFrame.h/HasClearChildren
@@ -682,16 +692,21 @@ class nsBlockFrame : public nsContainerF
/** set up the conditions necessary for an resize reflow
* the primary task is to mark the minimumly sufficient lines dirty.
*/
void PrepareResizeReflow(BlockReflowState& aState);
/** reflow all lines that have been marked dirty */
void ReflowDirtyLines(BlockReflowState& aState);
+ // Set on any block that has descendant frames in the normal
+ // flow with 'clear' set to something other than 'none'
+ // (including <BR CLEAR="..."> frames)
+ NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(HasClearChildren, bool)
+
/** Mark a given line dirty due to reflow being interrupted on or before it */
void MarkLineDirtyForInterrupt(nsLineBox* aLine);
//----------------------------------------
// Methods for line reflow
/**
* Reflow a line.
*
This segment declares the frame property AlignContentOverflow:
nsBlockFrame.h Review AlignContentOverflow
/**
+ * Calculates the necessary shift to honor 'align-content' and applies it.
+ */
+ void AlignContent(BlockReflowState& aState, ReflowOutput& aMetrics,
+ nscoord* aBEndEdgeOfChildren);
+ // Stash the effective align-content shift value between reflows
+ NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(AlignContentShift, nscoord)
+ // Trigger reflow when alignment was limited due to limited AvailableBSize
+ NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(AlignContentOverflow, bool)
To track these statuses more easily,
we set up some infrastructure in BlockReflowState.
This segment declares and initializes the BockReflowState::Flags mHasClearChildren and mHasAlignContentOverflow:
BlockReflowState.h/mHasClearChildren, mHasAlignContentOveflow
diff --git a/layout/generic/BlockReflowState.h b/layout/generic/BlockReflowState.h
--- a/layout/generic/BlockReflowState.h
+++ b/layout/generic/BlockReflowState.h
@@ -33,16 +33,18 @@ class BlockReflowState {
Flags()
: mIsBStartMarginRoot(false),
mIsBEndMarginRoot(false),
mShouldApplyBStartMargin(false),
mHasLineAdjacentToTop(false),
mBlockNeedsFloatManager(false),
mIsLineLayoutEmpty(false),
mIsFloatListInBlockPropertyTable(false),
+ mHasClearChildren(false),
+ mHasAlignContentOverflow(false),
mCanHaveOverflowMarkers(false) {}
// Set in the BlockReflowState constructor when reflowing a "block margin
// root" frame (i.e. a frame with the NS_BLOCK_MARGIN_ROOT flag set, for
// which margins apply by default).
//
// The flag is also set when reflowing a frame whose computed BStart border
// padding is non-zero.
@@ -84,28 +86,43 @@ class BlockReflowState {
// Set when nsLineLayout::LineIsEmpty was true at the end of reflowing
// the current line.
bool mIsLineLayoutEmpty : 1;
// Set when our mPushedFloats list is stored on the block's property table.
bool mIsFloatListInBlockPropertyTable : 1;
+ // Set when we have descendants with 'clear'
+ bool mHasClearChildren : 1;
+
+ // Set when we truncated alignment to avoid overflowing the fragmentainer
+ bool mHasAlignContentOverflow : 1;
+
// Set when we need text-overflow or -webkit-line-clamp processing.
bool mCanHaveOverflowMarkers : 1;
};
This segment defines the BlockReflowState destructor
to manage setting and removing the NS_BLOCK_HAS_SPECIAL_CHILDREN frame bit
and the HasClearChildren and AlignContentOverflow frame properties
based on the BlockReflowState’s mHasClearChildren and mHasAlignContentOverflowflags.
BlockReflowState::~BlockReflowState()
+BlockReflowState::~BlockReflowState() {
+ // annotate conditions that require Reflow to inspect deeper
+ if (mFlags.mHasClearChildren || mFlags.mHasAlignContentOverflow) {
+ // this lookup is frequent, so we note it in frame bits
+ mBlock->AddStateBits(NS_BLOCK_HAS_SPECIAL_CHILDREN);
+ // positive cases are rare, so distinguish in frame properties
+ if (mFlags.mHasClearChildren)
+ mBlock->SetProperty(nsBlockFrame::HasClearChildren(), true);
+ if (mFlags.mHasAlignContentOverflow)
+ mBlock->SetProperty(nsBlockFrame::AlignContentOverflow(), true);
+ } else if (mBlock->HasAnyStateBits(NS_BLOCK_HAS_SPECIAL_CHILDREN)) {
+ mBlock->RemoveStateBits(NS_BLOCK_HAS_SPECIAL_CHILDREN);
+ mBlock->RemoveProperty(nsBlockFrame::HasClearChildren());
+ mBlock->RemoveProperty(nsBlockFrame::AlignContentOverflow());
+ }
+}
+
And now we update the nsBlockFrame call sites
to use the new flag system,
refactoring the LineHasClear() pattern to use a new
BlockReflowState::CaptureSpecialReflowNeeds() function.
This segment updates the nsBlockFrame::ReflowDirtyLines()
to use the new special reflow system:
nsBlockFrame::ReflowDirtyLines()
@@ -2614,17 +2698,18 @@ static bool LinesAreEmpty(const nsLineLi
}
}
return true;
}
void nsBlockFrame::ReflowDirtyLines(BlockReflowState& aState) {
bool keepGoing = true;
bool repositionViews = false; // should we really need this?
- bool foundAnyClears = aState.mTrailingClearFromPIF != StyleClear::None;
+ aState.mFlags.mHasClearChildren =
+ aState.mTrailingClearFromPIF != StyleClear::None;
bool willReflowAgain = false;
#ifdef DEBUG
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": reflowing dirty lines");
printf(" computedISize=%d\n", aState.mReflowInput.ComputedISize());
@@ -2708,17 +2793,17 @@ void nsBlockFrame::ReflowDirtyLines(Bloc
if (selfDirty) {
line->MarkDirty();
}
// This really sucks, but we have to look inside any blocks that have clear
// elements inside them.
// XXX what can we do smarter here?
if (!line->IsDirty() && line->IsBlock() &&
- line->mFirstChild->HasAnyStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN)) {
+ line->mFirstChild->HasAnyStateBits(NS_BLOCK_HAS_SPECIAL_CHILDREN)) {
line->MarkDirty();
}
nsIFrame* floatAvoidingBlock = nullptr;
if (line->IsBlock() &&
!nsBlockFrame::BlockCanIntersectFloats(line->mFirstChild)) {
floatAvoidingBlock = line->mFirstChild;
}
@@ -3069,19 +3160,17 @@ void nsBlockFrame::ReflowDirtyLines(Bloc
// Record if we need to clear floats before reflowing the next
// line. Note that inlineFloatClearType will be handled and
// cleared before the next line is processed, so there is no
// need to combine break types here.
if (line->HasFloatClearTypeAfter()) {
inlineFloatClearType = line->FloatClearTypeAfter();
}
- if (LineHasClear(line.get())) {
- foundAnyClears = true;
- }
+ aState.CaptureSpecialReflowNeeds(line.get());
DumpLine(aState, line, deltaBCoord, -1);
if (aState.mPresContext->HasPendingInterrupt()) {
willReflowAgain = true;
// Another option here might be to leave |line| clean if
// !HasPendingInterrupt() before the CheckForInterrupt() call, since in
// that case the line really did reflow as it should have. Not sure
@@ -3250,19 +3339,17 @@ void nsBlockFrame::ReflowDirtyLines(Bloc
DumpLine(aState, line, deltaBCoord, -1);
if (!keepGoing) {
if (0 == line->GetChildCount()) {
DeleteLine(aState, line, line_end);
}
break;
}
- if (LineHasClear(line.get())) {
- foundAnyClears = true;
- }
+ aState.CaptureSpecialReflowNeeds(line.get());
if (aState.mPresContext->CheckForInterrupt(this)) {
MarkLineDirtyForInterrupt(line);
break;
}
// If this is an inline frame then its time to stop
++line;
@@ -3321,22 +3408,16 @@ void nsBlockFrame::ReflowDirtyLines(Bloc
}
}
}
if (LinesAreEmpty(mLines) && ShouldHaveLineIfEmpty()) {
aState.mBCoord += aState.mMinLineHeight;
}
- if (foundAnyClears) {
- AddStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN);
- } else {
- RemoveStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN);
- }
-
#ifdef DEBUG
VerifyLines(true);
VerifyOverflowSituation();
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent - 1);
ListTag(stdout);
printf(": done reflowing dirty lines (status=%s)\n",
ToString(aState.mReflowStatus).c_str());
This segment removes the old LineHasClear() helper function:
nsBlockFrame.cpp/LineHasClear()
@@ -2553,25 +2646,16 @@ void nsBlockFrame::PropagateFloatDamage(
// changes, but that's not straightforward to check
if (wasImpactedByFloat || floatAvailableSpace.HasFloats()) {
aLine->MarkDirty();
}
}
}
}
-static bool LineHasClear(nsLineBox* aLine) {
- return aLine->IsBlock()
- ? (aLine->HasForcedLineBreakBefore() ||
- aLine->mFirstChild->HasAnyStateBits(
- NS_BLOCK_HAS_CLEAR_CHILDREN) ||
- !nsBlockFrame::BlockCanIntersectFloats(aLine->mFirstChild))
- : aLine->HasFloatClearTypeAfter();
-}
-
/**
* Reparent a whole list of floats from aOldParent to this block. The
* floats might be taken from aOldParent's overflow list. They will be
* removed from the list. They end up appended to our mFloats list.
*/
void nsBlockFrame::ReparentFloats(nsIFrame* aFirstFrame,
nsBlockFrame* aOldParent,
bool aReparentSiblings) {
This segment defines the new CaptureSpecialReflowNeeds() helper function:
BlockReflowState::CaptureSpecialReflowNeeds()
+
+void BlockReflowState::CaptureSpecialReflowNeeds(nsLineBox* aLine) {
+ if (aLine->IsBlock()) {
+ nsIFrame* child = aLine->mFirstChild;
+ bool hasClear = false;
+ if (child->HasAnyStateBits(NS_BLOCK_HAS_SPECIAL_CHILDREN)) {
+ mFlags.mHasAlignContentOverflow =
+ child->GetProperty(nsBlockFrame::AlignContentOverflow());
+ hasClear = child->GetProperty(nsBlockFrame::HasClearChildren());
+ }
+ mFlags.mHasClearChildren = hasClear || aLine->HasForcedLineBreakBefore() ||
+ !nsBlockFrame::BlockCanIntersectFloats(child);
+ } else {
+ mFlags.mHasClearChildren = aLine->HasFloatClearTypeAfter();
+ }
+}
This segment declares the new CaptureSpecialReflowNeeds() helper function
in the BlockReflowState class definition:
BlockReflowState.h/CaptureSpecialReflowNeeds()
@@ -232,16 +249,18 @@ class BlockReflowState {
void AdvanceToNextLine() {
if (mFlags.mIsLineLayoutEmpty) {
mFlags.mIsLineLayoutEmpty = false;
} else {
mLineNumber++;
}
}
+ void CaptureSpecialReflowNeeds(nsLineBox* aLine);
+
//----------------------------------------
// This state is the "global" state computed once for the reflow of
// the block.
// The block frame that is using this object
nsBlockFrame* mBlock;
Cache the Alignment Shift
Since we don't want to re-position all the frames twice
(which could affect thousands of children)
every time we run nsBlockFrame::Reflow(),
we start reflow by assuming that the alignment shift this time
is going to be the same alignment shift as last time.
This way, if nothing else changed in the block axis,
then none of the children move at all.
So we need to start layout at the shifted start position,
rather than at the content edge of the block.
However if something changed, we need to recalculate the shift
and move the boxes accordingly.
Remember that the AlignContent() function handles this
by calculating the delta and shifting things accordingly.
Also if the fragmentation limit
(AvailableBSize) changed,
we need to make sure we fragment against the new limit from an unshifted position,
even though we're positioned at a shifted position!
To manage all this, we introduce a new frame property, AlignContentShift,
to cache the shift amount;
and some infrastructure in BlockReflowState to manage it.
First, BlockReflowState applies the shift to all of our reflow coordinate tracking in its constructor;
then it provides a rewind function which is called by ComputeFinalSize()
so that it can actually compute an accurate final size
(which is then inputted into AlignContent() to calculate the shift value).
To do this, we need a mutable ReflowInput
(because we need to modify its AvailableBSize);
this is why the earlier supporting patch in the series
does some refactoring in nsBlockFrame::Reflow()
to make that more straightforward to manage.
This segment initializes BlockReflowState::mAlignContentShift and defines the BlockReflowState::UndoAlignContentShift() helper function:
BlockReflowState::BlockReflowState() and BlockReflowState::UndoAlignContentShift()
diff --git a/layout/generic/BlockReflowState.cpp b/layout/generic/BlockReflowState.cpp
--- a/layout/generic/BlockReflowState.cpp
+++ b/layout/generic/BlockReflowState.cpp
@@ -43,17 +43,18 @@ BlockReflowState::BlockReflowState(const
mBorderPadding(
mReflowInput
.ComputedLogicalBorderPadding(mReflowInput.GetWritingMode())
.ApplySkipSides(aFrame->PreReflowBlockLevelLogicalSkipSides())),
mPrevBEndMargin(),
mMinLineHeight(aReflowInput.GetLineHeight()),
mLineNumber(0),
mTrailingClearFromPIF(StyleClear::None),
- mConsumedBSize(aConsumedBSize) {
+ mConsumedBSize(aConsumedBSize),
+ mAlignContentShift(0) {
NS_ASSERTION(mConsumedBSize != NS_UNCONSTRAINEDSIZE,
"The consumed block-size should be constrained!");
WritingMode wm = aReflowInput.GetWritingMode();
// Note that mContainerSize is the physical size, needed to
// convert logical block-coordinates in vertical-rl writing mode
// (measured from a RHS origin) to physical coordinates within the
@@ -130,20 +131,88 @@ BlockReflowState::BlockReflowState(const
} else {
// When we are not in a paginated situation, then we always use a
// unconstrained block-size.
mContentArea.BSize(wm) = NS_UNCONSTRAINEDSIZE;
}
mContentArea.IStart(wm) = mBorderPadding.IStart(wm);
mBCoord = mContentArea.BStart(wm) = mBorderPadding.BStart(wm);
+ if (mBlock->HasAllStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS)) {
+ // We can skip this lookup on non-BFC frames; they can't be aligned.
+
+ // Cache the lookup
+ mAlignContentShift = aFrame->GetProperty(nsBlockFrame::AlignContentShift());
+
+ // Account for cached shift ; re-position in AlignContent() if needed
+ if (mAlignContentShift) {
+ mBCoord += mAlignContentShift;
+ mOverflowBCoord += mAlignContentShift;
+ mContentArea.BStart(wm) += mAlignContentShift;
+
+ if (mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) {
+ const_cast<ReflowInput&>(mReflowInput)
+ .SetAvailableBSize(mReflowInput.AvailableBSize() +
+ mAlignContentShift);
+ mContentArea.BSize(wm) += mAlignContentShift;
+ }
+ }
+ }
+
mPrevChild = nullptr;
mCurrentLine = aFrame->LinesEnd();
}
+void BlockReflowState::UndoAlignContentShift() {
+ if (!mAlignContentShift) return;
+
+ mBCoord -= mAlignContentShift;
+ mOverflowBCoord -= mAlignContentShift;
+ mContentArea.BStart(mReflowInput.GetWritingMode()) -= mAlignContentShift;
+
+ if (mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) {
+ const_cast<ReflowInput&>(mReflowInput)
+ .SetAvailableBSize(mReflowInput.AvailableBSize() - mAlignContentShift);
+ mContentArea.BSize(mReflowInput.GetWritingMode()) -= mAlignContentShift;
+ }
+}
This segment declares the BlockReflowState::UndoAlignContentShift() helper function:
BlockReflowState.h/UndoAlignContentShift()
public:
BlockReflowState(const ReflowInput& aReflowInput, nsPresContext* aPresContext,
nsBlockFrame* aFrame, bool aBStartMarginRoot,
bool aBEndMarginRoot, bool aBlockNeedsFloatManager,
const nscoord aConsumedBSize,
const nscoord aEffectiveContentBoxBSize);
/**
+ * Unshifts coords, restores availableBSize to reality.
+ * (Constructor applies any cached shift before reflow
+ * so that frames are reflowed with cached shift)
+ */
+ void UndoAlignContentShift();
+
+ ~BlockReflowState();
+
+ /**
* Get the available reflow space (the area not occupied by floats)
* for the current y coordinate. The available space is relative to
* our coordinate system, which is the content box, with (0, 0) in the
* upper left.
*
* Returns whether there are floats present at the given block-direction
* coordinate and within the inline size of the content rect.
*
This segment declares BlockReflowState::mAlignContentShift:
BlockReflowState.h/mAlignContentShift
@@ -392,16 +414,21 @@ class BlockReflowState {
// Cache the result of nsBlockFrame::FindTrailingClear() from mBlock's
// prev-in-flows. See nsBlockFrame::ReflowPushedFloats().
StyleClear mTrailingClearFromPIF;
// The amount of computed content block-size "consumed" by our previous
// continuations.
const nscoord mConsumedBSize;
+ // The amount of block-axis alignment shift to assume during reflow.
+ // Cached between reflows in the AlignContentShift property.
+ // (This system optimizes reflow for not changing the shift.)
+ nscoord mAlignContentShift;
+
// Cache the current line's BSize if nsBlockFrame::PlaceLine() fails to
// place the line. When redoing the line, it will be used to query the
// accurate float available space in AddFloat() and
// nsBlockFrame::PlaceLine().
Maybe<nscoord> mLineBSize;
private:
bool CanPlaceFloat(nscoord aFloatISize,
This segment calss BlockReflowState::UndoAlignContentShift() from halfway through ComputeFinalSize(),
after we compute the blockEndEdgeOfChildren
and before we compute the final metrics:
nsBlockFrame::ComputeFinalSize()
@@ -1970,16 +1973,21 @@ void nsBlockFrame::ComputeFinalSize(cons
// block-end margin of any floated elements; e.g., inside a table cell.
//
// Note: The block coordinate returned by ClearFloats is always greater than
// or equal to blockEndEdgeOfChildren.
std::tie(blockEndEdgeOfChildren, std::ignore) =
aState.ClearFloats(blockEndEdgeOfChildren, StyleClear::Both);
}
+ // undo cached alignment shift for sizing purposes
+ // (we used shifted positions because the float manager uses them)
+ blockEndEdgeOfChildren -= aState.mAlignContentShift;
+ aState.UndoAlignContentShift();
+
if (NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) {
// Note: We don't use blockEndEdgeOfChildren because it includes the
// previous margin.
const nscoord contentBSizeWithBStartBP =
aState.mBCoord + nonCarriedOutBDirMargin;
// We don't care about ApplyLineClamp's return value (the line-clamped
// content BSize) in this explicit-BSize codepath, but we do still need to
The rest of the handling for the cached value is in nsBlockFrame::AlignContent(),
see above,
this time paying attention to mAlignContentShift. :)
This concludes our tour of the patch. There are some additional notes for follow-up improvements in this file and Bug 1499443.