/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Robert O'Callahan * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* rendering object for css3 multi-column layout */ #include "nsHTMLContainerFrame.h" #include "nsIContent.h" #include "nsIFrame.h" #include "nsISupports.h" #include "nsIAtom.h" #include "nsPresContext.h" #include "nsHTMLParts.h" #include "nsLayoutAtoms.h" #include "nsStyleConsts.h" #include "nsCOMPtr.h" #include "nsReflowPath.h" class nsColumnSetFrame : public nsHTMLContainerFrame { public: nsColumnSetFrame(nsStyleContext* aContext); NS_IMETHOD SetInitialChildList(nsIAtom* aListName, nsIFrame* aChildList); NS_IMETHOD Reflow(nsPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus); NS_IMETHOD AppendFrames(nsIAtom* aListName, nsIFrame* aFrameList); NS_IMETHOD InsertFrames(nsIAtom* aListName, nsIFrame* aPrevFrame, nsIFrame* aFrameList); NS_IMETHOD RemoveFrame(nsIAtom* aListName, nsIFrame* aOldFrame); // REVIEW: Now by default the background of a frame receives events, // so this GetFrameForPoint override is no longer necessary. virtual nsIFrame* GetContentInsertionFrame() { return GetFirstChild(nsnull)->GetContentInsertionFrame(); } NS_IMETHOD BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsRect& aDirtyRect, const nsDisplayListSet& aLists); virtual nsIAtom* GetType() const; #ifdef DEBUG NS_IMETHOD GetFrameName(nsAString& aResult) const { return MakeFrameName(NS_LITERAL_STRING("ColumnSet"), aResult); } #endif protected: nscoord mLastBalanceHeight; nsReflowStatus mLastFrameStatus; virtual PRIntn GetSkipSides() const; /** * These are the parameters that control the layout of columns. */ struct ReflowConfig { PRInt32 mBalanceColCount; nscoord mColWidth; nscoord mExpectedWidthLeftOver; nscoord mColGap; nscoord mColMaxHeight; }; /** * Similar to nsBlockFrame::DrainOverflowLines. Locate any columns not * handled by our prev-in-flow, and any columns sitting on our own * overflow list, and put them in our primary child list for reflowing. */ void DrainOverflowColumns(); /** * The basic reflow strategy is to call this function repeatedly to * obtain specific parameters that determine the layout of the * columns. This function will compute those parameters from the CSS * style. This function will also be responsible for implementing * the state machine that controls column balancing. */ ReflowConfig ChooseColumnStrategy(const nsHTMLReflowState& aReflowState); /** * Reflow column children. Returns PR_TRUE iff the content that was reflowed * fit into the mColMaxHeight. */ PRBool ReflowChildren(nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowReason aReason, nsReflowStatus& aStatus, const ReflowConfig& aConfig, PRBool aLastColumnUnbounded, nsCollapsingMargin* aCarriedOutBottomMargin); }; /** * Tracking issues: * * XXX cursor movement around the top and bottom of colums seems to make the editor * lose the caret. * * XXX should we support CSS columns applied to table elements? */ nsIFrame* NS_NewColumnSetFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, PRUint32 aStateFlags) { nsColumnSetFrame* it = new (aPresShell) nsColumnSetFrame(aContext); if (it) { // set the state flags (if any are provided) it->AddStateBits(aStateFlags); } return it; } nsColumnSetFrame::nsColumnSetFrame(nsStyleContext* aContext) : nsHTMLContainerFrame(aContext), mLastBalanceHeight(NS_INTRINSICSIZE), mLastFrameStatus(NS_FRAME_COMPLETE) { } nsIAtom* nsColumnSetFrame::GetType() const { return nsLayoutAtoms::columnSetFrame; } NS_IMETHODIMP nsColumnSetFrame::SetInitialChildList(nsIAtom* aListName, nsIFrame* aChildList) { NS_ASSERTION(!aListName, "Only default child list supported"); NS_ASSERTION(aChildList && !aChildList->GetNextSibling(), "initial child list must have exactly one child"); // Queue up the frames for the content frame return nsHTMLContainerFrame::SetInitialChildList(nsnull, aChildList); } static nscoord GetAvailableContentWidth(const nsHTMLReflowState& aReflowState) { if (aReflowState.availableWidth == NS_INTRINSICSIZE) { return NS_INTRINSICSIZE; } nscoord borderPaddingWidth = aReflowState.mComputedBorderPadding.left + aReflowState.mComputedBorderPadding.right; return PR_MAX(0, aReflowState.availableWidth - borderPaddingWidth); } static nscoord GetAvailableContentHeight(const nsHTMLReflowState& aReflowState) { if (aReflowState.availableHeight == NS_INTRINSICSIZE) { return NS_INTRINSICSIZE; } nscoord borderPaddingHeight = aReflowState.mComputedBorderPadding.top + aReflowState.mComputedBorderPadding.bottom; return PR_MAX(0, aReflowState.availableHeight - borderPaddingHeight); } nsColumnSetFrame::ReflowConfig nsColumnSetFrame::ChooseColumnStrategy(const nsHTMLReflowState& aReflowState) { const nsStyleColumn* colStyle = GetStyleColumn(); nscoord availContentWidth = GetAvailableContentWidth(aReflowState); if (aReflowState.mComputedWidth != NS_INTRINSICSIZE) { availContentWidth = aReflowState.mComputedWidth; } nscoord colHeight = GetAvailableContentHeight(aReflowState); if (aReflowState.mComputedHeight != NS_INTRINSICSIZE) { colHeight = aReflowState.mComputedHeight; } nscoord colGap = 0; switch (colStyle->mColumnGap.GetUnit()) { case eStyleUnit_Coord: colGap = colStyle->mColumnGap.GetCoordValue(); break; case eStyleUnit_Percent: if (availContentWidth != NS_INTRINSICSIZE) { colGap = NSToCoordRound(colStyle->mColumnGap.GetPercentValue()*availContentWidth); } break; default: NS_NOTREACHED("Unknown gap type"); break; } PRInt32 numColumns = colStyle->mColumnCount; nscoord colWidth = NS_INTRINSICSIZE; if (colStyle->mColumnWidth.GetUnit() == eStyleUnit_Coord) { colWidth = colStyle->mColumnWidth.GetCoordValue(); // Reduce column count if necessary to make columns fit in the // available width. Compute max number of columns that fit in // availContentWidth, satisfying colGap*(maxColumns - 1) + // colWidth*maxColumns <= availContentWidth if (availContentWidth != NS_INTRINSICSIZE && colWidth + colGap > 0 && numColumns > 0) { // This expression uses truncated rounding, which is what we // want PRInt32 maxColumns = (availContentWidth + colGap)/(colGap + colWidth); numColumns = PR_MAX(1, PR_MIN(numColumns, maxColumns)); } } else if (numColumns > 0 && availContentWidth != NS_INTRINSICSIZE) { nscoord widthMinusGaps = availContentWidth - colGap*(numColumns - 1); colWidth = widthMinusGaps/numColumns; } // Take care of the situation where there's only one column but it's // still too wide colWidth = PR_MAX(1, PR_MIN(colWidth, availContentWidth)); nscoord expectedWidthLeftOver = 0; if (colWidth != NS_INTRINSICSIZE && availContentWidth != NS_INTRINSICSIZE) { // distribute leftover space // First, determine how many columns will be showing if the column // count is auto if (numColumns <= 0) { // choose so that colGap*(nominalColumnCount - 1) + // colWidth*nominalColumnCount is nearly availContentWidth // make sure to round down numColumns = (availContentWidth + colGap)/(colGap + colWidth); if (numColumns <= 0) { numColumns = 1; } } // Compute extra space and divide it among the columns nscoord extraSpace = availContentWidth - (colWidth*numColumns + colGap*(numColumns - 1)); nscoord extraToColumns = extraSpace/numColumns; colWidth += extraToColumns; expectedWidthLeftOver = extraSpace - (extraToColumns*numColumns); } // NOTE that the non-balancing behavior for non-auto computed height // is not in the CSS3 columns draft as of 18 January 2001 if (aReflowState.mComputedHeight == NS_INTRINSICSIZE) { // Balancing! if (numColumns <= 0) { // Hmm, auto column count, column width or available width is unknown, // and balancing is required. Let's just use one column then. numColumns = 1; } colHeight = PR_MIN(mLastBalanceHeight, GetAvailableContentHeight(aReflowState)); } else { // No balancing, so don't limit the column count numColumns = PR_INT32_MAX; } #ifdef DEBUG_roc printf("*** nsColumnSetFrame::ChooseColumnStrategy: numColumns=%d, colWidth=%d, expectedWidthLeftOver=%d, colHeight=%d, colGap=%d\n", numColumns, colWidth, expectedWidthLeftOver, colHeight, colGap); #endif ReflowConfig config = { numColumns, colWidth, expectedWidthLeftOver, colGap, colHeight }; return config; } // XXX copied from nsBlockFrame, should this be moved to nsContainerFrame? static void PlaceFrameView(nsIFrame* aFrame) { if (aFrame->HasView()) nsContainerFrame::PositionFrameView(aFrame); else nsContainerFrame::PositionChildViews(aFrame); } static void MoveChildTo(nsIFrame* aParent, nsIFrame* aChild, nsPoint aOrigin) { if (aChild->GetPosition() == aOrigin) { return; } nsRect* overflowArea = aChild->GetOverflowAreaProperty(PR_FALSE); nsRect r = overflowArea ? *overflowArea : nsRect(nsPoint(0, 0), aChild->GetSize()); r += aChild->GetPosition(); aParent->Invalidate(r); r -= aChild->GetPosition(); aChild->SetPosition(aOrigin); r += aOrigin; aParent->Invalidate(r); PlaceFrameView(aChild); } PRBool nsColumnSetFrame::ReflowChildren(nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowReason aKidReason, nsReflowStatus& aStatus, const ReflowConfig& aConfig, PRBool aUnboundedLastColumn, nsCollapsingMargin* aBottomMarginCarriedOut) { PRBool allFit = PR_TRUE; PRBool RTL = GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; PRBool shrinkingHeightOnly = aKidReason == eReflowReason_Resize && mLastBalanceHeight > aConfig.mColMaxHeight; #ifdef DEBUG_roc printf("*** Doing column reflow pass: mLastBalanceHeight=%d, mColMaxHeight=%d, RTL=%d\n, mBalanceColCount=%d, mColWidth=%d, mColGap=%d\n", mLastBalanceHeight, aConfig.mColMaxHeight, RTL, aConfig.mBalanceColCount, aConfig.mColWidth, aConfig.mColGap); #endif DrainOverflowColumns(); if (mLastBalanceHeight != aConfig.mColMaxHeight) { mLastBalanceHeight = aConfig.mColMaxHeight; // XXX Seems like this could fire if incremental reflow pushed the column set // down so we reflow incrementally with a different available height. // We need a way to do an incremental reflow and be sure availableHeight // changes are taken account of! Right now I think block frames with absolute // children might exit early. NS_ASSERTION(aKidReason != eReflowReason_Incremental, "incremental reflow should not have changed the balance height"); } // get our border and padding const nsMargin &borderPadding = aReflowState.mComputedBorderPadding; nsRect contentRect(0, 0, 0, 0); aDesiredSize.mMaxElementWidth = 0; nsRect overflowRect(0, 0, 0, 0); nsIFrame* child = mFrames.FirstChild(); nsPoint childOrigin = nsPoint(borderPadding.left, borderPadding.top); // For RTL, figure out where the last column's left edge should be. Since the // columns might not fill the frame exactly, we need to account for the // slop. Otherwise we'll waste time moving the columns by some tiny // amount unnecessarily. nscoord targetX = borderPadding.left; if (RTL) { nscoord availWidth = aReflowState.availableWidth; if (aReflowState.mComputedWidth != NS_INTRINSICSIZE) { availWidth = aReflowState.mComputedWidth; } if (availWidth != NS_INTRINSICSIZE) { childOrigin.x += availWidth - aConfig.mColWidth; targetX += aConfig.mExpectedWidthLeftOver; #ifdef DEBUG_roc printf("*** childOrigin.x = %d\n", childOrigin.x); #endif } } int columnCount = 0; PRBool reflowNext = PR_FALSE; while (child) { // Try to skip reflowing the child. We can't skip if the child is dirty. We also can't // skip if the next column is dirty, because the next column's first line(s) // might be pullable back to this column. We can't skip if it's the last child // because we need to obtain the bottom margin. PRBool skipIncremental = aKidReason == eReflowReason_Incremental && !(child->GetStateBits() & NS_FRAME_IS_DIRTY) && child->GetNextSibling() && !(child->GetNextSibling()->GetStateBits() & NS_FRAME_IS_DIRTY) && !aDesiredSize.mComputeMEW; // If we need to pull up content from the prev-in-flow then this is not just // a height shrink. The prev in flow will have set the dirty bit. PRBool skipResizeHeightShrink = shrinkingHeightOnly && child->GetSize().height <= aConfig.mColMaxHeight && !(child->GetStateBits() & NS_FRAME_IS_DIRTY); if (!reflowNext && (skipIncremental || skipResizeHeightShrink)) { // This child does not need to be reflowed, but we may need to move it MoveChildTo(this, child, childOrigin); // If this is the last frame then make sure we get the right status if (child->GetNextSibling()) { aStatus = NS_FRAME_NOT_COMPLETE; } else { aStatus = mLastFrameStatus; } #ifdef DEBUG_roc printf("*** Skipping child #%d %p (incremental %d, resize height shrink %d): status = %d\n", columnCount, (void*)child, skipIncremental, skipResizeHeightShrink, aStatus); #endif } else { nsSize availSize(aConfig.mColWidth, aConfig.mColMaxHeight); if (aUnboundedLastColumn && columnCount == aConfig.mBalanceColCount - 1) { availSize.height = GetAvailableContentHeight(aReflowState); } nsReflowReason tmpReason = aKidReason; if (reflowNext && aKidReason == eReflowReason_Incremental && !(child->GetStateBits() & NS_FRAME_IS_DIRTY)) { // If this frame was not being incrementally reflowed but was // just reflowed because the previous frame wants us to // reflow, then force this child to reflow its dirty lines! // XXX what we should really do here is add the child block to // the incremental reflow path! Currently I think if there's an // incremental reflow targeted only at the absolute frames of a // column, then the column will be dirty BUT reflowing it will // not reflow any lines affected by the prev-in-flow! tmpReason = eReflowReason_Dirty; } nsHTMLReflowState kidReflowState(GetPresContext(), aReflowState, child, availSize, availSize.width, aReflowState.mComputedHeight, tmpReason); kidReflowState.mFlags.mIsTopOfPage = PR_TRUE; #ifdef DEBUG_roc printf("*** Reflowing child #%d %p: reason = %d, availHeight=%d\n", columnCount, (void*)child, tmpReason, availSize.height); #endif // Note if the column's next in flow is not being changed by this incremental reflow. // This may allow the current column to avoid trying to pull lines from the next column. if (child->GetNextSibling() && aKidReason == eReflowReason_Incremental && !(child->GetNextSibling()->GetStateBits() & NS_FRAME_IS_DIRTY)) { kidReflowState.mFlags.mNextInFlowUntouched = PR_TRUE; } nsHTMLReflowMetrics kidDesiredSize(aDesiredSize.mComputeMEW, aDesiredSize.mFlags); // XXX it would be cool to consult the space manager for the // previous block to figure out the region of floats from the // previous column that extend into this column, and subtract // that region from the new space manager. So you could stick a // really big float in the first column and text in following // columns would flow around it. // Reflow the frame ReflowChild(child, GetPresContext(), kidDesiredSize, kidReflowState, childOrigin.x + kidReflowState.mComputedMargin.left, childOrigin.y + kidReflowState.mComputedMargin.top, 0, aStatus); if (kidDesiredSize.height > aConfig.mColMaxHeight) { allFit = PR_FALSE; } reflowNext = (aStatus & NS_FRAME_REFLOW_NEXTINFLOW) != 0; #ifdef DEBUG_roc printf("*** Reflowed child #%d %p: status = %d, desiredSize=%d,%d\n", columnCount, (void*)child, aStatus, kidDesiredSize.width, kidDesiredSize.height); #endif NS_FRAME_TRACE_REFLOW_OUT("Column::Reflow", aStatus); *aBottomMarginCarriedOut = kidDesiredSize.mCarriedOutBottomMargin; FinishReflowChild(child, GetPresContext(), &kidReflowState, kidDesiredSize, childOrigin.x, childOrigin.y, 0); if (aDesiredSize.mComputeMEW) { aDesiredSize.mMaxElementWidth = PR_MAX(aDesiredSize.mMaxElementWidth, kidDesiredSize.mMaxElementWidth); } } contentRect.UnionRect(contentRect, child->GetRect()); ConsiderChildOverflow(overflowRect, child); // Build a continuation column if necessary nsIFrame* kidNextInFlow = child->GetNextInFlow(); if (NS_FRAME_IS_COMPLETE(aStatus) && !NS_FRAME_IS_TRUNCATED(aStatus)) { NS_ASSERTION(!kidNextInFlow, "next in flow should have been deleted"); break; } else { ++columnCount; // Make sure that the column has a next-in-flow. If not, we must // create one to hold the overflowing stuff, even if we're just // going to put it on our overflow list and let *our* // next in flow handle it. if (!kidNextInFlow) { NS_ASSERTION(aStatus & NS_FRAME_REFLOW_NEXTINFLOW, "We have to create a continuation, but the block doesn't want us to reflow it?"); // We need to create a continuing column nsresult rv = CreateNextInFlow(GetPresContext(), this, child, kidNextInFlow); if (NS_FAILED(rv)) { NS_NOTREACHED("Couldn't create continuation"); break; } kidNextInFlow->AddStateBits(NS_BLOCK_SPACE_MGR); // Do an initial reflow if we're going to reflow this thing. aKidReason = eReflowReason_Initial; } if (columnCount >= aConfig.mBalanceColCount) { // No more columns allowed here. Stop. aStatus |= NS_FRAME_REFLOW_NEXTINFLOW; kidNextInFlow->AddStateBits(NS_FRAME_IS_DIRTY); // Move any of our leftover columns to our overflow list. Our // next-in-flow will eventually pick them up. nsIFrame* continuationColumns = child->GetNextSibling(); if (continuationColumns) { SetOverflowFrames(GetPresContext(), continuationColumns); child->SetNextSibling(nsnull); } break; } } // Advance to the next column child = child->GetNextSibling(); if (child) { if (!RTL) { childOrigin.x += aConfig.mColWidth + aConfig.mColGap; } else { childOrigin.x -= aConfig.mColWidth + aConfig.mColGap; } #ifdef DEBUG_roc printf("*** NEXT CHILD ORIGIN.x = %d\n", childOrigin.x); #endif } } // If we're doing RTL, we need to make sure our last column is at the left-hand side of the frame. if (RTL && childOrigin.x != targetX) { overflowRect = nsRect(0, 0, 0, 0); contentRect = nsRect(0, 0, 0, 0); PRInt32 deltaX = targetX - childOrigin.x; #ifdef DEBUG_roc printf("*** CHILDORIGIN.x = %d, targetX = %d, DELTAX = %d\n", childOrigin.x, targetX, deltaX); #endif for (child = mFrames.FirstChild(); child; child = child->GetNextSibling()) { MoveChildTo(this, child, child->GetPosition() + nsPoint(deltaX, 0)); ConsiderChildOverflow(overflowRect, child); contentRect.UnionRect(contentRect, child->GetRect()); } } mLastFrameStatus = aStatus; // contentRect included the borderPadding.left,borderPadding.top of the child rects contentRect -= nsPoint(borderPadding.left, borderPadding.top); nsSize contentSize = nsSize(contentRect.XMost(), contentRect.YMost()); // Apply computed and min/max values if (aReflowState.mComputedHeight != NS_INTRINSICSIZE) { contentSize.height = aReflowState.mComputedHeight; } else { if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMaxHeight) { contentSize.height = PR_MIN(aReflowState.mComputedMaxHeight, contentSize.height); } if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMinHeight) { contentSize.height = PR_MAX(aReflowState.mComputedMinHeight, contentSize.height); } } if (aReflowState.mComputedWidth != NS_INTRINSICSIZE) { contentSize.width = aReflowState.mComputedWidth; } else { if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMaxWidth) { contentSize.width = PR_MIN(aReflowState.mComputedMaxWidth, contentSize.width); } if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMinWidth) { contentSize.width = PR_MAX(aReflowState.mComputedMinWidth, contentSize.width); } } aDesiredSize.height = borderPadding.top + contentSize.height + borderPadding.bottom; aDesiredSize.width = contentSize.width + borderPadding.left + borderPadding.right; aDesiredSize.ascent = aDesiredSize.height; aDesiredSize.descent = 0; aDesiredSize.mMaximumWidth = aDesiredSize.width; if (aDesiredSize.mComputeMEW) { // add in padding. aDesiredSize.mMaxElementWidth += borderPadding.left + borderPadding.right; } overflowRect.UnionRect(overflowRect, nsRect(0, 0, aDesiredSize.width, aDesiredSize.height)); aDesiredSize.mOverflowArea = overflowRect; #ifdef DEBUG_roc printf("*** DONE PASS feasible=%d\n", allFit && NS_FRAME_IS_COMPLETE(aStatus) && !NS_FRAME_IS_TRUNCATED(aStatus)); #endif return allFit && NS_FRAME_IS_COMPLETE(aStatus) && !NS_FRAME_IS_TRUNCATED(aStatus); } static nscoord ComputeSumOfChildHeights(nsIFrame* aFrame) { nscoord totalHeight = 0; for (nsIFrame* f = aFrame->GetFirstChild(nsnull); f; f = f->GetNextSibling()) { // individual columns don't have borders or padding so this is a // reasonable way to get their content height totalHeight += f->GetSize().height; } return totalHeight; } void nsColumnSetFrame::DrainOverflowColumns() { // First grab the prev-in-flows overflows and reparent them to this // frame. nsColumnSetFrame* prev = NS_STATIC_CAST(nsColumnSetFrame*, GetPrevInFlow()); if (prev) { nsIFrame* overflows = prev->GetOverflowFrames(GetPresContext(), PR_TRUE); if (overflows) { // Make all the frames on the overflow list mine nsIFrame* lastFrame = nsnull; for (nsIFrame* f = overflows; f; f = f->GetNextSibling()) { f->SetParent(this); // When pushing and pulling frames we need to check for whether any // views need to be reparented nsHTMLContainerFrame::ReparentFrameView(GetPresContext(), f, prev, this); // Get the next frame lastFrame = f; } NS_ASSERTION(lastFrame, "overflow list was created with no frames"); lastFrame->SetNextSibling(mFrames.FirstChild()); mFrames.SetFrames(overflows); } } // Now pull back our own overflows and append them to our children. // We don't need to reparent them since we're already their parent. nsIFrame* overflows = GetOverflowFrames(GetPresContext(), PR_TRUE); if (overflows) { mFrames.AppendFrames(this, overflows); } } NS_IMETHODIMP nsColumnSetFrame::Reflow(nsPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) { DO_GLOBAL_REFLOW_COUNT("nsColumnSetFrame", aReflowState.reason); DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); // Initialize OUT parameter aStatus = NS_FRAME_COMPLETE; //------------ Handle Incremental Reflow ----------------- nsReflowReason kidReason = aReflowState.reason; if ( aReflowState.reason == eReflowReason_Incremental ) { nsHTMLReflowCommand *command = aReflowState.path->mReflowCommand; // Dirty any frames on the incremental reflow path nsReflowPath *path = aReflowState.path; nsReflowPath::iterator iter = path->FirstChild(); nsReflowPath::iterator end = path->EndChildren(); for ( ; iter != end; ++iter) { (*iter)->AddStateBits(NS_FRAME_IS_DIRTY); } // See if it's targeted at us if (command) { switch (command->Type()) { case eReflowType_StyleChanged: kidReason = eReflowReason_StyleChange; break; // if it's a dirty type then reflow us with a dirty reflow case eReflowType_ReflowDirty: kidReason = eReflowReason_Dirty; break; default: NS_ERROR("Unexpected Reflow Type"); } } } ReflowConfig config = ChooseColumnStrategy(aReflowState); PRBool isBalancing = config.mBalanceColCount < PR_INT32_MAX; // If balancing, then we allow the last column to grow to unbounded // height during the first reflow. This gives us a way to estimate // what the average column height should be, because we can measure // the heights of all the columns and sum them up. But don't do this // if we have a next in flow because we don't want to suck all its // content back here and then have to push it out again! nsIFrame* nextInFlow = GetNextInFlow(); PRBool unboundedLastColumn = isBalancing && nextInFlow; nsCollapsingMargin carriedOutBottomMargin; PRBool feasible = ReflowChildren(aDesiredSize, aReflowState, kidReason, aStatus, config, unboundedLastColumn, &carriedOutBottomMargin); if (isBalancing) { if (feasible || // All children were reflowed as necessary, so we can go ahead // and do resize reflows from now on kidReason != eReflowReason_StyleChange) { // Any non-stylechange reflow can be followed by resize // reflows. But a stylechange reflow demands that all children // be reflowed, which may not have happened yet; some of them // might just have been pushed to an overflow list. So we have to // keep doing stylechange reflows. kidReason = eReflowReason_Resize; } nscoord availableContentHeight = GetAvailableContentHeight(aReflowState); // Termination of the algorithm below is guaranteed because // knownFeasibleHeight - knownInfeasibleHeight decreases in every // iteration. nscoord knownFeasibleHeight = NS_INTRINSICSIZE; nscoord knownInfeasibleHeight = 0; // We set this flag when we detect that we may contain a frame // that can break anywhere (thus foiling the linear decrease-by-one // search) PRBool maybeContinuousBreakingDetected = PR_FALSE; while (1) { nscoord lastKnownFeasibleHeight = knownFeasibleHeight; nscoord maxHeight = 0; for (nsIFrame* f = mFrames.FirstChild(); f; f = f->GetNextSibling()) { maxHeight = PR_MAX(maxHeight, f->GetSize().height); } // Record what we learned from the last reflow if (feasible) { // maxHeight is feasible (and always maxHeight <= // mLastBalanceHeight) knownFeasibleHeight = PR_MIN(knownFeasibleHeight, maxHeight); // Furthermore, no height less than the height of the last // column can ever be feasible. if (mFrames.GetLength() == config.mBalanceColCount) { knownInfeasibleHeight = PR_MAX(knownInfeasibleHeight, mFrames.LastChild()->GetSize().height - 1); } } else { knownInfeasibleHeight = PR_MAX(knownInfeasibleHeight, mLastBalanceHeight); if (unboundedLastColumn) { // The last column is unbounded, so all content got reflowed, so the // maxHeight is feasible. knownFeasibleHeight = PR_MIN(knownFeasibleHeight, maxHeight); } } #ifdef DEBUG_roc printf("*** nsColumnSetFrame::Reflow balancing knownInfeasible=%d knownFeasible=%d\n", knownInfeasibleHeight, knownFeasibleHeight); #endif if (knownInfeasibleHeight >= knownFeasibleHeight - 1) { // knownFeasibleHeight is where we want to be break; } if (knownInfeasibleHeight >= availableContentHeight) { break; } if (lastKnownFeasibleHeight - knownFeasibleHeight == 1) { // We decreased the feasible height by one twip only. This could // indicate that there is a continuously breakable child frame // that we are crawling through. maybeContinuousBreakingDetected = PR_TRUE; } nscoord nextGuess = (knownFeasibleHeight + knownInfeasibleHeight)/2; // The constant of 600 twips is arbitrary. It's about two line-heights. if (knownFeasibleHeight - nextGuess < 600 && !maybeContinuousBreakingDetected) { // We're close to our target, so just try shrinking just the // minimum amount that will cause one of our columns to break // differently. nextGuess = knownFeasibleHeight - 1; } else if (unboundedLastColumn) { // Make a guess by dividing that into N columns. Add some slop // to try to make it on the feasible side. The constant of // 600 twips is arbitrary. It's about two line-heights. nextGuess = ComputeSumOfChildHeights(this)/config.mBalanceColCount + 600; // Sanitize it nextGuess = PR_MIN(PR_MAX(nextGuess, knownInfeasibleHeight + 1), knownFeasibleHeight - 1); } else if (knownFeasibleHeight == NS_INTRINSICSIZE) { // This can happen when we had a next-in-flow so we didn't // want to do an unbounded height measuring step. Let's just increase // from the infeasible height by some reasonable amount. nextGuess = knownInfeasibleHeight*2 + 600; } // Don't bother guessing more than our height constraint. nextGuess = PR_MIN(availableContentHeight, nextGuess); #ifdef DEBUG_roc printf("*** nsColumnSetFrame::Reflow balancing choosing next guess=%d\n", nextGuess); #endif config.mColMaxHeight = nextGuess; unboundedLastColumn = PR_FALSE; feasible = ReflowChildren(aDesiredSize, aReflowState, kidReason, aStatus, config, PR_FALSE, &carriedOutBottomMargin); } if (!feasible) { // We may need to reflow one more time at the feasible height to // get a valid layout. PRBool skip = PR_FALSE; if (knownInfeasibleHeight >= availableContentHeight) { config.mColMaxHeight = availableContentHeight; if (mLastBalanceHeight == availableContentHeight) { skip = PR_TRUE; } } else { config.mColMaxHeight = knownFeasibleHeight; } if (!skip) { ReflowChildren(aDesiredSize, aReflowState, eReflowReason_Resize, aStatus, config, PR_FALSE, &carriedOutBottomMargin); } } } CheckInvalidateSizeChange(GetPresContext(), aDesiredSize, aReflowState); FinishAndStoreOverflow(&aDesiredSize); aDesiredSize.mCarriedOutBottomMargin = carriedOutBottomMargin; NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); return NS_OK; } NS_IMETHODIMP nsColumnSetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsRect& aDirtyRect, const nsDisplayListSet& aLists) { nsresult rv = DisplayBorderBackgroundOutline(aBuilder, aLists); NS_ENSURE_SUCCESS(rv, rv); nsIFrame* kid = mFrames.FirstChild(); // Our children won't have backgrounds so it doesn't matter where we put them. while (kid) { nsresult rv = BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists); NS_ENSURE_SUCCESS(rv, rv); kid = kid->GetNextSibling(); } return NS_OK; } PRIntn nsColumnSetFrame::GetSkipSides() const { return 0; } NS_IMETHODIMP nsColumnSetFrame::AppendFrames(nsIAtom* aListName, nsIFrame* aFrameList) { NS_NOTREACHED("AppendFrames not supported"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsColumnSetFrame::InsertFrames(nsIAtom* aListName, nsIFrame* aPrevFrame, nsIFrame* aFrameList) { NS_NOTREACHED("InsertFrames not supported"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsColumnSetFrame::RemoveFrame(nsIAtom* aListName, nsIFrame* aOldFrame) { NS_NOTREACHED("RemoveFrame not supported"); return NS_ERROR_NOT_IMPLEMENTED; }