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