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 : if (aListID == kAbsoluteList) {
304 0 : return nsContainerFrame::SetInitialChildList(aListID, aChildList);
305 : }
306 :
307 0 : NS_ASSERTION(aListID == kPrincipalList,
308 : "Only default child list supported");
309 0 : NS_ASSERTION(aChildList.OnlyChild(),
310 : "initial child list must have exactly one child");
311 : // Queue up the frames for the content frame
312 0 : return nsContainerFrame::SetInitialChildList(kPrincipalList, aChildList);
313 : }
314 :
315 : static nscoord
316 0 : GetAvailableContentWidth(const nsHTMLReflowState& aReflowState)
317 : {
318 0 : if (aReflowState.availableWidth == NS_INTRINSICSIZE) {
319 0 : return NS_INTRINSICSIZE;
320 : }
321 : nscoord borderPaddingWidth =
322 : aReflowState.mComputedBorderPadding.left +
323 0 : aReflowState.mComputedBorderPadding.right;
324 0 : return NS_MAX(0, aReflowState.availableWidth - borderPaddingWidth);
325 : }
326 :
327 : static nscoord
328 0 : GetAvailableContentHeight(const nsHTMLReflowState& aReflowState)
329 : {
330 0 : if (aReflowState.availableHeight == NS_INTRINSICSIZE) {
331 0 : return NS_INTRINSICSIZE;
332 : }
333 : nscoord borderPaddingHeight =
334 : aReflowState.mComputedBorderPadding.top +
335 0 : aReflowState.mComputedBorderPadding.bottom;
336 0 : return NS_MAX(0, aReflowState.availableHeight - borderPaddingHeight);
337 : }
338 :
339 : static nscoord
340 0 : GetColumnGap(nsColumnSetFrame* aFrame,
341 : const nsStyleColumn* aColStyle)
342 : {
343 0 : if (eStyleUnit_Normal == aColStyle->mColumnGap.GetUnit())
344 0 : return aFrame->GetStyleFont()->mFont.size;
345 0 : if (eStyleUnit_Coord == aColStyle->mColumnGap.GetUnit()) {
346 0 : nscoord colGap = aColStyle->mColumnGap.GetCoordValue();
347 0 : NS_ASSERTION(colGap >= 0, "negative column gap");
348 0 : return colGap;
349 : }
350 :
351 0 : NS_NOTREACHED("Unknown gap type");
352 0 : return 0;
353 : }
354 :
355 : nsColumnSetFrame::ReflowConfig
356 0 : nsColumnSetFrame::ChooseColumnStrategy(const nsHTMLReflowState& aReflowState)
357 : {
358 0 : const nsStyleColumn* colStyle = GetStyleColumn();
359 0 : nscoord availContentWidth = GetAvailableContentWidth(aReflowState);
360 0 : if (aReflowState.ComputedWidth() != NS_INTRINSICSIZE) {
361 0 : availContentWidth = aReflowState.ComputedWidth();
362 : }
363 0 : nscoord colHeight = GetAvailableContentHeight(aReflowState);
364 0 : if (aReflowState.ComputedHeight() != NS_INTRINSICSIZE) {
365 0 : colHeight = aReflowState.ComputedHeight();
366 : }
367 :
368 0 : nscoord colGap = GetColumnGap(this, colStyle);
369 0 : PRInt32 numColumns = colStyle->mColumnCount;
370 :
371 0 : bool isBalancing = colStyle->mColumnFill == NS_STYLE_COLUMN_FILL_BALANCE;
372 0 : if (isBalancing) {
373 0 : const PRUint32 MAX_NESTED_COLUMN_BALANCING = 2;
374 0 : PRUint32 cnt = 1;
375 0 : for (const nsHTMLReflowState* rs = aReflowState.parentReflowState;
376 : rs && cnt < MAX_NESTED_COLUMN_BALANCING;
377 : rs = rs->parentReflowState) {
378 0 : if (rs->mFlags.mIsColumnBalancing) {
379 0 : ++cnt;
380 : }
381 : }
382 0 : if (cnt == MAX_NESTED_COLUMN_BALANCING) {
383 0 : numColumns = 1;
384 : }
385 : }
386 :
387 : nscoord colWidth;
388 0 : if (colStyle->mColumnWidth.GetUnit() == eStyleUnit_Coord) {
389 0 : colWidth = colStyle->mColumnWidth.GetCoordValue();
390 0 : NS_ASSERTION(colWidth >= 0, "negative column width");
391 : // Reduce column count if necessary to make columns fit in the
392 : // available width. Compute max number of columns that fit in
393 : // availContentWidth, satisfying colGap*(maxColumns - 1) +
394 : // colWidth*maxColumns <= availContentWidth
395 0 : if (availContentWidth != NS_INTRINSICSIZE && colGap + colWidth > 0
396 : && numColumns > 0) {
397 : // This expression uses truncated rounding, which is what we
398 : // want
399 0 : PRInt32 maxColumns = (availContentWidth + colGap)/(colGap + colWidth);
400 0 : numColumns = NS_MAX(1, NS_MIN(numColumns, maxColumns));
401 : }
402 0 : } else if (numColumns > 0 && availContentWidth != NS_INTRINSICSIZE) {
403 0 : nscoord widthMinusGaps = availContentWidth - colGap*(numColumns - 1);
404 0 : colWidth = widthMinusGaps/numColumns;
405 : } else {
406 0 : colWidth = NS_INTRINSICSIZE;
407 : }
408 : // Take care of the situation where there's only one column but it's
409 : // still too wide
410 0 : colWidth = NS_MAX(1, NS_MIN(colWidth, availContentWidth));
411 :
412 0 : nscoord expectedWidthLeftOver = 0;
413 :
414 0 : if (colWidth != NS_INTRINSICSIZE && availContentWidth != NS_INTRINSICSIZE) {
415 : // distribute leftover space
416 :
417 : // First, determine how many columns will be showing if the column
418 : // count is auto
419 0 : if (numColumns <= 0) {
420 : // choose so that colGap*(nominalColumnCount - 1) +
421 : // colWidth*nominalColumnCount is nearly availContentWidth
422 : // make sure to round down
423 0 : if (colGap + colWidth > 0) {
424 0 : numColumns = (availContentWidth + colGap)/(colGap + colWidth);
425 : }
426 0 : if (numColumns <= 0) {
427 0 : numColumns = 1;
428 : }
429 : }
430 :
431 : // Compute extra space and divide it among the columns
432 : nscoord extraSpace =
433 0 : NS_MAX(0, availContentWidth - (colWidth*numColumns + colGap*(numColumns - 1)));
434 0 : nscoord extraToColumns = extraSpace/numColumns;
435 0 : colWidth += extraToColumns;
436 0 : expectedWidthLeftOver = extraSpace - (extraToColumns*numColumns);
437 : }
438 :
439 : // If column-fill is set to 'balance', then we want to balance the columns.
440 0 : if (isBalancing) {
441 : // Balancing!
442 :
443 0 : if (numColumns <= 0) {
444 : // Hmm, auto column count, column width or available width is unknown,
445 : // and balancing is required. Let's just use one column then.
446 0 : numColumns = 1;
447 : }
448 :
449 : colHeight = NS_MIN(mLastBalanceHeight,
450 0 : GetAvailableContentHeight(aReflowState));
451 : } else {
452 : // This is the case when the column-fill property is set to 'auto'.
453 : // No balancing, so don't limit the column count
454 :
455 0 : numColumns = PR_INT32_MAX;
456 : }
457 :
458 : #ifdef DEBUG_roc
459 : printf("*** nsColumnSetFrame::ChooseColumnStrategy: numColumns=%d, colWidth=%d, expectedWidthLeftOver=%d, colHeight=%d, colGap=%d\n",
460 : numColumns, colWidth, expectedWidthLeftOver, colHeight, colGap);
461 : #endif
462 0 : ReflowConfig config = { numColumns, colWidth, expectedWidthLeftOver, colGap, colHeight };
463 : return config;
464 : }
465 :
466 : // XXX copied from nsBlockFrame, should this be moved to nsContainerFrame?
467 : static void
468 0 : PlaceFrameView(nsIFrame* aFrame)
469 : {
470 0 : if (aFrame->HasView())
471 0 : nsContainerFrame::PositionFrameView(aFrame);
472 : else
473 0 : nsContainerFrame::PositionChildViews(aFrame);
474 0 : }
475 :
476 0 : static void MoveChildTo(nsIFrame* aParent, nsIFrame* aChild, nsPoint aOrigin) {
477 0 : if (aChild->GetPosition() == aOrigin) {
478 0 : return;
479 : }
480 :
481 0 : nsRect r = aChild->GetVisualOverflowRect();
482 0 : r += aChild->GetPosition();
483 0 : aParent->Invalidate(r);
484 0 : r -= aChild->GetPosition();
485 0 : aChild->SetPosition(aOrigin);
486 0 : r += aOrigin;
487 0 : aParent->Invalidate(r);
488 0 : PlaceFrameView(aChild);
489 : }
490 :
491 : nscoord
492 0 : nsColumnSetFrame::GetMinWidth(nsRenderingContext *aRenderingContext) {
493 0 : nscoord width = 0;
494 0 : DISPLAY_MIN_WIDTH(this, width);
495 0 : if (mFrames.FirstChild()) {
496 0 : width = mFrames.FirstChild()->GetMinWidth(aRenderingContext);
497 : }
498 0 : const nsStyleColumn* colStyle = GetStyleColumn();
499 : nscoord colWidth;
500 0 : if (colStyle->mColumnWidth.GetUnit() == eStyleUnit_Coord) {
501 0 : colWidth = colStyle->mColumnWidth.GetCoordValue();
502 : // As available width reduces to zero, we reduce our number of columns
503 : // to one, and don't enforce the column width, so just return the min
504 : // of the child's min-width with any specified column width.
505 0 : width = NS_MIN(width, colWidth);
506 : } else {
507 0 : NS_ASSERTION(colStyle->mColumnCount > 0,
508 : "column-count and column-width can't both be auto");
509 : // As available width reduces to zero, we still have mColumnCount columns,
510 : // so multiply the child's min-width by the number of columns.
511 0 : colWidth = width;
512 0 : width *= colStyle->mColumnCount;
513 : // The multiplication above can make 'width' negative (integer overflow),
514 : // so use NS_MAX to protect against that.
515 0 : width = NS_MAX(width, colWidth);
516 : }
517 : // XXX count forced column breaks here? Maybe we should return the child's
518 : // min-width times the minimum number of columns.
519 0 : return width;
520 : }
521 :
522 : nscoord
523 0 : nsColumnSetFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) {
524 : // Our preferred width is our desired column width, if specified, otherwise
525 : // the child's preferred width, times the number of columns, plus the width
526 : // of any required column gaps
527 : // XXX what about forced column breaks here?
528 0 : nscoord result = 0;
529 0 : DISPLAY_PREF_WIDTH(this, result);
530 0 : const nsStyleColumn* colStyle = GetStyleColumn();
531 0 : nscoord colGap = GetColumnGap(this, colStyle);
532 :
533 : nscoord colWidth;
534 0 : if (colStyle->mColumnWidth.GetUnit() == eStyleUnit_Coord) {
535 0 : colWidth = colStyle->mColumnWidth.GetCoordValue();
536 0 : } else if (mFrames.FirstChild()) {
537 0 : colWidth = mFrames.FirstChild()->GetPrefWidth(aRenderingContext);
538 : } else {
539 0 : colWidth = 0;
540 : }
541 :
542 0 : PRInt32 numColumns = colStyle->mColumnCount;
543 0 : if (numColumns <= 0) {
544 : // if column-count is auto, assume one column
545 0 : numColumns = 1;
546 : }
547 :
548 0 : nscoord width = colWidth*numColumns + colGap*(numColumns - 1);
549 : // The multiplication above can make 'width' negative (integer overflow),
550 : // so use NS_MAX to protect against that.
551 0 : result = NS_MAX(width, colWidth);
552 0 : return result;
553 : }
554 :
555 : bool
556 0 : nsColumnSetFrame::ReflowChildren(nsHTMLReflowMetrics& aDesiredSize,
557 : const nsHTMLReflowState& aReflowState,
558 : nsReflowStatus& aStatus,
559 : const ReflowConfig& aConfig,
560 : bool aUnboundedLastColumn,
561 : nsCollapsingMargin* aBottomMarginCarriedOut,
562 : ColumnBalanceData& aColData)
563 : {
564 0 : aColData.Reset();
565 0 : bool allFit = true;
566 0 : bool RTL = GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
567 0 : bool shrinkingHeightOnly = !NS_SUBTREE_DIRTY(this) &&
568 0 : mLastBalanceHeight > aConfig.mColMaxHeight;
569 :
570 : #ifdef DEBUG_roc
571 : printf("*** Doing column reflow pass: mLastBalanceHeight=%d, mColMaxHeight=%d, RTL=%d\n, mBalanceColCount=%d, mColWidth=%d, mColGap=%d\n",
572 : mLastBalanceHeight, aConfig.mColMaxHeight, RTL, aConfig.mBalanceColCount,
573 : aConfig.mColWidth, aConfig.mColGap);
574 : #endif
575 :
576 0 : DrainOverflowColumns();
577 :
578 0 : if (mLastBalanceHeight != aConfig.mColMaxHeight) {
579 0 : mLastBalanceHeight = aConfig.mColMaxHeight;
580 : // XXX Seems like this could fire if incremental reflow pushed the column set
581 : // down so we reflow incrementally with a different available height.
582 : // We need a way to do an incremental reflow and be sure availableHeight
583 : // changes are taken account of! Right now I think block frames with absolute
584 : // children might exit early.
585 : //NS_ASSERTION(aKidReason != eReflowReason_Incremental,
586 : // "incremental reflow should not have changed the balance height");
587 : }
588 :
589 : // get our border and padding
590 0 : const nsMargin &borderPadding = aReflowState.mComputedBorderPadding;
591 :
592 0 : nsRect contentRect(0, 0, 0, 0);
593 0 : nsOverflowAreas overflowRects;
594 :
595 0 : nsIFrame* child = mFrames.FirstChild();
596 0 : nsPoint childOrigin = nsPoint(borderPadding.left, borderPadding.top);
597 : // For RTL, figure out where the last column's left edge should be. Since the
598 : // columns might not fill the frame exactly, we need to account for the
599 : // slop. Otherwise we'll waste time moving the columns by some tiny
600 : // amount unnecessarily.
601 0 : nscoord targetX = borderPadding.left;
602 0 : if (RTL) {
603 0 : nscoord availWidth = aReflowState.availableWidth;
604 0 : if (aReflowState.ComputedWidth() != NS_INTRINSICSIZE) {
605 0 : availWidth = aReflowState.ComputedWidth();
606 : }
607 0 : if (availWidth != NS_INTRINSICSIZE) {
608 0 : childOrigin.x += availWidth - aConfig.mColWidth;
609 0 : targetX += aConfig.mExpectedWidthLeftOver;
610 : #ifdef DEBUG_roc
611 : printf("*** childOrigin.x = %d\n", childOrigin.x);
612 : #endif
613 : }
614 : }
615 0 : int columnCount = 0;
616 0 : int contentBottom = 0;
617 0 : bool reflowNext = false;
618 :
619 0 : while (child) {
620 : // Try to skip reflowing the child. We can't skip if the child is dirty. We also can't
621 : // skip if the next column is dirty, because the next column's first line(s)
622 : // might be pullable back to this column. We can't skip if it's the last child
623 : // because we need to obtain the bottom margin. We can't skip
624 : // if this is the last column and we're supposed to assign unbounded
625 : // height to it, because that could change the available height from
626 : // the last time we reflowed it and we should try to pull all the
627 : // content from its next sibling. (Note that it might be the last
628 : // column, but not be the last child because the desired number of columns
629 : // has changed.)
630 0 : bool skipIncremental = !aReflowState.ShouldReflowAllKids()
631 0 : && !NS_SUBTREE_DIRTY(child)
632 0 : && child->GetNextSibling()
633 0 : && !(aUnboundedLastColumn && columnCount == aConfig.mBalanceColCount - 1)
634 0 : && !NS_SUBTREE_DIRTY(child->GetNextSibling());
635 : // If we need to pull up content from the prev-in-flow then this is not just
636 : // a height shrink. The prev in flow will have set the dirty bit.
637 : // Check the overflow rect YMost instead of just the child's content height. The child
638 : // may have overflowing content that cares about the available height boundary.
639 : // (It may also have overflowing content that doesn't care about the available height
640 : // boundary, but if so, too bad, this optimization is defeated.)
641 : // We want scrollable overflow here since this is a calculation that
642 : // affects layout.
643 : bool skipResizeHeightShrink = shrinkingHeightOnly
644 0 : && child->GetScrollableOverflowRect().YMost() <= aConfig.mColMaxHeight;
645 :
646 0 : nscoord childContentBottom = 0;
647 0 : if (!reflowNext && (skipIncremental || skipResizeHeightShrink)) {
648 : // This child does not need to be reflowed, but we may need to move it
649 0 : MoveChildTo(this, child, childOrigin);
650 :
651 : // If this is the last frame then make sure we get the right status
652 0 : nsIFrame* kidNext = child->GetNextSibling();
653 0 : if (kidNext) {
654 0 : aStatus = (kidNext->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)
655 : ? NS_FRAME_OVERFLOW_INCOMPLETE
656 0 : : NS_FRAME_NOT_COMPLETE;
657 : } else {
658 0 : aStatus = mLastFrameStatus;
659 : }
660 0 : childContentBottom = nsLayoutUtils::CalculateContentBottom(child);
661 : #ifdef DEBUG_roc
662 : printf("*** Skipping child #%d %p (incremental %d, resize height shrink %d): status = %d\n",
663 : columnCount, (void*)child, skipIncremental, skipResizeHeightShrink, aStatus);
664 : #endif
665 : } else {
666 0 : nsSize availSize(aConfig.mColWidth, aConfig.mColMaxHeight);
667 :
668 0 : if (aUnboundedLastColumn && columnCount == aConfig.mBalanceColCount - 1) {
669 0 : availSize.height = GetAvailableContentHeight(aReflowState);
670 : }
671 :
672 0 : if (reflowNext)
673 0 : child->AddStateBits(NS_FRAME_IS_DIRTY);
674 :
675 : nsHTMLReflowState kidReflowState(PresContext(), aReflowState, child,
676 : availSize, availSize.width,
677 0 : aReflowState.ComputedHeight());
678 0 : kidReflowState.mFlags.mIsTopOfPage = true;
679 0 : kidReflowState.mFlags.mTableIsSplittable = false;
680 0 : kidReflowState.mFlags.mIsColumnBalancing = aConfig.mBalanceColCount < PR_INT32_MAX;
681 :
682 : #ifdef DEBUG_roc
683 : printf("*** Reflowing child #%d %p: availHeight=%d\n",
684 : columnCount, (void*)child,availSize.height);
685 : #endif
686 :
687 : // Note if the column's next in flow is not being changed by this incremental reflow.
688 : // This may allow the current column to avoid trying to pull lines from the next column.
689 0 : if (child->GetNextSibling() &&
690 0 : !(GetStateBits() & NS_FRAME_IS_DIRTY) &&
691 0 : !(child->GetNextSibling()->GetStateBits() & NS_FRAME_IS_DIRTY)) {
692 0 : kidReflowState.mFlags.mNextInFlowUntouched = true;
693 : }
694 :
695 0 : nsHTMLReflowMetrics kidDesiredSize(aDesiredSize.mFlags);
696 :
697 : // XXX it would be cool to consult the float manager for the
698 : // previous block to figure out the region of floats from the
699 : // previous column that extend into this column, and subtract
700 : // that region from the new float manager. So you could stick a
701 : // really big float in the first column and text in following
702 : // columns would flow around it.
703 :
704 : // Reflow the frame
705 : ReflowChild(child, PresContext(), kidDesiredSize, kidReflowState,
706 : childOrigin.x + kidReflowState.mComputedMargin.left,
707 : childOrigin.y + kidReflowState.mComputedMargin.top,
708 0 : 0, aStatus);
709 :
710 0 : reflowNext = (aStatus & NS_FRAME_REFLOW_NEXTINFLOW) != 0;
711 :
712 : #ifdef DEBUG_roc
713 : printf("*** Reflowed child #%d %p: status = %d, desiredSize=%d,%d\n",
714 : columnCount, (void*)child, aStatus, kidDesiredSize.width, kidDesiredSize.height);
715 : #endif
716 :
717 0 : NS_FRAME_TRACE_REFLOW_OUT("Column::Reflow", aStatus);
718 :
719 0 : *aBottomMarginCarriedOut = kidDesiredSize.mCarriedOutBottomMargin;
720 :
721 : FinishReflowChild(child, PresContext(), &kidReflowState,
722 0 : kidDesiredSize, childOrigin.x, childOrigin.y, 0);
723 :
724 0 : childContentBottom = nsLayoutUtils::CalculateContentBottom(child);
725 0 : if (childContentBottom > aConfig.mColMaxHeight) {
726 0 : allFit = false;
727 : }
728 0 : if (childContentBottom > availSize.height) {
729 : aColData.mMaxOverflowingHeight = NS_MAX(childContentBottom,
730 0 : aColData.mMaxOverflowingHeight);
731 : }
732 : }
733 :
734 0 : contentRect.UnionRect(contentRect, child->GetRect());
735 :
736 0 : ConsiderChildOverflow(overflowRects, child);
737 0 : contentBottom = NS_MAX(contentBottom, childContentBottom);
738 0 : aColData.mLastHeight = childContentBottom;
739 0 : aColData.mSumHeight += childContentBottom;
740 :
741 : // Build a continuation column if necessary
742 0 : nsIFrame* kidNextInFlow = child->GetNextInFlow();
743 :
744 0 : if (NS_FRAME_IS_FULLY_COMPLETE(aStatus) && !NS_FRAME_IS_TRUNCATED(aStatus)) {
745 0 : NS_ASSERTION(!kidNextInFlow, "next in flow should have been deleted");
746 0 : child = nsnull;
747 0 : break;
748 : } else {
749 0 : ++columnCount;
750 : // Make sure that the column has a next-in-flow. If not, we must
751 : // create one to hold the overflowing stuff, even if we're just
752 : // going to put it on our overflow list and let *our*
753 : // next in flow handle it.
754 0 : if (!kidNextInFlow) {
755 0 : NS_ASSERTION(aStatus & NS_FRAME_REFLOW_NEXTINFLOW,
756 : "We have to create a continuation, but the block doesn't want us to reflow it?");
757 :
758 : // We need to create a continuing column
759 0 : nsresult rv = CreateNextInFlow(PresContext(), child, kidNextInFlow);
760 :
761 0 : if (NS_FAILED(rv)) {
762 0 : NS_NOTREACHED("Couldn't create continuation");
763 0 : child = nsnull;
764 0 : break;
765 : }
766 : }
767 :
768 : // Make sure we reflow a next-in-flow when it switches between being
769 : // normal or overflow container
770 0 : if (NS_FRAME_OVERFLOW_IS_INCOMPLETE(aStatus)) {
771 0 : if (!(kidNextInFlow->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)) {
772 0 : aStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
773 0 : reflowNext = true;
774 0 : kidNextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
775 : }
776 : }
777 0 : else if (kidNextInFlow->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) {
778 0 : aStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
779 0 : reflowNext = true;
780 0 : kidNextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
781 : }
782 :
783 0 : if (columnCount >= aConfig.mBalanceColCount) {
784 : // No more columns allowed here. Stop.
785 0 : aStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
786 0 : kidNextInFlow->AddStateBits(NS_FRAME_IS_DIRTY);
787 :
788 : // Move any of our leftover columns to our overflow list. Our
789 : // next-in-flow will eventually pick them up.
790 0 : const nsFrameList& continuationColumns = mFrames.RemoveFramesAfter(child);
791 0 : if (continuationColumns.NotEmpty()) {
792 0 : SetOverflowFrames(PresContext(), continuationColumns);
793 : }
794 0 : child = nsnull;
795 : break;
796 : }
797 : }
798 :
799 0 : if (PresContext()->HasPendingInterrupt()) {
800 : // Stop the loop now while |child| still points to the frame that bailed
801 : // out. We could keep going here and condition a bunch of the code in
802 : // this loop on whether there's an interrupt, or even just keep going and
803 : // trying to reflow the blocks (even though we know they'll interrupt
804 : // right after their first line), but stopping now is conceptually the
805 : // simplest (and probably fastest) thing.
806 0 : break;
807 : }
808 :
809 : // Advance to the next column
810 0 : child = child->GetNextSibling();
811 :
812 0 : if (child) {
813 0 : if (!RTL) {
814 0 : childOrigin.x += aConfig.mColWidth + aConfig.mColGap;
815 : } else {
816 0 : childOrigin.x -= aConfig.mColWidth + aConfig.mColGap;
817 : }
818 :
819 : #ifdef DEBUG_roc
820 : printf("*** NEXT CHILD ORIGIN.x = %d\n", childOrigin.x);
821 : #endif
822 : }
823 : }
824 :
825 0 : if (PresContext()->CheckForInterrupt(this) &&
826 0 : (GetStateBits() & NS_FRAME_IS_DIRTY)) {
827 : // Mark all our kids starting with |child| dirty
828 :
829 : // Note that this is a CheckForInterrupt call, not a HasPendingInterrupt,
830 : // because we might have interrupted while reflowing |child|, and since
831 : // we're about to add a dirty bit to |child| we need to make sure that
832 : // |this| is scheduled to have dirty bits marked on it and its ancestors.
833 : // Otherwise, when we go to mark dirty bits on |child|'s ancestors we'll
834 : // bail out immediately, since it'll already have a dirty bit.
835 0 : for (; child; child = child->GetNextSibling()) {
836 0 : child->AddStateBits(NS_FRAME_IS_DIRTY);
837 : }
838 : }
839 :
840 : // If we're doing RTL, we need to make sure our last column is at the left-hand side of the frame.
841 0 : if (RTL && childOrigin.x != targetX) {
842 0 : overflowRects.Clear();
843 0 : contentRect = nsRect(0, 0, 0, 0);
844 0 : PRInt32 deltaX = targetX - childOrigin.x;
845 : #ifdef DEBUG_roc
846 : printf("*** CHILDORIGIN.x = %d, targetX = %d, DELTAX = %d\n", childOrigin.x, targetX, deltaX);
847 : #endif
848 0 : for (child = mFrames.FirstChild(); child; child = child->GetNextSibling()) {
849 0 : MoveChildTo(this, child, child->GetPosition() + nsPoint(deltaX, 0));
850 0 : ConsiderChildOverflow(overflowRects, child);
851 0 : contentRect.UnionRect(contentRect, child->GetRect());
852 : }
853 : }
854 0 : aColData.mMaxHeight = contentBottom;
855 0 : contentRect.height = NS_MAX(contentRect.height, contentBottom);
856 0 : mLastFrameStatus = aStatus;
857 :
858 : // contentRect included the borderPadding.left,borderPadding.top of the child rects
859 0 : contentRect -= nsPoint(borderPadding.left, borderPadding.top);
860 :
861 0 : nsSize contentSize = nsSize(contentRect.XMost(), contentRect.YMost());
862 :
863 : // Apply computed and min/max values
864 0 : if (aReflowState.ComputedHeight() != NS_INTRINSICSIZE) {
865 0 : contentSize.height = aReflowState.ComputedHeight();
866 : } else {
867 0 : if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMaxHeight) {
868 0 : contentSize.height = NS_MIN(aReflowState.mComputedMaxHeight, contentSize.height);
869 : }
870 0 : if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMinHeight) {
871 0 : contentSize.height = NS_MAX(aReflowState.mComputedMinHeight, contentSize.height);
872 : }
873 : }
874 0 : if (aReflowState.ComputedWidth() != NS_INTRINSICSIZE) {
875 0 : contentSize.width = aReflowState.ComputedWidth();
876 : } else {
877 0 : if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMaxWidth) {
878 0 : contentSize.width = NS_MIN(aReflowState.mComputedMaxWidth, contentSize.width);
879 : }
880 0 : if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMinWidth) {
881 0 : contentSize.width = NS_MAX(aReflowState.mComputedMinWidth, contentSize.width);
882 : }
883 : }
884 :
885 : aDesiredSize.height = borderPadding.top + contentSize.height +
886 0 : borderPadding.bottom;
887 0 : aDesiredSize.width = contentSize.width + borderPadding.left + borderPadding.right;
888 0 : aDesiredSize.mOverflowAreas = overflowRects;
889 0 : aDesiredSize.UnionOverflowAreasWithDesiredBounds();
890 :
891 : #ifdef DEBUG_roc
892 : printf("*** DONE PASS feasible=%d\n", allFit && NS_FRAME_IS_FULLY_COMPLETE(aStatus)
893 : && !NS_FRAME_IS_TRUNCATED(aStatus));
894 : #endif
895 0 : return allFit && NS_FRAME_IS_FULLY_COMPLETE(aStatus)
896 0 : && !NS_FRAME_IS_TRUNCATED(aStatus);
897 : }
898 :
899 : void
900 0 : nsColumnSetFrame::DrainOverflowColumns()
901 : {
902 : // First grab the prev-in-flows overflows and reparent them to this
903 : // frame.
904 0 : nsColumnSetFrame* prev = static_cast<nsColumnSetFrame*>(GetPrevInFlow());
905 0 : if (prev) {
906 0 : nsAutoPtr<nsFrameList> overflows(prev->StealOverflowFrames());
907 0 : if (overflows) {
908 0 : nsContainerFrame::ReparentFrameViewList(PresContext(), *overflows,
909 0 : prev, this);
910 :
911 0 : mFrames.InsertFrames(this, nsnull, *overflows);
912 : }
913 : }
914 :
915 : // Now pull back our own overflows and append them to our children.
916 : // We don't need to reparent them since we're already their parent.
917 0 : nsAutoPtr<nsFrameList> overflows(StealOverflowFrames());
918 0 : if (overflows) {
919 : // We're already the parent for these frames, so no need to set
920 : // their parent again.
921 0 : mFrames.AppendFrames(nsnull, *overflows);
922 : }
923 0 : }
924 :
925 : NS_IMETHODIMP
926 0 : nsColumnSetFrame::Reflow(nsPresContext* aPresContext,
927 : nsHTMLReflowMetrics& aDesiredSize,
928 : const nsHTMLReflowState& aReflowState,
929 : nsReflowStatus& aStatus)
930 : {
931 : // Don't support interruption in columns
932 0 : nsPresContext::InterruptPreventer noInterrupts(aPresContext);
933 :
934 0 : DO_GLOBAL_REFLOW_COUNT("nsColumnSetFrame");
935 0 : DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
936 :
937 : // Initialize OUT parameter
938 0 : aStatus = NS_FRAME_COMPLETE;
939 :
940 : // Our children depend on our height if we have a fixed height.
941 0 : if (aReflowState.ComputedHeight() != NS_AUTOHEIGHT) {
942 0 : NS_ASSERTION(aReflowState.ComputedHeight() != NS_INTRINSICSIZE,
943 : "Unexpected mComputedHeight");
944 0 : AddStateBits(NS_FRAME_CONTAINS_RELATIVE_HEIGHT);
945 : }
946 : else {
947 0 : RemoveStateBits(NS_FRAME_CONTAINS_RELATIVE_HEIGHT);
948 : }
949 :
950 : //------------ Handle Incremental Reflow -----------------
951 :
952 0 : ReflowConfig config = ChooseColumnStrategy(aReflowState);
953 0 : bool isBalancing = config.mBalanceColCount < PR_INT32_MAX;
954 :
955 : // If balancing, then we allow the last column to grow to unbounded
956 : // height during the first reflow. This gives us a way to estimate
957 : // what the average column height should be, because we can measure
958 : // the heights of all the columns and sum them up. But don't do this
959 : // if we have a next in flow because we don't want to suck all its
960 : // content back here and then have to push it out again!
961 0 : nsIFrame* nextInFlow = GetNextInFlow();
962 0 : bool unboundedLastColumn = isBalancing && !nextInFlow;
963 0 : nsCollapsingMargin carriedOutBottomMargin;
964 : ColumnBalanceData colData;
965 : bool feasible = ReflowChildren(aDesiredSize, aReflowState,
966 0 : aStatus, config, unboundedLastColumn, &carriedOutBottomMargin, colData);
967 :
968 0 : if (isBalancing && !aPresContext->HasPendingInterrupt()) {
969 0 : nscoord availableContentHeight = GetAvailableContentHeight(aReflowState);
970 :
971 : // Termination of the algorithm below is guaranteed because
972 : // knownFeasibleHeight - knownInfeasibleHeight decreases in every
973 : // iteration.
974 0 : nscoord knownFeasibleHeight = NS_INTRINSICSIZE;
975 0 : nscoord knownInfeasibleHeight = 0;
976 : // We set this flag when we detect that we may contain a frame
977 : // that can break anywhere (thus foiling the linear decrease-by-one
978 : // search)
979 0 : bool maybeContinuousBreakingDetected = false;
980 :
981 0 : while (!aPresContext->HasPendingInterrupt()) {
982 0 : nscoord lastKnownFeasibleHeight = knownFeasibleHeight;
983 :
984 : // Record what we learned from the last reflow
985 0 : if (feasible) {
986 : // maxHeight is feasible. Also, mLastBalanceHeight is feasible.
987 0 : knownFeasibleHeight = NS_MIN(knownFeasibleHeight, colData.mMaxHeight);
988 0 : knownFeasibleHeight = NS_MIN(knownFeasibleHeight, mLastBalanceHeight);
989 :
990 : // Furthermore, no height less than the height of the last
991 : // column can ever be feasible. (We might be able to reduce the
992 : // height of a non-last column by moving content to a later column,
993 : // but we can't do that with the last column.)
994 0 : if (mFrames.GetLength() == config.mBalanceColCount) {
995 : knownInfeasibleHeight = NS_MAX(knownInfeasibleHeight,
996 0 : colData.mLastHeight - 1);
997 : }
998 : } else {
999 0 : knownInfeasibleHeight = NS_MAX(knownInfeasibleHeight, mLastBalanceHeight);
1000 : // If a column didn't fit in its available height, then its current
1001 : // height must be the minimum height for unbreakable content in
1002 : // the column, and therefore no smaller height can be feasible.
1003 : knownInfeasibleHeight = NS_MAX(knownInfeasibleHeight,
1004 0 : colData.mMaxOverflowingHeight - 1);
1005 :
1006 0 : if (unboundedLastColumn) {
1007 : // The last column is unbounded, so all content got reflowed, so the
1008 : // mColMaxHeight is feasible.
1009 : knownFeasibleHeight = NS_MIN(knownFeasibleHeight,
1010 0 : colData.mMaxHeight);
1011 : }
1012 : }
1013 :
1014 : #ifdef DEBUG_roc
1015 : printf("*** nsColumnSetFrame::Reflow balancing knownInfeasible=%d knownFeasible=%d\n",
1016 : knownInfeasibleHeight, knownFeasibleHeight);
1017 : #endif
1018 :
1019 0 : if (knownInfeasibleHeight >= knownFeasibleHeight - 1) {
1020 : // knownFeasibleHeight is where we want to be
1021 0 : break;
1022 : }
1023 :
1024 0 : if (knownInfeasibleHeight >= availableContentHeight) {
1025 0 : break;
1026 : }
1027 :
1028 0 : if (lastKnownFeasibleHeight - knownFeasibleHeight == 1) {
1029 : // We decreased the feasible height by one twip only. This could
1030 : // indicate that there is a continuously breakable child frame
1031 : // that we are crawling through.
1032 0 : maybeContinuousBreakingDetected = true;
1033 : }
1034 :
1035 0 : nscoord nextGuess = (knownFeasibleHeight + knownInfeasibleHeight)/2;
1036 : // The constant of 600 twips is arbitrary. It's about two line-heights.
1037 0 : if (knownFeasibleHeight - nextGuess < 600 &&
1038 0 : !maybeContinuousBreakingDetected) {
1039 : // We're close to our target, so just try shrinking just the
1040 : // minimum amount that will cause one of our columns to break
1041 : // differently.
1042 0 : nextGuess = knownFeasibleHeight - 1;
1043 0 : } else if (unboundedLastColumn) {
1044 : // Make a guess by dividing that into N columns. Add some slop
1045 : // to try to make it on the feasible side. The constant of
1046 : // 600 twips is arbitrary. It's about two line-heights.
1047 0 : nextGuess = colData.mSumHeight/config.mBalanceColCount + 600;
1048 : // Sanitize it
1049 : nextGuess = clamped(nextGuess, knownInfeasibleHeight + 1,
1050 0 : knownFeasibleHeight - 1);
1051 0 : } else if (knownFeasibleHeight == NS_INTRINSICSIZE) {
1052 : // This can happen when we had a next-in-flow so we didn't
1053 : // want to do an unbounded height measuring step. Let's just increase
1054 : // from the infeasible height by some reasonable amount.
1055 0 : nextGuess = knownInfeasibleHeight*2 + 600;
1056 : }
1057 : // Don't bother guessing more than our height constraint.
1058 0 : nextGuess = NS_MIN(availableContentHeight, nextGuess);
1059 :
1060 : #ifdef DEBUG_roc
1061 : printf("*** nsColumnSetFrame::Reflow balancing choosing next guess=%d\n", nextGuess);
1062 : #endif
1063 :
1064 0 : config.mColMaxHeight = nextGuess;
1065 :
1066 0 : unboundedLastColumn = false;
1067 0 : AddStateBits(NS_FRAME_IS_DIRTY);
1068 : feasible = ReflowChildren(aDesiredSize, aReflowState,
1069 : aStatus, config, false,
1070 0 : &carriedOutBottomMargin, colData);
1071 : }
1072 :
1073 0 : if (!feasible && !aPresContext->HasPendingInterrupt()) {
1074 : // We may need to reflow one more time at the feasible height to
1075 : // get a valid layout.
1076 0 : bool skip = false;
1077 0 : if (knownInfeasibleHeight >= availableContentHeight) {
1078 0 : config.mColMaxHeight = availableContentHeight;
1079 0 : if (mLastBalanceHeight == availableContentHeight) {
1080 0 : skip = true;
1081 : }
1082 : } else {
1083 0 : config.mColMaxHeight = knownFeasibleHeight;
1084 : }
1085 0 : if (!skip) {
1086 : // If our height is unconstrained, make sure that the last column is
1087 : // allowed to have arbitrary height here, even though we were balancing.
1088 : // Otherwise we'd have to split, and it's not clear what we'd do with
1089 : // that.
1090 0 : AddStateBits(NS_FRAME_IS_DIRTY);
1091 : ReflowChildren(aDesiredSize, aReflowState, aStatus, config,
1092 : availableContentHeight == NS_UNCONSTRAINEDSIZE,
1093 0 : &carriedOutBottomMargin, colData);
1094 : }
1095 : }
1096 : }
1097 :
1098 0 : if (aPresContext->HasPendingInterrupt() &&
1099 : aReflowState.availableHeight == NS_UNCONSTRAINEDSIZE) {
1100 : // In this situation, we might be lying about our reflow status, because
1101 : // our last kid (the one that got interrupted) was incomplete. Fix that.
1102 0 : aStatus = NS_FRAME_COMPLETE;
1103 : }
1104 :
1105 0 : CheckInvalidateSizeChange(aDesiredSize);
1106 :
1107 0 : FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowState, aStatus, false);
1108 :
1109 0 : aDesiredSize.mCarriedOutBottomMargin = carriedOutBottomMargin;
1110 :
1111 0 : NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
1112 :
1113 0 : NS_ASSERTION(NS_FRAME_IS_FULLY_COMPLETE(aStatus) ||
1114 : aReflowState.availableHeight != NS_UNCONSTRAINEDSIZE,
1115 : "Column set should be complete if the available height is unconstrained");
1116 :
1117 0 : return NS_OK;
1118 : }
1119 :
1120 : NS_IMETHODIMP
1121 0 : nsColumnSetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1122 : const nsRect& aDirtyRect,
1123 : const nsDisplayListSet& aLists) {
1124 0 : nsresult rv = DisplayBorderBackgroundOutline(aBuilder, aLists);
1125 0 : NS_ENSURE_SUCCESS(rv, rv);
1126 :
1127 : aLists.BorderBackground()->AppendNewToTop(new (aBuilder)
1128 : nsDisplayGeneric(aBuilder, this, ::PaintColumnRule, "ColumnRule",
1129 0 : nsDisplayItem::TYPE_COLUMN_RULE));
1130 :
1131 0 : nsIFrame* kid = mFrames.FirstChild();
1132 : // Our children won't have backgrounds so it doesn't matter where we put them.
1133 0 : while (kid) {
1134 0 : nsresult rv = BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists);
1135 0 : NS_ENSURE_SUCCESS(rv, rv);
1136 0 : kid = kid->GetNextSibling();
1137 : }
1138 0 : return NS_OK;
1139 : }
1140 :
1141 : PRIntn
1142 0 : nsColumnSetFrame::GetSkipSides() const
1143 : {
1144 0 : return 0;
1145 : }
1146 :
1147 : NS_IMETHODIMP
1148 0 : nsColumnSetFrame::AppendFrames(ChildListID aListID,
1149 : nsFrameList& aFrameList)
1150 : {
1151 0 : if (aListID == kAbsoluteList) {
1152 0 : return nsContainerFrame::AppendFrames(aListID, aFrameList);
1153 : }
1154 :
1155 0 : NS_ERROR("unexpected child list");
1156 0 : return NS_ERROR_INVALID_ARG;
1157 : }
1158 :
1159 : NS_IMETHODIMP
1160 0 : nsColumnSetFrame::InsertFrames(ChildListID aListID,
1161 : nsIFrame* aPrevFrame,
1162 : nsFrameList& aFrameList)
1163 : {
1164 0 : if (aListID == kAbsoluteList) {
1165 0 : return nsContainerFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
1166 : }
1167 :
1168 0 : NS_ERROR("unexpected child list");
1169 0 : return NS_ERROR_INVALID_ARG;
1170 : }
1171 :
1172 : NS_IMETHODIMP
1173 0 : nsColumnSetFrame::RemoveFrame(ChildListID aListID,
1174 : nsIFrame* aOldFrame)
1175 : {
1176 0 : if (aListID == kAbsoluteList) {
1177 0 : return nsContainerFrame::RemoveFrame(aListID, aOldFrame);
1178 : }
1179 :
1180 0 : NS_ERROR("unexpected child list");
1181 0 : return NS_ERROR_INVALID_ARG;
1182 : }
|