1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* state used in reflow of block frames */
9 #include "BlockReflowState.h"
12 #include "LayoutLogging.h"
13 #include "nsBlockFrame.h"
14 #include "nsLineLayout.h"
15 #include "nsPresContext.h"
16 #include "nsIFrameInlines.h"
17 #include "mozilla/AutoRestore.h"
18 #include "mozilla/DebugOnly.h"
19 #include "mozilla/Preferences.h"
20 #include "mozilla/StaticPrefs_layout.h"
21 #include "TextOverflow.h"
24 # include "nsBlockDebugFlags.h"
27 using namespace mozilla
;
28 using namespace mozilla::layout
;
30 BlockReflowState::BlockReflowState(const ReflowInput
& aReflowInput
,
31 nsPresContext
* aPresContext
,
32 nsBlockFrame
* aFrame
, bool aBStartMarginRoot
,
34 bool aBlockNeedsFloatManager
,
35 const nscoord aConsumedBSize
,
36 const nscoord aEffectiveContentBoxBSize
)
38 mPresContext(aPresContext
),
39 mReflowInput(aReflowInput
),
40 mContentArea(aReflowInput
.GetWritingMode()),
41 mPushedFloats(nullptr),
42 mOverflowTracker(nullptr),
45 .ComputedLogicalBorderPadding(mReflowInput
.GetWritingMode())
46 .ApplySkipSides(aFrame
->PreReflowBlockLevelLogicalSkipSides())),
48 mMinLineHeight(aReflowInput
.GetLineHeight()),
50 mFloatBreakType(StyleClear::None
),
51 mConsumedBSize(aConsumedBSize
) {
52 NS_ASSERTION(mConsumedBSize
!= NS_UNCONSTRAINEDSIZE
,
53 "The consumed block-size should be constrained!");
55 WritingMode wm
= aReflowInput
.GetWritingMode();
57 // Note that mContainerSize is the physical size, needed to
58 // convert logical block-coordinates in vertical-rl writing mode
59 // (measured from a RHS origin) to physical coordinates within the
61 // If aReflowInput doesn't have a constrained ComputedWidth(), we set
62 // mContainerSize.width to zero, which means lines will be positioned
63 // (physically) incorrectly; we will fix them up at the end of
64 // nsBlockFrame::Reflow, after we know the total block-size of the
66 mContainerSize
.width
= aReflowInput
.ComputedWidth();
67 if (mContainerSize
.width
== NS_UNCONSTRAINEDSIZE
) {
68 mContainerSize
.width
= 0;
71 mContainerSize
.width
+= mBorderPadding
.LeftRight(wm
);
73 // For now at least, we don't do that fix-up for mContainerHeight.
74 // It's only used in nsBidiUtils::ReorderFrames for vertical rtl
75 // writing modes, which aren't fully supported for the time being.
76 mContainerSize
.height
=
77 aReflowInput
.ComputedHeight() + mBorderPadding
.TopBottom(wm
);
79 if (aBStartMarginRoot
|| 0 != mBorderPadding
.BStart(wm
)) {
80 mFlags
.mIsBStartMarginRoot
= true;
81 mFlags
.mShouldApplyBStartMargin
= true;
83 if (aBEndMarginRoot
|| 0 != mBorderPadding
.BEnd(wm
)) {
84 mFlags
.mIsBEndMarginRoot
= true;
86 if (aBlockNeedsFloatManager
) {
87 mFlags
.mBlockNeedsFloatManager
= true;
90 // We need to check mInsideLineClamp here since we are here before the block
91 // has been reflowed, and CanHaveOverflowMarkers() relies on the block's
92 // NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS state bit to know if a -webkit-line-clamp
93 // ellipsis is set on one of the block's lines. And that state bit is only
94 // set after we do the bsize measuring reflow of the flex item.
95 mFlags
.mCanHaveOverflowMarkers
=
96 aReflowInput
.mFlags
.mInsideLineClamp
||
97 css::TextOverflow::CanHaveOverflowMarkers(mBlock
);
99 MOZ_ASSERT(FloatManager(),
100 "Float manager should be valid when creating BlockReflowState!");
102 // Save the coordinate system origin for later.
103 FloatManager()->GetTranslation(mFloatManagerI
, mFloatManagerB
);
104 FloatManager()->PushState(&mFloatManagerStateBefore
); // never popped
106 mNextInFlow
= static_cast<nsBlockFrame
*>(mBlock
->GetNextInFlow());
108 LAYOUT_WARN_IF_FALSE(NS_UNCONSTRAINEDSIZE
!= aReflowInput
.ComputedISize(),
109 "have unconstrained width; this should only result "
110 "from very large sizes, not attempts at intrinsic "
111 "width calculation");
112 mContentArea
.ISize(wm
) = aReflowInput
.ComputedISize();
114 // Compute content area block-size. Unlike the inline-size, if we have a
115 // specified style block-size, we ignore it since extra content is managed by
116 // the "overflow" property. When we don't have a specified style block-size,
117 // then we may end up limiting our block-size if the available block-size is
118 // constrained (this situation occurs when we are paginated).
119 if (const nscoord availableBSize
= aReflowInput
.AvailableBSize();
120 availableBSize
!= NS_UNCONSTRAINEDSIZE
) {
121 // We are in a paginated situation. The block-end edge of the available
122 // space to reflow the children is within our block-end border and padding.
123 // If we're cloning our border and padding, and we're going to request
124 // additional continuations because of our excessive content-box block-size,
125 // then reserve some of our available space for our (cloned) block-end
126 // border and padding.
127 const bool reserveSpaceForBlockEndBP
=
128 mReflowInput
.mStyleBorder
->mBoxDecorationBreak
==
129 StyleBoxDecorationBreak::Clone
&&
130 (aEffectiveContentBoxBSize
== NS_UNCONSTRAINEDSIZE
||
131 aEffectiveContentBoxBSize
+ mBorderPadding
.BStartEnd(wm
) >
133 const nscoord bp
= reserveSpaceForBlockEndBP
? mBorderPadding
.BStartEnd(wm
)
134 : mBorderPadding
.BStart(wm
);
135 mContentArea
.BSize(wm
) = std::max(0, availableBSize
- bp
);
137 // When we are not in a paginated situation, then we always use a
138 // unconstrained block-size.
139 mContentArea
.BSize(wm
) = NS_UNCONSTRAINEDSIZE
;
141 mContentArea
.IStart(wm
) = mBorderPadding
.IStart(wm
);
142 mBCoord
= mContentArea
.BStart(wm
) = mBorderPadding
.BStart(wm
);
144 mPrevChild
= nullptr;
145 mCurrentLine
= aFrame
->LinesEnd();
148 void BlockReflowState::ComputeReplacedBlockOffsetsForFloats(
149 nsIFrame
* aFrame
, const LogicalRect
& aFloatAvailableSpace
,
150 nscoord
& aIStartResult
, nscoord
& aIEndResult
) const {
151 WritingMode wm
= mReflowInput
.GetWritingMode();
152 // The frame is clueless about the float manager and therefore we
153 // only give it free space. An example is a table frame - the
154 // tables do not flow around floats.
155 // However, we can let its margins intersect floats.
156 NS_ASSERTION(aFloatAvailableSpace
.IStart(wm
) >= mContentArea
.IStart(wm
),
157 "bad avail space rect inline-coord");
158 NS_ASSERTION(aFloatAvailableSpace
.ISize(wm
) == 0 ||
159 aFloatAvailableSpace
.IEnd(wm
) <= mContentArea
.IEnd(wm
),
160 "bad avail space rect inline-size");
162 nscoord iStartOffset
, iEndOffset
;
163 if (aFloatAvailableSpace
.ISize(wm
) == mContentArea
.ISize(wm
)) {
164 // We don't need to compute margins when there are no floats around.
168 LogicalMargin
frameMargin(wm
);
169 SizeComputationInput
os(aFrame
, mReflowInput
.mRenderingContext
, wm
,
170 mContentArea
.ISize(wm
));
171 frameMargin
= os
.ComputedLogicalMargin(wm
);
173 nscoord iStartFloatIOffset
=
174 aFloatAvailableSpace
.IStart(wm
) - mContentArea
.IStart(wm
);
175 iStartOffset
= std::max(iStartFloatIOffset
, frameMargin
.IStart(wm
)) -
176 frameMargin
.IStart(wm
);
177 iStartOffset
= std::max(iStartOffset
, 0); // in case of negative margin
178 nscoord iEndFloatIOffset
=
179 mContentArea
.IEnd(wm
) - aFloatAvailableSpace
.IEnd(wm
);
181 std::max(iEndFloatIOffset
, frameMargin
.IEnd(wm
)) - frameMargin
.IEnd(wm
);
182 iEndOffset
= std::max(iEndOffset
, 0); // in case of negative margin
184 aIStartResult
= iStartOffset
;
185 aIEndResult
= iEndOffset
;
188 LogicalRect
BlockReflowState::ComputeBlockAvailSpace(
189 nsIFrame
* aFrame
, const nsFlowAreaRect
& aFloatAvailableSpace
,
190 bool aBlockAvoidsFloats
) {
191 #ifdef REALLY_NOISY_REFLOW
192 printf("CBAS frame=%p has floats %d\n", aFrame
,
193 aFloatAvailableSpace
.HasFloats());
195 WritingMode wm
= mReflowInput
.GetWritingMode();
196 LogicalRect
result(wm
);
197 result
.BStart(wm
) = mBCoord
;
198 // Note: ContentBSize() and ContentBEnd() are not our content-box size and its
199 // block-end edge. They really mean "the available block-size for children",
200 // and "the block-end edge of the available space for children".
201 result
.BSize(wm
) = ContentBSize() == NS_UNCONSTRAINEDSIZE
202 ? NS_UNCONSTRAINEDSIZE
203 : ContentBEnd() - mBCoord
;
204 // mBCoord might be greater than ContentBEnd() if the block's top margin
205 // pushes it off the page/column. Negative available block-size can confuse
206 // other code and is nonsense in principle.
208 // XXX Do we really want this condition to be this restrictive (i.e.,
209 // more restrictive than it used to be)? The |else| here is allowed
210 // by the CSS spec, but only out of desperation given implementations,
211 // and the behavior it leads to is quite undesirable (it can cause
212 // things to become extremely narrow when they'd fit quite well a
213 // little bit lower). Should the else be a quirk or something that
214 // applies to a specific set of frame classes and no new ones?
215 // If we did that, then for those frames where the condition below is
216 // true but nsBlockFrame::BlockCanIntersectFloats is false,
217 // nsBlockFrame::ISizeToClearPastFloats would need to use the
218 // shrink-wrap formula, max(MinISize, min(avail width, PrefISize))
219 // rather than just using MinISize.
221 nsBlockFrame::BlockCanIntersectFloats(aFrame
) == !aBlockAvoidsFloats
,
222 "unexpected replaced width");
223 if (!aBlockAvoidsFloats
) {
224 if (aFloatAvailableSpace
.HasFloats()) {
225 // Use the float-edge property to determine how the child block
226 // will interact with the float.
227 const nsStyleBorder
* borderStyle
= aFrame
->StyleBorder();
228 switch (borderStyle
->mFloatEdge
) {
230 case StyleFloatEdge::ContentBox
: // content and only content does
231 // runaround of floats
232 // The child block will flow around the float. Therefore
233 // give it all of the available space.
234 result
.IStart(wm
) = mContentArea
.IStart(wm
);
235 result
.ISize(wm
) = mContentArea
.ISize(wm
);
237 case StyleFloatEdge::MarginBox
: {
238 // The child block's margins should be placed adjacent to,
239 // but not overlap the float.
240 result
.IStart(wm
) = aFloatAvailableSpace
.mRect
.IStart(wm
);
241 result
.ISize(wm
) = aFloatAvailableSpace
.mRect
.ISize(wm
);
245 // Since there are no floats present the float-edge property
246 // doesn't matter therefore give the block element all of the
247 // available space since it will flow around the float itself.
248 result
.IStart(wm
) = mContentArea
.IStart(wm
);
249 result
.ISize(wm
) = mContentArea
.ISize(wm
);
252 nscoord iStartOffset
, iEndOffset
;
253 ComputeReplacedBlockOffsetsForFloats(aFrame
, aFloatAvailableSpace
.mRect
,
254 iStartOffset
, iEndOffset
);
255 result
.IStart(wm
) = mContentArea
.IStart(wm
) + iStartOffset
;
256 result
.ISize(wm
) = mContentArea
.ISize(wm
) - iStartOffset
- iEndOffset
;
259 #ifdef REALLY_NOISY_REFLOW
260 printf(" CBAS: result %d %d %d %d\n", result
.IStart(wm
), result
.BStart(wm
),
261 result
.ISize(wm
), result
.BSize(wm
));
267 bool BlockReflowState::ReplacedBlockFitsInAvailSpace(
268 nsIFrame
* aReplacedBlock
,
269 const nsFlowAreaRect
& aFloatAvailableSpace
) const {
270 if (!aFloatAvailableSpace
.HasFloats()) {
271 // If there aren't any floats here, then we always fit.
272 // We check this before calling ISizeToClearPastFloats, which is
273 // somewhat expensive.
276 WritingMode wm
= mReflowInput
.GetWritingMode();
277 nsBlockFrame::ReplacedElementISizeToClear replacedISize
=
278 nsBlockFrame::ISizeToClearPastFloats(*this, aFloatAvailableSpace
.mRect
,
280 // The inline-start side of the replaced element should be offset by
281 // the larger of the float intrusion or the replaced element's own
282 // start margin. The inline-end side is similar, except for Web
283 // compatibility we ignore the margin.
285 aFloatAvailableSpace
.mRect
.IStart(wm
) - mContentArea
.IStart(wm
),
286 replacedISize
.marginIStart
) +
287 replacedISize
.borderBoxISize
+
288 (mContentArea
.IEnd(wm
) - aFloatAvailableSpace
.mRect
.IEnd(wm
)) <=
289 mContentArea
.ISize(wm
);
292 nsFlowAreaRect
BlockReflowState::GetFloatAvailableSpaceWithState(
293 nscoord aBCoord
, ShapeType aShapeType
,
294 nsFloatManager::SavedState
* aState
) const {
295 WritingMode wm
= mReflowInput
.GetWritingMode();
297 // Verify that the caller setup the coordinate system properly
299 FloatManager()->GetTranslation(wI
, wB
);
301 NS_ASSERTION((wI
== mFloatManagerI
) && (wB
== mFloatManagerB
),
305 nscoord blockSize
= (mContentArea
.BSize(wm
) == nscoord_MAX
)
307 : std::max(mContentArea
.BEnd(wm
) - aBCoord
, 0);
308 nsFlowAreaRect result
= FloatManager()->GetFlowArea(
309 wm
, aBCoord
, blockSize
, BandInfoType::BandFromPoint
, aShapeType
,
310 mContentArea
, aState
, ContainerSize());
311 // Keep the inline size >= 0 for compatibility with nsSpaceManager.
312 if (result
.mRect
.ISize(wm
) < 0) {
313 result
.mRect
.ISize(wm
) = 0;
317 if (nsBlockFrame::gNoisyReflow
) {
318 nsIFrame::IndentBy(stdout
, nsBlockFrame::gNoiseIndent
);
319 printf("%s: band=%d,%d,%d,%d hasfloats=%d\n", __func__
,
320 result
.mRect
.IStart(wm
), result
.mRect
.BStart(wm
),
321 result
.mRect
.ISize(wm
), result
.mRect
.BSize(wm
), result
.HasFloats());
327 nsFlowAreaRect
BlockReflowState::GetFloatAvailableSpaceForBSize(
328 nscoord aBCoord
, nscoord aBSize
, nsFloatManager::SavedState
* aState
) const {
329 WritingMode wm
= mReflowInput
.GetWritingMode();
331 // Verify that the caller setup the coordinate system properly
333 FloatManager()->GetTranslation(wI
, wB
);
335 NS_ASSERTION((wI
== mFloatManagerI
) && (wB
== mFloatManagerB
),
338 nsFlowAreaRect result
= FloatManager()->GetFlowArea(
339 wm
, aBCoord
, aBSize
, BandInfoType::WidthWithinHeight
,
340 ShapeType::ShapeOutside
, mContentArea
, aState
, ContainerSize());
341 // Keep the width >= 0 for compatibility with nsSpaceManager.
342 if (result
.mRect
.ISize(wm
) < 0) {
343 result
.mRect
.ISize(wm
) = 0;
347 if (nsBlockFrame::gNoisyReflow
) {
348 nsIFrame::IndentBy(stdout
, nsBlockFrame::gNoiseIndent
);
349 printf("%s: space=%d,%d,%d,%d hasfloats=%d\n", __func__
,
350 result
.mRect
.IStart(wm
), result
.mRect
.BStart(wm
),
351 result
.mRect
.ISize(wm
), result
.mRect
.BSize(wm
), result
.HasFloats());
358 * Reconstruct the vertical margin before the line |aLine| in order to
359 * do an incremental reflow that begins with |aLine| without reflowing
360 * the line before it. |aLine| may point to the fencepost at the end of
361 * the line list, and it is used this way since we (for now, anyway)
362 * always need to recover margins at the end of a block.
364 * The reconstruction involves walking backward through the line list to
365 * find any collapsed margins preceding the line that would have been in
366 * the reflow input's |mPrevBEndMargin| when we reflowed that line in
367 * a full reflow (under the rule in CSS2 that all adjacent vertical
368 * margins of blocks collapse).
370 void BlockReflowState::ReconstructMarginBefore(nsLineList::iterator aLine
) {
371 mPrevBEndMargin
.Zero();
372 nsBlockFrame
* block
= mBlock
;
374 nsLineList::iterator firstLine
= block
->LinesBegin();
377 if (aLine
->IsBlock()) {
378 mPrevBEndMargin
= aLine
->GetCarriedOutBEndMargin();
381 if (!aLine
->IsEmpty()) {
384 if (aLine
== firstLine
) {
385 // If the top margin was carried out (and thus already applied),
386 // set it to zero. Either way, we're done.
387 if (!mFlags
.mIsBStartMarginRoot
) {
388 mPrevBEndMargin
.Zero();
395 void BlockReflowState::SetupPushedFloatList() {
396 MOZ_ASSERT(!mFlags
.mIsFloatListInBlockPropertyTable
== !mPushedFloats
,
398 if (!mFlags
.mIsFloatListInBlockPropertyTable
) {
399 // If we're being re-Reflow'd without our next-in-flow having been
400 // reflowed, some pushed floats from our previous reflow might
401 // still be on our pushed floats list. However, that's
402 // actually fine, since they'll all end up being stolen and
403 // reordered into the correct order again.
404 // (nsBlockFrame::ReflowDirtyLines ensures that any lines with
405 // pushed floats are reflowed.)
406 mPushedFloats
= mBlock
->EnsurePushedFloats();
407 mFlags
.mIsFloatListInBlockPropertyTable
= true;
411 void BlockReflowState::AppendPushedFloatChain(nsIFrame
* aFloatCont
) {
412 SetupPushedFloatList();
414 aFloatCont
->AddStateBits(NS_FRAME_IS_PUSHED_FLOAT
);
415 mPushedFloats
->AppendFrame(mBlock
, aFloatCont
);
416 aFloatCont
= aFloatCont
->GetNextInFlow();
417 if (!aFloatCont
|| aFloatCont
->GetParent() != mBlock
) {
420 mBlock
->StealFrame(aFloatCont
);
425 * Restore information about floats into the float manager for an
426 * incremental reflow, and simultaneously push the floats by
427 * |aDeltaBCoord|, which is the amount |aLine| was pushed relative to its
428 * parent. The recovery of state is one of the things that makes
429 * incremental reflow O(N^2) and this state should really be kept
430 * around, attached to the frame tree.
432 void BlockReflowState::RecoverFloats(nsLineList::iterator aLine
,
433 nscoord aDeltaBCoord
) {
434 WritingMode wm
= mReflowInput
.GetWritingMode();
435 if (aLine
->HasFloats()) {
436 // Place the floats into the float manager again. Also slide
437 // them, just like the regular frames on the line.
438 nsFloatCache
* fc
= aLine
->GetFirstFloat();
440 nsIFrame
* floatFrame
= fc
->mFloat
;
441 if (aDeltaBCoord
!= 0) {
442 floatFrame
->MovePositionBy(nsPoint(0, aDeltaBCoord
));
443 nsContainerFrame::PositionFrameView(floatFrame
);
444 nsContainerFrame::PositionChildViews(floatFrame
);
447 if (nsBlockFrame::gNoisyReflow
|| nsBlockFrame::gNoisyFloatManager
) {
449 FloatManager()->GetTranslation(tI
, tB
);
450 nsIFrame::IndentBy(stdout
, nsBlockFrame::gNoiseIndent
);
451 printf("RecoverFloats: tIB=%d,%d (%d,%d) ", tI
, tB
, mFloatManagerI
,
453 floatFrame
->ListTag(stdout
);
455 nsFloatManager::GetRegionFor(wm
, floatFrame
, ContainerSize());
456 printf(" aDeltaBCoord=%d region={%d,%d,%d,%d}\n", aDeltaBCoord
,
457 region
.IStart(wm
), region
.BStart(wm
), region
.ISize(wm
),
461 FloatManager()->AddFloat(
463 nsFloatManager::GetRegionFor(wm
, floatFrame
, ContainerSize()), wm
,
467 } else if (aLine
->IsBlock()) {
468 nsBlockFrame::RecoverFloatsFor(aLine
->mFirstChild
, *FloatManager(), wm
,
474 * Everything done in this function is done O(N) times for each pass of
475 * reflow so it is O(N*M) where M is the number of incremental reflow
476 * passes. That's bad. Don't do stuff here.
478 * When this function is called, |aLine| has just been slid by |aDeltaBCoord|
479 * and the purpose of RecoverStateFrom is to ensure that the
480 * BlockReflowState is in the same state that it would have been in
481 * had the line just been reflowed.
483 * Most of the state recovery that we have to do involves floats.
485 void BlockReflowState::RecoverStateFrom(nsLineList::iterator aLine
,
486 nscoord aDeltaBCoord
) {
487 // Make the line being recovered the current line
488 mCurrentLine
= aLine
;
490 // Place floats for this line into the float manager
491 if (aLine
->HasFloats() || aLine
->IsBlock()) {
492 RecoverFloats(aLine
, aDeltaBCoord
);
495 if (nsBlockFrame::gNoisyReflow
|| nsBlockFrame::gNoisyFloatManager
) {
496 FloatManager()->List(stdout
);
502 // This is called by the line layout's AddFloat method when a
503 // place-holder frame is reflowed in a line. If the float is a
504 // left-most child (it's x coordinate is at the line's left margin)
505 // then the float is place immediately, otherwise the float
506 // placement is deferred until the line has been reflowed.
508 // XXXldb This behavior doesn't quite fit with CSS1 and CSS2 --
509 // technically we're supposed let the current line flow around the
510 // float as well unless it won't fit next to what we already have.
511 // But nobody else implements it that way...
512 bool BlockReflowState::AddFloat(nsLineLayout
* aLineLayout
, nsIFrame
* aFloat
,
513 nscoord aAvailableISize
) {
514 MOZ_ASSERT(aLineLayout
, "must have line layout");
515 MOZ_ASSERT(mBlock
->LinesEnd() != mCurrentLine
, "null ptr");
516 MOZ_ASSERT(aFloat
->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW
),
517 "aFloat must be an out-of-flow frame");
519 MOZ_ASSERT(aFloat
->GetParent(), "float must have parent");
520 MOZ_ASSERT(aFloat
->GetParent()->IsBlockFrameOrSubclass(),
521 "float's parent must be block");
522 if (aFloat
->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT
) ||
523 aFloat
->GetParent() != mBlock
) {
524 MOZ_ASSERT(aFloat
->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT
|
525 NS_FRAME_FIRST_REFLOW
),
526 "float should be in this block unless it was marked as "
527 "pushed float, or just inserted");
528 MOZ_ASSERT(aFloat
->GetParent()->FirstContinuation() ==
529 mBlock
->FirstContinuation());
530 // If, in a previous reflow, the float was pushed entirely to
531 // another column/page, we need to steal it back. (We might just
532 // push it again, though.) Likewise, if that previous reflow
533 // reflowed this block but not its next continuation, we might need
534 // to steal it from our own float-continuations list.
536 // For more about pushed floats, see the comment above
537 // nsBlockFrame::DrainPushedFloats.
538 auto* floatParent
= static_cast<nsBlockFrame
*>(aFloat
->GetParent());
539 floatParent
->StealFrame(aFloat
);
541 aFloat
->RemoveStateBits(NS_FRAME_IS_PUSHED_FLOAT
);
543 // Appending is fine, since if a float was pushed to the next
544 // page/column, all later floats were also pushed.
545 mBlock
->mFloats
.AppendFrame(mBlock
, aFloat
);
548 // Because we are in the middle of reflowing a placeholder frame
549 // within a line (and possibly nested in an inline frame or two
550 // that's a child of our block) we need to restore the space
551 // manager's translation to the space that the block resides in
552 // before placing the float.
554 FloatManager()->GetTranslation(oI
, oB
);
555 nscoord dI
= oI
- mFloatManagerI
;
556 nscoord dB
= oB
- mFloatManagerB
;
557 FloatManager()->Translate(-dI
, -dB
);
561 // Now place the float immediately if possible. Otherwise stash it
562 // away in mBelowCurrentLineFloats and place it later.
563 // If one or more floats has already been pushed to the next line,
564 // don't let this one go on the current line, since that would violate
566 LogicalRect floatAvailableSpace
=
567 GetFloatAvailableSpaceForPlacingFloat(mBCoord
).mRect
;
568 if (mBelowCurrentLineFloats
.IsEmpty() &&
569 (aLineLayout
->LineIsEmpty() ||
570 mBlock
->ComputeFloatISize(*this, floatAvailableSpace
, aFloat
) <=
573 placed
= FlowAndPlaceFloat(aFloat
);
575 // Pass on updated available space to the current inline reflow engine
576 WritingMode wm
= mReflowInput
.GetWritingMode();
577 // If we have mLineBSize, we are reflowing the line again due to
578 // LineReflowStatus::RedoMoreFloats. We should use mLineBSize to query the
579 // correct available space.
580 nsFlowAreaRect floatAvailSpace
=
581 mLineBSize
.isNothing() ? GetFloatAvailableSpace(mBCoord
)
582 : GetFloatAvailableSpaceForBSize(
583 mBCoord
, mLineBSize
.value(), nullptr);
584 LogicalRect
availSpace(wm
, floatAvailSpace
.mRect
.IStart(wm
), mBCoord
,
585 floatAvailSpace
.mRect
.ISize(wm
),
586 floatAvailSpace
.mRect
.BSize(wm
));
587 aLineLayout
->UpdateBand(wm
, availSpace
, aFloat
);
588 // Record this float in the current-line list
589 mCurrentLineFloats
.Append(mFloatCacheFreeList
.Alloc(aFloat
));
591 (*aLineLayout
->GetLine())->SetHadFloatPushed();
594 // Always claim to be placed; we don't know whether we fit yet, so we
595 // deal with this in PlaceBelowCurrentLineFloats
597 // This float will be placed after the line is done (it is a
598 // below-current-line float).
599 mBelowCurrentLineFloats
.Append(mFloatCacheFreeList
.Alloc(aFloat
));
602 // Restore coordinate system
603 FloatManager()->Translate(dI
, dB
);
608 bool BlockReflowState::CanPlaceFloat(
609 nscoord aFloatISize
, const nsFlowAreaRect
& aFloatAvailableSpace
) {
610 // A float fits at a given block-dir position if there are no floats
611 // at its inline-dir position (no matter what its inline size) or if
612 // its inline size fits in the space remaining after prior floats have
614 // FIXME: We should allow overflow by up to half a pixel here (bug 21193).
615 return !aFloatAvailableSpace
.HasFloats() ||
616 aFloatAvailableSpace
.mRect
.ISize(mReflowInput
.GetWritingMode()) >=
620 // Return the inline-size that the float (including margins) will take up
621 // in the writing mode of the containing block. If this returns
622 // NS_UNCONSTRAINEDSIZE, we're dealing with an orthogonal block that
623 // has block-size:auto, and we'll need to actually reflow it to find out
624 // how much inline-size it will occupy in the containing block's mode.
625 static nscoord
FloatMarginISize(const ReflowInput
& aCBReflowInput
,
626 nscoord aFloatAvailableISize
, nsIFrame
* aFloat
,
627 const SizeComputationInput
& aFloatOffsetState
) {
628 AutoMaybeDisableFontInflation
an(aFloat
);
629 WritingMode wm
= aFloatOffsetState
.GetWritingMode();
631 auto floatSize
= aFloat
->ComputeSize(
632 aCBReflowInput
.mRenderingContext
, wm
, aCBReflowInput
.ComputedSize(wm
),
633 aFloatAvailableISize
,
634 aFloatOffsetState
.ComputedLogicalMargin(wm
).Size(wm
),
635 aFloatOffsetState
.ComputedLogicalBorderPadding(wm
).Size(wm
), {},
636 ComputeSizeFlag::ShrinkWrap
);
638 WritingMode cbwm
= aCBReflowInput
.GetWritingMode();
639 nscoord floatISize
= floatSize
.mLogicalSize
.ConvertTo(cbwm
, wm
).ISize(cbwm
);
640 if (floatISize
== NS_UNCONSTRAINEDSIZE
) {
641 return NS_UNCONSTRAINEDSIZE
; // reflow is needed to get the true size
645 aFloatOffsetState
.ComputedLogicalMargin(cbwm
).IStartEnd(cbwm
) +
646 aFloatOffsetState
.ComputedLogicalBorderPadding(cbwm
).IStartEnd(cbwm
);
649 // A frame property that stores the last shape source / margin / etc. if there's
650 // any shape, in order to invalidate the float area properly when it changes.
652 // TODO(emilio): This could really belong to GetRegionFor / StoreRegionFor, but
653 // when I tried it was a bit awkward because of the logical -> physical
654 // conversion that happens there.
656 // Maybe all this code could be refactored to make this cleaner, but keeping the
657 // two properties separated was slightly nicer.
658 struct ShapeInvalidationData
{
659 StyleShapeOutside mShapeOutside
{StyleShapeOutside::None()};
660 float mShapeImageThreshold
= 0.0;
661 LengthPercentage mShapeMargin
;
663 ShapeInvalidationData() = default;
665 explicit ShapeInvalidationData(const nsStyleDisplay
& aDisplay
) {
669 static bool IsNeeded(const nsStyleDisplay
& aDisplay
) {
670 return !aDisplay
.mShapeOutside
.IsNone();
673 void Update(const nsStyleDisplay
& aDisplay
) {
674 MOZ_ASSERT(IsNeeded(aDisplay
));
675 mShapeOutside
= aDisplay
.mShapeOutside
;
676 mShapeImageThreshold
= aDisplay
.mShapeImageThreshold
;
677 mShapeMargin
= aDisplay
.mShapeMargin
;
680 bool Matches(const nsStyleDisplay
& aDisplay
) const {
681 return mShapeOutside
== aDisplay
.mShapeOutside
&&
682 mShapeImageThreshold
== aDisplay
.mShapeImageThreshold
&&
683 mShapeMargin
== aDisplay
.mShapeMargin
;
687 NS_DECLARE_FRAME_PROPERTY_DELETABLE(ShapeInvalidationDataProperty
,
688 ShapeInvalidationData
)
690 bool BlockReflowState::FlowAndPlaceFloat(nsIFrame
* aFloat
) {
691 MOZ_ASSERT(aFloat
->GetParent() == mBlock
);
693 WritingMode wm
= mReflowInput
.GetWritingMode();
694 // Save away the Y coordinate before placing the float. We will
695 // restore mBCoord at the end after placing the float. This is
696 // necessary because any adjustments to mBCoord during the float
697 // placement are for the float only, not for any non-floating
699 AutoRestore
<nscoord
> restoreBCoord(mBCoord
);
701 // Grab the float's display information
702 const nsStyleDisplay
* floatDisplay
= aFloat
->StyleDisplay();
704 // The float's old region, so we can propagate damage.
705 LogicalRect oldRegion
=
706 nsFloatManager::GetRegionFor(wm
, aFloat
, ContainerSize());
708 ShapeInvalidationData
* invalidationData
=
709 aFloat
->GetProperty(ShapeInvalidationDataProperty());
711 // Enforce CSS2 9.5.1 rule [2], i.e., make sure that a float isn't
712 // ``above'' another float that preceded it in the flow.
713 mBCoord
= std::max(FloatManager()->GetLowestFloatTop(), mBCoord
);
715 // See if the float should clear any preceding floats...
716 // XXX We need to mark this float somehow so that it gets reflowed
717 // when floats are inserted before it.
718 if (StyleClear::None
!= floatDisplay
->mBreakType
) {
719 // XXXldb Does this handle vertical margins correctly?
720 auto [bCoord
, result
] = ClearFloats(mBCoord
, floatDisplay
->mBreakType
);
721 if (result
== ClearFloatsResult::FloatsPushedOrSplit
) {
722 PushFloatPastBreak(aFloat
);
728 // Get the band of available space with respect to margin box.
729 nsFlowAreaRect floatAvailableSpace
=
730 GetFloatAvailableSpaceForPlacingFloat(mBCoord
);
731 LogicalRect adjustedAvailableSpace
=
732 mBlock
->AdjustFloatAvailableSpace(*this, floatAvailableSpace
.mRect
);
734 NS_ASSERTION(aFloat
->GetParent() == mBlock
, "Float frame has wrong parent");
736 SizeComputationInput
offsets(aFloat
, mReflowInput
.mRenderingContext
, wm
,
737 mReflowInput
.ComputedISize());
739 nscoord floatMarginISize
= FloatMarginISize(
740 mReflowInput
, adjustedAvailableSpace
.ISize(wm
), aFloat
, offsets
);
742 LogicalMargin
floatMargin(wm
); // computed margin
743 LogicalMargin
floatOffsets(wm
);
744 nsReflowStatus reflowStatus
;
746 // If it's a floating first-letter, we need to reflow it before we
747 // know how wide it is (since we don't compute which letters are part
748 // of the first letter until reflow!).
749 // We also need to do this early reflow if FloatMarginISize returned
750 // an unconstrained inline-size, which can occur if the float had an
751 // orthogonal writing mode and 'auto' block-size (in its mode).
752 bool earlyFloatReflow
=
753 aFloat
->IsLetterFrame() || floatMarginISize
== NS_UNCONSTRAINEDSIZE
;
754 if (earlyFloatReflow
) {
755 mBlock
->ReflowFloat(*this, adjustedAvailableSpace
, aFloat
, floatMargin
,
756 floatOffsets
, false, reflowStatus
);
757 floatMarginISize
= aFloat
->ISize(wm
) + floatMargin
.IStartEnd(wm
);
758 NS_ASSERTION(reflowStatus
.IsComplete(),
759 "letter frames and orthogonal floats with auto block-size "
760 "shouldn't break, and if they do now, then they're breaking "
761 "at the wrong point");
764 // Find a place to place the float. The CSS2 spec doesn't want
765 // floats overlapping each other or sticking out of the containing
766 // block if possible (CSS2 spec section 9.5.1, see the rule list).
767 StyleFloat floatStyle
= floatDisplay
->mFloat
;
768 MOZ_ASSERT(StyleFloat::Left
== floatStyle
|| StyleFloat::Right
== floatStyle
,
769 "Invalid float type!");
771 // Are we required to place at least part of the float because we're
772 // at the top of the page (to avoid an infinite loop of pushing and
774 bool mustPlaceFloat
= mReflowInput
.mFlags
.mIsTopOfPage
&& IsAdjacentWithTop();
777 if (mReflowInput
.AvailableHeight() != NS_UNCONSTRAINEDSIZE
&&
778 floatAvailableSpace
.mRect
.BSize(wm
) <= 0 && !mustPlaceFloat
) {
779 // No space, nowhere to put anything.
780 PushFloatPastBreak(aFloat
);
784 if (CanPlaceFloat(floatMarginISize
, floatAvailableSpace
)) {
785 // We found an appropriate place.
789 // Nope. try to advance to the next band.
790 mBCoord
+= floatAvailableSpace
.mRect
.BSize(wm
);
791 if (adjustedAvailableSpace
.BSize(wm
) != NS_UNCONSTRAINEDSIZE
) {
792 adjustedAvailableSpace
.BSize(wm
) -= floatAvailableSpace
.mRect
.BSize(wm
);
794 floatAvailableSpace
= GetFloatAvailableSpaceForPlacingFloat(mBCoord
);
795 mustPlaceFloat
= false;
798 // If the float is continued, it will get the same absolute x value as its
801 // We don't worry about the geometry of the prev in flow, let the continuation
802 // place and size itself as required.
804 // Assign inline and block dir coordinates to the float. We don't use
805 // LineLeft() and LineRight() here, because we would only have to
806 // convert the result back into this block's writing mode.
807 LogicalPoint
floatPos(wm
);
808 bool leftFloat
= floatStyle
== StyleFloat::Left
;
810 if (leftFloat
== wm
.IsBidiLTR()) {
811 floatPos
.I(wm
) = floatAvailableSpace
.mRect
.IStart(wm
);
813 floatPos
.I(wm
) = floatAvailableSpace
.mRect
.IEnd(wm
) - floatMarginISize
;
815 // CSS2 spec, 9.5.1 rule [4]: "A floating box's outer top may not
816 // be higher than the top of its containing block." (Since the
817 // containing block is the content edge of the block box, this
818 // means the margin edge of the float can't be higher than the
819 // content edge of the block that contains it.)
820 floatPos
.B(wm
) = std::max(mBCoord
, ContentBStart());
822 // Reflow the float after computing its vertical position so it knows
824 if (!earlyFloatReflow
) {
825 bool pushedDown
= mBCoord
!= restoreBCoord
.SavedValue();
826 mBlock
->ReflowFloat(*this, adjustedAvailableSpace
, aFloat
, floatMargin
,
827 floatOffsets
, pushedDown
, reflowStatus
);
829 if (aFloat
->GetPrevInFlow()) {
830 floatMargin
.BStart(wm
) = 0;
832 if (reflowStatus
.IsIncomplete()) {
833 floatMargin
.BEnd(wm
) = 0;
836 // If none of the float fit, and it needs to be pushed in its entirety to the
837 // next page, we need to bail.
838 if (reflowStatus
.IsTruncated() || reflowStatus
.IsInlineBreakBefore()) {
839 PushFloatPastBreak(aFloat
);
843 // We can't use aFloat->ShouldAvoidBreakInside(mReflowInput) here since
844 // its mIsTopOfPage may be true even though the float isn't at the
845 // top when floatPos.B(wm) > 0.
846 if (ContentBSize() != NS_UNCONSTRAINEDSIZE
&& !mustPlaceFloat
&&
847 (!mReflowInput
.mFlags
.mIsTopOfPage
|| floatPos
.B(wm
) > 0) &&
848 StyleBreakWithin::Avoid
== aFloat
->StyleDisplay()->mBreakInside
&&
849 (!reflowStatus
.IsFullyComplete() ||
850 aFloat
->BSize(wm
) + floatMargin
.BStartEnd(wm
) >
851 ContentBEnd() - floatPos
.B(wm
)) &&
852 !aFloat
->GetPrevInFlow()) {
853 PushFloatPastBreak(aFloat
);
857 // Calculate the actual origin of the float frame's border rect
858 // relative to the parent block; the margin must be added in
859 // to get the border rect
860 LogicalPoint
origin(wm
, floatMargin
.IStart(wm
) + floatPos
.I(wm
),
861 floatMargin
.BStart(wm
) + floatPos
.B(wm
));
863 // If float is relatively positioned, factor that in as well
864 ReflowInput::ApplyRelativePositioning(aFloat
, wm
, floatOffsets
, &origin
,
867 // Position the float and make sure and views are properly
868 // positioned. We need to explicitly position its child views as
869 // well, since we're moving the float after flowing it.
870 bool moved
= aFloat
->GetLogicalPosition(wm
, ContainerSize()) != origin
;
872 aFloat
->SetPosition(wm
, origin
, ContainerSize());
873 nsContainerFrame::PositionFrameView(aFloat
);
874 nsContainerFrame::PositionChildViews(aFloat
);
877 // Update the float combined area state
878 // XXX Floats should really just get invalidated here if necessary
879 mFloatOverflowAreas
.UnionWith(aFloat
->GetOverflowAreasRelativeToParent());
881 // Place the float in the float manager
883 LogicalRect region
= nsFloatManager::CalculateRegionFor(
884 wm
, aFloat
, floatMargin
, ContainerSize());
885 // if the float split, then take up all of the vertical height
886 if (reflowStatus
.IsIncomplete() && (NS_UNCONSTRAINEDSIZE
!= ContentBSize())) {
888 std::max(region
.BSize(wm
), ContentBSize() - floatPos
.B(wm
));
890 FloatManager()->AddFloat(aFloat
, region
, wm
, ContainerSize());
893 nsFloatManager::StoreRegionFor(wm
, aFloat
, region
, ContainerSize());
895 const bool invalidationDataNeeded
=
896 ShapeInvalidationData::IsNeeded(*floatDisplay
);
898 // If the float's dimensions or shape have changed, note the damage in the
900 if (!region
.IsEqualEdges(oldRegion
) ||
901 !!invalidationData
!= invalidationDataNeeded
||
902 (invalidationData
&& !invalidationData
->Matches(*floatDisplay
))) {
903 // XXXwaterson conservative: we could probably get away with noting
904 // less damage; e.g., if only height has changed, then only note the
905 // area into which the float has grown or from which the float has
907 nscoord blockStart
= std::min(region
.BStart(wm
), oldRegion
.BStart(wm
));
908 nscoord blockEnd
= std::max(region
.BEnd(wm
), oldRegion
.BEnd(wm
));
909 FloatManager()->IncludeInDamage(blockStart
, blockEnd
);
912 if (invalidationDataNeeded
) {
913 if (invalidationData
) {
914 invalidationData
->Update(*floatDisplay
);
916 aFloat
->SetProperty(ShapeInvalidationDataProperty(),
917 new ShapeInvalidationData(*floatDisplay
));
919 } else if (invalidationData
) {
920 invalidationData
= nullptr;
921 aFloat
->RemoveProperty(ShapeInvalidationDataProperty());
924 if (!reflowStatus
.IsFullyComplete()) {
925 mBlock
->SplitFloat(*this, aFloat
, reflowStatus
);
927 MOZ_ASSERT(!aFloat
->GetNextInFlow());
931 if (nsBlockFrame::gNoisyFloatManager
) {
933 FloatManager()->GetTranslation(tI
, tB
);
934 mBlock
->ListTag(stdout
);
935 printf(": FlowAndPlaceFloat: AddFloat: tIB=%d,%d (%d,%d) {%d,%d,%d,%d}\n",
936 tI
, tB
, mFloatManagerI
, mFloatManagerB
, region
.IStart(wm
),
937 region
.BStart(wm
), region
.ISize(wm
), region
.BSize(wm
));
940 if (nsBlockFrame::gNoisyReflow
) {
941 nsRect r
= aFloat
->GetRect();
942 nsIFrame::IndentBy(stdout
, nsBlockFrame::gNoiseIndent
);
943 printf("placed float: ");
944 aFloat
->ListTag(stdout
);
945 printf(" %d,%d,%d,%d\n", r
.x
, r
.y
, r
.width
, r
.height
);
952 void BlockReflowState::PushFloatPastBreak(nsIFrame
* aFloat
) {
953 // This ensures that we:
954 // * don't try to place later but smaller floats (which CSS says
955 // must have their tops below the top of this float)
956 // * don't waste much time trying to reflow this float again until
958 StyleFloat floatStyle
= aFloat
->StyleDisplay()->mFloat
;
959 if (floatStyle
== StyleFloat::Left
) {
960 FloatManager()->SetPushedLeftFloatPastBreak();
962 MOZ_ASSERT(floatStyle
== StyleFloat::Right
, "Unexpected float value!");
963 FloatManager()->SetPushedRightFloatPastBreak();
966 // Put the float on the pushed floats list, even though it
967 // isn't actually a continuation.
968 mBlock
->StealFrame(aFloat
);
969 AppendPushedFloatChain(aFloat
);
970 mReflowStatus
.SetOverflowIncomplete();
974 * Place below-current-line floats.
976 void BlockReflowState::PlaceBelowCurrentLineFloats(nsLineBox
* aLine
) {
977 MOZ_ASSERT(mBelowCurrentLineFloats
.NotEmpty());
978 nsFloatCache
* fc
= mBelowCurrentLineFloats
.Head();
981 if (nsBlockFrame::gNoisyReflow
) {
982 nsIFrame::IndentBy(stdout
, nsBlockFrame::gNoiseIndent
);
983 printf("placing bcl float: ");
984 fc
->mFloat
->ListTag(stdout
);
989 bool placed
= FlowAndPlaceFloat(fc
->mFloat
);
990 nsFloatCache
* next
= fc
->Next();
992 mBelowCurrentLineFloats
.Remove(fc
);
994 aLine
->SetHadFloatPushed();
998 aLine
->AppendFloats(mBelowCurrentLineFloats
);
1001 std::tuple
<nscoord
, BlockReflowState::ClearFloatsResult
>
1002 BlockReflowState::ClearFloats(nscoord aBCoord
, StyleClear aBreakType
,
1003 nsIFrame
* aReplacedBlock
) {
1005 if (nsBlockFrame::gNoisyReflow
) {
1006 nsIFrame::IndentBy(stdout
, nsBlockFrame::gNoiseIndent
);
1007 printf("clear floats: in: aBCoord=%d\n", aBCoord
);
1011 #ifdef NOISY_FLOAT_CLEARING
1012 printf("BlockReflowState::ClearFloats: aBCoord=%d breakType=%s\n", aBCoord
,
1013 nsLineBox::BreakTypeToString(aBreakType
));
1014 FloatManager()->List(stdout
);
1017 if (!FloatManager()->HasAnyFloats()) {
1018 return {aBCoord
, ClearFloatsResult::BCoordNoChange
};
1021 nscoord newBCoord
= aBCoord
;
1023 if (aBreakType
!= StyleClear::None
) {
1024 newBCoord
= FloatManager()->ClearFloats(newBCoord
, aBreakType
);
1026 if (FloatManager()->ClearContinues(aBreakType
)) {
1027 return {newBCoord
, ClearFloatsResult::FloatsPushedOrSplit
};
1031 if (aReplacedBlock
) {
1033 nsFlowAreaRect floatAvailableSpace
= GetFloatAvailableSpace(newBCoord
);
1034 if (ReplacedBlockFitsInAvailSpace(aReplacedBlock
, floatAvailableSpace
)) {
1037 // See the analogous code for inlines in
1038 // nsBlockFrame::DoReflowInlineFrames
1039 if (!AdvanceToNextBand(floatAvailableSpace
.mRect
, &newBCoord
)) {
1040 // Stop trying to clear here; we'll just get pushed to the
1041 // next column or page and try again there.
1048 if (nsBlockFrame::gNoisyReflow
) {
1049 nsIFrame::IndentBy(stdout
, nsBlockFrame::gNoiseIndent
);
1050 printf("clear floats: out: y=%d\n", newBCoord
);
1054 ClearFloatsResult result
= newBCoord
== aBCoord
1055 ? ClearFloatsResult::BCoordNoChange
1056 : ClearFloatsResult::BCoordAdvanced
;
1057 return {newBCoord
, result
};