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/. */
6
7 /* rendering object for css3 multi-column layout */
8
9 #include "nsColumnSetFrame.h"
10
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
18 using namespace mozilla;
19 using namespace mozilla::layout;
20
21 // To see this log, use $ MOZ_LOG=ColumnSet:4 ./mach run
22 static LazyLogModule sColumnSetLog("ColumnSet");
23 #define COLUMN_SET_LOG(msg, ...) \
24 MOZ_LOG(sColumnSetLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
25
26 class nsDisplayColumnRule : public nsPaintedDisplayItem {
27 public:
nsDisplayColumnRule(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame)28 nsDisplayColumnRule(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
29 : nsPaintedDisplayItem(aBuilder, aFrame) {
30 MOZ_COUNT_CTOR(nsDisplayColumnRule);
31 }
MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayColumnRule)32 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayColumnRule)
33
34 /**
35 * Returns the frame's visual overflow rect instead of the frame's bounds.
36 */
37 nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override {
38 *aSnap = false;
39 return static_cast<nsColumnSetFrame*>(mFrame)->CalculateColumnRuleBounds(
40 ToReferenceFrame());
41 }
42
43 bool CreateWebRenderCommands(
44 mozilla::wr::DisplayListBuilder& aBuilder,
45 mozilla::wr::IpcResourceUpdateQueue& aResources,
46 const StackingContextHelper& aSc,
47 mozilla::layers::RenderRootStateManager* aManager,
48 nsDisplayListBuilder* aDisplayListBuilder) override;
49 void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
50
51 NS_DISPLAY_DECL_NAME("ColumnRule", TYPE_COLUMN_RULE);
52
53 private:
54 nsTArray<nsCSSBorderRenderer> mBorderRenderers;
55 };
56
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)57 void nsDisplayColumnRule::Paint(nsDisplayListBuilder* aBuilder,
58 gfxContext* aCtx) {
59 static_cast<nsColumnSetFrame*>(mFrame)->CreateBorderRenderers(
60 mBorderRenderers, aCtx, GetPaintRect(), ToReferenceFrame());
61
62 for (auto iter = mBorderRenderers.begin(); iter != mBorderRenderers.end();
63 iter++) {
64 iter->DrawBorders();
65 }
66 }
67
CreateWebRenderCommands(mozilla::wr::DisplayListBuilder & aBuilder,mozilla::wr::IpcResourceUpdateQueue & aResources,const StackingContextHelper & aSc,mozilla::layers::RenderRootStateManager * aManager,nsDisplayListBuilder * aDisplayListBuilder)68 bool nsDisplayColumnRule::CreateWebRenderCommands(
69 mozilla::wr::DisplayListBuilder& aBuilder,
70 mozilla::wr::IpcResourceUpdateQueue& aResources,
71 const StackingContextHelper& aSc,
72 mozilla::layers::RenderRootStateManager* aManager,
73 nsDisplayListBuilder* aDisplayListBuilder) {
74 RefPtr<gfxContext> screenRefCtx = gfxContext::CreateOrNull(
75 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get());
76
77 static_cast<nsColumnSetFrame*>(mFrame)->CreateBorderRenderers(
78 mBorderRenderers, screenRefCtx, GetPaintRect(), ToReferenceFrame());
79
80 if (mBorderRenderers.IsEmpty()) {
81 return true;
82 }
83
84 for (auto& renderer : mBorderRenderers) {
85 renderer.CreateWebRenderCommands(this, aBuilder, aResources, aSc);
86 }
87
88 return true;
89 }
90
91 /**
92 * Tracking issues:
93 *
94 * XXX cursor movement around the top and bottom of colums seems to make the
95 * editor lose the caret.
96 *
97 * XXX should we support CSS columns applied to table elements?
98 */
NS_NewColumnSetFrame(PresShell * aPresShell,ComputedStyle * aStyle,nsFrameState aStateFlags)99 nsContainerFrame* NS_NewColumnSetFrame(PresShell* aPresShell,
100 ComputedStyle* aStyle,
101 nsFrameState aStateFlags) {
102 nsColumnSetFrame* it =
103 new (aPresShell) nsColumnSetFrame(aStyle, aPresShell->GetPresContext());
104 it->AddStateBits(aStateFlags);
105 return it;
106 }
107
NS_IMPL_FRAMEARENA_HELPERS(nsColumnSetFrame)108 NS_IMPL_FRAMEARENA_HELPERS(nsColumnSetFrame)
109
110 nsColumnSetFrame::nsColumnSetFrame(ComputedStyle* aStyle,
111 nsPresContext* aPresContext)
112 : nsContainerFrame(aStyle, aPresContext, kClassID),
113 mLastBalanceBSize(NS_UNCONSTRAINEDSIZE) {}
114
ForEachColumnRule(const std::function<void (const nsRect & lineRect)> & aSetLineRect,const nsPoint & aPt) const115 void nsColumnSetFrame::ForEachColumnRule(
116 const std::function<void(const nsRect& lineRect)>& aSetLineRect,
117 const nsPoint& aPt) const {
118 nsIFrame* child = mFrames.FirstChild();
119 if (!child) return; // no columns
120
121 nsIFrame* nextSibling = child->GetNextSibling();
122 if (!nextSibling) return; // 1 column only - this means no gap to draw on
123
124 const nsStyleColumn* colStyle = StyleColumn();
125 nscoord ruleWidth = colStyle->GetComputedColumnRuleWidth();
126 if (!ruleWidth) return;
127
128 WritingMode wm = GetWritingMode();
129 bool isVertical = wm.IsVertical();
130 bool isRTL = wm.IsBidiRTL();
131
132 nsRect contentRect = GetContentRectRelativeToSelf() + aPt;
133 nsSize ruleSize = isVertical ? nsSize(contentRect.width, ruleWidth)
134 : nsSize(ruleWidth, contentRect.height);
135
136 while (nextSibling) {
137 // The frame tree goes RTL in RTL.
138 // The |prevFrame| and |nextFrame| frames here are the visually preceding
139 // (left/above) and following (right/below) frames, not in logical writing-
140 // mode direction.
141 nsIFrame* prevFrame = isRTL ? nextSibling : child;
142 nsIFrame* nextFrame = isRTL ? child : nextSibling;
143
144 // Each child frame's position coordinates is actually relative to this
145 // nsColumnSetFrame.
146 // linePt will be at the top-left edge to paint the line.
147 nsPoint linePt;
148 if (isVertical) {
149 nscoord edgeOfPrev = prevFrame->GetRect().YMost() + aPt.y;
150 nscoord edgeOfNext = nextFrame->GetRect().Y() + aPt.y;
151 linePt = nsPoint(contentRect.x,
152 (edgeOfPrev + edgeOfNext - ruleSize.height) / 2);
153 } else {
154 nscoord edgeOfPrev = prevFrame->GetRect().XMost() + aPt.x;
155 nscoord edgeOfNext = nextFrame->GetRect().X() + aPt.x;
156 linePt = nsPoint((edgeOfPrev + edgeOfNext - ruleSize.width) / 2,
157 contentRect.y);
158 }
159
160 aSetLineRect(nsRect(linePt, ruleSize));
161
162 child = nextSibling;
163 nextSibling = nextSibling->GetNextSibling();
164 }
165 }
166
CalculateColumnRuleBounds(const nsPoint & aOffset) const167 nsRect nsColumnSetFrame::CalculateColumnRuleBounds(
168 const nsPoint& aOffset) const {
169 nsRect combined;
170 ForEachColumnRule(
171 [&combined](const nsRect& aLineRect) {
172 combined = combined.Union(aLineRect);
173 },
174 aOffset);
175 return combined;
176 }
177
CreateBorderRenderers(nsTArray<nsCSSBorderRenderer> & aBorderRenderers,gfxContext * aCtx,const nsRect & aDirtyRect,const nsPoint & aPt)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;
185
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;
193
194 nscoord ruleWidth = colStyle->GetComputedColumnRuleWidth();
195 if (!ruleWidth) return;
196
197 aBorderRenderers.Clear();
198 nscolor ruleColor =
199 GetVisitedDependentColor(&nsStyleColumn::mColumnRuleColor);
200
201 nsPresContext* presContext = PresContext();
202 // In order to re-use a large amount of code, we treat the column rule as a
203 // border. We create a new border style object and fill in all the details of
204 // the column rule as the left border. PaintBorder() does all the rendering
205 // for us, so we not only save an enormous amount of code but we'll support
206 // all the line styles that we support on borders!
207 nsStyleBorder border(*presContext->Document());
208 Sides skipSides;
209 if (isVertical) {
210 border.SetBorderWidth(eSideTop, ruleWidth);
211 border.SetBorderStyle(eSideTop, ruleStyle);
212 border.mBorderTopColor = StyleColor::FromColor(ruleColor);
213 skipSides |= mozilla::SideBits::eLeftRight;
214 skipSides |= mozilla::SideBits::eBottom;
215 } else {
216 border.SetBorderWidth(eSideLeft, ruleWidth);
217 border.SetBorderStyle(eSideLeft, ruleStyle);
218 border.mBorderLeftColor = StyleColor::FromColor(ruleColor);
219 skipSides |= mozilla::SideBits::eTopBottom;
220 skipSides |= mozilla::SideBits::eRight;
221 }
222 // If we use box-decoration-break: slice (the default), the border
223 // renderers will require clipping if we have continuations (see the
224 // aNeedsClip parameter to ConstructBorderRenderer in nsCSSRendering).
225 //
226 // Since it doesn't matter which box-decoration-break we use since
227 // we're only drawing borders (and not border-images), use 'clone'.
228 border.mBoxDecorationBreak = StyleBoxDecorationBreak::Clone;
229
230 ForEachColumnRule(
231 [&](const nsRect& aLineRect) {
232 // Assert that we're not drawing a border-image here; if we were, we
233 // couldn't ignore the ImgDrawResult that PaintBorderWithStyleBorder
234 // returns.
235 MOZ_ASSERT(border.mBorderImageSource.IsNone());
236
237 gfx::DrawTarget* dt = aCtx ? aCtx->GetDrawTarget() : nullptr;
238 bool borderIsEmpty = false;
239 Maybe<nsCSSBorderRenderer> br =
240 nsCSSRendering::CreateBorderRendererWithStyleBorder(
241 presContext, dt, this, aDirtyRect, aLineRect, border, Style(),
242 &borderIsEmpty, skipSides);
243 if (br.isSome()) {
244 MOZ_ASSERT(!borderIsEmpty);
245 aBorderRenderers.AppendElement(br.value());
246 }
247 },
248 aPt);
249 }
250
GetAvailableContentISize(const ReflowInput & aReflowInput)251 static nscoord GetAvailableContentISize(const ReflowInput& aReflowInput) {
252 if (aReflowInput.AvailableISize() == NS_UNCONSTRAINEDSIZE) {
253 return NS_UNCONSTRAINEDSIZE;
254 }
255
256 WritingMode wm = aReflowInput.GetWritingMode();
257 nscoord borderPaddingISize =
258 aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm);
259 return std::max(0, aReflowInput.AvailableISize() - borderPaddingISize);
260 }
261
ColumnBalancingDepth(const ReflowInput & aReflowInput,uint32_t aMaxDepth)262 static uint32_t ColumnBalancingDepth(const ReflowInput& aReflowInput,
263 uint32_t aMaxDepth) {
264 uint32_t depth = 0;
265 for (const ReflowInput* ri = aReflowInput.mParentReflowInput;
266 ri && depth < aMaxDepth; ri = ri->mParentReflowInput) {
267 if (ri->mFlags.mIsColumnBalancing) {
268 ++depth;
269 }
270 }
271 return depth;
272 }
273
ChooseColumnStrategy(const ReflowInput & aReflowInput,bool aForceAuto=false) const274 nsColumnSetFrame::ReflowConfig nsColumnSetFrame::ChooseColumnStrategy(
275 const ReflowInput& aReflowInput, bool aForceAuto = false) const {
276 const nsStyleColumn* colStyle = StyleColumn();
277 nscoord availContentISize = GetAvailableContentISize(aReflowInput);
278 if (aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE) {
279 availContentISize = aReflowInput.ComputedISize();
280 }
281
282 nscoord colBSize = aReflowInput.AvailableBSize();
283 nscoord colGap =
284 ColumnUtils::GetColumnGap(this, aReflowInput.ComputedISize());
285 int32_t numColumns = colStyle->mColumnCount;
286
287 // If column-fill is set to 'balance', then we want to balance the columns.
288 bool isBalancing =
289 colStyle->mColumnFill == StyleColumnFill::Balance && !aForceAuto;
290 if (isBalancing) {
291 const uint32_t kMaxNestedColumnBalancingDepth = 2;
292 const uint32_t balancingDepth =
293 ColumnBalancingDepth(aReflowInput, kMaxNestedColumnBalancingDepth);
294 if (balancingDepth == kMaxNestedColumnBalancingDepth) {
295 isBalancing = false;
296 numColumns = 1;
297 }
298 }
299
300 nscoord colISize;
301 // In vertical writing-mode, "column-width" (inline size) will actually be
302 // physical height, but its CSS name is still column-width.
303 if (colStyle->mColumnWidth.IsLength()) {
304 colISize =
305 ColumnUtils::ClampUsedColumnWidth(colStyle->mColumnWidth.AsLength());
306 NS_ASSERTION(colISize >= 0, "negative column width");
307 // Reduce column count if necessary to make columns fit in the
308 // available width. Compute max number of columns that fit in
309 // availContentISize, satisfying colGap*(maxColumns - 1) +
310 // colISize*maxColumns <= availContentISize
311 if (availContentISize != NS_UNCONSTRAINEDSIZE && colGap + colISize > 0 &&
312 numColumns > 0) {
313 // This expression uses truncated rounding, which is what we
314 // want
315 int32_t maxColumns =
316 std::min(nscoord(nsStyleColumn::kMaxColumnCount),
317 (availContentISize + colGap) / (colGap + colISize));
318 numColumns = std::max(1, std::min(numColumns, maxColumns));
319 }
320 } else if (numColumns > 0 && availContentISize != NS_UNCONSTRAINEDSIZE) {
321 nscoord iSizeMinusGaps = availContentISize - colGap * (numColumns - 1);
322 colISize = iSizeMinusGaps / numColumns;
323 } else {
324 colISize = NS_UNCONSTRAINEDSIZE;
325 }
326 // Take care of the situation where there's only one column but it's
327 // still too wide
328 colISize = std::max(1, std::min(colISize, availContentISize));
329
330 nscoord expectedISizeLeftOver = 0;
331
332 if (colISize != NS_UNCONSTRAINEDSIZE &&
333 availContentISize != NS_UNCONSTRAINEDSIZE) {
334 // distribute leftover space
335
336 // First, determine how many columns will be showing if the column
337 // count is auto
338 if (numColumns <= 0) {
339 // choose so that colGap*(nominalColumnCount - 1) +
340 // colISize*nominalColumnCount is nearly availContentISize
341 // make sure to round down
342 if (colGap + colISize > 0) {
343 numColumns = (availContentISize + colGap) / (colGap + colISize);
344 // The number of columns should never exceed kMaxColumnCount.
345 numColumns =
346 std::min(nscoord(nsStyleColumn::kMaxColumnCount), numColumns);
347 }
348 if (numColumns <= 0) {
349 numColumns = 1;
350 }
351 }
352
353 // Compute extra space and divide it among the columns
354 nscoord extraSpace =
355 std::max(0, availContentISize -
356 (colISize * numColumns + colGap * (numColumns - 1)));
357 nscoord extraToColumns = extraSpace / numColumns;
358 colISize += extraToColumns;
359 expectedISizeLeftOver = extraSpace - (extraToColumns * numColumns);
360 }
361
362 if (isBalancing) {
363 if (numColumns <= 0) {
364 // Hmm, auto column count, column width or available width is unknown,
365 // and balancing is required. Let's just use one column then.
366 numColumns = 1;
367 }
368 colBSize = std::min(mLastBalanceBSize, colBSize);
369 } else {
370 // CSS Fragmentation spec says, "To guarantee progress, fragmentainers are
371 // assumed to have a minimum block size of 1px regardless of their used
372 // size." https://drafts.csswg.org/css-break/#breaking-rules
373 //
374 // Note: we don't enforce the minimum block-size during balancing because
375 // this affects the result. If a balancing column container or its
376 // next-in-flows has zero block-size, it eventually gives up balancing, and
377 // ends up here.
378 colBSize = std::max(colBSize, nsPresContext::CSSPixelsToAppUnits(1));
379 }
380
381 ReflowConfig config;
382 config.mUsedColCount = numColumns;
383 config.mColISize = colISize;
384 config.mExpectedISizeLeftOver = expectedISizeLeftOver;
385 config.mColGap = colGap;
386 config.mColMaxBSize = colBSize;
387 config.mIsBalancing = isBalancing;
388 config.mForceAuto = aForceAuto;
389 config.mKnownFeasibleBSize = NS_UNCONSTRAINEDSIZE;
390 config.mKnownInfeasibleBSize = 0;
391
392 COLUMN_SET_LOG(
393 "%s: this=%p, mUsedColCount=%d, mColISize=%d, "
394 "mExpectedISizeLeftOver=%d, mColGap=%d, mColMaxBSize=%d, mIsBalancing=%d",
395 __func__, this, config.mUsedColCount, config.mColISize,
396 config.mExpectedISizeLeftOver, config.mColGap, config.mColMaxBSize,
397 config.mIsBalancing);
398
399 return config;
400 }
401
MarkPrincipalChildrenDirty(nsIFrame * aFrame)402 static void MarkPrincipalChildrenDirty(nsIFrame* aFrame) {
403 for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
404 childFrame->MarkSubtreeDirty();
405 }
406 }
407
ReflowColumns(ReflowOutput & aDesiredSize,const ReflowInput & aReflowInput,nsReflowStatus & aReflowStatus,ReflowConfig & aConfig,bool aUnboundedLastColumn)408 nsColumnSetFrame::ColumnBalanceData nsColumnSetFrame::ReflowColumns(
409 ReflowOutput& aDesiredSize, const ReflowInput& aReflowInput,
410 nsReflowStatus& aReflowStatus, ReflowConfig& aConfig,
411 bool aUnboundedLastColumn) {
412 const ColumnBalanceData colData = ReflowChildren(
413 aDesiredSize, aReflowInput, aReflowStatus, aConfig, aUnboundedLastColumn);
414
415 if (!colData.mHasExcessBSize) {
416 return colData;
417 }
418
419 aConfig = ChooseColumnStrategy(aReflowInput, true);
420
421 // We need to reflow our children again one last time, otherwise we might
422 // end up with a stale column block-size for some of our columns, since we
423 // bailed out of balancing.
424 return ReflowChildren(aDesiredSize, aReflowInput, aReflowStatus, aConfig,
425 aUnboundedLastColumn);
426 }
427
MoveChildTo(nsIFrame * aChild,LogicalPoint aOrigin,WritingMode aWM,const nsSize & aContainerSize)428 static void MoveChildTo(nsIFrame* aChild, LogicalPoint aOrigin, WritingMode aWM,
429 const nsSize& aContainerSize) {
430 if (aChild->GetLogicalPosition(aWM, aContainerSize) == aOrigin) {
431 return;
432 }
433
434 aChild->SetPosition(aWM, aOrigin, aContainerSize);
435 nsContainerFrame::PlaceFrameView(aChild);
436 }
437
GetMinISize(gfxContext * aRenderingContext)438 nscoord nsColumnSetFrame::GetMinISize(gfxContext* aRenderingContext) {
439 nscoord iSize = 0;
440 DISPLAY_MIN_INLINE_SIZE(this, iSize);
441
442 if (mFrames.FirstChild()) {
443 // We want to ignore this in the case that we're size contained
444 // because our children should not contribute to our
445 // intrinsic size.
446 iSize = mFrames.FirstChild()->GetMinISize(aRenderingContext);
447 }
448 const nsStyleColumn* colStyle = StyleColumn();
449 if (colStyle->mColumnWidth.IsLength()) {
450 nscoord colISize =
451 ColumnUtils::ClampUsedColumnWidth(colStyle->mColumnWidth.AsLength());
452 // As available width reduces to zero, we reduce our number of columns
453 // to one, and don't enforce the column width, so just return the min
454 // of the child's min-width with any specified column width.
455 iSize = std::min(iSize, colISize);
456 } else {
457 NS_ASSERTION(colStyle->mColumnCount > 0,
458 "column-count and column-width can't both be auto");
459 // As available width reduces to zero, we still have mColumnCount columns,
460 // so compute our minimum size based on the number of columns and their gaps
461 // and minimum per-column size.
462 nscoord colGap = ColumnUtils::GetColumnGap(this, NS_UNCONSTRAINEDSIZE);
463 iSize = ColumnUtils::IntrinsicISize(colStyle->mColumnCount, colGap, iSize);
464 }
465 // XXX count forced column breaks here? Maybe we should return the child's
466 // min-width times the minimum number of columns.
467 return iSize;
468 }
469
GetPrefISize(gfxContext * aRenderingContext)470 nscoord nsColumnSetFrame::GetPrefISize(gfxContext* aRenderingContext) {
471 // Our preferred width is our desired column width, if specified, otherwise
472 // the child's preferred width, times the number of columns, plus the width
473 // of any required column gaps
474 // XXX what about forced column breaks here?
475 nscoord result = 0;
476 DISPLAY_PREF_INLINE_SIZE(this, result);
477 const nsStyleColumn* colStyle = StyleColumn();
478
479 nscoord colISize;
480 if (colStyle->mColumnWidth.IsLength()) {
481 colISize =
482 ColumnUtils::ClampUsedColumnWidth(colStyle->mColumnWidth.AsLength());
483 } else if (mFrames.FirstChild()) {
484 // We want to ignore this in the case that we're size contained
485 // because our children should not contribute to our
486 // intrinsic size.
487 colISize = mFrames.FirstChild()->GetPrefISize(aRenderingContext);
488 } else {
489 colISize = 0;
490 }
491
492 // If column-count is auto, assume one column.
493 uint32_t numColumns =
494 colStyle->mColumnCount == nsStyleColumn::kColumnCountAuto
495 ? 1
496 : colStyle->mColumnCount;
497 nscoord colGap = ColumnUtils::GetColumnGap(this, NS_UNCONSTRAINEDSIZE);
498 result = ColumnUtils::IntrinsicISize(numColumns, colGap, colISize);
499 return result;
500 }
501
ReflowChildren(ReflowOutput & aDesiredSize,const ReflowInput & aReflowInput,nsReflowStatus & aStatus,const ReflowConfig & aConfig,bool aUnboundedLastColumn)502 nsColumnSetFrame::ColumnBalanceData nsColumnSetFrame::ReflowChildren(
503 ReflowOutput& aDesiredSize, const ReflowInput& aReflowInput,
504 nsReflowStatus& aStatus, const ReflowConfig& aConfig,
505 bool aUnboundedLastColumn) {
506 ColumnBalanceData colData;
507 bool allFit = true;
508 WritingMode wm = GetWritingMode();
509 const bool isRTL = wm.IsBidiRTL();
510 const bool shrinkingBSize = mLastBalanceBSize > aConfig.mColMaxBSize;
511 const bool changingBSize = mLastBalanceBSize != aConfig.mColMaxBSize;
512
513 COLUMN_SET_LOG(
514 "%s: Doing column reflow pass: mLastBalanceBSize=%d,"
515 " mColMaxBSize=%d, RTL=%d, mUsedColCount=%d,"
516 " mColISize=%d, mColGap=%d",
517 __func__, mLastBalanceBSize, aConfig.mColMaxBSize, isRTL,
518 aConfig.mUsedColCount, aConfig.mColISize, aConfig.mColGap);
519
520 DrainOverflowColumns();
521
522 if (changingBSize) {
523 mLastBalanceBSize = aConfig.mColMaxBSize;
524 // XXX Seems like this could fire if incremental reflow pushed the column
525 // set down so we reflow incrementally with a different available height.
526 // We need a way to do an incremental reflow and be sure availableHeight
527 // changes are taken account of! Right now I think block frames with
528 // absolute children might exit early.
529 /*
530 NS_ASSERTION(
531 aKidReason != eReflowReason_Incremental,
532 "incremental reflow should not have changed the balance height");
533 */
534 }
535
536 nsRect contentRect(0, 0, 0, 0);
537 nsOverflowAreas overflowRects;
538
539 nsIFrame* child = mFrames.FirstChild();
540 LogicalPoint childOrigin(wm, 0, 0);
541
542 // In vertical-rl mode, columns will not be correctly placed if the
543 // reflowInput's ComputedWidth() is UNCONSTRAINED (in which case we'll get
544 // a containerSize.width of zero here). In that case, the column positions
545 // will be adjusted later, after our correct contentSize is known.
546 //
547 // When column-span is enabled, containerSize.width is always constrained.
548 // However, for RTL, we need to adjust the column positions as well after our
549 // correct containerSize is known.
550 nsSize containerSize = aReflowInput.ComputedSizeAsContainerIfConstrained();
551
552 const nscoord computedBSize =
553 aReflowInput.mParentReflowInput->ComputedBSize();
554 int columnCount = 0;
555 nscoord contentBEnd = 0;
556 bool reflowNext = false;
557
558 while (child) {
559 const bool isMeasuringFeasibleContentBSize =
560 aUnboundedLastColumn && columnCount == aConfig.mUsedColCount - 1 &&
561 aConfig.mIsBalancing;
562
563 // Try to skip reflowing the child. We can't skip if the child is dirty. We
564 // also can't skip if the next column is dirty, because the next column's
565 // first line(s) might be pullable back to this column. We can't skip if
566 // it's the last child because we need to obtain the bottom margin. We can't
567 // skip if this is the last column and we're supposed to assign unbounded
568 // block-size to it, because that could change the available block-size from
569 // the last time we reflowed it and we should try to pull all the
570 // content from its next sibling. (Note that it might be the last
571 // column, but not be the last child because the desired number of columns
572 // has changed.)
573 bool skipIncremental =
574 !aReflowInput.ShouldReflowAllKids() && !NS_SUBTREE_DIRTY(child) &&
575 child->GetNextSibling() && !isMeasuringFeasibleContentBSize &&
576 !NS_SUBTREE_DIRTY(child->GetNextSibling());
577
578 // If column-fill is auto (not the default), then we might need to
579 // move content between columns for any change in column block-size.
580 //
581 // The same is true if we have a non-'auto' computed block-size.
582 //
583 // FIXME: It's not clear to me why it's *ever* valid to have
584 // skipIncremental be true when changingBSize is true, since it
585 // seems like a child broken over multiple columns might need to
586 // change the size of the fragment in each column.
587 if (skipIncremental && changingBSize &&
588 (StyleColumn()->mColumnFill == StyleColumnFill::Auto ||
589 computedBSize != NS_UNCONSTRAINEDSIZE)) {
590 skipIncremental = false;
591 }
592 // If we need to pull up content from the prev-in-flow then this is not just
593 // a block-size shrink. The prev in flow will have set the dirty bit.
594 // Check the overflow rect YMost instead of just the child's content
595 // block-size. The child may have overflowing content that cares about the
596 // available block-size boundary. (It may also have overflowing content that
597 // doesn't care about the available block-size boundary, but if so, too bad,
598 // this optimization is defeated.) We want scrollable overflow here since
599 // this is a calculation that affects layout.
600 if (skipIncremental && shrinkingBSize) {
601 switch (wm.GetBlockDir()) {
602 case WritingMode::eBlockTB:
603 if (child->GetScrollableOverflowRect().YMost() >
604 aConfig.mColMaxBSize) {
605 skipIncremental = false;
606 }
607 break;
608 case WritingMode::eBlockLR:
609 if (child->GetScrollableOverflowRect().XMost() >
610 aConfig.mColMaxBSize) {
611 skipIncremental = false;
612 }
613 break;
614 case WritingMode::eBlockRL:
615 // XXX not sure how to handle this, so for now just don't attempt
616 // the optimization
617 skipIncremental = false;
618 break;
619 default:
620 MOZ_ASSERT_UNREACHABLE("unknown block direction");
621 break;
622 }
623 }
624
625 nscoord childContentBEnd = 0;
626 if (!reflowNext && skipIncremental) {
627 // This child does not need to be reflowed, but we may need to move it
628 MoveChildTo(child, childOrigin, wm, containerSize);
629
630 // If this is the last frame then make sure we get the right status
631 nsIFrame* kidNext = child->GetNextSibling();
632 if (kidNext) {
633 aStatus.Reset();
634 if (kidNext->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) {
635 aStatus.SetOverflowIncomplete();
636 } else {
637 aStatus.SetIncomplete();
638 }
639 } else {
640 aStatus = mLastFrameStatus;
641 }
642 childContentBEnd = nsLayoutUtils::CalculateContentBEnd(wm, child);
643
644 COLUMN_SET_LOG("%s: Skipping child #%d %p (incremental %d): status=%s",
645 __func__, columnCount, child, skipIncremental,
646 ToString(aStatus).c_str());
647 } else {
648 LogicalSize availSize(wm, aConfig.mColISize, aConfig.mColMaxBSize);
649 if (isMeasuringFeasibleContentBSize) {
650 availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
651
652 COLUMN_SET_LOG(
653 "%s: Measuring content block-size, change available block-size "
654 "from %d to %d",
655 __func__, aConfig.mColMaxBSize, availSize.BSize(wm));
656 }
657
658 if (reflowNext) {
659 child->MarkSubtreeDirty();
660 }
661
662 LogicalSize kidCBSize(wm, availSize.ISize(wm), computedBSize);
663 ReflowInput kidReflowInput(PresContext(), aReflowInput, child, availSize,
664 Some(kidCBSize));
665 kidReflowInput.mFlags.mIsTopOfPage = true;
666 kidReflowInput.mFlags.mTableIsSplittable = false;
667 kidReflowInput.mFlags.mIsColumnBalancing = aConfig.mIsBalancing;
668
669 // We need to reflow any float placeholders, even if our column block-size
670 // hasn't changed.
671 kidReflowInput.mFlags.mMustReflowPlaceholders = !changingBSize;
672
673 COLUMN_SET_LOG(
674 "%s: Reflowing child #%d %p: availSize=(%d,%d), kidCBSize=(%d,%d)",
675 __func__, columnCount, child, availSize.ISize(wm),
676 availSize.BSize(wm), kidCBSize.ISize(wm), kidCBSize.BSize(wm));
677
678 // Note if the column's next in flow is not being changed by this
679 // incremental reflow. This may allow the current column to avoid trying
680 // to pull lines from the next column.
681 if (child->GetNextSibling() && !(GetStateBits() & NS_FRAME_IS_DIRTY) &&
682 !(child->GetNextSibling()->GetStateBits() & NS_FRAME_IS_DIRTY)) {
683 kidReflowInput.mFlags.mNextInFlowUntouched = true;
684 }
685
686 ReflowOutput kidDesiredSize(wm);
687
688 // XXX it would be cool to consult the float manager for the
689 // previous block to figure out the region of floats from the
690 // previous column that extend into this column, and subtract
691 // that region from the new float manager. So you could stick a
692 // really big float in the first column and text in following
693 // columns would flow around it.
694
695 // Reflow the frame
696 LogicalPoint origin(
697 wm,
698 childOrigin.I(wm) + kidReflowInput.ComputedLogicalMargin().IStart(wm),
699 childOrigin.B(wm) +
700 kidReflowInput.ComputedLogicalMargin().BStart(wm));
701 aStatus.Reset();
702 ReflowChild(child, PresContext(), kidDesiredSize, kidReflowInput, wm,
703 origin, containerSize, ReflowChildFlags::Default, aStatus);
704
705 reflowNext = aStatus.NextInFlowNeedsReflow();
706
707 COLUMN_SET_LOG(
708 "%s: Reflowed child #%d %p: status=%s,"
709 " desiredSize=(%d,%d), CarriedOutBEndMargin=%d (ignored)",
710 __func__, columnCount, child, ToString(aStatus).c_str(),
711 kidDesiredSize.ISize(wm), kidDesiredSize.BSize(wm),
712 kidDesiredSize.mCarriedOutBEndMargin.get());
713
714 // The carried-out block-end margin of column content might be non-zero
715 // when we try to find the best column balancing block size, but it should
716 // never affect the size column set nor be further carried out. Set it to
717 // zero.
718 //
719 // FIXME: For some types of fragmentation, we should carry the margin into
720 // the next column. Also see
721 // https://drafts.csswg.org/css-break-4/#break-margins
722 //
723 // FIXME: This should never happen for the last column, since it should be
724 // a margin root; see nsBlockFrame::IsMarginRoot(). However, sometimes the
725 // last column has an empty continuation while searching for the best
726 // column balancing bsize, which prevents the last column from being a
727 // margin root.
728 kidDesiredSize.mCarriedOutBEndMargin.Zero();
729
730 NS_FRAME_TRACE_REFLOW_OUT("Column::Reflow", aStatus);
731
732 FinishReflowChild(child, PresContext(), kidDesiredSize, &kidReflowInput,
733 wm, childOrigin, containerSize,
734 ReflowChildFlags::Default);
735
736 childContentBEnd = nsLayoutUtils::CalculateContentBEnd(wm, child);
737 if (childContentBEnd > aConfig.mColMaxBSize) {
738 allFit = false;
739 }
740 if (childContentBEnd > availSize.BSize(wm)) {
741 colData.mMaxOverflowingBSize =
742 std::max(childContentBEnd, colData.mMaxOverflowingBSize);
743 }
744 }
745
746 contentRect.UnionRect(contentRect, child->GetRect());
747
748 ConsiderChildOverflow(overflowRects, child);
749 contentBEnd = std::max(contentBEnd, childContentBEnd);
750 colData.mLastBSize = childContentBEnd;
751 colData.mSumBSize += childContentBEnd;
752
753 // Build a continuation column if necessary
754 nsIFrame* kidNextInFlow = child->GetNextInFlow();
755
756 if (aStatus.IsFullyComplete() && !aStatus.IsTruncated()) {
757 NS_ASSERTION(!kidNextInFlow, "next in flow should have been deleted");
758 child = nullptr;
759 break;
760 }
761
762 // Make sure that the column has a next-in-flow. If not, we must
763 // create one to hold the overflowing stuff, even if we're just
764 // going to put it on our overflow list and let *our*
765 // next in flow handle it.
766 if (!kidNextInFlow) {
767 NS_ASSERTION(aStatus.NextInFlowNeedsReflow(),
768 "We have to create a continuation, but the block doesn't "
769 "want us to reflow it?");
770
771 // We need to create a continuing column
772 kidNextInFlow = CreateNextInFlow(child);
773 }
774
775 // Make sure we reflow a next-in-flow when it switches between being
776 // normal or overflow container
777 if (aStatus.IsOverflowIncomplete()) {
778 if (!(kidNextInFlow->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)) {
779 aStatus.SetNextInFlowNeedsReflow();
780 reflowNext = true;
781 kidNextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
782 }
783 } else if (kidNextInFlow->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) {
784 aStatus.SetNextInFlowNeedsReflow();
785 reflowNext = true;
786 kidNextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
787 }
788
789 if ((contentBEnd > aReflowInput.ComputedMaxBSize() ||
790 contentBEnd > aReflowInput.ComputedBSize() ||
791 contentBEnd > aReflowInput.mCBReflowInput->ComputedMaxBSize()) &&
792 aConfig.mIsBalancing) {
793 // We overflowed vertically, but have not exceeded the number of
794 // columns. We're going to go into overflow columns now, so balancing
795 // no longer applies.
796 colData.mHasExcessBSize = true;
797 }
798
799 // We have reached the maximum number of columns. If we are balancing, stop
800 // this reflow and continue finding the optimal balancing block-size.
801 //
802 // Otherwise, i.e. we are not balancing, stop this reflow and let the parent
803 // of our multicol container create a next-in-flow if all of the following
804 // conditions are met.
805 //
806 // 1) We fill columns sequentially by the request of the style, not by our
807 // internal needs, i.e. aConfig.mForceAuto is false.
808 //
809 // We don't want to stop this reflow when we force fill the columns
810 // sequentially. We usually go into this mode when giving up balancing, and
811 // this is the last resort to fit all our children by creating overflow
812 // columns.
813 //
814 // 2) In a fragmented context, our multicol container still has block-size
815 // left for its next-in-flow, i.e.
816 // aReflowInput.mFlags.mColumnSetWrapperHasNoBSizeLeft is false.
817 //
818 // Note that in a continuous context, i.e. our multicol container's
819 // available block-size is unconstrained, if it has a fixed block-size
820 // mColumnSetWrapperHasNoBSizeLeft is always true because nothing stops it
821 // from applying all its block-size in the first-in-flow. Otherwise, i.e.
822 // our multicol container has an unconstrained block-size, we shouldn't be
823 // here because all our children should fit in the very first column even if
824 // mColumnSetWrapperHasNoBSizeLeft is false.
825 //
826 // According to the definition of mColumnSetWrapperHasNoBSizeLeft, if the
827 // bit is *not* set, either our multicol container has unconstrained
828 // block-size, or it has a constrained block-size and has block-size left
829 // for its next-in-flow. In either cases, the parent of our multicol
830 // container can create a next-in-flow for the container that guaranteed to
831 // have non-zero block-size for the container's children.
832 //
833 // Put simply, if either one of the above conditions is not met, we are
834 // going to create more overflow columns until all our children are fit.
835 if (columnCount >= aConfig.mUsedColCount - 1 &&
836 (aConfig.mIsBalancing ||
837 (!aConfig.mForceAuto &&
838 !aReflowInput.mFlags.mColumnSetWrapperHasNoBSizeLeft))) {
839 NS_ASSERTION(aConfig.mIsBalancing ||
840 aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
841 "Why are we here if we have unlimited block-size to fill "
842 "columns sequentially.");
843
844 // No more columns allowed here. Stop.
845 aStatus.SetNextInFlowNeedsReflow();
846 kidNextInFlow->MarkSubtreeDirty();
847 // Move any of our leftover columns to our overflow list. Our
848 // next-in-flow will eventually pick them up.
849 const nsFrameList& continuationColumns = mFrames.RemoveFramesAfter(child);
850 if (continuationColumns.NotEmpty()) {
851 SetOverflowFrames(continuationColumns);
852 }
853 child = nullptr;
854
855 COLUMN_SET_LOG("%s: We are not going to create overflow columns.",
856 __func__);
857 break;
858 }
859
860 if (PresContext()->HasPendingInterrupt()) {
861 // Stop the loop now while |child| still points to the frame that bailed
862 // out. We could keep going here and condition a bunch of the code in
863 // this loop on whether there's an interrupt, or even just keep going and
864 // trying to reflow the blocks (even though we know they'll interrupt
865 // right after their first line), but stopping now is conceptually the
866 // simplest (and probably fastest) thing.
867 break;
868 }
869
870 // Advance to the next column
871 child = child->GetNextSibling();
872 ++columnCount;
873
874 if (child) {
875 childOrigin.I(wm) += aConfig.mColISize + aConfig.mColGap;
876
877 COLUMN_SET_LOG("%s: Next childOrigin.iCoord=%d", __func__,
878 childOrigin.I(wm));
879 }
880 }
881
882 if (PresContext()->CheckForInterrupt(this) &&
883 (GetStateBits() & NS_FRAME_IS_DIRTY)) {
884 // Mark all our kids starting with |child| dirty
885
886 // Note that this is a CheckForInterrupt call, not a HasPendingInterrupt,
887 // because we might have interrupted while reflowing |child|, and since
888 // we're about to add a dirty bit to |child| we need to make sure that
889 // |this| is scheduled to have dirty bits marked on it and its ancestors.
890 // Otherwise, when we go to mark dirty bits on |child|'s ancestors we'll
891 // bail out immediately, since it'll already have a dirty bit.
892 for (; child; child = child->GetNextSibling()) {
893 child->MarkSubtreeDirty();
894 }
895 }
896
897 colData.mMaxBSize = contentBEnd;
898 LogicalSize contentSize = LogicalSize(wm, contentRect.Size());
899 contentSize.BSize(wm) = std::max(contentSize.BSize(wm), contentBEnd);
900 mLastFrameStatus = aStatus;
901
902 if (computedBSize != NS_UNCONSTRAINEDSIZE && !HasColumnSpanSiblings()) {
903 NS_ASSERTION(aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
904 "Available block-size should be constrained because it's "
905 "restricted by the computed block-size when our reflow "
906 "input is created in nsBlockFrame::ReflowBlockFrame()!");
907
908 // If a) our parent ColumnSetWrapper has constrained block-size
909 // (nsBlockFrame::ReflowBlockFrame() applies the block-size constraint
910 // when creating BlockReflowInput for ColumnSetFrame); and b) we are the
911 // sole ColumnSet or the last ColumnSet continuation split by column-spans
912 // in a ColumnSetWrapper, extend our block-size to consume the available
913 // block-size so that the column-rules are drawn to the content block-end
914 // edge of the multicol container.
915 contentSize.BSize(wm) =
916 std::max(contentSize.BSize(wm), aReflowInput.AvailableBSize());
917 }
918
919 aDesiredSize.SetSize(wm, contentSize);
920 aDesiredSize.mOverflowAreas = overflowRects;
921 aDesiredSize.UnionOverflowAreasWithDesiredBounds();
922
923 // In vertical-rl mode, make a second pass if necessary to reposition the
924 // columns with the correct container width. (In other writing modes,
925 // correct containerSize was not required for column positioning so we don't
926 // need this fixup.)
927 //
928 // RTL column positions also depend on ColumnSet's actual contentSize. We need
929 // this fixup, too.
930 if ((wm.IsVerticalRL() || isRTL) &&
931 containerSize.width != contentSize.Width(wm)) {
932 const nsSize finalContainerSize = aDesiredSize.PhysicalSize();
933 nsOverflowAreas overflowRects;
934 for (nsIFrame* child : mFrames) {
935 // Get the logical position as set previously using a provisional or
936 // dummy containerSize, and reset with the correct container size.
937 child->SetPosition(wm, child->GetLogicalPosition(wm, containerSize),
938 finalContainerSize);
939 ConsiderChildOverflow(overflowRects, child);
940 }
941 aDesiredSize.mOverflowAreas = overflowRects;
942 aDesiredSize.UnionOverflowAreasWithDesiredBounds();
943 }
944
945 colData.mFeasible =
946 allFit && aStatus.IsFullyComplete() && !aStatus.IsTruncated();
947 COLUMN_SET_LOG(
948 "%s: Done column reflow pass: %s, mMaxBSize=%d, mSumBSize=%d, "
949 "mMaxOverflowingBSize=%d",
950 __func__, colData.mFeasible ? "Feasible :)" : "Infeasible :(",
951 colData.mMaxBSize, colData.mSumBSize, colData.mMaxOverflowingBSize);
952
953 return colData;
954 }
955
DrainOverflowColumns()956 void nsColumnSetFrame::DrainOverflowColumns() {
957 // First grab the prev-in-flows overflows and reparent them to this
958 // frame.
959 nsPresContext* presContext = PresContext();
960 nsColumnSetFrame* prev = static_cast<nsColumnSetFrame*>(GetPrevInFlow());
961 if (prev) {
962 AutoFrameListPtr overflows(presContext, prev->StealOverflowFrames());
963 if (overflows) {
964 nsContainerFrame::ReparentFrameViewList(*overflows, prev, this);
965
966 mFrames.InsertFrames(this, nullptr, *overflows);
967 }
968 }
969
970 // Now pull back our own overflows and append them to our children.
971 // We don't need to reparent them since we're already their parent.
972 AutoFrameListPtr overflows(presContext, StealOverflowFrames());
973 if (overflows) {
974 // We're already the parent for these frames, so no need to set
975 // their parent again.
976 mFrames.AppendFrames(nullptr, *overflows);
977 }
978 }
979
FindBestBalanceBSize(const ReflowInput & aReflowInput,nsPresContext * aPresContext,ReflowConfig & aConfig,ColumnBalanceData aColData,ReflowOutput & aDesiredSize,bool aUnboundedLastColumn,nsReflowStatus & aStatus)980 void nsColumnSetFrame::FindBestBalanceBSize(const ReflowInput& aReflowInput,
981 nsPresContext* aPresContext,
982 ReflowConfig& aConfig,
983 ColumnBalanceData aColData,
984 ReflowOutput& aDesiredSize,
985 bool aUnboundedLastColumn,
986 nsReflowStatus& aStatus) {
987 const nscoord availableContentBSize = aReflowInput.AvailableBSize();
988
989 // Termination of the algorithm below is guaranteed because
990 // aConfig.knownFeasibleBSize - aConfig.knownInfeasibleBSize decreases in
991 // every iteration.
992
993 // We set this flag when we detect that we may contain a frame
994 // that can break anywhere (thus foiling the linear decrease-by-one
995 // search)
996 bool maybeContinuousBreakingDetected = false;
997
998 while (!aPresContext->HasPendingInterrupt()) {
999 nscoord lastKnownFeasibleBSize = aConfig.mKnownFeasibleBSize;
1000
1001 // Record what we learned from the last reflow
1002 if (aColData.mFeasible) {
1003 // maxBSize is feasible. Also, mLastBalanceBSize is feasible.
1004 aConfig.mKnownFeasibleBSize =
1005 std::min(aConfig.mKnownFeasibleBSize, aColData.mMaxBSize);
1006 aConfig.mKnownFeasibleBSize =
1007 std::min(aConfig.mKnownFeasibleBSize, mLastBalanceBSize);
1008
1009 // Furthermore, no block-size less than the block-size of the last
1010 // column can ever be feasible. (We might be able to reduce the
1011 // block-size of a non-last column by moving content to a later column,
1012 // but we can't do that with the last column.)
1013 if (mFrames.GetLength() == aConfig.mUsedColCount) {
1014 aConfig.mKnownInfeasibleBSize =
1015 std::max(aConfig.mKnownInfeasibleBSize, aColData.mLastBSize - 1);
1016 }
1017 } else {
1018 aConfig.mKnownInfeasibleBSize =
1019 std::max(aConfig.mKnownInfeasibleBSize, mLastBalanceBSize);
1020
1021 // If a column didn't fit in its available block-size, then its current
1022 // block-size must be the minimum block-size for unbreakable content in
1023 // the column, and therefore no smaller block-size can be feasible.
1024 aConfig.mKnownInfeasibleBSize = std::max(
1025 aConfig.mKnownInfeasibleBSize, aColData.mMaxOverflowingBSize - 1);
1026
1027 if (aUnboundedLastColumn) {
1028 // The last column is unbounded, so all content got reflowed, so the
1029 // mMaxBSize is feasible.
1030 aConfig.mKnownFeasibleBSize =
1031 std::min(aConfig.mKnownFeasibleBSize, aColData.mMaxBSize);
1032
1033 NS_ASSERTION(mLastFrameStatus.IsComplete(),
1034 "Last column should be complete if the available "
1035 "block-size is unconstrained!");
1036 }
1037 }
1038
1039 COLUMN_SET_LOG(
1040 "%s: this=%p, mKnownInfeasibleBSize=%d, mKnownFeasibleBSize=%d",
1041 __func__, this, aConfig.mKnownInfeasibleBSize,
1042 aConfig.mKnownFeasibleBSize);
1043
1044 if (aConfig.mKnownInfeasibleBSize >= aConfig.mKnownFeasibleBSize - 1) {
1045 // aConfig.mKnownFeasibleBSize is where we want to be
1046 break;
1047 }
1048
1049 if (aConfig.mKnownInfeasibleBSize >= availableContentBSize) {
1050 // There's no feasible block-size to fit our contents. We may need to
1051 // reflow one more time after this loop.
1052 break;
1053 }
1054
1055 if (lastKnownFeasibleBSize - aConfig.mKnownFeasibleBSize == 1) {
1056 // We decreased the feasible block-size by one twip only. This could
1057 // indicate that there is a continuously breakable child frame
1058 // that we are crawling through.
1059 maybeContinuousBreakingDetected = true;
1060 }
1061
1062 nscoord nextGuess =
1063 (aConfig.mKnownFeasibleBSize + aConfig.mKnownInfeasibleBSize) / 2;
1064 // The constant of 600 twips is arbitrary. It's about two line-heights.
1065 if (aConfig.mKnownFeasibleBSize - nextGuess < 600 &&
1066 !maybeContinuousBreakingDetected) {
1067 // We're close to our target, so just try shrinking just the
1068 // minimum amount that will cause one of our columns to break
1069 // differently.
1070 nextGuess = aConfig.mKnownFeasibleBSize - 1;
1071 } else if (aUnboundedLastColumn) {
1072 // Make a guess by dividing that into N columns. Add some slop
1073 // to try to make it on the feasible side. The constant of
1074 // 600 twips is arbitrary. It's about two line-heights.
1075 nextGuess = aColData.mSumBSize / aConfig.mUsedColCount + 600;
1076 // Sanitize it
1077 nextGuess = clamped(nextGuess, aConfig.mKnownInfeasibleBSize + 1,
1078 aConfig.mKnownFeasibleBSize - 1);
1079 } else if (aConfig.mKnownFeasibleBSize == NS_UNCONSTRAINEDSIZE) {
1080 // This can happen when we had a next-in-flow so we didn't
1081 // want to do an unbounded block-size measuring step. Let's just increase
1082 // from the infeasible block-size by some reasonable amount.
1083 nextGuess = aConfig.mKnownInfeasibleBSize * 2 + 600;
1084 }
1085 // Don't bother guessing more than our block-size constraint.
1086 nextGuess = std::min(availableContentBSize, nextGuess);
1087
1088 COLUMN_SET_LOG("%s: Choosing next guess=%d", __func__, nextGuess);
1089
1090 aConfig.mColMaxBSize = nextGuess;
1091
1092 aUnboundedLastColumn = false;
1093 MarkPrincipalChildrenDirty(this);
1094 aColData =
1095 ReflowColumns(aDesiredSize, aReflowInput, aStatus, aConfig, false);
1096
1097 if (!aConfig.mIsBalancing) {
1098 // Looks like we had excess block-size when balancing, so we gave up on
1099 // trying to balance.
1100 break;
1101 }
1102 }
1103
1104 if (aConfig.mIsBalancing && !aColData.mFeasible &&
1105 !aPresContext->HasPendingInterrupt()) {
1106 // We need to reflow one more time at the feasible block-size to
1107 // get a valid layout.
1108 if (aConfig.mKnownInfeasibleBSize >= availableContentBSize) {
1109 aConfig.mColMaxBSize = availableContentBSize;
1110 if (mLastBalanceBSize == availableContentBSize) {
1111 // If we end up here, we have a constrained available content
1112 // block-size, and our last column's block-size exceeds it. Also, if
1113 // this is the first balancing iteration, the last column is given
1114 // unconstrained available block-size, so it has a fully complete
1115 // reflow status. Therefore, we always want to reflow again at the
1116 // available content block-size to get a valid layout and a correct
1117 // reflow status (likely an *incomplete* status) so that our column
1118 // container can be fragmented if needed.
1119
1120 if (aReflowInput.mFlags.mColumnSetWrapperHasNoBSizeLeft) {
1121 // If our column container has a constrained block-size (either in a
1122 // paginated context or in a nested column container), and is going
1123 // to consume all its computed block-size in this fragment, then our
1124 // column container has no block-size left to contain our
1125 // next-in-flows. We have to give up balancing, and create our
1126 // own overflow columns.
1127 //
1128 // We don't want to create overflow columns immediately when our
1129 // content doesn't fit since this changes our reflow status from
1130 // incomplete to complete. Valid reasons include 1) the outer column
1131 // container might do column balancing, and it can enlarge the
1132 // available content block-size so that the nested one could fit its
1133 // content in next balancing iteration; or 2) the outer column
1134 // container is filling columns sequentially, and may have more
1135 // inline-size to create more column boxes for the nested column
1136 // container's next-in-flows.
1137 aConfig = ChooseColumnStrategy(aReflowInput, true);
1138 }
1139 }
1140 } else {
1141 aConfig.mColMaxBSize = aConfig.mKnownFeasibleBSize;
1142 }
1143
1144 // This is our last attempt to reflow. If our column container's available
1145 // block-size is unconstrained, make sure that the last column is
1146 // allowed to have arbitrary block-size here, even though we were
1147 // balancing. Otherwise we'd have to split, and it's not clear what we'd
1148 // do with that.
1149 const bool forceUnboundedLastColumn =
1150 aReflowInput.mParentReflowInput->AvailableBSize() ==
1151 NS_UNCONSTRAINEDSIZE;
1152 MarkPrincipalChildrenDirty(this);
1153 ReflowColumns(aDesiredSize, aReflowInput, aStatus, aConfig,
1154 forceUnboundedLastColumn);
1155 }
1156 }
1157
Reflow(nsPresContext * aPresContext,ReflowOutput & aDesiredSize,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)1158 void nsColumnSetFrame::Reflow(nsPresContext* aPresContext,
1159 ReflowOutput& aDesiredSize,
1160 const ReflowInput& aReflowInput,
1161 nsReflowStatus& aStatus) {
1162 MarkInReflow();
1163 // Don't support interruption in columns
1164 nsPresContext::InterruptPreventer noInterrupts(aPresContext);
1165
1166 DO_GLOBAL_REFLOW_COUNT("nsColumnSetFrame");
1167 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
1168 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
1169
1170 MOZ_ASSERT(aReflowInput.mCBReflowInput->mFrame->StyleColumn()
1171 ->IsColumnContainerStyle(),
1172 "The column container should have relevant column styles!");
1173 MOZ_ASSERT(aReflowInput.mParentReflowInput->mFrame->IsColumnSetWrapperFrame(),
1174 "The column container should be ColumnSetWrapperFrame!");
1175 MOZ_ASSERT(aReflowInput.ComputedLogicalBorderPadding().IsAllZero(),
1176 "Only the column container can have border and padding!");
1177
1178 #ifdef DEBUG
1179 nsFrameList::Enumerator oc(GetChildList(kOverflowContainersList));
1180 for (; !oc.AtEnd(); oc.Next()) {
1181 MOZ_ASSERT(!IS_TRUE_OVERFLOW_CONTAINER(oc.get()));
1182 }
1183 nsFrameList::Enumerator eoc(GetChildList(kExcessOverflowContainersList));
1184 for (; !eoc.AtEnd(); eoc.Next()) {
1185 MOZ_ASSERT(!IS_TRUE_OVERFLOW_CONTAINER(eoc.get()));
1186 }
1187 #endif
1188
1189 nsOverflowAreas ocBounds;
1190 nsReflowStatus ocStatus;
1191 if (GetPrevInFlow()) {
1192 ReflowOverflowContainerChildren(aPresContext, aReflowInput, ocBounds,
1193 ReflowChildFlags::Default, ocStatus);
1194 }
1195
1196 //------------ Handle Incremental Reflow -----------------
1197
1198 // If inline size is unconstrained, set aForceAuto to true to allow
1199 // the columns to expand in the inline direction. (This typically
1200 // happens in orthogonal flows where the inline direction is the
1201 // container's block direction).
1202 ReflowConfig config = ChooseColumnStrategy(
1203 aReflowInput, aReflowInput.ComputedISize() == NS_UNCONSTRAINEDSIZE);
1204
1205 // If balancing, then we allow the last column to grow to unbounded
1206 // block-size during the first reflow. This gives us a way to estimate
1207 // what the average column block-size should be, because we can measure
1208 // the block-size of all the columns and sum them up. But don't do this
1209 // if we have a next in flow because we don't want to suck all its
1210 // content back here and then have to push it out again!
1211 nsIFrame* nextInFlow = GetNextInFlow();
1212 bool unboundedLastColumn = config.mIsBalancing && !nextInFlow;
1213 const ColumnBalanceData colData = ReflowColumns(
1214 aDesiredSize, aReflowInput, aStatus, config, unboundedLastColumn);
1215
1216 // If we're not balancing, then we're already done, since we should have
1217 // reflown all of our children, and there is no need for a binary search to
1218 // determine proper column block-size.
1219 if (config.mIsBalancing && !aPresContext->HasPendingInterrupt()) {
1220 FindBestBalanceBSize(aReflowInput, aPresContext, config, colData,
1221 aDesiredSize, unboundedLastColumn, aStatus);
1222 }
1223
1224 if (aPresContext->HasPendingInterrupt() &&
1225 aReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
1226 // In this situation, we might be lying about our reflow status, because
1227 // our last kid (the one that got interrupted) was incomplete. Fix that.
1228 aStatus.Reset();
1229 }
1230
1231 NS_ASSERTION(aStatus.IsFullyComplete() ||
1232 aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
1233 "Column set should be complete if the available block-size is "
1234 "unconstrained");
1235
1236 // Merge overflow container bounds and status.
1237 aDesiredSize.mOverflowAreas.UnionWith(ocBounds);
1238 aStatus.MergeCompletionStatusFrom(ocStatus);
1239
1240 FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput,
1241 aStatus, false);
1242
1243 NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
1244 }
1245
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)1246 void nsColumnSetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1247 const nsDisplayListSet& aLists) {
1248 DisplayBorderBackgroundOutline(aBuilder, aLists);
1249
1250 if (IsVisibleForPainting()) {
1251 aLists.BorderBackground()->AppendNewToTop<nsDisplayColumnRule>(aBuilder,
1252 this);
1253 }
1254
1255 // Our children won't have backgrounds so it doesn't matter where we put them.
1256 for (nsFrameList::Enumerator e(mFrames); !e.AtEnd(); e.Next()) {
1257 BuildDisplayListForChild(aBuilder, e.get(), aLists);
1258 }
1259 }
1260
AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox> & aResult)1261 void nsColumnSetFrame::AppendDirectlyOwnedAnonBoxes(
1262 nsTArray<OwnedAnonBox>& aResult) {
1263 // Everything in mFrames is continuations of the first thing in mFrames.
1264 nsIFrame* column = mFrames.FirstChild();
1265
1266 // We might not have any columns, apparently?
1267 if (!column) {
1268 return;
1269 }
1270
1271 MOZ_ASSERT(column->Style()->GetPseudoType() == PseudoStyleType::columnContent,
1272 "What sort of child is this?");
1273 aResult.AppendElement(OwnedAnonBox(column));
1274 }
1275
1276 #ifdef DEBUG
SetInitialChildList(ChildListID aListID,nsFrameList & aChildList)1277 void nsColumnSetFrame::SetInitialChildList(ChildListID aListID,
1278 nsFrameList& aChildList) {
1279 MOZ_ASSERT(aListID != kPrincipalList || aChildList.OnlyChild(),
1280 "initial principal child list must have exactly one child");
1281 nsContainerFrame::SetInitialChildList(aListID, aChildList);
1282 }
1283
AppendFrames(ChildListID aListID,nsFrameList & aFrameList)1284 void nsColumnSetFrame::AppendFrames(ChildListID aListID,
1285 nsFrameList& aFrameList) {
1286 MOZ_CRASH("unsupported operation");
1287 }
1288
InsertFrames(ChildListID aListID,nsIFrame * aPrevFrame,const nsLineList::iterator * aPrevFrameLine,nsFrameList & aFrameList)1289 void nsColumnSetFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
1290 const nsLineList::iterator* aPrevFrameLine,
1291 nsFrameList& aFrameList) {
1292 MOZ_CRASH("unsupported operation");
1293 }
1294
RemoveFrame(ChildListID aListID,nsIFrame * aOldFrame)1295 void nsColumnSetFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) {
1296 MOZ_CRASH("unsupported operation");
1297 }
1298 #endif
1299