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