Bumping manifests a=b2g-bump
[gecko.git] / layout / generic / nsColumnSetFrame.cpp
blob05cf5c57457a50feef2fa677dd28acfd1450c507
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;
15 /**
16 * Tracking issues:
18 * XXX cursor movement around the top and bottom of colums seems to make the editor
19 * lose the caret.
21 * XXX should we support CSS columns applied to table elements?
23 nsContainerFrame*
24 NS_NewColumnSetFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, nsFrameState aStateFlags)
26 nsColumnSetFrame* it = new (aPresShell) nsColumnSetFrame(aContext);
27 it->AddStateBits(aStateFlags | NS_BLOCK_MARGIN_ROOT);
28 return it;
31 NS_IMPL_FRAMEARENA_HELPERS(nsColumnSetFrame)
33 nsColumnSetFrame::nsColumnSetFrame(nsStyleContext* aContext)
34 : nsContainerFrame(aContext), mLastBalanceHeight(NS_INTRINSICSIZE),
35 mLastFrameStatus(NS_FRAME_COMPLETE)
39 nsIAtom*
40 nsColumnSetFrame::GetType() const
42 return nsGkAtoms::columnSetFrame;
45 static void
46 PaintColumnRule(nsIFrame* aFrame, nsRenderingContext* aCtx,
47 const nsRect& aDirtyRect, nsPoint aPt)
49 static_cast<nsColumnSetFrame*>(aFrame)->PaintColumnRule(aCtx, aDirtyRect, aPt);
52 void
53 nsColumnSetFrame::PaintColumnRule(nsRenderingContext* aCtx,
54 const nsRect& aDirtyRect,
55 const nsPoint& aPt)
57 nsIFrame* child = mFrames.FirstChild();
58 if (!child)
59 return; // no columns
61 nsIFrame* nextSibling = child->GetNextSibling();
62 if (!nextSibling)
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();
68 uint8_t ruleStyle;
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;
74 else
75 ruleStyle = colStyle->mColumnRuleStyle;
77 nsPresContext* presContext = PresContext();
78 nscoord ruleWidth = colStyle->GetComputedColumnRuleWidth();
79 if (!ruleWidth)
80 return;
82 nscolor ruleColor =
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,
110 contentRect.y);
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(),
118 skipSides);
120 child = nextSibling;
121 nextSibling = nextSibling->GetNextSibling();
125 static nscoord
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);
137 nscoord
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());
150 static nscoord
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");
159 return colGap;
162 NS_NOTREACHED("Unknown gap type");
163 return 0;
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,
188 consumedBSize);
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
202 && !aForceAuto;
203 if (isBalancing) {
204 const uint32_t MAX_NESTED_COLUMN_BALANCING = 2;
205 uint32_t cnt = 0;
206 for (const nsHTMLReflowState* rs = aReflowState.parentReflowState;
207 rs && cnt < MAX_NESTED_COLUMN_BALANCING; rs = rs->parentReflowState) {
208 if (rs->mFlags.mIsColumnBalancing) {
209 ++cnt;
212 if (cnt == MAX_NESTED_COLUMN_BALANCING) {
213 numColumns = 1;
217 nscoord colWidth;
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
226 && numColumns > 0) {
227 // This expression uses truncated rounding, which is what we
228 // want
229 int32_t maxColumns =
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;
237 } else {
238 colWidth = NS_INTRINSICSIZE;
240 // Take care of the situation where there's only one column but it's
241 // still too wide
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
250 // count is auto
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),
259 numColumns);
261 if (numColumns <= 0) {
262 numColumns = 1;
266 // Compute extra space and divide it among the columns
267 nscoord extraSpace =
268 std::max(0, availContentWidth - (colWidth*numColumns + colGap*(numColumns - 1)));
269 nscoord extraToColumns = extraSpace/numColumns;
270 colWidth += extraToColumns;
271 expectedWidthLeftOver = extraSpace - (extraToColumns*numColumns);
274 if (isBalancing) {
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.
278 numColumns = 1;
280 colHeight = std::min(mLastBalanceHeight, colHeight);
281 } else {
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));
297 #ifdef DEBUG_roc
298 printf("*** nsColumnSetFrame::ChooseColumnStrategy: numColumns=%d, colWidth=%d, expectedWidthLeftOver=%d, colHeight=%d, colGap=%d\n",
299 numColumns, colWidth, expectedWidthLeftOver, colHeight, colGap);
300 #endif
301 ReflowConfig config = { numColumns, colWidth, expectedWidthLeftOver, colGap,
302 colHeight, isBalancing, knownFeasibleHeight,
303 knownInfeasibleHeight, computedBSize, consumedBSize };
304 return config;
307 bool
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);
331 return feasible;
334 static void MoveChildTo(nsIFrame* aParent, nsIFrame* aChild, nsPoint aOrigin) {
335 if (aChild->GetPosition() == aOrigin) {
336 return;
339 aChild->SetPosition(aOrigin);
340 nsContainerFrame::PlaceFrameView(aChild);
343 nscoord
344 nsColumnSetFrame::GetMinISize(nsRenderingContext *aRenderingContext) {
345 nscoord width = 0;
346 DISPLAY_MIN_WIDTH(this, width);
347 if (mFrames.FirstChild()) {
348 width = mFrames.FirstChild()->GetMinISize(aRenderingContext);
350 const nsStyleColumn* colStyle = StyleColumn();
351 nscoord colWidth;
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);
358 } else {
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.
363 colWidth = width;
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.
371 return width;
374 nscoord
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?
380 nscoord result = 0;
381 DISPLAY_PREF_WIDTH(this, result);
382 const nsStyleColumn* colStyle = StyleColumn();
383 nscoord colGap = GetColumnGap(this, colStyle);
385 nscoord colWidth;
386 if (colStyle->mColumnWidth.GetUnit() == eStyleUnit_Coord) {
387 colWidth = colStyle->mColumnWidth.GetCoordValue();
388 } else if (mFrames.FirstChild()) {
389 colWidth = mFrames.FirstChild()->GetPrefISize(aRenderingContext);
390 } else {
391 colWidth = 0;
394 int32_t numColumns = colStyle->mColumnCount;
395 if (numColumns <= 0) {
396 // if column-count is auto, assume one column
397 numColumns = 1;
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);
404 return result;
407 bool
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)
416 aColData.Reset();
417 bool allFit = true;
418 bool RTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
419 bool shrinkingHeightOnly = !NS_SUBTREE_DIRTY(this) &&
420 mLastBalanceHeight > aConfig.mColMaxHeight;
422 #ifdef DEBUG_roc
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);
426 #endif
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.
456 if (RTL) {
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;
463 #ifdef DEBUG_roc
464 printf("*** childOrigin.x = %d\n", childOrigin.x);
465 #endif
468 int columnCount = 0;
469 int contentBEnd = 0;
470 bool reflowNext = false;
472 while (child) {
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
482 // has changed.)
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
495 // affects layout.
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();
507 if (kidNext) {
508 aStatus = (kidNext->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)
509 ? NS_FRAME_OVERFLOW_INCOMPLETE
510 : NS_FRAME_NOT_COMPLETE;
511 } else {
512 aStatus = mLastFrameStatus;
514 childContentBEnd = nsLayoutUtils::CalculateContentBEnd(wm, child);
515 #ifdef DEBUG_roc
516 printf("*** Skipping child #%d %p (incremental %d, resize height shrink %d): status = %d\n",
517 columnCount, (void*)child, skipIncremental, skipResizeHeightShrink, aStatus);
518 #endif
519 } else {
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);
528 if (reflowNext)
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
539 // hasn't changed.
540 kidReflowState.mFlags.mMustReflowPlaceholders = !colHeightChanged;
542 #ifdef DEBUG_roc
543 printf("*** Reflowing child #%d %p: availHeight=%d\n",
544 columnCount, (void*)child,availSize.BSize(wm));
545 #endif
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.
564 // Reflow the frame
565 ReflowChild(child, PresContext(), kidDesiredSize, kidReflowState,
566 childOrigin.x + kidReflowState.ComputedPhysicalMargin().left,
567 childOrigin.y + kidReflowState.ComputedPhysicalMargin().top,
568 0, aStatus);
570 reflowNext = (aStatus & NS_FRAME_REFLOW_NEXTINFLOW) != 0;
572 #ifdef DEBUG_roc
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());
576 #endif
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) {
587 allFit = false;
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");
607 child = nullptr;
608 break;
609 } else {
610 ++columnCount;
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);
622 if (NS_FAILED(rv)) {
623 NS_NOTREACHED("Couldn't create continuation");
624 child = nullptr;
625 break;
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;
634 reflowNext = true;
635 kidNextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
638 else if (kidNextInFlow->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) {
639 aStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
640 reflowNext = true;
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);
663 child = nullptr;
664 break;
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.
675 break;
678 // Advance to the next column
679 child = child->GetNextSibling();
681 if (child) {
682 if (!RTL) {
683 childOrigin.x += aConfig.mColWidth + aConfig.mColGap;
684 } else {
685 childOrigin.x -= aConfig.mColWidth + aConfig.mColGap;
688 #ifdef DEBUG_roc
689 printf("*** NEXT CHILD ORIGIN.x = %d\n", childOrigin.x);
690 #endif
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);
725 } else {
726 contentSize.BSize(wm) = aConfig.mComputedHeight;
728 } else {
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();
740 } else {
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();
752 #ifdef DEBUG_roc
753 printf("*** DONE PASS feasible=%d\n", allFit && NS_FRAME_IS_FULLY_COMPLETE(aStatus)
754 && !NS_FRAME_IS_TRUNCATED(aStatus));
755 #endif
756 return allFit && NS_FRAME_IS_FULLY_COMPLETE(aStatus)
757 && !NS_FRAME_IS_TRUNCATED(aStatus);
760 void
761 nsColumnSetFrame::DrainOverflowColumns()
763 // First grab the prev-in-flows overflows and reparent them to this
764 // frame.
765 nsPresContext* presContext = PresContext();
766 nsColumnSetFrame* prev = static_cast<nsColumnSetFrame*>(GetPrevInFlow());
767 if (prev) {
768 AutoFrameListPtr overflows(presContext, prev->StealOverflowFrames());
769 if (overflows) {
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());
779 if (overflows) {
780 // We're already the parent for these frames, so no need to set
781 // their parent again.
782 mFrames.AppendFrames(nullptr, *overflows);
786 void
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
808 // iteration.
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
812 // search)
813 bool maybeContinuousBreakingDetected = false;
815 while (!aPresContext->HasPendingInterrupt()) {
816 nscoord lastKnownFeasibleHeight = aConfig.mKnownFeasibleHeight;
818 // Record what we learned from the last reflow
819 if (feasible) {
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,
824 mLastBalanceHeight);
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);
834 } else {
835 aConfig.mKnownInfeasibleHeight = std::max(aConfig.mKnownInfeasibleHeight,
836 mLastBalanceHeight);
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);
851 #ifdef DEBUG_roc
852 printf("*** nsColumnSetFrame::Reflow balancing knownInfeasible=%d knownFeasible=%d\n",
853 aConfig.mKnownInfeasibleHeight, aConfig.mKnownFeasibleHeight);
854 #endif
857 if (aConfig.mKnownInfeasibleHeight >= aConfig.mKnownFeasibleHeight - 1) {
858 // aConfig.mKnownFeasibleHeight is where we want to be
859 break;
862 if (aConfig.mKnownInfeasibleHeight >= availableContentHeight) {
863 break;
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
879 // differently.
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;
886 // Sanitize it
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);
898 #ifdef DEBUG_roc
899 printf("*** nsColumnSetFrame::Reflow balancing choosing next guess=%d\n", nextGuess);
900 #endif
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.
912 break;
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.
920 bool skip = false;
921 if (aConfig.mKnownInfeasibleHeight >= availableContentHeight) {
922 aConfig.mColMaxHeight = availableContentHeight;
923 if (mLastBalanceHeight == availableContentHeight) {
924 skip = true;
926 } else {
927 aConfig.mColMaxHeight = aConfig.mKnownFeasibleHeight;
929 if (!skip) {
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
933 // that.
934 AddStateBits(NS_FRAME_IS_DIRTY);
935 feasible = ReflowColumns(aDesiredSize, aReflowState, aStatus, aConfig,
936 availableContentHeight == NS_UNCONSTRAINEDSIZE,
937 &aOutMargin, aColData);
941 aRunWasFeasible = feasible;
944 void
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);
965 else {
966 RemoveStateBits(NS_FRAME_CONTAINS_RELATIVE_HEIGHT);
969 #ifdef DEBUG
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()));
978 #endif
980 nsOverflowAreas ocBounds;
981 nsReflowStatus ocStatus = NS_FRAME_COMPLETE;
982 if (GetPrevInFlow()) {
983 ReflowOverflowContainerChildren(aPresContext, aReflowState, ocBounds, 0,
984 ocStatus);
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,
1005 colData);
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);
1038 void
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);
1056 #ifdef DEBUG
1057 void
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);
1067 void
1068 nsColumnSetFrame::AppendFrames(ChildListID aListID,
1069 nsFrameList& aFrameList)
1071 MOZ_CRASH("unsupported operation");
1074 void
1075 nsColumnSetFrame::InsertFrames(ChildListID aListID,
1076 nsIFrame* aPrevFrame,
1077 nsFrameList& aFrameList)
1079 MOZ_CRASH("unsupported operation");
1082 void
1083 nsColumnSetFrame::RemoveFrame(ChildListID aListID,
1084 nsIFrame* aOldFrame)
1086 MOZ_CRASH("unsupported operation");
1088 #endif