Bug 575870 - Enable the firefox button on xp themed, classic, and aero basic. r=dao...
[mozilla-central.git] / layout / generic / nsColumnSetFrame.cpp
blobcb52c6b9a66d6e5dca7e2a170d802166b1ac2f2c
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->GetOverflowRect();
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 nsRect overflowRect(0, 0, 0, 0);
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 PRBool skipResizeHeightShrink = shrinkingHeightOnly
607 && child->GetOverflowRect().YMost() <= aConfig.mColMaxHeight;
609 nscoord childContentBottom = 0;
610 if (!reflowNext && (skipIncremental || skipResizeHeightShrink)) {
611 // This child does not need to be reflowed, but we may need to move it
612 MoveChildTo(this, child, childOrigin);
614 // If this is the last frame then make sure we get the right status
615 nsIFrame* kidNext = child->GetNextSibling();
616 if (kidNext) {
617 aStatus = (kidNext->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)
618 ? NS_FRAME_OVERFLOW_INCOMPLETE
619 : NS_FRAME_NOT_COMPLETE;
620 } else {
621 aStatus = mLastFrameStatus;
623 childContentBottom = nsLayoutUtils::CalculateContentBottom(child);
624 #ifdef DEBUG_roc
625 printf("*** Skipping child #%d %p (incremental %d, resize height shrink %d): status = %d\n",
626 columnCount, (void*)child, skipIncremental, skipResizeHeightShrink, aStatus);
627 #endif
628 } else {
629 nsSize availSize(aConfig.mColWidth, aConfig.mColMaxHeight);
631 if (aUnboundedLastColumn && columnCount == aConfig.mBalanceColCount - 1) {
632 availSize.height = GetAvailableContentHeight(aReflowState);
635 if (reflowNext)
636 child->AddStateBits(NS_FRAME_IS_DIRTY);
638 nsHTMLReflowState kidReflowState(PresContext(), aReflowState, child,
639 availSize, availSize.width,
640 aReflowState.ComputedHeight());
641 kidReflowState.mFlags.mIsTopOfPage = PR_TRUE;
642 kidReflowState.mFlags.mTableIsSplittable = PR_FALSE;
644 #ifdef DEBUG_roc
645 printf("*** Reflowing child #%d %p: availHeight=%d\n",
646 columnCount, (void*)child,availSize.height);
647 #endif
649 // Note if the column's next in flow is not being changed by this incremental reflow.
650 // This may allow the current column to avoid trying to pull lines from the next column.
651 if (child->GetNextSibling() &&
652 !(GetStateBits() & NS_FRAME_IS_DIRTY) &&
653 !(child->GetNextSibling()->GetStateBits() & NS_FRAME_IS_DIRTY)) {
654 kidReflowState.mFlags.mNextInFlowUntouched = PR_TRUE;
657 nsHTMLReflowMetrics kidDesiredSize(aDesiredSize.mFlags);
659 // XXX it would be cool to consult the float manager for the
660 // previous block to figure out the region of floats from the
661 // previous column that extend into this column, and subtract
662 // that region from the new float manager. So you could stick a
663 // really big float in the first column and text in following
664 // columns would flow around it.
666 // Reflow the frame
667 ReflowChild(child, PresContext(), kidDesiredSize, kidReflowState,
668 childOrigin.x + kidReflowState.mComputedMargin.left,
669 childOrigin.y + kidReflowState.mComputedMargin.top,
670 0, aStatus);
672 reflowNext = (aStatus & NS_FRAME_REFLOW_NEXTINFLOW) != 0;
674 #ifdef DEBUG_roc
675 printf("*** Reflowed child #%d %p: status = %d, desiredSize=%d,%d\n",
676 columnCount, (void*)child, aStatus, kidDesiredSize.width, kidDesiredSize.height);
677 #endif
679 NS_FRAME_TRACE_REFLOW_OUT("Column::Reflow", aStatus);
681 *aBottomMarginCarriedOut = kidDesiredSize.mCarriedOutBottomMargin;
683 FinishReflowChild(child, PresContext(), &kidReflowState,
684 kidDesiredSize, childOrigin.x, childOrigin.y, 0);
686 childContentBottom = nsLayoutUtils::CalculateContentBottom(child);
687 if (childContentBottom > aConfig.mColMaxHeight) {
688 allFit = PR_FALSE;
690 if (childContentBottom > availSize.height) {
691 aColData.mMaxOverflowingHeight = NS_MAX(childContentBottom,
692 aColData.mMaxOverflowingHeight);
696 contentRect.UnionRect(contentRect, child->GetRect());
698 ConsiderChildOverflow(overflowRect, child);
699 contentBottom = NS_MAX(contentBottom, childContentBottom);
700 aColData.mLastHeight = childContentBottom;
701 aColData.mSumHeight += childContentBottom;
703 // Build a continuation column if necessary
704 nsIFrame* kidNextInFlow = child->GetNextInFlow();
706 if (NS_FRAME_IS_FULLY_COMPLETE(aStatus) && !NS_FRAME_IS_TRUNCATED(aStatus)) {
707 NS_ASSERTION(!kidNextInFlow, "next in flow should have been deleted");
708 child = nsnull;
709 break;
710 } else {
711 ++columnCount;
712 // Make sure that the column has a next-in-flow. If not, we must
713 // create one to hold the overflowing stuff, even if we're just
714 // going to put it on our overflow list and let *our*
715 // next in flow handle it.
716 if (!kidNextInFlow) {
717 NS_ASSERTION(aStatus & NS_FRAME_REFLOW_NEXTINFLOW,
718 "We have to create a continuation, but the block doesn't want us to reflow it?");
720 // We need to create a continuing column
721 nsresult rv = CreateNextInFlow(PresContext(), child, kidNextInFlow);
723 if (NS_FAILED(rv)) {
724 NS_NOTREACHED("Couldn't create continuation");
725 child = nsnull;
726 break;
730 // Make sure we reflow a next-in-flow when it switches between being
731 // normal or overflow container
732 if (NS_FRAME_OVERFLOW_IS_INCOMPLETE(aStatus)) {
733 if (!(kidNextInFlow->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)) {
734 aStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
735 reflowNext = PR_TRUE;
736 kidNextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
739 else if (kidNextInFlow->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) {
740 aStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
741 reflowNext = PR_TRUE;
742 kidNextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
745 if (columnCount >= aConfig.mBalanceColCount) {
746 // No more columns allowed here. Stop.
747 aStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
748 kidNextInFlow->AddStateBits(NS_FRAME_IS_DIRTY);
750 // Move any of our leftover columns to our overflow list. Our
751 // next-in-flow will eventually pick them up.
752 const nsFrameList& continuationColumns = mFrames.RemoveFramesAfter(child);
753 if (continuationColumns.NotEmpty()) {
754 SetOverflowFrames(PresContext(), continuationColumns);
756 child = nsnull;
757 break;
761 if (PresContext()->HasPendingInterrupt()) {
762 // Stop the loop now while |child| still points to the frame that bailed
763 // out. We could keep going here and condition a bunch of the code in
764 // this loop on whether there's an interrupt, or even just keep going and
765 // trying to reflow the blocks (even though we know they'll interrupt
766 // right after their first line), but stopping now is conceptually the
767 // simplest (and probably fastest) thing.
768 break;
771 // Advance to the next column
772 child = child->GetNextSibling();
774 if (child) {
775 if (!RTL) {
776 childOrigin.x += aConfig.mColWidth + aConfig.mColGap;
777 } else {
778 childOrigin.x -= aConfig.mColWidth + aConfig.mColGap;
781 #ifdef DEBUG_roc
782 printf("*** NEXT CHILD ORIGIN.x = %d\n", childOrigin.x);
783 #endif
787 if (PresContext()->CheckForInterrupt(this) &&
788 (GetStateBits() & NS_FRAME_IS_DIRTY)) {
789 // Mark all our kids starting with |child| dirty
791 // Note that this is a CheckForInterrupt call, not a HasPendingInterrupt,
792 // because we might have interrupted while reflowing |child|, and since
793 // we're about to add a dirty bit to |child| we need to make sure that
794 // |this| is scheduled to have dirty bits marked on it and its ancestors.
795 // Otherwise, when we go to mark dirty bits on |child|'s ancestors we'll
796 // bail out immediately, since it'll already have a dirty bit.
797 for (; child; child = child->GetNextSibling()) {
798 child->AddStateBits(NS_FRAME_IS_DIRTY);
802 // If we're doing RTL, we need to make sure our last column is at the left-hand side of the frame.
803 if (RTL && childOrigin.x != targetX) {
804 overflowRect = nsRect(0, 0, 0, 0);
805 contentRect = nsRect(0, 0, 0, 0);
806 PRInt32 deltaX = targetX - childOrigin.x;
807 #ifdef DEBUG_roc
808 printf("*** CHILDORIGIN.x = %d, targetX = %d, DELTAX = %d\n", childOrigin.x, targetX, deltaX);
809 #endif
810 for (child = mFrames.FirstChild(); child; child = child->GetNextSibling()) {
811 MoveChildTo(this, child, child->GetPosition() + nsPoint(deltaX, 0));
812 ConsiderChildOverflow(overflowRect, child);
813 contentRect.UnionRect(contentRect, child->GetRect());
816 aColData.mMaxHeight = contentBottom;
817 contentRect.height = NS_MAX(contentRect.height, contentBottom);
818 mLastFrameStatus = aStatus;
820 // contentRect included the borderPadding.left,borderPadding.top of the child rects
821 contentRect -= nsPoint(borderPadding.left, borderPadding.top);
823 nsSize contentSize = nsSize(contentRect.XMost(), contentRect.YMost());
825 // Apply computed and min/max values
826 if (aReflowState.ComputedHeight() != NS_INTRINSICSIZE) {
827 contentSize.height = aReflowState.ComputedHeight();
828 } else {
829 if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMaxHeight) {
830 contentSize.height = NS_MIN(aReflowState.mComputedMaxHeight, contentSize.height);
832 if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMinHeight) {
833 contentSize.height = NS_MAX(aReflowState.mComputedMinHeight, contentSize.height);
836 if (aReflowState.ComputedWidth() != NS_INTRINSICSIZE) {
837 contentSize.width = aReflowState.ComputedWidth();
838 } else {
839 if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMaxWidth) {
840 contentSize.width = NS_MIN(aReflowState.mComputedMaxWidth, contentSize.width);
842 if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMinWidth) {
843 contentSize.width = NS_MAX(aReflowState.mComputedMinWidth, contentSize.width);
847 aDesiredSize.height = borderPadding.top + contentSize.height +
848 borderPadding.bottom;
849 aDesiredSize.width = contentSize.width + borderPadding.left + borderPadding.right;
850 overflowRect.UnionRect(overflowRect, nsRect(0, 0, aDesiredSize.width, aDesiredSize.height));
851 aDesiredSize.mOverflowArea = overflowRect;
853 #ifdef DEBUG_roc
854 printf("*** DONE PASS feasible=%d\n", allFit && NS_FRAME_IS_FULLY_COMPLETE(aStatus)
855 && !NS_FRAME_IS_TRUNCATED(aStatus));
856 #endif
857 return allFit && NS_FRAME_IS_FULLY_COMPLETE(aStatus)
858 && !NS_FRAME_IS_TRUNCATED(aStatus);
861 void
862 nsColumnSetFrame::DrainOverflowColumns()
864 // First grab the prev-in-flows overflows and reparent them to this
865 // frame.
866 nsColumnSetFrame* prev = static_cast<nsColumnSetFrame*>(GetPrevInFlow());
867 if (prev) {
868 nsAutoPtr<nsFrameList> overflows(prev->StealOverflowFrames());
869 if (overflows) {
870 nsHTMLContainerFrame::ReparentFrameViewList(PresContext(), *overflows,
871 prev, this);
873 mFrames.InsertFrames(this, nsnull, *overflows);
877 // Now pull back our own overflows and append them to our children.
878 // We don't need to reparent them since we're already their parent.
879 nsAutoPtr<nsFrameList> overflows(StealOverflowFrames());
880 if (overflows) {
881 // We're already the parent for these frames, so no need to set
882 // their parent again.
883 mFrames.AppendFrames(nsnull, *overflows);
887 NS_IMETHODIMP
888 nsColumnSetFrame::Reflow(nsPresContext* aPresContext,
889 nsHTMLReflowMetrics& aDesiredSize,
890 const nsHTMLReflowState& aReflowState,
891 nsReflowStatus& aStatus)
893 // Don't support interruption in columns
894 nsPresContext::InterruptPreventer noInterrupts(aPresContext);
896 DO_GLOBAL_REFLOW_COUNT("nsColumnSetFrame");
897 DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
899 // Initialize OUT parameter
900 aStatus = NS_FRAME_COMPLETE;
902 // Our children depend on our height if we have a fixed height.
903 if (aReflowState.ComputedHeight() != NS_AUTOHEIGHT) {
904 NS_ASSERTION(aReflowState.ComputedHeight() != NS_INTRINSICSIZE,
905 "Unexpected mComputedHeight");
906 AddStateBits(NS_FRAME_CONTAINS_RELATIVE_HEIGHT);
908 else {
909 RemoveStateBits(NS_FRAME_CONTAINS_RELATIVE_HEIGHT);
912 //------------ Handle Incremental Reflow -----------------
914 ReflowConfig config = ChooseColumnStrategy(aReflowState);
915 PRBool isBalancing = config.mBalanceColCount < PR_INT32_MAX;
917 // If balancing, then we allow the last column to grow to unbounded
918 // height during the first reflow. This gives us a way to estimate
919 // what the average column height should be, because we can measure
920 // the heights of all the columns and sum them up. But don't do this
921 // if we have a next in flow because we don't want to suck all its
922 // content back here and then have to push it out again!
923 nsIFrame* nextInFlow = GetNextInFlow();
924 PRBool unboundedLastColumn = isBalancing && !nextInFlow;
925 nsCollapsingMargin carriedOutBottomMargin;
926 ColumnBalanceData colData;
927 PRBool feasible = ReflowChildren(aDesiredSize, aReflowState,
928 aStatus, config, unboundedLastColumn, &carriedOutBottomMargin, colData);
930 if (isBalancing && !aPresContext->HasPendingInterrupt()) {
931 nscoord availableContentHeight = GetAvailableContentHeight(aReflowState);
933 // Termination of the algorithm below is guaranteed because
934 // knownFeasibleHeight - knownInfeasibleHeight decreases in every
935 // iteration.
936 nscoord knownFeasibleHeight = NS_INTRINSICSIZE;
937 nscoord knownInfeasibleHeight = 0;
938 // We set this flag when we detect that we may contain a frame
939 // that can break anywhere (thus foiling the linear decrease-by-one
940 // search)
941 PRBool maybeContinuousBreakingDetected = PR_FALSE;
943 while (!aPresContext->HasPendingInterrupt()) {
944 nscoord lastKnownFeasibleHeight = knownFeasibleHeight;
946 // Record what we learned from the last reflow
947 if (feasible) {
948 // maxHeight is feasible. Also, mLastBalanceHeight is feasible.
949 knownFeasibleHeight = NS_MIN(knownFeasibleHeight, colData.mMaxHeight);
950 knownFeasibleHeight = NS_MIN(knownFeasibleHeight, mLastBalanceHeight);
952 // Furthermore, no height less than the height of the last
953 // column can ever be feasible. (We might be able to reduce the
954 // height of a non-last column by moving content to a later column,
955 // but we can't do that with the last column.)
956 if (mFrames.GetLength() == config.mBalanceColCount) {
957 knownInfeasibleHeight = NS_MAX(knownInfeasibleHeight,
958 colData.mLastHeight - 1);
960 } else {
961 knownInfeasibleHeight = NS_MAX(knownInfeasibleHeight, mLastBalanceHeight);
962 // If a column didn't fit in its available height, then its current
963 // height must be the minimum height for unbreakable content in
964 // the column, and therefore no smaller height can be feasible.
965 knownInfeasibleHeight = NS_MAX(knownInfeasibleHeight,
966 colData.mMaxOverflowingHeight - 1);
968 if (unboundedLastColumn) {
969 // The last column is unbounded, so all content got reflowed, so the
970 // mColMaxHeight is feasible.
971 knownFeasibleHeight = NS_MIN(knownFeasibleHeight,
972 colData.mMaxHeight);
976 #ifdef DEBUG_roc
977 printf("*** nsColumnSetFrame::Reflow balancing knownInfeasible=%d knownFeasible=%d\n",
978 knownInfeasibleHeight, knownFeasibleHeight);
979 #endif
981 if (knownInfeasibleHeight >= knownFeasibleHeight - 1) {
982 // knownFeasibleHeight is where we want to be
983 break;
986 if (knownInfeasibleHeight >= availableContentHeight) {
987 break;
990 if (lastKnownFeasibleHeight - knownFeasibleHeight == 1) {
991 // We decreased the feasible height by one twip only. This could
992 // indicate that there is a continuously breakable child frame
993 // that we are crawling through.
994 maybeContinuousBreakingDetected = PR_TRUE;
997 nscoord nextGuess = (knownFeasibleHeight + knownInfeasibleHeight)/2;
998 // The constant of 600 twips is arbitrary. It's about two line-heights.
999 if (knownFeasibleHeight - nextGuess < 600 &&
1000 !maybeContinuousBreakingDetected) {
1001 // We're close to our target, so just try shrinking just the
1002 // minimum amount that will cause one of our columns to break
1003 // differently.
1004 nextGuess = knownFeasibleHeight - 1;
1005 } else if (unboundedLastColumn) {
1006 // Make a guess by dividing that into N columns. Add some slop
1007 // to try to make it on the feasible side. The constant of
1008 // 600 twips is arbitrary. It's about two line-heights.
1009 nextGuess = colData.mSumHeight/config.mBalanceColCount + 600;
1010 // Sanitize it
1011 nextGuess = NS_MIN(NS_MAX(nextGuess, knownInfeasibleHeight + 1),
1012 knownFeasibleHeight - 1);
1013 } else if (knownFeasibleHeight == NS_INTRINSICSIZE) {
1014 // This can happen when we had a next-in-flow so we didn't
1015 // want to do an unbounded height measuring step. Let's just increase
1016 // from the infeasible height by some reasonable amount.
1017 nextGuess = knownInfeasibleHeight*2 + 600;
1019 // Don't bother guessing more than our height constraint.
1020 nextGuess = NS_MIN(availableContentHeight, nextGuess);
1022 #ifdef DEBUG_roc
1023 printf("*** nsColumnSetFrame::Reflow balancing choosing next guess=%d\n", nextGuess);
1024 #endif
1026 config.mColMaxHeight = nextGuess;
1028 unboundedLastColumn = PR_FALSE;
1029 AddStateBits(NS_FRAME_IS_DIRTY);
1030 feasible = ReflowChildren(aDesiredSize, aReflowState,
1031 aStatus, config, PR_FALSE,
1032 &carriedOutBottomMargin, colData);
1035 if (!feasible && !aPresContext->HasPendingInterrupt()) {
1036 // We may need to reflow one more time at the feasible height to
1037 // get a valid layout.
1038 PRBool skip = PR_FALSE;
1039 if (knownInfeasibleHeight >= availableContentHeight) {
1040 config.mColMaxHeight = availableContentHeight;
1041 if (mLastBalanceHeight == availableContentHeight) {
1042 skip = PR_TRUE;
1044 } else {
1045 config.mColMaxHeight = knownFeasibleHeight;
1047 if (!skip) {
1048 // If our height is unconstrained, make sure that the last column is
1049 // allowed to have arbitrary height here, even though we were balancing.
1050 // Otherwise we'd have to split, and it's not clear what we'd do with
1051 // that.
1052 AddStateBits(NS_FRAME_IS_DIRTY);
1053 ReflowChildren(aDesiredSize, aReflowState, aStatus, config,
1054 availableContentHeight == NS_UNCONSTRAINEDSIZE,
1055 &carriedOutBottomMargin, colData);
1060 if (aPresContext->HasPendingInterrupt() &&
1061 aReflowState.availableHeight == NS_UNCONSTRAINEDSIZE) {
1062 // In this situation, we might be lying about our reflow status, because
1063 // our last kid (the one that got interrupted) was incomplete. Fix that.
1064 aStatus = NS_FRAME_COMPLETE;
1067 CheckInvalidateSizeChange(aDesiredSize);
1069 FinishAndStoreOverflow(&aDesiredSize);
1070 aDesiredSize.mCarriedOutBottomMargin = carriedOutBottomMargin;
1072 NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
1074 NS_ASSERTION(NS_FRAME_IS_FULLY_COMPLETE(aStatus) ||
1075 aReflowState.availableHeight != NS_UNCONSTRAINEDSIZE,
1076 "Column set should be complete if the available height is unconstrained");
1078 return NS_OK;
1081 NS_IMETHODIMP
1082 nsColumnSetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1083 const nsRect& aDirtyRect,
1084 const nsDisplayListSet& aLists) {
1085 nsresult rv = DisplayBorderBackgroundOutline(aBuilder, aLists);
1086 NS_ENSURE_SUCCESS(rv, rv);
1088 aLists.BorderBackground()->AppendNewToTop(new (aBuilder)
1089 nsDisplayGeneric(aBuilder, this, ::PaintColumnRule, "ColumnRule",
1090 nsDisplayItem::TYPE_COLUMN_RULE));
1092 nsIFrame* kid = mFrames.FirstChild();
1093 // Our children won't have backgrounds so it doesn't matter where we put them.
1094 while (kid) {
1095 nsresult rv = BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists);
1096 NS_ENSURE_SUCCESS(rv, rv);
1097 kid = kid->GetNextSibling();
1099 return NS_OK;
1102 PRIntn
1103 nsColumnSetFrame::GetSkipSides() const
1105 return 0;
1108 NS_IMETHODIMP
1109 nsColumnSetFrame::AppendFrames(nsIAtom* aListName,
1110 nsFrameList& aFrameList)
1112 NS_NOTREACHED("AppendFrames not supported");
1113 return NS_ERROR_NOT_IMPLEMENTED;
1116 NS_IMETHODIMP
1117 nsColumnSetFrame::InsertFrames(nsIAtom* aListName,
1118 nsIFrame* aPrevFrame,
1119 nsFrameList& aFrameList)
1121 NS_NOTREACHED("InsertFrames not supported");
1122 return NS_ERROR_NOT_IMPLEMENTED;
1125 NS_IMETHODIMP
1126 nsColumnSetFrame::RemoveFrame(nsIAtom* aListName,
1127 nsIFrame* aOldFrame)
1129 NS_NOTREACHED("RemoveFrame not supported");
1130 return NS_ERROR_NOT_IMPLEMENTED;