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 /* rendering object for css3 multi-column layout */
9 #include "nsColumnSetFrame.h"
11 #include "mozilla/ColumnUtils.h"
12 #include "mozilla/Logging.h"
13 #include "mozilla/PresShell.h"
14 #include "mozilla/StaticPrefs_layout.h"
15 #include "mozilla/ToString.h"
16 #include "nsCSSRendering.h"
17 #include "nsDisplayList.h"
18 #include "nsIFrameInlines.h"
19 #include "nsLayoutUtils.h"
21 using namespace mozilla
;
22 using namespace mozilla::layout
;
24 // To see this log, use $ MOZ_LOG=ColumnSet:4 ./mach run
25 static LazyLogModule
sColumnSetLog("ColumnSet");
26 #define COLUMN_SET_LOG(msg, ...) \
27 MOZ_LOG(sColumnSetLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
29 class nsDisplayColumnRule
: public nsPaintedDisplayItem
{
31 nsDisplayColumnRule(nsDisplayListBuilder
* aBuilder
, nsIFrame
* aFrame
)
32 : nsPaintedDisplayItem(aBuilder
, aFrame
) {
33 MOZ_COUNT_CTOR(nsDisplayColumnRule
);
35 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayColumnRule
)
37 nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
, bool* aSnap
) const override
{
39 // We just return the frame's ink-overflow rect, which is guaranteed to
40 // contain all the column-rule areas. It's not worth calculating the exact
41 // union of those areas since it would only lead to performance improvements
42 // during painting in rare edge cases.
43 return mFrame
->InkOverflowRect() + ToReferenceFrame();
46 bool CreateWebRenderCommands(
47 mozilla::wr::DisplayListBuilder
& aBuilder
,
48 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
49 const StackingContextHelper
& aSc
,
50 mozilla::layers::RenderRootStateManager
* aManager
,
51 nsDisplayListBuilder
* aDisplayListBuilder
) override
;
52 void Paint(nsDisplayListBuilder
* aBuilder
, gfxContext
* aCtx
) override
;
54 NS_DISPLAY_DECL_NAME("ColumnRule", TYPE_COLUMN_RULE
);
57 nsTArray
<nsCSSBorderRenderer
> mBorderRenderers
;
60 void nsDisplayColumnRule::Paint(nsDisplayListBuilder
* aBuilder
,
62 static_cast<nsColumnSetFrame
*>(mFrame
)->CreateBorderRenderers(
63 mBorderRenderers
, aCtx
, GetPaintRect(aBuilder
, aCtx
), ToReferenceFrame());
65 for (auto iter
= mBorderRenderers
.begin(); iter
!= mBorderRenderers
.end();
71 bool nsDisplayColumnRule::CreateWebRenderCommands(
72 mozilla::wr::DisplayListBuilder
& aBuilder
,
73 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
74 const StackingContextHelper
& aSc
,
75 mozilla::layers::RenderRootStateManager
* aManager
,
76 nsDisplayListBuilder
* aDisplayListBuilder
) {
77 RefPtr dt
= gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
78 if (!dt
|| !dt
->IsValid()) {
81 gfxContext
screenRefCtx(dt
);
84 static_cast<nsColumnSetFrame
*>(mFrame
)->CreateBorderRenderers(
85 mBorderRenderers
, &screenRefCtx
, GetBounds(aDisplayListBuilder
, &dummy
),
88 if (mBorderRenderers
.IsEmpty()) {
92 for (auto& renderer
: mBorderRenderers
) {
93 renderer
.CreateWebRenderCommands(this, aBuilder
, aResources
, aSc
);
99 // The maximum number of columns we support.
100 static constexpr int32_t kMaxColumnCount
= 1000;
105 * XXX cursor movement around the top and bottom of colums seems to make the
106 * editor lose the caret.
108 * XXX should we support CSS columns applied to table elements?
110 nsContainerFrame
* NS_NewColumnSetFrame(PresShell
* aPresShell
,
111 ComputedStyle
* aStyle
,
112 nsFrameState aStateFlags
) {
113 nsColumnSetFrame
* it
=
114 new (aPresShell
) nsColumnSetFrame(aStyle
, aPresShell
->GetPresContext());
115 it
->AddStateBits(aStateFlags
);
119 NS_IMPL_FRAMEARENA_HELPERS(nsColumnSetFrame
)
121 nsColumnSetFrame::nsColumnSetFrame(ComputedStyle
* aStyle
,
122 nsPresContext
* aPresContext
)
123 : nsContainerFrame(aStyle
, aPresContext
, kClassID
),
124 mLastBalanceBSize(NS_UNCONSTRAINEDSIZE
) {}
126 void nsColumnSetFrame::ForEachColumnRule(
127 const std::function
<void(const nsRect
& lineRect
)>& aSetLineRect
,
128 const nsPoint
& aPt
) const {
129 nsIFrame
* child
= mFrames
.FirstChild();
130 if (!child
) return; // no columns
132 nsIFrame
* nextSibling
= child
->GetNextSibling();
133 if (!nextSibling
) return; // 1 column only - this means no gap to draw on
135 const nsStyleColumn
* colStyle
= StyleColumn();
136 nscoord ruleWidth
= colStyle
->GetColumnRuleWidth();
137 if (!ruleWidth
) return;
139 WritingMode wm
= GetWritingMode();
140 bool isVertical
= wm
.IsVertical();
141 bool isRTL
= wm
.IsBidiRTL();
143 nsRect contentRect
= GetContentRectRelativeToSelf() + aPt
;
144 nsSize ruleSize
= isVertical
? nsSize(contentRect
.width
, ruleWidth
)
145 : nsSize(ruleWidth
, contentRect
.height
);
147 while (nextSibling
) {
148 // The frame tree goes RTL in RTL.
149 // The |prevFrame| and |nextFrame| frames here are the visually preceding
150 // (left/above) and following (right/below) frames, not in logical writing-
152 nsIFrame
* prevFrame
= isRTL
? nextSibling
: child
;
153 nsIFrame
* nextFrame
= isRTL
? child
: nextSibling
;
155 // Each child frame's position coordinates is actually relative to this
157 // linePt will be at the top-left edge to paint the line.
160 nscoord edgeOfPrev
= prevFrame
->GetRect().YMost() + aPt
.y
;
161 nscoord edgeOfNext
= nextFrame
->GetRect().Y() + aPt
.y
;
162 linePt
= nsPoint(contentRect
.x
,
163 (edgeOfPrev
+ edgeOfNext
- ruleSize
.height
) / 2);
165 nscoord edgeOfPrev
= prevFrame
->GetRect().XMost() + aPt
.x
;
166 nscoord edgeOfNext
= nextFrame
->GetRect().X() + aPt
.x
;
167 linePt
= nsPoint((edgeOfPrev
+ edgeOfNext
- ruleSize
.width
) / 2,
171 aSetLineRect(nsRect(linePt
, ruleSize
));
174 nextSibling
= nextSibling
->GetNextSibling();
178 void nsColumnSetFrame::CreateBorderRenderers(
179 nsTArray
<nsCSSBorderRenderer
>& aBorderRenderers
, gfxContext
* aCtx
,
180 const nsRect
& aDirtyRect
, const nsPoint
& aPt
) {
181 WritingMode wm
= GetWritingMode();
182 bool isVertical
= wm
.IsVertical();
183 const nsStyleColumn
* colStyle
= StyleColumn();
184 StyleBorderStyle ruleStyle
;
186 // Per spec, inset => ridge and outset => groove
187 if (colStyle
->mColumnRuleStyle
== StyleBorderStyle::Inset
) {
188 ruleStyle
= StyleBorderStyle::Ridge
;
189 } else if (colStyle
->mColumnRuleStyle
== StyleBorderStyle::Outset
) {
190 ruleStyle
= StyleBorderStyle::Groove
;
192 ruleStyle
= colStyle
->mColumnRuleStyle
;
195 nscoord ruleWidth
= colStyle
->GetColumnRuleWidth();
200 aBorderRenderers
.Clear();
202 GetVisitedDependentColor(&nsStyleColumn::mColumnRuleColor
);
204 nsPresContext
* pc
= PresContext();
205 // In order to re-use a large amount of code, we treat the column rule as a
206 // border. We create a new border style object and fill in all the details of
207 // the column rule as the left border. PaintBorder() does all the rendering
208 // for us, so we not only save an enormous amount of code but we'll support
209 // all the line styles that we support on borders!
210 nsStyleBorder border
;
213 border
.SetBorderWidth(eSideTop
, ruleWidth
, pc
->AppUnitsPerDevPixel());
214 border
.SetBorderStyle(eSideTop
, ruleStyle
);
215 border
.mBorderTopColor
= StyleColor::FromColor(ruleColor
);
216 skipSides
|= mozilla::SideBits::eLeftRight
;
217 skipSides
|= mozilla::SideBits::eBottom
;
219 border
.SetBorderWidth(eSideLeft
, ruleWidth
, pc
->AppUnitsPerDevPixel());
220 border
.SetBorderStyle(eSideLeft
, ruleStyle
);
221 border
.mBorderLeftColor
= StyleColor::FromColor(ruleColor
);
222 skipSides
|= mozilla::SideBits::eTopBottom
;
223 skipSides
|= mozilla::SideBits::eRight
;
225 // If we use box-decoration-break: slice (the default), the border
226 // renderers will require clipping if we have continuations (see the
227 // aNeedsClip parameter to ConstructBorderRenderer in nsCSSRendering).
229 // Since it doesn't matter which box-decoration-break we use since
230 // we're only drawing borders (and not border-images), use 'clone'.
231 border
.mBoxDecorationBreak
= StyleBoxDecorationBreak::Clone
;
234 [&](const nsRect
& aLineRect
) {
235 // Assert that we're not drawing a border-image here; if we were, we
236 // couldn't ignore the ImgDrawResult that PaintBorderWithStyleBorder
238 MOZ_ASSERT(border
.mBorderImageSource
.IsNone());
240 gfx::DrawTarget
* dt
= aCtx
? aCtx
->GetDrawTarget() : nullptr;
241 bool borderIsEmpty
= false;
242 Maybe
<nsCSSBorderRenderer
> br
=
243 nsCSSRendering::CreateBorderRendererWithStyleBorder(
244 pc
, dt
, this, aDirtyRect
, aLineRect
, border
, Style(),
245 &borderIsEmpty
, skipSides
);
247 MOZ_ASSERT(!borderIsEmpty
);
248 aBorderRenderers
.AppendElement(br
.value());
254 static uint32_t ColumnBalancingDepth(const ReflowInput
& aReflowInput
,
255 uint32_t aMaxDepth
) {
257 for (const ReflowInput
* ri
= aReflowInput
.mParentReflowInput
;
258 ri
&& depth
< aMaxDepth
; ri
= ri
->mParentReflowInput
) {
259 if (ri
->mFlags
.mIsColumnBalancing
) {
266 nsColumnSetFrame::ReflowConfig
nsColumnSetFrame::ChooseColumnStrategy(
267 const ReflowInput
& aReflowInput
, bool aForceAuto
= false) const {
268 const nsStyleColumn
* colStyle
= StyleColumn();
269 nscoord availContentISize
= aReflowInput
.AvailableISize();
270 if (aReflowInput
.ComputedISize() != NS_UNCONSTRAINEDSIZE
) {
271 availContentISize
= aReflowInput
.ComputedISize();
274 nscoord colBSize
= aReflowInput
.AvailableBSize();
276 ColumnUtils::GetColumnGap(this, aReflowInput
.ComputedISize());
278 colStyle
->mColumnCount
.IsAuto()
280 : std::min(colStyle
->mColumnCount
.AsInteger(), kMaxColumnCount
);
282 // If column-fill is set to 'balance' or we have a column-span sibling, then
283 // we want to balance the columns.
284 bool isBalancing
= (colStyle
->mColumnFill
== StyleColumnFill::Balance
||
285 HasColumnSpanSiblings()) &&
288 const uint32_t kMaxNestedColumnBalancingDepth
= 2;
289 const uint32_t balancingDepth
=
290 ColumnBalancingDepth(aReflowInput
, kMaxNestedColumnBalancingDepth
);
291 if (balancingDepth
== kMaxNestedColumnBalancingDepth
) {
298 // In vertical writing-mode, "column-width" (inline size) will actually be
299 // physical height, but its CSS name is still column-width.
300 if (colStyle
->mColumnWidth
.IsLength()) {
302 ColumnUtils::ClampUsedColumnWidth(colStyle
->mColumnWidth
.AsLength());
303 NS_ASSERTION(colISize
>= 0, "negative column width");
304 // Reduce column count if necessary to make columns fit in the
305 // available width. Compute max number of columns that fit in
306 // availContentISize, satisfying colGap*(maxColumns - 1) +
307 // colISize*maxColumns <= availContentISize
308 if (availContentISize
!= NS_UNCONSTRAINEDSIZE
&& colGap
+ colISize
> 0 &&
310 // This expression uses truncated rounding, which is what we
313 std::min(nscoord(kMaxColumnCount
),
314 (availContentISize
+ colGap
) / (colGap
+ colISize
));
315 numColumns
= std::max(1, std::min(numColumns
, maxColumns
));
317 } else if (numColumns
> 0 && availContentISize
!= NS_UNCONSTRAINEDSIZE
) {
318 nscoord iSizeMinusGaps
= availContentISize
- colGap
* (numColumns
- 1);
319 colISize
= iSizeMinusGaps
/ numColumns
;
321 colISize
= NS_UNCONSTRAINEDSIZE
;
323 // Take care of the situation where there's only one column but it's
325 colISize
= std::max(1, std::min(colISize
, availContentISize
));
327 nscoord expectedISizeLeftOver
= 0;
329 if (colISize
!= NS_UNCONSTRAINEDSIZE
&&
330 availContentISize
!= NS_UNCONSTRAINEDSIZE
) {
331 // distribute leftover space
333 // First, determine how many columns will be showing if the column
335 if (numColumns
<= 0) {
336 // choose so that colGap*(nominalColumnCount - 1) +
337 // colISize*nominalColumnCount is nearly availContentISize
338 // make sure to round down
339 if (colGap
+ colISize
> 0) {
340 numColumns
= (availContentISize
+ colGap
) / (colGap
+ colISize
);
341 // The number of columns should never exceed kMaxColumnCount.
342 numColumns
= std::min(kMaxColumnCount
, numColumns
);
344 if (numColumns
<= 0) {
349 // Compute extra space and divide it among the columns
351 std::max(0, availContentISize
-
352 (colISize
* numColumns
+ colGap
* (numColumns
- 1)));
353 nscoord extraToColumns
= extraSpace
/ numColumns
;
354 colISize
+= extraToColumns
;
355 expectedISizeLeftOver
= extraSpace
- (extraToColumns
* numColumns
);
359 if (numColumns
<= 0) {
360 // Hmm, auto column count, column width or available width is unknown,
361 // and balancing is required. Let's just use one column then.
364 colBSize
= std::min(mLastBalanceBSize
, colBSize
);
366 // CSS Fragmentation spec says, "To guarantee progress, fragmentainers are
367 // assumed to have a minimum block size of 1px regardless of their used
368 // size." https://drafts.csswg.org/css-break/#breaking-rules
370 // Note: we don't enforce the minimum block-size during balancing because
371 // this affects the result. If a balancing column container or its
372 // next-in-flows has zero block-size, it eventually gives up balancing, and
374 colBSize
= std::max(colBSize
, nsPresContext::CSSPixelsToAppUnits(1));
378 config
.mUsedColCount
= numColumns
;
379 config
.mColISize
= colISize
;
380 config
.mExpectedISizeLeftOver
= expectedISizeLeftOver
;
381 config
.mColGap
= colGap
;
382 config
.mColBSize
= colBSize
;
383 config
.mIsBalancing
= isBalancing
;
384 config
.mForceAuto
= aForceAuto
;
385 config
.mKnownFeasibleBSize
= NS_UNCONSTRAINEDSIZE
;
386 config
.mKnownInfeasibleBSize
= 0;
389 "%s: this=%p, mUsedColCount=%d, mColISize=%d, "
390 "mExpectedISizeLeftOver=%d, mColGap=%d, mColBSize=%d, mIsBalancing=%d",
391 __func__
, this, config
.mUsedColCount
, config
.mColISize
,
392 config
.mExpectedISizeLeftOver
, config
.mColGap
, config
.mColBSize
,
393 config
.mIsBalancing
);
398 static void MarkPrincipalChildrenDirty(nsIFrame
* aFrame
) {
399 for (nsIFrame
* childFrame
: aFrame
->PrincipalChildList()) {
400 childFrame
->MarkSubtreeDirty();
404 static void MoveChildTo(nsIFrame
* aChild
, LogicalPoint aOrigin
, WritingMode aWM
,
405 const nsSize
& aContainerSize
) {
406 if (aChild
->GetLogicalPosition(aWM
, aContainerSize
) == aOrigin
) {
410 aChild
->SetPosition(aWM
, aOrigin
, aContainerSize
);
411 nsContainerFrame::PlaceFrameView(aChild
);
414 nscoord
nsColumnSetFrame::GetMinISize(gfxContext
* aRenderingContext
) {
417 if (mFrames
.FirstChild()) {
418 // We want to ignore this in the case that we're size contained
419 // because our children should not contribute to our
421 iSize
= mFrames
.FirstChild()->GetMinISize(aRenderingContext
);
423 const nsStyleColumn
* colStyle
= StyleColumn();
424 if (colStyle
->mColumnWidth
.IsLength()) {
426 ColumnUtils::ClampUsedColumnWidth(colStyle
->mColumnWidth
.AsLength());
427 // As available width reduces to zero, we reduce our number of columns
428 // to one, and don't enforce the column width, so just return the min
429 // of the child's min-width with any specified column width.
430 iSize
= std::min(iSize
, colISize
);
432 NS_ASSERTION(!colStyle
->mColumnCount
.IsAuto(),
433 "column-count and column-width can't both be auto");
434 // As available width reduces to zero, we still have mColumnCount columns,
435 // so compute our minimum size based on the number of columns and their gaps
436 // and minimum per-column size.
437 nscoord colGap
= ColumnUtils::GetColumnGap(this, NS_UNCONSTRAINEDSIZE
);
438 iSize
= ColumnUtils::IntrinsicISize(colStyle
->mColumnCount
.AsInteger(),
441 // XXX count forced column breaks here? Maybe we should return the child's
442 // min-width times the minimum number of columns.
446 nscoord
nsColumnSetFrame::GetPrefISize(gfxContext
* aRenderingContext
) {
447 // Our preferred width is our desired column width, if specified, otherwise
448 // the child's preferred width, times the number of columns, plus the width
449 // of any required column gaps
450 // XXX what about forced column breaks here?
451 const nsStyleColumn
* colStyle
= StyleColumn();
454 if (colStyle
->mColumnWidth
.IsLength()) {
456 ColumnUtils::ClampUsedColumnWidth(colStyle
->mColumnWidth
.AsLength());
457 } else if (mFrames
.FirstChild()) {
458 // We want to ignore this in the case that we're size contained
459 // because our children should not contribute to our
461 colISize
= mFrames
.FirstChild()->GetPrefISize(aRenderingContext
);
466 // If column-count is auto, assume one column.
467 uint32_t numColumns
=
468 colStyle
->mColumnCount
.IsAuto() ? 1 : colStyle
->mColumnCount
.AsInteger();
469 nscoord colGap
= ColumnUtils::GetColumnGap(this, NS_UNCONSTRAINEDSIZE
);
470 return ColumnUtils::IntrinsicISize(numColumns
, colGap
, colISize
);
473 nsColumnSetFrame::ColumnBalanceData
nsColumnSetFrame::ReflowColumns(
474 ReflowOutput
& aDesiredSize
, const ReflowInput
& aReflowInput
,
475 nsReflowStatus
& aStatus
, const ReflowConfig
& aConfig
,
476 bool aUnboundedLastColumn
) {
477 ColumnBalanceData colData
;
479 WritingMode wm
= GetWritingMode();
480 const bool isRTL
= wm
.IsBidiRTL();
481 const bool shrinkingBSize
= mLastBalanceBSize
> aConfig
.mColBSize
;
482 const bool changingBSize
= mLastBalanceBSize
!= aConfig
.mColBSize
;
485 "%s: Doing column reflow pass: mLastBalanceBSize=%d,"
486 " mColBSize=%d, RTL=%d, mUsedColCount=%d,"
487 " mColISize=%d, mColGap=%d",
488 __func__
, mLastBalanceBSize
, aConfig
.mColBSize
, isRTL
,
489 aConfig
.mUsedColCount
, aConfig
.mColISize
, aConfig
.mColGap
);
491 DrainOverflowColumns();
494 mLastBalanceBSize
= aConfig
.mColBSize
;
495 // XXX Seems like this could fire if incremental reflow pushed the column
496 // set down so we reflow incrementally with a different available height.
497 // We need a way to do an incremental reflow and be sure availableHeight
498 // changes are taken account of! Right now I think block frames with
499 // absolute children might exit early.
502 aKidReason != eReflowReason_Incremental,
503 "incremental reflow should not have changed the balance height");
507 nsRect
contentRect(0, 0, 0, 0);
508 OverflowAreas overflowRects
;
510 nsIFrame
* child
= mFrames
.FirstChild();
511 LogicalPoint
childOrigin(wm
, 0, 0);
513 // In vertical-rl mode, columns will not be correctly placed if the
514 // reflowInput's ComputedWidth() is UNCONSTRAINED (in which case we'll get
515 // a containerSize.width of zero here). In that case, the column positions
516 // will be adjusted later, after our correct contentSize is known.
518 // When column-span is enabled, containerSize.width is always constrained.
519 // However, for RTL, we need to adjust the column positions as well after our
520 // correct containerSize is known.
521 nsSize containerSize
= aReflowInput
.ComputedSizeAsContainerIfConstrained();
523 const nscoord computedBSize
=
524 aReflowInput
.mParentReflowInput
->ComputedBSize();
525 nscoord contentBEnd
= 0;
526 bool reflowNext
= false;
529 const bool reflowLastColumnWithUnconstrainedAvailBSize
=
530 aUnboundedLastColumn
&& colData
.mColCount
== aConfig
.mUsedColCount
&&
531 aConfig
.mIsBalancing
;
533 // We need to reflow the child (column) ...
535 // if we are told to do so;
536 aReflowInput
.ShouldReflowAllKids() ||
537 // if the child is dirty;
538 child
->IsSubtreeDirty() ||
539 // if it's the last child because we need to obtain the block-end
541 !child
->GetNextSibling() ||
542 // if the next column is dirty, because the next column's first line(s)
543 // might be pullable back to this column;
544 child
->GetNextSibling()->IsSubtreeDirty() ||
545 // if this is the last column and we are supposed to assign unbounded
546 // block-size to it, because that could change the available block-size
547 // from the last time we reflowed it and we should try to pull all the
548 // content from its next sibling (Note that it might be the last column,
549 // but not be the last child because the desired number of columns has
551 reflowLastColumnWithUnconstrainedAvailBSize
;
553 // If column-fill is auto (not the default), then we might need to
554 // move content between columns for any change in column block-size.
556 // The same is true if we have a non-'auto' computed block-size.
558 // FIXME: It's not clear to me why it's *ever* valid to have
559 // reflowChild be false when changingBSize is true, since it
560 // seems like a child broken over multiple columns might need to
561 // change the size of the fragment in each column.
562 if (!reflowChild
&& changingBSize
&&
563 (StyleColumn()->mColumnFill
== StyleColumnFill::Auto
||
564 computedBSize
!= NS_UNCONSTRAINEDSIZE
)) {
567 // If we need to pull up content from the prev-in-flow then this is not just
568 // a block-size shrink. The prev in flow will have set the dirty bit.
569 // Check the overflow rect YMost instead of just the child's content
570 // block-size. The child may have overflowing content that cares about the
571 // available block-size boundary. (It may also have overflowing content that
572 // doesn't care about the available block-size boundary, but if so, too bad,
573 // this optimization is defeated.) We want scrollable overflow here since
574 // this is a calculation that affects layout.
575 if (!reflowChild
&& shrinkingBSize
) {
576 switch (wm
.GetBlockDir()) {
577 case WritingMode::BlockDir::TB
:
578 if (child
->ScrollableOverflowRect().YMost() > aConfig
.mColBSize
) {
582 case WritingMode::BlockDir::LR
:
583 if (child
->ScrollableOverflowRect().XMost() > aConfig
.mColBSize
) {
587 case WritingMode::BlockDir::RL
:
588 // XXX not sure how to handle this, so for now just don't attempt
593 MOZ_ASSERT_UNREACHABLE("unknown block direction");
598 nscoord childContentBEnd
= 0;
599 if (!reflowNext
&& !reflowChild
) {
600 // This child does not need to be reflowed, but we may need to move it
601 MoveChildTo(child
, childOrigin
, wm
, containerSize
);
603 // If this is the last frame then make sure we get the right status
604 nsIFrame
* kidNext
= child
->GetNextSibling();
607 if (kidNext
->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER
)) {
608 aStatus
.SetOverflowIncomplete();
610 aStatus
.SetIncomplete();
613 aStatus
= mLastFrameStatus
;
615 childContentBEnd
= nsLayoutUtils::CalculateContentBEnd(wm
, child
);
617 COLUMN_SET_LOG("%s: Skipping child #%d %p: status=%s", __func__
,
618 colData
.mColCount
, child
, ToString(aStatus
).c_str());
620 LogicalSize
availSize(wm
, aConfig
.mColISize
, aConfig
.mColBSize
);
621 if (reflowLastColumnWithUnconstrainedAvailBSize
) {
622 availSize
.BSize(wm
) = NS_UNCONSTRAINEDSIZE
;
625 "%s: Reflowing last column with unconstrained block-size. Change "
626 "available block-size from %d to %d",
627 __func__
, aConfig
.mColBSize
, availSize
.BSize(wm
));
631 child
->MarkSubtreeDirty();
634 LogicalSize
kidCBSize(wm
, availSize
.ISize(wm
), computedBSize
);
635 ReflowInput
kidReflowInput(PresContext(), aReflowInput
, child
, availSize
,
637 kidReflowInput
.mFlags
.mIsTopOfPage
= [&]() {
638 const bool isNestedMulticolOrPaginated
=
639 aReflowInput
.mParentReflowInput
->mFrame
->HasAnyStateBits(
640 NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR
) ||
641 PresContext()->IsPaginated();
642 if (isNestedMulticolOrPaginated
) {
643 if (aConfig
.mForceAuto
) {
644 // If we are forced to fill columns sequentially, force fit the
645 // content whether we are at top of page or not.
648 if (aReflowInput
.mFlags
.mIsTopOfPage
) {
649 // If this is the last balancing reflow, we want to force fit
650 // content to avoid infinite loops.
651 return !aConfig
.mIsBalancing
|| aConfig
.mIsLastBalancingReflow
;
653 // If we are a not at the top of page, we shouldn't force fit content.
654 // This is because our ColumnSetWrapperFrame can be pushed to the next
655 // column or page and reflowed again with a potentially larger
656 // available block-size.
659 // We are a top-level multicol in non-paginated context. Force fit the
660 // content only if we are not balancing columns.
661 return !aConfig
.mIsBalancing
;
663 kidReflowInput
.mFlags
.mTableIsSplittable
= false;
664 kidReflowInput
.mFlags
.mIsColumnBalancing
= aConfig
.mIsBalancing
;
665 kidReflowInput
.mFlags
.mIsInLastColumnBalancingReflow
=
666 aConfig
.mIsLastBalancingReflow
;
667 kidReflowInput
.mBreakType
= ReflowInput::BreakType::Column
;
669 // We need to reflow any float placeholders, even if our column block-size
671 kidReflowInput
.mFlags
.mMustReflowPlaceholders
= !changingBSize
;
674 "%s: Reflowing child #%d %p: availSize=(%d,%d), kidCBSize=(%d,%d), "
675 "child's mIsTopOfPage=%d",
676 __func__
, colData
.mColCount
, child
, availSize
.ISize(wm
),
677 availSize
.BSize(wm
), kidCBSize
.ISize(wm
), kidCBSize
.BSize(wm
),
678 kidReflowInput
.mFlags
.mIsTopOfPage
);
680 // Note if the column's next in flow is not being changed by this
681 // incremental reflow. This may allow the current column to avoid trying
682 // to pull lines from the next column.
683 if (child
->GetNextSibling() && !HasAnyStateBits(NS_FRAME_IS_DIRTY
) &&
684 !child
->GetNextSibling()->HasAnyStateBits(NS_FRAME_IS_DIRTY
)) {
685 kidReflowInput
.mFlags
.mNextInFlowUntouched
= true;
688 ReflowOutput
kidDesiredSize(wm
);
690 // XXX it would be cool to consult the float manager for the
691 // previous block to figure out the region of floats from the
692 // previous column that extend into this column, and subtract
693 // that region from the new float manager. So you could stick a
694 // really big float in the first column and text in following
695 // columns would flow around it.
697 MOZ_ASSERT(kidReflowInput
.ComputedLogicalMargin(wm
).IsAllZero(),
698 "-moz-column-content has no margin!");
700 ReflowChild(child
, PresContext(), kidDesiredSize
, kidReflowInput
, wm
,
701 childOrigin
, containerSize
, ReflowChildFlags::Default
,
704 if (colData
.mColCount
== 1 && aStatus
.IsInlineBreakBefore()) {
705 COLUMN_SET_LOG("%s: Content in the first column reports break-before!",
711 reflowNext
= aStatus
.NextInFlowNeedsReflow();
713 // The carried-out block-end margin of column content might be non-zero
714 // when we try to find the best column balancing block size, but it should
715 // never affect the size column set nor be further carried out. Set it to
718 // FIXME: For some types of fragmentation, we should carry the margin into
719 // the next column. Also see
720 // https://drafts.csswg.org/css-break-4/#break-margins
722 // FIXME: This should never happen for the last column, since it should be
723 // a margin root; see nsBlockFrame::IsMarginRoot(). However, sometimes the
724 // last column has an empty continuation while searching for the best
725 // column balancing bsize, which prevents the last column from being a
727 kidDesiredSize
.mCarriedOutBEndMargin
.Zero();
729 NS_FRAME_TRACE_REFLOW_OUT("Column::Reflow", aStatus
);
731 FinishReflowChild(child
, PresContext(), kidDesiredSize
, &kidReflowInput
,
732 wm
, childOrigin
, containerSize
,
733 ReflowChildFlags::Default
);
735 childContentBEnd
= nsLayoutUtils::CalculateContentBEnd(wm
, child
);
736 if (childContentBEnd
> aConfig
.mColBSize
) {
739 if (childContentBEnd
> availSize
.BSize(wm
)) {
740 colData
.mMaxOverflowingBSize
=
741 std::max(childContentBEnd
, colData
.mMaxOverflowingBSize
);
745 "%s: Reflowed child #%d %p: status=%s, desiredSize=(%d,%d), "
746 "childContentBEnd=%d, CarriedOutBEndMargin=%d (ignored)",
747 __func__
, colData
.mColCount
, child
, ToString(aStatus
).c_str(),
748 kidDesiredSize
.ISize(wm
), kidDesiredSize
.BSize(wm
), childContentBEnd
,
749 kidDesiredSize
.mCarriedOutBEndMargin
.Get());
752 contentRect
.UnionRect(contentRect
, child
->GetRect());
754 ConsiderChildOverflow(overflowRects
, child
);
755 contentBEnd
= std::max(contentBEnd
, childContentBEnd
);
756 colData
.mLastBSize
= childContentBEnd
;
757 colData
.mSumBSize
+= childContentBEnd
;
759 // Build a continuation column if necessary
760 nsIFrame
* kidNextInFlow
= child
->GetNextInFlow();
762 if (aStatus
.IsFullyComplete()) {
763 NS_ASSERTION(!kidNextInFlow
, "next in flow should have been deleted");
768 // Make sure that the column has a next-in-flow. If not, we must
769 // create one to hold the overflowing stuff, even if we're just
770 // going to put it on our overflow list and let *our*
771 // next in flow handle it.
772 if (!kidNextInFlow
) {
773 NS_ASSERTION(aStatus
.NextInFlowNeedsReflow(),
774 "We have to create a continuation, but the block doesn't "
775 "want us to reflow it?");
777 // We need to create a continuing column
778 kidNextInFlow
= CreateNextInFlow(child
);
781 // Make sure we reflow a next-in-flow when it switches between being
782 // normal or overflow container
783 if (aStatus
.IsOverflowIncomplete()) {
784 if (!kidNextInFlow
->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER
)) {
785 aStatus
.SetNextInFlowNeedsReflow();
787 kidNextInFlow
->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER
);
789 } else if (kidNextInFlow
->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER
)) {
790 aStatus
.SetNextInFlowNeedsReflow();
792 kidNextInFlow
->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER
);
795 // We have reached the maximum number of columns. If we are balancing, stop
796 // this reflow and continue finding the optimal balancing block-size.
798 // Otherwise, i.e. we are not balancing, stop this reflow and let the parent
799 // of our multicol container create a next-in-flow if all of the following
800 // conditions are met.
802 // 1) We fill columns sequentially by the request of the style, not by our
803 // internal needs, i.e. aConfig.mForceAuto is false.
805 // We don't want to stop this reflow when we force fill the columns
806 // sequentially. We usually go into this mode when giving up balancing, and
807 // this is the last resort to fit all our children by creating overflow
810 // 2) In a fragmented context, our multicol container still has block-size
811 // left for its next-in-flow, i.e.
812 // aReflowInput.mFlags.mColumnSetWrapperHasNoBSizeLeft is false.
814 // Note that in a continuous context, i.e. our multicol container's
815 // available block-size is unconstrained, if it has a fixed block-size
816 // mColumnSetWrapperHasNoBSizeLeft is always true because nothing stops it
817 // from applying all its block-size in the first-in-flow. Otherwise, i.e.
818 // our multicol container has an unconstrained block-size, we shouldn't be
819 // here because all our children should fit in the very first column even if
820 // mColumnSetWrapperHasNoBSizeLeft is false.
822 // According to the definition of mColumnSetWrapperHasNoBSizeLeft, if the
823 // bit is *not* set, either our multicol container has unconstrained
824 // block-size, or it has a constrained block-size and has block-size left
825 // for its next-in-flow. In either cases, the parent of our multicol
826 // container can create a next-in-flow for the container that guaranteed to
827 // have non-zero block-size for the container's children.
829 // Put simply, if either one of the above conditions is not met, we are
830 // going to create more overflow columns until all our children are fit.
831 if (colData
.mColCount
>= aConfig
.mUsedColCount
&&
832 (aConfig
.mIsBalancing
||
833 (!aConfig
.mForceAuto
&&
834 !aReflowInput
.mFlags
.mColumnSetWrapperHasNoBSizeLeft
))) {
835 NS_ASSERTION(aConfig
.mIsBalancing
||
836 aReflowInput
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
,
837 "Why are we here if we have unlimited block-size to fill "
838 "columns sequentially.");
840 // No more columns allowed here. Stop.
841 aStatus
.SetNextInFlowNeedsReflow();
842 kidNextInFlow
->MarkSubtreeDirty();
843 // Move any of our leftover columns to our overflow list. Our
844 // next-in-flow will eventually pick them up.
845 nsFrameList continuationColumns
= mFrames
.TakeFramesAfter(child
);
846 if (continuationColumns
.NotEmpty()) {
847 SetOverflowFrames(std::move(continuationColumns
));
851 COLUMN_SET_LOG("%s: We are not going to create overflow columns.",
856 if (PresContext()->HasPendingInterrupt()) {
857 // Stop the loop now while |child| still points to the frame that bailed
858 // out. We could keep going here and condition a bunch of the code in
859 // this loop on whether there's an interrupt, or even just keep going and
860 // trying to reflow the blocks (even though we know they'll interrupt
861 // right after their first line), but stopping now is conceptually the
862 // simplest (and probably fastest) thing.
866 // Advance to the next column
867 child
= child
->GetNextSibling();
871 childOrigin
.I(wm
) += aConfig
.mColISize
+ aConfig
.mColGap
;
873 COLUMN_SET_LOG("%s: Next childOrigin.iCoord=%d", __func__
,
878 if (PresContext()->CheckForInterrupt(this) &&
879 HasAnyStateBits(NS_FRAME_IS_DIRTY
)) {
880 // Mark all our kids starting with |child| dirty
882 // Note that this is a CheckForInterrupt call, not a HasPendingInterrupt,
883 // because we might have interrupted while reflowing |child|, and since
884 // we're about to add a dirty bit to |child| we need to make sure that
885 // |this| is scheduled to have dirty bits marked on it and its ancestors.
886 // Otherwise, when we go to mark dirty bits on |child|'s ancestors we'll
887 // bail out immediately, since it'll already have a dirty bit.
888 for (; child
; child
= child
->GetNextSibling()) {
889 child
->MarkSubtreeDirty();
893 colData
.mMaxBSize
= contentBEnd
;
894 LogicalSize contentSize
= LogicalSize(wm
, contentRect
.Size());
895 contentSize
.BSize(wm
) = std::max(contentSize
.BSize(wm
), contentBEnd
);
896 mLastFrameStatus
= aStatus
;
898 if (computedBSize
!= NS_UNCONSTRAINEDSIZE
&& !HasColumnSpanSiblings()) {
899 NS_ASSERTION(aReflowInput
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
,
900 "Available block-size should be constrained because it's "
901 "restricted by the computed block-size when our reflow "
902 "input is created in nsBlockFrame::ReflowBlockFrame()!");
904 // If a) our parent ColumnSetWrapper has constrained block-size
905 // (nsBlockFrame::ReflowBlockFrame() applies the block-size constraint
906 // when creating a ReflowInput for ColumnSetFrame child); and b) we are the
907 // sole ColumnSet or the last ColumnSet continuation split by column-spans
908 // in a ColumnSetWrapper, extend our block-size to consume the available
909 // block-size so that the column-rules are drawn to the content block-end
910 // edge of the multicol container.
911 contentSize
.BSize(wm
) =
912 std::max(contentSize
.BSize(wm
), aReflowInput
.AvailableBSize());
915 aDesiredSize
.SetSize(wm
, contentSize
);
916 aDesiredSize
.mOverflowAreas
= overflowRects
;
917 aDesiredSize
.UnionOverflowAreasWithDesiredBounds();
919 // In vertical-rl mode, make a second pass if necessary to reposition the
920 // columns with the correct container width. (In other writing modes,
921 // correct containerSize was not required for column positioning so we don't
924 // RTL column positions also depend on ColumnSet's actual contentSize. We need
926 if ((wm
.IsVerticalRL() || isRTL
) &&
927 containerSize
.width
!= contentSize
.Width(wm
)) {
928 const nsSize finalContainerSize
= aDesiredSize
.PhysicalSize();
929 OverflowAreas overflowRects
;
930 for (nsIFrame
* child
: mFrames
) {
931 // Get the logical position as set previously using a provisional or
932 // dummy containerSize, and reset with the correct container size.
933 child
->SetPosition(wm
, child
->GetLogicalPosition(wm
, containerSize
),
935 ConsiderChildOverflow(overflowRects
, child
);
937 aDesiredSize
.mOverflowAreas
= overflowRects
;
938 aDesiredSize
.UnionOverflowAreasWithDesiredBounds();
941 colData
.mFeasible
= allFit
&& aStatus
.IsFullyComplete();
944 "%s: Done column reflow pass: %s, mMaxBSize=%d, mSumBSize=%d, "
945 "mLastBSize=%d, mMaxOverflowingBSize=%d",
946 __func__
, colData
.mFeasible
? "Feasible :)" : "Infeasible :(",
947 colData
.mMaxBSize
, colData
.mSumBSize
, colData
.mLastBSize
,
948 colData
.mMaxOverflowingBSize
);
953 void nsColumnSetFrame::DrainOverflowColumns() {
954 // First grab the prev-in-flows overflows and reparent them to this
956 nsPresContext
* presContext
= PresContext();
957 nsColumnSetFrame
* prev
= static_cast<nsColumnSetFrame
*>(GetPrevInFlow());
959 AutoFrameListPtr
overflows(presContext
, prev
->StealOverflowFrames());
961 nsContainerFrame::ReparentFrameViewList(*overflows
, prev
, this);
963 mFrames
.InsertFrames(this, nullptr, std::move(*overflows
));
967 // Now pull back our own overflows and append them to our children.
968 // We don't need to reparent them since we're already their parent.
969 AutoFrameListPtr
overflows(presContext
, StealOverflowFrames());
971 // We're already the parent for these frames, so no need to set
972 // their parent again.
973 mFrames
.AppendFrames(nullptr, std::move(*overflows
));
977 void nsColumnSetFrame::FindBestBalanceBSize(const ReflowInput
& aReflowInput
,
978 nsPresContext
* aPresContext
,
979 ReflowConfig
& aConfig
,
980 ColumnBalanceData aColData
,
981 ReflowOutput
& aDesiredSize
,
982 bool aUnboundedLastColumn
,
983 nsReflowStatus
& aStatus
) {
984 MOZ_ASSERT(aConfig
.mIsBalancing
,
985 "Why are we here if we are not balancing columns?");
987 const nscoord availableContentBSize
= aReflowInput
.AvailableBSize();
989 // Termination of the algorithm below is guaranteed because
990 // aConfig.knownFeasibleBSize - aConfig.knownInfeasibleBSize decreases in
992 int32_t iterationCount
= 1;
994 // We set this flag when we detect that we may contain a frame
995 // that can break anywhere (thus foiling the linear decrease-by-one
997 bool maybeContinuousBreakingDetected
= false;
998 bool possibleOptimalBSizeDetected
= false;
1000 // This is the extra block-size added to the optimal column block-size
1001 // estimation which is calculated in the while-loop by dividing
1002 // aColData.mSumBSize into N columns.
1004 // The constant is arbitrary. We use a half of line-height first. In case a
1005 // column container uses *zero* (or a very small) line-height, use a half of
1006 // default line-height 1140/2 = 570 app units as the minimum value. Otherwise
1007 // we might take more than necessary iterations before finding a feasible
1009 nscoord extraBlockSize
= std::max(570, aReflowInput
.GetLineHeight() / 2);
1011 // We use divide-by-N to estimate the optimal column block-size only if the
1012 // last column's available block-size is unbounded.
1013 bool foundFeasibleBSizeCloserToBest
= !aUnboundedLastColumn
;
1015 // Stop the binary search when the difference of the feasible and infeasible
1016 // block-size is within this gap. Here we use one device pixel.
1017 const int32_t gapToStop
= aPresContext
->DevPixelsToAppUnits(1);
1019 while (!aPresContext
->HasPendingInterrupt()) {
1020 nscoord lastKnownFeasibleBSize
= aConfig
.mKnownFeasibleBSize
;
1022 // Record what we learned from the last reflow
1023 if (aColData
.mFeasible
) {
1024 // mMaxBSize is feasible. Also, mLastBalanceBSize is feasible.
1025 aConfig
.mKnownFeasibleBSize
=
1026 std::min(aConfig
.mKnownFeasibleBSize
, aColData
.mMaxBSize
);
1027 aConfig
.mKnownFeasibleBSize
=
1028 std::min(aConfig
.mKnownFeasibleBSize
, mLastBalanceBSize
);
1030 // Furthermore, no block-size less than the block-size of the last
1031 // column can ever be feasible. (We might be able to reduce the
1032 // block-size of a non-last column by moving content to a later column,
1033 // but we can't do that with the last column.)
1034 if (aColData
.mColCount
== aConfig
.mUsedColCount
) {
1035 aConfig
.mKnownInfeasibleBSize
=
1036 std::max(aConfig
.mKnownInfeasibleBSize
, aColData
.mLastBSize
- 1);
1039 aConfig
.mKnownInfeasibleBSize
=
1040 std::max(aConfig
.mKnownInfeasibleBSize
, mLastBalanceBSize
);
1042 // If a column didn't fit in its available block-size, then its current
1043 // block-size must be the minimum block-size for unbreakable content in
1044 // the column, and therefore no smaller block-size can be feasible.
1045 aConfig
.mKnownInfeasibleBSize
= std::max(
1046 aConfig
.mKnownInfeasibleBSize
, aColData
.mMaxOverflowingBSize
- 1);
1048 if (aUnboundedLastColumn
) {
1049 // The last column is unbounded, so all content got reflowed, so the
1050 // mMaxBSize is feasible.
1051 aConfig
.mKnownFeasibleBSize
=
1052 std::min(aConfig
.mKnownFeasibleBSize
, aColData
.mMaxBSize
);
1054 NS_ASSERTION(mLastFrameStatus
.IsComplete(),
1055 "Last column should be complete if the available "
1056 "block-size is unconstrained!");
1061 "%s: this=%p, mKnownInfeasibleBSize=%d, mKnownFeasibleBSize=%d",
1062 __func__
, this, aConfig
.mKnownInfeasibleBSize
,
1063 aConfig
.mKnownFeasibleBSize
);
1065 if (aConfig
.mKnownInfeasibleBSize
>= aConfig
.mKnownFeasibleBSize
- 1) {
1066 // aConfig.mKnownFeasibleBSize is where we want to be. This can happen in
1067 // the very first iteration when a column container solely has a tall
1068 // unbreakable child that overflows the container.
1072 if (aConfig
.mKnownInfeasibleBSize
>= availableContentBSize
) {
1073 // There's no feasible block-size to fit our contents. We may need to
1074 // reflow one more time after this loop.
1079 aConfig
.mKnownFeasibleBSize
- aConfig
.mKnownInfeasibleBSize
;
1080 if (gap
<= gapToStop
&& possibleOptimalBSizeDetected
) {
1081 // We detected a possible optimal block-size in the last iteration. If it
1082 // is infeasible, we may need to reflow one more time after this loop.
1086 if (lastKnownFeasibleBSize
- aConfig
.mKnownFeasibleBSize
== 1) {
1087 // We decreased the feasible block-size by one twip only. This could
1088 // indicate that there is a continuously breakable child frame
1089 // that we are crawling through.
1090 maybeContinuousBreakingDetected
= true;
1093 nscoord nextGuess
= aConfig
.mKnownInfeasibleBSize
+ gap
/ 2;
1094 if (aConfig
.mKnownFeasibleBSize
- nextGuess
< extraBlockSize
&&
1095 !maybeContinuousBreakingDetected
) {
1096 // We're close to our target, so just try shrinking just the
1097 // minimum amount that will cause one of our columns to break
1099 nextGuess
= aConfig
.mKnownFeasibleBSize
- 1;
1100 } else if (!foundFeasibleBSizeCloserToBest
) {
1101 // Make a guess by dividing mSumBSize into N columns and adding
1102 // extraBlockSize to try to make it on the feasible side.
1103 nextGuess
= aColData
.mSumBSize
/ aConfig
.mUsedColCount
+ extraBlockSize
;
1105 nextGuess
= clamped(nextGuess
, aConfig
.mKnownInfeasibleBSize
+ 1,
1106 aConfig
.mKnownFeasibleBSize
- 1);
1107 // We keep doubling extraBlockSize in every iteration until we find a
1109 extraBlockSize
*= 2;
1110 } else if (aConfig
.mKnownFeasibleBSize
== NS_UNCONSTRAINEDSIZE
) {
1111 // This can happen when we had a next-in-flow so we didn't
1112 // want to do an unbounded block-size measuring step. Let's just increase
1113 // from the infeasible block-size by some reasonable amount.
1114 nextGuess
= aConfig
.mKnownInfeasibleBSize
* 2 + extraBlockSize
;
1115 } else if (gap
<= gapToStop
) {
1116 // Floor nextGuess to the greatest multiple of gapToStop below or equal to
1117 // mKnownFeasibleBSize.
1118 nextGuess
= aConfig
.mKnownFeasibleBSize
/ gapToStop
* gapToStop
;
1119 possibleOptimalBSizeDetected
= true;
1122 // Don't bother guessing more than our block-size constraint.
1123 nextGuess
= std::min(availableContentBSize
, nextGuess
);
1125 COLUMN_SET_LOG("%s: Choosing next guess=%d, iteration=%d", __func__
,
1126 nextGuess
, iterationCount
);
1129 aConfig
.mColBSize
= nextGuess
;
1131 aUnboundedLastColumn
= false;
1132 MarkPrincipalChildrenDirty(this);
1134 ReflowColumns(aDesiredSize
, aReflowInput
, aStatus
, aConfig
, false);
1136 if (!foundFeasibleBSizeCloserToBest
&& aColData
.mFeasible
) {
1137 foundFeasibleBSizeCloserToBest
= true;
1141 if (!aColData
.mFeasible
&& !aPresContext
->HasPendingInterrupt()) {
1142 // We need to reflow one more time at the feasible block-size to
1143 // get a valid layout.
1144 if (aConfig
.mKnownInfeasibleBSize
>= availableContentBSize
) {
1145 aConfig
.mColBSize
= availableContentBSize
;
1146 if (mLastBalanceBSize
== availableContentBSize
) {
1147 // If we end up here, we have a constrained available content
1148 // block-size, and our last column's block-size exceeds it. Also, if
1149 // this is the first balancing iteration, the last column is given
1150 // unconstrained available block-size, so it has a fully complete
1151 // reflow status. Therefore, we always want to reflow again at the
1152 // available content block-size to get a valid layout and a correct
1153 // reflow status (likely an *incomplete* status) so that our column
1154 // container can be fragmented if needed.
1156 if (aReflowInput
.mFlags
.mColumnSetWrapperHasNoBSizeLeft
) {
1157 // If our column container has a constrained block-size (either in a
1158 // paginated context or in a nested column container), and is going
1159 // to consume all its computed block-size in this fragment, then our
1160 // column container has no block-size left to contain our
1161 // next-in-flows. We have to give up balancing, and create our
1162 // own overflow columns.
1164 // We don't want to create overflow columns immediately when our
1165 // content doesn't fit since this changes our reflow status from
1166 // incomplete to complete. Valid reasons include 1) the outer column
1167 // container might do column balancing, and it can enlarge the
1168 // available content block-size so that the nested one could fit its
1169 // content in next balancing iteration; or 2) the outer column
1170 // container is filling columns sequentially, and may have more
1171 // inline-size to create more column boxes for the nested column
1172 // container's next-in-flows.
1173 aConfig
= ChooseColumnStrategy(aReflowInput
, true);
1177 aConfig
.mColBSize
= aConfig
.mKnownFeasibleBSize
;
1180 // This is our last attempt to reflow. If our column container's available
1181 // block-size is unconstrained, make sure that the last column is
1182 // allowed to have arbitrary block-size here, even though we were
1183 // balancing. Otherwise we'd have to split, and it's not clear what we'd
1185 COLUMN_SET_LOG("%s: Last attempt to call ReflowColumns", __func__
);
1186 aConfig
.mIsLastBalancingReflow
= true;
1187 const bool forceUnboundedLastColumn
=
1188 aReflowInput
.mParentReflowInput
->AvailableBSize() ==
1189 NS_UNCONSTRAINEDSIZE
;
1190 MarkPrincipalChildrenDirty(this);
1191 ReflowColumns(aDesiredSize
, aReflowInput
, aStatus
, aConfig
,
1192 forceUnboundedLastColumn
);
1196 void nsColumnSetFrame::Reflow(nsPresContext
* aPresContext
,
1197 ReflowOutput
& aDesiredSize
,
1198 const ReflowInput
& aReflowInput
,
1199 nsReflowStatus
& aStatus
) {
1201 // Don't support interruption in columns
1202 nsPresContext::InterruptPreventer
noInterrupts(aPresContext
);
1204 DO_GLOBAL_REFLOW_COUNT("nsColumnSetFrame");
1205 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
1207 MOZ_ASSERT(aReflowInput
.mCBReflowInput
->mFrame
->StyleColumn()
1208 ->IsColumnContainerStyle(),
1209 "The column container should have relevant column styles!");
1210 MOZ_ASSERT(aReflowInput
.mParentReflowInput
->mFrame
->IsColumnSetWrapperFrame(),
1211 "The column container should be ColumnSetWrapperFrame!");
1213 aReflowInput
.ComputedLogicalBorderPadding(aReflowInput
.GetWritingMode())
1215 "Only the column container can have border and padding!");
1217 GetChildList(FrameChildListID::OverflowContainers
).IsEmpty() &&
1218 GetChildList(FrameChildListID::ExcessOverflowContainers
).IsEmpty(),
1219 "ColumnSetFrame should store overflow containers in principal "
1222 //------------ Handle Incremental Reflow -----------------
1224 COLUMN_SET_LOG("%s: Begin Reflow: this=%p, is nested multicol=%d", __func__
,
1226 aReflowInput
.mParentReflowInput
->mFrame
->HasAnyStateBits(
1227 NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR
));
1229 // If inline size is unconstrained, set aForceAuto to true to allow
1230 // the columns to expand in the inline direction. (This typically
1231 // happens in orthogonal flows where the inline direction is the
1232 // container's block direction).
1233 ReflowConfig config
= ChooseColumnStrategy(
1234 aReflowInput
, aReflowInput
.ComputedISize() == NS_UNCONSTRAINEDSIZE
);
1236 // If balancing, then we allow the last column to grow to unbounded
1237 // block-size during the first reflow. This gives us a way to estimate
1238 // what the average column block-size should be, because we can measure
1239 // the block-size of all the columns and sum them up. But don't do this
1240 // if we have a next in flow because we don't want to suck all its
1241 // content back here and then have to push it out again!
1242 nsIFrame
* nextInFlow
= GetNextInFlow();
1243 bool unboundedLastColumn
= config
.mIsBalancing
&& !nextInFlow
;
1244 const ColumnBalanceData colData
= ReflowColumns(
1245 aDesiredSize
, aReflowInput
, aStatus
, config
, unboundedLastColumn
);
1247 // If we're not balancing, then we're already done, since we should have
1248 // reflown all of our children, and there is no need for a binary search to
1249 // determine proper column block-size.
1250 if (config
.mIsBalancing
&& !aPresContext
->HasPendingInterrupt()) {
1251 FindBestBalanceBSize(aReflowInput
, aPresContext
, config
, colData
,
1252 aDesiredSize
, unboundedLastColumn
, aStatus
);
1255 if (aPresContext
->HasPendingInterrupt() &&
1256 aReflowInput
.AvailableBSize() == NS_UNCONSTRAINEDSIZE
) {
1257 // In this situation, we might be lying about our reflow status, because
1258 // our last kid (the one that got interrupted) was incomplete. Fix that.
1262 NS_ASSERTION(aStatus
.IsFullyComplete() ||
1263 aReflowInput
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
,
1264 "Column set should be complete if the available block-size is "
1267 MOZ_ASSERT(!HasAbsolutelyPositionedChildren(),
1268 "ColumnSetWrapperFrame should be the abs.pos container!");
1269 FinishAndStoreOverflow(&aDesiredSize
, aReflowInput
.mStyleDisplay
);
1271 COLUMN_SET_LOG("%s: End Reflow: this=%p", __func__
, this);
1274 void nsColumnSetFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
1275 const nsDisplayListSet
& aLists
) {
1276 DisplayBorderBackgroundOutline(aBuilder
, aLists
);
1278 if (IsVisibleForPainting()) {
1279 aLists
.BorderBackground()->AppendNewToTop
<nsDisplayColumnRule
>(aBuilder
,
1283 // Our children won't have backgrounds so it doesn't matter where we put them.
1284 for (nsIFrame
* f
: mFrames
) {
1285 BuildDisplayListForChild(aBuilder
, f
, aLists
);
1289 void nsColumnSetFrame::AppendDirectlyOwnedAnonBoxes(
1290 nsTArray
<OwnedAnonBox
>& aResult
) {
1291 // Everything in mFrames is continuations of the first thing in mFrames.
1292 nsIFrame
* column
= mFrames
.FirstChild();
1294 // We might not have any columns, apparently?
1299 MOZ_ASSERT(column
->Style()->GetPseudoType() == PseudoStyleType::columnContent
,
1300 "What sort of child is this?");
1301 aResult
.AppendElement(OwnedAnonBox(column
));
1304 Maybe
<nscoord
> nsColumnSetFrame::GetNaturalBaselineBOffset(
1305 WritingMode aWM
, BaselineSharingGroup aBaselineGroup
,
1306 BaselineExportContext aExportContext
) const {
1307 Maybe
<nscoord
> result
;
1308 for (const auto* kid
: mFrames
) {
1310 kid
->GetNaturalBaselineBOffset(aWM
, aBaselineGroup
, aExportContext
);
1314 // The kid frame may not necessarily be aligned with the columnset frame.
1315 LogicalRect kidRect
{aWM
, kid
->GetLogicalNormalPosition(aWM
, GetSize()),
1316 kid
->GetLogicalSize(aWM
)};
1317 if (aBaselineGroup
== BaselineSharingGroup::First
) {
1318 *kidBaseline
+= kidRect
.BStart(aWM
);
1320 *kidBaseline
+= (GetLogicalSize().BSize(aWM
) - kidRect
.BEnd(aWM
));
1322 // Take the smallest of the baselines (i.e. Closest to border-block-start
1323 // for `BaselineSharingGroup::First`, border-block-end for
1324 // `BaselineSharingGroup::Last`)
1325 if (!result
|| *kidBaseline
< *result
) {
1326 result
= kidBaseline
;
1333 void nsColumnSetFrame::SetInitialChildList(ChildListID aListID
,
1334 nsFrameList
&& aChildList
) {
1335 MOZ_ASSERT(aListID
!= FrameChildListID::Principal
|| aChildList
.OnlyChild(),
1336 "initial principal child list must have exactly one child");
1337 nsContainerFrame::SetInitialChildList(aListID
, std::move(aChildList
));
1340 void nsColumnSetFrame::AppendFrames(ChildListID aListID
,
1341 nsFrameList
&& aFrameList
) {
1342 MOZ_CRASH("unsupported operation");
1345 void nsColumnSetFrame::InsertFrames(ChildListID aListID
, nsIFrame
* aPrevFrame
,
1346 const nsLineList::iterator
* aPrevFrameLine
,
1347 nsFrameList
&& aFrameList
) {
1348 MOZ_CRASH("unsupported operation");
1351 void nsColumnSetFrame::RemoveFrame(DestroyContext
&, ChildListID
, nsIFrame
*) {
1352 MOZ_CRASH("unsupported operation");