summaryrefslogtreecommitdiffstats
path: root/layout/generic/BlockReflowState.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--layout/generic/BlockReflowState.cpp1090
1 files changed, 1090 insertions, 0 deletions
diff --git a/layout/generic/BlockReflowState.cpp b/layout/generic/BlockReflowState.cpp
new file mode 100644
index 0000000000..0060487c9e
--- /dev/null
+++ b/layout/generic/BlockReflowState.cpp
@@ -0,0 +1,1090 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* state used in reflow of block frames */
+
+#include "BlockReflowState.h"
+
+#include <algorithm>
+#include "LayoutLogging.h"
+#include "nsBlockFrame.h"
+#include "nsLineLayout.h"
+#include "nsPresContext.h"
+#include "nsIFrameInlines.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "TextOverflow.h"
+
+#ifdef DEBUG
+# include "nsBlockDebugFlags.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::layout;
+
+BlockReflowState::BlockReflowState(const ReflowInput& aReflowInput,
+ nsPresContext* aPresContext,
+ nsBlockFrame* aFrame, bool aBStartMarginRoot,
+ bool aBEndMarginRoot,
+ bool aBlockNeedsFloatManager,
+ const nscoord aConsumedBSize,
+ const nscoord aEffectiveContentBoxBSize)
+ : mBlock(aFrame),
+ mPresContext(aPresContext),
+ mReflowInput(aReflowInput),
+ mContentArea(aReflowInput.GetWritingMode()),
+ mPushedFloats(nullptr),
+ mOverflowTracker(nullptr),
+ mBorderPadding(
+ mReflowInput
+ .ComputedLogicalBorderPadding(mReflowInput.GetWritingMode())
+ .ApplySkipSides(aFrame->PreReflowBlockLevelLogicalSkipSides())),
+ mPrevBEndMargin(),
+ mMinLineHeight(aReflowInput.GetLineHeight()),
+ mLineNumber(0),
+ mTrailingClearFromPIF(StyleClear::None),
+ mConsumedBSize(aConsumedBSize) {
+ 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
+ // containing block.
+ // If aReflowInput doesn't have a constrained ComputedWidth(), we set
+ // mContainerSize.width to zero, which means lines will be positioned
+ // (physically) incorrectly; we will fix them up at the end of
+ // nsBlockFrame::Reflow, after we know the total block-size of the
+ // frame.
+ mContainerSize.width = aReflowInput.ComputedWidth();
+ if (mContainerSize.width == NS_UNCONSTRAINEDSIZE) {
+ mContainerSize.width = 0;
+ }
+
+ mContainerSize.width += mBorderPadding.LeftRight(wm);
+
+ // For now at least, we don't do that fix-up for mContainerHeight.
+ // It's only used in nsBidiUtils::ReorderFrames for vertical rtl
+ // writing modes, which aren't fully supported for the time being.
+ mContainerSize.height =
+ aReflowInput.ComputedHeight() + mBorderPadding.TopBottom(wm);
+
+ if (aBStartMarginRoot || 0 != mBorderPadding.BStart(wm)) {
+ mFlags.mIsBStartMarginRoot = true;
+ mFlags.mShouldApplyBStartMargin = true;
+ }
+ if (aBEndMarginRoot || 0 != mBorderPadding.BEnd(wm)) {
+ mFlags.mIsBEndMarginRoot = true;
+ }
+ if (aBlockNeedsFloatManager) {
+ mFlags.mBlockNeedsFloatManager = true;
+ }
+
+ mFlags.mCanHaveOverflowMarkers = css::TextOverflow::CanHaveOverflowMarkers(
+ mBlock, css::TextOverflow::BeforeReflow::Yes);
+
+ MOZ_ASSERT(FloatManager(),
+ "Float manager should be valid when creating BlockReflowState!");
+
+ // Save the coordinate system origin for later.
+ FloatManager()->GetTranslation(mFloatManagerI, mFloatManagerB);
+ FloatManager()->PushState(&mFloatManagerStateBefore); // never popped
+
+ mNextInFlow = static_cast<nsBlockFrame*>(mBlock->GetNextInFlow());
+
+ LAYOUT_WARN_IF_FALSE(NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedISize(),
+ "have unconstrained width; this should only result "
+ "from very large sizes, not attempts at intrinsic "
+ "width calculation");
+ mContentArea.ISize(wm) = aReflowInput.ComputedISize();
+
+ // Compute content area block-size. Unlike the inline-size, if we have a
+ // specified style block-size, we ignore it since extra content is managed by
+ // the "overflow" property. When we don't have a specified style block-size,
+ // then we may end up limiting our block-size if the available block-size is
+ // constrained (this situation occurs when we are paginated).
+ if (const nscoord availableBSize = aReflowInput.AvailableBSize();
+ availableBSize != NS_UNCONSTRAINEDSIZE) {
+ // We are in a paginated situation. The block-end edge of the available
+ // space to reflow the children is within our block-end border and padding.
+ // If we're cloning our border and padding, and we're going to request
+ // additional continuations because of our excessive content-box block-size,
+ // then reserve some of our available space for our (cloned) block-end
+ // border and padding.
+ const bool reserveSpaceForBlockEndBP =
+ mReflowInput.mStyleBorder->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone &&
+ (aEffectiveContentBoxBSize == NS_UNCONSTRAINEDSIZE ||
+ aEffectiveContentBoxBSize + mBorderPadding.BStartEnd(wm) >
+ availableBSize);
+ const nscoord bp = reserveSpaceForBlockEndBP ? mBorderPadding.BStartEnd(wm)
+ : mBorderPadding.BStart(wm);
+ mContentArea.BSize(wm) = std::max(0, availableBSize - bp);
+ } 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);
+
+ mPrevChild = nullptr;
+ mCurrentLine = aFrame->LinesEnd();
+}
+
+void BlockReflowState::ComputeFloatAvoidingOffsets(
+ nsIFrame* aFloatAvoidingBlock, const LogicalRect& aFloatAvailableSpace,
+ nscoord& aIStartResult, nscoord& aIEndResult) const {
+ WritingMode wm = mReflowInput.GetWritingMode();
+ // The frame is clueless about the float manager and therefore we
+ // only give it free space. An example is a table frame - the
+ // tables do not flow around floats.
+ // However, we can let its margins intersect floats.
+ NS_ASSERTION(aFloatAvailableSpace.IStart(wm) >= mContentArea.IStart(wm),
+ "bad avail space rect inline-coord");
+ NS_ASSERTION(aFloatAvailableSpace.ISize(wm) == 0 ||
+ aFloatAvailableSpace.IEnd(wm) <= mContentArea.IEnd(wm),
+ "bad avail space rect inline-size");
+
+ nscoord iStartOffset, iEndOffset;
+ if (aFloatAvailableSpace.ISize(wm) == mContentArea.ISize(wm)) {
+ // We don't need to compute margins when there are no floats around.
+ iStartOffset = 0;
+ iEndOffset = 0;
+ } else {
+ const LogicalMargin frameMargin =
+ SizeComputationInput(aFloatAvoidingBlock,
+ mReflowInput.mRenderingContext, wm,
+ mContentArea.ISize(wm))
+ .ComputedLogicalMargin(wm);
+
+ nscoord iStartFloatIOffset =
+ aFloatAvailableSpace.IStart(wm) - mContentArea.IStart(wm);
+ iStartOffset = std::max(iStartFloatIOffset, frameMargin.IStart(wm)) -
+ frameMargin.IStart(wm);
+ iStartOffset = std::max(iStartOffset, 0); // in case of negative margin
+ nscoord iEndFloatIOffset =
+ mContentArea.IEnd(wm) - aFloatAvailableSpace.IEnd(wm);
+ iEndOffset =
+ std::max(iEndFloatIOffset, frameMargin.IEnd(wm)) - frameMargin.IEnd(wm);
+ iEndOffset = std::max(iEndOffset, 0); // in case of negative margin
+ }
+ aIStartResult = iStartOffset;
+ aIEndResult = iEndOffset;
+}
+
+LogicalRect BlockReflowState::ComputeBlockAvailSpace(
+ nsIFrame* aFrame, const nsFlowAreaRect& aFloatAvailableSpace,
+ bool aBlockAvoidsFloats) {
+#ifdef REALLY_NOISY_REFLOW
+ printf("CBAS frame=%p has floats %d\n", aFrame,
+ aFloatAvailableSpace.HasFloats());
+#endif
+ WritingMode wm = mReflowInput.GetWritingMode();
+ LogicalRect result(wm);
+ result.BStart(wm) = mBCoord;
+ // Note: ContentBSize() and ContentBEnd() are not our content-box size and its
+ // block-end edge. They really mean "the available block-size for children",
+ // and "the block-end edge of the available space for children".
+ result.BSize(wm) = ContentBSize() == NS_UNCONSTRAINEDSIZE
+ ? NS_UNCONSTRAINEDSIZE
+ : ContentBEnd() - mBCoord;
+ // mBCoord might be greater than ContentBEnd() if the block's top margin
+ // pushes it off the page/column. Negative available block-size can confuse
+ // other code and is nonsense in principle.
+
+ // XXX Do we really want this condition to be this restrictive (i.e.,
+ // more restrictive than it used to be)? The |else| here is allowed
+ // by the CSS spec, but only out of desperation given implementations,
+ // and the behavior it leads to is quite undesirable (it can cause
+ // things to become extremely narrow when they'd fit quite well a
+ // little bit lower). Should the else be a quirk or something that
+ // applies to a specific set of frame classes and no new ones?
+ // If we did that, then for those frames where the condition below is
+ // true but nsBlockFrame::BlockCanIntersectFloats is false,
+ // nsBlockFrame::ISizeToClearPastFloats would need to use the
+ // shrink-wrap formula, max(MinISize, min(avail width, PrefISize))
+ // rather than just using MinISize.
+ NS_ASSERTION(
+ nsBlockFrame::BlockCanIntersectFloats(aFrame) == !aBlockAvoidsFloats,
+ "unexpected replaced width");
+ if (!aBlockAvoidsFloats) {
+ if (aFloatAvailableSpace.HasFloats()) {
+ // Use the float-edge property to determine how the child block
+ // will interact with the float.
+ const nsStyleBorder* borderStyle = aFrame->StyleBorder();
+ switch (borderStyle->mFloatEdge) {
+ default:
+ case StyleFloatEdge::ContentBox: // content and only content does
+ // runaround of floats
+ // The child block will flow around the float. Therefore
+ // give it all of the available space.
+ result.IStart(wm) = mContentArea.IStart(wm);
+ result.ISize(wm) = mContentArea.ISize(wm);
+ break;
+ case StyleFloatEdge::MarginBox: {
+ // The child block's margins should be placed adjacent to,
+ // but not overlap the float.
+ result.IStart(wm) = aFloatAvailableSpace.mRect.IStart(wm);
+ result.ISize(wm) = aFloatAvailableSpace.mRect.ISize(wm);
+ } break;
+ }
+ } else {
+ // Since there are no floats present the float-edge property
+ // doesn't matter therefore give the block element all of the
+ // available space since it will flow around the float itself.
+ result.IStart(wm) = mContentArea.IStart(wm);
+ result.ISize(wm) = mContentArea.ISize(wm);
+ }
+ } else {
+ nscoord iStartOffset, iEndOffset;
+ ComputeFloatAvoidingOffsets(aFrame, aFloatAvailableSpace.mRect,
+ iStartOffset, iEndOffset);
+ result.IStart(wm) = mContentArea.IStart(wm) + iStartOffset;
+ result.ISize(wm) = mContentArea.ISize(wm) - iStartOffset - iEndOffset;
+ }
+
+#ifdef REALLY_NOISY_REFLOW
+ printf(" CBAS: result %d %d %d %d\n", result.IStart(wm), result.BStart(wm),
+ result.ISize(wm), result.BSize(wm));
+#endif
+
+ return result;
+}
+
+LogicalSize BlockReflowState::ComputeAvailableSizeForFloat() const {
+ const auto wm = mReflowInput.GetWritingMode();
+ const nscoord availBSize = ContentBSize() == NS_UNCONSTRAINEDSIZE
+ ? NS_UNCONSTRAINEDSIZE
+ : std::max(0, ContentBEnd() - mBCoord);
+ return LogicalSize(wm, ContentISize(), availBSize);
+}
+
+bool BlockReflowState::FloatAvoidingBlockFitsInAvailSpace(
+ nsIFrame* aFloatAvoidingBlock,
+ const nsFlowAreaRect& aFloatAvailableSpace) const {
+ if (!aFloatAvailableSpace.HasFloats()) {
+ // If there aren't any floats here, then we always fit.
+ // We check this before calling ISizeToClearPastFloats, which is
+ // somewhat expensive.
+ return true;
+ }
+
+ // |aFloatAvailableSpace| was computed as having a negative size, which means
+ // there are floats on both sides pushing inwards past each other, and
+ // |aFloatAvoidingBlock| would necessarily intersect a float if we put it
+ // here. So, it doesn't fit.
+ if (aFloatAvailableSpace.ISizeIsActuallyNegative()) {
+ return false;
+ }
+
+ WritingMode wm = mReflowInput.GetWritingMode();
+ nsBlockFrame::FloatAvoidingISizeToClear replacedISize =
+ nsBlockFrame::ISizeToClearPastFloats(*this, aFloatAvailableSpace.mRect,
+ aFloatAvoidingBlock);
+ // The inline-start side of the replaced element should be offset by
+ // the larger of the float intrusion or the replaced element's own
+ // start margin. The inline-end side is similar, except for Web
+ // compatibility we ignore the margin.
+ return std::max(
+ aFloatAvailableSpace.mRect.IStart(wm) - mContentArea.IStart(wm),
+ replacedISize.marginIStart) +
+ replacedISize.borderBoxISize +
+ (mContentArea.IEnd(wm) - aFloatAvailableSpace.mRect.IEnd(wm)) <=
+ mContentArea.ISize(wm);
+}
+
+nsFlowAreaRect BlockReflowState::GetFloatAvailableSpaceWithState(
+ nscoord aBCoord, ShapeType aShapeType,
+ nsFloatManager::SavedState* aState) const {
+ WritingMode wm = mReflowInput.GetWritingMode();
+#ifdef DEBUG
+ // Verify that the caller setup the coordinate system properly
+ nscoord wI, wB;
+ FloatManager()->GetTranslation(wI, wB);
+
+ NS_ASSERTION((wI == mFloatManagerI) && (wB == mFloatManagerB),
+ "bad coord system");
+#endif
+
+ nscoord blockSize = (mContentArea.BSize(wm) == nscoord_MAX)
+ ? nscoord_MAX
+ : std::max(mContentArea.BEnd(wm) - aBCoord, 0);
+ nsFlowAreaRect result = FloatManager()->GetFlowArea(
+ wm, aBCoord, blockSize, BandInfoType::BandFromPoint, aShapeType,
+ mContentArea, aState, ContainerSize());
+ // Keep the inline size >= 0 for compatibility with nsSpaceManager.
+ if (result.mRect.ISize(wm) < 0) {
+ result.mRect.ISize(wm) = 0;
+ }
+
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyReflow) {
+ nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
+ printf("%s: band=%d,%d,%d,%d hasfloats=%d\n", __func__,
+ result.mRect.IStart(wm), result.mRect.BStart(wm),
+ result.mRect.ISize(wm), result.mRect.BSize(wm), result.HasFloats());
+ }
+#endif
+ return result;
+}
+
+nsFlowAreaRect BlockReflowState::GetFloatAvailableSpaceForBSize(
+ nscoord aBCoord, nscoord aBSize, nsFloatManager::SavedState* aState) const {
+ WritingMode wm = mReflowInput.GetWritingMode();
+#ifdef DEBUG
+ // Verify that the caller setup the coordinate system properly
+ nscoord wI, wB;
+ FloatManager()->GetTranslation(wI, wB);
+
+ NS_ASSERTION((wI == mFloatManagerI) && (wB == mFloatManagerB),
+ "bad coord system");
+#endif
+ nsFlowAreaRect result = FloatManager()->GetFlowArea(
+ wm, aBCoord, aBSize, BandInfoType::WidthWithinHeight,
+ ShapeType::ShapeOutside, mContentArea, aState, ContainerSize());
+ // Keep the width >= 0 for compatibility with nsSpaceManager.
+ if (result.mRect.ISize(wm) < 0) {
+ result.mRect.ISize(wm) = 0;
+ }
+
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyReflow) {
+ nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
+ printf("%s: space=%d,%d,%d,%d hasfloats=%d\n", __func__,
+ result.mRect.IStart(wm), result.mRect.BStart(wm),
+ result.mRect.ISize(wm), result.mRect.BSize(wm), result.HasFloats());
+ }
+#endif
+ return result;
+}
+
+/*
+ * Reconstruct the vertical margin before the line |aLine| in order to
+ * do an incremental reflow that begins with |aLine| without reflowing
+ * the line before it. |aLine| may point to the fencepost at the end of
+ * the line list, and it is used this way since we (for now, anyway)
+ * always need to recover margins at the end of a block.
+ *
+ * The reconstruction involves walking backward through the line list to
+ * find any collapsed margins preceding the line that would have been in
+ * the reflow input's |mPrevBEndMargin| when we reflowed that line in
+ * a full reflow (under the rule in CSS2 that all adjacent vertical
+ * margins of blocks collapse).
+ */
+void BlockReflowState::ReconstructMarginBefore(nsLineList::iterator aLine) {
+ mPrevBEndMargin.Zero();
+ nsBlockFrame* block = mBlock;
+
+ nsLineList::iterator firstLine = block->LinesBegin();
+ for (;;) {
+ --aLine;
+ if (aLine->IsBlock()) {
+ mPrevBEndMargin = aLine->GetCarriedOutBEndMargin();
+ break;
+ }
+ if (!aLine->IsEmpty()) {
+ break;
+ }
+ if (aLine == firstLine) {
+ // If the top margin was carried out (and thus already applied),
+ // set it to zero. Either way, we're done.
+ if (!mFlags.mIsBStartMarginRoot) {
+ mPrevBEndMargin.Zero();
+ }
+ break;
+ }
+ }
+}
+
+void BlockReflowState::SetupPushedFloatList() {
+ MOZ_ASSERT(!mFlags.mIsFloatListInBlockPropertyTable == !mPushedFloats,
+ "flag mismatch");
+ if (!mFlags.mIsFloatListInBlockPropertyTable) {
+ // If we're being re-Reflow'd without our next-in-flow having been
+ // reflowed, some pushed floats from our previous reflow might
+ // still be on our pushed floats list. However, that's
+ // actually fine, since they'll all end up being stolen and
+ // reordered into the correct order again.
+ // (nsBlockFrame::ReflowDirtyLines ensures that any lines with
+ // pushed floats are reflowed.)
+ mPushedFloats = mBlock->EnsurePushedFloats();
+ mFlags.mIsFloatListInBlockPropertyTable = true;
+ }
+}
+
+void BlockReflowState::AppendPushedFloatChain(nsIFrame* aFloatCont) {
+ SetupPushedFloatList();
+ while (true) {
+ aFloatCont->AddStateBits(NS_FRAME_IS_PUSHED_FLOAT);
+ mPushedFloats->AppendFrame(mBlock, aFloatCont);
+ aFloatCont = aFloatCont->GetNextInFlow();
+ if (!aFloatCont || aFloatCont->GetParent() != mBlock) {
+ break;
+ }
+ mBlock->StealFrame(aFloatCont);
+ }
+}
+
+/**
+ * Restore information about floats into the float manager for an
+ * incremental reflow, and simultaneously push the floats by
+ * |aDeltaBCoord|, which is the amount |aLine| was pushed relative to its
+ * parent. The recovery of state is one of the things that makes
+ * incremental reflow O(N^2) and this state should really be kept
+ * around, attached to the frame tree.
+ */
+void BlockReflowState::RecoverFloats(nsLineList::iterator aLine,
+ nscoord aDeltaBCoord) {
+ WritingMode wm = mReflowInput.GetWritingMode();
+ if (aLine->HasFloats()) {
+ // Place the floats into the float manager again. Also slide
+ // them, just like the regular frames on the line.
+ for (nsIFrame* floatFrame : aLine->Floats()) {
+ if (aDeltaBCoord != 0) {
+ floatFrame->MovePositionBy(nsPoint(0, aDeltaBCoord));
+ nsContainerFrame::PositionFrameView(floatFrame);
+ nsContainerFrame::PositionChildViews(floatFrame);
+ }
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyReflow || nsBlockFrame::gNoisyFloatManager) {
+ nscoord tI, tB;
+ FloatManager()->GetTranslation(tI, tB);
+ nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
+ printf("RecoverFloats: tIB=%d,%d (%d,%d) ", tI, tB, mFloatManagerI,
+ mFloatManagerB);
+ floatFrame->ListTag(stdout);
+ LogicalRect region =
+ nsFloatManager::GetRegionFor(wm, floatFrame, ContainerSize());
+ printf(" aDeltaBCoord=%d region={%d,%d,%d,%d}\n", aDeltaBCoord,
+ region.IStart(wm), region.BStart(wm), region.ISize(wm),
+ region.BSize(wm));
+ }
+#endif
+ FloatManager()->AddFloat(
+ floatFrame,
+ nsFloatManager::GetRegionFor(wm, floatFrame, ContainerSize()), wm,
+ ContainerSize());
+ }
+ } else if (aLine->IsBlock()) {
+ nsBlockFrame::RecoverFloatsFor(aLine->mFirstChild, *FloatManager(), wm,
+ ContainerSize());
+ }
+}
+
+/**
+ * Everything done in this function is done O(N) times for each pass of
+ * reflow so it is O(N*M) where M is the number of incremental reflow
+ * passes. That's bad. Don't do stuff here.
+ *
+ * When this function is called, |aLine| has just been slid by |aDeltaBCoord|
+ * and the purpose of RecoverStateFrom is to ensure that the
+ * BlockReflowState is in the same state that it would have been in
+ * had the line just been reflowed.
+ *
+ * Most of the state recovery that we have to do involves floats.
+ */
+void BlockReflowState::RecoverStateFrom(nsLineList::iterator aLine,
+ nscoord aDeltaBCoord) {
+ // Make the line being recovered the current line
+ mCurrentLine = aLine;
+
+ // Place floats for this line into the float manager
+ if (aLine->HasFloats() || aLine->IsBlock()) {
+ RecoverFloats(aLine, aDeltaBCoord);
+
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyReflow || nsBlockFrame::gNoisyFloatManager) {
+ FloatManager()->List(stdout);
+ }
+#endif
+ }
+}
+
+// This is called by the line layout's AddFloat method when a
+// place-holder frame is reflowed in a line. If the float is a
+// left-most child (it's x coordinate is at the line's left margin)
+// then the float is place immediately, otherwise the float
+// placement is deferred until the line has been reflowed.
+
+// XXXldb This behavior doesn't quite fit with CSS1 and CSS2 --
+// technically we're supposed let the current line flow around the
+// float as well unless it won't fit next to what we already have.
+// But nobody else implements it that way...
+bool BlockReflowState::AddFloat(nsLineLayout* aLineLayout, nsIFrame* aFloat,
+ nscoord aAvailableISize) {
+ MOZ_ASSERT(aLineLayout, "must have line layout");
+ MOZ_ASSERT(mBlock->LinesEnd() != mCurrentLine, "null ptr");
+ MOZ_ASSERT(aFloat->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
+ "aFloat must be an out-of-flow frame");
+
+ MOZ_ASSERT(aFloat->GetParent(), "float must have parent");
+ MOZ_ASSERT(aFloat->GetParent()->IsBlockFrameOrSubclass(),
+ "float's parent must be block");
+ if (aFloat->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT) ||
+ aFloat->GetParent() != mBlock) {
+ MOZ_ASSERT(aFloat->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT |
+ NS_FRAME_FIRST_REFLOW),
+ "float should be in this block unless it was marked as "
+ "pushed float, or just inserted");
+ MOZ_ASSERT(aFloat->GetParent()->FirstContinuation() ==
+ mBlock->FirstContinuation());
+ // If, in a previous reflow, the float was pushed entirely to
+ // another column/page, we need to steal it back. (We might just
+ // push it again, though.) Likewise, if that previous reflow
+ // reflowed this block but not its next continuation, we might need
+ // to steal it from our own float-continuations list.
+ //
+ // For more about pushed floats, see the comment above
+ // nsBlockFrame::DrainPushedFloats.
+ auto* floatParent = static_cast<nsBlockFrame*>(aFloat->GetParent());
+ floatParent->StealFrame(aFloat);
+
+ aFloat->RemoveStateBits(NS_FRAME_IS_PUSHED_FLOAT);
+
+ // Appending is fine, since if a float was pushed to the next
+ // page/column, all later floats were also pushed.
+ mBlock->mFloats.AppendFrame(mBlock, aFloat);
+ }
+
+ // Because we are in the middle of reflowing a placeholder frame
+ // within a line (and possibly nested in an inline frame or two
+ // that's a child of our block) we need to restore the space
+ // manager's translation to the space that the block resides in
+ // before placing the float.
+ nscoord oI, oB;
+ FloatManager()->GetTranslation(oI, oB);
+ nscoord dI = oI - mFloatManagerI;
+ nscoord dB = oB - mFloatManagerB;
+ FloatManager()->Translate(-dI, -dB);
+
+ bool placed = false;
+
+ // Now place the float immediately if possible. Otherwise stash it
+ // away in mBelowCurrentLineFloats and place it later.
+ // If one or more floats has already been pushed to the next line,
+ // don't let this one go on the current line, since that would violate
+ // float ordering.
+ bool shouldPlaceFloatBelowCurrentLine = false;
+ if (mBelowCurrentLineFloats.IsEmpty()) {
+ // If the current line is empty, we don't impose any inline-size constraint
+ // from the line layout.
+ Maybe<nscoord> availableISizeInCurrentLine =
+ aLineLayout->LineIsEmpty() ? Nothing() : Some(aAvailableISize);
+ PlaceFloatResult result =
+ FlowAndPlaceFloat(aFloat, availableISizeInCurrentLine);
+ if (result == PlaceFloatResult::Placed) {
+ placed = true;
+ // Pass on updated available space to the current inline reflow engine
+ WritingMode wm = mReflowInput.GetWritingMode();
+ // If we have mLineBSize, we are reflowing the line again due to
+ // LineReflowStatus::RedoMoreFloats. We should use mLineBSize to query the
+ // correct available space.
+ nsFlowAreaRect floatAvailSpace =
+ mLineBSize.isNothing() ? GetFloatAvailableSpace(mBCoord)
+ : GetFloatAvailableSpaceForBSize(
+ mBCoord, mLineBSize.value(), nullptr);
+ LogicalRect availSpace(wm, floatAvailSpace.mRect.IStart(wm), mBCoord,
+ floatAvailSpace.mRect.ISize(wm),
+ floatAvailSpace.mRect.BSize(wm));
+ aLineLayout->UpdateBand(wm, availSpace, aFloat);
+ // Record this float in the current-line list
+ mCurrentLineFloats.AppendElement(aFloat);
+ } else if (result == PlaceFloatResult::ShouldPlaceInNextContinuation) {
+ (*aLineLayout->GetLine())->SetHadFloatPushed();
+ } else {
+ MOZ_ASSERT(result == PlaceFloatResult::ShouldPlaceBelowCurrentLine);
+ shouldPlaceFloatBelowCurrentLine = true;
+ }
+ } else {
+ shouldPlaceFloatBelowCurrentLine = true;
+ }
+
+ if (shouldPlaceFloatBelowCurrentLine) {
+ // Always claim to be placed; we don't know whether we fit yet, so we
+ // deal with this in PlaceBelowCurrentLineFloats
+ placed = true;
+ // This float will be placed after the line is done (it is a
+ // below-current-line float).
+ mBelowCurrentLineFloats.AppendElement(aFloat);
+ }
+
+ // Restore coordinate system
+ FloatManager()->Translate(dI, dB);
+
+ return placed;
+}
+
+bool BlockReflowState::CanPlaceFloat(
+ nscoord aFloatISize, const nsFlowAreaRect& aFloatAvailableSpace) {
+ // A float fits at a given block-dir position if there are no floats
+ // at its inline-dir position (no matter what its inline size) or if
+ // its inline size fits in the space remaining after prior floats have
+ // been placed.
+ // FIXME: We should allow overflow by up to half a pixel here (bug 21193).
+ return !aFloatAvailableSpace.HasFloats() ||
+ aFloatAvailableSpace.mRect.ISize(mReflowInput.GetWritingMode()) >=
+ aFloatISize;
+}
+
+// Return the inline-size that the float (including margins) will take up
+// in the writing mode of the containing block. If this returns
+// NS_UNCONSTRAINEDSIZE, we're dealing with an orthogonal block that
+// has block-size:auto, and we'll need to actually reflow it to find out
+// how much inline-size it will occupy in the containing block's mode.
+static nscoord FloatMarginISize(WritingMode aCBWM,
+ const ReflowInput& aFloatRI) {
+ if (aFloatRI.ComputedSize(aCBWM).ISize(aCBWM) == NS_UNCONSTRAINEDSIZE) {
+ return NS_UNCONSTRAINEDSIZE; // reflow is needed to get the true size
+ }
+ return aFloatRI.ComputedSizeWithMarginBorderPadding(aCBWM).ISize(aCBWM);
+}
+
+// A frame property that stores the last shape source / margin / etc. if there's
+// any shape, in order to invalidate the float area properly when it changes.
+//
+// TODO(emilio): This could really belong to GetRegionFor / StoreRegionFor, but
+// when I tried it was a bit awkward because of the logical -> physical
+// conversion that happens there.
+//
+// Maybe all this code could be refactored to make this cleaner, but keeping the
+// two properties separated was slightly nicer.
+struct ShapeInvalidationData {
+ StyleShapeOutside mShapeOutside{StyleShapeOutside::None()};
+ float mShapeImageThreshold = 0.0;
+ LengthPercentage mShapeMargin;
+
+ ShapeInvalidationData() = default;
+
+ explicit ShapeInvalidationData(const nsStyleDisplay& aDisplay) {
+ Update(aDisplay);
+ }
+
+ static bool IsNeeded(const nsStyleDisplay& aDisplay) {
+ return !aDisplay.mShapeOutside.IsNone();
+ }
+
+ void Update(const nsStyleDisplay& aDisplay) {
+ MOZ_ASSERT(IsNeeded(aDisplay));
+ mShapeOutside = aDisplay.mShapeOutside;
+ mShapeImageThreshold = aDisplay.mShapeImageThreshold;
+ mShapeMargin = aDisplay.mShapeMargin;
+ }
+
+ bool Matches(const nsStyleDisplay& aDisplay) const {
+ return mShapeOutside == aDisplay.mShapeOutside &&
+ mShapeImageThreshold == aDisplay.mShapeImageThreshold &&
+ mShapeMargin == aDisplay.mShapeMargin;
+ }
+};
+
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(ShapeInvalidationDataProperty,
+ ShapeInvalidationData)
+
+BlockReflowState::PlaceFloatResult BlockReflowState::FlowAndPlaceFloat(
+ nsIFrame* aFloat, Maybe<nscoord> aAvailableISizeInCurrentLine) {
+ MOZ_ASSERT(aFloat->GetParent() == mBlock, "Float frame has wrong parent");
+
+ WritingMode wm = mReflowInput.GetWritingMode();
+ // Save away the block-dir coordinate before placing the float. We will
+ // restore mBCoord at the end after placing the float. This is
+ // necessary because any adjustments to mBCoord during the float
+ // placement are for the float only, not for any non-floating
+ // content.
+ AutoRestore<nscoord> restoreBCoord(mBCoord);
+
+ // Whether the block-direction position available to place a float has been
+ // pushed down due to the presence of other floats.
+ auto HasFloatPushedDown = [this, &restoreBCoord]() {
+ return mBCoord != restoreBCoord.SavedValue();
+ };
+
+ // Grab the float's display information
+ const nsStyleDisplay* floatDisplay = aFloat->StyleDisplay();
+
+ // The float's old region, so we can propagate damage.
+ LogicalRect oldRegion =
+ nsFloatManager::GetRegionFor(wm, aFloat, ContainerSize());
+
+ ShapeInvalidationData* invalidationData =
+ aFloat->GetProperty(ShapeInvalidationDataProperty());
+
+ // Enforce CSS2 9.5.1 rule [2], i.e., make sure that a float isn't
+ // ``above'' another float that preceded it in the flow.
+ mBCoord = std::max(FloatManager()->LowestFloatBStart(), mBCoord);
+
+ // See if the float should clear any preceding floats...
+ // XXX We need to mark this float somehow so that it gets reflowed
+ // when floats are inserted before it.
+ if (StyleClear::None != floatDisplay->mClear) {
+ // XXXldb Does this handle vertical margins correctly?
+ auto [bCoord, result] = ClearFloats(mBCoord, floatDisplay->mClear);
+ if (result == ClearFloatsResult::FloatsPushedOrSplit) {
+ PushFloatPastBreak(aFloat);
+ return PlaceFloatResult::ShouldPlaceInNextContinuation;
+ }
+ mBCoord = bCoord;
+ }
+
+ LogicalSize availSize = ComputeAvailableSizeForFloat();
+ const WritingMode floatWM = aFloat->GetWritingMode();
+ Maybe<ReflowInput> floatRI(std::in_place, mPresContext, mReflowInput, aFloat,
+ availSize.ConvertTo(floatWM, wm));
+
+ nscoord floatMarginISize = FloatMarginISize(wm, *floatRI);
+ LogicalMargin floatMargin = floatRI->ComputedLogicalMargin(wm);
+ nsReflowStatus reflowStatus;
+
+ // If it's a floating first-letter, we need to reflow it before we
+ // know how wide it is (since we don't compute which letters are part
+ // of the first letter until reflow!).
+ // We also need to do this early reflow if FloatMarginISize returned
+ // an unconstrained inline-size, which can occur if the float had an
+ // orthogonal writing mode and 'auto' block-size (in its mode).
+ bool earlyFloatReflow =
+ aFloat->IsLetterFrame() || floatMarginISize == NS_UNCONSTRAINEDSIZE;
+ if (earlyFloatReflow) {
+ mBlock->ReflowFloat(*this, *floatRI, aFloat, reflowStatus);
+ floatMarginISize = aFloat->ISize(wm) + floatMargin.IStartEnd(wm);
+ NS_ASSERTION(reflowStatus.IsComplete(),
+ "letter frames and orthogonal floats with auto block-size "
+ "shouldn't break, and if they do now, then they're breaking "
+ "at the wrong point");
+ }
+
+ // Now we've computed the float's margin inline-size.
+ if (aAvailableISizeInCurrentLine &&
+ floatMarginISize > *aAvailableISizeInCurrentLine) {
+ // The float cannot fit in the available inline-size of the current line.
+ // Let's notify our caller to place it later.
+ return PlaceFloatResult::ShouldPlaceBelowCurrentLine;
+ }
+
+ // Find a place to place the float. The CSS2 spec doesn't want
+ // floats overlapping each other or sticking out of the containing
+ // block if possible (CSS2 spec section 9.5.1, see the rule list).
+ StyleFloat floatStyle = floatDisplay->mFloat;
+ MOZ_ASSERT(StyleFloat::Left == floatStyle || StyleFloat::Right == floatStyle,
+ "Invalid float type!");
+
+ // Are we required to place at least part of the float because we're
+ // at the top of the page (to avoid an infinite loop of pushing and
+ // breaking).
+ bool mustPlaceFloat =
+ mReflowInput.mFlags.mIsTopOfPage && IsAdjacentWithBStart();
+
+ // Get the band of available space with respect to margin box.
+ nsFlowAreaRect floatAvailableSpace =
+ GetFloatAvailableSpaceForPlacingFloat(mBCoord);
+
+ for (;;) {
+ if (mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
+ floatAvailableSpace.mRect.BSize(wm) <= 0 && !mustPlaceFloat) {
+ // No space, nowhere to put anything.
+ PushFloatPastBreak(aFloat);
+ return PlaceFloatResult::ShouldPlaceInNextContinuation;
+ }
+
+ if (CanPlaceFloat(floatMarginISize, floatAvailableSpace)) {
+ // We found an appropriate place.
+ break;
+ }
+
+ // Nope. try to advance to the next band.
+ mBCoord += floatAvailableSpace.mRect.BSize(wm);
+ floatAvailableSpace = GetFloatAvailableSpaceForPlacingFloat(mBCoord);
+ mustPlaceFloat = false;
+ }
+
+ // If the float is continued, it will get the same absolute x value as its
+ // prev-in-flow
+
+ // We don't worry about the geometry of the prev in flow, let the continuation
+ // place and size itself as required.
+
+ // Assign inline and block dir coordinates to the float. We don't use
+ // LineLeft() and LineRight() here, because we would only have to
+ // convert the result back into this block's writing mode.
+ LogicalPoint floatPos(wm);
+ bool leftFloat = floatStyle == StyleFloat::Left;
+
+ if (leftFloat == wm.IsBidiLTR()) {
+ floatPos.I(wm) = floatAvailableSpace.mRect.IStart(wm);
+ } else {
+ floatPos.I(wm) = floatAvailableSpace.mRect.IEnd(wm) - floatMarginISize;
+ }
+ // CSS2 spec, 9.5.1 rule [4]: "A floating box's outer top may not
+ // be higher than the top of its containing block." (Since the
+ // containing block is the content edge of the block box, this
+ // means the margin edge of the float can't be higher than the
+ // content edge of the block that contains it.)
+ floatPos.B(wm) = std::max(mBCoord, ContentBStart());
+
+ // Reflow the float after computing its vertical position so it knows
+ // where to break.
+ if (!earlyFloatReflow) {
+ const LogicalSize oldAvailSize = availSize;
+ availSize = ComputeAvailableSizeForFloat();
+ if (oldAvailSize != availSize) {
+ floatRI.reset();
+ floatRI.emplace(mPresContext, mReflowInput, aFloat,
+ availSize.ConvertTo(floatWM, wm));
+ }
+ // Normally the mIsTopOfPage state is copied from the parent reflow input.
+ // However, when reflowing a float, if we've placed other floats that force
+ // this float being pushed down, we should unset the mIsTopOfPage bit.
+ if (floatRI->mFlags.mIsTopOfPage && HasFloatPushedDown()) {
+ // HasFloatPushedDown() implies that we increased mBCoord, and we
+ // should've turned off mustPlaceFloat when we did that.
+ NS_ASSERTION(!mustPlaceFloat,
+ "mustPlaceFloat shouldn't be set if we're not at the "
+ "top-of-page!");
+ floatRI->mFlags.mIsTopOfPage = false;
+ }
+ mBlock->ReflowFloat(*this, *floatRI, aFloat, reflowStatus);
+ }
+ if (aFloat->GetPrevInFlow()) {
+ floatMargin.BStart(wm) = 0;
+ }
+ if (reflowStatus.IsIncomplete()) {
+ floatMargin.BEnd(wm) = 0;
+ }
+
+ // If the float cannot fit (e.g. via fragmenting itself if applicable), or if
+ // we're forced to break before it for CSS break-* reasons, then it needs to
+ // be pushed in its entirety to the next column/page.
+ //
+ // Note we use the available block-size in floatRI rather than use
+ // availSize.BSize() because nsBlockReflowContext::ReflowBlock() might adjust
+ // floatRI's available size.
+ const nscoord availBSize = floatRI->AvailableSize(floatWM).BSize(floatWM);
+ const bool isTruncated =
+ availBSize != NS_UNCONSTRAINEDSIZE && aFloat->BSize(floatWM) > availBSize;
+ if ((!floatRI->mFlags.mIsTopOfPage && isTruncated) ||
+ reflowStatus.IsInlineBreakBefore()) {
+ PushFloatPastBreak(aFloat);
+ return PlaceFloatResult::ShouldPlaceInNextContinuation;
+ }
+
+ // We can't use aFloat->ShouldAvoidBreakInside(mReflowInput) here since
+ // its mIsTopOfPage may be true even though the float isn't at the
+ // top when floatPos.B(wm) > 0.
+ if (ContentBSize() != NS_UNCONSTRAINEDSIZE && !mustPlaceFloat &&
+ (!mReflowInput.mFlags.mIsTopOfPage || floatPos.B(wm) > 0) &&
+ StyleBreakWithin::Avoid == aFloat->StyleDisplay()->mBreakInside &&
+ (!reflowStatus.IsFullyComplete() ||
+ aFloat->BSize(wm) + floatMargin.BStartEnd(wm) >
+ ContentBEnd() - floatPos.B(wm)) &&
+ !aFloat->GetPrevInFlow()) {
+ PushFloatPastBreak(aFloat);
+ return PlaceFloatResult::ShouldPlaceInNextContinuation;
+ }
+
+ // Calculate the actual origin of the float frame's border rect
+ // relative to the parent block; the margin must be added in
+ // to get the border rect
+ LogicalPoint origin(wm, floatMargin.IStart(wm) + floatPos.I(wm),
+ floatMargin.BStart(wm) + floatPos.B(wm));
+
+ // If float is relatively positioned, factor that in as well
+ const LogicalMargin floatOffsets = floatRI->ComputedLogicalOffsets(wm);
+ ReflowInput::ApplyRelativePositioning(aFloat, wm, floatOffsets, &origin,
+ ContainerSize());
+
+ // Position the float and make sure and views are properly
+ // positioned. We need to explicitly position its child views as
+ // well, since we're moving the float after flowing it.
+ bool moved = aFloat->GetLogicalPosition(wm, ContainerSize()) != origin;
+ if (moved) {
+ aFloat->SetPosition(wm, origin, ContainerSize());
+ nsContainerFrame::PositionFrameView(aFloat);
+ nsContainerFrame::PositionChildViews(aFloat);
+ }
+
+ // Update the float combined area state
+ // XXX Floats should really just get invalidated here if necessary
+ mFloatOverflowAreas.UnionWith(aFloat->GetOverflowAreasRelativeToParent());
+
+ // Place the float in the float manager
+ // calculate region
+ LogicalRect region = nsFloatManager::CalculateRegionFor(
+ wm, aFloat, floatMargin, ContainerSize());
+ // if the float split, then take up all of the vertical height
+ if (reflowStatus.IsIncomplete() && (NS_UNCONSTRAINEDSIZE != ContentBSize())) {
+ region.BSize(wm) =
+ std::max(region.BSize(wm), ContentBSize() - floatPos.B(wm));
+ }
+ FloatManager()->AddFloat(aFloat, region, wm, ContainerSize());
+
+ // store region
+ nsFloatManager::StoreRegionFor(wm, aFloat, region, ContainerSize());
+
+ const bool invalidationDataNeeded =
+ ShapeInvalidationData::IsNeeded(*floatDisplay);
+
+ // If the float's dimensions or shape have changed, note the damage in the
+ // float manager.
+ if (!region.IsEqualEdges(oldRegion) ||
+ !!invalidationData != invalidationDataNeeded ||
+ (invalidationData && !invalidationData->Matches(*floatDisplay))) {
+ // XXXwaterson conservative: we could probably get away with noting
+ // less damage; e.g., if only height has changed, then only note the
+ // area into which the float has grown or from which the float has
+ // shrunk.
+ nscoord blockStart = std::min(region.BStart(wm), oldRegion.BStart(wm));
+ nscoord blockEnd = std::max(region.BEnd(wm), oldRegion.BEnd(wm));
+ FloatManager()->IncludeInDamage(blockStart, blockEnd);
+ }
+
+ if (invalidationDataNeeded) {
+ if (invalidationData) {
+ invalidationData->Update(*floatDisplay);
+ } else {
+ aFloat->SetProperty(ShapeInvalidationDataProperty(),
+ new ShapeInvalidationData(*floatDisplay));
+ }
+ } else if (invalidationData) {
+ invalidationData = nullptr;
+ aFloat->RemoveProperty(ShapeInvalidationDataProperty());
+ }
+
+ if (!reflowStatus.IsFullyComplete()) {
+ mBlock->SplitFloat(*this, aFloat, reflowStatus);
+ } else {
+ MOZ_ASSERT(!aFloat->GetNextInFlow());
+ }
+
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyFloatManager) {
+ nscoord tI, tB;
+ FloatManager()->GetTranslation(tI, tB);
+ mBlock->ListTag(stdout);
+ printf(": FlowAndPlaceFloat: AddFloat: tIB=%d,%d (%d,%d) {%d,%d,%d,%d}\n",
+ tI, tB, mFloatManagerI, mFloatManagerB, region.IStart(wm),
+ region.BStart(wm), region.ISize(wm), region.BSize(wm));
+ }
+
+ if (nsBlockFrame::gNoisyReflow) {
+ nsRect r = aFloat->GetRect();
+ nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
+ printf("placed float: ");
+ aFloat->ListTag(stdout);
+ printf(" %d,%d,%d,%d\n", r.x, r.y, r.width, r.height);
+ }
+#endif
+
+ return PlaceFloatResult::Placed;
+}
+
+void BlockReflowState::PushFloatPastBreak(nsIFrame* aFloat) {
+ // This ensures that we:
+ // * don't try to place later but smaller floats (which CSS says
+ // must have their tops below the top of this float)
+ // * don't waste much time trying to reflow this float again until
+ // after the break
+ StyleFloat floatStyle = aFloat->StyleDisplay()->mFloat;
+ if (floatStyle == StyleFloat::Left) {
+ FloatManager()->SetPushedLeftFloatPastBreak();
+ } else {
+ MOZ_ASSERT(floatStyle == StyleFloat::Right, "Unexpected float value!");
+ FloatManager()->SetPushedRightFloatPastBreak();
+ }
+
+ // Put the float on the pushed floats list, even though it
+ // isn't actually a continuation.
+ mBlock->StealFrame(aFloat);
+ AppendPushedFloatChain(aFloat);
+ mReflowStatus.SetOverflowIncomplete();
+}
+
+/**
+ * Place below-current-line floats.
+ */
+void BlockReflowState::PlaceBelowCurrentLineFloats(nsLineBox* aLine) {
+ MOZ_ASSERT(!mBelowCurrentLineFloats.IsEmpty());
+ nsTArray<nsIFrame*> floatsPlacedInLine;
+ for (nsIFrame* f : mBelowCurrentLineFloats) {
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyReflow) {
+ nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
+ printf("placing bcl float: ");
+ f->ListTag(stdout);
+ printf("\n");
+ }
+#endif
+ // Place the float
+ PlaceFloatResult result = FlowAndPlaceFloat(f);
+ MOZ_ASSERT(result != PlaceFloatResult::ShouldPlaceBelowCurrentLine,
+ "We are already dealing with below current line floats!");
+ if (result == PlaceFloatResult::Placed) {
+ floatsPlacedInLine.AppendElement(f);
+ }
+ }
+ if (floatsPlacedInLine.Length() != mBelowCurrentLineFloats.Length()) {
+ // We have some floats having ShouldPlaceInNextContinuation result.
+ aLine->SetHadFloatPushed();
+ }
+ aLine->AppendFloats(std::move(floatsPlacedInLine));
+ mBelowCurrentLineFloats.Clear();
+}
+
+std::tuple<nscoord, BlockReflowState::ClearFloatsResult>
+BlockReflowState::ClearFloats(nscoord aBCoord, StyleClear aClearType,
+ nsIFrame* aFloatAvoidingBlock) {
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyReflow) {
+ nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
+ printf("clear floats: in: aBCoord=%d\n", aBCoord);
+ }
+#endif
+
+ if (!FloatManager()->HasAnyFloats()) {
+ return {aBCoord, ClearFloatsResult::BCoordNoChange};
+ }
+
+ nscoord newBCoord = aBCoord;
+
+ if (aClearType != StyleClear::None) {
+ newBCoord = FloatManager()->ClearFloats(newBCoord, aClearType);
+
+ if (FloatManager()->ClearContinues(aClearType)) {
+ return {newBCoord, ClearFloatsResult::FloatsPushedOrSplit};
+ }
+ }
+
+ if (aFloatAvoidingBlock) {
+ for (;;) {
+ nsFlowAreaRect floatAvailableSpace = GetFloatAvailableSpace(newBCoord);
+ if (FloatAvoidingBlockFitsInAvailSpace(aFloatAvoidingBlock,
+ floatAvailableSpace)) {
+ break;
+ }
+ // See the analogous code for inlines in
+ // nsBlockFrame::DoReflowInlineFrames
+ if (!AdvanceToNextBand(floatAvailableSpace.mRect, &newBCoord)) {
+ // Stop trying to clear here; we'll just get pushed to the
+ // next column or page and try again there.
+ break;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyReflow) {
+ nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
+ printf("clear floats: out: y=%d\n", newBCoord);
+ }
+#endif
+
+ ClearFloatsResult result = newBCoord == aBCoord
+ ? ClearFloatsResult::BCoordNoChange
+ : ClearFloatsResult::BCoordAdvanced;
+ return {newBCoord, result};
+}