Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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:
17 *
18 * XXX cursor movement around the top and bottom of colums seems to make the editor
19 * lose the caret.
20 *
21 * XXX should we support CSS columns applied to table elements?
22 */
23 nsIFrame*
24 NS_NewColumnSetFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, nsFrameState aStateFlags)
25 {
26 nsColumnSetFrame* it = new (aPresShell) nsColumnSetFrame(aContext);
27 it->AddStateBits(aStateFlags | NS_BLOCK_MARGIN_ROOT);
28 return it;
29 }
31 NS_IMPL_FRAMEARENA_HELPERS(nsColumnSetFrame)
33 nsColumnSetFrame::nsColumnSetFrame(nsStyleContext* aContext)
34 : nsContainerFrame(aContext), mLastBalanceHeight(NS_INTRINSICSIZE),
35 mLastFrameStatus(NS_FRAME_COMPLETE)
36 {
37 }
39 nsIAtom*
40 nsColumnSetFrame::GetType() const
41 {
42 return nsGkAtoms::columnSetFrame;
43 }
45 static void
46 PaintColumnRule(nsIFrame* aFrame, nsRenderingContext* aCtx,
47 const nsRect& aDirtyRect, nsPoint aPt)
48 {
49 static_cast<nsColumnSetFrame*>(aFrame)->PaintColumnRule(aCtx, aDirtyRect, aPt);
50 }
52 void
53 nsColumnSetFrame::PaintColumnRule(nsRenderingContext* aCtx,
54 const nsRect& aDirtyRect,
55 const nsPoint& aPt)
56 {
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 nsCSSRendering::PaintBorderWithStyleBorder(presContext, *aCtx, this,
114 aDirtyRect, lineRect, border, StyleContext(),
115 // Remember, we only have the "left" "border". Skip everything else
116 (1 << NS_SIDE_TOP | 1 << NS_SIDE_RIGHT | 1 << NS_SIDE_BOTTOM));
118 child = nextSibling;
119 nextSibling = nextSibling->GetNextSibling();
120 }
121 }
123 nsresult
124 nsColumnSetFrame::SetInitialChildList(ChildListID aListID,
125 nsFrameList& aChildList)
126 {
127 if (aListID == kAbsoluteList) {
128 return nsContainerFrame::SetInitialChildList(aListID, aChildList);
129 }
131 NS_ASSERTION(aListID == kPrincipalList,
132 "Only default child list supported");
133 NS_ASSERTION(aChildList.OnlyChild(),
134 "initial child list must have exaisRevertingctly one child");
135 // Queue up the frames for the content frame
136 return nsContainerFrame::SetInitialChildList(kPrincipalList, aChildList);
137 }
139 static nscoord
140 GetAvailableContentWidth(const nsHTMLReflowState& aReflowState)
141 {
142 if (aReflowState.AvailableWidth() == NS_INTRINSICSIZE) {
143 return NS_INTRINSICSIZE;
144 }
145 nscoord borderPaddingWidth =
146 aReflowState.ComputedPhysicalBorderPadding().left +
147 aReflowState.ComputedPhysicalBorderPadding().right;
148 return std::max(0, aReflowState.AvailableWidth() - borderPaddingWidth);
149 }
151 nscoord
152 nsColumnSetFrame::GetAvailableContentHeight(const nsHTMLReflowState& aReflowState)
153 {
154 if (aReflowState.AvailableHeight() == NS_INTRINSICSIZE) {
155 return NS_INTRINSICSIZE;
156 }
158 nsMargin bp = aReflowState.ComputedPhysicalBorderPadding();
159 ApplySkipSides(bp, &aReflowState);
160 bp.bottom = aReflowState.ComputedPhysicalBorderPadding().bottom;
161 return std::max(0, aReflowState.AvailableHeight() - bp.TopBottom());
162 }
164 static nscoord
165 GetColumnGap(nsColumnSetFrame* aFrame,
166 const nsStyleColumn* aColStyle)
167 {
168 if (eStyleUnit_Normal == aColStyle->mColumnGap.GetUnit())
169 return aFrame->StyleFont()->mFont.size;
170 if (eStyleUnit_Coord == aColStyle->mColumnGap.GetUnit()) {
171 nscoord colGap = aColStyle->mColumnGap.GetCoordValue();
172 NS_ASSERTION(colGap >= 0, "negative column gap");
173 return colGap;
174 }
176 NS_NOTREACHED("Unknown gap type");
177 return 0;
178 }
180 nsColumnSetFrame::ReflowConfig
181 nsColumnSetFrame::ChooseColumnStrategy(const nsHTMLReflowState& aReflowState,
182 bool aForceAuto = false,
183 nscoord aFeasibleHeight = NS_INTRINSICSIZE,
184 nscoord aInfeasibleHeight = 0)
186 {
187 nscoord knownFeasibleHeight = aFeasibleHeight;
188 nscoord knownInfeasibleHeight = aInfeasibleHeight;
190 const nsStyleColumn* colStyle = StyleColumn();
191 nscoord availContentWidth = GetAvailableContentWidth(aReflowState);
192 if (aReflowState.ComputedWidth() != NS_INTRINSICSIZE) {
193 availContentWidth = aReflowState.ComputedWidth();
194 }
196 nscoord consumedHeight = GetConsumedHeight();
198 // The effective computed height is the height of the current continuation
199 // of the column set frame. This should be the same as the computed height
200 // if we have an unconstrained available height.
201 nscoord computedHeight = GetEffectiveComputedHeight(aReflowState,
202 consumedHeight);
203 nscoord colHeight = GetAvailableContentHeight(aReflowState);
205 if (aReflowState.ComputedHeight() != NS_INTRINSICSIZE) {
206 colHeight = aReflowState.ComputedHeight();
207 } else if (aReflowState.ComputedMaxHeight() != NS_INTRINSICSIZE) {
208 colHeight = std::min(colHeight, aReflowState.ComputedMaxHeight());
209 }
211 nscoord colGap = GetColumnGap(this, colStyle);
212 int32_t numColumns = colStyle->mColumnCount;
214 // If column-fill is set to 'balance', then we want to balance the columns.
215 const bool isBalancing = colStyle->mColumnFill == NS_STYLE_COLUMN_FILL_BALANCE
216 && !aForceAuto;
217 if (isBalancing) {
218 const uint32_t MAX_NESTED_COLUMN_BALANCING = 2;
219 uint32_t cnt = 0;
220 for (const nsHTMLReflowState* rs = aReflowState.parentReflowState;
221 rs && cnt < MAX_NESTED_COLUMN_BALANCING; rs = rs->parentReflowState) {
222 if (rs->mFlags.mIsColumnBalancing) {
223 ++cnt;
224 }
225 }
226 if (cnt == MAX_NESTED_COLUMN_BALANCING) {
227 numColumns = 1;
228 }
229 }
231 nscoord colWidth;
232 if (colStyle->mColumnWidth.GetUnit() == eStyleUnit_Coord) {
233 colWidth = colStyle->mColumnWidth.GetCoordValue();
234 NS_ASSERTION(colWidth >= 0, "negative column width");
235 // Reduce column count if necessary to make columns fit in the
236 // available width. Compute max number of columns that fit in
237 // availContentWidth, satisfying colGap*(maxColumns - 1) +
238 // colWidth*maxColumns <= availContentWidth
239 if (availContentWidth != NS_INTRINSICSIZE && colGap + colWidth > 0
240 && numColumns > 0) {
241 // This expression uses truncated rounding, which is what we
242 // want
243 int32_t maxColumns =
244 std::min(nscoord(nsStyleColumn::kMaxColumnCount),
245 (availContentWidth + colGap)/(colGap + colWidth));
246 numColumns = std::max(1, std::min(numColumns, maxColumns));
247 }
248 } else if (numColumns > 0 && availContentWidth != NS_INTRINSICSIZE) {
249 nscoord widthMinusGaps = availContentWidth - colGap*(numColumns - 1);
250 colWidth = widthMinusGaps/numColumns;
251 } else {
252 colWidth = NS_INTRINSICSIZE;
253 }
254 // Take care of the situation where there's only one column but it's
255 // still too wide
256 colWidth = std::max(1, std::min(colWidth, availContentWidth));
258 nscoord expectedWidthLeftOver = 0;
260 if (colWidth != NS_INTRINSICSIZE && availContentWidth != NS_INTRINSICSIZE) {
261 // distribute leftover space
263 // First, determine how many columns will be showing if the column
264 // count is auto
265 if (numColumns <= 0) {
266 // choose so that colGap*(nominalColumnCount - 1) +
267 // colWidth*nominalColumnCount is nearly availContentWidth
268 // make sure to round down
269 if (colGap + colWidth > 0) {
270 numColumns = (availContentWidth + colGap)/(colGap + colWidth);
271 // The number of columns should never exceed kMaxColumnCount.
272 numColumns = std::min(nscoord(nsStyleColumn::kMaxColumnCount),
273 numColumns);
274 }
275 if (numColumns <= 0) {
276 numColumns = 1;
277 }
278 }
280 // Compute extra space and divide it among the columns
281 nscoord extraSpace =
282 std::max(0, availContentWidth - (colWidth*numColumns + colGap*(numColumns - 1)));
283 nscoord extraToColumns = extraSpace/numColumns;
284 colWidth += extraToColumns;
285 expectedWidthLeftOver = extraSpace - (extraToColumns*numColumns);
286 }
288 if (isBalancing) {
289 if (numColumns <= 0) {
290 // Hmm, auto column count, column width or available width is unknown,
291 // and balancing is required. Let's just use one column then.
292 numColumns = 1;
293 }
294 colHeight = std::min(mLastBalanceHeight, colHeight);
295 } else {
296 // This is the case when the column-fill property is set to 'auto'.
297 // No balancing, so don't limit the column count
298 numColumns = INT32_MAX;
300 // XXX_jwir3: If a page's height is set to 0, we could continually
301 // create continuations, resulting in an infinite loop, since
302 // no progress is ever made. This is an issue with the spec
303 // (css3-multicol, css3-page, and css3-break) that is
304 // unresolved as of 27 Feb 2013. For the time being, we set this
305 // to have a minimum of 1 css px. Once a resolution is made
306 // on what minimum to have for a page height, we may need to
307 // change this value to match the appropriate spec(s).
308 colHeight = std::max(colHeight, nsPresContext::CSSPixelsToAppUnits(1));
309 }
311 #ifdef DEBUG_roc
312 printf("*** nsColumnSetFrame::ChooseColumnStrategy: numColumns=%d, colWidth=%d, expectedWidthLeftOver=%d, colHeight=%d, colGap=%d\n",
313 numColumns, colWidth, expectedWidthLeftOver, colHeight, colGap);
314 #endif
315 ReflowConfig config = { numColumns, colWidth, expectedWidthLeftOver, colGap,
316 colHeight, isBalancing, knownFeasibleHeight,
317 knownInfeasibleHeight, computedHeight, consumedHeight };
318 return config;
319 }
321 bool
322 nsColumnSetFrame::ReflowColumns(nsHTMLReflowMetrics& aDesiredSize,
323 const nsHTMLReflowState& aReflowState,
324 nsReflowStatus& aReflowStatus,
325 ReflowConfig& aConfig,
326 bool aLastColumnUnbounded,
327 nsCollapsingMargin* aCarriedOutBottomMargin,
328 ColumnBalanceData& aColData)
329 {
330 bool feasible = ReflowChildren(aDesiredSize, aReflowState,
331 aReflowStatus, aConfig, aLastColumnUnbounded,
332 aCarriedOutBottomMargin, aColData);
334 if (aColData.mHasExcessHeight) {
335 aConfig = ChooseColumnStrategy(aReflowState, true);
337 // We need to reflow our children again one last time, otherwise we might
338 // end up with a stale column height for some of our columns, since we
339 // bailed out of balancing.
340 feasible = ReflowChildren(aDesiredSize, aReflowState, aReflowStatus,
341 aConfig, aLastColumnUnbounded,
342 aCarriedOutBottomMargin, aColData);
343 }
345 return feasible;
346 }
348 static void MoveChildTo(nsIFrame* aParent, nsIFrame* aChild, nsPoint aOrigin) {
349 if (aChild->GetPosition() == aOrigin) {
350 return;
351 }
353 aChild->SetPosition(aOrigin);
354 nsContainerFrame::PlaceFrameView(aChild);
355 }
357 nscoord
358 nsColumnSetFrame::GetMinWidth(nsRenderingContext *aRenderingContext) {
359 nscoord width = 0;
360 DISPLAY_MIN_WIDTH(this, width);
361 if (mFrames.FirstChild()) {
362 width = mFrames.FirstChild()->GetMinWidth(aRenderingContext);
363 }
364 const nsStyleColumn* colStyle = StyleColumn();
365 nscoord colWidth;
366 if (colStyle->mColumnWidth.GetUnit() == eStyleUnit_Coord) {
367 colWidth = colStyle->mColumnWidth.GetCoordValue();
368 // As available width reduces to zero, we reduce our number of columns
369 // to one, and don't enforce the column width, so just return the min
370 // of the child's min-width with any specified column width.
371 width = std::min(width, colWidth);
372 } else {
373 NS_ASSERTION(colStyle->mColumnCount > 0,
374 "column-count and column-width can't both be auto");
375 // As available width reduces to zero, we still have mColumnCount columns,
376 // so multiply the child's min-width by the number of columns.
377 colWidth = width;
378 width *= colStyle->mColumnCount;
379 // The multiplication above can make 'width' negative (integer overflow),
380 // so use std::max to protect against that.
381 width = std::max(width, colWidth);
382 }
383 // XXX count forced column breaks here? Maybe we should return the child's
384 // min-width times the minimum number of columns.
385 return width;
386 }
388 nscoord
389 nsColumnSetFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) {
390 // Our preferred width is our desired column width, if specified, otherwise
391 // the child's preferred width, times the number of columns, plus the width
392 // of any required column gaps
393 // XXX what about forced column breaks here?
394 nscoord result = 0;
395 DISPLAY_PREF_WIDTH(this, result);
396 const nsStyleColumn* colStyle = StyleColumn();
397 nscoord colGap = GetColumnGap(this, colStyle);
399 nscoord colWidth;
400 if (colStyle->mColumnWidth.GetUnit() == eStyleUnit_Coord) {
401 colWidth = colStyle->mColumnWidth.GetCoordValue();
402 } else if (mFrames.FirstChild()) {
403 colWidth = mFrames.FirstChild()->GetPrefWidth(aRenderingContext);
404 } else {
405 colWidth = 0;
406 }
408 int32_t numColumns = colStyle->mColumnCount;
409 if (numColumns <= 0) {
410 // if column-count is auto, assume one column
411 numColumns = 1;
412 }
414 nscoord width = colWidth*numColumns + colGap*(numColumns - 1);
415 // The multiplication above can make 'width' negative (integer overflow),
416 // so use std::max to protect against that.
417 result = std::max(width, colWidth);
418 return result;
419 }
421 bool
422 nsColumnSetFrame::ReflowChildren(nsHTMLReflowMetrics& aDesiredSize,
423 const nsHTMLReflowState& aReflowState,
424 nsReflowStatus& aStatus,
425 const ReflowConfig& aConfig,
426 bool aUnboundedLastColumn,
427 nsCollapsingMargin* aBottomMarginCarriedOut,
428 ColumnBalanceData& aColData)
429 {
430 aColData.Reset();
431 bool allFit = true;
432 bool RTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
433 bool shrinkingHeightOnly = !NS_SUBTREE_DIRTY(this) &&
434 mLastBalanceHeight > aConfig.mColMaxHeight;
436 #ifdef DEBUG_roc
437 printf("*** Doing column reflow pass: mLastBalanceHeight=%d, mColMaxHeight=%d, RTL=%d\n, mBalanceColCount=%d, mColWidth=%d, mColGap=%d\n",
438 mLastBalanceHeight, aConfig.mColMaxHeight, RTL, aConfig.mBalanceColCount,
439 aConfig.mColWidth, aConfig.mColGap);
440 #endif
442 DrainOverflowColumns();
444 const bool colHeightChanged = mLastBalanceHeight != aConfig.mColMaxHeight;
446 if (colHeightChanged) {
447 mLastBalanceHeight = aConfig.mColMaxHeight;
448 // XXX Seems like this could fire if incremental reflow pushed the column set
449 // down so we reflow incrementally with a different available height.
450 // We need a way to do an incremental reflow and be sure availableHeight
451 // changes are taken account of! Right now I think block frames with absolute
452 // children might exit early.
453 //NS_ASSERTION(aKidReason != eReflowReason_Incremental,
454 // "incremental reflow should not have changed the balance height");
455 }
457 // get our border and padding
458 nsMargin borderPadding = aReflowState.ComputedPhysicalBorderPadding();
459 ApplySkipSides(borderPadding, &aReflowState);
461 nsRect contentRect(0, 0, 0, 0);
462 nsOverflowAreas overflowRects;
464 nsIFrame* child = mFrames.FirstChild();
465 nsPoint childOrigin = nsPoint(borderPadding.left, borderPadding.top);
466 // For RTL, figure out where the last column's left edge should be. Since the
467 // columns might not fill the frame exactly, we need to account for the
468 // slop. Otherwise we'll waste time moving the columns by some tiny
469 // amount unnecessarily.
470 if (RTL) {
471 nscoord availWidth = aReflowState.AvailableWidth();
472 if (aReflowState.ComputedWidth() != NS_INTRINSICSIZE) {
473 availWidth = aReflowState.ComputedWidth();
474 }
475 if (availWidth != NS_INTRINSICSIZE) {
476 childOrigin.x += availWidth - aConfig.mColWidth;
477 #ifdef DEBUG_roc
478 printf("*** childOrigin.x = %d\n", childOrigin.x);
479 #endif
480 }
481 }
482 int columnCount = 0;
483 int contentBottom = 0;
484 bool reflowNext = false;
486 while (child) {
487 // Try to skip reflowing the child. We can't skip if the child is dirty. We also can't
488 // skip if the next column is dirty, because the next column's first line(s)
489 // might be pullable back to this column. We can't skip if it's the last child
490 // because we need to obtain the bottom margin. We can't skip
491 // if this is the last column and we're supposed to assign unbounded
492 // height to it, because that could change the available height from
493 // the last time we reflowed it and we should try to pull all the
494 // content from its next sibling. (Note that it might be the last
495 // column, but not be the last child because the desired number of columns
496 // has changed.)
497 bool skipIncremental = !aReflowState.ShouldReflowAllKids()
498 && !NS_SUBTREE_DIRTY(child)
499 && child->GetNextSibling()
500 && !(aUnboundedLastColumn && columnCount == aConfig.mBalanceColCount - 1)
501 && !NS_SUBTREE_DIRTY(child->GetNextSibling());
502 // If we need to pull up content from the prev-in-flow then this is not just
503 // a height shrink. The prev in flow will have set the dirty bit.
504 // Check the overflow rect YMost instead of just the child's content height. The child
505 // may have overflowing content that cares about the available height boundary.
506 // (It may also have overflowing content that doesn't care about the available height
507 // boundary, but if so, too bad, this optimization is defeated.)
508 // We want scrollable overflow here since this is a calculation that
509 // affects layout.
510 bool skipResizeHeightShrink = shrinkingHeightOnly
511 && child->GetScrollableOverflowRect().YMost() <= aConfig.mColMaxHeight;
513 nscoord childContentBottom = 0;
514 if (!reflowNext && (skipIncremental || skipResizeHeightShrink)) {
515 // This child does not need to be reflowed, but we may need to move it
516 MoveChildTo(this, child, childOrigin);
518 // If this is the last frame then make sure we get the right status
519 nsIFrame* kidNext = child->GetNextSibling();
520 if (kidNext) {
521 aStatus = (kidNext->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)
522 ? NS_FRAME_OVERFLOW_INCOMPLETE
523 : NS_FRAME_NOT_COMPLETE;
524 } else {
525 aStatus = mLastFrameStatus;
526 }
527 childContentBottom = nsLayoutUtils::CalculateContentBottom(child);
528 #ifdef DEBUG_roc
529 printf("*** Skipping child #%d %p (incremental %d, resize height shrink %d): status = %d\n",
530 columnCount, (void*)child, skipIncremental, skipResizeHeightShrink, aStatus);
531 #endif
532 } else {
533 nsSize availSize(aConfig.mColWidth, aConfig.mColMaxHeight);
535 if (aUnboundedLastColumn && columnCount == aConfig.mBalanceColCount - 1) {
536 availSize.height = GetAvailableContentHeight(aReflowState);
537 }
539 if (reflowNext)
540 child->AddStateBits(NS_FRAME_IS_DIRTY);
542 nsHTMLReflowState kidReflowState(PresContext(), aReflowState, child,
543 availSize, availSize.width,
544 aReflowState.ComputedHeight());
545 kidReflowState.mFlags.mIsTopOfPage = true;
546 kidReflowState.mFlags.mTableIsSplittable = false;
547 kidReflowState.mFlags.mIsColumnBalancing = aConfig.mBalanceColCount < INT32_MAX;
549 // We need to reflow any float placeholders, even if our column height
550 // hasn't changed.
551 kidReflowState.mFlags.mMustReflowPlaceholders = !colHeightChanged;
553 #ifdef DEBUG_roc
554 printf("*** Reflowing child #%d %p: availHeight=%d\n",
555 columnCount, (void*)child,availSize.height);
556 #endif
558 // Note if the column's next in flow is not being changed by this incremental reflow.
559 // This may allow the current column to avoid trying to pull lines from the next column.
560 if (child->GetNextSibling() &&
561 !(GetStateBits() & NS_FRAME_IS_DIRTY) &&
562 !(child->GetNextSibling()->GetStateBits() & NS_FRAME_IS_DIRTY)) {
563 kidReflowState.mFlags.mNextInFlowUntouched = true;
564 }
566 nsHTMLReflowMetrics kidDesiredSize(aReflowState.GetWritingMode(),
567 aDesiredSize.mFlags);
569 // XXX it would be cool to consult the float manager for the
570 // previous block to figure out the region of floats from the
571 // previous column that extend into this column, and subtract
572 // that region from the new float manager. So you could stick a
573 // really big float in the first column and text in following
574 // columns would flow around it.
576 // Reflow the frame
577 ReflowChild(child, PresContext(), kidDesiredSize, kidReflowState,
578 childOrigin.x + kidReflowState.ComputedPhysicalMargin().left,
579 childOrigin.y + kidReflowState.ComputedPhysicalMargin().top,
580 0, aStatus);
582 reflowNext = (aStatus & NS_FRAME_REFLOW_NEXTINFLOW) != 0;
584 #ifdef DEBUG_roc
585 printf("*** Reflowed child #%d %p: status = %d, desiredSize=%d,%d CarriedOutBottomMargin=%d\n",
586 columnCount, (void*)child, aStatus, kidDesiredSize.Width(), kidDesiredSize.Height(),
587 kidDesiredSize.mCarriedOutBottomMargin.get());
588 #endif
590 NS_FRAME_TRACE_REFLOW_OUT("Column::Reflow", aStatus);
592 *aBottomMarginCarriedOut = kidDesiredSize.mCarriedOutBottomMargin;
594 FinishReflowChild(child, PresContext(), kidDesiredSize,
595 &kidReflowState, childOrigin.x, childOrigin.y, 0);
597 childContentBottom = nsLayoutUtils::CalculateContentBottom(child);
598 if (childContentBottom > aConfig.mColMaxHeight) {
599 allFit = false;
600 }
601 if (childContentBottom > availSize.height) {
602 aColData.mMaxOverflowingHeight = std::max(childContentBottom,
603 aColData.mMaxOverflowingHeight);
604 }
605 }
607 contentRect.UnionRect(contentRect, child->GetRect());
609 ConsiderChildOverflow(overflowRects, child);
610 contentBottom = std::max(contentBottom, childContentBottom);
611 aColData.mLastHeight = childContentBottom;
612 aColData.mSumHeight += childContentBottom;
614 // Build a continuation column if necessary
615 nsIFrame* kidNextInFlow = child->GetNextInFlow();
617 if (NS_FRAME_IS_FULLY_COMPLETE(aStatus) && !NS_FRAME_IS_TRUNCATED(aStatus)) {
618 NS_ASSERTION(!kidNextInFlow, "next in flow should have been deleted");
619 child = nullptr;
620 break;
621 } else {
622 ++columnCount;
623 // Make sure that the column has a next-in-flow. If not, we must
624 // create one to hold the overflowing stuff, even if we're just
625 // going to put it on our overflow list and let *our*
626 // next in flow handle it.
627 if (!kidNextInFlow) {
628 NS_ASSERTION(aStatus & NS_FRAME_REFLOW_NEXTINFLOW,
629 "We have to create a continuation, but the block doesn't want us to reflow it?");
631 // We need to create a continuing column
632 nsresult rv = CreateNextInFlow(child, kidNextInFlow);
634 if (NS_FAILED(rv)) {
635 NS_NOTREACHED("Couldn't create continuation");
636 child = nullptr;
637 break;
638 }
639 }
641 // Make sure we reflow a next-in-flow when it switches between being
642 // normal or overflow container
643 if (NS_FRAME_OVERFLOW_IS_INCOMPLETE(aStatus)) {
644 if (!(kidNextInFlow->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)) {
645 aStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
646 reflowNext = true;
647 kidNextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
648 }
649 }
650 else if (kidNextInFlow->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) {
651 aStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
652 reflowNext = true;
653 kidNextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
654 }
656 if ((contentBottom > aReflowState.ComputedMaxHeight() ||
657 contentBottom > aReflowState.ComputedHeight()) &&
658 aConfig.mBalanceColCount < INT32_MAX) {
659 // We overflowed vertically, but have not exceeded the number of
660 // columns. We're going to go into overflow columns now, so balancing
661 // no longer applies.
662 aColData.mHasExcessHeight = true;
663 }
665 if (columnCount >= aConfig.mBalanceColCount) {
666 // No more columns allowed here. Stop.
667 aStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
668 kidNextInFlow->AddStateBits(NS_FRAME_IS_DIRTY);
669 // Move any of our leftover columns to our overflow list. Our
670 // next-in-flow will eventually pick them up.
671 const nsFrameList& continuationColumns = mFrames.RemoveFramesAfter(child);
672 if (continuationColumns.NotEmpty()) {
673 SetOverflowFrames(continuationColumns);
674 }
675 child = nullptr;
676 break;
677 }
678 }
680 if (PresContext()->HasPendingInterrupt()) {
681 // Stop the loop now while |child| still points to the frame that bailed
682 // out. We could keep going here and condition a bunch of the code in
683 // this loop on whether there's an interrupt, or even just keep going and
684 // trying to reflow the blocks (even though we know they'll interrupt
685 // right after their first line), but stopping now is conceptually the
686 // simplest (and probably fastest) thing.
687 break;
688 }
690 // Advance to the next column
691 child = child->GetNextSibling();
693 if (child) {
694 if (!RTL) {
695 childOrigin.x += aConfig.mColWidth + aConfig.mColGap;
696 } else {
697 childOrigin.x -= aConfig.mColWidth + aConfig.mColGap;
698 }
700 #ifdef DEBUG_roc
701 printf("*** NEXT CHILD ORIGIN.x = %d\n", childOrigin.x);
702 #endif
703 }
704 }
706 if (PresContext()->CheckForInterrupt(this) &&
707 (GetStateBits() & NS_FRAME_IS_DIRTY)) {
708 // Mark all our kids starting with |child| dirty
710 // Note that this is a CheckForInterrupt call, not a HasPendingInterrupt,
711 // because we might have interrupted while reflowing |child|, and since
712 // we're about to add a dirty bit to |child| we need to make sure that
713 // |this| is scheduled to have dirty bits marked on it and its ancestors.
714 // Otherwise, when we go to mark dirty bits on |child|'s ancestors we'll
715 // bail out immediately, since it'll already have a dirty bit.
716 for (; child; child = child->GetNextSibling()) {
717 child->AddStateBits(NS_FRAME_IS_DIRTY);
718 }
719 }
721 aColData.mMaxHeight = contentBottom;
722 contentRect.height = std::max(contentRect.height, contentBottom);
723 mLastFrameStatus = aStatus;
725 // contentRect included the borderPadding.left,borderPadding.top of the child rects
726 contentRect -= nsPoint(borderPadding.left, borderPadding.top);
728 nsSize contentSize = nsSize(contentRect.XMost(), contentRect.YMost());
730 // Apply computed and min/max values
731 if (aConfig.mComputedHeight != NS_INTRINSICSIZE) {
732 if (aReflowState.AvailableHeight() != NS_INTRINSICSIZE) {
733 contentSize.height = std::min(contentSize.height,
734 aConfig.mComputedHeight);
735 } else {
736 contentSize.height = aConfig.mComputedHeight;
737 }
738 } else {
739 // We add the "consumed" height back in so that we're applying
740 // constraints to the correct height value, then subtract it again
741 // after we've finished with the min/max calculation. This prevents us from
742 // having a last continuation that is smaller than the min height. but which
743 // has prev-in-flows, trigger a larger height than actually required.
744 contentSize.height = aReflowState.ApplyMinMaxHeight(contentSize.height,
745 aConfig.mConsumedHeight);
746 }
747 if (aReflowState.ComputedWidth() != NS_INTRINSICSIZE) {
748 contentSize.width = aReflowState.ComputedWidth();
749 } else {
750 contentSize.width = aReflowState.ApplyMinMaxWidth(contentSize.width);
751 }
753 aDesiredSize.Height() = contentSize.height +
754 borderPadding.TopBottom();
755 aDesiredSize.Width() = contentSize.width +
756 borderPadding.LeftRight();
757 aDesiredSize.mOverflowAreas = overflowRects;
758 aDesiredSize.UnionOverflowAreasWithDesiredBounds();
760 #ifdef DEBUG_roc
761 printf("*** DONE PASS feasible=%d\n", allFit && NS_FRAME_IS_FULLY_COMPLETE(aStatus)
762 && !NS_FRAME_IS_TRUNCATED(aStatus));
763 #endif
764 return allFit && NS_FRAME_IS_FULLY_COMPLETE(aStatus)
765 && !NS_FRAME_IS_TRUNCATED(aStatus);
766 }
768 void
769 nsColumnSetFrame::DrainOverflowColumns()
770 {
771 // First grab the prev-in-flows overflows and reparent them to this
772 // frame.
773 nsPresContext* presContext = PresContext();
774 nsColumnSetFrame* prev = static_cast<nsColumnSetFrame*>(GetPrevInFlow());
775 if (prev) {
776 AutoFrameListPtr overflows(presContext, prev->StealOverflowFrames());
777 if (overflows) {
778 nsContainerFrame::ReparentFrameViewList(*overflows, prev, this);
780 mFrames.InsertFrames(this, nullptr, *overflows);
781 }
782 }
784 // Now pull back our own overflows and append them to our children.
785 // We don't need to reparent them since we're already their parent.
786 AutoFrameListPtr overflows(presContext, StealOverflowFrames());
787 if (overflows) {
788 // We're already the parent for these frames, so no need to set
789 // their parent again.
790 mFrames.AppendFrames(nullptr, *overflows);
791 }
792 }
794 void
795 nsColumnSetFrame::FindBestBalanceHeight(const nsHTMLReflowState& aReflowState,
796 nsPresContext* aPresContext,
797 ReflowConfig& aConfig,
798 ColumnBalanceData& aColData,
799 nsHTMLReflowMetrics& aDesiredSize,
800 nsCollapsingMargin& aOutMargin,
801 bool& aUnboundedLastColumn,
802 bool& aRunWasFeasible,
803 nsReflowStatus& aStatus)
804 {
805 bool feasible = aRunWasFeasible;
807 nsMargin bp = aReflowState.ComputedPhysicalBorderPadding();
808 ApplySkipSides(bp);
809 bp.bottom = aReflowState.ComputedPhysicalBorderPadding().bottom;
811 nscoord availableContentHeight =
812 GetAvailableContentHeight(aReflowState);
814 // Termination of the algorithm below is guaranteed because
815 // aConfig.knownFeasibleHeight - aConfig.knownInfeasibleHeight decreases in every
816 // iteration.
818 // We set this flag when we detect that we may contain a frame
819 // that can break anywhere (thus foiling the linear decrease-by-one
820 // search)
821 bool maybeContinuousBreakingDetected = false;
823 while (!aPresContext->HasPendingInterrupt()) {
824 nscoord lastKnownFeasibleHeight = aConfig.mKnownFeasibleHeight;
826 // Record what we learned from the last reflow
827 if (feasible) {
828 // maxHeight is feasible. Also, mLastBalanceHeight is feasible.
829 aConfig.mKnownFeasibleHeight = std::min(aConfig.mKnownFeasibleHeight,
830 aColData.mMaxHeight);
831 aConfig.mKnownFeasibleHeight = std::min(aConfig.mKnownFeasibleHeight,
832 mLastBalanceHeight);
834 // Furthermore, no height less than the height of the last
835 // column can ever be feasible. (We might be able to reduce the
836 // height of a non-last column by moving content to a later column,
837 // but we can't do that with the last column.)
838 if (mFrames.GetLength() == aConfig.mBalanceColCount) {
839 aConfig.mKnownInfeasibleHeight = std::max(aConfig.mKnownInfeasibleHeight,
840 aColData.mLastHeight - 1);
841 }
842 } else {
843 aConfig.mKnownInfeasibleHeight = std::max(aConfig.mKnownInfeasibleHeight,
844 mLastBalanceHeight);
845 // If a column didn't fit in its available height, then its current
846 // height must be the minimum height for unbreakable content in
847 // the column, and therefore no smaller height can be feasible.
848 aConfig.mKnownInfeasibleHeight = std::max(aConfig.mKnownInfeasibleHeight,
849 aColData.mMaxOverflowingHeight - 1);
851 if (aUnboundedLastColumn) {
852 // The last column is unbounded, so all content got reflowed, so the
853 // mColMaxHeight is feasible.
854 aConfig.mKnownFeasibleHeight = std::min(aConfig.mKnownFeasibleHeight,
855 aColData.mMaxHeight);
856 }
857 }
859 #ifdef DEBUG_roc
860 printf("*** nsColumnSetFrame::Reflow balancing knownInfeasible=%d knownFeasible=%d\n",
861 aConfig.mKnownInfeasibleHeight, aConfig.mKnownFeasibleHeight);
862 #endif
865 if (aConfig.mKnownInfeasibleHeight >= aConfig.mKnownFeasibleHeight - 1) {
866 // aConfig.mKnownFeasibleHeight is where we want to be
867 break;
868 }
870 if (aConfig.mKnownInfeasibleHeight >= availableContentHeight) {
871 break;
872 }
874 if (lastKnownFeasibleHeight - aConfig.mKnownFeasibleHeight == 1) {
875 // We decreased the feasible height by one twip only. This could
876 // indicate that there is a continuously breakable child frame
877 // that we are crawling through.
878 maybeContinuousBreakingDetected = true;
879 }
881 nscoord nextGuess = (aConfig.mKnownFeasibleHeight + aConfig.mKnownInfeasibleHeight)/2;
882 // The constant of 600 twips is arbitrary. It's about two line-heights.
883 if (aConfig.mKnownFeasibleHeight - nextGuess < 600 &&
884 !maybeContinuousBreakingDetected) {
885 // We're close to our target, so just try shrinking just the
886 // minimum amount that will cause one of our columns to break
887 // differently.
888 nextGuess = aConfig.mKnownFeasibleHeight - 1;
889 } else if (aUnboundedLastColumn) {
890 // Make a guess by dividing that into N columns. Add some slop
891 // to try to make it on the feasible side. The constant of
892 // 600 twips is arbitrary. It's about two line-heights.
893 nextGuess = aColData.mSumHeight/aConfig.mBalanceColCount + 600;
894 // Sanitize it
895 nextGuess = clamped(nextGuess, aConfig.mKnownInfeasibleHeight + 1,
896 aConfig.mKnownFeasibleHeight - 1);
897 } else if (aConfig.mKnownFeasibleHeight == NS_INTRINSICSIZE) {
898 // This can happen when we had a next-in-flow so we didn't
899 // want to do an unbounded height measuring step. Let's just increase
900 // from the infeasible height by some reasonable amount.
901 nextGuess = aConfig.mKnownInfeasibleHeight*2 + 600;
902 }
903 // Don't bother guessing more than our height constraint.
904 nextGuess = std::min(availableContentHeight, nextGuess);
906 #ifdef DEBUG_roc
907 printf("*** nsColumnSetFrame::Reflow balancing choosing next guess=%d\n", nextGuess);
908 #endif
910 aConfig.mColMaxHeight = nextGuess;
912 aUnboundedLastColumn = false;
913 AddStateBits(NS_FRAME_IS_DIRTY);
914 feasible = ReflowColumns(aDesiredSize, aReflowState, aStatus, aConfig, false,
915 &aOutMargin, aColData);
917 if (!aConfig.mIsBalancing) {
918 // Looks like we had excess height when balancing, so we gave up on
919 // trying to balance.
920 break;
921 }
922 }
924 if (aConfig.mIsBalancing && !feasible &&
925 !aPresContext->HasPendingInterrupt()) {
926 // We may need to reflow one more time at the feasible height to
927 // get a valid layout.
928 bool skip = false;
929 if (aConfig.mKnownInfeasibleHeight >= availableContentHeight) {
930 aConfig.mColMaxHeight = availableContentHeight;
931 if (mLastBalanceHeight == availableContentHeight) {
932 skip = true;
933 }
934 } else {
935 aConfig.mColMaxHeight = aConfig.mKnownFeasibleHeight;
936 }
937 if (!skip) {
938 // If our height is unconstrained, make sure that the last column is
939 // allowed to have arbitrary height here, even though we were balancing.
940 // Otherwise we'd have to split, and it's not clear what we'd do with
941 // that.
942 AddStateBits(NS_FRAME_IS_DIRTY);
943 feasible = ReflowColumns(aDesiredSize, aReflowState, aStatus, aConfig,
944 availableContentHeight == NS_UNCONSTRAINEDSIZE,
945 &aOutMargin, aColData);
946 }
947 }
949 aRunWasFeasible = feasible;
950 }
952 nsresult
953 nsColumnSetFrame::Reflow(nsPresContext* aPresContext,
954 nsHTMLReflowMetrics& aDesiredSize,
955 const nsHTMLReflowState& aReflowState,
956 nsReflowStatus& aStatus)
957 {
958 // Don't support interruption in columns
959 nsPresContext::InterruptPreventer noInterrupts(aPresContext);
961 DO_GLOBAL_REFLOW_COUNT("nsColumnSetFrame");
962 DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
964 // Initialize OUT parameter
965 aStatus = NS_FRAME_COMPLETE;
967 // Our children depend on our height if we have a fixed height.
968 if (aReflowState.ComputedHeight() != NS_AUTOHEIGHT) {
969 NS_ASSERTION(aReflowState.ComputedHeight() != NS_INTRINSICSIZE,
970 "Unexpected computed height");
971 AddStateBits(NS_FRAME_CONTAINS_RELATIVE_HEIGHT);
972 }
973 else {
974 RemoveStateBits(NS_FRAME_CONTAINS_RELATIVE_HEIGHT);
975 }
977 //------------ Handle Incremental Reflow -----------------
979 ReflowConfig config = ChooseColumnStrategy(aReflowState);
981 // If balancing, then we allow the last column to grow to unbounded
982 // height during the first reflow. This gives us a way to estimate
983 // what the average column height should be, because we can measure
984 // the heights of all the columns and sum them up. But don't do this
985 // if we have a next in flow because we don't want to suck all its
986 // content back here and then have to push it out again!
987 nsIFrame* nextInFlow = GetNextInFlow();
988 bool unboundedLastColumn = config.mIsBalancing && !nextInFlow;
989 nsCollapsingMargin carriedOutBottomMargin;
990 ColumnBalanceData colData;
991 colData.mHasExcessHeight = false;
993 bool feasible = ReflowColumns(aDesiredSize, aReflowState, aStatus, config,
994 unboundedLastColumn, &carriedOutBottomMargin,
995 colData);
997 // If we're not balancing, then we're already done, since we should have
998 // reflown all of our children, and there is no need for a binary search to
999 // determine proper column height.
1000 if (config.mIsBalancing && !aPresContext->HasPendingInterrupt()) {
1001 FindBestBalanceHeight(aReflowState, aPresContext, config, colData,
1002 aDesiredSize, carriedOutBottomMargin,
1003 unboundedLastColumn, feasible, aStatus);
1004 }
1006 if (aPresContext->HasPendingInterrupt() &&
1007 aReflowState.AvailableHeight() == NS_UNCONSTRAINEDSIZE) {
1008 // In this situation, we might be lying about our reflow status, because
1009 // our last kid (the one that got interrupted) was incomplete. Fix that.
1010 aStatus = NS_FRAME_COMPLETE;
1011 }
1013 FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowState, aStatus, false);
1015 aDesiredSize.mCarriedOutBottomMargin = carriedOutBottomMargin;
1017 NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
1019 NS_ASSERTION(NS_FRAME_IS_FULLY_COMPLETE(aStatus) ||
1020 aReflowState.AvailableHeight() != NS_UNCONSTRAINEDSIZE,
1021 "Column set should be complete if the available height is unconstrained");
1023 return NS_OK;
1024 }
1026 void
1027 nsColumnSetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1028 const nsRect& aDirtyRect,
1029 const nsDisplayListSet& aLists) {
1030 DisplayBorderBackgroundOutline(aBuilder, aLists);
1032 if (IsVisibleForPainting(aBuilder)) {
1033 aLists.BorderBackground()->AppendNewToTop(new (aBuilder)
1034 nsDisplayGenericOverflow(aBuilder, this, ::PaintColumnRule, "ColumnRule",
1035 nsDisplayItem::TYPE_COLUMN_RULE));
1036 }
1038 // Our children won't have backgrounds so it doesn't matter where we put them.
1039 for (nsFrameList::Enumerator e(mFrames); !e.AtEnd(); e.Next()) {
1040 BuildDisplayListForChild(aBuilder, e.get(), aDirtyRect, aLists);
1041 }
1042 }
1044 nsresult
1045 nsColumnSetFrame::AppendFrames(ChildListID aListID,
1046 nsFrameList& aFrameList)
1047 {
1048 if (aListID == kAbsoluteList) {
1049 return nsContainerFrame::AppendFrames(aListID, aFrameList);
1050 }
1052 NS_ERROR("unexpected child list");
1053 return NS_ERROR_INVALID_ARG;
1054 }
1056 nsresult
1057 nsColumnSetFrame::InsertFrames(ChildListID aListID,
1058 nsIFrame* aPrevFrame,
1059 nsFrameList& aFrameList)
1060 {
1061 if (aListID == kAbsoluteList) {
1062 return nsContainerFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
1063 }
1065 NS_ERROR("unexpected child list");
1066 return NS_ERROR_INVALID_ARG;
1067 }
1069 nsresult
1070 nsColumnSetFrame::RemoveFrame(ChildListID aListID,
1071 nsIFrame* aOldFrame)
1072 {
1073 if (aListID == kAbsoluteList) {
1074 return nsContainerFrame::RemoveFrame(aListID, aOldFrame);
1075 }
1077 NS_ERROR("unexpected child list");
1078 return NS_ERROR_INVALID_ARG;
1079 }