Bug 1909613 - Enable <details name=''> everywhere, r=emilio
[gecko.git] / layout / generic / nsColumnSetFrame.cpp
blobfa30a2b17e2c7370bf66a644bf7c999f25a13bb7
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 {
30 public:
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 {
38 *aSnap = false;
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);
56 private:
57 nsTArray<nsCSSBorderRenderer> mBorderRenderers;
60 void nsDisplayColumnRule::Paint(nsDisplayListBuilder* aBuilder,
61 gfxContext* aCtx) {
62 static_cast<nsColumnSetFrame*>(mFrame)->CreateBorderRenderers(
63 mBorderRenderers, aCtx, GetPaintRect(aBuilder, aCtx), ToReferenceFrame());
65 for (auto iter = mBorderRenderers.begin(); iter != mBorderRenderers.end();
66 iter++) {
67 iter->DrawBorders();
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()) {
79 return false;
81 gfxContext screenRefCtx(dt);
83 bool dummy;
84 static_cast<nsColumnSetFrame*>(mFrame)->CreateBorderRenderers(
85 mBorderRenderers, &screenRefCtx, GetBounds(aDisplayListBuilder, &dummy),
86 ToReferenceFrame());
88 if (mBorderRenderers.IsEmpty()) {
89 return true;
92 for (auto& renderer : mBorderRenderers) {
93 renderer.CreateWebRenderCommands(this, aBuilder, aResources, aSc);
96 return true;
99 // The maximum number of columns we support.
100 static constexpr int32_t kMaxColumnCount = 1000;
103 * Tracking issues:
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);
116 return it;
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-
151 // mode direction.
152 nsIFrame* prevFrame = isRTL ? nextSibling : child;
153 nsIFrame* nextFrame = isRTL ? child : nextSibling;
155 // Each child frame's position coordinates is actually relative to this
156 // nsColumnSetFrame.
157 // linePt will be at the top-left edge to paint the line.
158 nsPoint linePt;
159 if (isVertical) {
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);
164 } else {
165 nscoord edgeOfPrev = prevFrame->GetRect().XMost() + aPt.x;
166 nscoord edgeOfNext = nextFrame->GetRect().X() + aPt.x;
167 linePt = nsPoint((edgeOfPrev + edgeOfNext - ruleSize.width) / 2,
168 contentRect.y);
171 aSetLineRect(nsRect(linePt, ruleSize));
173 child = nextSibling;
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;
191 } else {
192 ruleStyle = colStyle->mColumnRuleStyle;
195 nscoord ruleWidth = colStyle->GetColumnRuleWidth();
196 if (!ruleWidth) {
197 return;
200 aBorderRenderers.Clear();
201 nscolor ruleColor =
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;
211 Sides skipSides;
212 if (isVertical) {
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;
218 } else {
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;
233 ForEachColumnRule(
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
237 // returns.
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);
246 if (br.isSome()) {
247 MOZ_ASSERT(!borderIsEmpty);
248 aBorderRenderers.AppendElement(br.value());
251 aPt);
254 static uint32_t ColumnBalancingDepth(const ReflowInput& aReflowInput,
255 uint32_t aMaxDepth) {
256 uint32_t depth = 0;
257 for (const ReflowInput* ri = aReflowInput.mParentReflowInput;
258 ri && depth < aMaxDepth; ri = ri->mParentReflowInput) {
259 if (ri->mFlags.mIsColumnBalancing) {
260 ++depth;
263 return depth;
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();
275 nscoord colGap =
276 ColumnUtils::GetColumnGap(this, aReflowInput.ComputedISize());
277 int32_t numColumns =
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()) &&
286 !aForceAuto;
287 if (isBalancing) {
288 const uint32_t kMaxNestedColumnBalancingDepth = 2;
289 const uint32_t balancingDepth =
290 ColumnBalancingDepth(aReflowInput, kMaxNestedColumnBalancingDepth);
291 if (balancingDepth == kMaxNestedColumnBalancingDepth) {
292 isBalancing = false;
293 numColumns = 1;
297 nscoord colISize;
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()) {
301 colISize =
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 &&
309 numColumns > 0) {
310 // This expression uses truncated rounding, which is what we
311 // want
312 int32_t maxColumns =
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;
320 } else {
321 colISize = NS_UNCONSTRAINEDSIZE;
323 // Take care of the situation where there's only one column but it's
324 // still too wide
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
334 // count is auto
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) {
345 numColumns = 1;
349 // Compute extra space and divide it among the columns
350 nscoord extraSpace =
351 std::max(0, availContentISize -
352 (colISize * numColumns + colGap * (numColumns - 1)));
353 nscoord extraToColumns = extraSpace / numColumns;
354 colISize += extraToColumns;
355 expectedISizeLeftOver = extraSpace - (extraToColumns * numColumns);
358 if (isBalancing) {
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.
362 numColumns = 1;
364 colBSize = std::min(mLastBalanceBSize, colBSize);
365 } else {
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
373 // ends up here.
374 colBSize = std::max(colBSize, nsPresContext::CSSPixelsToAppUnits(1));
377 ReflowConfig config;
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;
388 COLUMN_SET_LOG(
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);
395 return config;
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) {
407 return;
410 aChild->SetPosition(aWM, aOrigin, aContainerSize);
411 nsContainerFrame::PlaceFrameView(aChild);
414 nscoord nsColumnSetFrame::GetMinISize(gfxContext* aRenderingContext) {
415 nscoord iSize = 0;
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
420 // intrinsic size.
421 iSize = mFrames.FirstChild()->GetMinISize(aRenderingContext);
423 const nsStyleColumn* colStyle = StyleColumn();
424 if (colStyle->mColumnWidth.IsLength()) {
425 nscoord colISize =
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);
431 } else {
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(),
439 colGap, iSize);
441 // XXX count forced column breaks here? Maybe we should return the child's
442 // min-width times the minimum number of columns.
443 return iSize;
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();
453 nscoord colISize;
454 if (colStyle->mColumnWidth.IsLength()) {
455 colISize =
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
460 // intrinsic size.
461 colISize = mFrames.FirstChild()->GetPrefISize(aRenderingContext);
462 } else {
463 colISize = 0;
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;
478 bool allFit = true;
479 WritingMode wm = GetWritingMode();
480 const bool isRTL = wm.IsBidiRTL();
481 const bool shrinkingBSize = mLastBalanceBSize > aConfig.mColBSize;
482 const bool changingBSize = mLastBalanceBSize != aConfig.mColBSize;
484 COLUMN_SET_LOG(
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();
493 if (changingBSize) {
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.
501 NS_ASSERTION(
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;
528 while (child) {
529 const bool reflowLastColumnWithUnconstrainedAvailBSize =
530 aUnboundedLastColumn && colData.mColCount == aConfig.mUsedColCount &&
531 aConfig.mIsBalancing;
533 // We need to reflow the child (column) ...
534 bool reflowChild =
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
540 // margin;
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
550 // changed.)
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)) {
565 reflowChild = true;
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) {
579 reflowChild = true;
581 break;
582 case WritingMode::BlockDir::LR:
583 if (child->ScrollableOverflowRect().XMost() > aConfig.mColBSize) {
584 reflowChild = true;
586 break;
587 case WritingMode::BlockDir::RL:
588 // XXX not sure how to handle this, so for now just don't attempt
589 // the optimization
590 reflowChild = true;
591 break;
592 default:
593 MOZ_ASSERT_UNREACHABLE("unknown block direction");
594 break;
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();
605 if (kidNext) {
606 aStatus.Reset();
607 if (kidNext->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
608 aStatus.SetOverflowIncomplete();
609 } else {
610 aStatus.SetIncomplete();
612 } else {
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());
619 } else {
620 LogicalSize availSize(wm, aConfig.mColISize, aConfig.mColBSize);
621 if (reflowLastColumnWithUnconstrainedAvailBSize) {
622 availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
624 COLUMN_SET_LOG(
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));
630 if (reflowNext) {
631 child->MarkSubtreeDirty();
634 LogicalSize kidCBSize(wm, availSize.ISize(wm), computedBSize);
635 ReflowInput kidReflowInput(PresContext(), aReflowInput, child, availSize,
636 Some(kidCBSize));
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.
646 return true;
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.
657 return false;
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;
662 }();
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
670 // hasn't changed.
671 kidReflowInput.mFlags.mMustReflowPlaceholders = !changingBSize;
673 COLUMN_SET_LOG(
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!");
699 aStatus.Reset();
700 ReflowChild(child, PresContext(), kidDesiredSize, kidReflowInput, wm,
701 childOrigin, containerSize, ReflowChildFlags::Default,
702 aStatus);
704 if (colData.mColCount == 1 && aStatus.IsInlineBreakBefore()) {
705 COLUMN_SET_LOG("%s: Content in the first column reports break-before!",
706 __func__);
707 allFit = false;
708 break;
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
716 // zero.
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
726 // margin root.
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) {
737 allFit = false;
739 if (childContentBEnd > availSize.BSize(wm)) {
740 colData.mMaxOverflowingBSize =
741 std::max(childContentBEnd, colData.mMaxOverflowingBSize);
744 COLUMN_SET_LOG(
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");
764 child = nullptr;
765 break;
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();
786 reflowNext = true;
787 kidNextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
789 } else if (kidNextInFlow->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
790 aStatus.SetNextInFlowNeedsReflow();
791 reflowNext = true;
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
808 // columns.
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));
849 child = nullptr;
851 COLUMN_SET_LOG("%s: We are not going to create overflow columns.",
852 __func__);
853 break;
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.
863 break;
866 // Advance to the next column
867 child = child->GetNextSibling();
868 ++colData.mColCount;
870 if (child) {
871 childOrigin.I(wm) += aConfig.mColISize + aConfig.mColGap;
873 COLUMN_SET_LOG("%s: Next childOrigin.iCoord=%d", __func__,
874 childOrigin.I(wm));
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
922 // need this fixup.)
924 // RTL column positions also depend on ColumnSet's actual contentSize. We need
925 // this fixup, too.
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),
934 finalContainerSize);
935 ConsiderChildOverflow(overflowRects, child);
937 aDesiredSize.mOverflowAreas = overflowRects;
938 aDesiredSize.UnionOverflowAreasWithDesiredBounds();
941 colData.mFeasible = allFit && aStatus.IsFullyComplete();
943 COLUMN_SET_LOG(
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);
950 return colData;
953 void nsColumnSetFrame::DrainOverflowColumns() {
954 // First grab the prev-in-flows overflows and reparent them to this
955 // frame.
956 nsPresContext* presContext = PresContext();
957 nsColumnSetFrame* prev = static_cast<nsColumnSetFrame*>(GetPrevInFlow());
958 if (prev) {
959 AutoFrameListPtr overflows(presContext, prev->StealOverflowFrames());
960 if (overflows) {
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());
970 if (overflows) {
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
991 // every iteration.
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
996 // search)
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
1008 // block-size.
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);
1038 } else {
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!");
1060 COLUMN_SET_LOG(
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.
1069 break;
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.
1075 break;
1078 const nscoord gap =
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.
1083 break;
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
1098 // differently.
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;
1104 // Sanitize it
1105 nextGuess = clamped(nextGuess, aConfig.mKnownInfeasibleBSize + 1,
1106 aConfig.mKnownFeasibleBSize - 1);
1107 // We keep doubling extraBlockSize in every iteration until we find a
1108 // feasible guess.
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);
1127 ++iterationCount;
1129 aConfig.mColBSize = nextGuess;
1131 aUnboundedLastColumn = false;
1132 MarkPrincipalChildrenDirty(this);
1133 aColData =
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);
1176 } else {
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
1184 // do with that.
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) {
1200 MarkInReflow();
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!");
1212 MOZ_ASSERT(
1213 aReflowInput.ComputedLogicalBorderPadding(aReflowInput.GetWritingMode())
1214 .IsAllZero(),
1215 "Only the column container can have border and padding!");
1216 MOZ_ASSERT(
1217 GetChildList(FrameChildListID::OverflowContainers).IsEmpty() &&
1218 GetChildList(FrameChildListID::ExcessOverflowContainers).IsEmpty(),
1219 "ColumnSetFrame should store overflow containers in principal "
1220 "child list!");
1222 //------------ Handle Incremental Reflow -----------------
1224 COLUMN_SET_LOG("%s: Begin Reflow: this=%p, is nested multicol=%d", __func__,
1225 this,
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.
1259 aStatus.Reset();
1262 NS_ASSERTION(aStatus.IsFullyComplete() ||
1263 aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
1264 "Column set should be complete if the available block-size is "
1265 "unconstrained");
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,
1280 this);
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?
1295 if (!column) {
1296 return;
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) {
1309 auto kidBaseline =
1310 kid->GetNaturalBaselineBOffset(aWM, aBaselineGroup, aExportContext);
1311 if (!kidBaseline) {
1312 continue;
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);
1319 } else {
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;
1329 return result;
1332 #ifdef DEBUG
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");
1354 #endif