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