1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /* rendering object for css3 multi-column layout */
8 #include "nsColumnSetFrame.h"
9 #include "nsCSSRendering.h"
10 #include "nsDisplayList.h"
12 using namespace mozilla
;
13 using namespace mozilla::layout
;
18 * XXX cursor movement around the top and bottom of colums seems to make the editor
21 * XXX should we support CSS columns applied to table elements?
24 NS_NewColumnSetFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
, nsFrameState aStateFlags
)
26 nsColumnSetFrame
* it
= new (aPresShell
) nsColumnSetFrame(aContext
);
27 it
->AddStateBits(aStateFlags
| NS_BLOCK_MARGIN_ROOT
);
31 NS_IMPL_FRAMEARENA_HELPERS(nsColumnSetFrame
)
33 nsColumnSetFrame::nsColumnSetFrame(nsStyleContext
* aContext
)
34 : nsContainerFrame(aContext
), mLastBalanceHeight(NS_INTRINSICSIZE
),
35 mLastFrameStatus(NS_FRAME_COMPLETE
)
40 nsColumnSetFrame::GetType() const
42 return nsGkAtoms::columnSetFrame
;
46 PaintColumnRule(nsIFrame
* aFrame
, nsRenderingContext
* aCtx
,
47 const nsRect
& aDirtyRect
, nsPoint aPt
)
49 static_cast<nsColumnSetFrame
*>(aFrame
)->PaintColumnRule(aCtx
, aDirtyRect
, aPt
);
53 nsColumnSetFrame::PaintColumnRule(nsRenderingContext
* aCtx
,
54 const nsRect
& aDirtyRect
,
57 nsIFrame
* child
= mFrames
.FirstChild();
61 nsIFrame
* nextSibling
= child
->GetNextSibling();
63 return; // 1 column only - this means no gap to draw on
65 bool isRTL
= StyleVisibility()->mDirection
== NS_STYLE_DIRECTION_RTL
;
66 const nsStyleColumn
* colStyle
= StyleColumn();
69 // Per spec, inset => ridge and outset => groove
70 if (colStyle
->mColumnRuleStyle
== NS_STYLE_BORDER_STYLE_INSET
)
71 ruleStyle
= NS_STYLE_BORDER_STYLE_RIDGE
;
72 else if (colStyle
->mColumnRuleStyle
== NS_STYLE_BORDER_STYLE_OUTSET
)
73 ruleStyle
= NS_STYLE_BORDER_STYLE_GROOVE
;
75 ruleStyle
= colStyle
->mColumnRuleStyle
;
77 nsPresContext
* presContext
= PresContext();
78 nscoord ruleWidth
= colStyle
->GetComputedColumnRuleWidth();
83 GetVisitedDependentColor(eCSSProperty__moz_column_rule_color
);
85 // In order to re-use a large amount of code, we treat the column rule as a border.
86 // We create a new border style object and fill in all the details of the column rule as
87 // the left border. PaintBorder() does all the rendering for us, so we not
88 // only save an enormous amount of code but we'll support all the line styles that
89 // we support on borders!
90 nsStyleBorder
border(presContext
);
91 border
.SetBorderWidth(NS_SIDE_LEFT
, ruleWidth
);
92 border
.SetBorderStyle(NS_SIDE_LEFT
, ruleStyle
);
93 border
.SetBorderColor(NS_SIDE_LEFT
, ruleColor
);
95 // Get our content rect as an absolute coordinate, not relative to
96 // our parent (which is what the X and Y normally is)
97 nsRect contentRect
= GetContentRect() - GetRect().TopLeft() + aPt
;
98 nsSize
ruleSize(ruleWidth
, contentRect
.height
);
100 while (nextSibling
) {
101 // The frame tree goes RTL in RTL
102 nsIFrame
* leftSibling
= isRTL
? nextSibling
: child
;
103 nsIFrame
* rightSibling
= isRTL
? child
: nextSibling
;
105 // Each child frame's position coordinates is actually relative to this nsColumnSetFrame.
106 // linePt will be at the top-left edge to paint the line.
107 nsPoint edgeOfLeftSibling
= leftSibling
->GetRect().TopRight() + aPt
;
108 nsPoint edgeOfRightSibling
= rightSibling
->GetRect().TopLeft() + aPt
;
109 nsPoint
linePt((edgeOfLeftSibling
.x
+ edgeOfRightSibling
.x
- ruleWidth
) / 2,
112 nsRect
lineRect(linePt
, ruleSize
);
113 // Remember, we only have the "left" "border". Skip everything else.
114 Sides
skipSides(mozilla::eSideBitsTopBottom
);
115 skipSides
|= mozilla::eSideBitsRight
;
116 nsCSSRendering::PaintBorderWithStyleBorder(presContext
, *aCtx
, this,
117 aDirtyRect
, lineRect
, border
, StyleContext(),
121 nextSibling
= nextSibling
->GetNextSibling();
126 GetAvailableContentWidth(const nsHTMLReflowState
& aReflowState
)
128 if (aReflowState
.AvailableWidth() == NS_INTRINSICSIZE
) {
129 return NS_INTRINSICSIZE
;
131 nscoord borderPaddingWidth
=
132 aReflowState
.ComputedPhysicalBorderPadding().left
+
133 aReflowState
.ComputedPhysicalBorderPadding().right
;
134 return std::max(0, aReflowState
.AvailableWidth() - borderPaddingWidth
);
138 nsColumnSetFrame::GetAvailableContentHeight(const nsHTMLReflowState
& aReflowState
)
140 if (aReflowState
.AvailableHeight() == NS_INTRINSICSIZE
) {
141 return NS_INTRINSICSIZE
;
144 nsMargin bp
= aReflowState
.ComputedPhysicalBorderPadding();
145 bp
.ApplySkipSides(GetSkipSides(&aReflowState
));
146 bp
.bottom
= aReflowState
.ComputedPhysicalBorderPadding().bottom
;
147 return std::max(0, aReflowState
.AvailableHeight() - bp
.TopBottom());
151 GetColumnGap(nsColumnSetFrame
* aFrame
,
152 const nsStyleColumn
* aColStyle
)
154 if (eStyleUnit_Normal
== aColStyle
->mColumnGap
.GetUnit())
155 return aFrame
->StyleFont()->mFont
.size
;
156 if (eStyleUnit_Coord
== aColStyle
->mColumnGap
.GetUnit()) {
157 nscoord colGap
= aColStyle
->mColumnGap
.GetCoordValue();
158 NS_ASSERTION(colGap
>= 0, "negative column gap");
162 NS_NOTREACHED("Unknown gap type");
166 nsColumnSetFrame::ReflowConfig
167 nsColumnSetFrame::ChooseColumnStrategy(const nsHTMLReflowState
& aReflowState
,
168 bool aForceAuto
= false,
169 nscoord aFeasibleHeight
= NS_INTRINSICSIZE
,
170 nscoord aInfeasibleHeight
= 0)
173 nscoord knownFeasibleHeight
= aFeasibleHeight
;
174 nscoord knownInfeasibleHeight
= aInfeasibleHeight
;
176 const nsStyleColumn
* colStyle
= StyleColumn();
177 nscoord availContentWidth
= GetAvailableContentWidth(aReflowState
);
178 if (aReflowState
.ComputedWidth() != NS_INTRINSICSIZE
) {
179 availContentWidth
= aReflowState
.ComputedWidth();
182 nscoord consumedBSize
= GetConsumedBSize();
184 // The effective computed height is the height of the current continuation
185 // of the column set frame. This should be the same as the computed height
186 // if we have an unconstrained available height.
187 nscoord computedBSize
= GetEffectiveComputedBSize(aReflowState
,
189 nscoord colHeight
= GetAvailableContentHeight(aReflowState
);
191 if (aReflowState
.ComputedHeight() != NS_INTRINSICSIZE
) {
192 colHeight
= aReflowState
.ComputedHeight();
193 } else if (aReflowState
.ComputedMaxHeight() != NS_INTRINSICSIZE
) {
194 colHeight
= std::min(colHeight
, aReflowState
.ComputedMaxHeight());
197 nscoord colGap
= GetColumnGap(this, colStyle
);
198 int32_t numColumns
= colStyle
->mColumnCount
;
200 // If column-fill is set to 'balance', then we want to balance the columns.
201 const bool isBalancing
= colStyle
->mColumnFill
== NS_STYLE_COLUMN_FILL_BALANCE
204 const uint32_t MAX_NESTED_COLUMN_BALANCING
= 2;
206 for (const nsHTMLReflowState
* rs
= aReflowState
.parentReflowState
;
207 rs
&& cnt
< MAX_NESTED_COLUMN_BALANCING
; rs
= rs
->parentReflowState
) {
208 if (rs
->mFlags
.mIsColumnBalancing
) {
212 if (cnt
== MAX_NESTED_COLUMN_BALANCING
) {
218 if (colStyle
->mColumnWidth
.GetUnit() == eStyleUnit_Coord
) {
219 colWidth
= colStyle
->mColumnWidth
.GetCoordValue();
220 NS_ASSERTION(colWidth
>= 0, "negative column width");
221 // Reduce column count if necessary to make columns fit in the
222 // available width. Compute max number of columns that fit in
223 // availContentWidth, satisfying colGap*(maxColumns - 1) +
224 // colWidth*maxColumns <= availContentWidth
225 if (availContentWidth
!= NS_INTRINSICSIZE
&& colGap
+ colWidth
> 0
227 // This expression uses truncated rounding, which is what we
230 std::min(nscoord(nsStyleColumn::kMaxColumnCount
),
231 (availContentWidth
+ colGap
)/(colGap
+ colWidth
));
232 numColumns
= std::max(1, std::min(numColumns
, maxColumns
));
234 } else if (numColumns
> 0 && availContentWidth
!= NS_INTRINSICSIZE
) {
235 nscoord widthMinusGaps
= availContentWidth
- colGap
*(numColumns
- 1);
236 colWidth
= widthMinusGaps
/numColumns
;
238 colWidth
= NS_INTRINSICSIZE
;
240 // Take care of the situation where there's only one column but it's
242 colWidth
= std::max(1, std::min(colWidth
, availContentWidth
));
244 nscoord expectedWidthLeftOver
= 0;
246 if (colWidth
!= NS_INTRINSICSIZE
&& availContentWidth
!= NS_INTRINSICSIZE
) {
247 // distribute leftover space
249 // First, determine how many columns will be showing if the column
251 if (numColumns
<= 0) {
252 // choose so that colGap*(nominalColumnCount - 1) +
253 // colWidth*nominalColumnCount is nearly availContentWidth
254 // make sure to round down
255 if (colGap
+ colWidth
> 0) {
256 numColumns
= (availContentWidth
+ colGap
)/(colGap
+ colWidth
);
257 // The number of columns should never exceed kMaxColumnCount.
258 numColumns
= std::min(nscoord(nsStyleColumn::kMaxColumnCount
),
261 if (numColumns
<= 0) {
266 // Compute extra space and divide it among the columns
268 std::max(0, availContentWidth
- (colWidth
*numColumns
+ colGap
*(numColumns
- 1)));
269 nscoord extraToColumns
= extraSpace
/numColumns
;
270 colWidth
+= extraToColumns
;
271 expectedWidthLeftOver
= extraSpace
- (extraToColumns
*numColumns
);
275 if (numColumns
<= 0) {
276 // Hmm, auto column count, column width or available width is unknown,
277 // and balancing is required. Let's just use one column then.
280 colHeight
= std::min(mLastBalanceHeight
, colHeight
);
282 // This is the case when the column-fill property is set to 'auto'.
283 // No balancing, so don't limit the column count
284 numColumns
= INT32_MAX
;
286 // XXX_jwir3: If a page's height is set to 0, we could continually
287 // create continuations, resulting in an infinite loop, since
288 // no progress is ever made. This is an issue with the spec
289 // (css3-multicol, css3-page, and css3-break) that is
290 // unresolved as of 27 Feb 2013. For the time being, we set this
291 // to have a minimum of 1 css px. Once a resolution is made
292 // on what minimum to have for a page height, we may need to
293 // change this value to match the appropriate spec(s).
294 colHeight
= std::max(colHeight
, nsPresContext::CSSPixelsToAppUnits(1));
298 printf("*** nsColumnSetFrame::ChooseColumnStrategy: numColumns=%d, colWidth=%d, expectedWidthLeftOver=%d, colHeight=%d, colGap=%d\n",
299 numColumns
, colWidth
, expectedWidthLeftOver
, colHeight
, colGap
);
301 ReflowConfig config
= { numColumns
, colWidth
, expectedWidthLeftOver
, colGap
,
302 colHeight
, isBalancing
, knownFeasibleHeight
,
303 knownInfeasibleHeight
, computedBSize
, consumedBSize
};
308 nsColumnSetFrame::ReflowColumns(nsHTMLReflowMetrics
& aDesiredSize
,
309 const nsHTMLReflowState
& aReflowState
,
310 nsReflowStatus
& aReflowStatus
,
311 ReflowConfig
& aConfig
,
312 bool aLastColumnUnbounded
,
313 nsCollapsingMargin
* aCarriedOutBottomMargin
,
314 ColumnBalanceData
& aColData
)
316 bool feasible
= ReflowChildren(aDesiredSize
, aReflowState
,
317 aReflowStatus
, aConfig
, aLastColumnUnbounded
,
318 aCarriedOutBottomMargin
, aColData
);
320 if (aColData
.mHasExcessHeight
) {
321 aConfig
= ChooseColumnStrategy(aReflowState
, true);
323 // We need to reflow our children again one last time, otherwise we might
324 // end up with a stale column height for some of our columns, since we
325 // bailed out of balancing.
326 feasible
= ReflowChildren(aDesiredSize
, aReflowState
, aReflowStatus
,
327 aConfig
, aLastColumnUnbounded
,
328 aCarriedOutBottomMargin
, aColData
);
334 static void MoveChildTo(nsIFrame
* aParent
, nsIFrame
* aChild
, nsPoint aOrigin
) {
335 if (aChild
->GetPosition() == aOrigin
) {
339 aChild
->SetPosition(aOrigin
);
340 nsContainerFrame::PlaceFrameView(aChild
);
344 nsColumnSetFrame::GetMinISize(nsRenderingContext
*aRenderingContext
) {
346 DISPLAY_MIN_WIDTH(this, width
);
347 if (mFrames
.FirstChild()) {
348 width
= mFrames
.FirstChild()->GetMinISize(aRenderingContext
);
350 const nsStyleColumn
* colStyle
= StyleColumn();
352 if (colStyle
->mColumnWidth
.GetUnit() == eStyleUnit_Coord
) {
353 colWidth
= colStyle
->mColumnWidth
.GetCoordValue();
354 // As available width reduces to zero, we reduce our number of columns
355 // to one, and don't enforce the column width, so just return the min
356 // of the child's min-width with any specified column width.
357 width
= std::min(width
, colWidth
);
359 NS_ASSERTION(colStyle
->mColumnCount
> 0,
360 "column-count and column-width can't both be auto");
361 // As available width reduces to zero, we still have mColumnCount columns,
362 // so multiply the child's min-width by the number of columns.
364 width
*= colStyle
->mColumnCount
;
365 // The multiplication above can make 'width' negative (integer overflow),
366 // so use std::max to protect against that.
367 width
= std::max(width
, colWidth
);
369 // XXX count forced column breaks here? Maybe we should return the child's
370 // min-width times the minimum number of columns.
375 nsColumnSetFrame::GetPrefISize(nsRenderingContext
*aRenderingContext
) {
376 // Our preferred width is our desired column width, if specified, otherwise
377 // the child's preferred width, times the number of columns, plus the width
378 // of any required column gaps
379 // XXX what about forced column breaks here?
381 DISPLAY_PREF_WIDTH(this, result
);
382 const nsStyleColumn
* colStyle
= StyleColumn();
383 nscoord colGap
= GetColumnGap(this, colStyle
);
386 if (colStyle
->mColumnWidth
.GetUnit() == eStyleUnit_Coord
) {
387 colWidth
= colStyle
->mColumnWidth
.GetCoordValue();
388 } else if (mFrames
.FirstChild()) {
389 colWidth
= mFrames
.FirstChild()->GetPrefISize(aRenderingContext
);
394 int32_t numColumns
= colStyle
->mColumnCount
;
395 if (numColumns
<= 0) {
396 // if column-count is auto, assume one column
400 nscoord width
= colWidth
*numColumns
+ colGap
*(numColumns
- 1);
401 // The multiplication above can make 'width' negative (integer overflow),
402 // so use std::max to protect against that.
403 result
= std::max(width
, colWidth
);
408 nsColumnSetFrame::ReflowChildren(nsHTMLReflowMetrics
& aDesiredSize
,
409 const nsHTMLReflowState
& aReflowState
,
410 nsReflowStatus
& aStatus
,
411 const ReflowConfig
& aConfig
,
412 bool aUnboundedLastColumn
,
413 nsCollapsingMargin
* aBottomMarginCarriedOut
,
414 ColumnBalanceData
& aColData
)
418 bool RTL
= StyleVisibility()->mDirection
== NS_STYLE_DIRECTION_RTL
;
419 bool shrinkingHeightOnly
= !NS_SUBTREE_DIRTY(this) &&
420 mLastBalanceHeight
> aConfig
.mColMaxHeight
;
423 printf("*** Doing column reflow pass: mLastBalanceHeight=%d, mColMaxHeight=%d, RTL=%d\n, mBalanceColCount=%d, mColWidth=%d, mColGap=%d\n",
424 mLastBalanceHeight
, aConfig
.mColMaxHeight
, RTL
, aConfig
.mBalanceColCount
,
425 aConfig
.mColWidth
, aConfig
.mColGap
);
428 DrainOverflowColumns();
430 const bool colHeightChanged
= mLastBalanceHeight
!= aConfig
.mColMaxHeight
;
432 if (colHeightChanged
) {
433 mLastBalanceHeight
= aConfig
.mColMaxHeight
;
434 // XXX Seems like this could fire if incremental reflow pushed the column set
435 // down so we reflow incrementally with a different available height.
436 // We need a way to do an incremental reflow and be sure availableHeight
437 // changes are taken account of! Right now I think block frames with absolute
438 // children might exit early.
439 //NS_ASSERTION(aKidReason != eReflowReason_Incremental,
440 // "incremental reflow should not have changed the balance height");
443 // get our border and padding
444 nsMargin borderPadding
= aReflowState
.ComputedPhysicalBorderPadding();
445 borderPadding
.ApplySkipSides(GetSkipSides(&aReflowState
));
447 nsRect
contentRect(0, 0, 0, 0);
448 nsOverflowAreas overflowRects
;
450 nsIFrame
* child
= mFrames
.FirstChild();
451 nsPoint childOrigin
= nsPoint(borderPadding
.left
, borderPadding
.top
);
452 // For RTL, figure out where the last column's left edge should be. Since the
453 // columns might not fill the frame exactly, we need to account for the
454 // slop. Otherwise we'll waste time moving the columns by some tiny
455 // amount unnecessarily.
457 nscoord availWidth
= aReflowState
.AvailableWidth();
458 if (aReflowState
.ComputedWidth() != NS_INTRINSICSIZE
) {
459 availWidth
= aReflowState
.ComputedWidth();
461 if (availWidth
!= NS_INTRINSICSIZE
) {
462 childOrigin
.x
+= availWidth
- aConfig
.mColWidth
;
464 printf("*** childOrigin.x = %d\n", childOrigin
.x
);
470 bool reflowNext
= false;
473 // Try to skip reflowing the child. We can't skip if the child is dirty. We also can't
474 // skip if the next column is dirty, because the next column's first line(s)
475 // might be pullable back to this column. We can't skip if it's the last child
476 // because we need to obtain the bottom margin. We can't skip
477 // if this is the last column and we're supposed to assign unbounded
478 // height to it, because that could change the available height from
479 // the last time we reflowed it and we should try to pull all the
480 // content from its next sibling. (Note that it might be the last
481 // column, but not be the last child because the desired number of columns
483 bool skipIncremental
= !aReflowState
.ShouldReflowAllKids()
484 && !NS_SUBTREE_DIRTY(child
)
485 && child
->GetNextSibling()
486 && !(aUnboundedLastColumn
&& columnCount
== aConfig
.mBalanceColCount
- 1)
487 && !NS_SUBTREE_DIRTY(child
->GetNextSibling());
488 // If we need to pull up content from the prev-in-flow then this is not just
489 // a height shrink. The prev in flow will have set the dirty bit.
490 // Check the overflow rect YMost instead of just the child's content height. The child
491 // may have overflowing content that cares about the available height boundary.
492 // (It may also have overflowing content that doesn't care about the available height
493 // boundary, but if so, too bad, this optimization is defeated.)
494 // We want scrollable overflow here since this is a calculation that
496 bool skipResizeHeightShrink
= shrinkingHeightOnly
497 && child
->GetScrollableOverflowRect().YMost() <= aConfig
.mColMaxHeight
;
499 nscoord childContentBEnd
= 0;
500 WritingMode wm
= child
->GetWritingMode();
501 if (!reflowNext
&& (skipIncremental
|| skipResizeHeightShrink
)) {
502 // This child does not need to be reflowed, but we may need to move it
503 MoveChildTo(this, child
, childOrigin
);
505 // If this is the last frame then make sure we get the right status
506 nsIFrame
* kidNext
= child
->GetNextSibling();
508 aStatus
= (kidNext
->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER
)
509 ? NS_FRAME_OVERFLOW_INCOMPLETE
510 : NS_FRAME_NOT_COMPLETE
;
512 aStatus
= mLastFrameStatus
;
514 childContentBEnd
= nsLayoutUtils::CalculateContentBEnd(wm
, child
);
516 printf("*** Skipping child #%d %p (incremental %d, resize height shrink %d): status = %d\n",
517 columnCount
, (void*)child
, skipIncremental
, skipResizeHeightShrink
, aStatus
);
520 nsSize
physicalSize(aConfig
.mColWidth
, aConfig
.mColMaxHeight
);
522 if (aUnboundedLastColumn
&& columnCount
== aConfig
.mBalanceColCount
- 1) {
523 physicalSize
.height
= GetAvailableContentHeight(aReflowState
);
525 LogicalSize
availSize(wm
, physicalSize
);
526 LogicalSize computedSize
= aReflowState
.ComputedSize(wm
);
529 child
->AddStateBits(NS_FRAME_IS_DIRTY
);
531 nsHTMLReflowState
kidReflowState(PresContext(), aReflowState
, child
,
532 availSize
, availSize
.ISize(wm
),
533 computedSize
.BSize(wm
));
534 kidReflowState
.mFlags
.mIsTopOfPage
= true;
535 kidReflowState
.mFlags
.mTableIsSplittable
= false;
536 kidReflowState
.mFlags
.mIsColumnBalancing
= aConfig
.mBalanceColCount
< INT32_MAX
;
538 // We need to reflow any float placeholders, even if our column height
540 kidReflowState
.mFlags
.mMustReflowPlaceholders
= !colHeightChanged
;
543 printf("*** Reflowing child #%d %p: availHeight=%d\n",
544 columnCount
, (void*)child
,availSize
.BSize(wm
));
547 // Note if the column's next in flow is not being changed by this incremental reflow.
548 // This may allow the current column to avoid trying to pull lines from the next column.
549 if (child
->GetNextSibling() &&
550 !(GetStateBits() & NS_FRAME_IS_DIRTY
) &&
551 !(child
->GetNextSibling()->GetStateBits() & NS_FRAME_IS_DIRTY
)) {
552 kidReflowState
.mFlags
.mNextInFlowUntouched
= true;
555 nsHTMLReflowMetrics
kidDesiredSize(wm
, aDesiredSize
.mFlags
);
557 // XXX it would be cool to consult the float manager for the
558 // previous block to figure out the region of floats from the
559 // previous column that extend into this column, and subtract
560 // that region from the new float manager. So you could stick a
561 // really big float in the first column and text in following
562 // columns would flow around it.
565 ReflowChild(child
, PresContext(), kidDesiredSize
, kidReflowState
,
566 childOrigin
.x
+ kidReflowState
.ComputedPhysicalMargin().left
,
567 childOrigin
.y
+ kidReflowState
.ComputedPhysicalMargin().top
,
570 reflowNext
= (aStatus
& NS_FRAME_REFLOW_NEXTINFLOW
) != 0;
573 printf("*** Reflowed child #%d %p: status = %d, desiredSize=%d,%d CarriedOutBottomMargin=%d\n",
574 columnCount
, (void*)child
, aStatus
, kidDesiredSize
.Width(), kidDesiredSize
.Height(),
575 kidDesiredSize
.mCarriedOutBottomMargin
.get());
578 NS_FRAME_TRACE_REFLOW_OUT("Column::Reflow", aStatus
);
580 *aBottomMarginCarriedOut
= kidDesiredSize
.mCarriedOutBottomMargin
;
582 FinishReflowChild(child
, PresContext(), kidDesiredSize
,
583 &kidReflowState
, childOrigin
.x
, childOrigin
.y
, 0);
585 childContentBEnd
= nsLayoutUtils::CalculateContentBEnd(wm
, child
);
586 if (childContentBEnd
> aConfig
.mColMaxHeight
) {
589 if (childContentBEnd
> availSize
.BSize(wm
)) {
590 aColData
.mMaxOverflowingHeight
= std::max(childContentBEnd
,
591 aColData
.mMaxOverflowingHeight
);
595 contentRect
.UnionRect(contentRect
, child
->GetRect());
597 ConsiderChildOverflow(overflowRects
, child
);
598 contentBEnd
= std::max(contentBEnd
, childContentBEnd
);
599 aColData
.mLastHeight
= childContentBEnd
;
600 aColData
.mSumHeight
+= childContentBEnd
;
602 // Build a continuation column if necessary
603 nsIFrame
* kidNextInFlow
= child
->GetNextInFlow();
605 if (NS_FRAME_IS_FULLY_COMPLETE(aStatus
) && !NS_FRAME_IS_TRUNCATED(aStatus
)) {
606 NS_ASSERTION(!kidNextInFlow
, "next in flow should have been deleted");
611 // Make sure that the column has a next-in-flow. If not, we must
612 // create one to hold the overflowing stuff, even if we're just
613 // going to put it on our overflow list and let *our*
614 // next in flow handle it.
615 if (!kidNextInFlow
) {
616 NS_ASSERTION(aStatus
& NS_FRAME_REFLOW_NEXTINFLOW
,
617 "We have to create a continuation, but the block doesn't want us to reflow it?");
619 // We need to create a continuing column
620 nsresult rv
= CreateNextInFlow(child
, kidNextInFlow
);
623 NS_NOTREACHED("Couldn't create continuation");
629 // Make sure we reflow a next-in-flow when it switches between being
630 // normal or overflow container
631 if (NS_FRAME_OVERFLOW_IS_INCOMPLETE(aStatus
)) {
632 if (!(kidNextInFlow
->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER
)) {
633 aStatus
|= NS_FRAME_REFLOW_NEXTINFLOW
;
635 kidNextInFlow
->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER
);
638 else if (kidNextInFlow
->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER
) {
639 aStatus
|= NS_FRAME_REFLOW_NEXTINFLOW
;
641 kidNextInFlow
->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER
);
644 if ((contentBEnd
> aReflowState
.ComputedMaxBSize() ||
645 contentBEnd
> aReflowState
.ComputedBSize()) &&
646 aConfig
.mBalanceColCount
< INT32_MAX
) {
647 // We overflowed vertically, but have not exceeded the number of
648 // columns. We're going to go into overflow columns now, so balancing
649 // no longer applies.
650 aColData
.mHasExcessHeight
= true;
653 if (columnCount
>= aConfig
.mBalanceColCount
) {
654 // No more columns allowed here. Stop.
655 aStatus
|= NS_FRAME_REFLOW_NEXTINFLOW
;
656 kidNextInFlow
->AddStateBits(NS_FRAME_IS_DIRTY
);
657 // Move any of our leftover columns to our overflow list. Our
658 // next-in-flow will eventually pick them up.
659 const nsFrameList
& continuationColumns
= mFrames
.RemoveFramesAfter(child
);
660 if (continuationColumns
.NotEmpty()) {
661 SetOverflowFrames(continuationColumns
);
668 if (PresContext()->HasPendingInterrupt()) {
669 // Stop the loop now while |child| still points to the frame that bailed
670 // out. We could keep going here and condition a bunch of the code in
671 // this loop on whether there's an interrupt, or even just keep going and
672 // trying to reflow the blocks (even though we know they'll interrupt
673 // right after their first line), but stopping now is conceptually the
674 // simplest (and probably fastest) thing.
678 // Advance to the next column
679 child
= child
->GetNextSibling();
683 childOrigin
.x
+= aConfig
.mColWidth
+ aConfig
.mColGap
;
685 childOrigin
.x
-= aConfig
.mColWidth
+ aConfig
.mColGap
;
689 printf("*** NEXT CHILD ORIGIN.x = %d\n", childOrigin
.x
);
694 if (PresContext()->CheckForInterrupt(this) &&
695 (GetStateBits() & NS_FRAME_IS_DIRTY
)) {
696 // Mark all our kids starting with |child| dirty
698 // Note that this is a CheckForInterrupt call, not a HasPendingInterrupt,
699 // because we might have interrupted while reflowing |child|, and since
700 // we're about to add a dirty bit to |child| we need to make sure that
701 // |this| is scheduled to have dirty bits marked on it and its ancestors.
702 // Otherwise, when we go to mark dirty bits on |child|'s ancestors we'll
703 // bail out immediately, since it'll already have a dirty bit.
704 for (; child
; child
= child
->GetNextSibling()) {
705 child
->AddStateBits(NS_FRAME_IS_DIRTY
);
709 aColData
.mMaxHeight
= contentBEnd
;
710 contentRect
.height
= std::max(contentRect
.height
, contentBEnd
);
711 mLastFrameStatus
= aStatus
;
713 // contentRect included the borderPadding.left,borderPadding.top of the child rects
714 contentRect
-= nsPoint(borderPadding
.left
, borderPadding
.top
);
716 WritingMode wm
= aReflowState
.GetWritingMode();
717 LogicalSize
contentSize(wm
, nsSize(contentRect
.XMost(), contentRect
.YMost()));
719 // Apply computed and min/max values
720 // (aConfig members need to be converted from Width/Height to ISize/BSize)
721 if (aConfig
.mComputedHeight
!= NS_INTRINSICSIZE
) {
722 if (aReflowState
.AvailableHeight() != NS_INTRINSICSIZE
) {
723 contentSize
.BSize(wm
) = std::min(contentSize
.BSize(wm
),
724 aConfig
.mComputedHeight
);
726 contentSize
.BSize(wm
) = aConfig
.mComputedHeight
;
729 // We add the "consumed" height back in so that we're applying
730 // constraints to the correct height value, then subtract it again
731 // after we've finished with the min/max calculation. This prevents us from
732 // having a last continuation that is smaller than the min height. but which
733 // has prev-in-flows, trigger a larger height than actually required.
734 contentSize
.BSize(wm
) =
735 aReflowState
.ApplyMinMaxHeight(contentSize
.BSize(wm
),
736 aConfig
.mConsumedHeight
);
738 if (aReflowState
.ComputedISize() != NS_INTRINSICSIZE
) {
739 contentSize
.ISize(wm
) = aReflowState
.ComputedISize();
741 contentSize
.ISize(wm
) =
742 aReflowState
.ApplyMinMaxWidth(contentSize
.ISize(wm
));
745 LogicalMargin
bp(wm
, borderPadding
);
746 contentSize
.ISize(wm
) += bp
.IStartEnd(wm
);
747 contentSize
.BSize(wm
) += bp
.BStartEnd(wm
);
748 aDesiredSize
.SetSize(wm
, contentSize
);
749 aDesiredSize
.mOverflowAreas
= overflowRects
;
750 aDesiredSize
.UnionOverflowAreasWithDesiredBounds();
753 printf("*** DONE PASS feasible=%d\n", allFit
&& NS_FRAME_IS_FULLY_COMPLETE(aStatus
)
754 && !NS_FRAME_IS_TRUNCATED(aStatus
));
756 return allFit
&& NS_FRAME_IS_FULLY_COMPLETE(aStatus
)
757 && !NS_FRAME_IS_TRUNCATED(aStatus
);
761 nsColumnSetFrame::DrainOverflowColumns()
763 // First grab the prev-in-flows overflows and reparent them to this
765 nsPresContext
* presContext
= PresContext();
766 nsColumnSetFrame
* prev
= static_cast<nsColumnSetFrame
*>(GetPrevInFlow());
768 AutoFrameListPtr
overflows(presContext
, prev
->StealOverflowFrames());
770 nsContainerFrame::ReparentFrameViewList(*overflows
, prev
, this);
772 mFrames
.InsertFrames(this, nullptr, *overflows
);
776 // Now pull back our own overflows and append them to our children.
777 // We don't need to reparent them since we're already their parent.
778 AutoFrameListPtr
overflows(presContext
, StealOverflowFrames());
780 // We're already the parent for these frames, so no need to set
781 // their parent again.
782 mFrames
.AppendFrames(nullptr, *overflows
);
787 nsColumnSetFrame::FindBestBalanceHeight(const nsHTMLReflowState
& aReflowState
,
788 nsPresContext
* aPresContext
,
789 ReflowConfig
& aConfig
,
790 ColumnBalanceData
& aColData
,
791 nsHTMLReflowMetrics
& aDesiredSize
,
792 nsCollapsingMargin
& aOutMargin
,
793 bool& aUnboundedLastColumn
,
794 bool& aRunWasFeasible
,
795 nsReflowStatus
& aStatus
)
797 bool feasible
= aRunWasFeasible
;
799 nsMargin bp
= aReflowState
.ComputedPhysicalBorderPadding();
800 bp
.ApplySkipSides(GetSkipSides());
801 bp
.bottom
= aReflowState
.ComputedPhysicalBorderPadding().bottom
;
803 nscoord availableContentHeight
=
804 GetAvailableContentHeight(aReflowState
);
806 // Termination of the algorithm below is guaranteed because
807 // aConfig.knownFeasibleHeight - aConfig.knownInfeasibleHeight decreases in every
810 // We set this flag when we detect that we may contain a frame
811 // that can break anywhere (thus foiling the linear decrease-by-one
813 bool maybeContinuousBreakingDetected
= false;
815 while (!aPresContext
->HasPendingInterrupt()) {
816 nscoord lastKnownFeasibleHeight
= aConfig
.mKnownFeasibleHeight
;
818 // Record what we learned from the last reflow
820 // maxHeight is feasible. Also, mLastBalanceHeight is feasible.
821 aConfig
.mKnownFeasibleHeight
= std::min(aConfig
.mKnownFeasibleHeight
,
822 aColData
.mMaxHeight
);
823 aConfig
.mKnownFeasibleHeight
= std::min(aConfig
.mKnownFeasibleHeight
,
826 // Furthermore, no height less than the height of the last
827 // column can ever be feasible. (We might be able to reduce the
828 // height of a non-last column by moving content to a later column,
829 // but we can't do that with the last column.)
830 if (mFrames
.GetLength() == aConfig
.mBalanceColCount
) {
831 aConfig
.mKnownInfeasibleHeight
= std::max(aConfig
.mKnownInfeasibleHeight
,
832 aColData
.mLastHeight
- 1);
835 aConfig
.mKnownInfeasibleHeight
= std::max(aConfig
.mKnownInfeasibleHeight
,
837 // If a column didn't fit in its available height, then its current
838 // height must be the minimum height for unbreakable content in
839 // the column, and therefore no smaller height can be feasible.
840 aConfig
.mKnownInfeasibleHeight
= std::max(aConfig
.mKnownInfeasibleHeight
,
841 aColData
.mMaxOverflowingHeight
- 1);
843 if (aUnboundedLastColumn
) {
844 // The last column is unbounded, so all content got reflowed, so the
845 // mColMaxHeight is feasible.
846 aConfig
.mKnownFeasibleHeight
= std::min(aConfig
.mKnownFeasibleHeight
,
847 aColData
.mMaxHeight
);
852 printf("*** nsColumnSetFrame::Reflow balancing knownInfeasible=%d knownFeasible=%d\n",
853 aConfig
.mKnownInfeasibleHeight
, aConfig
.mKnownFeasibleHeight
);
857 if (aConfig
.mKnownInfeasibleHeight
>= aConfig
.mKnownFeasibleHeight
- 1) {
858 // aConfig.mKnownFeasibleHeight is where we want to be
862 if (aConfig
.mKnownInfeasibleHeight
>= availableContentHeight
) {
866 if (lastKnownFeasibleHeight
- aConfig
.mKnownFeasibleHeight
== 1) {
867 // We decreased the feasible height by one twip only. This could
868 // indicate that there is a continuously breakable child frame
869 // that we are crawling through.
870 maybeContinuousBreakingDetected
= true;
873 nscoord nextGuess
= (aConfig
.mKnownFeasibleHeight
+ aConfig
.mKnownInfeasibleHeight
)/2;
874 // The constant of 600 twips is arbitrary. It's about two line-heights.
875 if (aConfig
.mKnownFeasibleHeight
- nextGuess
< 600 &&
876 !maybeContinuousBreakingDetected
) {
877 // We're close to our target, so just try shrinking just the
878 // minimum amount that will cause one of our columns to break
880 nextGuess
= aConfig
.mKnownFeasibleHeight
- 1;
881 } else if (aUnboundedLastColumn
) {
882 // Make a guess by dividing that into N columns. Add some slop
883 // to try to make it on the feasible side. The constant of
884 // 600 twips is arbitrary. It's about two line-heights.
885 nextGuess
= aColData
.mSumHeight
/aConfig
.mBalanceColCount
+ 600;
887 nextGuess
= clamped(nextGuess
, aConfig
.mKnownInfeasibleHeight
+ 1,
888 aConfig
.mKnownFeasibleHeight
- 1);
889 } else if (aConfig
.mKnownFeasibleHeight
== NS_INTRINSICSIZE
) {
890 // This can happen when we had a next-in-flow so we didn't
891 // want to do an unbounded height measuring step. Let's just increase
892 // from the infeasible height by some reasonable amount.
893 nextGuess
= aConfig
.mKnownInfeasibleHeight
*2 + 600;
895 // Don't bother guessing more than our height constraint.
896 nextGuess
= std::min(availableContentHeight
, nextGuess
);
899 printf("*** nsColumnSetFrame::Reflow balancing choosing next guess=%d\n", nextGuess
);
902 aConfig
.mColMaxHeight
= nextGuess
;
904 aUnboundedLastColumn
= false;
905 AddStateBits(NS_FRAME_IS_DIRTY
);
906 feasible
= ReflowColumns(aDesiredSize
, aReflowState
, aStatus
, aConfig
, false,
907 &aOutMargin
, aColData
);
909 if (!aConfig
.mIsBalancing
) {
910 // Looks like we had excess height when balancing, so we gave up on
911 // trying to balance.
916 if (aConfig
.mIsBalancing
&& !feasible
&&
917 !aPresContext
->HasPendingInterrupt()) {
918 // We may need to reflow one more time at the feasible height to
919 // get a valid layout.
921 if (aConfig
.mKnownInfeasibleHeight
>= availableContentHeight
) {
922 aConfig
.mColMaxHeight
= availableContentHeight
;
923 if (mLastBalanceHeight
== availableContentHeight
) {
927 aConfig
.mColMaxHeight
= aConfig
.mKnownFeasibleHeight
;
930 // If our height is unconstrained, make sure that the last column is
931 // allowed to have arbitrary height here, even though we were balancing.
932 // Otherwise we'd have to split, and it's not clear what we'd do with
934 AddStateBits(NS_FRAME_IS_DIRTY
);
935 feasible
= ReflowColumns(aDesiredSize
, aReflowState
, aStatus
, aConfig
,
936 availableContentHeight
== NS_UNCONSTRAINEDSIZE
,
937 &aOutMargin
, aColData
);
941 aRunWasFeasible
= feasible
;
945 nsColumnSetFrame::Reflow(nsPresContext
* aPresContext
,
946 nsHTMLReflowMetrics
& aDesiredSize
,
947 const nsHTMLReflowState
& aReflowState
,
948 nsReflowStatus
& aStatus
)
950 // Don't support interruption in columns
951 nsPresContext::InterruptPreventer
noInterrupts(aPresContext
);
953 DO_GLOBAL_REFLOW_COUNT("nsColumnSetFrame");
954 DISPLAY_REFLOW(aPresContext
, this, aReflowState
, aDesiredSize
, aStatus
);
956 // Initialize OUT parameter
957 aStatus
= NS_FRAME_COMPLETE
;
959 // Our children depend on our height if we have a fixed height.
960 if (aReflowState
.ComputedHeight() != NS_AUTOHEIGHT
) {
961 NS_ASSERTION(aReflowState
.ComputedHeight() != NS_INTRINSICSIZE
,
962 "Unexpected computed height");
963 AddStateBits(NS_FRAME_CONTAINS_RELATIVE_HEIGHT
);
966 RemoveStateBits(NS_FRAME_CONTAINS_RELATIVE_HEIGHT
);
970 nsFrameList::Enumerator
oc(GetChildList(kOverflowContainersList
));
971 for (; !oc
.AtEnd(); oc
.Next()) {
972 MOZ_ASSERT(!IS_TRUE_OVERFLOW_CONTAINER(oc
.get()));
974 nsFrameList::Enumerator
eoc(GetChildList(kExcessOverflowContainersList
));
975 for (; !eoc
.AtEnd(); eoc
.Next()) {
976 MOZ_ASSERT(!IS_TRUE_OVERFLOW_CONTAINER(eoc
.get()));
980 nsOverflowAreas ocBounds
;
981 nsReflowStatus ocStatus
= NS_FRAME_COMPLETE
;
982 if (GetPrevInFlow()) {
983 ReflowOverflowContainerChildren(aPresContext
, aReflowState
, ocBounds
, 0,
987 //------------ Handle Incremental Reflow -----------------
989 ReflowConfig config
= ChooseColumnStrategy(aReflowState
);
991 // If balancing, then we allow the last column to grow to unbounded
992 // height during the first reflow. This gives us a way to estimate
993 // what the average column height should be, because we can measure
994 // the heights of all the columns and sum them up. But don't do this
995 // if we have a next in flow because we don't want to suck all its
996 // content back here and then have to push it out again!
997 nsIFrame
* nextInFlow
= GetNextInFlow();
998 bool unboundedLastColumn
= config
.mIsBalancing
&& !nextInFlow
;
999 nsCollapsingMargin carriedOutBottomMargin
;
1000 ColumnBalanceData colData
;
1001 colData
.mHasExcessHeight
= false;
1003 bool feasible
= ReflowColumns(aDesiredSize
, aReflowState
, aStatus
, config
,
1004 unboundedLastColumn
, &carriedOutBottomMargin
,
1007 // If we're not balancing, then we're already done, since we should have
1008 // reflown all of our children, and there is no need for a binary search to
1009 // determine proper column height.
1010 if (config
.mIsBalancing
&& !aPresContext
->HasPendingInterrupt()) {
1011 FindBestBalanceHeight(aReflowState
, aPresContext
, config
, colData
,
1012 aDesiredSize
, carriedOutBottomMargin
,
1013 unboundedLastColumn
, feasible
, aStatus
);
1016 if (aPresContext
->HasPendingInterrupt() &&
1017 aReflowState
.AvailableHeight() == NS_UNCONSTRAINEDSIZE
) {
1018 // In this situation, we might be lying about our reflow status, because
1019 // our last kid (the one that got interrupted) was incomplete. Fix that.
1020 aStatus
= NS_FRAME_COMPLETE
;
1023 NS_ASSERTION(NS_FRAME_IS_FULLY_COMPLETE(aStatus
) ||
1024 aReflowState
.AvailableHeight() != NS_UNCONSTRAINEDSIZE
,
1025 "Column set should be complete if the available height is unconstrained");
1027 // Merge overflow container bounds and status.
1028 aDesiredSize
.mOverflowAreas
.UnionWith(ocBounds
);
1029 NS_MergeReflowStatusInto(&aStatus
, ocStatus
);
1031 FinishReflowWithAbsoluteFrames(aPresContext
, aDesiredSize
, aReflowState
, aStatus
, false);
1033 aDesiredSize
.mCarriedOutBottomMargin
= carriedOutBottomMargin
;
1035 NS_FRAME_SET_TRUNCATION(aStatus
, aReflowState
, aDesiredSize
);
1039 nsColumnSetFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
1040 const nsRect
& aDirtyRect
,
1041 const nsDisplayListSet
& aLists
) {
1042 DisplayBorderBackgroundOutline(aBuilder
, aLists
);
1044 if (IsVisibleForPainting(aBuilder
)) {
1045 aLists
.BorderBackground()->AppendNewToTop(new (aBuilder
)
1046 nsDisplayGenericOverflow(aBuilder
, this, ::PaintColumnRule
, "ColumnRule",
1047 nsDisplayItem::TYPE_COLUMN_RULE
));
1050 // Our children won't have backgrounds so it doesn't matter where we put them.
1051 for (nsFrameList::Enumerator
e(mFrames
); !e
.AtEnd(); e
.Next()) {
1052 BuildDisplayListForChild(aBuilder
, e
.get(), aDirtyRect
, aLists
);
1058 nsColumnSetFrame::SetInitialChildList(ChildListID aListID
,
1059 nsFrameList
& aChildList
)
1061 MOZ_ASSERT(aListID
== kPrincipalList
, "unexpected child list");
1062 MOZ_ASSERT(aChildList
.OnlyChild(),
1063 "initial child list must have exactly one child");
1064 nsContainerFrame::SetInitialChildList(kPrincipalList
, aChildList
);
1068 nsColumnSetFrame::AppendFrames(ChildListID aListID
,
1069 nsFrameList
& aFrameList
)
1071 MOZ_CRASH("unsupported operation");
1075 nsColumnSetFrame::InsertFrames(ChildListID aListID
,
1076 nsIFrame
* aPrevFrame
,
1077 nsFrameList
& aFrameList
)
1079 MOZ_CRASH("unsupported operation");
1083 nsColumnSetFrame::RemoveFrame(ChildListID aListID
,
1084 nsIFrame
* aOldFrame
)
1086 MOZ_CRASH("unsupported operation");