layout/generic/nsColumnSetFrame.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial