nsColumnSetFrame.cpp (57409B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* rendering object for css3 multi-column layout */ 8 9 #include "nsColumnSetFrame.h" 10 11 #include "mozilla/ColumnUtils.h" 12 #include "mozilla/Logging.h" 13 #include "mozilla/PresShell.h" 14 #include "mozilla/StaticPrefs_layout.h" 15 #include "mozilla/ToString.h" 16 #include "nsCSSRendering.h" 17 #include "nsDisplayList.h" 18 #include "nsIFrameInlines.h" 19 #include "nsLayoutUtils.h" 20 21 using namespace mozilla; 22 using namespace mozilla::layout; 23 24 // To see this log, use $ MOZ_LOG=ColumnSet:4 ./mach run 25 static LazyLogModule sColumnSetLog("ColumnSet"); 26 #define COLUMN_SET_LOG(msg, ...) \ 27 MOZ_LOG(sColumnSetLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) 28 29 class nsDisplayColumnRule final : public nsPaintedDisplayItem { 30 public: 31 nsDisplayColumnRule(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) 32 : nsPaintedDisplayItem(aBuilder, aFrame) { 33 MOZ_COUNT_CTOR(nsDisplayColumnRule); 34 } 35 36 MOZ_COUNTED_DTOR_FINAL(nsDisplayColumnRule) 37 38 nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override { 39 *aSnap = false; 40 // We just return the frame's ink-overflow rect, which is guaranteed to 41 // contain all the column-rule areas. It's not worth calculating the exact 42 // union of those areas since it would only lead to performance improvements 43 // during painting in rare edge cases. 44 return mFrame->InkOverflowRect() + ToReferenceFrame(); 45 } 46 47 bool CreateWebRenderCommands( 48 mozilla::wr::DisplayListBuilder& aBuilder, 49 mozilla::wr::IpcResourceUpdateQueue& aResources, 50 const StackingContextHelper& aSc, 51 mozilla::layers::RenderRootStateManager* aManager, 52 nsDisplayListBuilder* aDisplayListBuilder) override; 53 void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; 54 55 NS_DISPLAY_DECL_NAME("ColumnRule", TYPE_COLUMN_RULE); 56 57 private: 58 nsTArray<nsCSSBorderRenderer> mBorderRenderers; 59 }; 60 61 void nsDisplayColumnRule::Paint(nsDisplayListBuilder* aBuilder, 62 gfxContext* aCtx) { 63 static_cast<nsColumnSetFrame*>(mFrame)->CreateBorderRenderers( 64 mBorderRenderers, aCtx, GetPaintRect(aBuilder, aCtx), ToReferenceFrame()); 65 66 for (auto iter = mBorderRenderers.begin(); iter != mBorderRenderers.end(); 67 iter++) { 68 iter->DrawBorders(); 69 } 70 } 71 72 bool nsDisplayColumnRule::CreateWebRenderCommands( 73 mozilla::wr::DisplayListBuilder& aBuilder, 74 mozilla::wr::IpcResourceUpdateQueue& aResources, 75 const StackingContextHelper& aSc, 76 mozilla::layers::RenderRootStateManager* aManager, 77 nsDisplayListBuilder* aDisplayListBuilder) { 78 RefPtr dt = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); 79 if (!dt || !dt->IsValid()) { 80 return false; 81 } 82 gfxContext screenRefCtx(dt); 83 84 bool dummy; 85 static_cast<nsColumnSetFrame*>(mFrame)->CreateBorderRenderers( 86 mBorderRenderers, &screenRefCtx, GetBounds(aDisplayListBuilder, &dummy), 87 ToReferenceFrame()); 88 89 if (mBorderRenderers.IsEmpty()) { 90 return true; 91 } 92 93 for (auto& renderer : mBorderRenderers) { 94 renderer.CreateWebRenderCommands(this, aBuilder, aResources, aSc); 95 } 96 97 return true; 98 } 99 100 // The maximum number of columns we support. 101 static constexpr int32_t kMaxColumnCount = 1000; 102 103 /** 104 * Tracking issues: 105 * 106 * XXX cursor movement around the top and bottom of colums seems to make the 107 * editor lose the caret. 108 * 109 * XXX should we support CSS columns applied to table elements? 110 */ 111 nsContainerFrame* NS_NewColumnSetFrame(PresShell* aPresShell, 112 ComputedStyle* aStyle, 113 nsFrameState aStateFlags) { 114 nsColumnSetFrame* it = 115 new (aPresShell) nsColumnSetFrame(aStyle, aPresShell->GetPresContext()); 116 it->AddStateBits(aStateFlags); 117 return it; 118 } 119 120 NS_IMPL_FRAMEARENA_HELPERS(nsColumnSetFrame) 121 122 nsColumnSetFrame::nsColumnSetFrame(ComputedStyle* aStyle, 123 nsPresContext* aPresContext) 124 : nsContainerFrame(aStyle, aPresContext, kClassID), 125 mLastBalanceBSize(NS_UNCONSTRAINEDSIZE) {} 126 127 void nsColumnSetFrame::ForEachColumnRule( 128 const std::function<void(const nsRect& lineRect)>& aSetLineRect, 129 const nsPoint& aPt) const { 130 nsIFrame* child = mFrames.FirstChild(); 131 if (!child) { 132 return; // no columns 133 } 134 135 nsIFrame* nextSibling = child->GetNextSibling(); 136 if (!nextSibling) { 137 return; // 1 column only - this means no gap to draw on 138 } 139 140 const nsStyleColumn* colStyle = StyleColumn(); 141 nscoord ruleWidth = colStyle->GetColumnRuleWidth(); 142 if (!ruleWidth) { 143 return; 144 } 145 146 WritingMode wm = GetWritingMode(); 147 bool isVertical = wm.IsVertical(); 148 bool isRTL = wm.IsBidiRTL(); 149 150 nsRect contentRect = GetContentRectRelativeToSelf() + aPt; 151 nsSize ruleSize = isVertical ? nsSize(contentRect.width, ruleWidth) 152 : nsSize(ruleWidth, contentRect.height); 153 154 while (nextSibling) { 155 // The frame tree goes RTL in RTL. 156 // The |prevFrame| and |nextFrame| frames here are the visually preceding 157 // (left/above) and following (right/below) frames, not in logical writing- 158 // mode direction. 159 nsIFrame* prevFrame = isRTL ? nextSibling : child; 160 nsIFrame* nextFrame = isRTL ? child : nextSibling; 161 162 // Each child frame's position coordinates is actually relative to this 163 // nsColumnSetFrame. 164 // linePt will be at the top-left edge to paint the line. 165 nsPoint linePt; 166 if (isVertical) { 167 nscoord edgeOfPrev = prevFrame->GetRect().YMost() + aPt.y; 168 nscoord edgeOfNext = nextFrame->GetRect().Y() + aPt.y; 169 linePt = nsPoint(contentRect.x, 170 (edgeOfPrev + edgeOfNext - ruleSize.height) / 2); 171 } else { 172 nscoord edgeOfPrev = prevFrame->GetRect().XMost() + aPt.x; 173 nscoord edgeOfNext = nextFrame->GetRect().X() + aPt.x; 174 linePt = nsPoint((edgeOfPrev + edgeOfNext - ruleSize.width) / 2, 175 contentRect.y); 176 } 177 178 aSetLineRect(nsRect(linePt, ruleSize)); 179 180 child = nextSibling; 181 nextSibling = nextSibling->GetNextSibling(); 182 } 183 } 184 185 void nsColumnSetFrame::CreateBorderRenderers( 186 nsTArray<nsCSSBorderRenderer>& aBorderRenderers, gfxContext* aCtx, 187 const nsRect& aDirtyRect, const nsPoint& aPt) { 188 WritingMode wm = GetWritingMode(); 189 bool isVertical = wm.IsVertical(); 190 const nsStyleColumn* colStyle = StyleColumn(); 191 StyleBorderStyle ruleStyle; 192 193 // Per spec, inset => ridge and outset => groove 194 if (colStyle->mColumnRuleStyle == StyleBorderStyle::Inset) { 195 ruleStyle = StyleBorderStyle::Ridge; 196 } else if (colStyle->mColumnRuleStyle == StyleBorderStyle::Outset) { 197 ruleStyle = StyleBorderStyle::Groove; 198 } else { 199 ruleStyle = colStyle->mColumnRuleStyle; 200 } 201 202 nscoord ruleWidth = colStyle->GetColumnRuleWidth(); 203 if (!ruleWidth) { 204 return; 205 } 206 207 aBorderRenderers.Clear(); 208 nscolor ruleColor = 209 GetVisitedDependentColor(&nsStyleColumn::mColumnRuleColor); 210 211 nsPresContext* pc = PresContext(); 212 // In order to re-use a large amount of code, we treat the column rule as a 213 // border. We create a new border style object and fill in all the details of 214 // the column rule as the left border. PaintBorder() does all the rendering 215 // for us, so we not only save an enormous amount of code but we'll support 216 // all the line styles that we support on borders! 217 nsStyleBorder border; 218 Sides skipSides; 219 if (isVertical) { 220 border.SetBorderWidth(eSideTop, ruleWidth, pc->AppUnitsPerDevPixel()); 221 border.SetBorderStyle(eSideTop, ruleStyle); 222 border.mBorderTopColor = StyleColor::FromColor(ruleColor); 223 skipSides |= mozilla::SideBits::eLeftRight; 224 skipSides |= mozilla::SideBits::eBottom; 225 } else { 226 border.SetBorderWidth(eSideLeft, ruleWidth, pc->AppUnitsPerDevPixel()); 227 border.SetBorderStyle(eSideLeft, ruleStyle); 228 border.mBorderLeftColor = StyleColor::FromColor(ruleColor); 229 skipSides |= mozilla::SideBits::eTopBottom; 230 skipSides |= mozilla::SideBits::eRight; 231 } 232 // If we use box-decoration-break: slice (the default), the border 233 // renderers will require clipping if we have continuations (see the 234 // aNeedsClip parameter to ConstructBorderRenderer in nsCSSRendering). 235 // 236 // Since it doesn't matter which box-decoration-break we use since 237 // we're only drawing borders (and not border-images), use 'clone'. 238 border.mBoxDecorationBreak = StyleBoxDecorationBreak::Clone; 239 240 ForEachColumnRule( 241 [&](const nsRect& aLineRect) { 242 // Assert that we're not drawing a border-image here; if we were, we 243 // couldn't ignore the ImgDrawResult that PaintBorderWithStyleBorder 244 // returns. 245 MOZ_ASSERT(border.mBorderImageSource.IsNone()); 246 247 gfx::DrawTarget* dt = aCtx ? aCtx->GetDrawTarget() : nullptr; 248 bool borderIsEmpty = false; 249 Maybe<nsCSSBorderRenderer> br = 250 nsCSSRendering::CreateBorderRendererWithStyleBorder( 251 pc, dt, this, aDirtyRect, aLineRect, border, Style(), 252 &borderIsEmpty, skipSides); 253 if (br.isSome()) { 254 MOZ_ASSERT(!borderIsEmpty); 255 aBorderRenderers.AppendElement(br.value()); 256 } 257 }, 258 aPt); 259 } 260 261 static uint32_t ColumnBalancingDepth(const ReflowInput& aReflowInput, 262 uint32_t aMaxDepth) { 263 uint32_t depth = 0; 264 for (const ReflowInput* ri = aReflowInput.mParentReflowInput; 265 ri && depth < aMaxDepth; ri = ri->mParentReflowInput) { 266 if (ri->mFlags.mIsColumnBalancing) { 267 ++depth; 268 } 269 } 270 return depth; 271 } 272 273 nsColumnSetFrame::ReflowConfig nsColumnSetFrame::ChooseColumnStrategy( 274 const ReflowInput& aReflowInput, bool aForceAuto = false) const { 275 const nsStyleColumn* colStyle = StyleColumn(); 276 nscoord availContentISize = aReflowInput.AvailableISize(); 277 if (aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE) { 278 availContentISize = aReflowInput.ComputedISize(); 279 } 280 281 nscoord colBSize = aReflowInput.AvailableBSize(); 282 nscoord colGap = 283 ColumnUtils::GetColumnGap(this, aReflowInput.ComputedISize()); 284 int32_t numColumns = 285 colStyle->mColumnCount.IsAuto() 286 ? 0 287 : std::min(colStyle->mColumnCount.AsInteger(), kMaxColumnCount); 288 289 // If column-fill is set to 'balance' or we have a column-span sibling, then 290 // we want to balance the columns. 291 bool isBalancing = (colStyle->mColumnFill == StyleColumnFill::Balance || 292 HasColumnSpanSiblings()) && 293 !aForceAuto; 294 if (isBalancing) { 295 const uint32_t kMaxNestedColumnBalancingDepth = 2; 296 const uint32_t balancingDepth = 297 ColumnBalancingDepth(aReflowInput, kMaxNestedColumnBalancingDepth); 298 if (balancingDepth == kMaxNestedColumnBalancingDepth) { 299 isBalancing = false; 300 numColumns = 1; 301 aForceAuto = true; 302 } 303 } 304 305 nscoord colISize; 306 // In vertical writing-mode, "column-width" (inline size) will actually be 307 // physical height, but its CSS name is still column-width. 308 if (colStyle->mColumnWidth.IsLength()) { 309 colISize = 310 ColumnUtils::ClampUsedColumnWidth(colStyle->mColumnWidth.AsLength()); 311 NS_ASSERTION(colISize >= 0, "negative column width"); 312 // Reduce column count if necessary to make columns fit in the 313 // available width. Compute max number of columns that fit in 314 // availContentISize, satisfying colGap*(maxColumns - 1) + 315 // colISize*maxColumns <= availContentISize 316 if (availContentISize != NS_UNCONSTRAINEDSIZE && colGap + colISize > 0 && 317 numColumns > 0) { 318 // This expression uses truncated rounding, which is what we 319 // want 320 int32_t maxColumns = 321 std::min(nscoord(kMaxColumnCount), 322 (availContentISize + colGap) / (colGap + colISize)); 323 numColumns = std::max(1, std::min(numColumns, maxColumns)); 324 } 325 } else if (numColumns > 0 && availContentISize != NS_UNCONSTRAINEDSIZE) { 326 nscoord iSizeMinusGaps = availContentISize - colGap * (numColumns - 1); 327 colISize = iSizeMinusGaps / numColumns; 328 } else { 329 colISize = NS_UNCONSTRAINEDSIZE; 330 } 331 // Take care of the situation where there's only one column but it's 332 // still too wide 333 colISize = std::max(1, std::min(colISize, availContentISize)); 334 335 nscoord expectedISizeLeftOver = 0; 336 337 if (colISize != NS_UNCONSTRAINEDSIZE && 338 availContentISize != NS_UNCONSTRAINEDSIZE) { 339 // distribute leftover space 340 341 // First, determine how many columns will be showing if the column 342 // count is auto 343 if (numColumns <= 0) { 344 // choose so that colGap*(nominalColumnCount - 1) + 345 // colISize*nominalColumnCount is nearly availContentISize 346 // make sure to round down 347 if (colGap + colISize > 0) { 348 numColumns = (availContentISize + colGap) / (colGap + colISize); 349 // The number of columns should never exceed kMaxColumnCount. 350 numColumns = std::min(kMaxColumnCount, numColumns); 351 } 352 if (numColumns <= 0) { 353 numColumns = 1; 354 } 355 } 356 357 // Compute extra space and divide it among the columns 358 nscoord extraSpace = 359 std::max(0, availContentISize - 360 (colISize * numColumns + colGap * (numColumns - 1))); 361 nscoord extraToColumns = extraSpace / numColumns; 362 colISize += extraToColumns; 363 expectedISizeLeftOver = extraSpace - (extraToColumns * numColumns); 364 } 365 366 if (isBalancing) { 367 if (numColumns <= 0) { 368 // Hmm, auto column count, column width or available width is unknown, 369 // and balancing is required. Let's just use one column then. 370 numColumns = 1; 371 } 372 colBSize = std::min(mLastBalanceBSize, colBSize); 373 } else { 374 // CSS Fragmentation spec says, "To guarantee progress, fragmentainers are 375 // assumed to have a minimum block size of 1px regardless of their used 376 // size." https://drafts.csswg.org/css-break/#breaking-rules 377 // 378 // Note: we don't enforce the minimum block-size during balancing because 379 // this affects the result. If a balancing column container or its 380 // next-in-flows has zero block-size, it eventually gives up balancing, and 381 // ends up here. 382 colBSize = std::max(colBSize, nsPresContext::CSSPixelsToAppUnits(1)); 383 } 384 385 ReflowConfig config; 386 config.mUsedColCount = numColumns; 387 config.mColISize = colISize; 388 config.mExpectedISizeLeftOver = expectedISizeLeftOver; 389 config.mColGap = colGap; 390 config.mColBSize = colBSize; 391 config.mIsBalancing = isBalancing; 392 config.mForceAuto = aForceAuto; 393 config.mKnownFeasibleBSize = NS_UNCONSTRAINEDSIZE; 394 config.mKnownInfeasibleBSize = 0; 395 396 COLUMN_SET_LOG( 397 "%s: this=%p, mUsedColCount=%d, mColISize=%d, " 398 "mExpectedISizeLeftOver=%d, mColGap=%d, mColBSize=%d, mIsBalancing=%d", 399 __func__, this, config.mUsedColCount, config.mColISize, 400 config.mExpectedISizeLeftOver, config.mColGap, config.mColBSize, 401 config.mIsBalancing); 402 403 return config; 404 } 405 406 static void MarkPrincipalChildrenDirty(nsIFrame* aFrame) { 407 for (nsIFrame* childFrame : aFrame->PrincipalChildList()) { 408 childFrame->MarkSubtreeDirty(); 409 } 410 } 411 412 static void MoveChildTo(nsIFrame* aChild, LogicalPoint aOrigin, WritingMode aWM, 413 const nsSize& aContainerSize) { 414 if (aChild->GetLogicalPosition(aWM, aContainerSize) == aOrigin) { 415 return; 416 } 417 418 aChild->SetPosition(aWM, aOrigin, aContainerSize); 419 } 420 421 nscoord nsColumnSetFrame::IntrinsicISize(const IntrinsicSizeInput& input, 422 IntrinsicISizeType aType) { 423 return aType == IntrinsicISizeType::MinISize ? MinISize(input) 424 : PrefISize(input); 425 } 426 427 nscoord nsColumnSetFrame::MinISize(const IntrinsicSizeInput& aInput) { 428 nscoord iSize = 0; 429 430 if (mFrames.FirstChild()) { 431 iSize = mFrames.FirstChild()->GetMinISize(aInput); 432 } 433 const nsStyleColumn* colStyle = StyleColumn(); 434 if (colStyle->mColumnWidth.IsLength()) { 435 nscoord colISize = 436 ColumnUtils::ClampUsedColumnWidth(colStyle->mColumnWidth.AsLength()); 437 // As available width reduces to zero, we reduce our number of columns 438 // to one, and don't enforce the column width, so just return the min 439 // of the child's min-width with any specified column width. 440 iSize = std::min(iSize, colISize); 441 } else { 442 NS_ASSERTION(!colStyle->mColumnCount.IsAuto(), 443 "column-count and column-width can't both be auto"); 444 // As available width reduces to zero, we still have mColumnCount columns, 445 // so compute our minimum size based on the number of columns and their gaps 446 // and minimum per-column size. 447 nscoord colGap = ColumnUtils::GetColumnGap(this, NS_UNCONSTRAINEDSIZE); 448 iSize = ColumnUtils::IntrinsicISize(colStyle->mColumnCount.AsInteger(), 449 colGap, iSize); 450 } 451 // XXX count forced column breaks here? Maybe we should return the child's 452 // min-width times the minimum number of columns. 453 return iSize; 454 } 455 456 nscoord nsColumnSetFrame::PrefISize(const IntrinsicSizeInput& aInput) { 457 // Our preferred width is our desired column width, if specified, otherwise 458 // the child's preferred width, times the number of columns, plus the width 459 // of any required column gaps 460 // XXX what about forced column breaks here? 461 const nsStyleColumn* colStyle = StyleColumn(); 462 463 nscoord colISize; 464 if (colStyle->mColumnWidth.IsLength()) { 465 colISize = 466 ColumnUtils::ClampUsedColumnWidth(colStyle->mColumnWidth.AsLength()); 467 } else if (mFrames.FirstChild()) { 468 colISize = mFrames.FirstChild()->GetPrefISize(aInput); 469 } else { 470 colISize = 0; 471 } 472 473 // If column-count is auto, assume one column. 474 uint32_t numColumns = 475 colStyle->mColumnCount.IsAuto() ? 1 : colStyle->mColumnCount.AsInteger(); 476 nscoord colGap = ColumnUtils::GetColumnGap(this, NS_UNCONSTRAINEDSIZE); 477 return ColumnUtils::IntrinsicISize(numColumns, colGap, colISize); 478 } 479 480 nsColumnSetFrame::ColumnBalanceData nsColumnSetFrame::ReflowColumns( 481 ReflowOutput& aDesiredSize, const ReflowInput& aReflowInput, 482 nsReflowStatus& aStatus, const ReflowConfig& aConfig, 483 bool aUnboundedLastColumn) { 484 ColumnBalanceData colData; 485 bool allFit = true; 486 WritingMode wm = GetWritingMode(); 487 const bool isRTL = wm.IsBidiRTL(); 488 const bool shrinkingBSize = mLastBalanceBSize > aConfig.mColBSize; 489 const bool changingBSize = mLastBalanceBSize != aConfig.mColBSize; 490 491 COLUMN_SET_LOG( 492 "%s: Doing column reflow pass: mLastBalanceBSize=%d," 493 " mColBSize=%d, RTL=%d, mUsedColCount=%d," 494 " mColISize=%d, mColGap=%d", 495 __func__, mLastBalanceBSize, aConfig.mColBSize, isRTL, 496 aConfig.mUsedColCount, aConfig.mColISize, aConfig.mColGap); 497 498 DrainOverflowColumns(); 499 500 if (changingBSize) { 501 mLastBalanceBSize = aConfig.mColBSize; 502 // XXX Seems like this could fire if incremental reflow pushed the column 503 // set down so we reflow incrementally with a different available height. 504 // We need a way to do an incremental reflow and be sure availableHeight 505 // changes are taken account of! Right now I think block frames with 506 // absolute children might exit early. 507 /* 508 NS_ASSERTION( 509 aKidReason != eReflowReason_Incremental, 510 "incremental reflow should not have changed the balance height"); 511 */ 512 } 513 514 nsRect contentRect(0, 0, 0, 0); 515 OverflowAreas overflowRects; 516 517 nsIFrame* child = mFrames.FirstChild(); 518 LogicalPoint childOrigin(wm, 0, 0); 519 520 // In vertical-rl mode, columns will not be correctly placed if the 521 // reflowInput's ComputedWidth() is UNCONSTRAINED (in which case we'll get 522 // a containerSize.width of zero here). In that case, the column positions 523 // will be adjusted later, after our correct contentSize is known. 524 // 525 // When column-span is enabled, containerSize.width is always constrained. 526 // However, for RTL, we need to adjust the column positions as well after our 527 // correct containerSize is known. 528 nsSize containerSize = aReflowInput.ComputedSizeAsContainerIfConstrained(); 529 530 const nscoord computedBSize = 531 aReflowInput.mParentReflowInput->ComputedBSize(); 532 nscoord contentBEnd = 0; 533 bool reflowNext = false; 534 535 while (child) { 536 const bool reflowLastColumnWithUnconstrainedAvailBSize = 537 aUnboundedLastColumn && colData.mColCount == aConfig.mUsedColCount && 538 aConfig.mIsBalancing; 539 540 // We need to reflow the child (column) ... 541 bool reflowChild = 542 // if we are told to do so; 543 aReflowInput.ShouldReflowAllKids() || 544 // if the child is dirty; 545 child->IsSubtreeDirty() || 546 // if it's the last child because we need to obtain the block-end 547 // margin; 548 !child->GetNextSibling() || 549 // if the next column is dirty, because the next column's first line(s) 550 // might be pullable back to this column; 551 child->GetNextSibling()->IsSubtreeDirty() || 552 // if this is the last column and we are supposed to assign unbounded 553 // block-size to it, because that could change the available block-size 554 // from the last time we reflowed it and we should try to pull all the 555 // content from its next sibling (Note that it might be the last column, 556 // but not be the last child because the desired number of columns has 557 // changed.) 558 reflowLastColumnWithUnconstrainedAvailBSize; 559 560 // If column-fill is auto (not the default), then we might need to 561 // move content between columns for any change in column block-size. 562 // 563 // The same is true if we have a non-'auto' computed block-size. 564 // 565 // FIXME: It's not clear to me why it's *ever* valid to have 566 // reflowChild be false when changingBSize is true, since it 567 // seems like a child broken over multiple columns might need to 568 // change the size of the fragment in each column. 569 if (!reflowChild && changingBSize && 570 (StyleColumn()->mColumnFill == StyleColumnFill::Auto || 571 computedBSize != NS_UNCONSTRAINEDSIZE)) { 572 reflowChild = true; 573 } 574 // If we need to pull up content from the prev-in-flow then this is not just 575 // a block-size shrink. The prev in flow will have set the dirty bit. 576 // Check the overflow rect YMost instead of just the child's content 577 // block-size. The child may have overflowing content that cares about the 578 // available block-size boundary. (It may also have overflowing content that 579 // doesn't care about the available block-size boundary, but if so, too bad, 580 // this optimization is defeated.) We want scrollable overflow here since 581 // this is a calculation that affects layout. 582 if (!reflowChild && shrinkingBSize) { 583 switch (wm.GetBlockDir()) { 584 case WritingMode::BlockDir::TB: 585 if (child->ScrollableOverflowRect().YMost() > aConfig.mColBSize) { 586 reflowChild = true; 587 } 588 break; 589 case WritingMode::BlockDir::LR: 590 if (child->ScrollableOverflowRect().XMost() > aConfig.mColBSize) { 591 reflowChild = true; 592 } 593 break; 594 case WritingMode::BlockDir::RL: 595 // XXX not sure how to handle this, so for now just don't attempt 596 // the optimization 597 reflowChild = true; 598 break; 599 default: 600 MOZ_ASSERT_UNREACHABLE("unknown block direction"); 601 break; 602 } 603 } 604 605 nscoord childContentBEnd = 0; 606 if (!reflowNext && !reflowChild) { 607 // This child does not need to be reflowed, but we may need to move it 608 MoveChildTo(child, childOrigin, wm, containerSize); 609 610 // If this is the last frame then make sure we get the right status 611 nsIFrame* kidNext = child->GetNextSibling(); 612 if (kidNext) { 613 aStatus.Reset(); 614 if (kidNext->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { 615 aStatus.SetOverflowIncomplete(); 616 } else { 617 aStatus.SetIncomplete(); 618 } 619 } else { 620 aStatus = mLastFrameStatus; 621 } 622 childContentBEnd = nsLayoutUtils::CalculateContentBEnd(wm, child); 623 624 COLUMN_SET_LOG("%s: Skipping child #%d %p: status=%s", __func__, 625 colData.mColCount, child, ToString(aStatus).c_str()); 626 } else { 627 LogicalSize availSize(wm, aConfig.mColISize, aConfig.mColBSize); 628 if (reflowLastColumnWithUnconstrainedAvailBSize) { 629 availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; 630 631 COLUMN_SET_LOG( 632 "%s: Reflowing last column with unconstrained block-size. Change " 633 "available block-size from %d to %d", 634 __func__, aConfig.mColBSize, availSize.BSize(wm)); 635 } 636 637 if (reflowNext) { 638 child->MarkSubtreeDirty(); 639 } 640 641 LogicalSize kidCBSize(wm, availSize.ISize(wm), computedBSize); 642 ReflowInput kidReflowInput(PresContext(), aReflowInput, child, availSize, 643 Some(kidCBSize)); 644 kidReflowInput.mFlags.mIsTopOfPage = [&]() { 645 const bool isNestedMulticolOrInRootPaginatedDoc = 646 aReflowInput.mParentReflowInput->mFrame->HasAnyStateBits( 647 NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) || 648 PresContext()->IsRootPaginatedDocument(); 649 if (isNestedMulticolOrInRootPaginatedDoc) { 650 if (aConfig.mForceAuto) { 651 // If we are forced to fill columns sequentially, force fit the 652 // content whether we are at top of page or not. 653 return true; 654 } 655 if (aReflowInput.mFlags.mIsTopOfPage) { 656 // If this is the last balancing reflow, we want to force fit 657 // content to avoid infinite loops. 658 return !aConfig.mIsBalancing || aConfig.mIsLastBalancingReflow; 659 } 660 // If we are a not at the top of page, we shouldn't force fit content. 661 // This is because our ColumnSetWrapperFrame can be pushed to the next 662 // column or page and reflowed again with a potentially larger 663 // available block-size. 664 return false; 665 } 666 // We are a top-level multicol in a non-paginated root document or in a 667 // subdocument (regardless of whether the root document is paginated). 668 // Force fit the content only if we are not balancing columns. 669 return !aConfig.mIsBalancing; 670 }(); 671 kidReflowInput.mFlags.mTableIsSplittable = false; 672 kidReflowInput.mFlags.mIsColumnBalancing = aConfig.mIsBalancing; 673 kidReflowInput.mFlags.mIsInLastColumnBalancingReflow = 674 aConfig.mIsLastBalancingReflow; 675 kidReflowInput.mBreakType = ReflowInput::BreakType::Column; 676 677 // We need to reflow any float placeholders, even if our column block-size 678 // hasn't changed. 679 kidReflowInput.mFlags.mMustReflowPlaceholders = !changingBSize; 680 681 COLUMN_SET_LOG( 682 "%s: Reflowing child #%d %p: availSize=(%d,%d), kidCBSize=(%d,%d), " 683 "child's mIsTopOfPage=%d", 684 __func__, colData.mColCount, child, availSize.ISize(wm), 685 availSize.BSize(wm), kidCBSize.ISize(wm), kidCBSize.BSize(wm), 686 kidReflowInput.mFlags.mIsTopOfPage); 687 688 // Note if the column's next in flow is not being changed by this 689 // incremental reflow. This may allow the current column to avoid trying 690 // to pull lines from the next column. 691 if (child->GetNextSibling() && !HasAnyStateBits(NS_FRAME_IS_DIRTY) && 692 !child->GetNextSibling()->HasAnyStateBits(NS_FRAME_IS_DIRTY)) { 693 kidReflowInput.mFlags.mNextInFlowUntouched = true; 694 } 695 696 ReflowOutput kidDesiredSize(wm); 697 698 // XXX it would be cool to consult the float manager for the 699 // previous block to figure out the region of floats from the 700 // previous column that extend into this column, and subtract 701 // that region from the new float manager. So you could stick a 702 // really big float in the first column and text in following 703 // columns would flow around it. 704 705 MOZ_ASSERT(kidReflowInput.ComputedLogicalMargin(wm).IsAllZero(), 706 "-moz-column-content has no margin!"); 707 aStatus.Reset(); 708 ReflowChild(child, PresContext(), kidDesiredSize, kidReflowInput, wm, 709 childOrigin, containerSize, ReflowChildFlags::Default, 710 aStatus); 711 712 if (colData.mColCount == 1 && aStatus.IsInlineBreakBefore()) { 713 COLUMN_SET_LOG("%s: Content in the first column reports break-before!", 714 __func__); 715 allFit = false; 716 break; 717 } 718 719 reflowNext = aStatus.NextInFlowNeedsReflow(); 720 721 // The carried-out block-end margin of column content might be non-zero 722 // when we try to find the best column balancing block size, but it should 723 // never affect the size column set nor be further carried out. Set it to 724 // zero. 725 // 726 // FIXME: For some types of fragmentation, we should carry the margin into 727 // the next column. Also see 728 // https://drafts.csswg.org/css-break-4/#break-margins 729 // 730 // FIXME: This should never happen for the last column, since it should be 731 // a margin root; see nsBlockFrame::IsMarginRoot(). However, sometimes the 732 // last column has an empty continuation while searching for the best 733 // column balancing bsize, which prevents the last column from being a 734 // margin root. 735 kidDesiredSize.mCarriedOutBEndMargin.Zero(); 736 737 NS_FRAME_TRACE_REFLOW_OUT("Column::Reflow", aStatus); 738 739 FinishReflowChild(child, PresContext(), kidDesiredSize, &kidReflowInput, 740 wm, childOrigin, containerSize, 741 ReflowChildFlags::Default); 742 743 childContentBEnd = nsLayoutUtils::CalculateContentBEnd(wm, child); 744 if (childContentBEnd > aConfig.mColBSize) { 745 allFit = false; 746 } 747 if (childContentBEnd > availSize.BSize(wm)) { 748 colData.mMaxOverflowingBSize = 749 std::max(childContentBEnd, colData.mMaxOverflowingBSize); 750 } 751 752 COLUMN_SET_LOG( 753 "%s: Reflowed child #%d %p: status=%s, desiredSize=(%d,%d), " 754 "childContentBEnd=%d, CarriedOutBEndMargin=%d (ignored)", 755 __func__, colData.mColCount, child, ToString(aStatus).c_str(), 756 kidDesiredSize.ISize(wm), kidDesiredSize.BSize(wm), childContentBEnd, 757 kidDesiredSize.mCarriedOutBEndMargin.Get()); 758 } 759 760 contentRect.UnionRect(contentRect, child->GetRect()); 761 762 ConsiderChildOverflow(overflowRects, child); 763 contentBEnd = std::max(contentBEnd, childContentBEnd); 764 colData.mLastBSize = childContentBEnd; 765 colData.mSumBSize += childContentBEnd; 766 767 // Build a continuation column if necessary 768 nsIFrame* kidNextInFlow = child->GetNextInFlow(); 769 770 if (aStatus.IsFullyComplete()) { 771 NS_ASSERTION(!kidNextInFlow, "next in flow should have been deleted"); 772 child = nullptr; 773 break; 774 } 775 776 // Make sure that the column has a next-in-flow. If not, we must 777 // create one to hold the overflowing stuff, even if we're just 778 // going to put it on our overflow list and let *our* 779 // next in flow handle it. 780 if (!kidNextInFlow) { 781 NS_ASSERTION(aStatus.NextInFlowNeedsReflow(), 782 "We have to create a continuation, but the block doesn't " 783 "want us to reflow it?"); 784 785 // We need to create a continuing column 786 kidNextInFlow = CreateNextInFlow(child); 787 } 788 789 // Make sure we reflow a next-in-flow when it switches between being 790 // normal or overflow container 791 if (aStatus.IsOverflowIncomplete()) { 792 if (!kidNextInFlow->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { 793 aStatus.SetNextInFlowNeedsReflow(); 794 reflowNext = true; 795 kidNextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); 796 } 797 } else if (kidNextInFlow->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { 798 aStatus.SetNextInFlowNeedsReflow(); 799 reflowNext = true; 800 kidNextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); 801 } 802 803 // We have reached the maximum number of columns. If we are balancing, stop 804 // this reflow and continue finding the optimal balancing block-size. 805 // 806 // Otherwise, i.e. we are not balancing, stop this reflow and let the parent 807 // of our multicol container create a next-in-flow if all of the following 808 // conditions are met. 809 // 810 // 1) We fill columns sequentially by the request of the style, not by our 811 // internal needs, i.e. aConfig.mForceAuto is false. 812 // 813 // We don't want to stop this reflow when we force fill the columns 814 // sequentially. We usually go into this mode when giving up balancing, and 815 // this is the last resort to fit all our children by creating overflow 816 // columns. 817 // 818 // 2) In a fragmented context, our multicol container still has block-size 819 // left for its next-in-flow, i.e. 820 // aReflowInput.mFlags.mColumnSetWrapperHasNoBSizeLeft is false. 821 // 822 // Note that in a continuous context, i.e. our multicol container's 823 // available block-size is unconstrained, if it has a fixed block-size 824 // mColumnSetWrapperHasNoBSizeLeft is always true because nothing stops it 825 // from applying all its block-size in the first-in-flow. Otherwise, i.e. 826 // our multicol container has an unconstrained block-size, we shouldn't be 827 // here because all our children should fit in the very first column even if 828 // mColumnSetWrapperHasNoBSizeLeft is false. 829 // 830 // According to the definition of mColumnSetWrapperHasNoBSizeLeft, if the 831 // bit is *not* set, either our multicol container has unconstrained 832 // block-size, or it has a constrained block-size and has block-size left 833 // for its next-in-flow. In either cases, the parent of our multicol 834 // container can create a next-in-flow for the container that guaranteed to 835 // have non-zero block-size for the container's children. 836 // 837 // Put simply, if either one of the above conditions is not met, we are 838 // going to create more overflow columns until all our children are fit. 839 if (colData.mColCount >= aConfig.mUsedColCount && 840 (aConfig.mIsBalancing || 841 (!aConfig.mForceAuto && 842 !aReflowInput.mFlags.mColumnSetWrapperHasNoBSizeLeft))) { 843 NS_ASSERTION(aConfig.mIsBalancing || 844 aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE, 845 "Why are we here if we have unlimited block-size to fill " 846 "columns sequentially."); 847 848 // No more columns allowed here. Stop. 849 aStatus.SetNextInFlowNeedsReflow(); 850 kidNextInFlow->MarkSubtreeDirty(); 851 // Move any of our leftover columns to our overflow list. Our 852 // next-in-flow will eventually pick them up. 853 nsFrameList continuationColumns = mFrames.TakeFramesAfter(child); 854 if (continuationColumns.NotEmpty()) { 855 SetOverflowFrames(std::move(continuationColumns)); 856 } 857 child = nullptr; 858 859 COLUMN_SET_LOG("%s: We are not going to create overflow columns.", 860 __func__); 861 break; 862 } 863 864 if (PresContext()->HasPendingInterrupt()) { 865 // Stop the loop now while |child| still points to the frame that bailed 866 // out. We could keep going here and condition a bunch of the code in 867 // this loop on whether there's an interrupt, or even just keep going and 868 // trying to reflow the blocks (even though we know they'll interrupt 869 // right after their first line), but stopping now is conceptually the 870 // simplest (and probably fastest) thing. 871 break; 872 } 873 874 // Advance to the next column 875 child = child->GetNextSibling(); 876 ++colData.mColCount; 877 878 if (child) { 879 childOrigin.I(wm) += aConfig.mColISize + aConfig.mColGap; 880 881 COLUMN_SET_LOG("%s: Next childOrigin.iCoord=%d", __func__, 882 childOrigin.I(wm)); 883 } 884 } 885 886 if (PresContext()->CheckForInterrupt(this) && 887 HasAnyStateBits(NS_FRAME_IS_DIRTY)) { 888 // Mark all our kids starting with |child| dirty 889 890 // Note that this is a CheckForInterrupt call, not a HasPendingInterrupt, 891 // because we might have interrupted while reflowing |child|, and since 892 // we're about to add a dirty bit to |child| we need to make sure that 893 // |this| is scheduled to have dirty bits marked on it and its ancestors. 894 // Otherwise, when we go to mark dirty bits on |child|'s ancestors we'll 895 // bail out immediately, since it'll already have a dirty bit. 896 for (; child; child = child->GetNextSibling()) { 897 child->MarkSubtreeDirty(); 898 } 899 } 900 901 colData.mMaxBSize = contentBEnd; 902 LogicalSize contentSize = LogicalSize(wm, contentRect.Size()); 903 contentSize.BSize(wm) = std::max(contentSize.BSize(wm), contentBEnd); 904 mLastFrameStatus = aStatus; 905 906 if (computedBSize != NS_UNCONSTRAINEDSIZE && !HasColumnSpanSiblings()) { 907 NS_ASSERTION(aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE, 908 "Available block-size should be constrained because it's " 909 "restricted by the computed block-size when our reflow " 910 "input is created in nsBlockFrame::ReflowBlockFrame()!"); 911 912 // If a) our parent ColumnSetWrapper has constrained block-size 913 // (nsBlockFrame::ReflowBlockFrame() applies the block-size constraint 914 // when creating a ReflowInput for ColumnSetFrame child); and b) we are the 915 // sole ColumnSet or the last ColumnSet continuation split by column-spans 916 // in a ColumnSetWrapper, extend our block-size to consume the available 917 // block-size so that the column-rules are drawn to the content block-end 918 // edge of the multicol container. 919 contentSize.BSize(wm) = 920 std::max(contentSize.BSize(wm), aReflowInput.AvailableBSize()); 921 } 922 923 aDesiredSize.SetSize(wm, contentSize); 924 aDesiredSize.mOverflowAreas = overflowRects; 925 aDesiredSize.UnionOverflowAreasWithDesiredBounds(); 926 927 // In vertical-rl mode, make a second pass if necessary to reposition the 928 // columns with the correct container width. (In other writing modes, 929 // correct containerSize was not required for column positioning so we don't 930 // need this fixup.) 931 // 932 // RTL column positions also depend on ColumnSet's actual contentSize. We need 933 // this fixup, too. 934 if ((wm.IsVerticalRL() || isRTL) && 935 containerSize.width != contentSize.Width(wm)) { 936 const nsSize finalContainerSize = aDesiredSize.PhysicalSize(); 937 OverflowAreas overflowRects; 938 for (nsIFrame* child : mFrames) { 939 // Get the logical position as set previously using a provisional or 940 // dummy containerSize, and reset with the correct container size. 941 child->SetPosition(wm, child->GetLogicalPosition(wm, containerSize), 942 finalContainerSize); 943 ConsiderChildOverflow(overflowRects, child); 944 } 945 aDesiredSize.mOverflowAreas = overflowRects; 946 aDesiredSize.UnionOverflowAreasWithDesiredBounds(); 947 } 948 949 colData.mFeasible = allFit && aStatus.IsFullyComplete(); 950 951 COLUMN_SET_LOG( 952 "%s: Done column reflow pass: %s, mMaxBSize=%d, mSumBSize=%d, " 953 "mLastBSize=%d, mMaxOverflowingBSize=%d", 954 __func__, colData.mFeasible ? "Feasible :)" : "Infeasible :(", 955 colData.mMaxBSize, colData.mSumBSize, colData.mLastBSize, 956 colData.mMaxOverflowingBSize); 957 958 return colData; 959 } 960 961 void nsColumnSetFrame::DrainOverflowColumns() { 962 // First grab the prev-in-flows overflows and reparent them to this 963 // frame. 964 nsPresContext* presContext = PresContext(); 965 nsColumnSetFrame* prev = static_cast<nsColumnSetFrame*>(GetPrevInFlow()); 966 if (prev) { 967 AutoFrameListPtr overflows(presContext, prev->StealOverflowFrames()); 968 if (overflows) { 969 mFrames.InsertFrames(this, nullptr, std::move(*overflows)); 970 } 971 } 972 973 // Now pull back our own overflows and append them to our children. 974 // We don't need to reparent them since we're already their parent. 975 AutoFrameListPtr overflows(presContext, StealOverflowFrames()); 976 if (overflows) { 977 // We're already the parent for these frames, so no need to set 978 // their parent again. 979 mFrames.AppendFrames(nullptr, std::move(*overflows)); 980 } 981 } 982 983 void nsColumnSetFrame::FindBestBalanceBSize(const ReflowInput& aReflowInput, 984 nsPresContext* aPresContext, 985 ReflowConfig& aConfig, 986 ColumnBalanceData aColData, 987 ReflowOutput& aDesiredSize, 988 bool aUnboundedLastColumn, 989 nsReflowStatus& aStatus) { 990 MOZ_ASSERT(aConfig.mIsBalancing, 991 "Why are we here if we are not balancing columns?"); 992 993 const nscoord availableContentBSize = aReflowInput.AvailableBSize(); 994 995 // Termination of the algorithm below is guaranteed because 996 // aConfig.knownFeasibleBSize - aConfig.knownInfeasibleBSize decreases in 997 // every iteration. 998 int32_t iterationCount = 1; 999 1000 // We set this flag when we detect that we may contain a frame 1001 // that can break anywhere (thus foiling the linear decrease-by-one 1002 // search) 1003 bool maybeContinuousBreakingDetected = false; 1004 bool possibleOptimalBSizeDetected = false; 1005 1006 // This is the extra block-size added to the optimal column block-size 1007 // estimation which is calculated in the while-loop by dividing 1008 // aColData.mSumBSize into N columns. 1009 // 1010 // The constant is arbitrary. We use a half of line-height first. In case a 1011 // column container uses *zero* (or a very small) line-height, use a half of 1012 // default line-height 1140/2 = 570 app units as the minimum value. Otherwise 1013 // we might take more than necessary iterations before finding a feasible 1014 // block-size. 1015 nscoord extraBlockSize = std::max(570, aReflowInput.GetLineHeight() / 2); 1016 1017 // We use divide-by-N to estimate the optimal column block-size only if the 1018 // last column's available block-size is unbounded. 1019 bool foundFeasibleBSizeCloserToBest = !aUnboundedLastColumn; 1020 1021 // Stop the binary search when the difference of the feasible and infeasible 1022 // block-size is within this gap. Here we use one device pixel. 1023 const int32_t gapToStop = aPresContext->DevPixelsToAppUnits(1); 1024 1025 while (!aPresContext->HasPendingInterrupt()) { 1026 nscoord lastKnownFeasibleBSize = aConfig.mKnownFeasibleBSize; 1027 1028 // Record what we learned from the last reflow 1029 if (aColData.mFeasible) { 1030 // mMaxBSize is feasible. Also, mLastBalanceBSize is feasible. 1031 aConfig.mKnownFeasibleBSize = 1032 std::min(aConfig.mKnownFeasibleBSize, aColData.mMaxBSize); 1033 aConfig.mKnownFeasibleBSize = 1034 std::min(aConfig.mKnownFeasibleBSize, mLastBalanceBSize); 1035 1036 // Furthermore, no block-size less than the block-size of the last 1037 // column can ever be feasible. (We might be able to reduce the 1038 // block-size of a non-last column by moving content to a later column, 1039 // but we can't do that with the last column.) 1040 if (aColData.mColCount == aConfig.mUsedColCount) { 1041 aConfig.mKnownInfeasibleBSize = 1042 std::max(aConfig.mKnownInfeasibleBSize, aColData.mLastBSize - 1); 1043 } 1044 } else { 1045 aConfig.mKnownInfeasibleBSize = 1046 std::max(aConfig.mKnownInfeasibleBSize, mLastBalanceBSize); 1047 1048 // If a column didn't fit in its available block-size, then its current 1049 // block-size must be the minimum block-size for unbreakable content in 1050 // the column, and therefore no smaller block-size can be feasible. 1051 aConfig.mKnownInfeasibleBSize = std::max( 1052 aConfig.mKnownInfeasibleBSize, aColData.mMaxOverflowingBSize - 1); 1053 1054 if (aUnboundedLastColumn) { 1055 // The last column is unbounded, so all content got reflowed, so the 1056 // mMaxBSize is feasible. 1057 aConfig.mKnownFeasibleBSize = 1058 std::min(aConfig.mKnownFeasibleBSize, aColData.mMaxBSize); 1059 1060 NS_ASSERTION(mLastFrameStatus.IsComplete(), 1061 "Last column should be complete if the available " 1062 "block-size is unconstrained!"); 1063 } 1064 } 1065 1066 COLUMN_SET_LOG( 1067 "%s: this=%p, mKnownInfeasibleBSize=%d, mKnownFeasibleBSize=%d", 1068 __func__, this, aConfig.mKnownInfeasibleBSize, 1069 aConfig.mKnownFeasibleBSize); 1070 1071 if (aConfig.mKnownInfeasibleBSize >= aConfig.mKnownFeasibleBSize - 1) { 1072 // aConfig.mKnownFeasibleBSize is where we want to be. This can happen in 1073 // the very first iteration when a column container solely has a tall 1074 // unbreakable child that overflows the container. 1075 break; 1076 } 1077 1078 if (aConfig.mKnownInfeasibleBSize >= availableContentBSize) { 1079 // There's no feasible block-size to fit our contents. We may need to 1080 // reflow one more time after this loop. 1081 break; 1082 } 1083 1084 const nscoord gap = 1085 aConfig.mKnownFeasibleBSize - aConfig.mKnownInfeasibleBSize; 1086 if (gap <= gapToStop && possibleOptimalBSizeDetected) { 1087 // We detected a possible optimal block-size in the last iteration. If it 1088 // is infeasible, we may need to reflow one more time after this loop. 1089 break; 1090 } 1091 1092 if (lastKnownFeasibleBSize - aConfig.mKnownFeasibleBSize == 1) { 1093 // We decreased the feasible block-size by one twip only. This could 1094 // indicate that there is a continuously breakable child frame 1095 // that we are crawling through. 1096 maybeContinuousBreakingDetected = true; 1097 } 1098 1099 nscoord nextGuess = aConfig.mKnownInfeasibleBSize + gap / 2; 1100 if (aConfig.mKnownFeasibleBSize - nextGuess < extraBlockSize && 1101 !maybeContinuousBreakingDetected) { 1102 // We're close to our target, so just try shrinking just the 1103 // minimum amount that will cause one of our columns to break 1104 // differently. 1105 nextGuess = aConfig.mKnownFeasibleBSize - 1; 1106 } else if (!foundFeasibleBSizeCloserToBest) { 1107 // Make a guess by dividing mSumBSize into N columns and adding 1108 // extraBlockSize to try to make it on the feasible side. 1109 nextGuess = aColData.mSumBSize / aConfig.mUsedColCount + extraBlockSize; 1110 // Sanitize it 1111 nextGuess = std::clamp(nextGuess, aConfig.mKnownInfeasibleBSize + 1, 1112 aConfig.mKnownFeasibleBSize - 1); 1113 // We keep doubling extraBlockSize in every iteration until we find a 1114 // feasible guess. 1115 extraBlockSize *= 2; 1116 } else if (aConfig.mKnownFeasibleBSize == NS_UNCONSTRAINEDSIZE) { 1117 // This can happen when we had a next-in-flow so we didn't 1118 // want to do an unbounded block-size measuring step. Let's just increase 1119 // from the infeasible block-size by some reasonable amount. 1120 nextGuess = aConfig.mKnownInfeasibleBSize * 2 + extraBlockSize; 1121 } else if (gap <= gapToStop) { 1122 // Floor nextGuess to the greatest multiple of gapToStop below or equal to 1123 // mKnownFeasibleBSize. 1124 nextGuess = aConfig.mKnownFeasibleBSize / gapToStop * gapToStop; 1125 possibleOptimalBSizeDetected = true; 1126 } 1127 1128 // Don't bother guessing more than our block-size constraint. 1129 nextGuess = std::min(availableContentBSize, nextGuess); 1130 1131 COLUMN_SET_LOG("%s: Choosing next guess=%d, iteration=%d", __func__, 1132 nextGuess, iterationCount); 1133 ++iterationCount; 1134 1135 aConfig.mColBSize = nextGuess; 1136 1137 aUnboundedLastColumn = false; 1138 MarkPrincipalChildrenDirty(this); 1139 aColData = 1140 ReflowColumns(aDesiredSize, aReflowInput, aStatus, aConfig, false); 1141 1142 if (!foundFeasibleBSizeCloserToBest && aColData.mFeasible) { 1143 foundFeasibleBSizeCloserToBest = true; 1144 } 1145 } 1146 1147 if (!aColData.mFeasible && !aPresContext->HasPendingInterrupt()) { 1148 // We need to reflow one more time at the feasible block-size to 1149 // get a valid layout. 1150 if (aConfig.mKnownInfeasibleBSize >= availableContentBSize) { 1151 aConfig.mColBSize = availableContentBSize; 1152 if (mLastBalanceBSize == availableContentBSize) { 1153 // If we end up here, we have a constrained available content 1154 // block-size, and our last column's block-size exceeds it. Also, if 1155 // this is the first balancing iteration, the last column is given 1156 // unconstrained available block-size, so it has a fully complete 1157 // reflow status. Therefore, we always want to reflow again at the 1158 // available content block-size to get a valid layout and a correct 1159 // reflow status (likely an *incomplete* status) so that our column 1160 // container can be fragmented if needed. 1161 1162 if (aReflowInput.mFlags.mColumnSetWrapperHasNoBSizeLeft) { 1163 // If our column container has a constrained block-size (either in a 1164 // paginated context or in a nested column container), and is going 1165 // to consume all its computed block-size in this fragment, then our 1166 // column container has no block-size left to contain our 1167 // next-in-flows. We have to give up balancing, and create our 1168 // own overflow columns. 1169 // 1170 // We don't want to create overflow columns immediately when our 1171 // content doesn't fit since this changes our reflow status from 1172 // incomplete to complete. Valid reasons include 1) the outer column 1173 // container might do column balancing, and it can enlarge the 1174 // available content block-size so that the nested one could fit its 1175 // content in next balancing iteration; or 2) the outer column 1176 // container is filling columns sequentially, and may have more 1177 // inline-size to create more column boxes for the nested column 1178 // container's next-in-flows. 1179 aConfig = ChooseColumnStrategy(aReflowInput, true); 1180 } 1181 } 1182 } else { 1183 aConfig.mColBSize = aConfig.mKnownFeasibleBSize; 1184 } 1185 1186 // This is our last attempt to reflow. If our column container's available 1187 // block-size is unconstrained, make sure that the last column is 1188 // allowed to have arbitrary block-size here, even though we were 1189 // balancing. Otherwise we'd have to split, and it's not clear what we'd 1190 // do with that. 1191 COLUMN_SET_LOG("%s: Last attempt to call ReflowColumns", __func__); 1192 aConfig.mIsLastBalancingReflow = true; 1193 const bool forceUnboundedLastColumn = 1194 aReflowInput.mParentReflowInput->AvailableBSize() == 1195 NS_UNCONSTRAINEDSIZE; 1196 MarkPrincipalChildrenDirty(this); 1197 ReflowColumns(aDesiredSize, aReflowInput, aStatus, aConfig, 1198 forceUnboundedLastColumn); 1199 } 1200 } 1201 1202 void nsColumnSetFrame::Reflow(nsPresContext* aPresContext, 1203 ReflowOutput& aDesiredSize, 1204 const ReflowInput& aReflowInput, 1205 nsReflowStatus& aStatus) { 1206 MarkInReflow(); 1207 // Don't support interruption in columns 1208 nsPresContext::InterruptPreventer noInterrupts(aPresContext); 1209 1210 DO_GLOBAL_REFLOW_COUNT("nsColumnSetFrame"); 1211 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 1212 1213 MOZ_ASSERT(aReflowInput.mCBReflowInput->mFrame->StyleColumn() 1214 ->IsColumnContainerStyle(), 1215 "The column container should have relevant column styles!"); 1216 MOZ_ASSERT(aReflowInput.mParentReflowInput->mFrame->IsColumnSetWrapperFrame(), 1217 "The column container should be ColumnSetWrapperFrame!"); 1218 MOZ_ASSERT( 1219 aReflowInput.ComputedLogicalBorderPadding(aReflowInput.GetWritingMode()) 1220 .IsAllZero(), 1221 "Only the column container can have border and padding!"); 1222 MOZ_ASSERT( 1223 GetChildList(FrameChildListID::OverflowContainers).IsEmpty() && 1224 GetChildList(FrameChildListID::ExcessOverflowContainers).IsEmpty(), 1225 "ColumnSetFrame should store overflow containers in principal " 1226 "child list!"); 1227 1228 //------------ Handle Incremental Reflow ----------------- 1229 1230 COLUMN_SET_LOG("%s: Begin Reflow: this=%p, is nested multicol=%d", __func__, 1231 this, 1232 aReflowInput.mParentReflowInput->mFrame->HasAnyStateBits( 1233 NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR)); 1234 1235 // If inline size is unconstrained, set aForceAuto to true to allow 1236 // the columns to expand in the inline direction. (This typically 1237 // happens in orthogonal flows where the inline direction is the 1238 // container's block direction). 1239 ReflowConfig config = ChooseColumnStrategy( 1240 aReflowInput, aReflowInput.ComputedISize() == NS_UNCONSTRAINEDSIZE); 1241 1242 // If balancing, then we allow the last column to grow to unbounded 1243 // block-size during the first reflow. This gives us a way to estimate 1244 // what the average column block-size should be, because we can measure 1245 // the block-size of all the columns and sum them up. But don't do this 1246 // if we have a next in flow because we don't want to suck all its 1247 // content back here and then have to push it out again! 1248 nsIFrame* nextInFlow = GetNextInFlow(); 1249 bool unboundedLastColumn = config.mIsBalancing && !nextInFlow; 1250 const ColumnBalanceData colData = ReflowColumns( 1251 aDesiredSize, aReflowInput, aStatus, config, unboundedLastColumn); 1252 1253 // If we're not balancing, then we're already done, since we should have 1254 // reflown all of our children, and there is no need for a binary search to 1255 // determine proper column block-size. 1256 if (config.mIsBalancing && !aPresContext->HasPendingInterrupt()) { 1257 FindBestBalanceBSize(aReflowInput, aPresContext, config, colData, 1258 aDesiredSize, unboundedLastColumn, aStatus); 1259 } 1260 1261 if (aPresContext->HasPendingInterrupt() && 1262 aReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) { 1263 // In this situation, we might be lying about our reflow status, because 1264 // our last kid (the one that got interrupted) was incomplete. Fix that. 1265 aStatus.Reset(); 1266 } 1267 1268 NS_ASSERTION(aStatus.IsFullyComplete() || 1269 aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE, 1270 "Column set should be complete if the available block-size is " 1271 "unconstrained"); 1272 1273 MOZ_ASSERT(!HasAbsolutelyPositionedChildren(), 1274 "ColumnSetWrapperFrame should be the abs.pos container!"); 1275 FinishAndStoreOverflow(&aDesiredSize, aReflowInput.mStyleDisplay); 1276 1277 COLUMN_SET_LOG("%s: End Reflow: this=%p", __func__, this); 1278 } 1279 1280 void nsColumnSetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 1281 const nsDisplayListSet& aLists) { 1282 DisplayBorderBackgroundOutline(aBuilder, aLists); 1283 1284 if (IsVisibleForPainting()) { 1285 aLists.BorderBackground()->AppendNewToTop<nsDisplayColumnRule>(aBuilder, 1286 this); 1287 } 1288 1289 if (HidesContent()) { 1290 return; 1291 } 1292 1293 // Our children won't have backgrounds so it doesn't matter where we put them. 1294 for (nsIFrame* f : mFrames) { 1295 BuildDisplayListForChild(aBuilder, f, aLists); 1296 } 1297 } 1298 1299 void nsColumnSetFrame::AppendDirectlyOwnedAnonBoxes( 1300 nsTArray<OwnedAnonBox>& aResult) { 1301 // Everything in mFrames is continuations of the first thing in mFrames. 1302 nsIFrame* column = mFrames.FirstChild(); 1303 1304 // We might not have any columns, apparently? 1305 if (!column) { 1306 return; 1307 } 1308 1309 MOZ_ASSERT(column->Style()->GetPseudoType() == PseudoStyleType::columnContent, 1310 "What sort of child is this?"); 1311 aResult.AppendElement(OwnedAnonBox(column)); 1312 } 1313 1314 Maybe<nscoord> nsColumnSetFrame::GetNaturalBaselineBOffset( 1315 WritingMode aWM, BaselineSharingGroup aBaselineGroup, 1316 BaselineExportContext aExportContext) const { 1317 Maybe<nscoord> result; 1318 for (const auto* kid : mFrames) { 1319 auto kidBaseline = 1320 kid->GetNaturalBaselineBOffset(aWM, aBaselineGroup, aExportContext); 1321 if (!kidBaseline) { 1322 continue; 1323 } 1324 // The kid frame may not necessarily be aligned with the columnset frame. 1325 LogicalRect kidRect{aWM, kid->GetLogicalNormalPosition(aWM, GetSize()), 1326 kid->GetLogicalSize(aWM)}; 1327 if (aBaselineGroup == BaselineSharingGroup::First) { 1328 *kidBaseline += kidRect.BStart(aWM); 1329 } else { 1330 *kidBaseline += (GetLogicalSize().BSize(aWM) - kidRect.BEnd(aWM)); 1331 } 1332 // Take the smallest of the baselines (i.e. Closest to border-block-start 1333 // for `BaselineSharingGroup::First`, border-block-end for 1334 // `BaselineSharingGroup::Last`) 1335 if (!result || *kidBaseline < *result) { 1336 result = kidBaseline; 1337 } 1338 } 1339 return result; 1340 } 1341 1342 #ifdef DEBUG 1343 void nsColumnSetFrame::SetInitialChildList(ChildListID aListID, 1344 nsFrameList&& aChildList) { 1345 MOZ_ASSERT(aListID != FrameChildListID::Principal || aChildList.OnlyChild(), 1346 "initial principal child list must have exactly one child"); 1347 nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList)); 1348 } 1349 1350 void nsColumnSetFrame::AppendFrames(ChildListID aListID, 1351 nsFrameList&& aFrameList) { 1352 MOZ_CRASH("unsupported operation"); 1353 } 1354 1355 void nsColumnSetFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, 1356 const nsLineList::iterator* aPrevFrameLine, 1357 nsFrameList&& aFrameList) { 1358 MOZ_CRASH("unsupported operation"); 1359 } 1360 1361 void nsColumnSetFrame::RemoveFrame(DestroyContext&, ChildListID, nsIFrame*) { 1362 MOZ_CRASH("unsupported operation"); 1363 } 1364 #endif