(no bug) fix end-of-line whitespace so I can add a changeset with an a=bustage annotation
[mozilla-central.git] / layout / generic / nsColumnSetFrame.cpp
blob77c746f2d56fe841d01385d5b94212c0e2129834
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Robert O'Callahan <roc@ocallahan.org>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either of the GNU General Public License Version 2 or later (the "GPL"),
27 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 /* rendering object for css3 multi-column layout */
41 #include "nsHTMLContainerFrame.h"
42 #include "nsIContent.h"
43 #include "nsIFrame.h"
44 #include "nsISupports.h"
45 #include "nsIAtom.h"
46 #include "nsPresContext.h"
47 #include "nsHTMLParts.h"
48 #include "nsGkAtoms.h"
49 #include "nsStyleConsts.h"
50 #include "nsCOMPtr.h"
51 #include "nsLayoutUtils.h"
52 #include "nsDisplayList.h"
53 #include "nsCSSRendering.h"
55 class nsColumnSetFrame : public nsHTMLContainerFrame {
56 public:
57 NS_DECL_FRAMEARENA_HELPERS
59 nsColumnSetFrame(nsStyleContext* aContext);
61 NS_IMETHOD SetInitialChildList(nsIAtom* aListName,
62 nsFrameList& aChildList);
64 NS_IMETHOD Reflow(nsPresContext* aPresContext,
65 nsHTMLReflowMetrics& aDesiredSize,
66 const nsHTMLReflowState& aReflowState,
67 nsReflowStatus& aStatus);
69 NS_IMETHOD AppendFrames(nsIAtom* aListName,
70 nsFrameList& aFrameList);
71 NS_IMETHOD InsertFrames(nsIAtom* aListName,
72 nsIFrame* aPrevFrame,
73 nsFrameList& aFrameList);
74 NS_IMETHOD RemoveFrame(nsIAtom* aListName,
75 nsIFrame* aOldFrame);
77 virtual nscoord GetMinWidth(nsIRenderingContext *aRenderingContext);
78 virtual nscoord GetPrefWidth(nsIRenderingContext *aRenderingContext);
80 virtual nsIFrame* GetContentInsertionFrame() {
81 nsIFrame* frame = GetFirstChild(nsnull);
83 // if no children return nsnull
84 if (!frame)
85 return nsnull;
87 return frame->GetContentInsertionFrame();
90 virtual nsresult StealFrame(nsPresContext* aPresContext,
91 nsIFrame* aChild,
92 PRBool aForceNormal)
93 { // nsColumnSetFrame keeps overflow containers in main child list
94 return nsContainerFrame::StealFrame(aPresContext, aChild, PR_TRUE);
97 NS_IMETHOD BuildDisplayList(nsDisplayListBuilder* aBuilder,
98 const nsRect& aDirtyRect,
99 const nsDisplayListSet& aLists);
101 virtual nsIAtom* GetType() const;
103 virtual void PaintColumnRule(nsIRenderingContext* aCtx,
104 const nsRect& aDirtyRect,
105 const nsPoint& aPt);
107 #ifdef DEBUG
108 NS_IMETHOD GetFrameName(nsAString& aResult) const {
109 return MakeFrameName(NS_LITERAL_STRING("ColumnSet"), aResult);
111 #endif
113 protected:
114 nscoord mLastBalanceHeight;
115 nsReflowStatus mLastFrameStatus;
117 virtual PRIntn GetSkipSides() const;
120 * These are the parameters that control the layout of columns.
122 struct ReflowConfig {
123 PRInt32 mBalanceColCount;
124 nscoord mColWidth;
125 nscoord mExpectedWidthLeftOver;
126 nscoord mColGap;
127 nscoord mColMaxHeight;
131 * Some data that is better calculated during reflow
133 struct ColumnBalanceData {
134 // The maximum "content height" of any column
135 nscoord mMaxHeight;
136 // The sum of the "content heights" for all columns
137 nscoord mSumHeight;
138 // The "content height" of the last column
139 nscoord mLastHeight;
140 // The maximum "content height" of all columns that overflowed
141 // their available height
142 nscoord mMaxOverflowingHeight;
143 void Reset() {
144 mMaxHeight = mSumHeight = mLastHeight = mMaxOverflowingHeight = 0;
149 * Similar to nsBlockFrame::DrainOverflowLines. Locate any columns not
150 * handled by our prev-in-flow, and any columns sitting on our own
151 * overflow list, and put them in our primary child list for reflowing.
153 void DrainOverflowColumns();
156 * The basic reflow strategy is to call this function repeatedly to
157 * obtain specific parameters that determine the layout of the
158 * columns. This function will compute those parameters from the CSS
159 * style. This function will also be responsible for implementing
160 * the state machine that controls column balancing.
162 ReflowConfig ChooseColumnStrategy(const nsHTMLReflowState& aReflowState);
165 * Reflow column children. Returns PR_TRUE iff the content that was reflowed
166 * fit into the mColMaxHeight.
168 PRBool ReflowChildren(nsHTMLReflowMetrics& aDesiredSize,
169 const nsHTMLReflowState& aReflowState,
170 nsReflowStatus& aStatus,
171 const ReflowConfig& aConfig,
172 PRBool aLastColumnUnbounded,
173 nsCollapsingMargin* aCarriedOutBottomMargin,
174 ColumnBalanceData& aColData);
178 * Tracking issues:
180 * XXX cursor movement around the top and bottom of colums seems to make the editor
181 * lose the caret.
183 * XXX should we support CSS columns applied to table elements?
185 nsIFrame*
186 NS_NewColumnSetFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, PRUint32 aStateFlags)
188 nsColumnSetFrame* it = new (aPresShell) nsColumnSetFrame(aContext);
189 if (it) {
190 // set the state flags (if any are provided)
191 it->AddStateBits(aStateFlags);
194 return it;
197 NS_IMPL_FRAMEARENA_HELPERS(nsColumnSetFrame)
199 nsColumnSetFrame::nsColumnSetFrame(nsStyleContext* aContext)
200 : nsHTMLContainerFrame(aContext), mLastBalanceHeight(NS_INTRINSICSIZE),
201 mLastFrameStatus(NS_FRAME_COMPLETE)
205 nsIAtom*
206 nsColumnSetFrame::GetType() const
208 return nsGkAtoms::columnSetFrame;
211 static void
212 PaintColumnRule(nsIFrame* aFrame, nsIRenderingContext* aCtx,
213 const nsRect& aDirtyRect, nsPoint aPt)
215 static_cast<nsColumnSetFrame*>(aFrame)->PaintColumnRule(aCtx, aDirtyRect, aPt);
218 void
219 nsColumnSetFrame::PaintColumnRule(nsIRenderingContext* aCtx,
220 const nsRect& aDirtyRect,
221 const nsPoint& aPt)
223 nsIFrame* child = mFrames.FirstChild();
224 if (!child)
225 return; // no columns
227 nsIFrame* nextSibling = child->GetNextSibling();
228 if (!nextSibling)
229 return; // 1 column only - this means no gap to draw on
231 PRBool isRTL = GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
232 const nsStyleColumn* colStyle = GetStyleColumn();
234 PRUint8 ruleStyle;
235 // Per spec, inset => ridge and outset => groove
236 if (colStyle->mColumnRuleStyle == NS_STYLE_BORDER_STYLE_INSET)
237 ruleStyle = NS_STYLE_BORDER_STYLE_RIDGE;
238 else if (colStyle->mColumnRuleStyle == NS_STYLE_BORDER_STYLE_OUTSET)
239 ruleStyle = NS_STYLE_BORDER_STYLE_GROOVE;
240 else
241 ruleStyle = colStyle->mColumnRuleStyle;
243 nsPresContext* presContext = PresContext();
244 nscoord ruleWidth = colStyle->GetComputedColumnRuleWidth();
245 if (!ruleWidth)
246 return;
248 nscolor ruleColor =
249 GetVisitedDependentColor(eCSSProperty__moz_column_rule_color);
251 // In order to re-use a large amount of code, we treat the column rule as a border.
252 // We create a new border style object and fill in all the details of the column rule as
253 // the left border. PaintBorder() does all the rendering for us, so we not
254 // only save an enormous amount of code but we'll support all the line styles that
255 // we support on borders!
256 nsStyleBorder border(presContext);
257 border.SetBorderWidth(NS_SIDE_LEFT, ruleWidth);
258 border.SetBorderStyle(NS_SIDE_LEFT, ruleStyle);
259 border.SetBorderColor(NS_SIDE_LEFT, ruleColor);
261 // Get our content rect as an absolute coordinate, not relative to
262 // our parent (which is what the X and Y normally is)
263 nsRect contentRect = GetContentRect() - GetRect().TopLeft() + aPt;
264 nsSize ruleSize(ruleWidth, contentRect.height);
266 while (nextSibling) {
267 // The frame tree goes RTL in RTL
268 nsIFrame* leftSibling = isRTL ? nextSibling : child;
269 nsIFrame* rightSibling = isRTL ? child : nextSibling;
271 // Each child frame's position coordinates is actually relative to this nsColumnSetFrame.
272 // linePt will be at the top-left edge to paint the line.
273 nsPoint edgeOfLeftSibling = leftSibling->GetRect().TopRight() + aPt;
274 nsPoint edgeOfRightSibling = rightSibling->GetRect().TopLeft() + aPt;
275 nsPoint linePt((edgeOfLeftSibling.x + edgeOfRightSibling.x - ruleWidth) / 2,
276 contentRect.y);
278 nsRect lineRect(linePt, ruleSize);
279 nsCSSRendering::PaintBorderWithStyleBorder(presContext, *aCtx, this,
280 aDirtyRect, lineRect, border, GetStyleContext(),
281 // Remember, we only have the "left" "border". Skip everything else
282 (1 << NS_SIDE_TOP | 1 << NS_SIDE_RIGHT | 1 << NS_SIDE_BOTTOM));
284 child = nextSibling;
285 nextSibling = nextSibling->GetNextSibling();
289 NS_IMETHODIMP
290 nsColumnSetFrame::SetInitialChildList(nsIAtom* aListName,
291 nsFrameList& aChildList)
293 NS_ASSERTION(!aListName, "Only default child list supported");
294 NS_ASSERTION(aChildList.OnlyChild(),
295 "initial child list must have exactly one child");
296 // Queue up the frames for the content frame
297 return nsHTMLContainerFrame::SetInitialChildList(nsnull, aChildList);
300 static nscoord
301 GetAvailableContentWidth(const nsHTMLReflowState& aReflowState)
303 if (aReflowState.availableWidth == NS_INTRINSICSIZE) {
304 return NS_INTRINSICSIZE;
306 nscoord borderPaddingWidth =
307 aReflowState.mComputedBorderPadding.left +
308 aReflowState.mComputedBorderPadding.right;
309 return NS_MAX(0, aReflowState.availableWidth - borderPaddingWidth);
312 static nscoord
313 GetAvailableContentHeight(const nsHTMLReflowState& aReflowState)
315 if (aReflowState.availableHeight == NS_INTRINSICSIZE) {
316 return NS_INTRINSICSIZE;
318 nscoord borderPaddingHeight =
319 aReflowState.mComputedBorderPadding.top +
320 aReflowState.mComputedBorderPadding.bottom;
321 return NS_MAX(0, aReflowState.availableHeight - borderPaddingHeight);
324 static nscoord
325 GetColumnGap(nsColumnSetFrame* aFrame,
326 const nsStyleColumn* aColStyle)
328 if (eStyleUnit_Normal == aColStyle->mColumnGap.GetUnit())
329 return aFrame->GetStyleFont()->mFont.size;
330 if (eStyleUnit_Coord == aColStyle->mColumnGap.GetUnit()) {
331 nscoord colGap = aColStyle->mColumnGap.GetCoordValue();
332 NS_ASSERTION(colGap >= 0, "negative column gap");
333 return colGap;
336 NS_NOTREACHED("Unknown gap type");
337 return 0;
340 nsColumnSetFrame::ReflowConfig
341 nsColumnSetFrame::ChooseColumnStrategy(const nsHTMLReflowState& aReflowState)
343 const nsStyleColumn* colStyle = GetStyleColumn();
344 nscoord availContentWidth = GetAvailableContentWidth(aReflowState);
345 if (aReflowState.ComputedWidth() != NS_INTRINSICSIZE) {
346 availContentWidth = aReflowState.ComputedWidth();
348 nscoord colHeight = GetAvailableContentHeight(aReflowState);
349 if (aReflowState.ComputedHeight() != NS_INTRINSICSIZE) {
350 colHeight = aReflowState.ComputedHeight();
353 nscoord colGap = GetColumnGap(this, colStyle);
354 PRInt32 numColumns = colStyle->mColumnCount;
356 nscoord colWidth;
357 if (colStyle->mColumnWidth.GetUnit() == eStyleUnit_Coord) {
358 colWidth = colStyle->mColumnWidth.GetCoordValue();
359 NS_ASSERTION(colWidth >= 0, "negative column width");
360 // Reduce column count if necessary to make columns fit in the
361 // available width. Compute max number of columns that fit in
362 // availContentWidth, satisfying colGap*(maxColumns - 1) +
363 // colWidth*maxColumns <= availContentWidth
364 if (availContentWidth != NS_INTRINSICSIZE && colGap + colWidth > 0
365 && numColumns > 0) {
366 // This expression uses truncated rounding, which is what we
367 // want
368 PRInt32 maxColumns = (availContentWidth + colGap)/(colGap + colWidth);
369 numColumns = NS_MAX(1, NS_MIN(numColumns, maxColumns));
371 } else if (numColumns > 0 && availContentWidth != NS_INTRINSICSIZE) {
372 nscoord widthMinusGaps = availContentWidth - colGap*(numColumns - 1);
373 colWidth = widthMinusGaps/numColumns;
374 } else {
375 colWidth = NS_INTRINSICSIZE;
377 // Take care of the situation where there's only one column but it's
378 // still too wide
379 colWidth = NS_MAX(1, NS_MIN(colWidth, availContentWidth));
381 nscoord expectedWidthLeftOver = 0;
383 if (colWidth != NS_INTRINSICSIZE && availContentWidth != NS_INTRINSICSIZE) {
384 // distribute leftover space
386 // First, determine how many columns will be showing if the column
387 // count is auto
388 if (numColumns <= 0) {
389 // choose so that colGap*(nominalColumnCount - 1) +
390 // colWidth*nominalColumnCount is nearly availContentWidth
391 // make sure to round down
392 if (colGap + colWidth > 0) {
393 numColumns = (availContentWidth + colGap)/(colGap + colWidth);
395 if (numColumns <= 0) {
396 numColumns = 1;
400 // Compute extra space and divide it among the columns
401 nscoord extraSpace =
402 NS_MAX(0, availContentWidth - (colWidth*numColumns + colGap*(numColumns - 1)));
403 nscoord extraToColumns = extraSpace/numColumns;
404 colWidth += extraToColumns;
405 expectedWidthLeftOver = extraSpace - (extraToColumns*numColumns);
408 // NOTE that the non-balancing behavior for non-auto computed height
409 // is not in the CSS3 columns draft as of 18 January 2001
410 if (aReflowState.ComputedHeight() == NS_INTRINSICSIZE) {
411 // Balancing!
412 if (numColumns <= 0) {
413 // Hmm, auto column count, column width or available width is unknown,
414 // and balancing is required. Let's just use one column then.
415 numColumns = 1;
417 colHeight = NS_MIN(mLastBalanceHeight, GetAvailableContentHeight(aReflowState));
418 } else {
419 // No balancing, so don't limit the column count
420 numColumns = PR_INT32_MAX;
423 #ifdef DEBUG_roc
424 printf("*** nsColumnSetFrame::ChooseColumnStrategy: numColumns=%d, colWidth=%d, expectedWidthLeftOver=%d, colHeight=%d, colGap=%d\n",
425 numColumns, colWidth, expectedWidthLeftOver, colHeight, colGap);
426 #endif
427 ReflowConfig config = { numColumns, colWidth, expectedWidthLeftOver, colGap, colHeight };
428 return config;
431 // XXX copied from nsBlockFrame, should this be moved to nsContainerFrame?
432 static void
433 PlaceFrameView(nsIFrame* aFrame)
435 if (aFrame->HasView())
436 nsContainerFrame::PositionFrameView(aFrame);
437 else
438 nsContainerFrame::PositionChildViews(aFrame);
441 static void MoveChildTo(nsIFrame* aParent, nsIFrame* aChild, nsPoint aOrigin) {
442 if (aChild->GetPosition() == aOrigin) {
443 return;
446 nsRect r = aChild->GetVisualOverflowRect();
447 r += aChild->GetPosition();
448 aParent->Invalidate(r);
449 r -= aChild->GetPosition();
450 aChild->SetPosition(aOrigin);
451 r += aOrigin;
452 aParent->Invalidate(r);
453 PlaceFrameView(aChild);
456 nscoord
457 nsColumnSetFrame::GetMinWidth(nsIRenderingContext *aRenderingContext) {
458 nscoord width = 0;
459 DISPLAY_MIN_WIDTH(this, width);
460 if (mFrames.FirstChild()) {
461 width = mFrames.FirstChild()->GetMinWidth(aRenderingContext);
463 const nsStyleColumn* colStyle = GetStyleColumn();
464 nscoord colWidth;
465 if (colStyle->mColumnWidth.GetUnit() == eStyleUnit_Coord) {
466 colWidth = colStyle->mColumnWidth.GetCoordValue();
467 // As available width reduces to zero, we reduce our number of columns
468 // to one, and don't enforce the column width, so just return the min
469 // of the child's min-width with any specified column width.
470 width = NS_MIN(width, colWidth);
471 } else {
472 NS_ASSERTION(colStyle->mColumnCount > 0,
473 "column-count and column-width can't both be auto");
474 // As available width reduces to zero, we still have mColumnCount columns,
475 // so multiply the child's min-width by the number of columns.
476 colWidth = width;
477 width *= colStyle->mColumnCount;
478 // The multiplication above can make 'width' negative (integer overflow),
479 // so use NS_MAX to protect against that.
480 width = NS_MAX(width, colWidth);
482 // XXX count forced column breaks here? Maybe we should return the child's
483 // min-width times the minimum number of columns.
484 return width;
487 nscoord
488 nsColumnSetFrame::GetPrefWidth(nsIRenderingContext *aRenderingContext) {
489 // Our preferred width is our desired column width, if specified, otherwise
490 // the child's preferred width, times the number of columns, plus the width
491 // of any required column gaps
492 // XXX what about forced column breaks here?
493 nscoord result = 0;
494 DISPLAY_PREF_WIDTH(this, result);
495 const nsStyleColumn* colStyle = GetStyleColumn();
496 nscoord colGap = GetColumnGap(this, colStyle);
498 nscoord colWidth;
499 if (colStyle->mColumnWidth.GetUnit() == eStyleUnit_Coord) {
500 colWidth = colStyle->mColumnWidth.GetCoordValue();
501 } else if (mFrames.FirstChild()) {
502 colWidth = mFrames.FirstChild()->GetPrefWidth(aRenderingContext);
503 } else {
504 colWidth = 0;
507 PRInt32 numColumns = colStyle->mColumnCount;
508 if (numColumns <= 0) {
509 // if column-count is auto, assume one column
510 numColumns = 1;
513 nscoord width = colWidth*numColumns + colGap*(numColumns - 1);
514 // The multiplication above can make 'width' negative (integer overflow),
515 // so use NS_MAX to protect against that.
516 result = NS_MAX(width, colWidth);
517 return result;
520 PRBool
521 nsColumnSetFrame::ReflowChildren(nsHTMLReflowMetrics& aDesiredSize,
522 const nsHTMLReflowState& aReflowState,
523 nsReflowStatus& aStatus,
524 const ReflowConfig& aConfig,
525 PRBool aUnboundedLastColumn,
526 nsCollapsingMargin* aBottomMarginCarriedOut,
527 ColumnBalanceData& aColData)
529 aColData.Reset();
530 PRBool allFit = PR_TRUE;
531 PRBool RTL = GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
532 PRBool shrinkingHeightOnly = !NS_SUBTREE_DIRTY(this) &&
533 mLastBalanceHeight > aConfig.mColMaxHeight;
535 #ifdef DEBUG_roc
536 printf("*** Doing column reflow pass: mLastBalanceHeight=%d, mColMaxHeight=%d, RTL=%d\n, mBalanceColCount=%d, mColWidth=%d, mColGap=%d\n",
537 mLastBalanceHeight, aConfig.mColMaxHeight, RTL, aConfig.mBalanceColCount,
538 aConfig.mColWidth, aConfig.mColGap);
539 #endif
541 DrainOverflowColumns();
543 if (mLastBalanceHeight != aConfig.mColMaxHeight) {
544 mLastBalanceHeight = aConfig.mColMaxHeight;
545 // XXX Seems like this could fire if incremental reflow pushed the column set
546 // down so we reflow incrementally with a different available height.
547 // We need a way to do an incremental reflow and be sure availableHeight
548 // changes are taken account of! Right now I think block frames with absolute
549 // children might exit early.
550 //NS_ASSERTION(aKidReason != eReflowReason_Incremental,
551 // "incremental reflow should not have changed the balance height");
554 // get our border and padding
555 const nsMargin &borderPadding = aReflowState.mComputedBorderPadding;
557 nsRect contentRect(0, 0, 0, 0);
558 nsOverflowAreas overflowRects;
560 nsIFrame* child = mFrames.FirstChild();
561 nsPoint childOrigin = nsPoint(borderPadding.left, borderPadding.top);
562 // For RTL, figure out where the last column's left edge should be. Since the
563 // columns might not fill the frame exactly, we need to account for the
564 // slop. Otherwise we'll waste time moving the columns by some tiny
565 // amount unnecessarily.
566 nscoord targetX = borderPadding.left;
567 if (RTL) {
568 nscoord availWidth = aReflowState.availableWidth;
569 if (aReflowState.ComputedWidth() != NS_INTRINSICSIZE) {
570 availWidth = aReflowState.ComputedWidth();
572 if (availWidth != NS_INTRINSICSIZE) {
573 childOrigin.x += availWidth - aConfig.mColWidth;
574 targetX += aConfig.mExpectedWidthLeftOver;
575 #ifdef DEBUG_roc
576 printf("*** childOrigin.x = %d\n", childOrigin.x);
577 #endif
580 int columnCount = 0;
581 int contentBottom = 0;
582 PRBool reflowNext = PR_FALSE;
584 while (child) {
585 // Try to skip reflowing the child. We can't skip if the child is dirty. We also can't
586 // skip if the next column is dirty, because the next column's first line(s)
587 // might be pullable back to this column. We can't skip if it's the last child
588 // because we need to obtain the bottom margin. We can't skip
589 // if this is the last column and we're supposed to assign unbounded
590 // height to it, because that could change the available height from
591 // the last time we reflowed it and we should try to pull all the
592 // content from its next sibling. (Note that it might be the last
593 // column, but not be the last child because the desired number of columns
594 // has changed.)
595 PRBool skipIncremental = !aReflowState.ShouldReflowAllKids()
596 && !NS_SUBTREE_DIRTY(child)
597 && child->GetNextSibling()
598 && !(aUnboundedLastColumn && columnCount == aConfig.mBalanceColCount - 1)
599 && !NS_SUBTREE_DIRTY(child->GetNextSibling());
600 // If we need to pull up content from the prev-in-flow then this is not just
601 // a height shrink. The prev in flow will have set the dirty bit.
602 // Check the overflow rect YMost instead of just the child's content height. The child
603 // may have overflowing content that cares about the available height boundary.
604 // (It may also have overflowing content that doesn't care about the available height
605 // boundary, but if so, too bad, this optimization is defeated.)
606 // We want scrollable overflow here since this is a calculation that
607 // affects layout.
608 PRBool skipResizeHeightShrink = shrinkingHeightOnly
609 && child->GetScrollableOverflowRect().YMost() <= aConfig.mColMaxHeight;
611 nscoord childContentBottom = 0;
612 if (!reflowNext && (skipIncremental || skipResizeHeightShrink)) {
613 // This child does not need to be reflowed, but we may need to move it
614 MoveChildTo(this, child, childOrigin);
616 // If this is the last frame then make sure we get the right status
617 nsIFrame* kidNext = child->GetNextSibling();
618 if (kidNext) {
619 aStatus = (kidNext->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)
620 ? NS_FRAME_OVERFLOW_INCOMPLETE
621 : NS_FRAME_NOT_COMPLETE;
622 } else {
623 aStatus = mLastFrameStatus;
625 childContentBottom = nsLayoutUtils::CalculateContentBottom(child);
626 #ifdef DEBUG_roc
627 printf("*** Skipping child #%d %p (incremental %d, resize height shrink %d): status = %d\n",
628 columnCount, (void*)child, skipIncremental, skipResizeHeightShrink, aStatus);
629 #endif
630 } else {
631 nsSize availSize(aConfig.mColWidth, aConfig.mColMaxHeight);
633 if (aUnboundedLastColumn && columnCount == aConfig.mBalanceColCount - 1) {
634 availSize.height = GetAvailableContentHeight(aReflowState);
637 if (reflowNext)
638 child->AddStateBits(NS_FRAME_IS_DIRTY);
640 nsHTMLReflowState kidReflowState(PresContext(), aReflowState, child,
641 availSize, availSize.width,
642 aReflowState.ComputedHeight());
643 kidReflowState.mFlags.mIsTopOfPage = PR_TRUE;
644 kidReflowState.mFlags.mTableIsSplittable = PR_FALSE;
646 #ifdef DEBUG_roc
647 printf("*** Reflowing child #%d %p: availHeight=%d\n",
648 columnCount, (void*)child,availSize.height);
649 #endif
651 // Note if the column's next in flow is not being changed by this incremental reflow.
652 // This may allow the current column to avoid trying to pull lines from the next column.
653 if (child->GetNextSibling() &&
654 !(GetStateBits() & NS_FRAME_IS_DIRTY) &&
655 !(child->GetNextSibling()->GetStateBits() & NS_FRAME_IS_DIRTY)) {
656 kidReflowState.mFlags.mNextInFlowUntouched = PR_TRUE;
659 nsHTMLReflowMetrics kidDesiredSize(aDesiredSize.mFlags);
661 // XXX it would be cool to consult the float manager for the
662 // previous block to figure out the region of floats from the
663 // previous column that extend into this column, and subtract
664 // that region from the new float manager. So you could stick a
665 // really big float in the first column and text in following
666 // columns would flow around it.
668 // Reflow the frame
669 ReflowChild(child, PresContext(), kidDesiredSize, kidReflowState,
670 childOrigin.x + kidReflowState.mComputedMargin.left,
671 childOrigin.y + kidReflowState.mComputedMargin.top,
672 0, aStatus);
674 reflowNext = (aStatus & NS_FRAME_REFLOW_NEXTINFLOW) != 0;
676 #ifdef DEBUG_roc
677 printf("*** Reflowed child #%d %p: status = %d, desiredSize=%d,%d\n",
678 columnCount, (void*)child, aStatus, kidDesiredSize.width, kidDesiredSize.height);
679 #endif
681 NS_FRAME_TRACE_REFLOW_OUT("Column::Reflow", aStatus);
683 *aBottomMarginCarriedOut = kidDesiredSize.mCarriedOutBottomMargin;
685 FinishReflowChild(child, PresContext(), &kidReflowState,
686 kidDesiredSize, childOrigin.x, childOrigin.y, 0);
688 childContentBottom = nsLayoutUtils::CalculateContentBottom(child);
689 if (childContentBottom > aConfig.mColMaxHeight) {
690 allFit = PR_FALSE;
692 if (childContentBottom > availSize.height) {
693 aColData.mMaxOverflowingHeight = NS_MAX(childContentBottom,
694 aColData.mMaxOverflowingHeight);
698 contentRect.UnionRect(contentRect, child->GetRect());
700 ConsiderChildOverflow(overflowRects, child);
701 contentBottom = NS_MAX(contentBottom, childContentBottom);
702 aColData.mLastHeight = childContentBottom;
703 aColData.mSumHeight += childContentBottom;
705 // Build a continuation column if necessary
706 nsIFrame* kidNextInFlow = child->GetNextInFlow();
708 if (NS_FRAME_IS_FULLY_COMPLETE(aStatus) && !NS_FRAME_IS_TRUNCATED(aStatus)) {
709 NS_ASSERTION(!kidNextInFlow, "next in flow should have been deleted");
710 child = nsnull;
711 break;
712 } else {
713 ++columnCount;
714 // Make sure that the column has a next-in-flow. If not, we must
715 // create one to hold the overflowing stuff, even if we're just
716 // going to put it on our overflow list and let *our*
717 // next in flow handle it.
718 if (!kidNextInFlow) {
719 NS_ASSERTION(aStatus & NS_FRAME_REFLOW_NEXTINFLOW,
720 "We have to create a continuation, but the block doesn't want us to reflow it?");
722 // We need to create a continuing column
723 nsresult rv = CreateNextInFlow(PresContext(), child, kidNextInFlow);
725 if (NS_FAILED(rv)) {
726 NS_NOTREACHED("Couldn't create continuation");
727 child = nsnull;
728 break;
732 // Make sure we reflow a next-in-flow when it switches between being
733 // normal or overflow container
734 if (NS_FRAME_OVERFLOW_IS_INCOMPLETE(aStatus)) {
735 if (!(kidNextInFlow->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)) {
736 aStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
737 reflowNext = PR_TRUE;
738 kidNextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
741 else if (kidNextInFlow->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) {
742 aStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
743 reflowNext = PR_TRUE;
744 kidNextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
747 if (columnCount >= aConfig.mBalanceColCount) {
748 // No more columns allowed here. Stop.
749 aStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
750 kidNextInFlow->AddStateBits(NS_FRAME_IS_DIRTY);
752 // Move any of our leftover columns to our overflow list. Our
753 // next-in-flow will eventually pick them up.
754 const nsFrameList& continuationColumns = mFrames.RemoveFramesAfter(child);
755 if (continuationColumns.NotEmpty()) {
756 SetOverflowFrames(PresContext(), continuationColumns);
758 child = nsnull;
759 break;
763 if (PresContext()->HasPendingInterrupt()) {
764 // Stop the loop now while |child| still points to the frame that bailed
765 // out. We could keep going here and condition a bunch of the code in
766 // this loop on whether there's an interrupt, or even just keep going and
767 // trying to reflow the blocks (even though we know they'll interrupt
768 // right after their first line), but stopping now is conceptually the
769 // simplest (and probably fastest) thing.
770 break;
773 // Advance to the next column
774 child = child->GetNextSibling();
776 if (child) {
777 if (!RTL) {
778 childOrigin.x += aConfig.mColWidth + aConfig.mColGap;
779 } else {
780 childOrigin.x -= aConfig.mColWidth + aConfig.mColGap;
783 #ifdef DEBUG_roc
784 printf("*** NEXT CHILD ORIGIN.x = %d\n", childOrigin.x);
785 #endif
789 if (PresContext()->CheckForInterrupt(this) &&
790 (GetStateBits() & NS_FRAME_IS_DIRTY)) {
791 // Mark all our kids starting with |child| dirty
793 // Note that this is a CheckForInterrupt call, not a HasPendingInterrupt,
794 // because we might have interrupted while reflowing |child|, and since
795 // we're about to add a dirty bit to |child| we need to make sure that
796 // |this| is scheduled to have dirty bits marked on it and its ancestors.
797 // Otherwise, when we go to mark dirty bits on |child|'s ancestors we'll
798 // bail out immediately, since it'll already have a dirty bit.
799 for (; child; child = child->GetNextSibling()) {
800 child->AddStateBits(NS_FRAME_IS_DIRTY);
804 // If we're doing RTL, we need to make sure our last column is at the left-hand side of the frame.
805 if (RTL && childOrigin.x != targetX) {
806 overflowRects.Clear();
807 contentRect = nsRect(0, 0, 0, 0);
808 PRInt32 deltaX = targetX - childOrigin.x;
809 #ifdef DEBUG_roc
810 printf("*** CHILDORIGIN.x = %d, targetX = %d, DELTAX = %d\n", childOrigin.x, targetX, deltaX);
811 #endif
812 for (child = mFrames.FirstChild(); child; child = child->GetNextSibling()) {
813 MoveChildTo(this, child, child->GetPosition() + nsPoint(deltaX, 0));
814 ConsiderChildOverflow(overflowRects, child);
815 contentRect.UnionRect(contentRect, child->GetRect());
818 aColData.mMaxHeight = contentBottom;
819 contentRect.height = NS_MAX(contentRect.height, contentBottom);
820 mLastFrameStatus = aStatus;
822 // contentRect included the borderPadding.left,borderPadding.top of the child rects
823 contentRect -= nsPoint(borderPadding.left, borderPadding.top);
825 nsSize contentSize = nsSize(contentRect.XMost(), contentRect.YMost());
827 // Apply computed and min/max values
828 if (aReflowState.ComputedHeight() != NS_INTRINSICSIZE) {
829 contentSize.height = aReflowState.ComputedHeight();
830 } else {
831 if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMaxHeight) {
832 contentSize.height = NS_MIN(aReflowState.mComputedMaxHeight, contentSize.height);
834 if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMinHeight) {
835 contentSize.height = NS_MAX(aReflowState.mComputedMinHeight, contentSize.height);
838 if (aReflowState.ComputedWidth() != NS_INTRINSICSIZE) {
839 contentSize.width = aReflowState.ComputedWidth();
840 } else {
841 if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMaxWidth) {
842 contentSize.width = NS_MIN(aReflowState.mComputedMaxWidth, contentSize.width);
844 if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMinWidth) {
845 contentSize.width = NS_MAX(aReflowState.mComputedMinWidth, contentSize.width);
849 aDesiredSize.height = borderPadding.top + contentSize.height +
850 borderPadding.bottom;
851 aDesiredSize.width = contentSize.width + borderPadding.left + borderPadding.right;
852 aDesiredSize.mOverflowAreas = overflowRects;
853 aDesiredSize.UnionOverflowAreasWithDesiredBounds();
855 #ifdef DEBUG_roc
856 printf("*** DONE PASS feasible=%d\n", allFit && NS_FRAME_IS_FULLY_COMPLETE(aStatus)
857 && !NS_FRAME_IS_TRUNCATED(aStatus));
858 #endif
859 return allFit && NS_FRAME_IS_FULLY_COMPLETE(aStatus)
860 && !NS_FRAME_IS_TRUNCATED(aStatus);
863 void
864 nsColumnSetFrame::DrainOverflowColumns()
866 // First grab the prev-in-flows overflows and reparent them to this
867 // frame.
868 nsColumnSetFrame* prev = static_cast<nsColumnSetFrame*>(GetPrevInFlow());
869 if (prev) {
870 nsAutoPtr<nsFrameList> overflows(prev->StealOverflowFrames());
871 if (overflows) {
872 nsHTMLContainerFrame::ReparentFrameViewList(PresContext(), *overflows,
873 prev, this);
875 mFrames.InsertFrames(this, nsnull, *overflows);
879 // Now pull back our own overflows and append them to our children.
880 // We don't need to reparent them since we're already their parent.
881 nsAutoPtr<nsFrameList> overflows(StealOverflowFrames());
882 if (overflows) {
883 // We're already the parent for these frames, so no need to set
884 // their parent again.
885 mFrames.AppendFrames(nsnull, *overflows);
889 NS_IMETHODIMP
890 nsColumnSetFrame::Reflow(nsPresContext* aPresContext,
891 nsHTMLReflowMetrics& aDesiredSize,
892 const nsHTMLReflowState& aReflowState,
893 nsReflowStatus& aStatus)
895 // Don't support interruption in columns
896 nsPresContext::InterruptPreventer noInterrupts(aPresContext);
898 DO_GLOBAL_REFLOW_COUNT("nsColumnSetFrame");
899 DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
901 // Initialize OUT parameter
902 aStatus = NS_FRAME_COMPLETE;
904 // Our children depend on our height if we have a fixed height.
905 if (aReflowState.ComputedHeight() != NS_AUTOHEIGHT) {
906 NS_ASSERTION(aReflowState.ComputedHeight() != NS_INTRINSICSIZE,
907 "Unexpected mComputedHeight");
908 AddStateBits(NS_FRAME_CONTAINS_RELATIVE_HEIGHT);
910 else {
911 RemoveStateBits(NS_FRAME_CONTAINS_RELATIVE_HEIGHT);
914 //------------ Handle Incremental Reflow -----------------
916 ReflowConfig config = ChooseColumnStrategy(aReflowState);
917 PRBool isBalancing = config.mBalanceColCount < PR_INT32_MAX;
919 // If balancing, then we allow the last column to grow to unbounded
920 // height during the first reflow. This gives us a way to estimate
921 // what the average column height should be, because we can measure
922 // the heights of all the columns and sum them up. But don't do this
923 // if we have a next in flow because we don't want to suck all its
924 // content back here and then have to push it out again!
925 nsIFrame* nextInFlow = GetNextInFlow();
926 PRBool unboundedLastColumn = isBalancing && !nextInFlow;
927 nsCollapsingMargin carriedOutBottomMargin;
928 ColumnBalanceData colData;
929 PRBool feasible = ReflowChildren(aDesiredSize, aReflowState,
930 aStatus, config, unboundedLastColumn, &carriedOutBottomMargin, colData);
932 if (isBalancing && !aPresContext->HasPendingInterrupt()) {
933 nscoord availableContentHeight = GetAvailableContentHeight(aReflowState);
935 // Termination of the algorithm below is guaranteed because
936 // knownFeasibleHeight - knownInfeasibleHeight decreases in every
937 // iteration.
938 nscoord knownFeasibleHeight = NS_INTRINSICSIZE;
939 nscoord knownInfeasibleHeight = 0;
940 // We set this flag when we detect that we may contain a frame
941 // that can break anywhere (thus foiling the linear decrease-by-one
942 // search)
943 PRBool maybeContinuousBreakingDetected = PR_FALSE;
945 while (!aPresContext->HasPendingInterrupt()) {
946 nscoord lastKnownFeasibleHeight = knownFeasibleHeight;
948 // Record what we learned from the last reflow
949 if (feasible) {
950 // maxHeight is feasible. Also, mLastBalanceHeight is feasible.
951 knownFeasibleHeight = NS_MIN(knownFeasibleHeight, colData.mMaxHeight);
952 knownFeasibleHeight = NS_MIN(knownFeasibleHeight, mLastBalanceHeight);
954 // Furthermore, no height less than the height of the last
955 // column can ever be feasible. (We might be able to reduce the
956 // height of a non-last column by moving content to a later column,
957 // but we can't do that with the last column.)
958 if (mFrames.GetLength() == config.mBalanceColCount) {
959 knownInfeasibleHeight = NS_MAX(knownInfeasibleHeight,
960 colData.mLastHeight - 1);
962 } else {
963 knownInfeasibleHeight = NS_MAX(knownInfeasibleHeight, mLastBalanceHeight);
964 // If a column didn't fit in its available height, then its current
965 // height must be the minimum height for unbreakable content in
966 // the column, and therefore no smaller height can be feasible.
967 knownInfeasibleHeight = NS_MAX(knownInfeasibleHeight,
968 colData.mMaxOverflowingHeight - 1);
970 if (unboundedLastColumn) {
971 // The last column is unbounded, so all content got reflowed, so the
972 // mColMaxHeight is feasible.
973 knownFeasibleHeight = NS_MIN(knownFeasibleHeight,
974 colData.mMaxHeight);
978 #ifdef DEBUG_roc
979 printf("*** nsColumnSetFrame::Reflow balancing knownInfeasible=%d knownFeasible=%d\n",
980 knownInfeasibleHeight, knownFeasibleHeight);
981 #endif
983 if (knownInfeasibleHeight >= knownFeasibleHeight - 1) {
984 // knownFeasibleHeight is where we want to be
985 break;
988 if (knownInfeasibleHeight >= availableContentHeight) {
989 break;
992 if (lastKnownFeasibleHeight - knownFeasibleHeight == 1) {
993 // We decreased the feasible height by one twip only. This could
994 // indicate that there is a continuously breakable child frame
995 // that we are crawling through.
996 maybeContinuousBreakingDetected = PR_TRUE;
999 nscoord nextGuess = (knownFeasibleHeight + knownInfeasibleHeight)/2;
1000 // The constant of 600 twips is arbitrary. It's about two line-heights.
1001 if (knownFeasibleHeight - nextGuess < 600 &&
1002 !maybeContinuousBreakingDetected) {
1003 // We're close to our target, so just try shrinking just the
1004 // minimum amount that will cause one of our columns to break
1005 // differently.
1006 nextGuess = knownFeasibleHeight - 1;
1007 } else if (unboundedLastColumn) {
1008 // Make a guess by dividing that into N columns. Add some slop
1009 // to try to make it on the feasible side. The constant of
1010 // 600 twips is arbitrary. It's about two line-heights.
1011 nextGuess = colData.mSumHeight/config.mBalanceColCount + 600;
1012 // Sanitize it
1013 nextGuess = NS_MIN(NS_MAX(nextGuess, knownInfeasibleHeight + 1),
1014 knownFeasibleHeight - 1);
1015 } else if (knownFeasibleHeight == NS_INTRINSICSIZE) {
1016 // This can happen when we had a next-in-flow so we didn't
1017 // want to do an unbounded height measuring step. Let's just increase
1018 // from the infeasible height by some reasonable amount.
1019 nextGuess = knownInfeasibleHeight*2 + 600;
1021 // Don't bother guessing more than our height constraint.
1022 nextGuess = NS_MIN(availableContentHeight, nextGuess);
1024 #ifdef DEBUG_roc
1025 printf("*** nsColumnSetFrame::Reflow balancing choosing next guess=%d\n", nextGuess);
1026 #endif
1028 config.mColMaxHeight = nextGuess;
1030 unboundedLastColumn = PR_FALSE;
1031 AddStateBits(NS_FRAME_IS_DIRTY);
1032 feasible = ReflowChildren(aDesiredSize, aReflowState,
1033 aStatus, config, PR_FALSE,
1034 &carriedOutBottomMargin, colData);
1037 if (!feasible && !aPresContext->HasPendingInterrupt()) {
1038 // We may need to reflow one more time at the feasible height to
1039 // get a valid layout.
1040 PRBool skip = PR_FALSE;
1041 if (knownInfeasibleHeight >= availableContentHeight) {
1042 config.mColMaxHeight = availableContentHeight;
1043 if (mLastBalanceHeight == availableContentHeight) {
1044 skip = PR_TRUE;
1046 } else {
1047 config.mColMaxHeight = knownFeasibleHeight;
1049 if (!skip) {
1050 // If our height is unconstrained, make sure that the last column is
1051 // allowed to have arbitrary height here, even though we were balancing.
1052 // Otherwise we'd have to split, and it's not clear what we'd do with
1053 // that.
1054 AddStateBits(NS_FRAME_IS_DIRTY);
1055 ReflowChildren(aDesiredSize, aReflowState, aStatus, config,
1056 availableContentHeight == NS_UNCONSTRAINEDSIZE,
1057 &carriedOutBottomMargin, colData);
1062 if (aPresContext->HasPendingInterrupt() &&
1063 aReflowState.availableHeight == NS_UNCONSTRAINEDSIZE) {
1064 // In this situation, we might be lying about our reflow status, because
1065 // our last kid (the one that got interrupted) was incomplete. Fix that.
1066 aStatus = NS_FRAME_COMPLETE;
1069 CheckInvalidateSizeChange(aDesiredSize);
1071 FinishAndStoreOverflow(&aDesiredSize);
1072 aDesiredSize.mCarriedOutBottomMargin = carriedOutBottomMargin;
1074 NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
1076 NS_ASSERTION(NS_FRAME_IS_FULLY_COMPLETE(aStatus) ||
1077 aReflowState.availableHeight != NS_UNCONSTRAINEDSIZE,
1078 "Column set should be complete if the available height is unconstrained");
1080 return NS_OK;
1083 NS_IMETHODIMP
1084 nsColumnSetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1085 const nsRect& aDirtyRect,
1086 const nsDisplayListSet& aLists) {
1087 nsresult rv = DisplayBorderBackgroundOutline(aBuilder, aLists);
1088 NS_ENSURE_SUCCESS(rv, rv);
1090 aLists.BorderBackground()->AppendNewToTop(new (aBuilder)
1091 nsDisplayGeneric(aBuilder, this, ::PaintColumnRule, "ColumnRule",
1092 nsDisplayItem::TYPE_COLUMN_RULE));
1094 nsIFrame* kid = mFrames.FirstChild();
1095 // Our children won't have backgrounds so it doesn't matter where we put them.
1096 while (kid) {
1097 nsresult rv = BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists);
1098 NS_ENSURE_SUCCESS(rv, rv);
1099 kid = kid->GetNextSibling();
1101 return NS_OK;
1104 PRIntn
1105 nsColumnSetFrame::GetSkipSides() const
1107 return 0;
1110 NS_IMETHODIMP
1111 nsColumnSetFrame::AppendFrames(nsIAtom* aListName,
1112 nsFrameList& aFrameList)
1114 NS_NOTREACHED("AppendFrames not supported");
1115 return NS_ERROR_NOT_IMPLEMENTED;
1118 NS_IMETHODIMP
1119 nsColumnSetFrame::InsertFrames(nsIAtom* aListName,
1120 nsIFrame* aPrevFrame,
1121 nsFrameList& aFrameList)
1123 NS_NOTREACHED("InsertFrames not supported");
1124 return NS_ERROR_NOT_IMPLEMENTED;
1127 NS_IMETHODIMP
1128 nsColumnSetFrame::RemoveFrame(nsIAtom* aListName,
1129 nsIFrame* aOldFrame)
1131 NS_NOTREACHED("RemoveFrame not supported");
1132 return NS_ERROR_NOT_IMPLEMENTED;