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
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.
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"
44 #include "nsISupports.h"
46 #include "nsPresContext.h"
47 #include "nsHTMLParts.h"
48 #include "nsGkAtoms.h"
49 #include "nsStyleConsts.h"
51 #include "nsLayoutUtils.h"
52 #include "nsDisplayList.h"
53 #include "nsCSSRendering.h"
55 class nsColumnSetFrame
: public nsHTMLContainerFrame
{
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
,
73 nsFrameList
& aFrameList
);
74 NS_IMETHOD
RemoveFrame(nsIAtom
* aListName
,
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
87 return frame
->GetContentInsertionFrame();
90 virtual nsresult
StealFrame(nsPresContext
* aPresContext
,
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
,
108 NS_IMETHOD
GetFrameName(nsAString
& aResult
) const {
109 return MakeFrameName(NS_LITERAL_STRING("ColumnSet"), aResult
);
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
;
125 nscoord mExpectedWidthLeftOver
;
127 nscoord mColMaxHeight
;
131 * Some data that is better calculated during reflow
133 struct ColumnBalanceData
{
134 // The maximum "content height" of any column
136 // The sum of the "content heights" for all columns
138 // The "content height" of the last column
140 // The maximum "content height" of all columns that overflowed
141 // their available height
142 nscoord mMaxOverflowingHeight
;
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
);
180 * XXX cursor movement around the top and bottom of colums seems to make the editor
183 * XXX should we support CSS columns applied to table elements?
186 NS_NewColumnSetFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
, PRUint32 aStateFlags
)
188 nsColumnSetFrame
* it
= new (aPresShell
) nsColumnSetFrame(aContext
);
190 // set the state flags (if any are provided)
191 it
->AddStateBits(aStateFlags
);
197 NS_IMPL_FRAMEARENA_HELPERS(nsColumnSetFrame
)
199 nsColumnSetFrame::nsColumnSetFrame(nsStyleContext
* aContext
)
200 : nsHTMLContainerFrame(aContext
), mLastBalanceHeight(NS_INTRINSICSIZE
),
201 mLastFrameStatus(NS_FRAME_COMPLETE
)
206 nsColumnSetFrame::GetType() const
208 return nsGkAtoms::columnSetFrame
;
212 PaintColumnRule(nsIFrame
* aFrame
, nsIRenderingContext
* aCtx
,
213 const nsRect
& aDirtyRect
, nsPoint aPt
)
215 static_cast<nsColumnSetFrame
*>(aFrame
)->PaintColumnRule(aCtx
, aDirtyRect
, aPt
);
219 nsColumnSetFrame::PaintColumnRule(nsIRenderingContext
* aCtx
,
220 const nsRect
& aDirtyRect
,
223 nsIFrame
* child
= mFrames
.FirstChild();
225 return; // no columns
227 nsIFrame
* nextSibling
= child
->GetNextSibling();
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();
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
;
241 ruleStyle
= colStyle
->mColumnRuleStyle
;
243 nsPresContext
* presContext
= PresContext();
244 nscoord ruleWidth
= colStyle
->GetComputedColumnRuleWidth();
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,
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
));
285 nextSibling
= nextSibling
->GetNextSibling();
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
);
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
);
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
);
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");
336 NS_NOTREACHED("Unknown gap type");
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
;
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
366 // This expression uses truncated rounding, which is what we
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
;
375 colWidth
= NS_INTRINSICSIZE
;
377 // Take care of the situation where there's only one column but it's
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
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) {
400 // Compute extra space and divide it among the columns
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
) {
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.
417 colHeight
= NS_MIN(mLastBalanceHeight
, GetAvailableContentHeight(aReflowState
));
419 // No balancing, so don't limit the column count
420 numColumns
= PR_INT32_MAX
;
424 printf("*** nsColumnSetFrame::ChooseColumnStrategy: numColumns=%d, colWidth=%d, expectedWidthLeftOver=%d, colHeight=%d, colGap=%d\n",
425 numColumns
, colWidth
, expectedWidthLeftOver
, colHeight
, colGap
);
427 ReflowConfig config
= { numColumns
, colWidth
, expectedWidthLeftOver
, colGap
, colHeight
};
431 // XXX copied from nsBlockFrame, should this be moved to nsContainerFrame?
433 PlaceFrameView(nsIFrame
* aFrame
)
435 if (aFrame
->HasView())
436 nsContainerFrame::PositionFrameView(aFrame
);
438 nsContainerFrame::PositionChildViews(aFrame
);
441 static void MoveChildTo(nsIFrame
* aParent
, nsIFrame
* aChild
, nsPoint aOrigin
) {
442 if (aChild
->GetPosition() == aOrigin
) {
446 nsRect r
= aChild
->GetOverflowRect();
447 r
+= aChild
->GetPosition();
448 aParent
->Invalidate(r
);
449 r
-= aChild
->GetPosition();
450 aChild
->SetPosition(aOrigin
);
452 aParent
->Invalidate(r
);
453 PlaceFrameView(aChild
);
457 nsColumnSetFrame::GetMinWidth(nsIRenderingContext
*aRenderingContext
) {
459 DISPLAY_MIN_WIDTH(this, width
);
460 if (mFrames
.FirstChild()) {
461 width
= mFrames
.FirstChild()->GetMinWidth(aRenderingContext
);
463 const nsStyleColumn
* colStyle
= GetStyleColumn();
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
);
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.
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.
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?
494 DISPLAY_PREF_WIDTH(this, result
);
495 const nsStyleColumn
* colStyle
= GetStyleColumn();
496 nscoord colGap
= GetColumnGap(this, colStyle
);
499 if (colStyle
->mColumnWidth
.GetUnit() == eStyleUnit_Coord
) {
500 colWidth
= colStyle
->mColumnWidth
.GetCoordValue();
501 } else if (mFrames
.FirstChild()) {
502 colWidth
= mFrames
.FirstChild()->GetPrefWidth(aRenderingContext
);
507 PRInt32 numColumns
= colStyle
->mColumnCount
;
508 if (numColumns
<= 0) {
509 // if column-count is auto, assume one column
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
);
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
)
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
;
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
);
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
;
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
;
576 printf("*** childOrigin.x = %d\n", childOrigin
.x
);
581 int contentBottom
= 0;
582 PRBool reflowNext
= PR_FALSE
;
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
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();
617 aStatus
= (kidNext
->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER
)
618 ? NS_FRAME_OVERFLOW_INCOMPLETE
619 : NS_FRAME_NOT_COMPLETE
;
621 aStatus
= mLastFrameStatus
;
623 childContentBottom
= nsLayoutUtils::CalculateContentBottom(child
);
625 printf("*** Skipping child #%d %p (incremental %d, resize height shrink %d): status = %d\n",
626 columnCount
, (void*)child
, skipIncremental
, skipResizeHeightShrink
, aStatus
);
629 nsSize
availSize(aConfig
.mColWidth
, aConfig
.mColMaxHeight
);
631 if (aUnboundedLastColumn
&& columnCount
== aConfig
.mBalanceColCount
- 1) {
632 availSize
.height
= GetAvailableContentHeight(aReflowState
);
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
;
645 printf("*** Reflowing child #%d %p: availHeight=%d\n",
646 columnCount
, (void*)child
,availSize
.height
);
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.
667 ReflowChild(child
, PresContext(), kidDesiredSize
, kidReflowState
,
668 childOrigin
.x
+ kidReflowState
.mComputedMargin
.left
,
669 childOrigin
.y
+ kidReflowState
.mComputedMargin
.top
,
672 reflowNext
= (aStatus
& NS_FRAME_REFLOW_NEXTINFLOW
) != 0;
675 printf("*** Reflowed child #%d %p: status = %d, desiredSize=%d,%d\n",
676 columnCount
, (void*)child
, aStatus
, kidDesiredSize
.width
, kidDesiredSize
.height
);
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
) {
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");
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
);
724 NS_NOTREACHED("Couldn't create continuation");
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
);
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.
771 // Advance to the next column
772 child
= child
->GetNextSibling();
776 childOrigin
.x
+= aConfig
.mColWidth
+ aConfig
.mColGap
;
778 childOrigin
.x
-= aConfig
.mColWidth
+ aConfig
.mColGap
;
782 printf("*** NEXT CHILD ORIGIN.x = %d\n", childOrigin
.x
);
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
;
808 printf("*** CHILDORIGIN.x = %d, targetX = %d, DELTAX = %d\n", childOrigin
.x
, targetX
, deltaX
);
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();
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();
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
;
854 printf("*** DONE PASS feasible=%d\n", allFit
&& NS_FRAME_IS_FULLY_COMPLETE(aStatus
)
855 && !NS_FRAME_IS_TRUNCATED(aStatus
));
857 return allFit
&& NS_FRAME_IS_FULLY_COMPLETE(aStatus
)
858 && !NS_FRAME_IS_TRUNCATED(aStatus
);
862 nsColumnSetFrame::DrainOverflowColumns()
864 // First grab the prev-in-flows overflows and reparent them to this
866 nsColumnSetFrame
* prev
= static_cast<nsColumnSetFrame
*>(GetPrevInFlow());
868 nsAutoPtr
<nsFrameList
> overflows(prev
->StealOverflowFrames());
870 nsHTMLContainerFrame::ReparentFrameViewList(PresContext(), *overflows
,
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());
881 // We're already the parent for these frames, so no need to set
882 // their parent again.
883 mFrames
.AppendFrames(nsnull
, *overflows
);
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
);
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
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
941 PRBool maybeContinuousBreakingDetected
= PR_FALSE
;
943 while (!aPresContext
->HasPendingInterrupt()) {
944 nscoord lastKnownFeasibleHeight
= knownFeasibleHeight
;
946 // Record what we learned from the last reflow
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);
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
,
977 printf("*** nsColumnSetFrame::Reflow balancing knownInfeasible=%d knownFeasible=%d\n",
978 knownInfeasibleHeight
, knownFeasibleHeight
);
981 if (knownInfeasibleHeight
>= knownFeasibleHeight
- 1) {
982 // knownFeasibleHeight is where we want to be
986 if (knownInfeasibleHeight
>= availableContentHeight
) {
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
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;
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
);
1023 printf("*** nsColumnSetFrame::Reflow balancing choosing next guess=%d\n", nextGuess
);
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
) {
1045 config
.mColMaxHeight
= knownFeasibleHeight
;
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
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");
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.
1095 nsresult rv
= BuildDisplayListForChild(aBuilder
, kid
, aDirtyRect
, aLists
);
1096 NS_ENSURE_SUCCESS(rv
, rv
);
1097 kid
= kid
->GetNextSibling();
1103 nsColumnSetFrame::GetSkipSides() const
1109 nsColumnSetFrame::AppendFrames(nsIAtom
* aListName
,
1110 nsFrameList
& aFrameList
)
1112 NS_NOTREACHED("AppendFrames not supported");
1113 return NS_ERROR_NOT_IMPLEMENTED
;
1117 nsColumnSetFrame::InsertFrames(nsIAtom
* aListName
,
1118 nsIFrame
* aPrevFrame
,
1119 nsFrameList
& aFrameList
)
1121 NS_NOTREACHED("InsertFrames not supported");
1122 return NS_ERROR_NOT_IMPLEMENTED
;
1126 nsColumnSetFrame::RemoveFrame(nsIAtom
* aListName
,
1127 nsIFrame
* aOldFrame
)
1129 NS_NOTREACHED("RemoveFrame not supported");
1130 return NS_ERROR_NOT_IMPLEMENTED
;