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
->GetVisualOverflowRect();
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 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
;
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 // We want scrollable overflow here since this is a calculation that
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();
619 aStatus
= (kidNext
->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER
)
620 ? NS_FRAME_OVERFLOW_INCOMPLETE
621 : NS_FRAME_NOT_COMPLETE
;
623 aStatus
= mLastFrameStatus
;
625 childContentBottom
= nsLayoutUtils::CalculateContentBottom(child
);
627 printf("*** Skipping child #%d %p (incremental %d, resize height shrink %d): status = %d\n",
628 columnCount
, (void*)child
, skipIncremental
, skipResizeHeightShrink
, aStatus
);
631 nsSize
availSize(aConfig
.mColWidth
, aConfig
.mColMaxHeight
);
633 if (aUnboundedLastColumn
&& columnCount
== aConfig
.mBalanceColCount
- 1) {
634 availSize
.height
= GetAvailableContentHeight(aReflowState
);
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
;
647 printf("*** Reflowing child #%d %p: availHeight=%d\n",
648 columnCount
, (void*)child
,availSize
.height
);
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.
669 ReflowChild(child
, PresContext(), kidDesiredSize
, kidReflowState
,
670 childOrigin
.x
+ kidReflowState
.mComputedMargin
.left
,
671 childOrigin
.y
+ kidReflowState
.mComputedMargin
.top
,
674 reflowNext
= (aStatus
& NS_FRAME_REFLOW_NEXTINFLOW
) != 0;
677 printf("*** Reflowed child #%d %p: status = %d, desiredSize=%d,%d\n",
678 columnCount
, (void*)child
, aStatus
, kidDesiredSize
.width
, kidDesiredSize
.height
);
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
) {
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");
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
);
726 NS_NOTREACHED("Couldn't create continuation");
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
);
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.
773 // Advance to the next column
774 child
= child
->GetNextSibling();
778 childOrigin
.x
+= aConfig
.mColWidth
+ aConfig
.mColGap
;
780 childOrigin
.x
-= aConfig
.mColWidth
+ aConfig
.mColGap
;
784 printf("*** NEXT CHILD ORIGIN.x = %d\n", childOrigin
.x
);
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
;
810 printf("*** CHILDORIGIN.x = %d, targetX = %d, DELTAX = %d\n", childOrigin
.x
, targetX
, deltaX
);
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();
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();
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();
856 printf("*** DONE PASS feasible=%d\n", allFit
&& NS_FRAME_IS_FULLY_COMPLETE(aStatus
)
857 && !NS_FRAME_IS_TRUNCATED(aStatus
));
859 return allFit
&& NS_FRAME_IS_FULLY_COMPLETE(aStatus
)
860 && !NS_FRAME_IS_TRUNCATED(aStatus
);
864 nsColumnSetFrame::DrainOverflowColumns()
866 // First grab the prev-in-flows overflows and reparent them to this
868 nsColumnSetFrame
* prev
= static_cast<nsColumnSetFrame
*>(GetPrevInFlow());
870 nsAutoPtr
<nsFrameList
> overflows(prev
->StealOverflowFrames());
872 nsHTMLContainerFrame::ReparentFrameViewList(PresContext(), *overflows
,
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());
883 // We're already the parent for these frames, so no need to set
884 // their parent again.
885 mFrames
.AppendFrames(nsnull
, *overflows
);
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
);
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
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
943 PRBool maybeContinuousBreakingDetected
= PR_FALSE
;
945 while (!aPresContext
->HasPendingInterrupt()) {
946 nscoord lastKnownFeasibleHeight
= knownFeasibleHeight
;
948 // Record what we learned from the last reflow
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);
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
,
979 printf("*** nsColumnSetFrame::Reflow balancing knownInfeasible=%d knownFeasible=%d\n",
980 knownInfeasibleHeight
, knownFeasibleHeight
);
983 if (knownInfeasibleHeight
>= knownFeasibleHeight
- 1) {
984 // knownFeasibleHeight is where we want to be
988 if (knownInfeasibleHeight
>= availableContentHeight
) {
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
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;
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
);
1025 printf("*** nsColumnSetFrame::Reflow balancing choosing next guess=%d\n", nextGuess
);
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
) {
1047 config
.mColMaxHeight
= knownFeasibleHeight
;
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
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");
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.
1097 nsresult rv
= BuildDisplayListForChild(aBuilder
, kid
, aDirtyRect
, aLists
);
1098 NS_ENSURE_SUCCESS(rv
, rv
);
1099 kid
= kid
->GetNextSibling();
1105 nsColumnSetFrame::GetSkipSides() const
1111 nsColumnSetFrame::AppendFrames(nsIAtom
* aListName
,
1112 nsFrameList
& aFrameList
)
1114 NS_NOTREACHED("AppendFrames not supported");
1115 return NS_ERROR_NOT_IMPLEMENTED
;
1119 nsColumnSetFrame::InsertFrames(nsIAtom
* aListName
,
1120 nsIFrame
* aPrevFrame
,
1121 nsFrameList
& aFrameList
)
1123 NS_NOTREACHED("InsertFrames not supported");
1124 return NS_ERROR_NOT_IMPLEMENTED
;
1128 nsColumnSetFrame::RemoveFrame(nsIAtom
* aListName
,
1129 nsIFrame
* aOldFrame
)
1131 NS_NOTREACHED("RemoveFrame not supported");
1132 return NS_ERROR_NOT_IMPLEMENTED
;