Bug 1763869 [wpt PR 33577] - Fix adb command to find webview package, a=testonly
[gecko.git] / layout / generic / BlockReflowState.cpp
blobd8115aea0b3e3f61631c8587b63320a128f8b871
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"
11 #include <algorithm>
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"
23 #ifdef DEBUG
24 # include "nsBlockDebugFlags.h"
25 #endif
27 using namespace mozilla;
28 using namespace mozilla::layout;
30 BlockReflowState::BlockReflowState(const ReflowInput& aReflowInput,
31 nsPresContext* aPresContext,
32 nsBlockFrame* aFrame, bool aBStartMarginRoot,
33 bool aBEndMarginRoot,
34 bool aBlockNeedsFloatManager,
35 const nscoord aConsumedBSize,
36 const nscoord aEffectiveContentBoxBSize)
37 : mBlock(aFrame),
38 mPresContext(aPresContext),
39 mReflowInput(aReflowInput),
40 mContentArea(aReflowInput.GetWritingMode()),
41 mPushedFloats(nullptr),
42 mOverflowTracker(nullptr),
43 mBorderPadding(
44 mReflowInput
45 .ComputedLogicalBorderPadding(mReflowInput.GetWritingMode())
46 .ApplySkipSides(aFrame->PreReflowBlockLevelLogicalSkipSides())),
47 mPrevBEndMargin(),
48 mMinLineHeight(aReflowInput.GetLineHeight()),
49 mLineNumber(0),
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
60 // containing block.
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
65 // frame.
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) >
132 availableBSize);
133 const nscoord bp = reserveSpaceForBlockEndBP ? mBorderPadding.BStartEnd(wm)
134 : mBorderPadding.BStart(wm);
135 mContentArea.BSize(wm) = std::max(0, availableBSize - bp);
136 } else {
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.
165 iStartOffset = 0;
166 iEndOffset = 0;
167 } else {
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);
180 iEndOffset =
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());
194 #endif
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.
220 NS_ASSERTION(
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) {
229 default:
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);
236 break;
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);
242 } break;
244 } else {
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);
251 } else {
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));
262 #endif
264 return result;
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.
274 return true;
276 WritingMode wm = mReflowInput.GetWritingMode();
277 nsBlockFrame::ReplacedElementISizeToClear replacedISize =
278 nsBlockFrame::ISizeToClearPastFloats(*this, aFloatAvailableSpace.mRect,
279 aReplacedBlock);
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.
284 return std::max(
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();
296 #ifdef DEBUG
297 // Verify that the caller setup the coordinate system properly
298 nscoord wI, wB;
299 FloatManager()->GetTranslation(wI, wB);
301 NS_ASSERTION((wI == mFloatManagerI) && (wB == mFloatManagerB),
302 "bad coord system");
303 #endif
305 nscoord blockSize = (mContentArea.BSize(wm) == nscoord_MAX)
306 ? 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;
316 #ifdef DEBUG
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());
323 #endif
324 return result;
327 nsFlowAreaRect BlockReflowState::GetFloatAvailableSpaceForBSize(
328 nscoord aBCoord, nscoord aBSize, nsFloatManager::SavedState* aState) const {
329 WritingMode wm = mReflowInput.GetWritingMode();
330 #ifdef DEBUG
331 // Verify that the caller setup the coordinate system properly
332 nscoord wI, wB;
333 FloatManager()->GetTranslation(wI, wB);
335 NS_ASSERTION((wI == mFloatManagerI) && (wB == mFloatManagerB),
336 "bad coord system");
337 #endif
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;
346 #ifdef DEBUG
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());
353 #endif
354 return result;
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();
375 for (;;) {
376 --aLine;
377 if (aLine->IsBlock()) {
378 mPrevBEndMargin = aLine->GetCarriedOutBEndMargin();
379 break;
381 if (!aLine->IsEmpty()) {
382 break;
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();
390 break;
395 void BlockReflowState::SetupPushedFloatList() {
396 MOZ_ASSERT(!mFlags.mIsFloatListInBlockPropertyTable == !mPushedFloats,
397 "flag mismatch");
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();
413 while (true) {
414 aFloatCont->AddStateBits(NS_FRAME_IS_PUSHED_FLOAT);
415 mPushedFloats->AppendFrame(mBlock, aFloatCont);
416 aFloatCont = aFloatCont->GetNextInFlow();
417 if (!aFloatCont || aFloatCont->GetParent() != mBlock) {
418 break;
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();
439 while (fc) {
440 nsIFrame* floatFrame = fc->mFloat;
441 if (aDeltaBCoord != 0) {
442 floatFrame->MovePositionBy(nsPoint(0, aDeltaBCoord));
443 nsContainerFrame::PositionFrameView(floatFrame);
444 nsContainerFrame::PositionChildViews(floatFrame);
446 #ifdef DEBUG
447 if (nsBlockFrame::gNoisyReflow || nsBlockFrame::gNoisyFloatManager) {
448 nscoord tI, tB;
449 FloatManager()->GetTranslation(tI, tB);
450 nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
451 printf("RecoverFloats: tIB=%d,%d (%d,%d) ", tI, tB, mFloatManagerI,
452 mFloatManagerB);
453 floatFrame->ListTag(stdout);
454 LogicalRect region =
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),
458 region.BSize(wm));
460 #endif
461 FloatManager()->AddFloat(
462 floatFrame,
463 nsFloatManager::GetRegionFor(wm, floatFrame, ContainerSize()), wm,
464 ContainerSize());
465 fc = fc->Next();
467 } else if (aLine->IsBlock()) {
468 nsBlockFrame::RecoverFloatsFor(aLine->mFirstChild, *FloatManager(), wm,
469 ContainerSize());
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);
494 #ifdef DEBUG
495 if (nsBlockFrame::gNoisyReflow || nsBlockFrame::gNoisyFloatManager) {
496 FloatManager()->List(stdout);
498 #endif
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.
553 nscoord oI, oB;
554 FloatManager()->GetTranslation(oI, oB);
555 nscoord dI = oI - mFloatManagerI;
556 nscoord dB = oB - mFloatManagerB;
557 FloatManager()->Translate(-dI, -dB);
559 bool placed;
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
565 // float ordering.
566 LogicalRect floatAvailableSpace =
567 GetFloatAvailableSpaceForPlacingFloat(mBCoord).mRect;
568 if (mBelowCurrentLineFloats.IsEmpty() &&
569 (aLineLayout->LineIsEmpty() ||
570 mBlock->ComputeFloatISize(*this, floatAvailableSpace, aFloat) <=
571 aAvailableISize)) {
572 // And then place it
573 placed = FlowAndPlaceFloat(aFloat);
574 if (placed) {
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));
590 } else {
591 (*aLineLayout->GetLine())->SetHadFloatPushed();
593 } else {
594 // Always claim to be placed; we don't know whether we fit yet, so we
595 // deal with this in PlaceBelowCurrentLineFloats
596 placed = true;
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);
605 return placed;
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
613 // been placed.
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()) >=
617 aFloatISize;
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
644 return floatISize +
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) {
666 Update(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
698 // content.
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);
723 return false;
725 mBCoord = bCoord;
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
773 // breaking).
774 bool mustPlaceFloat = mReflowInput.mFlags.mIsTopOfPage && IsAdjacentWithTop();
776 for (;;) {
777 if (mReflowInput.AvailableHeight() != NS_UNCONSTRAINEDSIZE &&
778 floatAvailableSpace.mRect.BSize(wm) <= 0 && !mustPlaceFloat) {
779 // No space, nowhere to put anything.
780 PushFloatPastBreak(aFloat);
781 return false;
784 if (CanPlaceFloat(floatMarginISize, floatAvailableSpace)) {
785 // We found an appropriate place.
786 break;
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
799 // prev-in-flow
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);
812 } else {
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
823 // where to break.
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);
840 return false;
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);
854 return false;
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,
865 ContainerSize());
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;
871 if (moved) {
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
882 // calculate region
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())) {
887 region.BSize(wm) =
888 std::max(region.BSize(wm), ContentBSize() - floatPos.B(wm));
890 FloatManager()->AddFloat(aFloat, region, wm, ContainerSize());
892 // store region
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
899 // float manager.
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
906 // shrunk.
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);
915 } else {
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);
926 } else {
927 MOZ_ASSERT(!aFloat->GetNextInFlow());
930 #ifdef DEBUG
931 if (nsBlockFrame::gNoisyFloatManager) {
932 nscoord tI, tB;
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);
947 #endif
949 return true;
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
957 // after the break
958 StyleFloat floatStyle = aFloat->StyleDisplay()->mFloat;
959 if (floatStyle == StyleFloat::Left) {
960 FloatManager()->SetPushedLeftFloatPastBreak();
961 } else {
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();
979 while (fc) {
980 #ifdef DEBUG
981 if (nsBlockFrame::gNoisyReflow) {
982 nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
983 printf("placing bcl float: ");
984 fc->mFloat->ListTag(stdout);
985 printf("\n");
987 #endif
988 // Place the float
989 bool placed = FlowAndPlaceFloat(fc->mFloat);
990 nsFloatCache* next = fc->Next();
991 if (!placed) {
992 mBelowCurrentLineFloats.Remove(fc);
993 delete fc;
994 aLine->SetHadFloatPushed();
996 fc = next;
998 aLine->AppendFloats(mBelowCurrentLineFloats);
1001 std::tuple<nscoord, BlockReflowState::ClearFloatsResult>
1002 BlockReflowState::ClearFloats(nscoord aBCoord, StyleClear aBreakType,
1003 nsIFrame* aReplacedBlock) {
1004 #ifdef DEBUG
1005 if (nsBlockFrame::gNoisyReflow) {
1006 nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
1007 printf("clear floats: in: aBCoord=%d\n", aBCoord);
1009 #endif
1011 #ifdef NOISY_FLOAT_CLEARING
1012 printf("BlockReflowState::ClearFloats: aBCoord=%d breakType=%s\n", aBCoord,
1013 nsLineBox::BreakTypeToString(aBreakType));
1014 FloatManager()->List(stdout);
1015 #endif
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) {
1032 for (;;) {
1033 nsFlowAreaRect floatAvailableSpace = GetFloatAvailableSpace(newBCoord);
1034 if (ReplacedBlockFitsInAvailSpace(aReplacedBlock, floatAvailableSpace)) {
1035 break;
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.
1042 break;
1047 #ifdef DEBUG
1048 if (nsBlockFrame::gNoisyReflow) {
1049 nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
1050 printf("clear floats: out: y=%d\n", newBCoord);
1052 #endif
1054 ClearFloatsResult result = newBCoord == aBCoord
1055 ? ClearFloatsResult::BCoordNoChange
1056 : ClearFloatsResult::BCoordAdvanced;
1057 return {newBCoord, result};