nsBlockFrame.cpp (346943B)
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 /* 8 * rendering object for CSS display:block, inline-block, and list-item 9 * boxes, also used for various anonymous boxes 10 */ 11 12 #include "nsBlockFrame.h" 13 14 #include <inttypes.h> 15 16 #include <algorithm> 17 18 #include "BlockReflowState.h" 19 #include "CounterStyleManager.h" 20 #include "TextOverflow.h" 21 #include "fmt/format.h" 22 #include "gfxContext.h" 23 #include "mozilla/AbsoluteContainingBlock.h" 24 #include "mozilla/AppUnits.h" 25 #include "mozilla/Baseline.h" 26 #include "mozilla/ComputedStyle.h" 27 #include "mozilla/DebugOnly.h" 28 #include "mozilla/DefineEnum.h" 29 #include "mozilla/Likely.h" 30 #include "mozilla/Maybe.h" 31 #include "mozilla/PresShell.h" 32 #include "mozilla/RestyleManager.h" 33 #include "mozilla/SVGUtils.h" 34 #include "mozilla/ScrollContainerFrame.h" 35 #include "mozilla/ServoStyleSet.h" 36 #include "mozilla/Sprintf.h" 37 #include "mozilla/StaticPrefs_browser.h" 38 #include "mozilla/StaticPrefs_layout.h" 39 #include "mozilla/ToString.h" 40 #include "mozilla/UniquePtr.h" 41 #include "mozilla/dom/Selection.h" 42 #include "nsBidiPresUtils.h" 43 #include "nsBlockReflowContext.h" 44 #include "nsCOMPtr.h" 45 #include "nsCRT.h" 46 #include "nsCSSFrameConstructor.h" 47 #include "nsCSSRendering.h" 48 #include "nsDisplayList.h" 49 #include "nsError.h" 50 #include "nsFirstLetterFrame.h" 51 #include "nsFlexContainerFrame.h" 52 #include "nsFloatManager.h" 53 #include "nsFontMetrics.h" 54 #include "nsFrameManager.h" 55 #include "nsGenericHTMLElement.h" 56 #include "nsGkAtoms.h" 57 #include "nsHTMLParts.h" 58 #include "nsIFrameInlines.h" 59 #include "nsLayoutUtils.h" 60 #include "nsLineBox.h" 61 #include "nsLineLayout.h" 62 #include "nsPlaceholderFrame.h" 63 #include "nsPresContext.h" 64 #include "nsPresContextInlines.h" 65 #include "nsStyleConsts.h" 66 #include "nsTextControlFrame.h" 67 #include "prenv.h" 68 69 static const int MIN_LINES_NEEDING_CURSOR = 20; 70 71 using namespace mozilla; 72 using namespace mozilla::css; 73 using namespace mozilla::dom; 74 using namespace mozilla::layout; 75 using ClearFloatsResult = BlockReflowState::ClearFloatsResult; 76 using ShapeType = nsFloatManager::ShapeType; 77 78 static void MarkAllInlineLinesDirty(nsBlockFrame* aBlock) { 79 for (auto& line : aBlock->Lines()) { 80 if (line.IsInline()) { 81 line.MarkDirty(); 82 } 83 } 84 } 85 86 static void MarkAllDescendantLinesDirty(nsBlockFrame* aBlock) { 87 for (auto& line : aBlock->Lines()) { 88 if (line.IsBlock()) { 89 nsBlockFrame* bf = do_QueryFrame(line.mFirstChild); 90 if (bf) { 91 MarkAllDescendantLinesDirty(bf); 92 } 93 } 94 line.MarkDirty(); 95 } 96 } 97 98 static void MarkSameFloatManagerLinesDirty(nsBlockFrame* aBlock) { 99 nsBlockFrame* blockWithFloatMgr = aBlock; 100 while (!blockWithFloatMgr->HasAnyStateBits(NS_BLOCK_BFC)) { 101 nsBlockFrame* bf = do_QueryFrame(blockWithFloatMgr->GetParent()); 102 if (!bf) { 103 break; 104 } 105 blockWithFloatMgr = bf; 106 } 107 108 // Mark every line at and below the line where the float was 109 // dirty, and mark their lines dirty too. We could probably do 110 // something more efficient --- e.g., just dirty the lines that intersect 111 // the float vertically. 112 MarkAllDescendantLinesDirty(blockWithFloatMgr); 113 } 114 115 /** 116 * Returns true if aFrame is a block that has one or more float children. 117 */ 118 static bool BlockHasAnyFloats(nsIFrame* aFrame) { 119 nsBlockFrame* block = do_QueryFrame(aFrame); 120 if (!block) { 121 return false; 122 } 123 if (block->GetChildList(FrameChildListID::Float).FirstChild()) { 124 return true; 125 } 126 127 for (const auto& line : block->Lines()) { 128 if (line.IsBlock() && BlockHasAnyFloats(line.mFirstChild)) { 129 return true; 130 } 131 } 132 return false; 133 } 134 135 // Determines whether the given frame is visible text or has visible text that 136 // participate in the same line. Frames that are not line participants do not 137 // have their children checked. 138 static bool FrameHasVisibleInlineText(nsIFrame* aFrame) { 139 MOZ_ASSERT(aFrame, "Frame argument cannot be null"); 140 if (!aFrame->IsLineParticipant()) { 141 return false; 142 } 143 if (aFrame->IsTextFrame()) { 144 return aFrame->StyleVisibility()->IsVisible() && 145 NS_GET_A(aFrame->StyleText()->mWebkitTextFillColor.CalcColor( 146 aFrame)) != 0; 147 } 148 for (nsIFrame* kid : aFrame->PrincipalChildList()) { 149 if (FrameHasVisibleInlineText(kid)) { 150 return true; 151 } 152 } 153 return false; 154 } 155 156 // Determines whether any of the frames from the given line have visible text. 157 static bool LineHasVisibleInlineText(nsLineBox* aLine) { 158 nsIFrame* kid = aLine->mFirstChild; 159 int32_t n = aLine->GetChildCount(); 160 while (n-- > 0) { 161 if (FrameHasVisibleInlineText(kid)) { 162 return true; 163 } 164 kid = kid->GetNextSibling(); 165 } 166 return false; 167 } 168 169 /** 170 * Iterates through the frame's in-flow children and 171 * unions the ink overflow of all text frames which 172 * participate in the line aFrame belongs to. 173 * If a child of aFrame is not a text frame, 174 * we recurse with the child as the aFrame argument. 175 * If aFrame isn't a line participant, we skip it entirely 176 * and return an empty rect. 177 * The resulting nsRect is offset relative to the parent of aFrame. 178 */ 179 static nsRect GetFrameTextArea(nsIFrame* aFrame, 180 nsDisplayListBuilder* aBuilder) { 181 nsRect textArea; 182 if (const nsTextFrame* textFrame = do_QueryFrame(aFrame)) { 183 if (!textFrame->IsEntirelyWhitespace()) { 184 textArea = aFrame->InkOverflowRect(); 185 } 186 } else if (aFrame->IsLineParticipant()) { 187 for (nsIFrame* kid : aFrame->PrincipalChildList()) { 188 nsRect kidTextArea = GetFrameTextArea(kid, aBuilder); 189 textArea.OrWith(kidTextArea); 190 } 191 } 192 // add aFrame's position to keep textArea relative to aFrame's parent 193 return textArea + aFrame->GetPosition(); 194 } 195 196 /** 197 * Iterates through the line's children and 198 * unions the ink overflow of all text frames. 199 * GetFrameTextArea unions and returns the ink overflow 200 * from all line-participating text frames within the given child. 201 * The nsRect returned from GetLineTextArea is offset 202 * relative to the given line. 203 */ 204 static nsRect GetLineTextArea(nsLineBox* aLine, 205 nsDisplayListBuilder* aBuilder) { 206 nsRect textArea; 207 nsIFrame* kid = aLine->mFirstChild; 208 int32_t n = aLine->GetChildCount(); 209 while (n-- > 0) { 210 nsRect kidTextArea = GetFrameTextArea(kid, aBuilder); 211 textArea.OrWith(kidTextArea); 212 kid = kid->GetNextSibling(); 213 } 214 215 return textArea; 216 } 217 218 /** 219 * Starting with aFrame, iterates upward through parent frames and checks for 220 * non-transparent background colors. If one is found, we use that as our 221 * backplate color. Otheriwse, we use the default background color from 222 * our high contrast theme. 223 */ 224 static nscolor GetBackplateColor(nsIFrame* aFrame) { 225 nsPresContext* pc = aFrame->PresContext(); 226 nscolor currentBackgroundColor = NS_TRANSPARENT; 227 for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) { 228 // NOTE(emilio): We assume themed frames (frame->IsThemed()) have correct 229 // background-color information so as to compute the right backplate color. 230 // 231 // This holds because HTML widgets with author-specified backgrounds or 232 // borders disable theming. So as long as the UA-specified background colors 233 // match the actual theme (which they should because we always use system 234 // colors with the non-native theme, and native system colors should also 235 // match the native theme), then we're alright and we should compute an 236 // appropriate backplate color. 237 const auto* style = frame->Style(); 238 if (style->StyleBackground()->IsTransparent(style)) { 239 continue; 240 } 241 bool drawImage = false, drawColor = false; 242 nscolor backgroundColor = nsCSSRendering::DetermineBackgroundColor( 243 pc, style, frame, drawImage, drawColor); 244 if (!drawColor && !drawImage) { 245 continue; 246 } 247 if (NS_GET_A(backgroundColor) == 0) { 248 // Even if there's a background image, if there's no background color we 249 // keep going up the frame tree, see bug 1723938. 250 continue; 251 } 252 if (NS_GET_A(currentBackgroundColor) == 0) { 253 // Try to avoid somewhat expensive math in the common case. 254 currentBackgroundColor = backgroundColor; 255 } else { 256 currentBackgroundColor = 257 NS_ComposeColors(backgroundColor, currentBackgroundColor); 258 } 259 if (NS_GET_A(currentBackgroundColor) == 0xff) { 260 // If fully opaque, we're done, otherwise keep going up blending with our 261 // background. 262 return currentBackgroundColor; 263 } 264 } 265 nscolor backgroundColor = aFrame->PresContext()->DefaultBackgroundColor(); 266 if (NS_GET_A(currentBackgroundColor) == 0) { 267 return backgroundColor; 268 } 269 return NS_ComposeColors(backgroundColor, currentBackgroundColor); 270 } 271 272 static nsRect GetNormalMarginRect(const nsIFrame& aFrame, 273 bool aIncludePositiveMargins = true) { 274 nsMargin m = aFrame.GetUsedMargin().ApplySkipSides(aFrame.GetSkipSides()); 275 if (!aIncludePositiveMargins) { 276 m.EnsureAtMost(nsMargin()); 277 } 278 auto rect = aFrame.GetRectRelativeToSelf(); 279 rect.Inflate(m); 280 return rect + aFrame.GetNormalPosition(); 281 } 282 283 #ifdef DEBUG 284 # include "nsBlockDebugFlags.h" 285 286 bool nsBlockFrame::gLamePaintMetrics; 287 bool nsBlockFrame::gLameReflowMetrics; 288 bool nsBlockFrame::gNoisy; 289 bool nsBlockFrame::gNoisyDamageRepair; 290 bool nsBlockFrame::gNoisyIntrinsic; 291 bool nsBlockFrame::gNoisyReflow; 292 bool nsBlockFrame::gReallyNoisyReflow; 293 bool nsBlockFrame::gNoisyFloatManager; 294 bool nsBlockFrame::gVerifyLines; 295 bool nsBlockFrame::gDisableResizeOpt; 296 297 int32_t nsBlockFrame::gNoiseIndent; 298 299 struct BlockDebugFlags { 300 const char* name; 301 bool* on; 302 }; 303 304 static const BlockDebugFlags gFlags[] = { 305 {"reflow", &nsBlockFrame::gNoisyReflow}, 306 {"really-noisy-reflow", &nsBlockFrame::gReallyNoisyReflow}, 307 {"intrinsic", &nsBlockFrame::gNoisyIntrinsic}, 308 {"float-manager", &nsBlockFrame::gNoisyFloatManager}, 309 {"verify-lines", &nsBlockFrame::gVerifyLines}, 310 {"damage-repair", &nsBlockFrame::gNoisyDamageRepair}, 311 {"lame-paint-metrics", &nsBlockFrame::gLamePaintMetrics}, 312 {"lame-reflow-metrics", &nsBlockFrame::gLameReflowMetrics}, 313 {"disable-resize-opt", &nsBlockFrame::gDisableResizeOpt}, 314 }; 315 # define NUM_DEBUG_FLAGS (sizeof(gFlags) / sizeof(gFlags[0])) 316 317 static void ShowDebugFlags() { 318 printf("Here are the available GECKO_BLOCK_DEBUG_FLAGS:\n"); 319 const BlockDebugFlags* bdf = gFlags; 320 const BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS; 321 for (; bdf < end; bdf++) { 322 printf(" %s\n", bdf->name); 323 } 324 printf("Note: GECKO_BLOCK_DEBUG_FLAGS is a comma separated list of flag\n"); 325 printf("names (no whitespace)\n"); 326 } 327 328 void nsBlockFrame::InitDebugFlags() { 329 static bool firstTime = true; 330 if (firstTime) { 331 firstTime = false; 332 char* flags = PR_GetEnv("GECKO_BLOCK_DEBUG_FLAGS"); 333 if (flags) { 334 bool error = false; 335 for (;;) { 336 char* cm = strchr(flags, ','); 337 if (cm) { 338 *cm = '\0'; 339 } 340 341 bool found = false; 342 const BlockDebugFlags* bdf = gFlags; 343 const BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS; 344 for (; bdf < end; bdf++) { 345 if (nsCRT::strcasecmp(bdf->name, flags) == 0) { 346 *(bdf->on) = true; 347 printf("nsBlockFrame: setting %s debug flag on\n", bdf->name); 348 gNoisy = true; 349 found = true; 350 break; 351 } 352 } 353 if (!found) { 354 error = true; 355 } 356 357 if (!cm) { 358 break; 359 } 360 *cm = ','; 361 flags = cm + 1; 362 } 363 if (error) { 364 ShowDebugFlags(); 365 } 366 } 367 } 368 } 369 370 MOZ_DEFINE_ENUM_TOSTRING_FUNC(LineReflowStatus, 371 (OK, Stop, RedoNoPull, RedoMoreFloats, 372 RedoNextBand, Truncated)); 373 #endif 374 375 #ifdef REFLOW_STATUS_COVERAGE 376 static void RecordReflowStatus(bool aChildIsBlock, 377 const nsReflowStatus& aFrameReflowStatus) { 378 static uint32_t record[2]; 379 380 // 0: child-is-block 381 // 1: child-is-inline 382 int index = 0; 383 if (!aChildIsBlock) { 384 index |= 1; 385 } 386 387 // Compute new status 388 uint32_t newS = record[index]; 389 if (aFrameReflowStatus.IsInlineBreak()) { 390 if (aFrameReflowStatus.IsInlineBreakBefore()) { 391 newS |= 1; 392 } else if (aFrameReflowStatus.IsIncomplete()) { 393 newS |= 2; 394 } else { 395 newS |= 4; 396 } 397 } else if (aFrameReflowStatus.IsIncomplete()) { 398 newS |= 8; 399 } else { 400 newS |= 16; 401 } 402 403 // Log updates to the status that yield different values 404 if (record[index] != newS) { 405 record[index] = newS; 406 printf("record(%d): %02x %02x\n", index, record[0], record[1]); 407 } 408 } 409 #endif 410 411 NS_DECLARE_FRAME_PROPERTY_WITH_DTOR_NEVER_CALLED(OverflowLinesProperty, 412 nsBlockFrame::FrameLines) 413 NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OverflowOutOfFlowsProperty) 414 NS_DECLARE_FRAME_PROPERTY_FRAMELIST(FloatsProperty) 415 NS_DECLARE_FRAME_PROPERTY_FRAMELIST(PushedFloatsProperty) 416 NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OutsideMarkerProperty) 417 NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(InsideMarkerProperty, nsIFrame) 418 419 //---------------------------------------------------------------------- 420 421 nsBlockFrame* NS_NewBlockFrame(PresShell* aPresShell, ComputedStyle* aStyle) { 422 return new (aPresShell) nsBlockFrame(aStyle, aPresShell->GetPresContext()); 423 } 424 425 NS_IMPL_FRAMEARENA_HELPERS(nsBlockFrame) 426 427 nsBlockFrame::~nsBlockFrame() = default; 428 429 void nsBlockFrame::AddSizeOfExcludingThisForTree( 430 nsWindowSizes& aWindowSizes) const { 431 nsContainerFrame::AddSizeOfExcludingThisForTree(aWindowSizes); 432 433 // Add the size of any nsLineBox::mFrames hashtables we might have: 434 for (const auto& line : Lines()) { 435 line.AddSizeOfExcludingThis(aWindowSizes); 436 } 437 const FrameLines* overflowLines = GetOverflowLines(); 438 if (overflowLines) { 439 ConstLineIterator line = overflowLines->mLines.begin(), 440 line_end = overflowLines->mLines.end(); 441 for (; line != line_end; ++line) { 442 line->AddSizeOfExcludingThis(aWindowSizes); 443 } 444 } 445 } 446 447 void nsBlockFrame::Destroy(DestroyContext& aContext) { 448 ClearLineCursors(); 449 DestroyAbsoluteFrames(aContext); 450 nsPresContext* presContext = PresContext(); 451 mozilla::PresShell* presShell = presContext->PresShell(); 452 if (HasFloats()) { 453 SafelyDestroyFrameListProp(aContext, presShell, FloatsProperty()); 454 RemoveStateBits(NS_BLOCK_HAS_FLOATS); 455 } 456 nsLineBox::DeleteLineList(presContext, mLines, &mFrames, aContext); 457 458 if (HasPushedFloats()) { 459 SafelyDestroyFrameListProp(aContext, presShell, PushedFloatsProperty()); 460 RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS); 461 } 462 463 // destroy overflow lines now 464 FrameLines* overflowLines = RemoveOverflowLines(); 465 if (overflowLines) { 466 nsLineBox::DeleteLineList(presContext, overflowLines->mLines, 467 &overflowLines->mFrames, aContext); 468 delete overflowLines; 469 } 470 471 if (HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) { 472 SafelyDestroyFrameListProp(aContext, presShell, 473 OverflowOutOfFlowsProperty()); 474 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS); 475 } 476 477 if (HasMarker()) { 478 SafelyDestroyFrameListProp(aContext, presShell, OutsideMarkerProperty()); 479 RemoveStateBits(NS_BLOCK_HAS_MARKER); 480 } 481 482 nsContainerFrame::Destroy(aContext); 483 } 484 485 /* virtual */ 486 nsILineIterator* nsBlockFrame::GetLineIterator() { 487 nsLineIterator* iter = GetProperty(LineIteratorProperty()); 488 if (!iter) { 489 const nsStyleVisibility* visibility = StyleVisibility(); 490 iter = new nsLineIterator(mLines, 491 visibility->mDirection == StyleDirection::Rtl); 492 SetProperty(LineIteratorProperty(), iter); 493 } 494 return iter; 495 } 496 497 NS_QUERYFRAME_HEAD(nsBlockFrame) 498 NS_QUERYFRAME_ENTRY(nsBlockFrame) 499 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 500 501 #ifdef DEBUG_FRAME_DUMP 502 void nsBlockFrame::List(FILE* out, const char* aPrefix, 503 ListFlags aFlags) const { 504 nsCString str; 505 ListGeneric(str, aPrefix, aFlags); 506 507 fprintf_stderr(out, "%s <\n", str.get()); 508 509 nsCString pfx(aPrefix); 510 pfx += " "; 511 512 // Output the lines 513 if (!mLines.empty()) { 514 ConstLineIterator line = LinesBegin(), line_end = LinesEnd(); 515 for (; line != line_end; ++line) { 516 line->List(out, pfx.get(), aFlags); 517 } 518 } 519 520 // Output the overflow lines. 521 const FrameLines* overflowLines = GetOverflowLines(); 522 if (overflowLines && !overflowLines->mLines.empty()) { 523 fprintf_stderr(out, "%sOverflow-lines %p/%p <\n", pfx.get(), overflowLines, 524 &overflowLines->mFrames); 525 nsCString nestedPfx(pfx); 526 nestedPfx += " "; 527 ConstLineIterator line = overflowLines->mLines.begin(), 528 line_end = overflowLines->mLines.end(); 529 for (; line != line_end; ++line) { 530 line->List(out, nestedPfx.get(), aFlags); 531 } 532 fprintf_stderr(out, "%s>\n", pfx.get()); 533 } 534 535 // skip the principal list - we printed the lines above 536 // skip the overflow list - we printed the overflow lines above 537 ChildListIDs skip = {FrameChildListID::Principal, FrameChildListID::Overflow}; 538 ListChildLists(out, pfx.get(), aFlags, skip); 539 540 fprintf_stderr(out, "%s>\n", aPrefix); 541 } 542 543 nsresult nsBlockFrame::GetFrameName(nsAString& aResult) const { 544 return MakeFrameName(u"Block"_ns, aResult); 545 } 546 #endif 547 548 void nsBlockFrame::InvalidateFrame(uint32_t aDisplayItemKey, 549 bool aRebuildDisplayItems) { 550 if (IsInSVGTextSubtree()) { 551 NS_ASSERTION(GetParent()->IsSVGTextFrame(), 552 "unexpected block frame in SVG text"); 553 GetParent()->InvalidateFrame(); 554 return; 555 } 556 nsContainerFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems); 557 } 558 559 void nsBlockFrame::InvalidateFrameWithRect(const nsRect& aRect, 560 uint32_t aDisplayItemKey, 561 bool aRebuildDisplayItems) { 562 if (IsInSVGTextSubtree()) { 563 NS_ASSERTION(GetParent()->IsSVGTextFrame(), 564 "unexpected block frame in SVG text"); 565 GetParent()->InvalidateFrame(); 566 return; 567 } 568 nsContainerFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey, 569 aRebuildDisplayItems); 570 } 571 572 nscoord nsBlockFrame::SynthesizeFallbackBaseline( 573 WritingMode aWM, BaselineSharingGroup aBaselineGroup) const { 574 if (IsButtonLike() && StyleDisplay()->IsInlineOutsideStyle()) { 575 return Baseline::SynthesizeBOffsetFromContentBox(this, aWM, aBaselineGroup); 576 } 577 return Baseline::SynthesizeBOffsetFromMarginBox(this, aWM, aBaselineGroup); 578 } 579 580 template <typename LineIteratorType> 581 Maybe<nscoord> nsBlockFrame::GetBaselineBOffset( 582 LineIteratorType aStart, LineIteratorType aEnd, WritingMode aWM, 583 BaselineSharingGroup aBaselineGroup, 584 BaselineExportContext aExportContext) const { 585 MOZ_ASSERT((std::is_same_v<LineIteratorType, ConstLineIterator> && 586 aBaselineGroup == BaselineSharingGroup::First) || 587 (std::is_same_v<LineIteratorType, ConstReverseLineIterator> && 588 aBaselineGroup == BaselineSharingGroup::Last), 589 "Iterator direction must match baseline sharing group."); 590 for (auto line = aStart; line != aEnd; ++line) { 591 if (!line->IsBlock()) { 592 // XXX Is this the right test? We have some bogus empty lines 593 // floating around, but IsEmpty is perhaps too weak. 594 if (line->BSize() != 0 || !line->IsEmpty()) { 595 const auto ascent = line->BStart() + line->GetLogicalAscent(); 596 if (aBaselineGroup == BaselineSharingGroup::Last) { 597 return Some(BSize(aWM) - ascent); 598 } 599 return Some(ascent); 600 } 601 continue; 602 } 603 nsIFrame* kid = line->mFirstChild; 604 if (aWM.IsOrthogonalTo(kid->GetWritingMode())) { 605 continue; 606 } 607 if (aExportContext == BaselineExportContext::LineLayout && 608 kid->IsTableWrapperFrame()) { 609 // `<table>` in inline-block context does not export any baseline. 610 continue; 611 } 612 const auto kidBaselineGroup = 613 aExportContext == BaselineExportContext::LineLayout 614 ? kid->GetDefaultBaselineSharingGroup() 615 : aBaselineGroup; 616 const auto kidBaseline = 617 kid->GetNaturalBaselineBOffset(aWM, kidBaselineGroup, aExportContext); 618 if (!kidBaseline) { 619 continue; 620 } 621 auto result = *kidBaseline; 622 if (kidBaselineGroup == BaselineSharingGroup::Last) { 623 result = kid->BSize(aWM) - result; 624 } 625 // Ignore relative positioning for baseline calculations. 626 const nsSize& sz = line->mContainerSize; 627 result += kid->GetLogicalNormalPosition(aWM, sz).B(aWM); 628 if (aBaselineGroup == BaselineSharingGroup::Last) { 629 return Some(BSize(aWM) - result); 630 } 631 return Some(result); 632 } 633 return Nothing{}; 634 } 635 636 Maybe<nscoord> nsBlockFrame::GetNaturalBaselineBOffset( 637 WritingMode aWM, BaselineSharingGroup aBaselineGroup, 638 BaselineExportContext aExportContext) const { 639 if (StyleDisplay()->IsContainLayout()) { 640 return Nothing{}; 641 } 642 643 Maybe<nscoord> offset = 644 aBaselineGroup == BaselineSharingGroup::First 645 ? GetBaselineBOffset(LinesBegin(), LinesEnd(), aWM, aBaselineGroup, 646 aExportContext) 647 : GetBaselineBOffset(LinesRBegin(), LinesREnd(), aWM, aBaselineGroup, 648 aExportContext); 649 if (!offset && IsButtonLike()) { 650 for (const auto& line : Reversed(Lines())) { 651 if (line.IsEmpty()) { 652 continue; 653 } 654 // Buttons use the end of the content as a baseline if we haven't found 655 // one yet. 656 nscoord bEnd = line.BEnd(); 657 offset.emplace(aBaselineGroup == BaselineSharingGroup::Last 658 ? BSize(aWM) - bEnd 659 : bEnd); 660 break; 661 } 662 } 663 return offset; 664 } 665 666 nscoord nsBlockFrame::GetCaretBaseline() const { 667 const auto wm = GetWritingMode(); 668 if (!mLines.empty()) { 669 ConstLineIterator line = LinesBegin(); 670 if (!line->IsEmpty()) { 671 if (line->IsBlock()) { 672 return GetLogicalUsedBorderAndPadding(wm).BStart(wm) + 673 line->mFirstChild->GetCaretBaseline(); 674 } 675 return line->BStart() + line->GetLogicalAscent(); 676 } 677 } 678 return GetFontMetricsDerivedCaretBaseline(ContentBSize(wm)); 679 } 680 681 ///////////////////////////////////////////////////////////////////////////// 682 // Child frame enumeration 683 684 const nsFrameList& nsBlockFrame::GetChildList(ChildListID aListID) const { 685 switch (aListID) { 686 case FrameChildListID::Principal: 687 return mFrames; 688 case FrameChildListID::Overflow: { 689 FrameLines* overflowLines = GetOverflowLines(); 690 return overflowLines ? overflowLines->mFrames : nsFrameList::EmptyList(); 691 } 692 case FrameChildListID::OverflowOutOfFlow: { 693 const nsFrameList* list = GetOverflowOutOfFlows(); 694 return list ? *list : nsFrameList::EmptyList(); 695 } 696 case FrameChildListID::Float: { 697 const nsFrameList* list = GetFloats(); 698 return list ? *list : nsFrameList::EmptyList(); 699 } 700 case FrameChildListID::PushedFloats: { 701 const nsFrameList* list = GetPushedFloats(); 702 return list ? *list : nsFrameList::EmptyList(); 703 } 704 case FrameChildListID::Marker: { 705 const nsFrameList* list = GetOutsideMarkerList(); 706 return list ? *list : nsFrameList::EmptyList(); 707 } 708 default: 709 return nsContainerFrame::GetChildList(aListID); 710 } 711 } 712 713 void nsBlockFrame::GetChildLists(nsTArray<ChildList>* aLists) const { 714 nsContainerFrame::GetChildLists(aLists); 715 FrameLines* overflowLines = GetOverflowLines(); 716 if (overflowLines) { 717 overflowLines->mFrames.AppendIfNonempty(aLists, FrameChildListID::Overflow); 718 } 719 if (const nsFrameList* list = GetOverflowOutOfFlows()) { 720 list->AppendIfNonempty(aLists, FrameChildListID::OverflowOutOfFlow); 721 } 722 if (const nsFrameList* list = GetOutsideMarkerList()) { 723 list->AppendIfNonempty(aLists, FrameChildListID::Marker); 724 } 725 if (const nsFrameList* list = GetFloats()) { 726 list->AppendIfNonempty(aLists, FrameChildListID::Float); 727 } 728 if (const nsFrameList* list = GetPushedFloats()) { 729 list->AppendIfNonempty(aLists, FrameChildListID::PushedFloats); 730 } 731 } 732 733 /* virtual */ 734 bool nsBlockFrame::IsFloatContainingBlock() const { return true; } 735 736 /** 737 * Remove the first line from aFromLines and adjust the associated frame list 738 * aFromFrames accordingly. The removed line is assigned to *aOutLine and 739 * a frame list with its frames is assigned to *aOutFrames, i.e. the frames 740 * that were extracted from the head of aFromFrames. 741 * aFromLines must contain at least one line, the line may be empty. 742 * @return true if aFromLines becomes empty 743 */ 744 static bool RemoveFirstLine(nsLineList& aFromLines, nsFrameList& aFromFrames, 745 nsLineBox** aOutLine, nsFrameList* aOutFrames) { 746 LineListIterator removedLine = aFromLines.begin(); 747 *aOutLine = removedLine; 748 LineListIterator next = aFromLines.erase(removedLine); 749 bool isLastLine = next == aFromLines.end(); 750 nsIFrame* firstFrameInNextLine = isLastLine ? nullptr : next->mFirstChild; 751 *aOutFrames = aFromFrames.TakeFramesBefore(firstFrameInNextLine); 752 return isLastLine; 753 } 754 755 ////////////////////////////////////////////////////////////////////// 756 // Reflow methods 757 758 /* virtual */ 759 void nsBlockFrame::MarkIntrinsicISizesDirty() { 760 nsBlockFrame* dirtyBlock = static_cast<nsBlockFrame*>(FirstContinuation()); 761 dirtyBlock->mCachedIntrinsics.Clear(); 762 if (!HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION)) { 763 for (nsIFrame* frame = dirtyBlock; frame; 764 frame = frame->GetNextContinuation()) { 765 frame->AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION); 766 } 767 } 768 769 nsContainerFrame::MarkIntrinsicISizesDirty(); 770 } 771 772 void nsBlockFrame::CheckIntrinsicCacheAgainstShrinkWrapState() { 773 nsPresContext* presContext = PresContext(); 774 if (!nsLayoutUtils::FontSizeInflationEnabled(presContext)) { 775 return; 776 } 777 bool inflationEnabled = !presContext->mInflationDisabledForShrinkWrap; 778 if (inflationEnabled != HasAnyStateBits(NS_BLOCK_INTRINSICS_INFLATED)) { 779 mCachedIntrinsics.Clear(); 780 AddOrRemoveStateBits(NS_BLOCK_INTRINSICS_INFLATED, inflationEnabled); 781 } 782 } 783 784 // Whether this line is indented by the text-indent amount. 785 bool nsBlockFrame::TextIndentAppliesTo(const LineIterator& aLine) const { 786 const auto& textIndent = StyleText()->mTextIndent; 787 788 bool isFirstLineOrAfterHardBreak = [&] { 789 if (aLine != LinesBegin()) { 790 // If not the first line of the block, but 'each-line' is in effect, 791 // check if the previous line was not wrapped. 792 return textIndent.each_line && !aLine.prev()->IsLineWrapped(); 793 } 794 if (nsBlockFrame* prevBlock = do_QueryFrame(GetPrevInFlow())) { 795 // There's a prev-in-flow, so this only counts as a first-line if 796 // 'each-line' and the prev-in-flow's last line was not wrapped. 797 return textIndent.each_line && 798 (prevBlock->Lines().empty() || 799 !prevBlock->LinesEnd().prev()->IsLineWrapped()); 800 } 801 return true; 802 }(); 803 804 // The 'hanging' option inverts which lines are/aren't indented. 805 return isFirstLineOrAfterHardBreak != textIndent.hanging; 806 } 807 808 nscoord nsBlockFrame::IntrinsicISize(const IntrinsicSizeInput& aInput, 809 IntrinsicISizeType aType) { 810 nsIFrame* firstCont = FirstContinuation(); 811 if (firstCont != this) { 812 return firstCont->IntrinsicISize(aInput, aType); 813 } 814 815 CheckIntrinsicCacheAgainstShrinkWrapState(); 816 817 return mCachedIntrinsics.GetOrSet(*this, aType, aInput, [&] { 818 return aType == IntrinsicISizeType::MinISize ? MinISize(aInput) 819 : PrefISize(aInput); 820 }); 821 } 822 823 /* virtual */ 824 nscoord nsBlockFrame::MinISize(const IntrinsicSizeInput& aInput) { 825 if (Maybe<nscoord> containISize = ContainIntrinsicISize()) { 826 return *containISize; 827 } 828 829 #ifdef DEBUG 830 if (gNoisyIntrinsic) { 831 IndentBy(stdout, gNoiseIndent); 832 ListTag(stdout); 833 printf(": MinISize\n"); 834 } 835 AutoNoisyIndenter indenter(gNoisyIntrinsic); 836 #endif 837 838 for (nsBlockFrame* curFrame = this; curFrame; 839 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) { 840 curFrame->LazyMarkLinesDirty(); 841 } 842 843 if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) && 844 PresContext()->BidiEnabled()) { 845 ResolveBidi(); 846 } 847 848 const bool whiteSpaceCanWrap = StyleText()->WhiteSpaceCanWrapStyle(); 849 InlineMinISizeData data; 850 for (nsBlockFrame* curFrame = this; curFrame; 851 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) { 852 for (LineIterator line = curFrame->LinesBegin(), 853 line_end = curFrame->LinesEnd(); 854 line != line_end; ++line) { 855 #ifdef DEBUG 856 if (gNoisyIntrinsic) { 857 IndentBy(stdout, gNoiseIndent); 858 printf("line (%s%s)\n", line->IsBlock() ? "block" : "inline", 859 line->IsEmpty() ? ", empty" : ""); 860 } 861 AutoNoisyIndenter lineindent(gNoisyIntrinsic); 862 #endif 863 if (line->IsBlock()) { 864 data.ForceBreak(); 865 nsIFrame* kid = line->mFirstChild; 866 const IntrinsicSizeInput kidInput(aInput, kid->GetWritingMode(), 867 GetWritingMode()); 868 data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer( 869 kidInput.mContext, kid, IntrinsicISizeType::MinISize, 870 kidInput.mPercentageBasisForChildren); 871 data.ForceBreak(); 872 } else { 873 if (!curFrame->GetPrevContinuation() && TextIndentAppliesTo(line)) { 874 data.mCurrentLine += StyleText()->mTextIndent.length.Resolve(0); 875 } 876 data.mLine = &line; 877 data.SetLineContainer(curFrame); 878 nsIFrame* kid = line->mFirstChild; 879 for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end; 880 ++i, kid = kid->GetNextSibling()) { 881 const IntrinsicSizeInput kidInput(aInput, kid->GetWritingMode(), 882 GetWritingMode()); 883 kid->AddInlineMinISize(kidInput, &data); 884 if (whiteSpaceCanWrap && data.mTrailingWhitespace) { 885 data.OptionallyBreak(); 886 } 887 } 888 } 889 #ifdef DEBUG 890 if (gNoisyIntrinsic) { 891 IndentBy(stdout, gNoiseIndent); 892 printf("min: [prevLines=%d currentLine=%d]\n", data.mPrevLines, 893 data.mCurrentLine); 894 } 895 #endif 896 } 897 } 898 data.ForceBreak(); 899 return data.mPrevLines; 900 } 901 902 /* virtual */ 903 nscoord nsBlockFrame::PrefISize(const IntrinsicSizeInput& aInput) { 904 if (Maybe<nscoord> containISize = ContainIntrinsicISize()) { 905 return *containISize; 906 } 907 908 #ifdef DEBUG 909 if (gNoisyIntrinsic) { 910 IndentBy(stdout, gNoiseIndent); 911 ListTag(stdout); 912 printf(": PrefISize\n"); 913 } 914 AutoNoisyIndenter indenter(gNoisyIntrinsic); 915 #endif 916 917 for (nsBlockFrame* curFrame = this; curFrame; 918 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) { 919 curFrame->LazyMarkLinesDirty(); 920 } 921 922 if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) && 923 PresContext()->BidiEnabled()) { 924 ResolveBidi(); 925 } 926 InlinePrefISizeData data; 927 for (nsBlockFrame* curFrame = this; curFrame; 928 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) { 929 for (LineIterator line = curFrame->LinesBegin(), 930 line_end = curFrame->LinesEnd(); 931 line != line_end; ++line) { 932 #ifdef DEBUG 933 if (gNoisyIntrinsic) { 934 IndentBy(stdout, gNoiseIndent); 935 printf("line (%s%s)\n", line->IsBlock() ? "block" : "inline", 936 line->IsEmpty() ? ", empty" : ""); 937 } 938 AutoNoisyIndenter lineindent(gNoisyIntrinsic); 939 #endif 940 if (line->IsBlock()) { 941 nsIFrame* kid = line->mFirstChild; 942 UsedClear clearType; 943 if (!data.mLineIsEmpty || BlockCanIntersectFloats(kid)) { 944 clearType = UsedClear::Both; 945 } else { 946 clearType = kid->StyleDisplay()->UsedClear(GetWritingMode()); 947 } 948 data.ForceBreak(clearType); 949 const IntrinsicSizeInput kidInput(aInput, kid->GetWritingMode(), 950 GetWritingMode()); 951 data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer( 952 kidInput.mContext, kid, IntrinsicISizeType::PrefISize, 953 kidInput.mPercentageBasisForChildren); 954 data.ForceBreak(); 955 } else { 956 if (!curFrame->GetPrevContinuation() && TextIndentAppliesTo(line)) { 957 nscoord indent = StyleText()->mTextIndent.length.Resolve(0); 958 data.mCurrentLine += indent; 959 // XXXmats should the test below be indent > 0? 960 if (indent != nscoord(0)) { 961 data.mLineIsEmpty = false; 962 } 963 } 964 data.mLine = &line; 965 data.SetLineContainer(curFrame); 966 nsIFrame* kid = line->mFirstChild; 967 for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end; 968 ++i, kid = kid->GetNextSibling()) { 969 const IntrinsicSizeInput kidInput(aInput, kid->GetWritingMode(), 970 GetWritingMode()); 971 kid->AddInlinePrefISize(kidInput, &data); 972 } 973 } 974 #ifdef DEBUG 975 if (gNoisyIntrinsic) { 976 IndentBy(stdout, gNoiseIndent); 977 printf("pref: [prevLines=%d currentLine=%d]\n", data.mPrevLines, 978 data.mCurrentLine); 979 } 980 #endif 981 } 982 } 983 data.ForceBreak(); 984 return data.mPrevLines; 985 } 986 987 nsRect nsBlockFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const { 988 // be conservative 989 if (Style()->HasTextDecorationLines()) { 990 return InkOverflowRect(); 991 } 992 return ComputeSimpleTightBounds(aDrawTarget); 993 } 994 995 /* virtual */ 996 nsresult nsBlockFrame::GetPrefWidthTightBounds(gfxContext* aRenderingContext, 997 nscoord* aX, nscoord* aXMost) { 998 nsIFrame* firstInFlow = FirstContinuation(); 999 if (firstInFlow != this) { 1000 return firstInFlow->GetPrefWidthTightBounds(aRenderingContext, aX, aXMost); 1001 } 1002 1003 *aX = 0; 1004 *aXMost = 0; 1005 1006 nsresult rv; 1007 InlinePrefISizeData data; 1008 for (nsBlockFrame* curFrame = this; curFrame; 1009 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) { 1010 for (LineIterator line = curFrame->LinesBegin(), 1011 line_end = curFrame->LinesEnd(); 1012 line != line_end; ++line) { 1013 nscoord childX, childXMost; 1014 if (line->IsBlock()) { 1015 data.ForceBreak(); 1016 rv = line->mFirstChild->GetPrefWidthTightBounds(aRenderingContext, 1017 &childX, &childXMost); 1018 NS_ENSURE_SUCCESS(rv, rv); 1019 *aX = std::min(*aX, childX); 1020 *aXMost = std::max(*aXMost, childXMost); 1021 } else { 1022 if (!curFrame->GetPrevContinuation() && TextIndentAppliesTo(line)) { 1023 data.mCurrentLine += StyleText()->mTextIndent.length.Resolve(0); 1024 } 1025 data.mLine = &line; 1026 data.SetLineContainer(curFrame); 1027 nsIFrame* kid = line->mFirstChild; 1028 // Per comment in nsIFrame::GetPrefWidthTightBounds(), the function is 1029 // only implemented for nsBlockFrame and nsTextFrame and is used to 1030 // determine the intrinsic inline sizes of MathML token elements. These 1031 // elements shouldn't have percentage block sizes that require a 1032 // percentage basis for resolution. 1033 const IntrinsicSizeInput kidInput(aRenderingContext, Nothing(), 1034 Nothing()); 1035 for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end; 1036 ++i, kid = kid->GetNextSibling()) { 1037 rv = kid->GetPrefWidthTightBounds(aRenderingContext, &childX, 1038 &childXMost); 1039 NS_ENSURE_SUCCESS(rv, rv); 1040 *aX = std::min(*aX, data.mCurrentLine + childX); 1041 *aXMost = std::max(*aXMost, data.mCurrentLine + childXMost); 1042 kid->AddInlinePrefISize(kidInput, &data); 1043 } 1044 } 1045 } 1046 } 1047 data.ForceBreak(); 1048 1049 return NS_OK; 1050 } 1051 1052 /** 1053 * Return whether aNewAvailableSpace is smaller *on either side* 1054 * (inline-start or inline-end) than aOldAvailableSpace, so that we know 1055 * if we need to redo layout on an line, replaced block, or block 1056 * formatting context, because its height (which we used to compute 1057 * aNewAvailableSpace) caused it to intersect additional floats. 1058 */ 1059 static bool AvailableSpaceShrunk(WritingMode aWM, 1060 const LogicalRect& aOldAvailableSpace, 1061 const LogicalRect& aNewAvailableSpace, 1062 bool aCanGrow /* debug-only */) { 1063 if (aNewAvailableSpace.ISize(aWM) == 0) { 1064 // Positions are not significant if the inline size is zero. 1065 return aOldAvailableSpace.ISize(aWM) != 0; 1066 } 1067 if (aCanGrow) { 1068 NS_ASSERTION( 1069 aNewAvailableSpace.IStart(aWM) <= aOldAvailableSpace.IStart(aWM) || 1070 aNewAvailableSpace.IEnd(aWM) <= aOldAvailableSpace.IEnd(aWM), 1071 "available space should not shrink on the start side and " 1072 "grow on the end side"); 1073 NS_ASSERTION( 1074 aNewAvailableSpace.IStart(aWM) >= aOldAvailableSpace.IStart(aWM) || 1075 aNewAvailableSpace.IEnd(aWM) >= aOldAvailableSpace.IEnd(aWM), 1076 "available space should not grow on the start side and " 1077 "shrink on the end side"); 1078 } else { 1079 NS_ASSERTION( 1080 aOldAvailableSpace.IStart(aWM) <= aNewAvailableSpace.IStart(aWM) && 1081 aOldAvailableSpace.IEnd(aWM) >= aNewAvailableSpace.IEnd(aWM), 1082 "available space should never grow"); 1083 } 1084 // Have we shrunk on either side? 1085 return aNewAvailableSpace.IStart(aWM) > aOldAvailableSpace.IStart(aWM) || 1086 aNewAvailableSpace.IEnd(aWM) < aOldAvailableSpace.IEnd(aWM); 1087 } 1088 1089 /** 1090 * Returns aFrame if it is an in-flow, non-BFC block frame, and null otherwise. 1091 * 1092 * This is used to determine whether to recurse into aFrame when applying 1093 * -webkit-line-clamp. 1094 */ 1095 static const nsBlockFrame* GetAsLineClampDescendant(const nsIFrame* aFrame) { 1096 const nsBlockFrame* block = do_QueryFrame(aFrame); 1097 if (!block || block->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW | NS_BLOCK_BFC)) { 1098 return nullptr; 1099 } 1100 return block; 1101 } 1102 1103 static nsBlockFrame* GetAsLineClampDescendant(nsIFrame* aFrame) { 1104 return const_cast<nsBlockFrame*>( 1105 GetAsLineClampDescendant(const_cast<const nsIFrame*>(aFrame))); 1106 } 1107 1108 static bool IsLineClampRoot(const nsBlockFrame* aFrame) { 1109 if (!aFrame->StyleDisplay()->mWebkitLineClamp) { 1110 return false; 1111 } 1112 1113 if (!aFrame->HasAnyStateBits(NS_BLOCK_BFC)) { 1114 return false; 1115 } 1116 1117 if (StaticPrefs::layout_css_webkit_line_clamp_block_enabled() || 1118 aFrame->PresContext()->Document()->ChromeRulesEnabled()) { 1119 return true; 1120 } 1121 1122 // For now, -webkit-box is the only thing allowed to be a line-clamp root. 1123 // Ideally we'd just make this work everywhere, but for now we're carrying 1124 // this forward as a limitation on the legacy -webkit-line-clamp feature, 1125 // since relaxing this limitation might create webcompat trouble. 1126 auto origDisplay = [&] { 1127 if (aFrame->Style()->GetPseudoType() == PseudoStyleType::scrolledContent) { 1128 // If we're the anonymous block inside the scroll frame, we need to look 1129 // at the original display of our parent frame. 1130 MOZ_ASSERT(aFrame->GetParent()); 1131 const auto& parentDisp = *aFrame->GetParent()->StyleDisplay(); 1132 MOZ_ASSERT(parentDisp.mWebkitLineClamp == 1133 aFrame->StyleDisplay()->mWebkitLineClamp, 1134 ":-moz-scrolled-content should inherit -webkit-line-clamp, " 1135 "via rule in UA stylesheet"); 1136 return parentDisp.mOriginalDisplay; 1137 } 1138 return aFrame->StyleDisplay()->mOriginalDisplay; 1139 }(); 1140 return origDisplay.Inside() == StyleDisplayInside::WebkitBox; 1141 } 1142 1143 nsBlockFrame* nsBlockFrame::GetLineClampRoot() const { 1144 if (IsLineClampRoot(this)) { 1145 return const_cast<nsBlockFrame*>(this); 1146 } 1147 const nsBlockFrame* cur = this; 1148 while (GetAsLineClampDescendant(cur)) { 1149 cur = do_QueryFrame(cur->GetParent()); 1150 if (!cur) { 1151 break; 1152 } 1153 if (IsLineClampRoot(cur)) { 1154 return const_cast<nsBlockFrame*>(cur); 1155 } 1156 } 1157 return nullptr; 1158 } 1159 1160 bool nsBlockFrame::MaybeHasFloats() const { 1161 if (HasFloats()) { 1162 return true; 1163 } 1164 if (HasPushedFloats()) { 1165 return true; 1166 } 1167 // For the OverflowOutOfFlowsProperty I think we do enforce that, but it's 1168 // a mix of out-of-flow frames, so that's why the method name has "Maybe". 1169 return HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS); 1170 } 1171 1172 /** 1173 * Iterator over all descendant inline line boxes, except for those that are 1174 * under an independent formatting context. 1175 */ 1176 class MOZ_RAII LineClampLineIterator { 1177 public: 1178 LineClampLineIterator(nsBlockFrame* aFrame, const nsBlockFrame* aStopAtFrame) 1179 : mCur(aFrame->LinesBegin()), 1180 mEnd(aFrame->LinesEnd()), 1181 mCurrentFrame(mCur == mEnd ? nullptr : aFrame), 1182 mStopAtFrame(aStopAtFrame) { 1183 if (mCur != mEnd && !mCur->IsInline()) { 1184 Advance(); 1185 } 1186 } 1187 1188 nsLineBox* GetCurrentLine() { return mCurrentFrame ? mCur.get() : nullptr; } 1189 nsBlockFrame* GetCurrentFrame() { return mCurrentFrame; } 1190 1191 // Advances the iterator to the next line line. 1192 // 1193 // Next() shouldn't be called once the iterator is at the end, which can be 1194 // checked for by GetCurrentLine() or GetCurrentFrame() returning null. 1195 void Next() { 1196 MOZ_ASSERT(mCur != mEnd && mCurrentFrame, 1197 "Don't call Next() when the iterator is at the end"); 1198 ++mCur; 1199 Advance(); 1200 } 1201 1202 private: 1203 void Advance() { 1204 for (;;) { 1205 if (mCur == mEnd) { 1206 // Reached the end of the current block. Pop the parent off the 1207 // stack; if there isn't one, then we've reached the end. 1208 if (mStack.IsEmpty()) { 1209 mCurrentFrame = nullptr; 1210 break; 1211 } 1212 if (mCurrentFrame == mStopAtFrame) { 1213 mStack.Clear(); 1214 mCurrentFrame = nullptr; 1215 break; 1216 } 1217 1218 auto entry = mStack.PopLastElement(); 1219 mCurrentFrame = entry.first; 1220 mCur = entry.second; 1221 mEnd = mCurrentFrame->LinesEnd(); 1222 } else if (mCur->IsBlock()) { 1223 if (nsBlockFrame* child = GetAsLineClampDescendant(mCur->mFirstChild)) { 1224 nsBlockFrame::LineIterator next = mCur; 1225 ++next; 1226 mStack.AppendElement(std::make_pair(mCurrentFrame, next)); 1227 mCur = child->LinesBegin(); 1228 mEnd = child->LinesEnd(); 1229 mCurrentFrame = child; 1230 } else { 1231 // Some kind of frame we shouldn't descend into. 1232 ++mCur; 1233 } 1234 } else { 1235 MOZ_ASSERT(mCur->IsInline()); 1236 break; 1237 } 1238 } 1239 } 1240 1241 // The current line within the current block. 1242 // 1243 // When this is equal to mEnd, the iterator is at its end, and mCurrentFrame 1244 // is set to null. 1245 nsBlockFrame::LineIterator mCur; 1246 1247 // The iterator end for the current block. 1248 nsBlockFrame::LineIterator mEnd; 1249 1250 // The current block. 1251 nsBlockFrame* mCurrentFrame; 1252 1253 // The block past which we can't look at line-clamp. 1254 const nsBlockFrame* mStopAtFrame; 1255 1256 // Stack of mCurrentFrame and mEnd values that we push and pop as we enter and 1257 // exist blocks. 1258 AutoTArray<std::pair<nsBlockFrame*, nsBlockFrame::LineIterator>, 8> mStack; 1259 }; 1260 1261 static bool ClearLineClampEllipsis(nsBlockFrame* aFrame) { 1262 if (aFrame->HasLineClampEllipsis()) { 1263 MOZ_ASSERT(!aFrame->HasLineClampEllipsisDescendant()); 1264 aFrame->SetHasLineClampEllipsis(false); 1265 for (auto& line : aFrame->Lines()) { 1266 if (line.HasLineClampEllipsis()) { 1267 line.ClearHasLineClampEllipsis(); 1268 break; 1269 } 1270 } 1271 return true; 1272 } 1273 1274 if (aFrame->HasLineClampEllipsisDescendant()) { 1275 aFrame->SetHasLineClampEllipsisDescendant(false); 1276 for (nsIFrame* f : aFrame->PrincipalChildList()) { 1277 if (nsBlockFrame* child = GetAsLineClampDescendant(f)) { 1278 if (ClearLineClampEllipsis(child)) { 1279 return true; 1280 } 1281 } 1282 } 1283 } 1284 1285 return false; 1286 } 1287 1288 void nsBlockFrame::ClearLineClampEllipsis() { ::ClearLineClampEllipsis(this); } 1289 1290 void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics, 1291 const ReflowInput& aReflowInput, 1292 nsReflowStatus& aStatus) { 1293 if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) { 1294 FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay); 1295 return; 1296 } 1297 1298 MarkInReflow(); 1299 DO_GLOBAL_REFLOW_COUNT("nsBlockFrame"); 1300 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 1301 1302 #ifdef DEBUG 1303 if (gNoisyReflow) { 1304 IndentBy(stdout, gNoiseIndent); 1305 fmt::println(FMT_STRING("{}: begin reflow: availSize={} computedSize={}"), 1306 ListTag().get(), ToString(aReflowInput.AvailableSize()), 1307 ToString(aReflowInput.ComputedSize())); 1308 } 1309 AutoNoisyIndenter indent(gNoisy); 1310 PRTime start = 0; // Initialize these variablies to silence the compiler. 1311 int32_t ctc = 0; // We only use these if they are set (gLameReflowMetrics). 1312 if (gLameReflowMetrics) { 1313 start = PR_Now(); 1314 ctc = nsLineBox::GetCtorCount(); 1315 } 1316 #endif 1317 1318 // ColumnSetWrapper's children depend on ColumnSetWrapper's block-size or 1319 // max-block-size because both affect the children's available block-size. 1320 if (IsColumnSetWrapperFrame()) { 1321 AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE); 1322 } 1323 1324 Maybe<nscoord> restoreReflowInputAvailBSize; 1325 auto MaybeRestore = MakeScopeExit([&] { 1326 if (MOZ_UNLIKELY(restoreReflowInputAvailBSize)) { 1327 const_cast<ReflowInput&>(aReflowInput) 1328 .SetAvailableBSize(*restoreReflowInputAvailBSize); 1329 } 1330 }); 1331 1332 WritingMode wm = aReflowInput.GetWritingMode(); 1333 const nscoord consumedBSize = CalcAndCacheConsumedBSize(); 1334 const nscoord effectiveContentBoxBSize = 1335 GetEffectiveComputedBSize(aReflowInput, consumedBSize); 1336 // If we have non-auto block size, we're clipping our kids and we fit, 1337 // make sure our kids fit too. 1338 if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE && 1339 aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE && 1340 ShouldApplyOverflowClipping(aReflowInput.mStyleDisplay) 1341 .contains(wm.PhysicalAxis(LogicalAxis::Block))) { 1342 LogicalMargin blockDirExtras = 1343 aReflowInput.ComputedLogicalBorderPadding(wm); 1344 if (GetLogicalSkipSides().BStart()) { 1345 blockDirExtras.BStart(wm) = 0; 1346 } else { 1347 // Block-end margin never causes us to create continuations, so we 1348 // don't need to worry about whether it fits in its entirety. 1349 blockDirExtras.BStart(wm) += 1350 aReflowInput.ComputedLogicalMargin(wm).BStart(wm); 1351 } 1352 1353 if (effectiveContentBoxBSize + blockDirExtras.BStartEnd(wm) <= 1354 aReflowInput.AvailableBSize()) { 1355 restoreReflowInputAvailBSize.emplace(aReflowInput.AvailableBSize()); 1356 const_cast<ReflowInput&>(aReflowInput) 1357 .SetAvailableBSize(NS_UNCONSTRAINEDSIZE); 1358 } 1359 } 1360 1361 if (IsFrameTreeTooDeep(aReflowInput, aMetrics, aStatus)) { 1362 return; 1363 } 1364 1365 // OK, some lines may be reflowed. Blow away any saved line cursor 1366 // because we may invalidate the nondecreasing 1367 // overflowArea.InkOverflow().y/yMost invariant, and we may even 1368 // delete the line with the line cursor. 1369 ClearLineCursors(); 1370 1371 // See comment below about oldSize. Use *only* for the 1372 // abs-pos-containing-block-size-change optimization! 1373 nsSize oldSize = GetSize(); 1374 1375 // Should we create a float manager? 1376 nsAutoFloatManager autoFloatManager(const_cast<ReflowInput&>(aReflowInput)); 1377 1378 // XXXldb If we start storing the float manager in the frame rather 1379 // than keeping it around only during reflow then we should create it 1380 // only when there are actually floats to manage. Otherwise things 1381 // like tables will gain significant bloat. 1382 // 1383 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1931286: 1384 // if we're a reflow root and no float manager is provided by the caller 1385 // in aReflowInput, we'd normally expect the block to be a BFC and so 1386 // BlockNeedsFloatManager will return true. But sometimes the block may 1387 // have lost its BFC-ness since it was recorded as a dirty reflow root 1388 // but before the reflow actually happens. Creating a float manager here 1389 // avoids crashing, but may not be entirely correct in such a case. 1390 bool needFloatManager = 1391 !aReflowInput.mFloatManager || nsBlockFrame::BlockNeedsFloatManager(this); 1392 if (needFloatManager) { 1393 autoFloatManager.CreateFloatManager(aPresContext); 1394 } 1395 1396 if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) && 1397 PresContext()->BidiEnabled()) { 1398 static_cast<nsBlockFrame*>(FirstContinuation())->ResolveBidi(); 1399 } 1400 1401 // Whether to apply text-wrap: balance behavior. 1402 bool tryBalance = 1403 StyleText()->mTextWrapStyle == StyleTextWrapStyle::Balance && 1404 !GetPrevContinuation(); 1405 1406 // Struct used to hold the "target" number of lines or clamp position to 1407 // maintain when doing text-wrap: balance. 1408 struct BalanceTarget { 1409 // If line-clamp is in effect, mContent and mOffset indicate the starting 1410 // position of the first line after the clamp limit, and mBlockCoord is the 1411 // block-axis offset of its position. 1412 // If line-clamp is not in use, mContent is null, mOffset is the total 1413 // number of lines that the block must contain, and mBlockCoord is its end 1414 // edge in the block direction. 1415 nsIContent* mContent = nullptr; 1416 int32_t mOffset = -1; 1417 nscoord mBlockCoord = 0; 1418 1419 bool operator==(const BalanceTarget& aOther) const = default; 1420 bool operator!=(const BalanceTarget& aOther) const = default; 1421 }; 1422 1423 BalanceTarget balanceTarget; 1424 1425 // Helpers for text-wrap: balance implementation: 1426 1427 // Count the number of inline lines in the mLines list, but return -1 (to 1428 // suppress balancing) instead if the count is going to exceed aLimit. 1429 auto countLinesUpTo = [&](int32_t aLimit) -> int32_t { 1430 int32_t n = 0; 1431 for (auto iter = mLines.begin(); iter != mLines.end(); ++iter) { 1432 // Block lines are ignored as they do not participate in balancing. 1433 if (iter->IsInline() && ++n > aLimit) { 1434 return -1; 1435 } 1436 } 1437 return n; 1438 }; 1439 1440 // Return a BalanceTarget record representing the position at which line-clamp 1441 // will take effect for the current line list. Only to be used when there are 1442 // enough lines that the clamp will apply. 1443 auto getClampPosition = [&](uint32_t aClampCount) -> BalanceTarget { 1444 if (NS_WARN_IF(aClampCount >= mLines.size())) { 1445 return BalanceTarget{}; 1446 } 1447 auto iter = mLines.begin(); 1448 for (uint32_t i = 0; i < aClampCount; i++) { 1449 ++iter; 1450 } 1451 nsIFrame* firstChild = iter->mFirstChild; 1452 if (!firstChild) { 1453 return BalanceTarget{}; 1454 } 1455 nsIContent* content = firstChild->GetContent(); 1456 if (!content) { 1457 return BalanceTarget{}; 1458 } 1459 int32_t offset = 0; 1460 if (firstChild->IsTextFrame()) { 1461 auto* textFrame = static_cast<nsTextFrame*>(firstChild); 1462 offset = textFrame->GetContentOffset(); 1463 } 1464 return BalanceTarget{content, offset, iter.get()->BStart()}; 1465 }; 1466 1467 // "balancing" is implemented by shortening the effective inline-size of the 1468 // lines, so that content will tend to be pushed down to fill later lines of 1469 // the block. `balanceInset` is the current amount of "inset" to apply, and 1470 // `balanceStep` is the increment to adjust it by for the next iteration. 1471 nscoord balanceStep = 0; 1472 1473 // text-wrap: balance loop, executed only once if balancing is not required. 1474 nsReflowStatus reflowStatus; 1475 TrialReflowState trialState(consumedBSize, effectiveContentBoxBSize, 1476 needFloatManager); 1477 while (true) { 1478 // Save the initial floatManager state for repeated trial reflows. 1479 // We'll restore (and re-save) the initial state each time we repeat the 1480 // reflow. 1481 nsFloatManager::SavedState floatManagerState; 1482 aReflowInput.mFloatManager->PushState(&floatManagerState); 1483 1484 aMetrics = ReflowOutput(aMetrics.GetWritingMode()); 1485 reflowStatus = 1486 TrialReflow(aPresContext, aMetrics, aReflowInput, trialState); 1487 1488 // Do we need to start a `text-wrap: balance` iteration? 1489 if (tryBalance) { 1490 tryBalance = false; 1491 // Don't try to balance an incomplete block, or if we had to use an 1492 // overflow-wrap break position in the initial reflow. 1493 if (!reflowStatus.IsFullyComplete() || trialState.mUsedOverflowWrap) { 1494 break; 1495 } 1496 balanceTarget.mOffset = 1497 countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit()); 1498 if (balanceTarget.mOffset < 2) { 1499 // If there are less than 2 lines, or the number exceeds the limit, 1500 // no balancing is needed; just break from the balance loop. 1501 break; 1502 } 1503 balanceTarget.mBlockCoord = mLines.back()->BEnd(); 1504 // Initialize the amount of inset to try, and the iteration step size. 1505 balanceStep = aReflowInput.ComputedISize() / balanceTarget.mOffset; 1506 trialState.ResetForBalance(balanceStep); 1507 balanceStep /= 2; 1508 1509 // If -webkit-line-clamp is in effect, then we need to maintain the 1510 // content location at which clamping occurs, rather than the total 1511 // number of lines in the block. 1512 if (StaticPrefs::layout_css_text_wrap_balance_after_clamp_enabled() && 1513 IsLineClampRoot(this)) { 1514 uint32_t lineClampCount = aReflowInput.mStyleDisplay->mWebkitLineClamp; 1515 if (uint32_t(balanceTarget.mOffset) > lineClampCount) { 1516 auto t = getClampPosition(lineClampCount); 1517 if (t.mContent) { 1518 balanceTarget = t; 1519 } 1520 } 1521 } 1522 1523 // Restore initial floatManager state for a new trial with updated inset. 1524 aReflowInput.mFloatManager->PopState(&floatManagerState); 1525 continue; 1526 } 1527 1528 // Helper to determine whether the current trial succeeded (i.e. was able 1529 // to fit the content into the expected number of lines). 1530 auto trialSucceeded = [&]() -> bool { 1531 if (!reflowStatus.IsFullyComplete() || trialState.mUsedOverflowWrap) { 1532 return false; 1533 } 1534 if (balanceTarget.mContent) { 1535 auto t = getClampPosition(aReflowInput.mStyleDisplay->mWebkitLineClamp); 1536 return t == balanceTarget; 1537 } 1538 int32_t numLines = 1539 countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit()); 1540 return numLines == balanceTarget.mOffset && 1541 mLines.back()->BEnd() == balanceTarget.mBlockCoord; 1542 }; 1543 1544 // If we're in the process of a balance operation, check whether we've 1545 // inset by too much and either increase or reduce the inset for the next 1546 // iteration. 1547 if (balanceStep > 0) { 1548 if (trialSucceeded()) { 1549 trialState.ResetForBalance(balanceStep); 1550 } else { 1551 trialState.ResetForBalance(-balanceStep); 1552 } 1553 balanceStep /= 2; 1554 1555 aReflowInput.mFloatManager->PopState(&floatManagerState); 1556 continue; 1557 } 1558 1559 // If we were attempting to balance, check whether the final iteration was 1560 // successful, and if not, back up by one step. 1561 if (balanceTarget.mOffset >= 0) { 1562 if (!trialState.mInset || trialSucceeded()) { 1563 break; 1564 } 1565 trialState.ResetForBalance(-1); 1566 1567 aReflowInput.mFloatManager->PopState(&floatManagerState); 1568 continue; 1569 } 1570 1571 // If we reach here, no balancing was required, so just exit; we don't 1572 // reset (pop) the floatManager state because this is the reflow we're 1573 // going to keep. So the saved state is just dropped. 1574 break; 1575 } // End of text-wrap: balance retry loop 1576 1577 // If the block direction is right-to-left, we need to update the bounds of 1578 // lines that were placed relative to mContainerSize during reflow, as 1579 // we typically do not know the true container size until we've reflowed all 1580 // its children. So we use a dummy mContainerSize during reflow (see 1581 // BlockReflowState's constructor) and then fix up the positions of the 1582 // lines here, once the final block size is known. 1583 // 1584 // Note that writing-mode:vertical-rl is the only case where the block 1585 // logical direction progresses in a negative physical direction, and 1586 // therefore block-dir coordinate conversion depends on knowing the width 1587 // of the coordinate space in order to translate between the logical and 1588 // physical origins. 1589 if (aReflowInput.GetWritingMode().IsVerticalRL()) { 1590 nsSize containerSize = aMetrics.PhysicalSize(); 1591 nscoord deltaX = containerSize.width - trialState.mContainerWidth; 1592 if (deltaX != 0) { 1593 // We compute our lines and markers' overflow areas later in 1594 // ComputeOverflowAreas(), so we don't need to adjust their overflow areas 1595 // here. 1596 const nsPoint physicalDelta(deltaX, 0); 1597 for (auto& line : Lines()) { 1598 UpdateLineContainerSize(&line, containerSize); 1599 } 1600 trialState.mFcBounds.Clear(); 1601 if (nsFrameList* floats = GetFloats()) { 1602 for (nsIFrame* f : *floats) { 1603 f->MovePositionBy(physicalDelta); 1604 ConsiderChildOverflow(trialState.mFcBounds, f); 1605 } 1606 } 1607 if (nsFrameList* markerList = GetOutsideMarkerList()) { 1608 for (nsIFrame* f : *markerList) { 1609 f->MovePositionBy(physicalDelta); 1610 } 1611 } 1612 if (nsFrameList* overflowContainers = GetOverflowContainers()) { 1613 trialState.mOcBounds.Clear(); 1614 for (nsIFrame* f : *overflowContainers) { 1615 f->MovePositionBy(physicalDelta); 1616 ConsiderChildOverflow(trialState.mOcBounds, f); 1617 } 1618 } 1619 } 1620 } 1621 1622 aMetrics.SetOverflowAreasToDesiredBounds(); 1623 ComputeOverflowAreas(aMetrics.mOverflowAreas, aReflowInput.mStyleDisplay); 1624 // Factor overflow container child bounds into the overflow area 1625 aMetrics.mOverflowAreas.UnionWith(trialState.mOcBounds); 1626 // Factor pushed float child bounds into the overflow area 1627 aMetrics.mOverflowAreas.UnionWith(trialState.mFcBounds); 1628 1629 // Let the absolutely positioned container reflow any absolutely positioned 1630 // child frames that need to be reflowed, e.g., elements with a percentage 1631 // based width/height 1632 // We want to do this under either of two conditions: 1633 // 1. If we didn't do the incremental reflow above. 1634 // 2. If our size changed. 1635 // Even though it's the padding edge that's the containing block, we 1636 // can use our rect (the border edge) since if the border style 1637 // changed, the reflow would have been targeted at us so we'd satisfy 1638 // condition 1. 1639 // XXX checking oldSize is bogus, there are various reasons we might have 1640 // reflowed but our size might not have been changed to what we 1641 // asked for (e.g., we ended up being pushed to a new page) 1642 // When WillReflowAgainForClearance is true, we will reflow again without 1643 // resetting the size. Because of this, we must not reflow our abs-pos 1644 // children in that situation --- what we think is our "new size" will not be 1645 // our real new size. This also happens to be more efficient. 1646 auto* absoluteContainer = GetAbsoluteContainingBlock(); 1647 if (absoluteContainer && absoluteContainer->PrepareAbsoluteFrames(this)) { 1648 bool haveInterrupt = aPresContext->HasPendingInterrupt(); 1649 if (aReflowInput.WillReflowAgainForClearance() || haveInterrupt) { 1650 // Make sure that when we reflow again we'll actually reflow all the abs 1651 // pos frames that might conceivably depend on our size (or all of them, 1652 // if we're dirty right now and interrupted; in that case we also need 1653 // to mark them all with NS_FRAME_IS_DIRTY). Sadly, we can't do much 1654 // better than that, because we don't really know what our size will be, 1655 // and it might in fact not change on the followup reflow! 1656 if (haveInterrupt && HasAnyStateBits(NS_FRAME_IS_DIRTY)) { 1657 absoluteContainer->MarkAllFramesDirty(); 1658 } else { 1659 absoluteContainer->MarkSizeDependentFramesDirty(); 1660 } 1661 if (haveInterrupt) { 1662 // We're not going to reflow absolute frames; make sure to account for 1663 // their existing overflow areas, which is usually a side effect of this 1664 // reflow. 1665 // 1666 // TODO(emilio): AbsoluteContainingBlock::Reflow already checks for 1667 // interrupt, can we just rely on it and unconditionally take the else 1668 // branch below? That's a bit more subtle / risky, since I don't see 1669 // what would reflow them in that case if they depended on our size. 1670 for (nsIFrame* kid : absoluteContainer->GetChildList()) { 1671 ConsiderChildOverflow(aMetrics.mOverflowAreas, kid); 1672 } 1673 } 1674 } else { 1675 // Mark frames that depend on changes we just made to this frame as dirty: 1676 // Now we can assume that the padding edge hasn't moved. 1677 // We need to reflow the absolutes if one of them depends on 1678 // its placeholder position, or the containing block size in a 1679 // direction in which the containing block size might have 1680 // changed. 1681 1682 // XXX "width" and "height" in this block will become ISize and BSize 1683 // when AbsoluteContainingBlock is logicalized 1684 bool cbWidthChanged = aMetrics.Width() != oldSize.width; 1685 bool isRoot = !GetContent()->GetParent(); 1686 // If isRoot and we have auto height, then we are the initial 1687 // containing block and the containing block height is the 1688 // viewport height, which can't change during incremental 1689 // reflow. 1690 bool cbHeightChanged = 1691 !(isRoot && NS_UNCONSTRAINEDSIZE == aReflowInput.ComputedHeight()) && 1692 aMetrics.Height() != oldSize.height; 1693 1694 AbsPosReflowFlags flags{AbsPosReflowFlag::AllowFragmentation}; 1695 if (cbWidthChanged) { 1696 flags += AbsPosReflowFlag::CBWidthChanged; 1697 } 1698 if (cbHeightChanged) { 1699 flags += AbsPosReflowFlag::CBHeightChanged; 1700 } 1701 // Setup the line cursor here to optimize line searching for 1702 // calculating hypothetical position of absolutely-positioned 1703 // frames. 1704 SetupLineCursorForQuery(); 1705 1706 LogicalRect cbRect(wm, LogicalPoint(wm), aMetrics.Size(wm)); 1707 cbRect.Deflate(wm, aReflowInput.ComputedLogicalBorder(wm).ApplySkipSides( 1708 PreReflowBlockLevelLogicalSkipSides())); 1709 absoluteContainer->Reflow( 1710 this, aPresContext, aReflowInput, reflowStatus, 1711 cbRect.GetPhysicalRect(wm, aMetrics.PhysicalSize()), flags, 1712 &aMetrics.mOverflowAreas); 1713 } 1714 } 1715 1716 FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay); 1717 1718 aStatus = reflowStatus; 1719 1720 #ifdef DEBUG 1721 // Between when we drain pushed floats and when we complete reflow, 1722 // we're allowed to have multiple continuations of the same float on 1723 // our floats list, since a first-in-flow might get pushed to a later 1724 // continuation of its containing block. But it's not permitted 1725 // outside that time. 1726 nsLayoutUtils::AssertNoDuplicateContinuations( 1727 this, GetChildList(FrameChildListID::Float)); 1728 1729 if (gNoisyReflow) { 1730 IndentBy(stdout, gNoiseIndent); 1731 fmt::print(FMT_STRING("{}: status={} metrics={} carriedMargin={}"), 1732 ListTag().get(), ToString(aStatus), ToString(aMetrics.Size(wm)), 1733 aMetrics.mCarriedOutBEndMargin.Get()); 1734 if (HasOverflowAreas()) { 1735 fmt::print(FMT_STRING(" overflow-ink={} overflow-scr={}"), 1736 ToString(aMetrics.InkOverflow()), 1737 ToString(aMetrics.ScrollableOverflow())); 1738 } 1739 printf("\n"); 1740 } 1741 1742 if (gLameReflowMetrics) { 1743 PRTime end = PR_Now(); 1744 1745 int32_t ectc = nsLineBox::GetCtorCount(); 1746 int32_t numLines = mLines.size(); 1747 if (!numLines) { 1748 numLines = 1; 1749 } 1750 PRTime delta, perLineDelta, lines; 1751 lines = int64_t(numLines); 1752 delta = end - start; 1753 perLineDelta = delta / lines; 1754 1755 ListTag(stdout); 1756 char buf[400]; 1757 SprintfLiteral(buf, 1758 ": %" PRId64 " elapsed (%" PRId64 1759 " per line) (%d lines; %d new lines)", 1760 delta, perLineDelta, numLines, ectc - ctc); 1761 printf("%s\n", buf); 1762 } 1763 #endif 1764 } 1765 1766 nsReflowStatus nsBlockFrame::TrialReflow(nsPresContext* aPresContext, 1767 ReflowOutput& aMetrics, 1768 const ReflowInput& aReflowInput, 1769 TrialReflowState& aTrialState) { 1770 #ifdef DEBUG 1771 // Between when we drain pushed floats and when we complete reflow, 1772 // we're allowed to have multiple continuations of the same float on 1773 // our floats list, since a first-in-flow might get pushed to a later 1774 // continuation of its containing block. But it's not permitted 1775 // outside that time. 1776 nsLayoutUtils::AssertNoDuplicateContinuations( 1777 this, GetChildList(FrameChildListID::Float)); 1778 #endif 1779 1780 // ALWAYS drain overflow. We never want to leave the previnflow's 1781 // overflow lines hanging around; block reflow depends on the 1782 // overflow line lists being cleared out between reflow passes. 1783 DrainOverflowLines(); 1784 1785 // Clear any existing -webkit-line-clamp ellipsis if we're reflowing the 1786 // line-clamp root. 1787 if (IsLineClampRoot(this)) { 1788 ClearLineClampEllipsis(); 1789 } 1790 1791 bool blockStartMarginRoot, blockEndMarginRoot; 1792 IsMarginRoot(&blockStartMarginRoot, &blockEndMarginRoot); 1793 1794 BlockReflowState state(aReflowInput, aPresContext, this, blockStartMarginRoot, 1795 blockEndMarginRoot, aTrialState.mNeedFloatManager, 1796 aTrialState.mConsumedBSize, 1797 aTrialState.mEffectiveContentBoxBSize, 1798 aTrialState.mInset); 1799 1800 // Handle paginated overflow (see nsContainerFrame.h) 1801 nsReflowStatus ocStatus; 1802 if (GetPrevInFlow()) { 1803 ReflowOverflowContainerChildren( 1804 aPresContext, aReflowInput, aTrialState.mOcBounds, 1805 ReflowChildFlags::Default, ocStatus, DefaultChildFrameMerge, 1806 Some(state.ContainerSize())); 1807 } 1808 1809 // Now that we're done cleaning up our overflow container lists, we can 1810 // give |state| its nsOverflowContinuationTracker. 1811 nsOverflowContinuationTracker tracker(this, false); 1812 state.mOverflowTracker = &tracker; 1813 1814 // Drain & handle pushed floats 1815 DrainPushedFloats(); 1816 ReflowPushedFloats(state, aTrialState.mFcBounds); 1817 1818 // If we're not dirty (which means we'll mark everything dirty later) 1819 // and our inline-size has changed, mark the lines dirty that we need to 1820 // mark dirty for a resize reflow. 1821 if (!HasAnyStateBits(NS_FRAME_IS_DIRTY) && aReflowInput.IsIResize()) { 1822 PrepareResizeReflow(state); 1823 } 1824 1825 // The same for percentage text-indent, except conditioned on the 1826 // parent resizing. 1827 if (!HasAnyStateBits(NS_FRAME_IS_DIRTY) && aReflowInput.mCBReflowInput && 1828 aReflowInput.mCBReflowInput->IsIResize() && 1829 StyleText()->mTextIndent.length.HasPercent() && !mLines.empty()) { 1830 mLines.front()->MarkDirty(); 1831 } 1832 1833 // For text-wrap:balance trials, we need to reflow all the inline lines even 1834 // if they're not all "dirty", but we don't need to reflow any block lines. 1835 if (aTrialState.mBalancing) { 1836 MarkAllInlineLinesDirty(this); 1837 } else { 1838 LazyMarkLinesDirty(); 1839 } 1840 1841 // Now reflow... 1842 aTrialState.mUsedOverflowWrap = ReflowDirtyLines(state); 1843 1844 // If we have a next-in-flow, and that next-in-flow has pushed floats from 1845 // this frame from a previous iteration of reflow, then we should not return 1846 // a status with IsFullyComplete() equals to true, since we actually have 1847 // overflow, it's just already been handled. 1848 1849 // NOTE: This really shouldn't happen, since we _should_ pull back our floats 1850 // and reflow them, but just in case it does, this is a safety precaution so 1851 // we don't end up with a placeholder pointing to frames that have already 1852 // been deleted as part of removing our next-in-flow. 1853 if (state.mReflowStatus.IsFullyComplete()) { 1854 nsBlockFrame* nif = static_cast<nsBlockFrame*>(GetNextInFlow()); 1855 while (nif) { 1856 if (nif->HasPushedFloatsFromPrevContinuation()) { 1857 if (nif->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { 1858 state.mReflowStatus.SetOverflowIncomplete(); 1859 } else { 1860 state.mReflowStatus.SetIncomplete(); 1861 } 1862 break; 1863 } 1864 1865 nif = static_cast<nsBlockFrame*>(nif->GetNextInFlow()); 1866 } 1867 } 1868 1869 state.mReflowStatus.MergeCompletionStatusFrom(ocStatus); 1870 1871 // If we end in a BR with clear and affected floats continue, 1872 // we need to continue, too. 1873 if (NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize() && 1874 state.mReflowStatus.IsComplete() && 1875 state.FloatManager()->ClearContinues(FindTrailingClear())) { 1876 state.mReflowStatus.SetIncomplete(); 1877 } 1878 1879 if (!state.mReflowStatus.IsFullyComplete()) { 1880 if (HasOverflowLines() || HasPushedFloats()) { 1881 state.mReflowStatus.SetNextInFlowNeedsReflow(); 1882 } 1883 } 1884 1885 // Place the ::marker's frame if it is placed next to a block child. 1886 // 1887 // According to the CSS2 spec, section 12.6.1, the ::marker's box 1888 // participates in the height calculation of the list-item box's 1889 // first line box. 1890 // 1891 // There are exactly two places a ::marker can be placed: near the 1892 // first or second line. It's only placed on the second line in a 1893 // rare case: an empty first line followed by a second line that 1894 // contains a block (example: <LI>\n<P>... ). This is where 1895 // the second case can happen. 1896 nsIFrame* outsideMarker = GetOutsideMarker(); 1897 if (outsideMarker && !mLines.empty() && 1898 (mLines.front()->IsBlock() || 1899 (0 == mLines.front()->BSize() && mLines.front() != mLines.back() && 1900 mLines.begin().next()->IsBlock()))) { 1901 // Reflow the ::marker's frame. 1902 ReflowOutput reflowOutput(aReflowInput); 1903 // XXX Use the entire line when we fix bug 25888. 1904 nsLayoutUtils::LinePosition position; 1905 WritingMode wm = aReflowInput.GetWritingMode(); 1906 bool havePosition = 1907 nsLayoutUtils::GetFirstLinePosition(wm, this, &position); 1908 nscoord lineBStart = 1909 havePosition ? position.mBStart 1910 : aReflowInput.ComputedLogicalBorderPadding(wm).BStart(wm); 1911 ReflowOutsideMarker(outsideMarker, state, reflowOutput, lineBStart); 1912 NS_ASSERTION(!MarkerIsEmpty(outsideMarker) || reflowOutput.BSize(wm) == 0, 1913 "empty ::marker frame took up space"); 1914 1915 if (havePosition && !MarkerIsEmpty(outsideMarker)) { 1916 // We have some lines to align the ::marker with. 1917 1918 // Doing the alignment using the baseline will also cater for 1919 // ::markers that are placed next to a child block (bug 92896) 1920 1921 // Tall ::markers won't look particularly nice here... 1922 LogicalRect bbox = 1923 outsideMarker->GetLogicalRect(wm, reflowOutput.PhysicalSize()); 1924 const auto baselineGroup = BaselineSharingGroup::First; 1925 Maybe<nscoord> result; 1926 if (MOZ_LIKELY(!wm.IsOrthogonalTo(outsideMarker->GetWritingMode()))) { 1927 result = outsideMarker->GetNaturalBaselineBOffset( 1928 wm, baselineGroup, BaselineExportContext::LineLayout); 1929 } 1930 const auto markerBaseline = 1931 result.valueOrFrom([bbox, wm, outsideMarker]() { 1932 return bbox.BSize(wm) + 1933 outsideMarker->GetLogicalUsedMargin(wm).BEnd(wm); 1934 }); 1935 bbox.BStart(wm) = position.mBaseline - markerBaseline; 1936 outsideMarker->SetRect(wm, bbox, reflowOutput.PhysicalSize()); 1937 } 1938 // Otherwise just leave the ::marker where it is, up against our 1939 // block-start padding. 1940 } 1941 1942 CheckFloats(state); 1943 1944 // Compute our final size (for this trial layout) 1945 aTrialState.mBlockEndEdgeOfChildren = 1946 ComputeFinalSize(aReflowInput, state, aMetrics); 1947 aTrialState.mContainerWidth = state.ContainerSize().width; 1948 1949 // Align content 1950 AlignContent(state, aMetrics, aTrialState.mBlockEndEdgeOfChildren); 1951 1952 return state.mReflowStatus; 1953 } 1954 1955 bool nsBlockFrame::CheckForCollapsedBEndMarginFromClearanceLine() { 1956 for (auto& line : Reversed(Lines())) { 1957 if (0 != line.BSize() || !line.CachedIsEmpty()) { 1958 return false; 1959 } 1960 if (line.HasClearance()) { 1961 return true; 1962 } 1963 } 1964 return false; 1965 } 1966 1967 std::pair<nsBlockFrame*, nsLineBox*> FindLineClampTarget( 1968 nsBlockFrame* const aRootFrame, const nsBlockFrame* const aStopAtFrame, 1969 StyleLineClamp aLineNumber) { 1970 MOZ_ASSERT(aLineNumber > 0); 1971 1972 nsLineBox* targetLine = nullptr; 1973 nsBlockFrame* targetFrame = nullptr; 1974 bool foundFollowingLine = false; 1975 1976 LineClampLineIterator iter(aRootFrame, aStopAtFrame); 1977 1978 while (nsLineBox* line = iter.GetCurrentLine()) { 1979 // Don't count a line that only has collapsible white space (as might exist 1980 // after calling e.g. getBoxQuads). 1981 if (line->IsEmpty()) { 1982 iter.Next(); 1983 continue; 1984 } 1985 1986 if (aLineNumber == 0) { 1987 // We already previously found our target line, and now we have 1988 // confirmed that there is another line after it. 1989 foundFollowingLine = true; 1990 break; 1991 } 1992 1993 if (--aLineNumber == 0) { 1994 // This is our target line. Continue looping to confirm that we 1995 // have another line after us. 1996 targetLine = line; 1997 targetFrame = iter.GetCurrentFrame(); 1998 } 1999 2000 iter.Next(); 2001 } 2002 2003 if (!foundFollowingLine) { 2004 MOZ_ASSERT(!aRootFrame->HasLineClampEllipsis(), 2005 "should have been removed earlier"); 2006 return std::pair(nullptr, nullptr); 2007 } 2008 2009 MOZ_ASSERT(targetLine); 2010 MOZ_ASSERT(targetFrame); 2011 2012 // If targetFrame is not the same as the line-clamp root, any ellipsis on the 2013 // root should have been previously cleared. 2014 MOZ_ASSERT(targetFrame == aRootFrame || !aRootFrame->HasLineClampEllipsis(), 2015 "line-clamp target mismatch"); 2016 2017 return std::pair(targetFrame, targetLine); 2018 } 2019 2020 nscoord nsBlockFrame::ApplyLineClamp(nscoord aContentBlockEndEdge) { 2021 auto* root = GetLineClampRoot(); 2022 if (!root) { 2023 return aContentBlockEndEdge; 2024 } 2025 2026 auto lineClamp = root->StyleDisplay()->mWebkitLineClamp; 2027 auto [target, line] = FindLineClampTarget(root, this, lineClamp); 2028 if (!line) { 2029 // The number of lines did not exceed the -webkit-line-clamp value. 2030 return aContentBlockEndEdge; 2031 } 2032 2033 // Mark the line as having an ellipsis so that TextOverflow will render it. 2034 line->SetHasLineClampEllipsis(); 2035 target->SetHasLineClampEllipsis(true); 2036 2037 // Translate the b-end edge of the line up to aFrame's space. 2038 nscoord edge = line->BEnd(); 2039 for (nsIFrame* f = target; f; f = f->GetParent()) { 2040 MOZ_ASSERT(f->IsBlockFrameOrSubclass(), 2041 "GetAsLineClampDescendant guarantees this"); 2042 if (f != target) { 2043 static_cast<nsBlockFrame*>(f)->SetHasLineClampEllipsisDescendant(true); 2044 } 2045 if (f == this) { 2046 break; 2047 } 2048 if (f == root) { 2049 // The clamped line is not in our subtree. 2050 return aContentBlockEndEdge; 2051 } 2052 const auto wm = f->GetWritingMode(); 2053 const nsSize parentSize = f->GetParent()->GetSize(); 2054 edge = f->GetLogicalRect(parentSize).BEnd(wm); 2055 } 2056 2057 return edge; 2058 } 2059 2060 nscoord nsBlockFrame::ComputeFinalSize(const ReflowInput& aReflowInput, 2061 BlockReflowState& aState, 2062 ReflowOutput& aMetrics) { 2063 WritingMode wm = aState.mReflowInput.GetWritingMode(); 2064 const LogicalMargin& borderPadding = aState.BorderPadding(); 2065 #ifdef NOISY_FINAL_SIZE 2066 ListTag(stdout); 2067 printf(": mBCoord=%d mIsBEndMarginRoot=%s mPrevBEndMargin=%d bp=%d,%d\n", 2068 aState.mBCoord, aState.mFlags.mIsBEndMarginRoot ? "yes" : "no", 2069 aState.mPrevBEndMargin.get(), borderPadding.BStart(wm), 2070 borderPadding.BEnd(wm)); 2071 #endif 2072 2073 // Compute final inline size 2074 LogicalSize finalSize(wm); 2075 finalSize.ISize(wm) = 2076 NSCoordSaturatingAdd(NSCoordSaturatingAdd(borderPadding.IStart(wm), 2077 aReflowInput.ComputedISize()), 2078 borderPadding.IEnd(wm)); 2079 2080 // Return block-end margin information 2081 // rbs says he hit this assertion occasionally (see bug 86947), so 2082 // just set the margin to zero and we'll figure out why later 2083 // NS_ASSERTION(aMetrics.mCarriedOutBEndMargin.IsZero(), 2084 // "someone else set the margin"); 2085 nscoord nonCarriedOutBDirMargin = 0; 2086 if (!aState.mFlags.mIsBEndMarginRoot) { 2087 // Apply rule from CSS 2.1 section 8.3.1. If we have some empty 2088 // line with clearance and a non-zero block-start margin and all 2089 // subsequent lines are empty, then we do not allow our children's 2090 // carried out block-end margin to be carried out of us and collapse 2091 // with our own block-end margin. 2092 if (CheckForCollapsedBEndMarginFromClearanceLine()) { 2093 // Convert the children's carried out margin to something that 2094 // we will include in our height 2095 nonCarriedOutBDirMargin = aState.mPrevBEndMargin.Get(); 2096 aState.mPrevBEndMargin.Zero(); 2097 } 2098 aMetrics.mCarriedOutBEndMargin = aState.mPrevBEndMargin; 2099 } else { 2100 aMetrics.mCarriedOutBEndMargin.Zero(); 2101 } 2102 2103 nscoord blockEndEdgeOfChildren = aState.mBCoord + nonCarriedOutBDirMargin; 2104 // Shrink wrap our height around our contents. 2105 if (aState.mFlags.mIsBEndMarginRoot || 2106 NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) { 2107 // When we are a block-end-margin root make sure that our last 2108 // child's block-end margin is fully applied. We also do this when 2109 // we have a computed height, since in that case the carried out 2110 // margin is not going to be applied anywhere, so we should note it 2111 // here to be included in the overflow area. 2112 // Apply the margin only if there's space for it. 2113 if (blockEndEdgeOfChildren < aState.mReflowInput.AvailableBSize()) { 2114 // Truncate block-end margin if it doesn't fit to our available BSize. 2115 blockEndEdgeOfChildren = 2116 std::min(blockEndEdgeOfChildren + aState.mPrevBEndMargin.Get(), 2117 aState.mReflowInput.AvailableBSize()); 2118 } 2119 } 2120 if (aState.mFlags.mBlockNeedsFloatManager) { 2121 // Include the float manager's state to properly account for the 2122 // block-end margin of any floated elements; e.g., inside a table cell. 2123 // 2124 // Note: The block coordinate returned by ClearFloats is always greater than 2125 // or equal to blockEndEdgeOfChildren. 2126 std::tie(blockEndEdgeOfChildren, std::ignore) = 2127 aState.ClearFloats(blockEndEdgeOfChildren, UsedClear::Both); 2128 } 2129 2130 // undo cached alignment shift for sizing purposes 2131 // (we used shifted positions because the float manager uses them) 2132 blockEndEdgeOfChildren -= aState.mAlignContentShift; 2133 aState.UndoAlignContentShift(); 2134 2135 if (NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) { 2136 // Note: We don't use blockEndEdgeOfChildren because it includes the 2137 // previous margin. 2138 const nscoord contentBSizeWithBStartBP = 2139 aState.mBCoord + nonCarriedOutBDirMargin; 2140 2141 // We don't care about ApplyLineClamp's return value (the line-clamped 2142 // content BSize) in this explicit-BSize codepath, but we do still need to 2143 // call ApplyLineClamp for ellipsis markers to be placed as-needed. 2144 ApplyLineClamp(contentBSizeWithBStartBP); 2145 2146 finalSize.BSize(wm) = ComputeFinalBSize(aState, contentBSizeWithBStartBP); 2147 2148 // If the content block-size is larger than the effective computed 2149 // block-size, we extend the block-size to contain all the content. 2150 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum 2151 if (aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis()) { 2152 // Note: finalSize.BSize(wm) is the border-box size, so we compare it with 2153 // the content's block-size plus our border and padding.. 2154 finalSize.BSize(wm) = 2155 std::max(finalSize.BSize(wm), 2156 contentBSizeWithBStartBP + borderPadding.BEnd(wm)); 2157 2158 // The size should be capped by its maximum block size. 2159 if (aReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) { 2160 finalSize.BSize(wm) = 2161 std::min(finalSize.BSize(wm), aReflowInput.ComputedMaxBSize() + 2162 borderPadding.BStartEnd(wm)); 2163 } 2164 } 2165 2166 // Don't carry out a block-end margin when our BSize is fixed. 2167 // 2168 // Note: this also includes the case that aReflowInput.ComputedBSize() is 2169 // calculated from aspect-ratio. i.e. Don't carry out block margin-end if it 2170 // is replaced by the block size from aspect-ratio and inline size. 2171 aMetrics.mCarriedOutBEndMargin.Zero(); 2172 } else if (Maybe<nscoord> containBSize = ContainIntrinsicBSize( 2173 IsComboboxControlFrame() ? aReflowInput.GetLineHeight() : 0)) { 2174 // If we're size-containing in block axis and we don't have a specified 2175 // block size, then our final size should actually be computed from only 2176 // our border, padding and contain-intrinsic-block-size, ignoring the 2177 // actual contents. Hence this case is a simplified version of the case 2178 // below. 2179 nscoord contentBSize = *containBSize; 2180 nscoord autoBSize = 2181 aReflowInput.ApplyMinMaxBSize(contentBSize, aState.mConsumedBSize); 2182 aMetrics.mCarriedOutBEndMargin.Zero(); 2183 autoBSize += borderPadding.BStartEnd(wm); 2184 finalSize.BSize(wm) = autoBSize; 2185 } else if (aState.mReflowStatus.IsInlineBreakBefore()) { 2186 // Our parent is expected to push this frame to the next page/column so 2187 // what size we set here doesn't really matter. 2188 finalSize.BSize(wm) = aReflowInput.AvailableBSize(); 2189 } else if (aState.mReflowStatus.IsComplete()) { 2190 const nscoord lineClampedContentBlockEndEdge = 2191 ApplyLineClamp(blockEndEdgeOfChildren); 2192 2193 const nscoord bpBStart = borderPadding.BStart(wm); 2194 const nscoord contentBSize = blockEndEdgeOfChildren - bpBStart; 2195 const nscoord lineClampedContentBSize = 2196 lineClampedContentBlockEndEdge - bpBStart; 2197 2198 const nscoord autoBSize = aReflowInput.ApplyMinMaxBSize( 2199 lineClampedContentBSize, aState.mConsumedBSize); 2200 if (autoBSize != contentBSize) { 2201 // Our min-block-size, max-block-size, or -webkit-line-clamp value made 2202 // our bsize change. Don't carry out our kids' block-end margins. 2203 aMetrics.mCarriedOutBEndMargin.Zero(); 2204 } 2205 nscoord bSize = autoBSize + borderPadding.BStartEnd(wm); 2206 if (MOZ_UNLIKELY(autoBSize > contentBSize && 2207 bSize > aReflowInput.AvailableBSize() && 2208 aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE)) { 2209 // Applying `min-size` made us overflow our available size. 2210 // Clamp it and report that we're Incomplete, or BreakBefore if we have 2211 // 'break-inside: avoid' that is applicable. 2212 bSize = aReflowInput.AvailableBSize(); 2213 if (ShouldAvoidBreakInside(aReflowInput)) { 2214 aState.mReflowStatus.SetInlineLineBreakBeforeAndReset(); 2215 } else { 2216 aState.mReflowStatus.SetIncomplete(); 2217 } 2218 } 2219 finalSize.BSize(wm) = bSize; 2220 } else { 2221 NS_ASSERTION(aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE, 2222 "Shouldn't be incomplete if availableBSize is UNCONSTRAINED."); 2223 nscoord bSize = std::max(aState.mBCoord, aReflowInput.AvailableBSize()); 2224 if (aReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) { 2225 // This should never happen, but it does. See bug 414255 2226 bSize = aState.mBCoord; 2227 } 2228 const nscoord maxBSize = aReflowInput.ComputedMaxBSize(); 2229 if (maxBSize != NS_UNCONSTRAINEDSIZE && 2230 aState.mConsumedBSize + bSize - borderPadding.BStart(wm) > maxBSize) { 2231 // Compute this fragment's block-size, with the max-block-size 2232 // constraint taken into consideration. 2233 const nscoord clampedBSizeWithoutEndBP = 2234 std::max(0, maxBSize - aState.mConsumedBSize) + 2235 borderPadding.BStart(wm); 2236 const nscoord clampedBSize = 2237 clampedBSizeWithoutEndBP + borderPadding.BEnd(wm); 2238 if (clampedBSize <= aReflowInput.AvailableBSize()) { 2239 // We actually fit after applying `max-size` so we should be 2240 // Overflow-Incomplete instead. 2241 bSize = clampedBSize; 2242 aState.mReflowStatus.SetOverflowIncomplete(); 2243 } else { 2244 // We cannot fit after applying `max-size` with our block-end BP, so 2245 // we should draw it in our next continuation. 2246 bSize = clampedBSizeWithoutEndBP; 2247 } 2248 } 2249 finalSize.BSize(wm) = bSize; 2250 } 2251 2252 if (IsTrueOverflowContainer()) { 2253 if (aState.mReflowStatus.IsIncomplete()) { 2254 // Overflow containers can only be overflow complete. 2255 // Note that auto height overflow containers have no normal children 2256 NS_ASSERTION(finalSize.BSize(wm) == 0, 2257 "overflow containers must be zero-block-size"); 2258 aState.mReflowStatus.SetOverflowIncomplete(); 2259 } 2260 } else if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE && 2261 !aState.mReflowStatus.IsInlineBreakBefore() && 2262 aState.mReflowStatus.IsComplete()) { 2263 // Currently only used for grid items, but could be used in other contexts. 2264 // The FragStretchBSizeProperty is our expected non-fragmented block-size 2265 // we should stretch to (for align-self:stretch etc). In some fragmentation 2266 // cases though, the last fragment (this frame since we're complete), needs 2267 // to have extra size applied because earlier fragments consumed too much of 2268 // our computed size due to overflowing their containing block. (E.g. this 2269 // ensures we fill the last row when a multi-row grid item is fragmented). 2270 bool found; 2271 nscoord bSize = GetProperty(FragStretchBSizeProperty(), &found); 2272 if (found) { 2273 finalSize.BSize(wm) = std::max(bSize, finalSize.BSize(wm)); 2274 } 2275 } 2276 2277 // Clamp the content size to fit within the margin-box clamp size, if any. 2278 if (MOZ_UNLIKELY(aReflowInput.mComputeSizeFlags.contains( 2279 ComputeSizeFlag::BClampMarginBoxMinSize)) && 2280 aState.mReflowStatus.IsComplete()) { 2281 bool found; 2282 nscoord cbSize = GetProperty(BClampMarginBoxMinSizeProperty(), &found); 2283 if (found) { 2284 auto marginBoxBSize = 2285 finalSize.BSize(wm) + 2286 aReflowInput.ComputedLogicalMargin(wm).BStartEnd(wm); 2287 auto overflow = marginBoxBSize - cbSize; 2288 if (overflow > 0) { 2289 auto contentBSize = finalSize.BSize(wm) - borderPadding.BStartEnd(wm); 2290 auto newContentBSize = std::max(nscoord(0), contentBSize - overflow); 2291 // XXXmats deal with percentages better somehow? 2292 finalSize.BSize(wm) -= contentBSize - newContentBSize; 2293 } 2294 } 2295 } 2296 2297 // Screen out negative block sizes --- can happen due to integer overflows :-( 2298 finalSize.BSize(wm) = std::max(0, finalSize.BSize(wm)); 2299 aMetrics.SetSize(wm, finalSize); 2300 2301 return blockEndEdgeOfChildren; 2302 } 2303 2304 void nsBlockFrame::AlignContent(BlockReflowState& aState, 2305 ReflowOutput& aMetrics, 2306 nscoord aBEndEdgeOfChildren) { 2307 StyleAlignFlags alignment = EffectiveAlignContent(); 2308 alignment &= ~StyleAlignFlags::FLAG_BITS; 2309 2310 // Short circuit 2311 const bool isCentered = alignment == StyleAlignFlags::CENTER || 2312 alignment == StyleAlignFlags::SPACE_AROUND || 2313 alignment == StyleAlignFlags::SPACE_EVENLY; 2314 const bool isEndAlign = alignment == StyleAlignFlags::END || 2315 alignment == StyleAlignFlags::FLEX_END || 2316 alignment == StyleAlignFlags::LAST_BASELINE; 2317 if (!isEndAlign && !isCentered && !aState.mAlignContentShift) { 2318 // desired shift = 0, no cached shift to undo 2319 return; 2320 } 2321 2322 // NOTE: ComputeFinalSize already called aState.UndoAlignContentShift(), 2323 // so metrics no longer include cached shift. 2324 // NOTE: Content is currently positioned at cached shift 2325 // NOTE: Content has been fragmented against 0-shift assumption. 2326 2327 // Calculate shift 2328 nscoord shift = 0; 2329 WritingMode wm = aState.mReflowInput.GetWritingMode(); 2330 if ((isCentered || isEndAlign) && !mLines.empty() && 2331 aState.mReflowStatus.IsFullyComplete() && !GetPrevInFlow()) { 2332 nscoord availB = aState.mReflowInput.AvailableBSize(); 2333 nscoord endB = 2334 aMetrics.Size(wm).BSize(wm) - aState.BorderPadding().BEnd(wm); 2335 shift = std::min(availB, endB) - aBEndEdgeOfChildren; 2336 2337 // note: these measures all include start BP, so it subtracts out 2338 if (!(StylePosition()->mAlignContent.primary & StyleAlignFlags::UNSAFE)) { 2339 shift = std::max(0, shift); 2340 } 2341 if (isCentered) { 2342 shift = shift / 2; 2343 } 2344 } 2345 // else: zero shift if start-aligned or if fragmented 2346 2347 nscoord delta = shift - aState.mAlignContentShift; 2348 if (delta) { 2349 // Shift children 2350 LogicalPoint translation(wm, 0, delta); 2351 for (nsLineBox& line : Lines()) { 2352 SlideLine(aState, &line, delta); 2353 } 2354 for (nsIFrame* kid : GetChildList(FrameChildListID::Float)) { 2355 kid->MovePositionBy(wm, translation); 2356 } 2357 nsIFrame* outsideMarker = GetOutsideMarker(); 2358 if (outsideMarker && !mLines.empty()) { 2359 outsideMarker->MovePositionBy(wm, translation); 2360 } 2361 } 2362 2363 if (shift) { 2364 // Cache shift 2365 SetProperty(AlignContentShift(), shift); 2366 } else { 2367 RemoveProperty(AlignContentShift()); 2368 } 2369 } 2370 2371 void nsBlockFrame::ComputeOverflowAreas(OverflowAreas& aOverflowAreas, 2372 const nsStyleDisplay* aDisplay) const { 2373 // XXX_perf: This can be done incrementally. It is currently one of 2374 // the things that makes incremental reflow O(N^2). 2375 auto overflowClipAxes = ShouldApplyOverflowClipping(aDisplay); 2376 auto overflowClipMargin = 2377 OverflowClipMargin(overflowClipAxes, /* aAllowNegative = */ false); 2378 if (overflowClipAxes == kPhysicalAxesBoth && overflowClipMargin.IsAllZero()) { 2379 return; 2380 } 2381 2382 // We rely here on our caller having called SetOverflowAreasToDesiredBounds(). 2383 const nsRect frameBounds = aOverflowAreas.ScrollableOverflow(); 2384 2385 const auto wm = GetWritingMode(); 2386 const auto borderPadding = 2387 GetLogicalUsedBorderAndPadding(wm).GetPhysicalMargin(wm); 2388 // Compute content-box by subtracting borderPadding off of frame rect. 2389 // This gives us a reasonable starting-rect for the child-rect-unioning 2390 // below, which we can then inflate by our padding (without needing to 2391 // worry about having double-counted our padding or anything). 2392 auto frameContentBounds = frameBounds; 2393 frameContentBounds.Deflate(borderPadding); 2394 // Margin rects (Zero-area rects included) of in-flow children (And floats, 2395 // as discussed later) are unioned (starting with the scroller's own 2396 // content-box), then inflated by the scroll container's padding... 2397 auto inFlowChildBounds = frameContentBounds; 2398 // ... While scrollable overflow rects contributed from further descendants 2399 // (Regardless of if they're in-flow or out-of-flow) are unioned separately 2400 // and their union does not get inflated by the scroll container's padding. 2401 auto inFlowScrollableOverflow = frameContentBounds; 2402 2403 for (const auto& line : Lines()) { 2404 aOverflowAreas.InkOverflow() = 2405 aOverflowAreas.InkOverflow().Union(line.InkOverflowRect()); 2406 if (aDisplay->IsContainLayout()) { 2407 // If we have layout containment, we should only consider our child's 2408 // ink overflow, leaving the scrollable regions of the parent 2409 // unaffected. 2410 // Note: Any overflow must be treated as ink overflow (As per 2411 // https://drafts.csswg.org/css-contain/#containment-layout part 3). 2412 // However, by unioning the children's ink overflow, we've already 2413 // incorporated its scrollable overflow, since scrollable overflow 2414 // is a subset of ink overflow. 2415 continue; 2416 } 2417 2418 if (line.IsInline()) { 2419 // This is the maximum contribution for inline line-participating frames - 2420 // See `GetLineFrameInFlowBounds`. 2421 inFlowChildBounds = 2422 inFlowChildBounds.UnionEdges(line.GetPhysicalBounds()); 2423 } 2424 auto lineInFlowChildBounds = line.GetInFlowChildBounds(); 2425 if (lineInFlowChildBounds) { 2426 inFlowChildBounds = inFlowChildBounds.UnionEdges(*lineInFlowChildBounds); 2427 } 2428 inFlowScrollableOverflow = 2429 inFlowScrollableOverflow.Union(line.ScrollableOverflowRect()); 2430 } 2431 2432 if (Style()->GetPseudoType() == PseudoStyleType::scrolledContent) { 2433 // Padding inflation only applies to scrolled containers. 2434 const auto paddingInflatedOverflow = 2435 ComputePaddingInflatedScrollableOverflow(inFlowChildBounds); 2436 aOverflowAreas.UnionAllWith(paddingInflatedOverflow); 2437 } 2438 // Note: we're using UnionAllWith so as to maintain the invariant of 2439 // ink overflow being a superset of scrollable overflow. 2440 aOverflowAreas.UnionAllWith(inFlowScrollableOverflow); 2441 2442 // Factor an outside ::marker in; normally the ::marker will be factored 2443 // into the line-box's overflow areas. However, if the line is a block 2444 // line then it won't; if there are no lines, it won't. So just 2445 // factor it in anyway (it can't hurt if it was already done). 2446 // XXXldb Can we just fix GetOverflowArea instead? 2447 if (nsIFrame* outsideMarker = GetOutsideMarker()) { 2448 aOverflowAreas.UnionAllWith(outsideMarker->GetRect()); 2449 } 2450 2451 if (!overflowClipAxes.isEmpty()) { 2452 aOverflowAreas.ApplyClipping(frameBounds, overflowClipAxes, 2453 overflowClipMargin); 2454 } 2455 2456 #ifdef NOISY_OVERFLOW_AREAS 2457 printf("%s: InkOverflowArea=%s, ScrollableOverflowArea=%s\n", ListTag().get(), 2458 ToString(aOverflowAreas.InkOverflow()).c_str(), 2459 ToString(aOverflowAreas.ScrollableOverflow()).c_str()); 2460 #endif 2461 } 2462 2463 // Depending on our ancestor, determine if we need to restrict padding inflation 2464 // in inline direction. This assumes that the passed-in frame is a scrolled 2465 // frame. HACK(dshin): Reaching out and querying the type like this isn't ideal. 2466 static bool RestrictPaddingInflationInInline(const nsIFrame* aFrame) { 2467 MOZ_ASSERT(aFrame); 2468 if (aFrame->Style()->GetPseudoType() != PseudoStyleType::scrolledContent) { 2469 // This can only happen when computing scrollable overflow for overflow: 2470 // visible frames (for scroll{Width,Height}). 2471 return false; 2472 } 2473 // If we're `input` or `textarea`, our grandparent element must be the text 2474 // control element that we can query. 2475 const auto* parent = aFrame->GetParent(); 2476 if (!parent) { 2477 return false; 2478 } 2479 MOZ_ASSERT(parent->IsScrollContainerOrSubclass(), "Not a scrolled frame?"); 2480 2481 nsTextControlFrame* textControl = do_QueryFrame(parent->GetParent()); 2482 if (MOZ_LIKELY(!textControl)) { 2483 return false; 2484 } 2485 2486 // We implement `textarea` as a special case of a div, but based on 2487 // web-platform-tests, different rules apply for it - namely, no inline 2488 // padding inflation. See 2489 // `textarea-padding-iend-overlaps-content-001.tentative.html`. 2490 if (!textControl->IsTextArea()) { 2491 return false; 2492 } 2493 return true; 2494 } 2495 2496 nsRect nsBlockFrame::ComputePaddingInflatedScrollableOverflow( 2497 const nsRect& aInFlowChildBounds) const { 2498 auto result = aInFlowChildBounds; 2499 const auto wm = GetWritingMode(); 2500 auto padding = GetLogicalUsedPadding(wm); 2501 if (RestrictPaddingInflationInInline(this)) { 2502 padding.IStart(wm) = padding.IEnd(wm) = 0; 2503 } 2504 result.Inflate(padding.GetPhysicalMargin(wm)); 2505 return result; 2506 } 2507 2508 Maybe<nsRect> nsBlockFrame::GetLineFrameInFlowBounds( 2509 const nsLineBox& aLine, const nsIFrame& aLineChildFrame, 2510 bool aConsiderPositiveMargins) const { 2511 MOZ_ASSERT(aLineChildFrame.GetParent() == this, 2512 "Line's frame doesn't belong to this block frame?"); 2513 // Line participants are considered in-flow for content within the line 2514 // bounds, which should be accounted for from the line bounds. This is 2515 // consistent with e.g. inline element's `margin-bottom` not affecting the 2516 // placement of the next line. 2517 if (aLineChildFrame.IsPlaceholderFrame() || 2518 aLineChildFrame.IsLineParticipant()) { 2519 return Nothing{}; 2520 } 2521 if (aLine.IsInline()) { 2522 return Some(GetNormalMarginRect(aLineChildFrame, aConsiderPositiveMargins)); 2523 } 2524 const auto wm = GetWritingMode(); 2525 auto rect = aLineChildFrame.GetRectRelativeToSelf(); 2526 // Special handling is required for boxes of zero block size, which carry 2527 // out margin collapsing with themselves. We end up "rewinding" the line 2528 // position after carrying out the block start margin. This is not reflected 2529 // in the zero-sized frame's own frame-position. 2530 const auto linePoint = aLine.GetPhysicalBounds().TopLeft(); 2531 const auto normalPosition = aLineChildFrame.GetLogicalSize(wm).BSize(wm) == 0 2532 ? linePoint 2533 : aLineChildFrame.GetNormalPosition(); 2534 // Ensure we use the margin we actually carried out. 2535 nsMargin margin; 2536 if (aConsiderPositiveMargins) { 2537 auto logicalMargin = aLineChildFrame.GetLogicalUsedMargin(wm); 2538 logicalMargin.BEnd(wm) = aLine.GetCarriedOutBEndMargin().Get(); 2539 margin = logicalMargin.GetPhysicalMargin(wm).ApplySkipSides( 2540 aLineChildFrame.GetSkipSides()); 2541 } else { 2542 margin = aLineChildFrame.GetUsedMargin().ApplySkipSides( 2543 aLineChildFrame.GetSkipSides()); 2544 margin.EnsureAtMost(nsMargin()); 2545 } 2546 rect.Inflate(margin); 2547 return Some(rect + normalPosition); 2548 } 2549 2550 void nsBlockFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas, 2551 bool aAsIfScrolled) { 2552 // We need to update the overflow areas of lines manually, as they 2553 // get cached and re-used otherwise. Lines aren't exposed as normal 2554 // frame children, so calling UnionChildOverflow alone will end up 2555 // using the old cached values. 2556 const auto wm = GetWritingMode(); 2557 2558 // ButtonControlFrame elements don't support scrolling, and some like 2559 // comboboxes intentionally ignore padding to place their inner elements like 2560 // the button, so to preserve behavior of stuff like the scroll{Width,Height} 2561 // APIs, forcefully ignore aAsIfScrolled there. 2562 aAsIfScrolled = aAsIfScrolled && !IsButtonControlFrame(); 2563 2564 // Overflow area computed here should agree with one computed in 2565 // `ComputeOverflowAreas` (see bug 1800939 and bug 1800719). So the 2566 // documentation in that function applies here as well. 2567 const bool isScrolled = aAsIfScrolled || Style()->GetPseudoType() == 2568 PseudoStyleType::scrolledContent; 2569 // Note that we don't add line in-flow margins if we're not a BFC (which can 2570 // happen only for overflow: visible), so that we don't incorrectly account 2571 // for margins that otherwise collapse through, see bug 1936156. Note that 2572 // ::-moz-scrolled-content is always a BFC (see `AnonymousBoxIsBFC`). 2573 const bool considerPositiveMarginsForInFlowChildBounds = 2574 isScrolled && HasAnyStateBits(NS_BLOCK_BFC); 2575 2576 // Relying on aOverflowAreas having been set to frame border rect (if 2577 // aAsIfScrolled is false), or padding rect (if true). 2578 auto frameContentBounds = aOverflowAreas.ScrollableOverflow(); 2579 frameContentBounds.Deflate((aAsIfScrolled 2580 ? GetLogicalUsedPadding(wm) 2581 : GetLogicalUsedBorderAndPadding(wm)) 2582 .GetPhysicalMargin(wm)); 2583 // We need to take in-flow children's margin rect into account, and inflate 2584 // it by the padding. 2585 auto inFlowChildBounds = frameContentBounds; 2586 auto inFlowScrollableOverflow = frameContentBounds; 2587 2588 const auto inkOverflowOnly = 2589 !aAsIfScrolled && StyleDisplay()->IsContainLayout(); 2590 2591 for (auto& line : Lines()) { 2592 nsRect bounds = line.GetPhysicalBounds(); 2593 OverflowAreas lineAreas(bounds, bounds); 2594 2595 int32_t n = line.GetChildCount(); 2596 for (nsIFrame* lineFrame = line.mFirstChild; n > 0; 2597 lineFrame = lineFrame->GetNextSibling(), --n) { 2598 // Ensure this is called for each frame in the line 2599 ConsiderChildOverflow(lineAreas, lineFrame, aAsIfScrolled); 2600 2601 if (inkOverflowOnly || !isScrolled) { 2602 continue; 2603 } 2604 2605 if (auto lineFrameBounds = GetLineFrameInFlowBounds( 2606 line, *lineFrame, considerPositiveMarginsForInFlowChildBounds)) { 2607 inFlowChildBounds = inFlowChildBounds.UnionEdges(*lineFrameBounds); 2608 } 2609 } 2610 2611 // Consider the overflow areas of the floats attached to the line as well 2612 if (line.HasFloats()) { 2613 for (nsIFrame* f : line.Floats()) { 2614 ConsiderChildOverflow(lineAreas, f, aAsIfScrolled); 2615 if (inkOverflowOnly || !isScrolled) { 2616 continue; 2617 } 2618 auto rect = GetNormalMarginRect( 2619 *f, considerPositiveMarginsForInFlowChildBounds); 2620 inFlowChildBounds = inFlowChildBounds.UnionEdges(rect); 2621 } 2622 } 2623 2624 if (!aAsIfScrolled) { 2625 line.SetOverflowAreas(lineAreas); 2626 } 2627 aOverflowAreas.InkOverflow() = 2628 aOverflowAreas.InkOverflow().Union(lineAreas.InkOverflow()); 2629 if (!inkOverflowOnly) { 2630 inFlowScrollableOverflow = 2631 inFlowScrollableOverflow.Union(lineAreas.ScrollableOverflow()); 2632 } 2633 } 2634 2635 if (isScrolled) { 2636 const auto paddingInflatedOverflow = 2637 ComputePaddingInflatedScrollableOverflow(inFlowChildBounds); 2638 aOverflowAreas.UnionAllWith(paddingInflatedOverflow); 2639 } 2640 aOverflowAreas.UnionAllWith(inFlowScrollableOverflow); 2641 2642 // Union with child frames, skipping the principal and float lists 2643 // since we already handled those using the line boxes. 2644 nsLayoutUtils::UnionChildOverflow( 2645 this, aOverflowAreas, 2646 {FrameChildListID::Principal, FrameChildListID::Float}); 2647 } 2648 2649 bool nsBlockFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) { 2650 // Line cursor invariants depend on the overflow areas of the lines, so 2651 // we must clear the line cursor since those areas may have changed. 2652 ClearLineCursors(); 2653 return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas); 2654 } 2655 2656 void nsBlockFrame::LazyMarkLinesDirty() { 2657 if (HasAnyStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES)) { 2658 for (LineIterator line = LinesBegin(), line_end = LinesEnd(); 2659 line != line_end; ++line) { 2660 int32_t n = line->GetChildCount(); 2661 for (nsIFrame* lineFrame = line->mFirstChild; n > 0; 2662 lineFrame = lineFrame->GetNextSibling(), --n) { 2663 if (lineFrame->IsSubtreeDirty()) { 2664 // NOTE: MarkLineDirty does more than just marking the line dirty. 2665 MarkLineDirty(line, &mLines); 2666 break; 2667 } 2668 } 2669 } 2670 RemoveStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES); 2671 } 2672 } 2673 2674 void nsBlockFrame::MarkLineDirty(LineIterator aLine, 2675 const nsLineList* aLineList) { 2676 // Mark aLine dirty 2677 aLine->MarkDirty(); 2678 aLine->SetInvalidateTextRuns(true); 2679 #ifdef DEBUG 2680 if (gNoisyReflow) { 2681 IndentBy(stdout, gNoiseIndent); 2682 ListTag(stdout); 2683 printf(": mark line %p dirty\n", static_cast<void*>(aLine.get())); 2684 } 2685 #endif 2686 2687 // Mark previous line dirty if it's an inline line so that it can 2688 // maybe pullup something from the line just affected. 2689 // XXX We don't need to do this if aPrevLine ends in a break-after... 2690 if (aLine != aLineList->front() && aLine->IsInline() && 2691 aLine.prev()->IsInline()) { 2692 aLine.prev()->MarkDirty(); 2693 aLine.prev()->SetInvalidateTextRuns(true); 2694 #ifdef DEBUG 2695 if (gNoisyReflow) { 2696 IndentBy(stdout, gNoiseIndent); 2697 ListTag(stdout); 2698 printf(": mark prev-line %p dirty\n", 2699 static_cast<void*>(aLine.prev().get())); 2700 } 2701 #endif 2702 } 2703 } 2704 2705 /** 2706 * Test whether lines are certain to be aligned left so that we can make 2707 * resizing optimizations 2708 */ 2709 static inline bool IsAlignedLeft(StyleTextAlign aAlignment, 2710 StyleDirection aDirection, 2711 StyleUnicodeBidi aUnicodeBidi, 2712 nsIFrame* aFrame) { 2713 return aFrame->IsInSVGTextSubtree() || StyleTextAlign::Left == aAlignment || 2714 (((StyleTextAlign::Start == aAlignment && 2715 StyleDirection::Ltr == aDirection) || 2716 (StyleTextAlign::End == aAlignment && 2717 StyleDirection::Rtl == aDirection)) && 2718 aUnicodeBidi != StyleUnicodeBidi::Plaintext); 2719 } 2720 2721 void nsBlockFrame::PrepareResizeReflow(BlockReflowState& aState) { 2722 // See if we can try and avoid marking all the lines as dirty 2723 // FIXME(emilio): This should be writing-mode aware, I guess. 2724 bool tryAndSkipLines = 2725 // The left content-edge must be a constant distance from the left 2726 // border-edge. 2727 !StylePadding()->mPadding.Get(eSideLeft).HasPercent(); 2728 2729 #ifdef DEBUG 2730 if (gDisableResizeOpt) { 2731 tryAndSkipLines = false; 2732 } 2733 if (gNoisyReflow) { 2734 if (!tryAndSkipLines) { 2735 IndentBy(stdout, gNoiseIndent); 2736 ListTag(stdout); 2737 printf(": marking all lines dirty: availISize=%d\n", 2738 aState.mReflowInput.AvailableISize()); 2739 } 2740 } 2741 #endif 2742 2743 if (tryAndSkipLines) { 2744 WritingMode wm = aState.mReflowInput.GetWritingMode(); 2745 nscoord newAvailISize = 2746 aState.mReflowInput.ComputedLogicalBorderPadding(wm).IStart(wm) + 2747 aState.mReflowInput.ComputedISize(); 2748 2749 #ifdef DEBUG 2750 if (gNoisyReflow) { 2751 IndentBy(stdout, gNoiseIndent); 2752 ListTag(stdout); 2753 printf(": trying to avoid marking all lines dirty\n"); 2754 } 2755 #endif 2756 2757 for (LineIterator line = LinesBegin(), line_end = LinesEnd(); 2758 line != line_end; ++line) { 2759 // We let child blocks make their own decisions the same 2760 // way we are here. 2761 bool isLastLine = line == mLines.back() && !GetNextInFlow(); 2762 if (line->IsBlock() || line->HasFloats() || 2763 (!isLastLine && !line->HasForcedLineBreakAfter()) || 2764 ((isLastLine || !line->IsLineWrapped())) || 2765 line->ResizeReflowOptimizationDisabled() || 2766 line->IsImpactedByFloat() || (line->IEnd() > newAvailISize)) { 2767 line->MarkDirty(); 2768 } 2769 2770 #ifdef REALLY_NOISY_REFLOW 2771 if (!line->IsBlock()) { 2772 printf("PrepareResizeReflow thinks line %p is %simpacted by floats\n", 2773 line.get(), line->IsImpactedByFloat() ? "" : "not "); 2774 } 2775 #endif 2776 #ifdef DEBUG 2777 if (gNoisyReflow && !line->IsDirty()) { 2778 IndentBy(stdout, gNoiseIndent + 1); 2779 printf( 2780 "skipped: line=%p next=%p %s %s%s%s clearTypeBefore/After=%s/%s " 2781 "xmost=%d\n", 2782 static_cast<void*>(line.get()), 2783 static_cast<void*>( 2784 (line.next() != LinesEnd() ? line.next().get() : nullptr)), 2785 line->IsBlock() ? "block" : "inline", 2786 line->HasForcedLineBreakAfter() ? "has-break-after " : "", 2787 line->HasFloats() ? "has-floats " : "", 2788 line->IsImpactedByFloat() ? "impacted " : "", 2789 line->UsedClearToString(line->FloatClearTypeBefore()), 2790 line->UsedClearToString(line->FloatClearTypeAfter()), line->IEnd()); 2791 } 2792 #endif 2793 } 2794 } else { 2795 // Mark everything dirty 2796 for (auto& line : Lines()) { 2797 line.MarkDirty(); 2798 } 2799 } 2800 } 2801 2802 //---------------------------------------- 2803 2804 /** 2805 * Propagate reflow "damage" from from earlier lines to the current 2806 * line. The reflow damage comes from the following sources: 2807 * 1. The regions of float damage remembered during reflow. 2808 * 2. The combination of nonzero |aDeltaBCoord| and any impact by a 2809 * float, either the previous reflow or now. 2810 * 2811 * When entering this function, |aLine| is still at its old position and 2812 * |aDeltaBCoord| indicates how much it will later be slid (assuming it 2813 * doesn't get marked dirty and reflowed entirely). 2814 */ 2815 void nsBlockFrame::PropagateFloatDamage(BlockReflowState& aState, 2816 nsLineBox* aLine, 2817 nscoord aDeltaBCoord) { 2818 nsFloatManager* floatManager = aState.FloatManager(); 2819 NS_ASSERTION( 2820 (aState.mReflowInput.mParentReflowInput && 2821 aState.mReflowInput.mParentReflowInput->mFloatManager == floatManager) || 2822 aState.mReflowInput.mBlockDelta == 0, 2823 "Bad block delta passed in"); 2824 2825 // Check to see if there are any floats; if there aren't, there can't 2826 // be any float damage 2827 if (!floatManager->HasAnyFloats()) { 2828 return; 2829 } 2830 2831 // Check the damage region recorded in the float damage. 2832 if (floatManager->HasFloatDamage()) { 2833 // Need to check mBounds *and* mCombinedArea to find intersections 2834 // with aLine's floats 2835 nscoord lineBCoordBefore = aLine->BStart() + aDeltaBCoord; 2836 nscoord lineBCoordAfter = lineBCoordBefore + aLine->BSize(); 2837 // Scrollable overflow should be sufficient for things that affect 2838 // layout. 2839 WritingMode wm = aState.mReflowInput.GetWritingMode(); 2840 nsSize containerSize = aState.ContainerSize(); 2841 LogicalRect overflow = 2842 aLine->GetOverflowArea(OverflowType::Scrollable, wm, containerSize); 2843 nscoord lineBCoordCombinedBefore = overflow.BStart(wm) + aDeltaBCoord; 2844 nscoord lineBCoordCombinedAfter = 2845 lineBCoordCombinedBefore + overflow.BSize(wm); 2846 2847 bool isDirty = 2848 floatManager->IntersectsDamage(lineBCoordBefore, lineBCoordAfter) || 2849 floatManager->IntersectsDamage(lineBCoordCombinedBefore, 2850 lineBCoordCombinedAfter); 2851 if (isDirty) { 2852 aLine->MarkDirty(); 2853 return; 2854 } 2855 } 2856 2857 // Check if the line is moving relative to the float manager 2858 if (aDeltaBCoord + aState.mReflowInput.mBlockDelta != 0) { 2859 if (aLine->IsBlock()) { 2860 // Unconditionally reflow sliding blocks; we only really need to reflow 2861 // if there's a float impacting this block, but the current float manager 2862 // makes it difficult to check that. Therefore, we let the child block 2863 // decide what it needs to reflow. 2864 aLine->MarkDirty(); 2865 } else { 2866 bool wasImpactedByFloat = aLine->IsImpactedByFloat(); 2867 nsFlowAreaRect floatAvailableSpace = 2868 aState.GetFloatAvailableSpaceForBSize( 2869 aState.mReflowInput.GetWritingMode(), 2870 aLine->BStart() + aDeltaBCoord, aLine->BSize(), nullptr); 2871 2872 #ifdef REALLY_NOISY_REFLOW 2873 printf("nsBlockFrame::PropagateFloatDamage %p was = %d, is=%d\n", this, 2874 wasImpactedByFloat, floatAvailableSpace.HasFloats()); 2875 #endif 2876 2877 // Mark the line dirty if it was or is affected by a float 2878 // We actually only really need to reflow if the amount of impact 2879 // changes, but that's not straightforward to check 2880 if (wasImpactedByFloat || floatAvailableSpace.HasFloats()) { 2881 aLine->MarkDirty(); 2882 } 2883 } 2884 } 2885 } 2886 2887 static bool LineHasClear(nsLineBox* aLine) { 2888 return aLine->IsBlock() 2889 ? (aLine->HasFloatClearTypeBefore() || 2890 aLine->mFirstChild->HasAnyStateBits( 2891 NS_BLOCK_HAS_CLEAR_CHILDREN) || 2892 !nsBlockFrame::BlockCanIntersectFloats(aLine->mFirstChild)) 2893 : aLine->HasFloatClearTypeAfter(); 2894 } 2895 2896 /** 2897 * Reparent a whole list of floats from aOldParent to this block. The 2898 * floats might be taken from aOldParent's overflow list. They will be 2899 * removed from the list. They end up appended to our floats list. 2900 */ 2901 void nsBlockFrame::ReparentFloats(nsIFrame* aFirstFrame, 2902 nsBlockFrame* aOldParent, 2903 bool aReparentSiblings) { 2904 nsFrameList list; 2905 aOldParent->CollectFloats(aFirstFrame, list, aReparentSiblings); 2906 if (list.NotEmpty()) { 2907 for (nsIFrame* f : list) { 2908 MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW), 2909 "CollectFloats should've removed that bit"); 2910 ReparentFrame(f, aOldParent, this); 2911 } 2912 EnsureFloats()->AppendFrames(nullptr, std::move(list)); 2913 } 2914 } 2915 2916 static void DumpLine(const BlockReflowState& aState, nsLineBox* aLine, 2917 nscoord aDeltaBCoord, int32_t aDeltaIndent) { 2918 #ifdef DEBUG 2919 if (nsBlockFrame::gNoisyReflow) { 2920 nsBlockFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent + aDeltaIndent); 2921 fmt::println( 2922 FMT_STRING("line={} mBCoord={} dirty={} bounds={} overflow-ink={} " 2923 "overflow-scr={} deltaBCoord={} mPrevBEndMargin={} " 2924 "childCount={}"), 2925 static_cast<void*>(aLine), aState.mBCoord, YesOrNo(aLine->IsDirty()), 2926 ToString(aLine->GetBounds()), ToString(aLine->InkOverflowRect()), 2927 ToString(aLine->ScrollableOverflowRect()), aDeltaBCoord, 2928 aState.mPrevBEndMargin.Get(), aLine->GetChildCount()); 2929 } 2930 #endif 2931 } 2932 2933 bool nsBlockFrame::LinesAreEmpty() const { 2934 for (const auto& line : mLines) { 2935 if (!line.IsEmpty()) { 2936 return false; 2937 } 2938 } 2939 return true; 2940 } 2941 2942 bool nsBlockFrame::ReflowDirtyLines(BlockReflowState& aState) { 2943 bool keepGoing = true; 2944 bool foundAnyClears = aState.mTrailingClearFromPIF != UsedClear::None; 2945 bool willReflowAgain = false; 2946 bool usedOverflowWrap = false; 2947 2948 #ifdef DEBUG 2949 if (gNoisyReflow) { 2950 IndentBy(stdout, gNoiseIndent); 2951 ListTag(stdout); 2952 printf(": reflowing dirty lines"); 2953 printf(" computedISize=%d\n", aState.mReflowInput.ComputedISize()); 2954 } 2955 AutoNoisyIndenter indent(gNoisyReflow); 2956 #endif 2957 2958 bool selfDirty = HasAnyStateBits(NS_FRAME_IS_DIRTY) || 2959 (aState.mReflowInput.IsBResize() && 2960 HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)); 2961 2962 // Reflow our last line if our availableBSize has increased 2963 // so that we (and our last child) pull up content as necessary 2964 if (aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE && 2965 GetNextInFlow() && 2966 aState.mReflowInput.AvailableBSize() > 2967 GetLogicalSize().BSize(aState.mReflowInput.GetWritingMode())) { 2968 LineIterator lastLine = LinesEnd(); 2969 if (lastLine != LinesBegin()) { 2970 --lastLine; 2971 lastLine->MarkDirty(); 2972 } 2973 } 2974 // the amount by which we will slide the current line if it is not 2975 // dirty 2976 nscoord deltaBCoord = 0; 2977 2978 // whether we did NOT reflow the previous line and thus we need to 2979 // recompute the carried out margin before the line if we want to 2980 // reflow it or if its previous margin is dirty 2981 bool needToRecoverState = false; 2982 // Float continuations were reflowed in ReflowPushedFloats 2983 bool reflowedFloat = 2984 HasFloats() && GetFloats()->FirstChild()->HasAnyStateBits( 2985 NS_FRAME_IS_PUSHED_OUT_OF_FLOW); 2986 bool lastLineMovedUp = false; 2987 // We save up information about BR-clearance here 2988 UsedClear inlineFloatClearType = aState.mTrailingClearFromPIF; 2989 2990 LineIterator line = LinesBegin(), line_end = LinesEnd(); 2991 2992 // Determine if children of this frame could have breaks between them for 2993 // page names. 2994 // 2995 // We need to check for paginated layout, the named-page pref, and if the 2996 // available block-size is constrained. 2997 // 2998 // Note that we need to check for paginated layout as named-pages are only 2999 // used during paginated reflow. We need to additionally check for 3000 // unconstrained block-size to avoid introducing fragmentation breaks during 3001 // "measuring" reflows within an overall paginated reflow, and to avoid 3002 // fragmentation in monolithic containers like 'inline-block'. 3003 // 3004 // Because we can only break for named pages using Class A breakpoints, we 3005 // also need to check that the block flow direction of the containing frame 3006 // of these items (which is this block) is parallel to that of this page. 3007 // See: https://www.w3.org/TR/css-break-3/#btw-blocks 3008 const nsPresContext* const presCtx = aState.mPresContext; 3009 const bool canBreakForPageNames = 3010 aState.mReflowInput.mFlags.mCanHaveClassABreakpoints && 3011 aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE && 3012 presCtx->GetPresShell()->GetRootFrame()->GetWritingMode().IsVertical() == 3013 GetWritingMode().IsVertical(); 3014 3015 // ReflowInput.mFlags.mCanHaveClassABreakpoints should respect the named 3016 // pages pref and presCtx->IsPaginated, so we did not explicitly check these 3017 // above when setting canBreakForPageNames. 3018 if (canBreakForPageNames) { 3019 MOZ_ASSERT(presCtx->IsPaginated(), 3020 "canBreakForPageNames should not be set during non-paginated " 3021 "reflow"); 3022 } 3023 3024 // Reflow the lines that are already ours 3025 for (; line != line_end; ++line, aState.AdvanceToNextLine()) { 3026 DumpLine(aState, line, deltaBCoord, 0); 3027 #ifdef DEBUG 3028 AutoNoisyIndenter indent2(gNoisyReflow); 3029 #endif 3030 3031 if (selfDirty) { 3032 line->MarkDirty(); 3033 } 3034 3035 // This really sucks, but we have to look inside any blocks that have clear 3036 // elements inside them. 3037 // XXX what can we do smarter here? 3038 if (!line->IsDirty() && line->IsBlock() && 3039 line->mFirstChild->HasAnyStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN) && 3040 aState.FloatManager()->HasAnyFloats()) { 3041 line->MarkDirty(); 3042 } 3043 3044 nsIFrame* floatAvoidingBlock = nullptr; 3045 if (line->IsBlock() && 3046 !nsBlockFrame::BlockCanIntersectFloats(line->mFirstChild)) { 3047 floatAvoidingBlock = line->mFirstChild; 3048 } 3049 3050 // We have to reflow the line if it's a block whose clearance 3051 // might have changed, so detect that. 3052 if (!line->IsDirty() && 3053 (line->HasFloatClearTypeBefore() || floatAvoidingBlock)) { 3054 nscoord curBCoord = aState.mBCoord; 3055 // See where we would be after applying any clearance due to 3056 // BRs. 3057 if (inlineFloatClearType != UsedClear::None) { 3058 std::tie(curBCoord, std::ignore) = 3059 aState.ClearFloats(curBCoord, inlineFloatClearType); 3060 } 3061 3062 auto [newBCoord, result] = aState.ClearFloats( 3063 curBCoord, line->FloatClearTypeBefore(), floatAvoidingBlock); 3064 3065 if (line->HasClearance()) { 3066 // Reflow the line if it might not have clearance anymore. 3067 if (result == ClearFloatsResult::BCoordNoChange 3068 // aState.mBCoord is the clearance point which should be the 3069 // block-start border-edge of the block frame. If sliding the 3070 // block by deltaBCoord isn't going to put it in the predicted 3071 // position, then we'd better reflow the line. 3072 || newBCoord != line->BStart() + deltaBCoord) { 3073 line->MarkDirty(); 3074 } 3075 } else { 3076 // Reflow the line if the line might have clearance now. 3077 if (result != ClearFloatsResult::BCoordNoChange) { 3078 line->MarkDirty(); 3079 } 3080 } 3081 } 3082 3083 // We might have to reflow a line that is after a clearing BR. 3084 if (inlineFloatClearType != UsedClear::None) { 3085 std::tie(aState.mBCoord, std::ignore) = 3086 aState.ClearFloats(aState.mBCoord, inlineFloatClearType); 3087 if (aState.mBCoord != line->BStart() + deltaBCoord) { 3088 // SlideLine is not going to put the line where the clearance 3089 // put it. Reflow the line to be sure. 3090 line->MarkDirty(); 3091 } 3092 inlineFloatClearType = UsedClear::None; 3093 } 3094 3095 bool previousMarginWasDirty = line->IsPreviousMarginDirty(); 3096 if (previousMarginWasDirty) { 3097 // If the previous margin is dirty, reflow the current line 3098 line->MarkDirty(); 3099 line->ClearPreviousMarginDirty(); 3100 } else if (aState.ContentBSize() != NS_UNCONSTRAINEDSIZE) { 3101 const nscoord scrollableOverflowBEnd = 3102 LogicalRect(line->mWritingMode, line->ScrollableOverflowRect(), 3103 line->mContainerSize) 3104 .BEnd(line->mWritingMode); 3105 if (scrollableOverflowBEnd + deltaBCoord > aState.ContentBEnd()) { 3106 // Lines that aren't dirty but get slid past our available block-size 3107 // constraint must be reflowed. 3108 line->MarkDirty(); 3109 } 3110 } 3111 3112 if (!line->IsDirty()) { 3113 const bool isPaginated = 3114 // Last column can be reflowed unconstrained during column balancing. 3115 // Hence the additional NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR bit check 3116 // as a fail-safe fallback. 3117 aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE || 3118 HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) || 3119 // Table can also be reflowed unconstrained during printing. 3120 aState.mPresContext->IsPaginated(); 3121 if (isPaginated) { 3122 // We are in a paginated context, i.e. in columns or pages. 3123 const bool mayContainFloats = 3124 line->IsBlock() || line->HasFloats() || line->HadFloatPushed(); 3125 if (mayContainFloats) { 3126 // The following if-else conditions check whether this line -- which 3127 // might have floats in its subtree, or has floats as direct children, 3128 // or had floats pushed -- needs to be reflowed. 3129 if (deltaBCoord != 0 || aState.mReflowInput.IsBResize()) { 3130 // The distance to the block-end edge might have changed. Reflow the 3131 // line both because the breakpoints within its floats may have 3132 // changed and because we might have to push/pull the floats in 3133 // their entirety. 3134 line->MarkDirty(); 3135 } else if (HasPushedFloats()) { 3136 // We had pushed floats which haven't been drained by our 3137 // next-in-flow, which means our parent is currently reflowing us 3138 // again due to clearance without creating a next-in-flow for us. 3139 // Reflow the line to redo the floats split logic to correctly set 3140 // our reflow status. 3141 line->MarkDirty(); 3142 } else if (aState.mReflowInput.mFlags.mMustReflowPlaceholders) { 3143 // Reflow the line (that may containing a float's placeholder frame) 3144 // if our parent tells us to do so. 3145 line->MarkDirty(); 3146 } else if (aState.mReflowInput.mFlags.mMovedBlockFragments) { 3147 // Our parent's line containing us moved to a different fragment. 3148 // Reflow the line because the decision about whether the float fits 3149 // may be different in a different fragment. 3150 line->MarkDirty(); 3151 } 3152 } 3153 } 3154 } 3155 3156 if (!line->IsDirty()) { 3157 // See if there's any reflow damage that requires that we mark the 3158 // line dirty. 3159 PropagateFloatDamage(aState, line, deltaBCoord); 3160 } 3161 3162 // If the container size has changed, reset mContainerSize. If the 3163 // line's writing mode is not ltr, or if the line is not left-aligned, also 3164 // mark the line dirty. 3165 if (aState.ContainerSize() != line->mContainerSize) { 3166 line->mContainerSize = aState.ContainerSize(); 3167 3168 const bool isLastLine = line == mLines.back() && !GetNextInFlow(); 3169 const auto align = isLastLine ? StyleText()->TextAlignForLastLine() 3170 : StyleText()->mTextAlign; 3171 if (line->mWritingMode.IsVertical() || line->mWritingMode.IsBidiRTL() || 3172 !IsAlignedLeft(align, StyleVisibility()->mDirection, 3173 StyleTextReset()->mUnicodeBidi, this)) { 3174 line->MarkDirty(); 3175 } 3176 } 3177 3178 // Check for a page break caused by CSS named pages. 3179 // 3180 // We should break for named pages when two frames meet at a class A 3181 // breakpoint, where the first frame has a different end page value to the 3182 // second frame's start page value. canBreakForPageNames is true iff 3183 // children of this frame can form class A breakpoints, and that we are not 3184 // in a measurement reflow or in a monolithic container such as 3185 // 'inline-block'. 3186 // 3187 // We specifically do not want to cause a page-break for named pages when 3188 // we are at the top of a page. This would otherwise happen when the 3189 // previous sibling is an nsPageBreakFrame, or all previous siblings on the 3190 // current page are zero-height. The latter may not be per-spec, but is 3191 // compatible with Chrome's implementation of named pages. 3192 const nsAtom* nextPageName = nullptr; 3193 bool shouldBreakForPageName = false; 3194 if (canBreakForPageNames && (!aState.mReflowInput.mFlags.mIsTopOfPage || 3195 !aState.IsAdjacentWithBStart())) { 3196 const nsIFrame* const frame = line->mFirstChild; 3197 if (!frame->IsPlaceholderFrame() && !frame->IsPageBreakFrame()) { 3198 nextPageName = frame->GetStartPageValue(); 3199 // Walk back to the last frame that isn't a placeholder. 3200 const nsIFrame* prevFrame = frame->GetPrevSibling(); 3201 while (prevFrame && prevFrame->IsPlaceholderFrame()) { 3202 prevFrame = prevFrame->GetPrevSibling(); 3203 } 3204 if (prevFrame && prevFrame->GetEndPageValue() != nextPageName) { 3205 shouldBreakForPageName = true; 3206 line->MarkDirty(); 3207 } 3208 } 3209 } 3210 3211 if (needToRecoverState && line->IsDirty()) { 3212 // We need to reconstruct the block-end margin only if we didn't 3213 // reflow the previous line and we do need to reflow (or repair 3214 // the block-start position of) the next line. 3215 aState.ReconstructMarginBefore(line); 3216 } 3217 3218 bool reflowedPrevLine = !needToRecoverState; 3219 if (needToRecoverState) { 3220 needToRecoverState = false; 3221 3222 // Update aState.mPrevChild as if we had reflowed all of the frames in 3223 // this line. 3224 if (line->IsDirty()) { 3225 NS_ASSERTION( 3226 line->mFirstChild->GetPrevSibling() == line.prev()->LastChild(), 3227 "unexpected line frames"); 3228 aState.mPrevChild = line->mFirstChild->GetPrevSibling(); 3229 } 3230 } 3231 3232 // Now repair the line and update |aState.mBCoord| by calling 3233 // |ReflowLine| or |SlideLine|. 3234 // If we're going to reflow everything again, then no need to reflow 3235 // the dirty line ... unless the line has floats, in which case we'd 3236 // better reflow it now to refresh its float cache, which may contain 3237 // dangling frame pointers! Ugh! This reflow of the line may be 3238 // incorrect because we skipped reflowing previous lines (e.g., floats 3239 // may be placed incorrectly), but that's OK because we'll mark the 3240 // line dirty below under "if (aState.mReflowInput.mDiscoveredClearance..." 3241 if (line->IsDirty() && (line->HasFloats() || !willReflowAgain)) { 3242 lastLineMovedUp = true; 3243 3244 bool maybeReflowingForFirstTime = 3245 line->IStart() == 0 && line->BStart() == 0 && line->ISize() == 0 && 3246 line->BSize() == 0; 3247 3248 // Compute the dirty lines "before" BEnd, after factoring in 3249 // the running deltaBCoord value - the running value is implicit in 3250 // aState.mBCoord. 3251 nscoord oldB = line->BStart(); 3252 nscoord oldBMost = line->BEnd(); 3253 3254 NS_ASSERTION(!willReflowAgain || !line->IsBlock(), 3255 "Don't reflow blocks while willReflowAgain is true, reflow " 3256 "of block abs-pos children depends on this"); 3257 3258 if (shouldBreakForPageName) { 3259 // Immediately fragment for page-name. It is possible we could break 3260 // out of the loop right here, but this should make it more similar to 3261 // what happens when reflow causes fragmentation. 3262 // Set the page name, so that PushTruncatedLine does not need to 3263 // recalculate the new page name. 3264 PresShell()->FrameConstructor()->SetNextPageContentFramePageName( 3265 nextPageName ? nextPageName : GetAutoPageValue()); 3266 PushTruncatedLine(aState, line, &keepGoing, 3267 ComputeNewPageNameIfNeeded::No); 3268 } else { 3269 // Reflow the dirty line. If it's an incremental reflow, then force 3270 // it to invalidate the dirty area if necessary 3271 usedOverflowWrap |= ReflowLine(aState, line, &keepGoing); 3272 } 3273 3274 if (aState.mReflowInput.WillReflowAgainForClearance()) { 3275 line->MarkDirty(); 3276 willReflowAgain = true; 3277 // Note that once we've entered this state, every line that gets here 3278 // (e.g. because it has floats) gets marked dirty and reflowed again. 3279 // in the next pass. This is important, see above. 3280 } 3281 3282 if (line->HasFloats()) { 3283 reflowedFloat = true; 3284 } 3285 3286 if (!keepGoing) { 3287 DumpLine(aState, line, deltaBCoord, -1); 3288 if (0 == line->GetChildCount()) { 3289 DeleteLine(aState, line, line_end); 3290 } 3291 break; 3292 } 3293 3294 // Test to see whether the margin that should be carried out 3295 // to the next line (NL) might have changed. In ReflowBlockFrame 3296 // we call nextLine->MarkPreviousMarginDirty if the block's 3297 // actual carried-out block-end margin changed. So here we only 3298 // need to worry about the following effects: 3299 // 1) the line was just created, and it might now be blocking 3300 // a carried-out block-end margin from previous lines that 3301 // used to reach NL from reaching NL 3302 // 2) the line used to be empty, and is now not empty, 3303 // thus blocking a carried-out block-end margin from previous lines 3304 // that used to reach NL from reaching NL 3305 // 3) the line wasn't empty, but now is, so a carried-out 3306 // block-end margin from previous lines that didn't used to reach NL 3307 // now does 3308 // 4) the line might have changed in a way that affects NL's 3309 // ShouldApplyBStartMargin decision. The three things that matter 3310 // are the line's emptiness, its adjacency to the block-start edge of the 3311 // block, and whether it has clearance (the latter only matters if the 3312 // block was and is adjacent to the block-start and empty). 3313 // 3314 // If the line is empty now, we can't reliably tell if the line was empty 3315 // before, so we just assume it was and do 3316 // nextLine->MarkPreviousMarginDirty. This means the checks in 4) are 3317 // redundant; if the line is empty now we don't need to check 4), but if 3318 // the line is not empty now and we're sure it wasn't empty before, any 3319 // adjacency and clearance changes are irrelevant to the result of 3320 // nextLine->ShouldApplyBStartMargin. 3321 if (line.next() != LinesEnd()) { 3322 bool maybeWasEmpty = oldB == line.next()->BStart(); 3323 bool isEmpty = line->CachedIsEmpty(); 3324 if (maybeReflowingForFirstTime /*1*/ || 3325 (isEmpty || maybeWasEmpty) /*2/3/4*/) { 3326 line.next()->MarkPreviousMarginDirty(); 3327 // since it's marked dirty, nobody will care about |deltaBCoord| 3328 } 3329 } 3330 3331 // If the line was just reflowed for the first time, then its 3332 // old mBounds cannot be trusted so this deltaBCoord computation is 3333 // bogus. But that's OK because we just did 3334 // MarkPreviousMarginDirty on the next line which will force it 3335 // to be reflowed, so this computation of deltaBCoord will not be 3336 // used. 3337 deltaBCoord = line->BEnd() - oldBMost; 3338 3339 // Now do an interrupt check. We want to do this only in the case when we 3340 // actually reflow the line, so that if we get back in here we'll get 3341 // further on the reflow before interrupting. 3342 aState.mPresContext->CheckForInterrupt(this); 3343 } else { 3344 aState.mOverflowTracker->Skip(line->mFirstChild, aState.mReflowStatus); 3345 // Nop except for blocks (we don't create overflow container 3346 // continuations for any inlines atm), so only checking mFirstChild 3347 // is enough 3348 3349 lastLineMovedUp = deltaBCoord < 0; 3350 3351 if (deltaBCoord != 0) { 3352 SlideLine(aState, line, deltaBCoord); 3353 } 3354 3355 NS_ASSERTION(!line->IsDirty() || !line->HasFloats(), 3356 "Possibly stale float cache here!"); 3357 if (willReflowAgain && line->IsBlock()) { 3358 // If we're going to reflow everything again, and this line is a block, 3359 // then there is no need to recover float state. The line may contain 3360 // other lines with floats, but in that case RecoverStateFrom would only 3361 // add floats to the float manager. We don't need to do that because 3362 // everything's going to get reflowed again "for real". Calling 3363 // RecoverStateFrom in this situation could be lethal because the 3364 // block's descendant lines may have float caches containing dangling 3365 // frame pointers. Ugh! 3366 // If this line is inline, then we need to recover its state now 3367 // to make sure that we don't forget to move its floats by deltaBCoord. 3368 } else { 3369 // XXX EVIL O(N^2) EVIL 3370 aState.RecoverStateFrom(line, deltaBCoord); 3371 } 3372 3373 // Keep mBCoord up to date in case we're propagating reflow damage 3374 // and also because our final height may depend on it. If the 3375 // line is inlines, then only update mBCoord if the line is not 3376 // empty, because that's what PlaceLine does. (Empty blocks may 3377 // want to update mBCoord, e.g. if they have clearance.) 3378 if (line->IsBlock() || !line->CachedIsEmpty()) { 3379 aState.mBCoord = line->BEnd(); 3380 } 3381 3382 needToRecoverState = true; 3383 3384 if (reflowedPrevLine && !line->IsBlock() && 3385 aState.mPresContext->HasPendingInterrupt()) { 3386 // Need to make sure to pull overflows from any prev-in-flows 3387 for (nsIFrame* inlineKid = line->mFirstChild; inlineKid; 3388 inlineKid = inlineKid->PrincipalChildList().FirstChild()) { 3389 inlineKid->PullOverflowsFromPrevInFlow(); 3390 } 3391 } 3392 } 3393 3394 // Record if we need to clear floats before reflowing the next 3395 // line. Note that inlineFloatClearType will be handled and 3396 // cleared before the next line is processed, so there is no 3397 // need to combine break types here. 3398 if (line->HasFloatClearTypeAfter()) { 3399 inlineFloatClearType = line->FloatClearTypeAfter(); 3400 } 3401 3402 if (LineHasClear(line.get())) { 3403 foundAnyClears = true; 3404 } 3405 3406 DumpLine(aState, line, deltaBCoord, -1); 3407 3408 if (aState.mPresContext->HasPendingInterrupt()) { 3409 willReflowAgain = true; 3410 // Another option here might be to leave |line| clean if 3411 // !HasPendingInterrupt() before the CheckForInterrupt() call, since in 3412 // that case the line really did reflow as it should have. Not sure 3413 // whether that would be safe, so doing this for now instead. Also not 3414 // sure whether we really want to mark all lines dirty after an 3415 // interrupt, but until we get better at propagating float damage we 3416 // really do need to do it this way; see comments inside MarkLineDirty. 3417 MarkLineDirtyForInterrupt(line); 3418 } 3419 } 3420 3421 // Handle BR-clearance from the last line of the block 3422 if (inlineFloatClearType != UsedClear::None) { 3423 std::tie(aState.mBCoord, std::ignore) = 3424 aState.ClearFloats(aState.mBCoord, inlineFloatClearType); 3425 } 3426 3427 if (needToRecoverState) { 3428 // Is this expensive? 3429 aState.ReconstructMarginBefore(line); 3430 3431 // Update aState.mPrevChild as if we had reflowed all of the frames in 3432 // the last line. 3433 NS_ASSERTION(line == line_end || line->mFirstChild->GetPrevSibling() == 3434 line.prev()->LastChild(), 3435 "unexpected line frames"); 3436 aState.mPrevChild = line == line_end ? mFrames.LastChild() 3437 : line->mFirstChild->GetPrevSibling(); 3438 } 3439 3440 // We can skip trying to pull up the next line if our height is constrained 3441 // (so we can report being incomplete) and there is no next in flow or we 3442 // were told not to or we know it will be futile, i.e., 3443 // -- the next in flow is not changing 3444 // -- and we cannot have added more space for its first line to be 3445 // pulled up into, 3446 // -- it's an incremental reflow of a descendant 3447 // -- and we didn't reflow any floats (so the available space 3448 // didn't change) 3449 // -- my chain of next-in-flows either has no first line, or its first 3450 // line isn't dirty. 3451 bool heightConstrained = 3452 aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE; 3453 bool skipPull = willReflowAgain && heightConstrained; 3454 if (!skipPull && heightConstrained && aState.mNextInFlow && 3455 (aState.mReflowInput.mFlags.mNextInFlowUntouched && !lastLineMovedUp && 3456 !HasAnyStateBits(NS_FRAME_IS_DIRTY) && !reflowedFloat)) { 3457 // We'll place lineIter at the last line of this block, so that 3458 // nsBlockInFlowLineIterator::Next() will take us to the first 3459 // line of my next-in-flow-chain. (But first, check that I 3460 // have any lines -- if I don't, just bail out of this 3461 // optimization.) 3462 LineIterator lineIter = this->LinesEnd(); 3463 if (lineIter != this->LinesBegin()) { 3464 lineIter--; // I have lines; step back from dummy iterator to last line. 3465 nsBlockInFlowLineIterator bifLineIter(this, lineIter); 3466 3467 // Check for next-in-flow-chain's first line. 3468 // (First, see if there is such a line, and second, see if it's clean) 3469 if (!bifLineIter.Next() || !bifLineIter.GetLine()->IsDirty()) { 3470 skipPull = true; 3471 } 3472 } 3473 } 3474 3475 if (skipPull && aState.mNextInFlow) { 3476 NS_ASSERTION(heightConstrained, "Height should be constrained here\n"); 3477 if (aState.mNextInFlow->IsTrueOverflowContainer()) { 3478 aState.mReflowStatus.SetOverflowIncomplete(); 3479 } else { 3480 aState.mReflowStatus.SetIncomplete(); 3481 } 3482 } 3483 3484 if (!skipPull && aState.mNextInFlow) { 3485 // Pull data from a next-in-flow if there's still room for more 3486 // content here. 3487 while (keepGoing && aState.mNextInFlow) { 3488 // Grab first line from our next-in-flow 3489 nsBlockFrame* nextInFlow = aState.mNextInFlow; 3490 nsLineBox* pulledLine; 3491 nsFrameList pulledFrames; 3492 if (!nextInFlow->mLines.empty()) { 3493 RemoveFirstLine(nextInFlow->mLines, nextInFlow->mFrames, &pulledLine, 3494 &pulledFrames); 3495 ClearLineCursors(); 3496 } else { 3497 // Grab an overflow line if there are any 3498 FrameLines* overflowLines = nextInFlow->GetOverflowLines(); 3499 if (!overflowLines) { 3500 aState.mNextInFlow = 3501 static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow()); 3502 continue; 3503 } 3504 bool last = 3505 RemoveFirstLine(overflowLines->mLines, overflowLines->mFrames, 3506 &pulledLine, &pulledFrames); 3507 if (last) { 3508 nextInFlow->DestroyOverflowLines(); 3509 } 3510 } 3511 3512 if (pulledFrames.IsEmpty()) { 3513 // The line is empty. Try the next one. 3514 NS_ASSERTION( 3515 pulledLine->GetChildCount() == 0 && !pulledLine->mFirstChild, 3516 "bad empty line"); 3517 nextInFlow->FreeLineBox(pulledLine); 3518 continue; 3519 } 3520 3521 if (nextInFlow->MaybeHasLineCursor()) { 3522 if (pulledLine == nextInFlow->GetLineCursorForDisplay()) { 3523 nextInFlow->ClearLineCursorForDisplay(); 3524 } 3525 if (pulledLine == nextInFlow->GetLineCursorForQuery()) { 3526 nextInFlow->ClearLineCursorForQuery(); 3527 } 3528 } 3529 ReparentFrames(pulledFrames, nextInFlow, this); 3530 pulledLine->SetMovedFragments(); 3531 3532 NS_ASSERTION(pulledFrames.LastChild() == pulledLine->LastChild(), 3533 "Unexpected last frame"); 3534 NS_ASSERTION(aState.mPrevChild || mLines.empty(), 3535 "should have a prevchild here"); 3536 NS_ASSERTION(aState.mPrevChild == mFrames.LastChild(), 3537 "Incorrect aState.mPrevChild before inserting line at end"); 3538 3539 // Shift pulledLine's frames into our mFrames list. 3540 mFrames.AppendFrames(nullptr, std::move(pulledFrames)); 3541 3542 // Add line to our line list, and set its last child as our new prev-child 3543 line = mLines.before_insert(LinesEnd(), pulledLine); 3544 aState.mPrevChild = mFrames.LastChild(); 3545 3546 // Reparent floats whose placeholders are in the line. 3547 ReparentFloats(pulledLine->mFirstChild, nextInFlow, true); 3548 3549 DumpLine(aState, pulledLine, deltaBCoord, 0); 3550 #ifdef DEBUG 3551 AutoNoisyIndenter indent2(gNoisyReflow); 3552 #endif 3553 3554 if (aState.mPresContext->HasPendingInterrupt()) { 3555 MarkLineDirtyForInterrupt(line); 3556 } else { 3557 // Now reflow it and any lines that it makes during it's reflow 3558 // (we have to loop here because reflowing the line may cause a new 3559 // line to be created; see SplitLine's callers for examples of 3560 // when this happens). 3561 while (line != LinesEnd()) { 3562 usedOverflowWrap |= ReflowLine(aState, line, &keepGoing); 3563 3564 if (aState.mReflowInput.WillReflowAgainForClearance()) { 3565 line->MarkDirty(); 3566 keepGoing = false; 3567 aState.mReflowStatus.SetIncomplete(); 3568 break; 3569 } 3570 3571 DumpLine(aState, line, deltaBCoord, -1); 3572 if (!keepGoing) { 3573 if (0 == line->GetChildCount()) { 3574 DeleteLine(aState, line, line_end); 3575 } 3576 break; 3577 } 3578 3579 if (LineHasClear(line.get())) { 3580 foundAnyClears = true; 3581 } 3582 3583 if (aState.mPresContext->CheckForInterrupt(this)) { 3584 MarkLineDirtyForInterrupt(line); 3585 break; 3586 } 3587 3588 // If this is an inline frame then its time to stop 3589 ++line; 3590 aState.AdvanceToNextLine(); 3591 } 3592 } 3593 } 3594 3595 if (aState.mReflowStatus.IsIncomplete()) { 3596 aState.mReflowStatus.SetNextInFlowNeedsReflow(); 3597 } // XXXfr shouldn't set this flag when nextinflow has no lines 3598 } 3599 3600 // Handle an odd-ball case: a list-item with no lines 3601 nsIFrame* outsideMarker = GetOutsideMarker(); 3602 if (outsideMarker && mLines.empty()) { 3603 ReflowOutput metrics(aState.mReflowInput); 3604 WritingMode wm = aState.mReflowInput.GetWritingMode(); 3605 ReflowOutsideMarker( 3606 outsideMarker, aState, metrics, 3607 aState.mReflowInput.ComputedPhysicalBorderPadding().top); 3608 NS_ASSERTION(!MarkerIsEmpty(outsideMarker) || metrics.BSize(wm) == 0, 3609 "empty ::marker frame took up space"); 3610 3611 if (!MarkerIsEmpty(outsideMarker)) { 3612 // There are no lines so we have to fake up some y motion so that 3613 // we end up with *some* height. 3614 // (Note: if we're layout-contained, we have to be sure to leave our 3615 // ReflowOutput's BlockStartAscent() (i.e. the baseline) untouched, 3616 // because layout-contained frames have no baseline.) 3617 if (!aState.mReflowInput.mStyleDisplay->IsContainLayout() && 3618 metrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) { 3619 nscoord ascent; 3620 WritingMode wm = aState.mReflowInput.GetWritingMode(); 3621 if (nsLayoutUtils::GetFirstLineBaseline(wm, outsideMarker, &ascent)) { 3622 metrics.SetBlockStartAscent(ascent); 3623 } else { 3624 metrics.SetBlockStartAscent(metrics.BSize(wm)); 3625 } 3626 } 3627 3628 RefPtr<nsFontMetrics> fm = 3629 nsLayoutUtils::GetInflatedFontMetricsForFrame(this); 3630 3631 nscoord minAscent = nsLayoutUtils::GetCenteredFontBaseline( 3632 fm, aState.mMinLineHeight, wm.IsLineInverted()); 3633 nscoord minDescent = aState.mMinLineHeight - minAscent; 3634 3635 aState.mBCoord += 3636 std::max(minAscent, metrics.BlockStartAscent()) + 3637 std::max(minDescent, metrics.BSize(wm) - metrics.BlockStartAscent()); 3638 3639 nscoord offset = minAscent - metrics.BlockStartAscent(); 3640 if (offset > 0) { 3641 outsideMarker->SetRect(outsideMarker->GetRect() + nsPoint(0, offset)); 3642 } 3643 } 3644 } 3645 3646 if (LinesAreEmpty() && ShouldHaveLineIfEmpty()) { 3647 aState.mBCoord += aState.mMinLineHeight; 3648 } 3649 3650 if (foundAnyClears) { 3651 AddStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN); 3652 } else { 3653 RemoveStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN); 3654 } 3655 3656 #ifdef DEBUG 3657 VerifyLines(true); 3658 VerifyOverflowSituation(); 3659 if (gNoisyReflow) { 3660 IndentBy(stdout, gNoiseIndent - 1); 3661 ListTag(stdout); 3662 printf(": done reflowing dirty lines (status=%s)\n", 3663 ToString(aState.mReflowStatus).c_str()); 3664 } 3665 #endif 3666 3667 return usedOverflowWrap; 3668 } 3669 3670 void nsBlockFrame::MarkLineDirtyForInterrupt(nsLineBox* aLine) { 3671 aLine->MarkDirty(); 3672 3673 // Just checking NS_FRAME_IS_DIRTY is ok, because we've already 3674 // marked the lines that need to be marked dirty based on our 3675 // vertical resize stuff. So we'll definitely reflow all those kids; 3676 // the only question is how they should behave. 3677 if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) { 3678 // Mark all our child frames dirty so we make sure to reflow them 3679 // later. 3680 int32_t n = aLine->GetChildCount(); 3681 for (nsIFrame* f = aLine->mFirstChild; n > 0; 3682 f = f->GetNextSibling(), --n) { 3683 f->MarkSubtreeDirty(); 3684 } 3685 // And mark all the floats whose reflows we might be skipping dirty too. 3686 if (aLine->HasFloats()) { 3687 for (nsIFrame* f : aLine->Floats()) { 3688 f->MarkSubtreeDirty(); 3689 } 3690 } 3691 } else { 3692 // Dirty all the descendant lines of block kids to handle float damage, 3693 // since our nsFloatManager will go away by the next time we're reflowing. 3694 // XXXbz Can we do something more like what PropagateFloatDamage does? 3695 // Would need to sort out the exact business with mBlockDelta for that.... 3696 // This marks way too much dirty. If we ever make this better, revisit 3697 // which lines we mark dirty in the interrupt case in ReflowDirtyLines. 3698 nsBlockFrame* bf = do_QueryFrame(aLine->mFirstChild); 3699 if (bf) { 3700 MarkAllDescendantLinesDirty(bf); 3701 } 3702 } 3703 } 3704 3705 void nsBlockFrame::DeleteLine(BlockReflowState& aState, 3706 nsLineList::iterator aLine, 3707 nsLineList::iterator aLineEnd) { 3708 MOZ_ASSERT(0 == aLine->GetChildCount(), "can't delete !empty line"); 3709 if (0 == aLine->GetChildCount()) { 3710 NS_ASSERTION(aState.mCurrentLine == aLine, 3711 "using function more generally than designed, " 3712 "but perhaps OK now"); 3713 nsLineBox* line = aLine; 3714 aLine = mLines.erase(aLine); 3715 FreeLineBox(line); 3716 ClearLineCursors(); 3717 // Mark the previous margin of the next line dirty since we need to 3718 // recompute its top position. 3719 if (aLine != aLineEnd) { 3720 aLine->MarkPreviousMarginDirty(); 3721 } 3722 } 3723 } 3724 3725 /** 3726 * Reflow a line. The line will either contain a single block frame 3727 * or contain 1 or more inline frames. aKeepReflowGoing indicates 3728 * whether or not the caller should continue to reflow more lines. 3729 * Returns true if the reflow used an overflow-wrap breakpoint. 3730 */ 3731 bool nsBlockFrame::ReflowLine(BlockReflowState& aState, LineIterator aLine, 3732 bool* aKeepReflowGoing) { 3733 MOZ_ASSERT(aLine->GetChildCount(), "reflowing empty line"); 3734 3735 // Setup the line-layout for the new line 3736 aState.mCurrentLine = aLine; 3737 aLine->ClearDirty(); 3738 aLine->InvalidateCachedIsEmpty(); 3739 aLine->ClearHadFloatPushed(); 3740 3741 // If this line contains a single block that is hidden by `content-visibility` 3742 // don't reflow the line. If this line contains inlines and the first one is 3743 // hidden by `content-visibility`, all of them are, so avoid reflow in that 3744 // case as well. 3745 // For frames that own anonymous children, even the first child is hidden by 3746 // `content-visibility`, there could be some anonymous children need reflow, 3747 // so we don't skip reflow this line. 3748 nsIFrame* firstChild = aLine->mFirstChild; 3749 if (firstChild->IsHiddenByContentVisibilityOfInFlowParentForLayout() && 3750 !HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES)) { 3751 return false; 3752 } 3753 3754 // Now that we know what kind of line we have, reflow it 3755 bool usedOverflowWrap = false; 3756 if (aLine->IsBlock()) { 3757 ReflowBlockFrame(aState, aLine, aKeepReflowGoing); 3758 } else { 3759 aLine->SetLineWrapped(false); 3760 usedOverflowWrap = ReflowInlineFrames(aState, aLine, aKeepReflowGoing); 3761 3762 // Store the line's float edges for overflow marker analysis if needed. 3763 aLine->ClearFloatEdges(); 3764 if (aState.mFlags.mCanHaveOverflowMarkers) { 3765 WritingMode wm = aLine->mWritingMode; 3766 nsFlowAreaRect r = aState.GetFloatAvailableSpaceForBSize( 3767 wm, aLine->BStart(), aLine->BSize(), nullptr); 3768 if (r.HasFloats()) { 3769 LogicalRect so = aLine->GetOverflowArea(OverflowType::Scrollable, wm, 3770 aLine->mContainerSize); 3771 nscoord s = r.mRect.IStart(wm); 3772 nscoord e = r.mRect.IEnd(wm); 3773 if (so.IEnd(wm) > e || so.IStart(wm) < s) { 3774 // This line is overlapping a float - store the edges marking the area 3775 // between the floats for text-overflow analysis. 3776 aLine->SetFloatEdges(s, e); 3777 } 3778 } 3779 } 3780 } 3781 3782 aLine->ClearMovedFragments(); 3783 3784 return usedOverflowWrap; 3785 } 3786 3787 nsIFrame* nsBlockFrame::PullFrame(BlockReflowState& aState, 3788 LineIterator aLine) { 3789 // First check our remaining lines. 3790 if (LinesEnd() != aLine.next()) { 3791 return PullFrameFrom(aLine, this, aLine.next()); 3792 } 3793 3794 NS_ASSERTION( 3795 !GetOverflowLines(), 3796 "Our overflow lines should have been removed at the start of reflow"); 3797 3798 // Try each next-in-flow. 3799 nsBlockFrame* nextInFlow = aState.mNextInFlow; 3800 while (nextInFlow) { 3801 if (nextInFlow->mLines.empty()) { 3802 nextInFlow->DrainSelfOverflowList(); 3803 } 3804 if (!nextInFlow->mLines.empty()) { 3805 return PullFrameFrom(aLine, nextInFlow, nextInFlow->mLines.begin()); 3806 } 3807 nextInFlow = static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow()); 3808 aState.mNextInFlow = nextInFlow; 3809 } 3810 3811 return nullptr; 3812 } 3813 3814 nsIFrame* nsBlockFrame::PullFrameFrom(nsLineBox* aLine, 3815 nsBlockFrame* aFromContainer, 3816 nsLineList::iterator aFromLine) { 3817 nsLineBox* fromLine = aFromLine; 3818 MOZ_ASSERT(fromLine, "bad line to pull from"); 3819 MOZ_ASSERT(fromLine->GetChildCount(), "empty line"); 3820 MOZ_ASSERT(aLine->GetChildCount(), "empty line"); 3821 MOZ_ASSERT(!HasProperty(LineIteratorProperty()), 3822 "Shouldn't have line iterators mid-reflow"); 3823 3824 NS_ASSERTION(fromLine->IsBlock() == fromLine->mFirstChild->IsBlockOutside(), 3825 "Disagreement about whether it's a block or not"); 3826 3827 if (fromLine->IsBlock()) { 3828 // If our line is not empty and the child in aFromLine is a block 3829 // then we cannot pull up the frame into this line. In this case 3830 // we stop pulling. 3831 return nullptr; 3832 } 3833 // Take frame from fromLine 3834 nsIFrame* frame = fromLine->mFirstChild; 3835 nsIFrame* newFirstChild = frame->GetNextSibling(); 3836 3837 if (aFromContainer != this) { 3838 // The frame is being pulled from a next-in-flow; therefore we need to add 3839 // it to our sibling list. 3840 MOZ_ASSERT(aLine == mLines.back()); 3841 MOZ_ASSERT(aFromLine == aFromContainer->mLines.begin(), 3842 "should only pull from first line"); 3843 aFromContainer->mFrames.RemoveFrame(frame); 3844 3845 // When pushing and pulling frames we need to check for whether any 3846 // views need to be reparented. 3847 ReparentFrame(frame, aFromContainer, this); 3848 mFrames.AppendFrame(nullptr, frame); 3849 3850 // The frame might have (or contain) floats that need to be brought 3851 // over too. (pass 'false' since there are no siblings to check) 3852 ReparentFloats(frame, aFromContainer, false); 3853 } else { 3854 MOZ_ASSERT(aLine == aFromLine.prev()); 3855 } 3856 3857 aLine->NoteFrameAdded(frame); 3858 fromLine->NoteFrameRemoved(frame); 3859 3860 if (fromLine->GetChildCount() > 0) { 3861 // Mark line dirty now that we pulled a child 3862 fromLine->MarkDirty(); 3863 fromLine->mFirstChild = newFirstChild; 3864 } else { 3865 // Free up the fromLine now that it's empty. 3866 // Its bounds might need to be redrawn, though. 3867 if (aFromLine.next() != aFromContainer->mLines.end()) { 3868 aFromLine.next()->MarkPreviousMarginDirty(); 3869 } 3870 aFromContainer->mLines.erase(aFromLine); 3871 // aFromLine is now invalid 3872 aFromContainer->FreeLineBox(fromLine); 3873 } 3874 3875 #ifdef DEBUG 3876 VerifyLines(true); 3877 VerifyOverflowSituation(); 3878 #endif 3879 3880 return frame; 3881 } 3882 3883 void nsBlockFrame::SlideLine(BlockReflowState& aState, nsLineBox* aLine, 3884 nscoord aDeltaBCoord) { 3885 MOZ_ASSERT(aDeltaBCoord != 0, "why slide a line nowhere?"); 3886 3887 // Adjust line state 3888 aLine->SlideBy(aDeltaBCoord, aState.ContainerSize()); 3889 3890 // Adjust the frames in the line 3891 MoveChildFramesOfLine(aLine, aDeltaBCoord); 3892 } 3893 3894 void nsBlockFrame::UpdateLineContainerSize(nsLineBox* aLine, 3895 const nsSize& aNewContainerSize) { 3896 if (aNewContainerSize == aLine->mContainerSize) { 3897 return; 3898 } 3899 3900 // Adjust line state 3901 nsSize sizeDelta = aLine->UpdateContainerSize(aNewContainerSize); 3902 3903 // Changing container width only matters if writing mode is vertical-rl 3904 if (GetWritingMode().IsVerticalRL()) { 3905 MoveChildFramesOfLine(aLine, sizeDelta.width); 3906 } 3907 } 3908 3909 void nsBlockFrame::MoveChildFramesOfLine(nsLineBox* aLine, 3910 nscoord aDeltaBCoord) { 3911 // Adjust the frames in the line 3912 nsIFrame* kid = aLine->mFirstChild; 3913 if (!kid) { 3914 return; 3915 } 3916 3917 WritingMode wm = GetWritingMode(); 3918 LogicalPoint translation(wm, 0, aDeltaBCoord); 3919 3920 if (aLine->IsBlock()) { 3921 if (aDeltaBCoord) { 3922 kid->MovePositionBy(wm, translation); 3923 } 3924 } else { 3925 // Adjust the block-dir coordinate of the frames in the line. 3926 // Note: we need to re-position views even if aDeltaBCoord is 0, because 3927 // one of our parent frames may have moved and so the view's position 3928 // relative to its parent may have changed. 3929 if (aDeltaBCoord) { 3930 int32_t n = aLine->GetChildCount(); 3931 while (--n >= 0) { 3932 kid->MovePositionBy(wm, translation); 3933 kid = kid->GetNextSibling(); 3934 } 3935 } 3936 } 3937 } 3938 3939 static inline bool IsNonAutoNonZeroBSize(const StyleSize& aCoord) { 3940 // The "extremum length" values (see ExtremumLength) that return true from 3941 // 'BehavesLikeInitialValueOnBlockAxis()' were originally aimed at 3942 // inline-size (or width, as it was before logicalization). For now, let them 3943 // return false here, so we treat them like 'auto' pending a real 3944 // implementation. (See bug 1126420.) 3945 if (aCoord.BehavesLikeInitialValueOnBlockAxis()) { 3946 return false; 3947 } 3948 if (aCoord.BehavesLikeStretchOnBlockAxis()) { 3949 // We return true for "stretch" because it's essentially equivalent to 3950 // "100%" for the purposes of this function (and this function returns true 3951 // for nonzero percentage values, in the final return statement below). 3952 return true; 3953 } 3954 MOZ_ASSERT(aCoord.IsLengthPercentage()); 3955 // If we evaluate the length/percent/calc at a percentage basis of 3956 // both nscoord_MAX and 0, and it's zero both ways, then it's a zero 3957 // length, percent, or combination thereof. Test > 0 so we clamp 3958 // negative calc() results to 0. 3959 return aCoord.AsLengthPercentage().Resolve(nscoord_MAX) > 0 || 3960 aCoord.AsLengthPercentage().Resolve(0) > 0; 3961 } 3962 3963 /* virtual */ 3964 bool nsBlockFrame::IsSelfEmpty() { 3965 if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) { 3966 return true; 3967 } 3968 3969 // Blocks which are margin-roots (including inline-blocks) cannot be treated 3970 // as empty for margin-collapsing and other purposes. They're more like 3971 // replaced elements. 3972 if (HasAnyStateBits(NS_BLOCK_BFC)) { 3973 return false; 3974 } 3975 3976 WritingMode wm = GetWritingMode(); 3977 const nsStylePosition* position = StylePosition(); 3978 const auto anchorResolutionParams = AnchorPosResolutionParams::From(this); 3979 const auto bSize = position->BSize(wm, anchorResolutionParams); 3980 3981 if (IsNonAutoNonZeroBSize(*position->MinBSize(wm, anchorResolutionParams)) || 3982 IsNonAutoNonZeroBSize(*bSize)) { 3983 return false; 3984 } 3985 3986 // FIXME: Bug 1646100 - Take intrinsic size into account. 3987 // FIXME: Handle the case that both inline and block sizes are auto. 3988 // https://github.com/w3c/csswg-drafts/issues/5060. 3989 // Note: block-size could be zero or auto/intrinsic keywords here. 3990 if (bSize->BehavesLikeInitialValueOnBlockAxis() && 3991 position->mAspectRatio.HasFiniteRatio()) { 3992 return false; 3993 } 3994 3995 const nsStyleBorder* border = StyleBorder(); 3996 const nsStylePadding* padding = StylePadding(); 3997 3998 if (border->GetComputedBorderWidth(wm.PhysicalSide(LogicalSide::BStart)) != 3999 0 || 4000 border->GetComputedBorderWidth(wm.PhysicalSide(LogicalSide::BEnd)) != 0 || 4001 !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBStart(wm)) || 4002 !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBEnd(wm))) { 4003 return false; 4004 } 4005 4006 nsIFrame* outsideMarker = GetOutsideMarker(); 4007 if (outsideMarker && !MarkerIsEmpty(outsideMarker)) { 4008 return false; 4009 } 4010 4011 return true; 4012 } 4013 4014 bool nsBlockFrame::CachedIsEmpty() { 4015 if (!IsSelfEmpty()) { 4016 return false; 4017 } 4018 for (auto& line : mLines) { 4019 if (!line.CachedIsEmpty()) { 4020 return false; 4021 } 4022 } 4023 return true; 4024 } 4025 4026 bool nsBlockFrame::IsEmpty() { 4027 if (!IsSelfEmpty()) { 4028 return false; 4029 } 4030 return LinesAreEmpty(); 4031 } 4032 4033 bool nsBlockFrame::ShouldApplyBStartMargin(BlockReflowState& aState, 4034 nsLineBox* aLine) { 4035 if (aLine->mFirstChild->IsPageBreakFrame()) { 4036 // A page break frame consumes margins adjacent to it. 4037 // https://drafts.csswg.org/css-break/#break-margins 4038 return false; 4039 } 4040 4041 if (aState.mFlags.mShouldApplyBStartMargin) { 4042 // Apply short-circuit check to avoid searching the line list 4043 return true; 4044 } 4045 4046 if (!aState.IsAdjacentWithBStart()) { 4047 // If we aren't at the start block-coordinate then something of non-zero 4048 // height must have been placed. Therefore the childs block-start margin 4049 // applies. 4050 aState.mFlags.mShouldApplyBStartMargin = true; 4051 return true; 4052 } 4053 4054 // Determine if this line is "essentially" the first line 4055 LineIterator line = LinesBegin(); 4056 if (aState.mFlags.mHasLineAdjacentToTop) { 4057 line = aState.mLineAdjacentToTop; 4058 } 4059 while (line != aLine) { 4060 if (!line->CachedIsEmpty() || line->HasClearance()) { 4061 // A line which precedes aLine is non-empty, or has clearance, 4062 // so therefore the block-start margin applies. 4063 aState.mFlags.mShouldApplyBStartMargin = true; 4064 return true; 4065 } 4066 // No need to apply the block-start margin if the line has floats. We 4067 // should collapse anyway (bug 44419) 4068 ++line; 4069 aState.mFlags.mHasLineAdjacentToTop = true; 4070 aState.mLineAdjacentToTop = line; 4071 } 4072 4073 // The line being reflowed is "essentially" the first line in the 4074 // block. Therefore its block-start margin will be collapsed by the 4075 // generational collapsing logic with its parent (us). 4076 return false; 4077 } 4078 4079 void nsBlockFrame::ReflowBlockFrame(BlockReflowState& aState, 4080 LineIterator aLine, 4081 bool* aKeepReflowGoing) { 4082 MOZ_ASSERT(*aKeepReflowGoing, "bad caller"); 4083 4084 nsIFrame* frame = aLine->mFirstChild; 4085 if (!frame) { 4086 NS_ASSERTION(false, "program error - unexpected empty line"); 4087 return; 4088 } 4089 4090 // If the previous frame was a page-break-frame, then preemptively push this 4091 // frame to the next page. 4092 // This is primarily important for the placeholders for abspos frames, which 4093 // measure as zero height and then would be placed on this page. 4094 if (aState.ContentBSize() != NS_UNCONSTRAINEDSIZE) { 4095 const nsIFrame* const prev = frame->GetPrevSibling(); 4096 if (prev && prev->IsPageBreakFrame()) { 4097 PushTruncatedLine(aState, aLine, aKeepReflowGoing); 4098 return; 4099 } 4100 } 4101 4102 // Prepare the block reflow engine 4103 nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput); 4104 4105 WritingMode cbWM = frame->GetContainingBlock()->GetWritingMode(); 4106 UsedClear clearType = frame->StyleDisplay()->UsedClear(cbWM); 4107 if (aState.mTrailingClearFromPIF != UsedClear::None) { 4108 clearType = nsLayoutUtils::CombineClearType(clearType, 4109 aState.mTrailingClearFromPIF); 4110 aState.mTrailingClearFromPIF = UsedClear::None; 4111 } 4112 4113 // Clear past floats before the block if the clear style is not none 4114 aLine->ClearForcedLineBreak(); 4115 if (clearType != UsedClear::None) { 4116 aLine->SetFloatClearTypeBefore(clearType); 4117 } 4118 4119 // See if we should apply the block-start margin. If the block frame being 4120 // reflowed is a continuation, then we don't apply its block-start margin 4121 // because it's not significant. Otherwise, dig deeper. 4122 bool applyBStartMargin = 4123 !frame->GetPrevContinuation() && ShouldApplyBStartMargin(aState, aLine); 4124 if (applyBStartMargin) { 4125 // The HasClearance setting is only valid if ShouldApplyBStartMargin 4126 // returned false (in which case the block-start margin-root set our 4127 // clearance flag). Otherwise clear it now. We'll set it later on 4128 // ourselves if necessary. 4129 aLine->ClearHasClearance(); 4130 } 4131 bool treatWithClearance = aLine->HasClearance(); 4132 4133 bool mightClearFloats = clearType != UsedClear::None; 4134 nsIFrame* floatAvoidingBlock = nullptr; 4135 if (!nsBlockFrame::BlockCanIntersectFloats(frame)) { 4136 mightClearFloats = true; 4137 floatAvoidingBlock = frame; 4138 } 4139 4140 // If our block-start margin was counted as part of some parent's block-start 4141 // margin collapse, and we are being speculatively reflowed assuming this 4142 // frame DID NOT need clearance, then we need to check that 4143 // assumption. 4144 if (!treatWithClearance && !applyBStartMargin && mightClearFloats && 4145 aState.mReflowInput.mDiscoveredClearance) { 4146 nscoord curBCoord = aState.mBCoord + aState.mPrevBEndMargin.Get(); 4147 if (auto [clearBCoord, result] = 4148 aState.ClearFloats(curBCoord, clearType, floatAvoidingBlock); 4149 result != ClearFloatsResult::BCoordNoChange) { 4150 (void)clearBCoord; 4151 4152 // Only record the first frame that requires clearance 4153 if (!*aState.mReflowInput.mDiscoveredClearance) { 4154 *aState.mReflowInput.mDiscoveredClearance = frame; 4155 } 4156 aState.mPrevChild = frame; 4157 // Exactly what we do now is flexible since we'll definitely be 4158 // reflowed. 4159 return; 4160 } 4161 } 4162 if (treatWithClearance) { 4163 applyBStartMargin = true; 4164 } 4165 4166 nsIFrame* clearanceFrame = nullptr; 4167 const nscoord startingBCoord = aState.mBCoord; 4168 const CollapsingMargin incomingMargin = aState.mPrevBEndMargin; 4169 nscoord clearance; 4170 while (true) { 4171 clearance = 0; 4172 nscoord bStartMargin = 0; 4173 bool mayNeedRetry = false; 4174 bool clearedFloats = false; 4175 bool clearedPushedOrSplitFloat = false; 4176 if (applyBStartMargin) { 4177 // Precompute the blocks block-start margin value so that we can get the 4178 // correct available space (there might be a float that's 4179 // already been placed below the aState.mPrevBEndMargin 4180 4181 // Setup a reflowInput to get the style computed block-start margin 4182 // value. We'll use a reason of `resize' so that we don't fudge 4183 // any incremental reflow input. 4184 4185 // The availSpace here is irrelevant to our needs - all we want 4186 // out if this setup is the block-start margin value which doesn't depend 4187 // on the childs available space. 4188 // XXX building a complete ReflowInput just to get the block-start 4189 // margin seems like a waste. And we do this for almost every block! 4190 WritingMode wm = frame->GetWritingMode(); 4191 LogicalSize availSpace = aState.ContentSize(wm); 4192 ReflowInput reflowInput(aState.mPresContext, aState.mReflowInput, frame, 4193 availSpace); 4194 4195 if (treatWithClearance) { 4196 aState.mBCoord += aState.mPrevBEndMargin.Get(); 4197 aState.mPrevBEndMargin.Zero(); 4198 } 4199 4200 // Now compute the collapsed margin-block-start value into 4201 // aState.mPrevBEndMargin, assuming that all child margins 4202 // collapse down to clearanceFrame. 4203 brc.ComputeCollapsedBStartMargin(reflowInput, &aState.mPrevBEndMargin, 4204 clearanceFrame, &mayNeedRetry); 4205 4206 // XXX optimization; we could check the collapsing children to see if they 4207 // are sure to require clearance, and so avoid retrying them 4208 4209 if (clearanceFrame) { 4210 // Don't allow retries on the second pass. The clearance decisions for 4211 // the blocks whose block-start margins collapse with ours are now 4212 // fixed. 4213 mayNeedRetry = false; 4214 } 4215 4216 if (!treatWithClearance && !clearanceFrame && mightClearFloats) { 4217 // We don't know if we need clearance and this is the first, 4218 // optimistic pass. So determine whether *this block* needs 4219 // clearance. Note that we do not allow the decision for whether 4220 // this block has clearance to change on the second pass; that 4221 // decision is only allowed to be made under the optimistic 4222 // first pass. 4223 nscoord curBCoord = aState.mBCoord + aState.mPrevBEndMargin.Get(); 4224 if (auto [clearBCoord, result] = 4225 aState.ClearFloats(curBCoord, clearType, floatAvoidingBlock); 4226 result != ClearFloatsResult::BCoordNoChange) { 4227 (void)clearBCoord; 4228 4229 // Looks like we need clearance and we didn't know about it already. 4230 // So recompute collapsed margin 4231 treatWithClearance = true; 4232 // Remember this decision, needed for incremental reflow 4233 aLine->SetHasClearance(); 4234 4235 // Apply incoming margins 4236 aState.mBCoord += aState.mPrevBEndMargin.Get(); 4237 aState.mPrevBEndMargin.Zero(); 4238 4239 // Compute the collapsed margin again, ignoring the incoming margin 4240 // this time 4241 mayNeedRetry = false; 4242 brc.ComputeCollapsedBStartMargin(reflowInput, &aState.mPrevBEndMargin, 4243 clearanceFrame, &mayNeedRetry); 4244 } 4245 } 4246 4247 // Temporarily advance the running block-direction value so that the 4248 // GetFloatAvailableSpace method will return the right available space. 4249 // This undone as soon as the horizontal margins are computed. 4250 bStartMargin = aState.mPrevBEndMargin.Get(); 4251 4252 if (treatWithClearance) { 4253 nscoord currentBCoord = aState.mBCoord; 4254 // advance mBCoord to the clear position. 4255 auto [clearBCoord, result] = 4256 aState.ClearFloats(aState.mBCoord, clearType, floatAvoidingBlock); 4257 aState.mBCoord = clearBCoord; 4258 4259 clearedFloats = result != ClearFloatsResult::BCoordNoChange; 4260 clearedPushedOrSplitFloat = 4261 result == ClearFloatsResult::FloatsPushedOrSplit; 4262 4263 // Compute clearance. It's the amount we need to add to the block-start 4264 // border-edge of the frame, after applying collapsed margins 4265 // from the frame and its children, to get it to line up with 4266 // the block-end of the floats. The former is 4267 // currentBCoord + bStartMargin, the latter is the current 4268 // aState.mBCoord. 4269 // Note that negative clearance is possible 4270 clearance = aState.mBCoord - (currentBCoord + bStartMargin); 4271 4272 // Add clearance to our block-start margin while we compute available 4273 // space for the frame 4274 bStartMargin += clearance; 4275 4276 // Note that aState.mBCoord should stay where it is: at the block-start 4277 // border-edge of the frame 4278 } else { 4279 // Advance aState.mBCoord to the block-start border-edge of the frame. 4280 aState.mBCoord += bStartMargin; 4281 } 4282 } 4283 4284 aLine->SetLineIsImpactedByFloat(false); 4285 4286 // Here aState.mBCoord is the block-start border-edge of the block. 4287 // Compute the available space for the block 4288 nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace(cbWM); 4289 WritingMode wm = aState.mReflowInput.GetWritingMode(); 4290 LogicalRect availSpace = aState.ComputeBlockAvailSpace( 4291 frame, floatAvailableSpace, (floatAvoidingBlock)); 4292 4293 // The check for 4294 // (!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats) 4295 // is to some degree out of paranoia: if we reliably eat up block-start 4296 // margins at the top of the page as we ought to, it wouldn't be 4297 // needed. 4298 if ((!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats) && 4299 (availSpace.BSize(wm) < 0 || clearedPushedOrSplitFloat)) { 4300 // We know already that this child block won't fit on this 4301 // page/column due to the block-start margin or the clearance. So we 4302 // need to get out of here now. (If we don't, most blocks will handle 4303 // things fine, and report break-before, but zero-height blocks 4304 // won't, and will thus make their parent overly-large and force 4305 // *it* to be pushed in its entirety.) 4306 aState.mBCoord = startingBCoord; 4307 aState.mPrevBEndMargin = incomingMargin; 4308 if (ShouldAvoidBreakInside(aState.mReflowInput)) { 4309 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing); 4310 } else { 4311 PushTruncatedLine(aState, aLine, aKeepReflowGoing); 4312 } 4313 return; 4314 } 4315 4316 // Now put the block-dir coordinate back to the start of the 4317 // block-start-margin + clearance. 4318 aState.mBCoord -= bStartMargin; 4319 availSpace.BStart(wm) -= bStartMargin; 4320 if (NS_UNCONSTRAINEDSIZE != availSpace.BSize(wm)) { 4321 availSpace.BSize(wm) += bStartMargin; 4322 } 4323 4324 // Construct the reflow input for the block. 4325 Maybe<ReflowInput> childReflowInput; 4326 Maybe<LogicalSize> cbSize; 4327 LogicalSize availSize = availSpace.Size(wm); 4328 bool columnSetWrapperHasNoBSizeLeft = false; 4329 if (Style()->GetPseudoType() == PseudoStyleType::columnContent) { 4330 // Calculate the multicol containing block's block size so that the 4331 // children with percentage block size get correct percentage basis. 4332 const ReflowInput* cbReflowInput = 4333 aState.mReflowInput.mParentReflowInput->mCBReflowInput; 4334 MOZ_ASSERT(cbReflowInput->mFrame->StyleColumn()->IsColumnContainerStyle(), 4335 "Get unexpected reflow input of multicol containing block!"); 4336 4337 // Use column-width as the containing block's inline-size, i.e. the column 4338 // content's computed inline-size. 4339 cbSize.emplace(LogicalSize(wm, aState.mReflowInput.ComputedISize(), 4340 cbReflowInput->ComputedBSize()) 4341 .ConvertTo(frame->GetWritingMode(), wm)); 4342 4343 // If a ColumnSetWrapper is in a balancing column content, it may be 4344 // pushed or pulled back and forth between column contents. Always add 4345 // NS_FRAME_HAS_DIRTY_CHILDREN bit to it so that its ColumnSet children 4346 // can have a chance to reflow under current block size constraint. 4347 if (aState.mReflowInput.mFlags.mIsColumnBalancing && 4348 frame->IsColumnSetWrapperFrame()) { 4349 frame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); 4350 } 4351 } else if (IsColumnSetWrapperFrame()) { 4352 // If we are reflowing our ColumnSet children, we want to apply our block 4353 // size constraint to the available block size when constructing reflow 4354 // input for ColumnSet so that ColumnSet can use it to compute its max 4355 // column block size. 4356 if (frame->IsColumnSetFrame()) { 4357 nscoord contentBSize = aState.mReflowInput.ComputedBSize(); 4358 if (aState.mReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) { 4359 contentBSize = 4360 std::min(contentBSize, aState.mReflowInput.ComputedMaxBSize()); 4361 } 4362 if (contentBSize != NS_UNCONSTRAINEDSIZE) { 4363 // To get the remaining content block-size, subtract the content 4364 // block-size consumed by our previous continuations. 4365 contentBSize -= aState.mConsumedBSize; 4366 4367 // ColumnSet is not the outermost frame in the column container, so it 4368 // cannot have any margin. We don't need to consider any margin that 4369 // can be generated by "box-decoration-break: clone" as we do in 4370 // BlockReflowState::ComputeBlockAvailSpace(). 4371 const nscoord availContentBSize = std::max( 4372 0, contentBSize - (aState.mBCoord - aState.ContentBStart())); 4373 if (availSize.BSize(wm) >= availContentBSize) { 4374 availSize.BSize(wm) = availContentBSize; 4375 columnSetWrapperHasNoBSizeLeft = true; 4376 } 4377 } 4378 } 4379 } 4380 4381 childReflowInput.emplace(aState.mPresContext, aState.mReflowInput, frame, 4382 availSize.ConvertTo(frame->GetWritingMode(), wm), 4383 cbSize); 4384 4385 childReflowInput->mFlags.mColumnSetWrapperHasNoBSizeLeft = 4386 columnSetWrapperHasNoBSizeLeft; 4387 4388 if (aLine->MovedFragments()) { 4389 // We only need to set this the first reflow, since if we reflow 4390 // again (and replace childReflowInput) we'll be reflowing it 4391 // again in the same fragment as the previous time. 4392 childReflowInput->mFlags.mMovedBlockFragments = true; 4393 } 4394 4395 nsFloatManager::SavedState floatManagerState; 4396 nsReflowStatus frameReflowStatus; 4397 do { 4398 if (floatAvailableSpace.HasFloats()) { 4399 // Set if floatAvailableSpace.HasFloats() is true for any 4400 // iteration of the loop. 4401 aLine->SetLineIsImpactedByFloat(true); 4402 } 4403 4404 // We might need to store into mDiscoveredClearance later if it's 4405 // currently null; we want to overwrite any writes that 4406 // brc.ReflowBlock() below does, so we need to remember now 4407 // whether it's empty. 4408 const bool shouldStoreClearance = 4409 aState.mReflowInput.mDiscoveredClearance && 4410 !*aState.mReflowInput.mDiscoveredClearance; 4411 4412 // Reflow the block into the available space 4413 if (mayNeedRetry || floatAvoidingBlock) { 4414 aState.FloatManager()->PushState(&floatManagerState); 4415 } 4416 4417 if (mayNeedRetry) { 4418 childReflowInput->mDiscoveredClearance = &clearanceFrame; 4419 } else if (!applyBStartMargin) { 4420 childReflowInput->mDiscoveredClearance = 4421 aState.mReflowInput.mDiscoveredClearance; 4422 } 4423 4424 frameReflowStatus.Reset(); 4425 brc.ReflowBlock(availSpace, applyBStartMargin, aState.mPrevBEndMargin, 4426 clearance, aLine.get(), *childReflowInput, 4427 frameReflowStatus, aState); 4428 4429 if (frameReflowStatus.IsInlineBreakBefore()) { 4430 // No need to retry this loop if there is a break opportunity before the 4431 // child block. 4432 break; 4433 } 4434 4435 // Now the block has a height. Using that height, get the 4436 // available space again and call ComputeBlockAvailSpace again. 4437 // If ComputeBlockAvailSpace gives a different result, we need to 4438 // reflow again. 4439 if (!floatAvoidingBlock) { 4440 break; 4441 } 4442 4443 LogicalRect oldFloatAvailableSpaceRect(floatAvailableSpace.mRect); 4444 floatAvailableSpace = aState.GetFloatAvailableSpaceForBSize( 4445 cbWM, aState.mBCoord + bStartMargin, brc.GetMetrics().BSize(wm), 4446 &floatManagerState); 4447 NS_ASSERTION(floatAvailableSpace.mRect.BStart(wm) == 4448 oldFloatAvailableSpaceRect.BStart(wm), 4449 "yikes"); 4450 // Restore the height to the position of the next band. 4451 floatAvailableSpace.mRect.BSize(wm) = 4452 oldFloatAvailableSpaceRect.BSize(wm); 4453 // Determine whether the available space shrunk on either side, 4454 // because (the first time round) we now know the block's height, 4455 // and it may intersect additional floats, or (on later 4456 // iterations) because narrowing the width relative to the 4457 // previous time may cause the block to become taller. Note that 4458 // since we're reflowing the block, narrowing the width might also 4459 // make it shorter, so we must pass aCanGrow as true. 4460 if (!AvailableSpaceShrunk(wm, oldFloatAvailableSpaceRect, 4461 floatAvailableSpace.mRect, true)) { 4462 // The size and position we chose before are fine (i.e., they 4463 // don't cause intersecting with floats that requires a change 4464 // in size or position), so we're done. 4465 break; 4466 } 4467 4468 bool advanced = false; 4469 if (!aState.FloatAvoidingBlockFitsInAvailSpace(floatAvoidingBlock, 4470 floatAvailableSpace)) { 4471 // Advance to the next band. 4472 nscoord newBCoord = aState.mBCoord; 4473 if (aState.AdvanceToNextBand(floatAvailableSpace.mRect, &newBCoord)) { 4474 advanced = true; 4475 } 4476 // ClearFloats might be able to advance us further once we're there. 4477 std::tie(aState.mBCoord, std::ignore) = 4478 aState.ClearFloats(newBCoord, UsedClear::None, floatAvoidingBlock); 4479 4480 // Start over with a new available space rect at the new height. 4481 floatAvailableSpace = aState.GetFloatAvailableSpaceWithState( 4482 cbWM, aState.mBCoord, ShapeType::ShapeOutside, &floatManagerState); 4483 } 4484 4485 const LogicalRect oldAvailSpace = availSpace; 4486 availSpace = aState.ComputeBlockAvailSpace(frame, floatAvailableSpace, 4487 (floatAvoidingBlock)); 4488 4489 if (!advanced && availSpace.IsEqualEdges(oldAvailSpace)) { 4490 break; 4491 } 4492 4493 // We need another reflow. 4494 aState.FloatManager()->PopState(&floatManagerState); 4495 4496 if (!treatWithClearance && !applyBStartMargin && 4497 aState.mReflowInput.mDiscoveredClearance) { 4498 // We set shouldStoreClearance above to record only the first 4499 // frame that requires clearance. 4500 if (shouldStoreClearance) { 4501 *aState.mReflowInput.mDiscoveredClearance = frame; 4502 } 4503 aState.mPrevChild = frame; 4504 // Exactly what we do now is flexible since we'll definitely be 4505 // reflowed. 4506 return; 4507 } 4508 4509 if (advanced) { 4510 // We're pushing down the border-box, so we don't apply margin anymore. 4511 // This should never cause us to move up since the call to 4512 // GetFloatAvailableSpaceForBSize above included the margin. 4513 applyBStartMargin = false; 4514 bStartMargin = 0; 4515 treatWithClearance = true; // avoid hitting test above 4516 clearance = 0; 4517 } 4518 4519 childReflowInput.reset(); 4520 childReflowInput.emplace( 4521 aState.mPresContext, aState.mReflowInput, frame, 4522 availSpace.Size(wm).ConvertTo(frame->GetWritingMode(), wm)); 4523 } while (true); 4524 4525 if (mayNeedRetry && clearanceFrame) { 4526 // Found a clearance frame, so we need to reflow |frame| a second time. 4527 // Restore the states and start over again. 4528 aState.FloatManager()->PopState(&floatManagerState); 4529 aState.mBCoord = startingBCoord; 4530 aState.mPrevBEndMargin = incomingMargin; 4531 continue; 4532 } 4533 4534 aState.mPrevChild = frame; 4535 4536 if (childReflowInput->WillReflowAgainForClearance()) { 4537 return; 4538 } 4539 4540 #if defined(REFLOW_STATUS_COVERAGE) 4541 RecordReflowStatus(true, frameReflowStatus); 4542 #endif 4543 4544 if (frameReflowStatus.IsInlineBreakBefore()) { 4545 // None of the child block fits. 4546 if (ShouldAvoidBreakInside(aState.mReflowInput)) { 4547 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing); 4548 } else { 4549 PushTruncatedLine(aState, aLine, aKeepReflowGoing); 4550 } 4551 } else { 4552 // Note: line-break-after a block is a nop 4553 4554 // Try to place the child block. 4555 // Don't force the block to fit if we have positive clearance, because 4556 // pushing it to the next page would give it more room. 4557 // Don't force the block to fit if it's impacted by a float. If it is, 4558 // then pushing it to the next page would give it more room. Note that 4559 // isImpacted doesn't include impact from the block's own floats. 4560 bool forceFit = aState.IsAdjacentWithBStart() && clearance <= 0 && 4561 !floatAvailableSpace.HasFloats(); 4562 CollapsingMargin collapsedBEndMargin; 4563 OverflowAreas overflowAreas; 4564 *aKeepReflowGoing = 4565 brc.PlaceBlock(*childReflowInput, forceFit, aLine.get(), 4566 collapsedBEndMargin, overflowAreas, frameReflowStatus); 4567 if (!frameReflowStatus.IsFullyComplete() && 4568 ShouldAvoidBreakInside(aState.mReflowInput)) { 4569 *aKeepReflowGoing = false; 4570 aLine->MarkDirty(); 4571 } 4572 4573 if (aLine->SetCarriedOutBEndMargin(collapsedBEndMargin)) { 4574 LineIterator nextLine = aLine; 4575 ++nextLine; 4576 if (nextLine != LinesEnd()) { 4577 nextLine->MarkPreviousMarginDirty(); 4578 } 4579 } 4580 4581 if (Style()->GetPseudoType() == PseudoStyleType::scrolledContent) { 4582 auto lineFrameBounds = GetLineFrameInFlowBounds(*aLine, *frame); 4583 MOZ_ASSERT(aLine->GetChildCount() == 1, 4584 "More than one child in block line?"); 4585 // Inline-line (i.e. Multiple frames in one line) handled in one of 4586 // other callsites. 4587 aLine->SetInFlowChildBounds(lineFrameBounds); 4588 } 4589 4590 aLine->SetOverflowAreas(overflowAreas); 4591 if (*aKeepReflowGoing) { 4592 // Some of the child block fit 4593 4594 // Advance to new Y position 4595 nscoord newBCoord = aLine->BEnd(); 4596 aState.mBCoord = newBCoord; 4597 4598 // Continue the block frame now if it didn't completely fit in 4599 // the available space. 4600 if (!frameReflowStatus.IsFullyComplete()) { 4601 bool madeContinuation = CreateContinuationFor(aState, nullptr, frame); 4602 4603 nsIFrame* nextFrame = frame->GetNextInFlow(); 4604 NS_ASSERTION(nextFrame, 4605 "We're supposed to have a next-in-flow by now"); 4606 4607 if (frameReflowStatus.IsIncomplete()) { 4608 // If nextFrame used to be an overflow container, make it a normal 4609 // block 4610 if (!madeContinuation && 4611 nextFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { 4612 nsOverflowContinuationTracker::AutoFinish fini( 4613 aState.mOverflowTracker, frame); 4614 nsContainerFrame* parent = nextFrame->GetParent(); 4615 parent->StealFrame(nextFrame); 4616 if (parent != this) { 4617 ReparentFrame(nextFrame, parent, this); 4618 } 4619 mFrames.InsertFrame(nullptr, frame, nextFrame); 4620 madeContinuation = true; // needs to be added to mLines 4621 nextFrame->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); 4622 frameReflowStatus.SetNextInFlowNeedsReflow(); 4623 } 4624 4625 // Push continuation to a new line, but only if we actually made 4626 // one. 4627 if (madeContinuation) { 4628 nsLineBox* line = NewLineBox(nextFrame, true); 4629 mLines.after_insert(aLine, line); 4630 } 4631 4632 PushTruncatedLine(aState, aLine.next(), aKeepReflowGoing); 4633 4634 // If we need to reflow the continuation of the block child, 4635 // then we'd better reflow our continuation 4636 if (frameReflowStatus.NextInFlowNeedsReflow()) { 4637 aState.mReflowStatus.SetNextInFlowNeedsReflow(); 4638 // We also need to make that continuation's line dirty so 4639 // it gets reflowed when we reflow our next in flow. The 4640 // nif's line must always be either a line of the nif's 4641 // parent block (only if we didn't make a continuation) or 4642 // else one of our own overflow lines. In the latter case 4643 // the line is already marked dirty, so just handle the 4644 // first case. 4645 if (!madeContinuation) { 4646 nsBlockFrame* nifBlock = do_QueryFrame(nextFrame->GetParent()); 4647 NS_ASSERTION( 4648 nifBlock, 4649 "A block's child's next in flow's parent must be a block!"); 4650 for (auto& line : nifBlock->Lines()) { 4651 if (line.Contains(nextFrame)) { 4652 line.MarkDirty(); 4653 break; 4654 } 4655 } 4656 } 4657 } 4658 4659 // The block-end margin for a block is only applied on the last 4660 // flow block. Since we just continued the child block frame, 4661 // we know that line->mFirstChild is not the last flow block 4662 // therefore zero out the running margin value. 4663 #ifdef NOISY_BLOCK_DIR_MARGINS 4664 ListTag(stdout); 4665 printf(": reflow incomplete, frame="); 4666 frame->ListTag(stdout); 4667 printf(" prevBEndMargin=%d, setting to zero\n", 4668 aState.mPrevBEndMargin.get()); 4669 #endif 4670 aState.mPrevBEndMargin.Zero(); 4671 } else { // frame is complete but its overflow is not complete 4672 // Disconnect the next-in-flow and put it in our overflow tracker 4673 if (!madeContinuation && 4674 !nextFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { 4675 // It already exists, but as a normal next-in-flow, so we need 4676 // to dig it out of the child lists. 4677 nextFrame->GetParent()->StealFrame(nextFrame); 4678 } else if (madeContinuation) { 4679 mFrames.RemoveFrame(nextFrame); 4680 } 4681 4682 // Put it in our overflow list 4683 aState.mOverflowTracker->Insert(nextFrame, frameReflowStatus); 4684 aState.mReflowStatus.MergeCompletionStatusFrom(frameReflowStatus); 4685 4686 #ifdef NOISY_BLOCK_DIR_MARGINS 4687 ListTag(stdout); 4688 printf(": reflow complete but overflow incomplete for "); 4689 frame->ListTag(stdout); 4690 printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n", 4691 aState.mPrevBEndMargin.get(), collapsedBEndMargin.get()); 4692 #endif 4693 aState.mPrevBEndMargin = collapsedBEndMargin; 4694 } 4695 } else { // frame is fully complete 4696 #ifdef NOISY_BLOCK_DIR_MARGINS 4697 ListTag(stdout); 4698 printf(": reflow complete for "); 4699 frame->ListTag(stdout); 4700 printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n", 4701 aState.mPrevBEndMargin.get(), collapsedBEndMargin.get()); 4702 #endif 4703 aState.mPrevBEndMargin = collapsedBEndMargin; 4704 } 4705 #ifdef NOISY_BLOCK_DIR_MARGINS 4706 ListTag(stdout); 4707 printf(": frame="); 4708 frame->ListTag(stdout); 4709 printf(" carriedOutBEndMargin=%d collapsedBEndMargin=%d => %d\n", 4710 brc.GetCarriedOutBEndMargin().get(), collapsedBEndMargin.get(), 4711 aState.mPrevBEndMargin.get()); 4712 #endif 4713 } else { 4714 if (!frameReflowStatus.IsFullyComplete()) { 4715 // The frame reported an incomplete status, but then it also didn't 4716 // fit. This means we need to reflow it again so that it can 4717 // (again) report the incomplete status. 4718 frame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); 4719 } 4720 4721 if ((aLine == mLines.front() && !GetPrevInFlow()) || 4722 ShouldAvoidBreakInside(aState.mReflowInput)) { 4723 // If it's our very first line *or* we're not at the top of the page 4724 // and we have page-break-inside:avoid, then we need to be pushed to 4725 // our parent's next-in-flow. 4726 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing); 4727 } else { 4728 // Push the line that didn't fit and any lines that follow it 4729 // to our next-in-flow. 4730 PushTruncatedLine(aState, aLine, aKeepReflowGoing); 4731 } 4732 } 4733 } 4734 break; // out of the reflow retry loop 4735 } 4736 4737 #ifdef DEBUG 4738 VerifyLines(true); 4739 #endif 4740 } 4741 4742 // Returns true if an overflow-wrap break was used. 4743 bool nsBlockFrame::ReflowInlineFrames(BlockReflowState& aState, 4744 LineIterator aLine, 4745 bool* aKeepReflowGoing) { 4746 *aKeepReflowGoing = true; 4747 bool usedOverflowWrap = false; 4748 4749 aLine->SetLineIsImpactedByFloat(false); 4750 4751 // Setup initial coordinate system for reflowing the inline frames 4752 // into. Apply a previous block frame's block-end margin first. 4753 if (ShouldApplyBStartMargin(aState, aLine)) { 4754 aState.mBCoord += aState.mPrevBEndMargin.Get(); 4755 } 4756 nsFlowAreaRect floatAvailableSpace = 4757 aState.GetFloatAvailableSpace(GetWritingMode()); 4758 4759 LineReflowStatus lineReflowStatus; 4760 do { 4761 nscoord availableSpaceBSize = 0; 4762 aState.mLineBSize.reset(); 4763 do { 4764 bool allowPullUp = true; 4765 nsIFrame* forceBreakInFrame = nullptr; 4766 int32_t forceBreakOffset = -1; 4767 gfxBreakPriority forceBreakPriority = gfxBreakPriority::eNoBreak; 4768 do { 4769 nsFloatManager::SavedState floatManagerState; 4770 aState.FloatManager()->PushState(&floatManagerState); 4771 4772 // Once upon a time we allocated the first 30 nsLineLayout objects 4773 // on the stack, and then we switched to the heap. At that time 4774 // these objects were large (1100 bytes on a 32 bit system). 4775 // Then the nsLineLayout object was shrunk to 156 bytes by 4776 // removing some internal buffers. Given that it is so much 4777 // smaller, the complexity of 2 different ways of allocating 4778 // no longer makes sense. Now we always allocate on the stack. 4779 nsLineLayout lineLayout(aState.mPresContext, aState.FloatManager(), 4780 aState.mReflowInput, &aLine, nullptr); 4781 lineLayout.Init(&aState, aState.mMinLineHeight, aState.mLineNumber); 4782 if (forceBreakInFrame) { 4783 lineLayout.ForceBreakAtPosition(forceBreakInFrame, forceBreakOffset); 4784 } 4785 DoReflowInlineFrames(aState, lineLayout, aLine, floatAvailableSpace, 4786 availableSpaceBSize, &floatManagerState, 4787 aKeepReflowGoing, &lineReflowStatus, allowPullUp); 4788 usedOverflowWrap = lineLayout.EndLineReflow(); 4789 4790 if (LineReflowStatus::RedoNoPull == lineReflowStatus || 4791 LineReflowStatus::RedoMoreFloats == lineReflowStatus || 4792 LineReflowStatus::RedoNextBand == lineReflowStatus) { 4793 if (lineLayout.NeedsBackup()) { 4794 NS_ASSERTION(!forceBreakInFrame, 4795 "Backing up twice; this should never be necessary"); 4796 // If there is no saved break position, then this will set 4797 // set forceBreakInFrame to null and we won't back up, which is 4798 // correct. 4799 forceBreakInFrame = lineLayout.GetLastOptionalBreakPosition( 4800 &forceBreakOffset, &forceBreakPriority); 4801 } else { 4802 forceBreakInFrame = nullptr; 4803 } 4804 // restore the float manager state 4805 aState.FloatManager()->PopState(&floatManagerState); 4806 // Clear out float lists 4807 aState.mCurrentLineFloats.Clear(); 4808 aState.mBelowCurrentLineFloats.Clear(); 4809 aState.mNoWrapFloats.Clear(); 4810 } 4811 4812 // Don't allow pullup on a subsequent LineReflowStatus::RedoNoPull pass 4813 allowPullUp = false; 4814 } while (LineReflowStatus::RedoNoPull == lineReflowStatus); 4815 } while (LineReflowStatus::RedoMoreFloats == lineReflowStatus); 4816 } while (LineReflowStatus::RedoNextBand == lineReflowStatus); 4817 4818 return usedOverflowWrap; 4819 } 4820 4821 void nsBlockFrame::SetBreakBeforeStatusBeforeLine(BlockReflowState& aState, 4822 LineIterator aLine, 4823 bool* aKeepReflowGoing) { 4824 aState.mReflowStatus.SetInlineLineBreakBeforeAndReset(); 4825 // Reflow the line again when we reflow at our new position. 4826 aLine->MarkDirty(); 4827 *aKeepReflowGoing = false; 4828 } 4829 4830 void nsBlockFrame::PushTruncatedLine( 4831 BlockReflowState& aState, LineIterator aLine, bool* aKeepReflowGoing, 4832 ComputeNewPageNameIfNeeded aComputeNewPageName) { 4833 PushLines(aState, aLine.prev()); 4834 *aKeepReflowGoing = false; 4835 4836 if (aComputeNewPageName == ComputeNewPageNameIfNeeded::Yes) { 4837 // mCanHaveClassABreakpoints can only be true during paginated reflow, and 4838 // we expect this function to only be called when the available bsize is 4839 // constrained. 4840 const WritingMode wm = GetWritingMode(); 4841 const bool canBreakForPageNames = 4842 aState.mReflowInput.mFlags.mCanHaveClassABreakpoints && 4843 !PresShell()->GetRootFrame()->GetWritingMode().IsOrthogonalTo(wm); 4844 if (canBreakForPageNames) { 4845 PresShell()->FrameConstructor()->MaybeSetNextPageContentFramePageName( 4846 aLine->mFirstChild); 4847 } 4848 } 4849 aState.mReflowStatus.SetIncomplete(); 4850 } 4851 4852 void nsBlockFrame::DoReflowInlineFrames( 4853 BlockReflowState& aState, nsLineLayout& aLineLayout, LineIterator aLine, 4854 nsFlowAreaRect& aFloatAvailableSpace, nscoord& aAvailableSpaceBSize, 4855 nsFloatManager::SavedState* aFloatStateBeforeLine, bool* aKeepReflowGoing, 4856 LineReflowStatus* aLineReflowStatus, bool aAllowPullUp) { 4857 // Forget all of the floats on the line 4858 aLine->ClearFloats(); 4859 aState.mFloatOverflowAreas.Clear(); 4860 4861 // We need to set this flag on the line if any of our reflow passes 4862 // are impacted by floats. 4863 if (aFloatAvailableSpace.HasFloats()) { 4864 aLine->SetLineIsImpactedByFloat(true); 4865 } 4866 #ifdef REALLY_NOISY_REFLOW 4867 printf("nsBlockFrame::DoReflowInlineFrames %p impacted = %d\n", this, 4868 aFloatAvailableSpace.HasFloats()); 4869 #endif 4870 4871 WritingMode outerWM = aState.mReflowInput.GetWritingMode(); 4872 WritingMode lineWM = WritingModeForLine(outerWM, aLine->mFirstChild); 4873 LogicalRect lineRect = aFloatAvailableSpace.mRect.ConvertTo( 4874 lineWM, outerWM, aState.ContainerSize()); 4875 4876 nscoord iStart = lineRect.IStart(lineWM); 4877 nscoord availISize = lineRect.ISize(lineWM); 4878 nscoord availBSize; 4879 if (aState.mReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) { 4880 availBSize = NS_UNCONSTRAINEDSIZE; 4881 } else { 4882 /* XXX get the height right! */ 4883 availBSize = lineRect.BSize(lineWM); 4884 } 4885 4886 // Make sure to enable resize optimization before we call BeginLineReflow 4887 // because it might get disabled there 4888 aLine->EnableResizeReflowOptimization(); 4889 4890 aLineLayout.BeginLineReflow(iStart, aState.mBCoord, availISize, availBSize, 4891 aFloatAvailableSpace.HasFloats(), 4892 false /*XXX isTopOfPage*/, lineWM, 4893 aState.mContainerSize, aState.mInsetForBalance); 4894 4895 aState.mFlags.mIsLineLayoutEmpty = false; 4896 4897 // XXX Unfortunately we need to know this before reflowing the first 4898 // inline frame in the line. FIX ME. 4899 if (0 == aLineLayout.GetLineNumber() && 4900 HasAllStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD | 4901 NS_BLOCK_HAS_FIRST_LETTER_STYLE)) { 4902 aLineLayout.SetFirstLetterStyleOK(true); 4903 } 4904 NS_ASSERTION(!(HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD) && 4905 GetPrevContinuation()), 4906 "first letter child bit should only be on first continuation"); 4907 4908 // Reflow the frames that are already on the line first 4909 LineReflowStatus lineReflowStatus = LineReflowStatus::OK; 4910 int32_t i; 4911 nsIFrame* frame = aLine->mFirstChild; 4912 4913 if (aFloatAvailableSpace.HasFloats()) { 4914 // There is a soft break opportunity at the start of the line, because 4915 // we can always move this line down below float(s). 4916 if (aLineLayout.NotifyOptionalBreakPosition( 4917 frame, 0, true, gfxBreakPriority::eNormalBreak)) { 4918 lineReflowStatus = LineReflowStatus::RedoNextBand; 4919 } 4920 } 4921 4922 // need to repeatedly call GetChildCount here, because the child 4923 // count can change during the loop! 4924 for (i = 0; 4925 LineReflowStatus::OK == lineReflowStatus && i < aLine->GetChildCount(); 4926 i++, frame = frame->GetNextSibling()) { 4927 SetLineCursorForDisplay(aLine); 4928 ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus); 4929 if (LineReflowStatus::OK != lineReflowStatus) { 4930 // It is possible that one or more of next lines are empty 4931 // (because of DeleteNextInFlowChild). If so, delete them now 4932 // in case we are finished. 4933 ++aLine; 4934 while ((aLine != LinesEnd()) && (0 == aLine->GetChildCount())) { 4935 // XXX Is this still necessary now that DeleteNextInFlowChild 4936 // uses DoRemoveFrame? 4937 nsLineBox* toremove = aLine; 4938 aLine = mLines.erase(aLine); 4939 NS_ASSERTION(nullptr == toremove->mFirstChild, "bad empty line"); 4940 FreeLineBox(toremove); 4941 ClearLineCursors(); 4942 } 4943 --aLine; 4944 4945 NS_ASSERTION(lineReflowStatus != LineReflowStatus::Truncated, 4946 "ReflowInlineFrame should never determine that a line " 4947 "needs to go to the next page/column"); 4948 } 4949 } 4950 4951 // Don't pull up new frames into lines with continuation placeholders 4952 if (aAllowPullUp) { 4953 // Pull frames and reflow them until we can't 4954 while (LineReflowStatus::OK == lineReflowStatus) { 4955 frame = PullFrame(aState, aLine); 4956 if (!frame) { 4957 break; 4958 } 4959 4960 while (LineReflowStatus::OK == lineReflowStatus) { 4961 int32_t oldCount = aLine->GetChildCount(); 4962 SetLineCursorForDisplay(aLine); 4963 ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus); 4964 if (aLine->GetChildCount() != oldCount) { 4965 // We just created a continuation for aFrame AND its going 4966 // to end up on this line (e.g. :first-letter 4967 // situation). Therefore we have to loop here before trying 4968 // to pull another frame. 4969 frame = frame->GetNextSibling(); 4970 } else { 4971 break; 4972 } 4973 } 4974 } 4975 } 4976 ClearLineCursors(); 4977 4978 aState.mFlags.mIsLineLayoutEmpty = aLineLayout.LineIsEmpty(); 4979 4980 // We only need to backup if the line isn't going to be reflowed again anyway 4981 bool needsBackup = aLineLayout.NeedsBackup() && 4982 (lineReflowStatus == LineReflowStatus::Stop || 4983 lineReflowStatus == LineReflowStatus::OK); 4984 if (needsBackup && aLineLayout.HaveForcedBreakPosition()) { 4985 NS_WARNING( 4986 "We shouldn't be backing up more than once! " 4987 "Someone must have set a break opportunity beyond the available width, " 4988 "even though there were better break opportunities before it"); 4989 needsBackup = false; 4990 } 4991 if (needsBackup) { 4992 // We need to try backing up to before a text run 4993 // XXX It's possible, in fact not unusual, for the break opportunity to 4994 // already be the end of the line. We should detect that and optimize to not 4995 // re-do the line. 4996 if (aLineLayout.HasOptionalBreakPosition()) { 4997 // We can back up! 4998 lineReflowStatus = LineReflowStatus::RedoNoPull; 4999 } 5000 } else { 5001 // In case we reflow this line again, remember that we don't 5002 // need to force any breaking 5003 aLineLayout.ClearOptionalBreakPosition(); 5004 } 5005 5006 if (LineReflowStatus::RedoNextBand == lineReflowStatus) { 5007 // This happens only when we have a line that is impacted by 5008 // floats and the first element in the line doesn't fit with 5009 // the floats. 5010 // 5011 // If there's block space available, we either try to reflow the line 5012 // past the current band (if it's non-zero and the band definitely won't 5013 // widen around a shape-outside), otherwise we try one pixel down. If 5014 // there's no block space available, we push the line to the next 5015 // page/column. 5016 NS_ASSERTION( 5017 NS_UNCONSTRAINEDSIZE != aFloatAvailableSpace.mRect.BSize(outerWM), 5018 "unconstrained block size on totally empty line"); 5019 5020 // See the analogous code for blocks in BlockReflowState::ClearFloats. 5021 nscoord bandBSize = aFloatAvailableSpace.mRect.BSize(outerWM); 5022 if (bandBSize > 0 || 5023 NS_UNCONSTRAINEDSIZE == aState.mReflowInput.AvailableBSize()) { 5024 NS_ASSERTION(bandBSize == 0 || aFloatAvailableSpace.HasFloats(), 5025 "redo line on totally empty line with non-empty band..."); 5026 // We should never hit this case if we've placed floats on the 5027 // line; if we have, then the GetFloatAvailableSpace call is wrong 5028 // and needs to happen after the caller pops the float manager 5029 // state. 5030 aState.FloatManager()->AssertStateMatches(aFloatStateBeforeLine); 5031 5032 if (!aFloatAvailableSpace.MayWiden() && bandBSize > 0) { 5033 // Move it down far enough to clear the current band. 5034 aState.mBCoord += bandBSize; 5035 } else { 5036 // Move it down by one dev pixel. 5037 aState.mBCoord += aState.mPresContext->DevPixelsToAppUnits(1); 5038 } 5039 5040 aFloatAvailableSpace = aState.GetFloatAvailableSpace(GetWritingMode()); 5041 } else { 5042 // There's nowhere to retry placing the line, so we want to push 5043 // it to the next page/column where its contents can fit not 5044 // next to a float. 5045 lineReflowStatus = LineReflowStatus::Truncated; 5046 PushTruncatedLine(aState, aLine, aKeepReflowGoing); 5047 } 5048 5049 // XXX: a small optimization can be done here when paginating: 5050 // if the new Y coordinate is past the end of the block then 5051 // push the line and return now instead of later on after we are 5052 // past the float. 5053 } else if (LineReflowStatus::Truncated != lineReflowStatus && 5054 LineReflowStatus::RedoNoPull != lineReflowStatus) { 5055 // If we are propagating out a break-before status then there is 5056 // no point in placing the line. 5057 if (!aState.mReflowStatus.IsInlineBreakBefore()) { 5058 if (!PlaceLine(aState, aLineLayout, aLine, aFloatStateBeforeLine, 5059 aFloatAvailableSpace, aAvailableSpaceBSize, 5060 aKeepReflowGoing)) { 5061 lineReflowStatus = LineReflowStatus::RedoMoreFloats; 5062 // PlaceLine already called GetFloatAvailableSpaceForBSize or its 5063 // variant for us. 5064 } 5065 } 5066 } 5067 #ifdef DEBUG 5068 if (gNoisyReflow) { 5069 IndentBy(stdout, gNoiseIndent); 5070 fmt::println(FMT_STRING("LineReflowStatus={}"), ToString(lineReflowStatus)); 5071 } 5072 #endif 5073 5074 if (aLineLayout.GetDirtyNextLine()) { 5075 // aLine may have been pushed to the overflow lines. 5076 FrameLines* overflowLines = GetOverflowLines(); 5077 // We can't just compare iterators front() to aLine here, since they may be 5078 // in different lists. 5079 bool pushedToOverflowLines = 5080 overflowLines && overflowLines->mLines.front() == aLine.get(); 5081 if (pushedToOverflowLines) { 5082 // aLine is stale, it's associated with the main line list but it should 5083 // be associated with the overflow line list now 5084 aLine = overflowLines->mLines.begin(); 5085 } 5086 nsBlockInFlowLineIterator iter(this, aLine, pushedToOverflowLines); 5087 if (iter.Next() && iter.GetLine()->IsInline()) { 5088 iter.GetLine()->MarkDirty(); 5089 if (iter.GetContainer() != this) { 5090 aState.mReflowStatus.SetNextInFlowNeedsReflow(); 5091 } 5092 } 5093 } 5094 5095 *aLineReflowStatus = lineReflowStatus; 5096 } 5097 5098 /** 5099 * Reflow an inline frame. The reflow status is mapped from the frames 5100 * reflow status to the lines reflow status (not to our reflow status). 5101 * The line reflow status is simple: true means keep placing frames 5102 * on the line; false means don't (the line is done). If the line 5103 * has some sort of breaking affect then aLine's break-type will be set 5104 * to something other than UsedClear::None. 5105 */ 5106 void nsBlockFrame::ReflowInlineFrame(BlockReflowState& aState, 5107 nsLineLayout& aLineLayout, 5108 LineIterator aLine, nsIFrame* aFrame, 5109 LineReflowStatus* aLineReflowStatus) { 5110 MOZ_ASSERT(aFrame); 5111 *aLineReflowStatus = LineReflowStatus::OK; 5112 5113 #ifdef NOISY_FIRST_LETTER 5114 ListTag(stdout); 5115 printf(": reflowing "); 5116 aFrame->ListTag(stdout); 5117 printf(" reflowingFirstLetter=%s\n", 5118 aLineLayout.GetFirstLetterStyleOK() ? "on" : "off"); 5119 #endif 5120 5121 if (aFrame->IsPlaceholderFrame()) { 5122 auto ph = static_cast<nsPlaceholderFrame*>(aFrame); 5123 ph->ForgetLineIsEmptySoFar(); 5124 } 5125 5126 // Reflow the inline frame 5127 nsReflowStatus frameReflowStatus; 5128 bool pushedFrame; 5129 aLineLayout.ReflowFrame(aFrame, frameReflowStatus, nullptr, pushedFrame); 5130 5131 if (frameReflowStatus.NextInFlowNeedsReflow()) { 5132 aLineLayout.SetDirtyNextLine(); 5133 } 5134 5135 #ifdef REALLY_NOISY_REFLOW 5136 aFrame->ListTag(stdout); 5137 printf(": status=%s\n", ToString(frameReflowStatus).c_str()); 5138 #endif 5139 5140 #if defined(REFLOW_STATUS_COVERAGE) 5141 RecordReflowStatus(false, frameReflowStatus); 5142 #endif 5143 5144 // Send post-reflow notification 5145 aState.mPrevChild = aFrame; 5146 5147 /* XXX 5148 This is where we need to add logic to handle some odd behavior. 5149 For one thing, we should usually place at least one thing next 5150 to a left float, even when that float takes up all the width on a line. 5151 see bug 22496 5152 */ 5153 5154 // Process the child frames reflow status. There are 5 cases: 5155 // complete, not-complete, break-before, break-after-complete, 5156 // break-after-not-complete. There are two situations: we are a 5157 // block or we are an inline. This makes a total of 10 cases 5158 // (fortunately, there is some overlap). 5159 aLine->ClearForcedLineBreak(); 5160 if (frameReflowStatus.IsInlineBreak() || 5161 aState.mTrailingClearFromPIF != UsedClear::None) { 5162 // Always abort the line reflow (because a line break is the 5163 // minimal amount of break we do). 5164 *aLineReflowStatus = LineReflowStatus::Stop; 5165 5166 // XXX what should aLine's break-type be set to in all these cases? 5167 if (frameReflowStatus.IsInlineBreakBefore()) { 5168 // Break-before cases. 5169 if (aFrame == aLine->mFirstChild) { 5170 // If we break before the first frame on the line then we must 5171 // be trying to place content where there's no room (e.g. on a 5172 // line with wide floats). Inform the caller to reflow the 5173 // line after skipping past a float. 5174 *aLineReflowStatus = LineReflowStatus::RedoNextBand; 5175 } else { 5176 // It's not the first child on this line so go ahead and split 5177 // the line. We will see the frame again on the next-line. 5178 SplitLine(aState, aLineLayout, aLine, aFrame, aLineReflowStatus); 5179 5180 // If we're splitting the line because the frame didn't fit and it 5181 // was pushed, then mark the line as having word wrapped. We need to 5182 // know that if we're shrink wrapping our width 5183 if (pushedFrame) { 5184 aLine->SetLineWrapped(true); 5185 } 5186 } 5187 } else { 5188 MOZ_ASSERT(frameReflowStatus.IsInlineBreakAfter() || 5189 aState.mTrailingClearFromPIF != UsedClear::None, 5190 "We should've handled inline break-before in the if-branch!"); 5191 5192 // If a float split and its prev-in-flow was followed by a <BR>, then 5193 // combine the <BR>'s float clear type with the inline's float clear type 5194 // (the inline will be the very next frame after the split float). 5195 UsedClear clearType = frameReflowStatus.FloatClearType(); 5196 if (aState.mTrailingClearFromPIF != UsedClear::None) { 5197 clearType = nsLayoutUtils::CombineClearType( 5198 clearType, aState.mTrailingClearFromPIF); 5199 aState.mTrailingClearFromPIF = UsedClear::None; 5200 } 5201 // Break-after cases 5202 if (clearType != UsedClear::None || aLineLayout.GetLineEndsInBR()) { 5203 aLine->SetForcedLineBreakAfter(clearType); 5204 } 5205 if (frameReflowStatus.IsComplete()) { 5206 // Split line, but after the frame just reflowed 5207 SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(), 5208 aLineReflowStatus); 5209 5210 if (frameReflowStatus.IsInlineBreakAfter() && 5211 !aLineLayout.GetLineEndsInBR()) { 5212 aLineLayout.SetDirtyNextLine(); 5213 } 5214 } 5215 } 5216 } 5217 5218 if (!frameReflowStatus.IsFullyComplete()) { 5219 // Create a continuation for the incomplete frame. Note that the 5220 // frame may already have a continuation. 5221 CreateContinuationFor(aState, aLine, aFrame); 5222 5223 // Remember that the line has wrapped 5224 if (!aLineLayout.GetLineEndsInBR()) { 5225 aLine->SetLineWrapped(true); 5226 } 5227 5228 // If we just ended a first-letter frame or reflowed a placeholder then 5229 // don't split the line and don't stop the line reflow... 5230 // But if we are going to stop anyways we'd better split the line. 5231 if ((!frameReflowStatus.FirstLetterComplete() && 5232 !aFrame->IsPlaceholderFrame()) || 5233 *aLineReflowStatus == LineReflowStatus::Stop) { 5234 // Split line after the current frame 5235 *aLineReflowStatus = LineReflowStatus::Stop; 5236 SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(), 5237 aLineReflowStatus); 5238 } 5239 } 5240 } 5241 5242 bool nsBlockFrame::CreateContinuationFor(BlockReflowState& aState, 5243 nsLineBox* aLine, nsIFrame* aFrame) { 5244 nsIFrame* newFrame = nullptr; 5245 5246 if (!aFrame->GetNextInFlow()) { 5247 newFrame = 5248 PresShell()->FrameConstructor()->CreateContinuingFrame(aFrame, this); 5249 5250 mFrames.InsertFrame(nullptr, aFrame, newFrame); 5251 5252 if (aLine) { 5253 aLine->NoteFrameAdded(newFrame); 5254 } 5255 } 5256 #ifdef DEBUG 5257 VerifyLines(false); 5258 #endif 5259 return !!newFrame; 5260 } 5261 5262 void nsBlockFrame::SplitFloat(BlockReflowState& aState, nsIFrame* aFloat, 5263 const nsReflowStatus& aFloatStatus) { 5264 MOZ_ASSERT(!aFloatStatus.IsFullyComplete(), 5265 "why split the frame if it's fully complete?"); 5266 MOZ_ASSERT(aState.mBlock == this); 5267 5268 nsIFrame* nextInFlow = aFloat->GetNextInFlow(); 5269 if (nextInFlow) { 5270 nsContainerFrame* oldParent = nextInFlow->GetParent(); 5271 oldParent->StealFrame(nextInFlow); 5272 if (oldParent != this) { 5273 ReparentFrame(nextInFlow, oldParent, this); 5274 } 5275 if (!aFloatStatus.IsOverflowIncomplete()) { 5276 nextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); 5277 } 5278 } else { 5279 nextInFlow = 5280 PresShell()->FrameConstructor()->CreateContinuingFrame(aFloat, this); 5281 } 5282 if (aFloatStatus.IsOverflowIncomplete()) { 5283 nextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); 5284 } 5285 5286 UsedFloat floatStyle = 5287 aFloat->StyleDisplay()->UsedFloat(aState.mReflowInput.GetWritingMode()); 5288 if (floatStyle == UsedFloat::Left) { 5289 aState.FloatManager()->SetSplitLeftFloatAcrossBreak(); 5290 } else { 5291 MOZ_ASSERT(floatStyle == UsedFloat::Right, "Unexpected float side!"); 5292 aState.FloatManager()->SetSplitRightFloatAcrossBreak(); 5293 } 5294 5295 aState.AppendPushedFloatChain(nextInFlow); 5296 if (MOZ_LIKELY(!HasAnyStateBits(NS_BLOCK_BFC)) || 5297 MOZ_UNLIKELY(IsTrueOverflowContainer())) { 5298 aState.mReflowStatus.SetOverflowIncomplete(); 5299 } else { 5300 aState.mReflowStatus.SetIncomplete(); 5301 } 5302 } 5303 5304 static bool CheckPlaceholderInLine(nsIFrame* aBlock, nsLineBox* aLine, 5305 nsIFrame* aFloat) { 5306 if (!aFloat) { 5307 return true; 5308 } 5309 NS_ASSERTION(!aFloat->GetPrevContinuation(), 5310 "float in a line should never be a continuation"); 5311 NS_ASSERTION(!aFloat->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW), 5312 "float in a line should never be a pushed float"); 5313 nsIFrame* ph = aFloat->FirstInFlow()->GetPlaceholderFrame(); 5314 for (nsIFrame* f = ph; f; f = f->GetParent()) { 5315 if (f->GetParent() == aBlock) { 5316 return aLine->Contains(f); 5317 } 5318 } 5319 NS_ASSERTION(false, "aBlock is not an ancestor of aFrame!"); 5320 return true; 5321 } 5322 5323 void nsBlockFrame::SplitLine(BlockReflowState& aState, 5324 nsLineLayout& aLineLayout, LineIterator aLine, 5325 nsIFrame* aFrame, 5326 LineReflowStatus* aLineReflowStatus) { 5327 MOZ_ASSERT(aLine->IsInline(), "illegal SplitLine on block line"); 5328 5329 int32_t pushCount = 5330 aLine->GetChildCount() - aLineLayout.GetCurrentSpanCount(); 5331 MOZ_ASSERT(pushCount >= 0, "bad push count"); 5332 5333 #ifdef DEBUG 5334 if (gNoisyReflow) { 5335 nsIFrame::IndentBy(stdout, gNoiseIndent); 5336 printf("split line: from line=%p pushCount=%d aFrame=", 5337 static_cast<void*>(aLine.get()), pushCount); 5338 if (aFrame) { 5339 aFrame->ListTag(stdout); 5340 } else { 5341 printf("(null)"); 5342 } 5343 printf("\n"); 5344 if (gReallyNoisyReflow) { 5345 aLine->List(stdout, gNoiseIndent + 1); 5346 } 5347 } 5348 #endif 5349 5350 if (0 != pushCount) { 5351 MOZ_ASSERT(aLine->GetChildCount() > pushCount, "bad push"); 5352 MOZ_ASSERT(nullptr != aFrame, "whoops"); 5353 #ifdef DEBUG 5354 { 5355 nsIFrame* f = aFrame; 5356 int32_t count = pushCount; 5357 while (f && count > 0) { 5358 f = f->GetNextSibling(); 5359 --count; 5360 } 5361 NS_ASSERTION(count == 0, "Not enough frames to push"); 5362 } 5363 #endif 5364 5365 // Put frames being split out into their own line 5366 nsLineBox* newLine = NewLineBox(aLine, aFrame, pushCount); 5367 mLines.after_insert(aLine, newLine); 5368 #ifdef DEBUG 5369 if (gReallyNoisyReflow) { 5370 newLine->List(stdout, gNoiseIndent + 1); 5371 } 5372 #endif 5373 5374 // Let line layout know that some frames are no longer part of its 5375 // state. 5376 aLineLayout.SplitLineTo(aLine->GetChildCount()); 5377 5378 // If floats have been placed whose placeholders have been pushed to the new 5379 // line, we need to reflow the old line again. We don't want to look at the 5380 // frames in the new line, because as a large paragraph is laid out the 5381 // we'd get O(N^2) performance. So instead we just check that the last 5382 // float and the last below-current-line float are still in aLine. 5383 if (!CheckPlaceholderInLine( 5384 this, aLine, 5385 aLine->HasFloats() ? aLine->Floats().LastElement() : nullptr) || 5386 !CheckPlaceholderInLine( 5387 this, aLine, 5388 aState.mBelowCurrentLineFloats.SafeLastElement(nullptr))) { 5389 *aLineReflowStatus = LineReflowStatus::RedoNoPull; 5390 } 5391 5392 #ifdef DEBUG 5393 VerifyLines(true); 5394 #endif 5395 } 5396 } 5397 5398 bool nsBlockFrame::IsLastLine(BlockReflowState& aState, LineIterator aLine) { 5399 while (++aLine != LinesEnd()) { 5400 // There is another line 5401 if (0 != aLine->GetChildCount()) { 5402 // If the next line is a block line then this line is the last in a 5403 // group of inline lines. 5404 return aLine->IsBlock(); 5405 } 5406 // The next line is empty, try the next one 5407 } 5408 5409 // Try our next-in-flows lines to answer the question 5410 nsBlockFrame* nextInFlow = (nsBlockFrame*)GetNextInFlow(); 5411 while (nullptr != nextInFlow) { 5412 for (const auto& line : nextInFlow->Lines()) { 5413 if (0 != line.GetChildCount()) { 5414 return line.IsBlock(); 5415 } 5416 } 5417 nextInFlow = (nsBlockFrame*)nextInFlow->GetNextInFlow(); 5418 } 5419 5420 // This is the last line - so don't allow justification 5421 return true; 5422 } 5423 5424 bool nsBlockFrame::PlaceLine(BlockReflowState& aState, 5425 nsLineLayout& aLineLayout, LineIterator aLine, 5426 nsFloatManager::SavedState* aFloatStateBeforeLine, 5427 nsFlowAreaRect& aFlowArea, 5428 nscoord& aAvailableSpaceBSize, 5429 bool* aKeepReflowGoing) { 5430 // Try to position the floats in a nowrap context. 5431 aLineLayout.FlushNoWrapFloats(); 5432 5433 // Trim extra white-space from the line before placing the frames 5434 aLineLayout.TrimTrailingWhiteSpace(); 5435 5436 // Vertically align the frames on this line. 5437 // 5438 // According to the CSS2 spec, section 12.6.1, the "marker" box 5439 // participates in the height calculation of the list-item box's 5440 // first line box. 5441 // 5442 // There are exactly two places a ::marker can be placed: near the 5443 // first or second line. It's only placed on the second line in a 5444 // rare case: when the first line is empty. 5445 WritingMode wm = aState.mReflowInput.GetWritingMode(); 5446 bool addedMarker = false; 5447 nsIFrame* outsideMarker = GetOutsideMarker(); 5448 if (outsideMarker && 5449 ((aLine == mLines.front() && 5450 (!aLineLayout.IsZeroBSize() || (aLine == mLines.back()))) || 5451 (mLines.front() != mLines.back() && 0 == mLines.front()->BSize() && 5452 aLine == mLines.begin().next()))) { 5453 ReflowOutput metrics(aState.mReflowInput); 5454 ReflowOutsideMarker(outsideMarker, aState, metrics, aState.mBCoord); 5455 NS_ASSERTION(!MarkerIsEmpty(outsideMarker) || metrics.BSize(wm) == 0, 5456 "empty ::marker frame took up space"); 5457 aLineLayout.AddMarkerFrame(outsideMarker, metrics); 5458 addedMarker = true; 5459 } 5460 aLineLayout.VerticalAlignLine(); 5461 5462 // We want to consider the floats in the current line when determining 5463 // whether the float available space is shrunk. If mLineBSize doesn't 5464 // exist, we are in the first pass trying to place the line. Calling 5465 // GetFloatAvailableSpace() like we did in BlockReflowState::AddFloat() 5466 // for UpdateBand(). 5467 5468 // floatAvailableSpaceWithOldLineBSize is the float available space with 5469 // the old BSize, but including the floats that were added in this line. 5470 LogicalRect floatAvailableSpaceWithOldLineBSize = 5471 aState.mLineBSize.isNothing() 5472 ? aState.GetFloatAvailableSpace(wm, aLine->BStart()).mRect 5473 : aState 5474 .GetFloatAvailableSpaceForBSize( 5475 wm, aLine->BStart(), aState.mLineBSize.value(), nullptr) 5476 .mRect; 5477 5478 // As we redo for floats, we can't reduce the amount of BSize we're 5479 // checking. 5480 aAvailableSpaceBSize = std::max(aAvailableSpaceBSize, aLine->BSize()); 5481 LogicalRect floatAvailableSpaceWithLineBSize = 5482 aState 5483 .GetFloatAvailableSpaceForBSize(wm, aLine->BStart(), 5484 aAvailableSpaceBSize, nullptr) 5485 .mRect; 5486 5487 // If the available space between the floats is smaller now that we 5488 // know the BSize, return false (and cause another pass with 5489 // LineReflowStatus::RedoMoreFloats). We ensure aAvailableSpaceBSize 5490 // never decreases, which means that we can't reduce the set of floats 5491 // we intersect, which means that the available space cannot grow. 5492 if (AvailableSpaceShrunk(wm, floatAvailableSpaceWithOldLineBSize, 5493 floatAvailableSpaceWithLineBSize, false)) { 5494 // Prepare data for redoing the line. 5495 aState.mLineBSize = Some(aLine->BSize()); 5496 5497 // Since we want to redo the line, we update aFlowArea by using the 5498 // aFloatStateBeforeLine, which is the float manager's state before the 5499 // line is placed. 5500 LogicalRect oldFloatAvailableSpace(aFlowArea.mRect); 5501 aFlowArea = aState.GetFloatAvailableSpaceForBSize( 5502 wm, aLine->BStart(), aAvailableSpaceBSize, aFloatStateBeforeLine); 5503 5504 NS_ASSERTION( 5505 aFlowArea.mRect.BStart(wm) == oldFloatAvailableSpace.BStart(wm), 5506 "yikes"); 5507 // Restore the BSize to the position of the next band. 5508 aFlowArea.mRect.BSize(wm) = oldFloatAvailableSpace.BSize(wm); 5509 5510 // Enforce both IStart() and IEnd() never move outwards to prevent 5511 // infinite grow-shrink loops. 5512 const nscoord iStartDiff = 5513 aFlowArea.mRect.IStart(wm) - oldFloatAvailableSpace.IStart(wm); 5514 const nscoord iEndDiff = 5515 aFlowArea.mRect.IEnd(wm) - oldFloatAvailableSpace.IEnd(wm); 5516 if (iStartDiff < 0) { 5517 aFlowArea.mRect.IStart(wm) -= iStartDiff; 5518 aFlowArea.mRect.ISize(wm) += iStartDiff; 5519 } 5520 if (iEndDiff > 0) { 5521 aFlowArea.mRect.ISize(wm) -= iEndDiff; 5522 } 5523 5524 return false; 5525 } 5526 5527 #ifdef DEBUG 5528 if (!GetParent()->IsAbsurdSizeAssertSuppressed()) { 5529 static nscoord lastHeight = 0; 5530 if (ABSURD_SIZE(aLine->BStart())) { 5531 lastHeight = aLine->BStart(); 5532 if (abs(aLine->BStart() - lastHeight) > ABSURD_COORD / 10) { 5533 nsIFrame::ListTag(stdout); 5534 printf(": line=%p y=%d line.bounds.height=%d\n", 5535 static_cast<void*>(aLine.get()), aLine->BStart(), 5536 aLine->BSize()); 5537 } 5538 } else { 5539 lastHeight = 0; 5540 } 5541 } 5542 #endif 5543 5544 // Only block frames horizontally align their children because 5545 // inline frames "shrink-wrap" around their children (therefore 5546 // there is no extra horizontal space). 5547 const nsStyleText* styleText = StyleText(); 5548 5549 /** 5550 * We don't care checking for IsLastLine properly if we don't care (if it 5551 * can't change the used text-align value for the line). 5552 * 5553 * In other words, isLastLine really means isLastLineAndWeCare. 5554 */ 5555 const bool isLastLine = 5556 !IsInSVGTextSubtree() && 5557 styleText->TextAlignForLastLine() != styleText->mTextAlign && 5558 (aLineLayout.GetLineEndsInBR() || IsLastLine(aState, aLine)); 5559 5560 aLineLayout.TextAlignLine(aLine, isLastLine); 5561 5562 // From here on, pfd->mBounds rectangles are incorrect because bidi 5563 // might have moved frames around! 5564 OverflowAreas overflowAreas; 5565 aLineLayout.RelativePositionFrames(overflowAreas); 5566 if (Style()->GetPseudoType() == PseudoStyleType::scrolledContent) { 5567 Maybe<nsRect> inFlowBounds; 5568 int32_t n = aLine->GetChildCount(); 5569 for (nsIFrame* lineFrame = aLine->mFirstChild; n > 0; 5570 lineFrame = lineFrame->GetNextSibling(), --n) { 5571 auto lineFrameBounds = GetLineFrameInFlowBounds(*aLine, *lineFrame); 5572 if (!lineFrameBounds) { 5573 continue; 5574 } 5575 if (inFlowBounds) { 5576 *inFlowBounds = inFlowBounds->UnionEdges(*lineFrameBounds); 5577 } else { 5578 inFlowBounds = Some(*lineFrameBounds); 5579 } 5580 } 5581 aLine->SetInFlowChildBounds(inFlowBounds); 5582 } 5583 aLine->SetOverflowAreas(overflowAreas); 5584 if (addedMarker) { 5585 aLineLayout.RemoveMarkerFrame(GetOutsideMarker()); 5586 } 5587 5588 // Inline lines do not have margins themselves; however they are 5589 // impacted by prior block margins. If this line ends up having some 5590 // height then we zero out the previous block-end margin value that was 5591 // already applied to the line's starting Y coordinate. Otherwise we 5592 // leave it be so that the previous blocks block-end margin can be 5593 // collapsed with a block that follows. 5594 nscoord newBCoord; 5595 5596 if (!aLine->CachedIsEmpty()) { 5597 // This line has some height. Therefore the application of the 5598 // previous-bottom-margin should stick. 5599 aState.mPrevBEndMargin.Zero(); 5600 newBCoord = aLine->BEnd(); 5601 } else { 5602 // Don't let the previous-bottom-margin value affect the newBCoord 5603 // coordinate (it was applied in ReflowInlineFrames speculatively) 5604 // since the line is empty. 5605 // We already called |ShouldApplyBStartMargin|, and if we applied it 5606 // then mShouldApplyBStartMargin is set. 5607 nscoord dy = aState.mFlags.mShouldApplyBStartMargin 5608 ? -aState.mPrevBEndMargin.Get() 5609 : 0; 5610 newBCoord = aState.mBCoord + dy; 5611 } 5612 5613 if (!aState.mReflowStatus.IsFullyComplete() && 5614 ShouldAvoidBreakInside(aState.mReflowInput)) { 5615 aLine->AppendFloats(std::move(aState.mCurrentLineFloats)); 5616 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing); 5617 return true; 5618 } 5619 5620 // See if the line fit (our first line always does). 5621 if (mLines.front() != aLine && 5622 aState.ContentBSize() != NS_UNCONSTRAINEDSIZE && 5623 newBCoord > aState.ContentBEnd()) { 5624 NS_ASSERTION(aState.mCurrentLine == aLine, "oops"); 5625 if (ShouldAvoidBreakInside(aState.mReflowInput)) { 5626 // All our content doesn't fit, start on the next page. 5627 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing); 5628 } else { 5629 // Push aLine and all of its children and anything else that 5630 // follows to our next-in-flow. 5631 PushTruncatedLine(aState, aLine, aKeepReflowGoing); 5632 } 5633 return true; 5634 } 5635 5636 // Note that any early return before this update of aState.mBCoord 5637 // must either (a) return false or (b) set aKeepReflowGoing to false. 5638 // Otherwise we'll keep reflowing later lines at an incorrect 5639 // position, and we might not come back and clean up the damage later. 5640 aState.mBCoord = newBCoord; 5641 5642 // Add the already placed current-line floats to the line 5643 aLine->AppendFloats(std::move(aState.mCurrentLineFloats)); 5644 5645 // Any below current line floats to place? 5646 if (!aState.mBelowCurrentLineFloats.IsEmpty()) { 5647 // Reflow the below-current-line floats, which places on the line's 5648 // float list. 5649 aState.PlaceBelowCurrentLineFloats(aLine); 5650 } 5651 5652 // When a line has floats, factor them into the overflow areas computations. 5653 if (aLine->HasFloats()) { 5654 // Union the float overflow areas (stored in aState) and the value computed 5655 // by the line layout code. 5656 OverflowAreas lineOverflowAreas = aState.mFloatOverflowAreas; 5657 lineOverflowAreas.UnionWith(aLine->GetOverflowAreas()); 5658 aLine->SetOverflowAreas(lineOverflowAreas); 5659 if (Style()->GetPseudoType() == PseudoStyleType::scrolledContent) { 5660 Span<const nsIFrame* const> floats(aLine->Floats()); 5661 // Guaranteed to have at least 1 element since `HasFloats()` is true. 5662 auto floatRect = GetNormalMarginRect(*floats[0]); 5663 for (const nsIFrame* f : floats.From(1)) { 5664 floatRect = floatRect.UnionEdges(GetNormalMarginRect(*f)); 5665 } 5666 auto inFlowBounds = aLine->GetInFlowChildBounds(); 5667 aLine->SetInFlowChildBounds( 5668 Some(inFlowBounds ? inFlowBounds->UnionEdges(floatRect) : floatRect)); 5669 } 5670 5671 #ifdef NOISY_OVERFLOW_AREAS 5672 printf("%s: Line %p, InkOverflowRect=%s, ScrollableOverflowRect=%s\n", 5673 ListTag().get(), aLine.get(), 5674 ToString(aLine->InkOverflowRect()).c_str(), 5675 ToString(aLine->ScrollableOverflowRect()).c_str()); 5676 #endif 5677 } 5678 5679 // Apply break-after clearing if necessary 5680 // This must stay in sync with |ReflowDirtyLines|. 5681 if (aLine->HasFloatClearTypeAfter()) { 5682 std::tie(aState.mBCoord, std::ignore) = 5683 aState.ClearFloats(aState.mBCoord, aLine->FloatClearTypeAfter()); 5684 } 5685 return true; 5686 } 5687 5688 void nsBlockFrame::PushLines(BlockReflowState& aState, 5689 nsLineList::iterator aLineBefore) { 5690 // NOTE: aLineBefore is always a normal line, not an overflow line. 5691 // The following expression will assert otherwise. 5692 DebugOnly<bool> check = aLineBefore == mLines.begin(); 5693 5694 nsLineList::iterator overBegin(aLineBefore.next()); 5695 5696 // PushTruncatedPlaceholderLine sometimes pushes the first line. Ugh. 5697 bool firstLine = overBegin == LinesBegin(); 5698 5699 if (overBegin != LinesEnd()) { 5700 // Remove floats in the lines from floats list. 5701 nsFrameList floats; 5702 CollectFloats(overBegin->mFirstChild, floats, true); 5703 5704 if (floats.NotEmpty()) { 5705 #ifdef DEBUG 5706 for (nsIFrame* f : floats) { 5707 MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW), 5708 "CollectFloats should've removed that bit"); 5709 } 5710 #endif 5711 // Push the floats onto the front of the overflow out-of-flows list 5712 nsAutoOOFFrameList oofs(this); 5713 oofs.mList.InsertFrames(nullptr, nullptr, std::move(floats)); 5714 } 5715 5716 // overflow lines can already exist in some cases, in particular, 5717 // when shrinkwrapping and we discover that the shrinkwap causes 5718 // the height of some child block to grow which creates additional 5719 // overflowing content. In such cases we must prepend the new 5720 // overflow to the existing overflow. 5721 FrameLines* overflowLines = RemoveOverflowLines(); 5722 if (!overflowLines) { 5723 // XXXldb use presshell arena! 5724 overflowLines = new FrameLines(); 5725 } 5726 if (overflowLines) { 5727 nsIFrame* lineBeforeLastFrame; 5728 if (firstLine) { 5729 lineBeforeLastFrame = nullptr; // removes all frames 5730 } else { 5731 nsIFrame* f = overBegin->mFirstChild; 5732 lineBeforeLastFrame = f ? f->GetPrevSibling() : mFrames.LastChild(); 5733 NS_ASSERTION(!f || lineBeforeLastFrame == aLineBefore->LastChild(), 5734 "unexpected line frames"); 5735 } 5736 nsFrameList pushedFrames = mFrames.TakeFramesAfter(lineBeforeLastFrame); 5737 overflowLines->mFrames.InsertFrames(nullptr, nullptr, 5738 std::move(pushedFrames)); 5739 5740 overflowLines->mLines.splice(overflowLines->mLines.begin(), mLines, 5741 overBegin, LinesEnd()); 5742 NS_ASSERTION(!overflowLines->mLines.empty(), "should not be empty"); 5743 // this takes ownership but it won't delete it immediately so we 5744 // can keep using it. 5745 SetOverflowLines(overflowLines); 5746 5747 // Mark all the overflow lines dirty so that they get reflowed when 5748 // they are pulled up by our next-in-flow. 5749 5750 nsLineBox* cursor = GetLineCursorForDisplay(); 5751 5752 // XXXldb Can this get called O(N) times making the whole thing O(N^2)? 5753 for (LineIterator line = overflowLines->mLines.begin(), 5754 line_end = overflowLines->mLines.end(); 5755 line != line_end; ++line) { 5756 if (line == cursor) { 5757 ClearLineCursors(); 5758 } 5759 line->MarkDirty(); 5760 line->MarkPreviousMarginDirty(); 5761 line->SetMovedFragments(); 5762 line->SetBoundsEmpty(); 5763 if (line->HasFloats()) { 5764 line->ClearFloats(); 5765 } 5766 } 5767 } 5768 } 5769 5770 #ifdef DEBUG 5771 VerifyOverflowSituation(); 5772 #endif 5773 } 5774 5775 // The overflowLines property is stored as a pointer to a line list, 5776 // which must be deleted. However, the following functions all maintain 5777 // the invariant that the property is never set if the list is empty. 5778 5779 bool nsBlockFrame::DrainOverflowLines() { 5780 #ifdef DEBUG 5781 VerifyOverflowSituation(); 5782 #endif 5783 5784 // Steal the prev-in-flow's overflow lines and prepend them. 5785 bool didFindOverflow = false; 5786 nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow()); 5787 if (prevBlock) { 5788 prevBlock->ClearLineCursors(); 5789 FrameLines* overflowLines = prevBlock->RemoveOverflowLines(); 5790 if (overflowLines) { 5791 // Make all the frames on the overflow line list mine. 5792 ReparentFrames(overflowLines->mFrames, prevBlock, this); 5793 5794 // Collect overflow containers from our OverflowContainers list that are 5795 // continuations from the frames we picked up from our prev-in-flow, then 5796 // prepend those to ExcessOverflowContainers to ensure the continuations 5797 // are ordered. 5798 if (GetOverflowContainers()) { 5799 nsFrameList ocContinuations; 5800 for (auto* f : overflowLines->mFrames) { 5801 auto* cont = f; 5802 bool done = false; 5803 while (!done && (cont = cont->GetNextContinuation()) && 5804 cont->GetParent() == this) { 5805 bool onlyChild = !cont->GetPrevSibling() && !cont->GetNextSibling(); 5806 if (cont->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER) && 5807 TryRemoveFrame(OverflowContainersProperty(), cont)) { 5808 ocContinuations.AppendFrame(nullptr, cont); 5809 done = onlyChild; 5810 continue; 5811 } 5812 break; 5813 } 5814 if (done) { 5815 break; 5816 } 5817 } 5818 if (!ocContinuations.IsEmpty()) { 5819 if (nsFrameList* eoc = GetExcessOverflowContainers()) { 5820 eoc->InsertFrames(nullptr, nullptr, std::move(ocContinuations)); 5821 } else { 5822 SetExcessOverflowContainers(std::move(ocContinuations)); 5823 } 5824 } 5825 } 5826 5827 // Make the overflow out-of-flow frames mine too. 5828 nsAutoOOFFrameList oofs(prevBlock); 5829 if (oofs.mList.NotEmpty()) { 5830 // In case we own any next-in-flows of any of the drained frames, then 5831 // move those to the PushedFloat list. 5832 nsFrameList pushedFloats; 5833 for (nsIFrame* f : oofs.mList) { 5834 nsIFrame* nif = f->GetNextInFlow(); 5835 for (; nif && nif->GetParent() == this; nif = nif->GetNextInFlow()) { 5836 MOZ_ASSERT(nif->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW)); 5837 RemoveFloat(nif); 5838 pushedFloats.AppendFrame(nullptr, nif); 5839 } 5840 } 5841 ReparentFrames(oofs.mList, prevBlock, this); 5842 EnsureFloats()->InsertFrames(nullptr, nullptr, std::move(oofs.mList)); 5843 if (!pushedFloats.IsEmpty()) { 5844 nsFrameList* pf = EnsurePushedFloats(); 5845 pf->InsertFrames(nullptr, nullptr, std::move(pushedFloats)); 5846 } 5847 } 5848 5849 if (!mLines.empty()) { 5850 // Remember to recompute the margins on the first line. This will 5851 // also recompute the correct deltaBCoord if necessary. 5852 mLines.front()->MarkPreviousMarginDirty(); 5853 } 5854 // The overflow lines have already been marked dirty and their previous 5855 // margins marked dirty also. 5856 5857 // Prepend the overflow frames/lines to our principal list. 5858 mFrames.InsertFrames(nullptr, nullptr, std::move(overflowLines->mFrames)); 5859 mLines.splice(mLines.begin(), overflowLines->mLines); 5860 NS_ASSERTION(overflowLines->mLines.empty(), "splice should empty list"); 5861 delete overflowLines; 5862 didFindOverflow = true; 5863 } 5864 } 5865 5866 // Now append our own overflow lines. 5867 return DrainSelfOverflowList() || didFindOverflow; 5868 } 5869 5870 bool nsBlockFrame::DrainSelfOverflowList() { 5871 UniquePtr<FrameLines> ourOverflowLines(RemoveOverflowLines()); 5872 if (!ourOverflowLines) { 5873 return false; 5874 } 5875 5876 // No need to reparent frames in our own overflow lines/oofs, because they're 5877 // already ours. But we should put overflow floats back in our floats list. 5878 // (explicit scope to remove the OOF list before VerifyOverflowSituation) 5879 { 5880 nsAutoOOFFrameList oofs(this); 5881 if (oofs.mList.NotEmpty()) { 5882 #ifdef DEBUG 5883 for (nsIFrame* f : oofs.mList) { 5884 MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW), 5885 "CollectFloats should've removed that bit"); 5886 } 5887 #endif 5888 // The overflow floats go after our regular floats. 5889 EnsureFloats()->AppendFrames(nullptr, std::move(oofs).mList); 5890 } 5891 } 5892 if (!ourOverflowLines->mLines.empty()) { 5893 mFrames.AppendFrames(nullptr, std::move(ourOverflowLines->mFrames)); 5894 mLines.splice(mLines.end(), ourOverflowLines->mLines); 5895 } 5896 5897 #ifdef DEBUG 5898 VerifyOverflowSituation(); 5899 #endif 5900 return true; 5901 } 5902 5903 /** 5904 * Pushed floats are floats whose placeholders are in a previous 5905 * continuation. They might themselves be next-continuations of a float 5906 * that partially fit in an earlier continuation, or they might be the 5907 * first continuation of a float that couldn't be placed at all. 5908 * 5909 * Pushed floats live permanently at the beginning of a block's float 5910 * list, where they must live *before* any floats whose placeholders are 5911 * in that block. 5912 * 5913 * Temporarily, during reflow, they also live on the pushed floats list, 5914 * which only holds them between (a) when one continuation pushes them to 5915 * its pushed floats list because they don't fit and (b) when the next 5916 * continuation pulls them onto the beginning of its float list. 5917 * 5918 * DrainPushedFloats sets up pushed floats the way we need them at the 5919 * start of reflow; they are then reflowed by ReflowPushedFloats (which 5920 * might push some of them on). Floats with placeholders in this block 5921 * are reflowed by (BlockReflowState/nsLineLayout)::AddFloat, which 5922 * also maintains these invariants. 5923 * 5924 * DrainSelfPushedFloats moves any pushed floats from this block's own 5925 * pushed floats list back into floats list. DrainPushedFloats additionally 5926 * moves frames from its prev-in-flow's pushed floats list into floats list. 5927 */ 5928 void nsBlockFrame::DrainSelfPushedFloats() { 5929 // If we're getting reflowed multiple times without our 5930 // next-continuation being reflowed, we might need to pull back floats 5931 // that we just put in the list to be pushed to our next-in-flow. 5932 // We don't want to pull back any next-in-flows of floats on our own 5933 // float list, and we only need to pull back first-in-flows whose 5934 // placeholders were in earlier blocks (since first-in-flows whose 5935 // placeholders are in this block will get pulled appropriately by 5936 // AddFloat, and will then be more likely to be in the correct order). 5937 mozilla::PresShell* presShell = PresShell(); 5938 nsFrameList* ourPushedFloats = GetPushedFloats(); 5939 if (ourPushedFloats) { 5940 nsFrameList* floats = GetFloats(); 5941 5942 // When we pull back floats, we want to put them with the pushed 5943 // floats, which must live at the start of our float list, but we 5944 // want them at the end of those pushed floats. 5945 // FIXME: This isn't quite right! What if they're all pushed floats? 5946 nsIFrame* insertionPrevSibling = nullptr; /* beginning of list */ 5947 for (nsIFrame* f = floats ? floats->FirstChild() : nullptr; 5948 f && f->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW); 5949 f = f->GetNextSibling()) { 5950 insertionPrevSibling = f; 5951 } 5952 5953 nsIFrame* f = ourPushedFloats->LastChild(); 5954 while (f) { 5955 nsIFrame* prevSibling = f->GetPrevSibling(); 5956 5957 nsPlaceholderFrame* placeholder = f->GetPlaceholderFrame(); 5958 nsIFrame* floatOriginalParent = 5959 presShell->FrameConstructor()->GetFloatContainingBlock(placeholder); 5960 if (floatOriginalParent != this) { 5961 // This is a first continuation that was pushed from one of our 5962 // previous continuations. Take it out of the pushed floats 5963 // list and put it in our floats list, before any of our 5964 // floats, but after other pushed floats. 5965 ourPushedFloats->RemoveFrame(f); 5966 if (!floats) { 5967 floats = EnsureFloats(); 5968 } 5969 floats->InsertFrame(nullptr, insertionPrevSibling, f); 5970 } 5971 5972 f = prevSibling; 5973 } 5974 5975 if (ourPushedFloats->IsEmpty()) { 5976 StealPushedFloats()->Delete(presShell); 5977 } 5978 } 5979 } 5980 5981 void nsBlockFrame::DrainPushedFloats() { 5982 DrainSelfPushedFloats(); 5983 5984 // After our prev-in-flow has completed reflow, it may have a pushed 5985 // floats list, containing floats that we need to own. Take these. 5986 nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow()); 5987 if (prevBlock) { 5988 AutoFrameListPtr list(PresContext(), prevBlock->StealPushedFloats()); 5989 if (list && list->NotEmpty()) { 5990 EnsureFloats()->InsertFrames(this, nullptr, std::move(*list)); 5991 } 5992 } 5993 } 5994 5995 nsBlockFrame::FrameLines* nsBlockFrame::GetOverflowLines() const { 5996 if (!HasOverflowLines()) { 5997 return nullptr; 5998 } 5999 FrameLines* prop = GetProperty(OverflowLinesProperty()); 6000 NS_ASSERTION( 6001 prop && !prop->mLines.empty() && 6002 prop->mLines.front()->GetChildCount() == 0 6003 ? prop->mFrames.IsEmpty() 6004 : prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(), 6005 "value should always be stored and non-empty when state set"); 6006 return prop; 6007 } 6008 6009 nsBlockFrame::FrameLines* nsBlockFrame::RemoveOverflowLines() { 6010 if (!HasOverflowLines()) { 6011 return nullptr; 6012 } 6013 FrameLines* prop = TakeProperty(OverflowLinesProperty()); 6014 NS_ASSERTION( 6015 prop && !prop->mLines.empty() && 6016 prop->mLines.front()->GetChildCount() == 0 6017 ? prop->mFrames.IsEmpty() 6018 : prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(), 6019 "value should always be stored and non-empty when state set"); 6020 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES); 6021 return prop; 6022 } 6023 6024 void nsBlockFrame::DestroyOverflowLines() { 6025 NS_ASSERTION(HasOverflowLines(), "huh?"); 6026 FrameLines* prop = TakeProperty(OverflowLinesProperty()); 6027 NS_ASSERTION(prop && prop->mLines.empty(), 6028 "value should always be stored but empty when destroying"); 6029 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES); 6030 delete prop; 6031 } 6032 6033 // This takes ownership of aOverflowLines. 6034 // XXX We should allocate overflowLines from presShell arena! 6035 void nsBlockFrame::SetOverflowLines(FrameLines* aOverflowLines) { 6036 NS_ASSERTION(aOverflowLines, "null lines"); 6037 NS_ASSERTION(!aOverflowLines->mLines.empty(), "empty lines"); 6038 NS_ASSERTION(aOverflowLines->mLines.front()->mFirstChild == 6039 aOverflowLines->mFrames.FirstChild(), 6040 "invalid overflow lines / frames"); 6041 NS_ASSERTION(!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_LINES), 6042 "Overwriting existing overflow lines"); 6043 6044 // Verify that we won't overwrite an existing overflow list 6045 NS_ASSERTION(!GetProperty(OverflowLinesProperty()), "existing overflow list"); 6046 SetProperty(OverflowLinesProperty(), aOverflowLines); 6047 AddStateBits(NS_BLOCK_HAS_OVERFLOW_LINES); 6048 } 6049 6050 nsFrameList* nsBlockFrame::GetOverflowOutOfFlows() const { 6051 if (!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) { 6052 return nullptr; 6053 } 6054 nsFrameList* result = GetProperty(OverflowOutOfFlowsProperty()); 6055 NS_ASSERTION(result, "value should always be non-empty when state set"); 6056 return result; 6057 } 6058 6059 void nsBlockFrame::SetOverflowOutOfFlows(nsFrameList&& aList, 6060 nsFrameList* aPropValue) { 6061 MOZ_ASSERT( 6062 HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS) == !!aPropValue, 6063 "state does not match value"); 6064 6065 if (aList.IsEmpty()) { 6066 if (!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) { 6067 return; 6068 } 6069 nsFrameList* list = TakeProperty(OverflowOutOfFlowsProperty()); 6070 NS_ASSERTION(aPropValue == list, "prop value mismatch"); 6071 list->Clear(); 6072 list->Delete(PresShell()); 6073 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS); 6074 } else if (HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) { 6075 NS_ASSERTION(aPropValue == GetProperty(OverflowOutOfFlowsProperty()), 6076 "prop value mismatch"); 6077 *aPropValue = std::move(aList); 6078 } else { 6079 SetProperty(OverflowOutOfFlowsProperty(), 6080 new (PresShell()) nsFrameList(std::move(aList))); 6081 AddStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS); 6082 } 6083 } 6084 6085 nsIFrame* nsBlockFrame::GetInsideMarker() const { 6086 if (!HasMarker()) { 6087 return nullptr; 6088 } 6089 if (nsIFrame* frame = GetProperty(InsideMarkerProperty())) { 6090 return frame; 6091 } 6092 return nullptr; 6093 } 6094 6095 nsIFrame* nsBlockFrame::GetOutsideMarker() const { 6096 nsFrameList* list = GetOutsideMarkerList(); 6097 return list ? list->FirstChild() : nullptr; 6098 } 6099 6100 nsFrameList* nsBlockFrame::GetOutsideMarkerList() const { 6101 if (!HasMarker()) { 6102 return nullptr; 6103 } 6104 if (nsFrameList* list = GetProperty(OutsideMarkerProperty())) { 6105 MOZ_ASSERT(list->GetLength() == 1, "bogus outside ::marker list"); 6106 return list; 6107 } 6108 return nullptr; 6109 } 6110 6111 bool nsBlockFrame::HasFloats() const { 6112 const bool isStateBitSet = HasAnyStateBits(NS_BLOCK_HAS_FLOATS); 6113 MOZ_ASSERT( 6114 isStateBitSet == HasProperty(FloatsProperty()), 6115 "State bit should accurately reflect presence/absence of the property!"); 6116 return isStateBitSet; 6117 } 6118 6119 nsFrameList* nsBlockFrame::GetFloats() const { 6120 if (!HasFloats()) { 6121 return nullptr; 6122 } 6123 nsFrameList* list = GetProperty(FloatsProperty()); 6124 MOZ_ASSERT(list, "List should always be valid when the property is set!"); 6125 MOZ_ASSERT(list->NotEmpty(), 6126 "Someone forgot to delete the list when it is empty!"); 6127 return list; 6128 } 6129 6130 nsFrameList* nsBlockFrame::EnsureFloats() { 6131 nsFrameList* list = GetFloats(); 6132 if (list) { 6133 return list; 6134 } 6135 list = new (PresShell()) nsFrameList; 6136 SetProperty(FloatsProperty(), list); 6137 AddStateBits(NS_BLOCK_HAS_FLOATS); 6138 return list; 6139 } 6140 6141 nsFrameList* nsBlockFrame::StealFloats() { 6142 if (!HasFloats()) { 6143 return nullptr; 6144 } 6145 nsFrameList* list = TakeProperty(FloatsProperty()); 6146 RemoveStateBits(NS_BLOCK_HAS_FLOATS); 6147 MOZ_ASSERT(list, "List should always be valid when the property is set!"); 6148 return list; 6149 } 6150 6151 bool nsBlockFrame::HasPushedFloats() const { 6152 const bool isStateBitSet = HasAnyStateBits(NS_BLOCK_HAS_PUSHED_FLOATS); 6153 MOZ_ASSERT( 6154 isStateBitSet == HasProperty(PushedFloatsProperty()), 6155 "State bit should accurately reflect presence/absence of the property!"); 6156 return isStateBitSet; 6157 } 6158 6159 nsFrameList* nsBlockFrame::GetPushedFloats() const { 6160 if (!HasPushedFloats()) { 6161 return nullptr; 6162 } 6163 nsFrameList* list = GetProperty(PushedFloatsProperty()); 6164 MOZ_ASSERT(list, "List should always be valid when the property is set!"); 6165 MOZ_ASSERT(list->NotEmpty(), 6166 "Someone forgot to delete the list when it is empty!"); 6167 return list; 6168 } 6169 6170 nsFrameList* nsBlockFrame::EnsurePushedFloats() { 6171 nsFrameList* result = GetPushedFloats(); 6172 if (result) { 6173 return result; 6174 } 6175 6176 result = new (PresShell()) nsFrameList; 6177 SetProperty(PushedFloatsProperty(), result); 6178 AddStateBits(NS_BLOCK_HAS_PUSHED_FLOATS); 6179 6180 return result; 6181 } 6182 6183 nsFrameList* nsBlockFrame::StealPushedFloats() { 6184 if (!HasPushedFloats()) { 6185 return nullptr; 6186 } 6187 nsFrameList* list = TakeProperty(PushedFloatsProperty()); 6188 RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS); 6189 MOZ_ASSERT(list, "List should always be valid when the property is set!"); 6190 return list; 6191 } 6192 6193 ////////////////////////////////////////////////////////////////////// 6194 // Frame list manipulation routines 6195 6196 void nsBlockFrame::AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) { 6197 if (aFrameList.IsEmpty()) { 6198 return; 6199 } 6200 if (aListID != FrameChildListID::Principal) { 6201 if (FrameChildListID::Float == aListID) { 6202 DrainSelfPushedFloats(); // ensure the last frame is in floats list. 6203 EnsureFloats()->AppendFrames(nullptr, std::move(aFrameList)); 6204 return; 6205 } 6206 MOZ_ASSERT(FrameChildListID::NoReflowPrincipal == aListID, 6207 "unexpected child list"); 6208 } 6209 6210 // Find the proper last-child for where the append should go 6211 nsIFrame* lastKid = mFrames.LastChild(); 6212 NS_ASSERTION( 6213 (mLines.empty() ? nullptr : mLines.back()->LastChild()) == lastKid, 6214 "out-of-sync mLines / mFrames"); 6215 6216 #ifdef NOISY_REFLOW_REASON 6217 ListTag(stdout); 6218 printf(": append "); 6219 for (nsIFrame* frame : aFrameList) { 6220 frame->ListTag(stdout); 6221 } 6222 if (lastKid) { 6223 printf(" after "); 6224 lastKid->ListTag(stdout); 6225 } 6226 printf("\n"); 6227 #endif 6228 6229 if (IsInSVGTextSubtree()) { 6230 MOZ_ASSERT(GetParent()->IsSVGTextFrame(), 6231 "unexpected block frame in SVG text"); 6232 // Workaround for bug 1399425 in case this bit has been removed from the 6233 // SVGTextFrame just before the parser adds more descendant nodes. 6234 GetParent()->AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY); 6235 } 6236 6237 AddFrames(std::move(aFrameList), lastKid, nullptr); 6238 if (aListID != FrameChildListID::NoReflowPrincipal) { 6239 PresShell()->FrameNeedsReflow( 6240 this, IntrinsicDirty::FrameAndAncestors, 6241 NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient? 6242 } 6243 } 6244 6245 void nsBlockFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, 6246 const nsLineList::iterator* aPrevFrameLine, 6247 nsFrameList&& aFrameList) { 6248 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, 6249 "inserting after sibling frame with different parent"); 6250 6251 if (aListID != FrameChildListID::Principal) { 6252 if (FrameChildListID::Float == aListID) { 6253 DrainSelfPushedFloats(); // ensure aPrevFrame is in floats list. 6254 EnsureFloats()->InsertFrames(this, aPrevFrame, std::move(aFrameList)); 6255 return; 6256 } 6257 MOZ_ASSERT(FrameChildListID::NoReflowPrincipal == aListID, 6258 "unexpected child list"); 6259 } 6260 6261 #ifdef NOISY_REFLOW_REASON 6262 ListTag(stdout); 6263 printf(": insert "); 6264 for (nsIFrame* frame : aFrameList) { 6265 frame->ListTag(stdout); 6266 } 6267 if (aPrevFrame) { 6268 printf(" after "); 6269 aPrevFrame->ListTag(stdout); 6270 } 6271 printf("\n"); 6272 #endif 6273 6274 AddFrames(std::move(aFrameList), aPrevFrame, aPrevFrameLine); 6275 if (aListID != FrameChildListID::NoReflowPrincipal) { 6276 PresShell()->FrameNeedsReflow( 6277 this, IntrinsicDirty::FrameAndAncestors, 6278 NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient? 6279 } 6280 } 6281 6282 void nsBlockFrame::RemoveFrame(DestroyContext& aContext, ChildListID aListID, 6283 nsIFrame* aOldFrame) { 6284 #ifdef NOISY_REFLOW_REASON 6285 ListTag(stdout); 6286 printf(": remove "); 6287 aOldFrame->ListTag(stdout); 6288 printf("\n"); 6289 #endif 6290 6291 if (aListID == FrameChildListID::Principal) { 6292 bool hasFloats = BlockHasAnyFloats(aOldFrame); 6293 DoRemoveFrame(aContext, aOldFrame, REMOVE_FIXED_CONTINUATIONS); 6294 if (hasFloats) { 6295 MarkSameFloatManagerLinesDirty(this); 6296 } 6297 } else if (FrameChildListID::Float == aListID) { 6298 // Make sure to mark affected lines dirty for the float frame 6299 // we are removing; this way is a bit messy, but so is the rest of the code. 6300 // See bug 390762. 6301 NS_ASSERTION(!aOldFrame->GetPrevContinuation(), 6302 "RemoveFrame should not be called on pushed floats."); 6303 for (nsIFrame* f = aOldFrame; 6304 f && !f->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); 6305 f = f->GetNextContinuation()) { 6306 MarkSameFloatManagerLinesDirty( 6307 static_cast<nsBlockFrame*>(f->GetParent())); 6308 } 6309 DoRemoveFloats(aContext, aOldFrame); 6310 } else if (FrameChildListID::NoReflowPrincipal == aListID) { 6311 // Skip the call to |FrameNeedsReflow| below by returning now. 6312 DoRemoveFrame(aContext, aOldFrame, REMOVE_FIXED_CONTINUATIONS); 6313 return; 6314 } else { 6315 MOZ_CRASH("unexpected child list"); 6316 } 6317 6318 PresShell()->FrameNeedsReflow( 6319 this, IntrinsicDirty::FrameAndAncestors, 6320 NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient? 6321 } 6322 6323 static bool ShouldPutNextSiblingOnNewLine(nsIFrame* aLastFrame) { 6324 LayoutFrameType type = aLastFrame->Type(); 6325 if (type == LayoutFrameType::Br) { 6326 return true; 6327 } 6328 // XXX the TEXT_OFFSETS_NEED_FIXING check is a wallpaper for bug 822910. 6329 if (type == LayoutFrameType::Text && 6330 !aLastFrame->HasAnyStateBits(TEXT_OFFSETS_NEED_FIXING)) { 6331 return aLastFrame->HasSignificantTerminalNewline(); 6332 } 6333 return false; 6334 } 6335 6336 void nsBlockFrame::AddFrames(nsFrameList&& aFrameList, nsIFrame* aPrevSibling, 6337 const nsLineList::iterator* aPrevSiblingLine) { 6338 // Clear our line cursor, since our lines may change. 6339 ClearLineCursors(); 6340 6341 if (aFrameList.IsEmpty()) { 6342 return; 6343 } 6344 6345 // Attempt to find the line that contains the previous sibling 6346 nsLineList* lineList = &mLines; 6347 nsFrameList* frames = &mFrames; 6348 nsLineList::iterator prevSibLine; 6349 int32_t prevSiblingIndex; 6350 if (aPrevSiblingLine) { 6351 MOZ_ASSERT(aPrevSibling); 6352 prevSibLine = *aPrevSiblingLine; 6353 FrameLines* overflowLines = GetOverflowLines(); 6354 MOZ_ASSERT(prevSibLine.IsInSameList(mLines.begin()) || 6355 (overflowLines && 6356 prevSibLine.IsInSameList(overflowLines->mLines.begin())), 6357 "must be one of our line lists"); 6358 if (overflowLines) { 6359 // We need to find out which list it's actually in. Assume that 6360 // *if* we have overflow lines, that our primary lines aren't 6361 // huge, but our overflow lines might be. 6362 nsLineList::iterator line = mLines.begin(), lineEnd = mLines.end(); 6363 while (line != lineEnd) { 6364 if (line == prevSibLine) { 6365 break; 6366 } 6367 ++line; 6368 } 6369 if (line == lineEnd) { 6370 // By elimination, the line must be in our overflow lines. 6371 lineList = &overflowLines->mLines; 6372 frames = &overflowLines->mFrames; 6373 } 6374 } 6375 6376 nsLineList::iterator nextLine = prevSibLine.next(); 6377 nsIFrame* lastFrameInLine = nextLine == lineList->end() 6378 ? frames->LastChild() 6379 : nextLine->mFirstChild->GetPrevSibling(); 6380 prevSiblingIndex = prevSibLine->RLIndexOf(aPrevSibling, lastFrameInLine); 6381 MOZ_ASSERT(prevSiblingIndex >= 0, 6382 "aPrevSibling must be in aPrevSiblingLine"); 6383 } else { 6384 prevSibLine = lineList->end(); 6385 prevSiblingIndex = -1; 6386 if (aPrevSibling) { 6387 // XXX_perf This is technically O(N^2) in some cases, but by using 6388 // RFind instead of Find, we make it O(N) in the most common case, 6389 // which is appending content. 6390 6391 // Find the line that contains the previous sibling 6392 if (!nsLineBox::RFindLineContaining(aPrevSibling, lineList->begin(), 6393 prevSibLine, mFrames.LastChild(), 6394 &prevSiblingIndex)) { 6395 // Not in mLines - try overflow lines. 6396 FrameLines* overflowLines = GetOverflowLines(); 6397 bool found = false; 6398 if (overflowLines) { 6399 prevSibLine = overflowLines->mLines.end(); 6400 prevSiblingIndex = -1; 6401 found = nsLineBox::RFindLineContaining( 6402 aPrevSibling, overflowLines->mLines.begin(), prevSibLine, 6403 overflowLines->mFrames.LastChild(), &prevSiblingIndex); 6404 } 6405 if (MOZ_LIKELY(found)) { 6406 lineList = &overflowLines->mLines; 6407 frames = &overflowLines->mFrames; 6408 } else { 6409 // Note: defensive code! RFindLineContaining must not return 6410 // false in this case, so if it does... 6411 MOZ_ASSERT_UNREACHABLE("prev sibling not in line list"); 6412 aPrevSibling = nullptr; 6413 prevSibLine = lineList->end(); 6414 } 6415 } 6416 } 6417 } 6418 6419 // Find the frame following aPrevSibling so that we can join up the 6420 // two lists of frames. 6421 if (aPrevSibling) { 6422 // Split line containing aPrevSibling in two if the insertion 6423 // point is somewhere in the middle of the line. 6424 int32_t rem = prevSibLine->GetChildCount() - prevSiblingIndex - 1; 6425 if (rem) { 6426 // Split the line in two where the frame(s) are being inserted. 6427 nsLineBox* line = 6428 NewLineBox(prevSibLine, aPrevSibling->GetNextSibling(), rem); 6429 lineList->after_insert(prevSibLine, line); 6430 // Mark prevSibLine dirty and as needing textrun invalidation, since 6431 // we may be breaking up text in the line. Its previous line may also 6432 // need to be invalidated because it may be able to pull some text up. 6433 MarkLineDirty(prevSibLine, lineList); 6434 // The new line will also need its textruns recomputed because of the 6435 // frame changes. 6436 line->MarkDirty(); 6437 line->SetInvalidateTextRuns(true); 6438 } 6439 } else if (!lineList->empty()) { 6440 lineList->front()->MarkDirty(); 6441 lineList->front()->SetInvalidateTextRuns(true); 6442 } 6443 const nsFrameList::Slice& newFrames = 6444 frames->InsertFrames(nullptr, aPrevSibling, std::move(aFrameList)); 6445 6446 // Walk through the new frames being added and update the line data 6447 // structures to fit. 6448 for (nsIFrame* newFrame : newFrames) { 6449 NS_ASSERTION(!aPrevSibling || aPrevSibling->GetNextSibling() == newFrame, 6450 "Unexpected aPrevSibling"); 6451 NS_ASSERTION( 6452 !newFrame->IsPlaceholderFrame() || 6453 (!newFrame->IsAbsolutelyPositioned() && !newFrame->IsFloating()), 6454 "Placeholders should not float or be positioned"); 6455 6456 bool isBlock = newFrame->IsBlockOutside(); 6457 6458 // If the frame is a block frame, or if there is no previous line or if the 6459 // previous line is a block line we need to make a new line. We also make 6460 // a new line, as an optimization, in the two cases we know we'll need it: 6461 // if the previous line ended with a <br>, or if it has significant 6462 // whitespace and ended in a newline. 6463 if (isBlock || prevSibLine == lineList->end() || prevSibLine->IsBlock() || 6464 (aPrevSibling && ShouldPutNextSiblingOnNewLine(aPrevSibling))) { 6465 // Create a new line for the frame and add its line to the line 6466 // list. 6467 nsLineBox* line = NewLineBox(newFrame, isBlock); 6468 if (prevSibLine != lineList->end()) { 6469 // Append new line after prevSibLine 6470 lineList->after_insert(prevSibLine, line); 6471 ++prevSibLine; 6472 } else { 6473 // New line is going before the other lines 6474 lineList->push_front(line); 6475 prevSibLine = lineList->begin(); 6476 } 6477 } else { 6478 prevSibLine->NoteFrameAdded(newFrame); 6479 // We're adding inline content to prevSibLine, so we need to mark it 6480 // dirty, ensure its textruns are recomputed, and possibly do the same 6481 // to its previous line since that line may be able to pull content up. 6482 MarkLineDirty(prevSibLine, lineList); 6483 } 6484 6485 aPrevSibling = newFrame; 6486 } 6487 6488 #ifdef DEBUG 6489 MOZ_ASSERT(aFrameList.IsEmpty()); 6490 VerifyLines(true); 6491 #endif 6492 } 6493 6494 nsContainerFrame* nsBlockFrame::GetRubyContentPseudoFrame() { 6495 auto* firstChild = PrincipalChildList().FirstChild(); 6496 if (firstChild && firstChild->IsRubyFrame() && 6497 firstChild->Style()->GetPseudoType() == 6498 PseudoStyleType::blockRubyContent) { 6499 return static_cast<nsContainerFrame*>(firstChild); 6500 } 6501 return nullptr; 6502 } 6503 6504 nsContainerFrame* nsBlockFrame::GetContentInsertionFrame() { 6505 // 'display:block ruby' use the inner (Ruby) frame for insertions. 6506 if (auto* rubyContentPseudoFrame = GetRubyContentPseudoFrame()) { 6507 return rubyContentPseudoFrame; 6508 } 6509 return this; 6510 } 6511 6512 void nsBlockFrame::AppendDirectlyOwnedAnonBoxes( 6513 nsTArray<OwnedAnonBox>& aResult) { 6514 if (auto* rubyContentPseudoFrame = GetRubyContentPseudoFrame()) { 6515 aResult.AppendElement(OwnedAnonBox(rubyContentPseudoFrame)); 6516 } 6517 } 6518 6519 void nsBlockFrame::RemoveFloatFromFloatCache(nsIFrame* aFloat) { 6520 // Find which line contains the float, so we can update 6521 // the float cache. 6522 for (auto& line : Lines()) { 6523 if (line.IsInline() && line.RemoveFloat(aFloat)) { 6524 break; 6525 } 6526 } 6527 } 6528 6529 void nsBlockFrame::RemoveFloat(nsIFrame* aFloat) { 6530 MOZ_ASSERT(aFloat); 6531 6532 // Floats live in floats list, pushed floats list, or overflow out-of-flow 6533 // list. 6534 MOZ_ASSERT( 6535 GetChildList(FrameChildListID::Float).ContainsFrame(aFloat) || 6536 GetChildList(FrameChildListID::PushedFloats).ContainsFrame(aFloat) || 6537 GetChildList(FrameChildListID::OverflowOutOfFlow) 6538 .ContainsFrame(aFloat), 6539 "aFloat is not our child or on an unexpected frame list"); 6540 6541 bool didStartRemovingFloat = false; 6542 if (nsFrameList* floats = GetFloats()) { 6543 didStartRemovingFloat = true; 6544 if (floats->StartRemoveFrame(aFloat)) { 6545 if (floats->IsEmpty()) { 6546 StealFloats()->Delete(PresShell()); 6547 } 6548 return; 6549 } 6550 } 6551 6552 if (nsFrameList* pushedFloats = GetPushedFloats()) { 6553 bool found; 6554 if (didStartRemovingFloat) { 6555 found = pushedFloats->ContinueRemoveFrame(aFloat); 6556 } else { 6557 didStartRemovingFloat = true; 6558 found = pushedFloats->StartRemoveFrame(aFloat); 6559 } 6560 if (found) { 6561 if (pushedFloats->IsEmpty()) { 6562 StealPushedFloats()->Delete(PresShell()); 6563 } 6564 return; 6565 } 6566 } 6567 6568 { 6569 nsAutoOOFFrameList oofs(this); 6570 if (didStartRemovingFloat ? oofs.mList.ContinueRemoveFrame(aFloat) 6571 : oofs.mList.StartRemoveFrame(aFloat)) { 6572 return; 6573 } 6574 } 6575 } 6576 6577 void nsBlockFrame::DoRemoveFloats(DestroyContext& aContext, nsIFrame* aFrame) { 6578 // Note: nsFloatingFirstLetterFrame might lose its floating style after 6579 // nsBlockFrame::UpdateFirstLetterStyle(). 6580 MOZ_ASSERT(aFrame->IsFloating() || static_cast<nsFloatingFirstLetterFrame*>( 6581 do_QueryFrame(aFrame)), 6582 "DoRemoveFloats() can only remove float elements!"); 6583 6584 // The containing block is always the parent of aFrame. 6585 auto* block = static_cast<nsBlockFrame*>(aFrame->GetParent()); 6586 6587 // First remove aFrame's next-in-flows. 6588 if (nsIFrame* nif = aFrame->GetNextInFlow()) { 6589 nif->GetParent()->DeleteNextInFlowChild(aContext, nif, false); 6590 } 6591 // Now remove aFrame from its child list and Destroy it. 6592 block->RemoveFloatFromFloatCache(aFrame); 6593 block->RemoveFloat(aFrame); 6594 aFrame->Destroy(aContext); 6595 } 6596 6597 /** 6598 * This helps us iterate over the list of all normal + overflow lines 6599 */ 6600 void nsBlockFrame::TryAllLines(nsLineList::iterator* aIterator, 6601 nsLineList::iterator* aStartIterator, 6602 nsLineList::iterator* aEndIterator, 6603 bool* aInOverflowLines, 6604 FrameLines** aOverflowLines) { 6605 if (*aIterator == *aEndIterator) { 6606 if (!*aInOverflowLines) { 6607 // Try the overflow lines 6608 *aInOverflowLines = true; 6609 FrameLines* lines = GetOverflowLines(); 6610 if (lines) { 6611 *aStartIterator = lines->mLines.begin(); 6612 *aIterator = *aStartIterator; 6613 *aEndIterator = lines->mLines.end(); 6614 *aOverflowLines = lines; 6615 } 6616 } 6617 } 6618 } 6619 6620 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame, 6621 LineIterator aLine) 6622 : mFrame(aFrame), mLine(aLine), mLineList(&aFrame->mLines) { 6623 // This will assert if aLine isn't in mLines of aFrame: 6624 DebugOnly<bool> check = aLine == mFrame->LinesBegin(); 6625 } 6626 6627 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame, 6628 LineIterator aLine, 6629 bool aInOverflow) 6630 : mFrame(aFrame), 6631 mLine(aLine), 6632 mLineList(aInOverflow ? &aFrame->GetOverflowLines()->mLines 6633 : &aFrame->mLines) {} 6634 6635 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame, 6636 bool* aFoundValidLine) 6637 : mFrame(aFrame), mLineList(&aFrame->mLines) { 6638 mLine = aFrame->LinesBegin(); 6639 *aFoundValidLine = FindValidLine(); 6640 } 6641 6642 static bool AnonymousBoxIsBFC(const ComputedStyle* aStyle) { 6643 switch (aStyle->GetPseudoType()) { 6644 case PseudoStyleType::fieldsetContent: 6645 case PseudoStyleType::columnContent: 6646 case PseudoStyleType::cellContent: 6647 case PseudoStyleType::scrolledContent: 6648 case PseudoStyleType::anonymousItem: 6649 return true; 6650 default: 6651 return false; 6652 } 6653 } 6654 6655 static bool StyleEstablishesBFC(const ComputedStyle* aStyle) { 6656 // paint/layout containment boxes and multi-column containers establish an 6657 // independent formatting context. 6658 // https://drafts.csswg.org/css-contain/#containment-paint 6659 // https://drafts.csswg.org/css-contain/#containment-layout 6660 // https://github.com/w3c/csswg-drafts/issues/10544 6661 // https://drafts.csswg.org/css-align/#distribution-block 6662 // https://drafts.csswg.org/css-multicol/#columns 6663 const auto* disp = aStyle->StyleDisplay(); 6664 return disp->IsContainPaint() || disp->IsContainLayout() || 6665 disp->mContainerType & 6666 (StyleContainerType::SIZE | StyleContainerType::INLINE_SIZE) || 6667 disp->DisplayInside() == StyleDisplayInside::FlowRoot || 6668 disp->IsAbsolutelyPositionedStyle() || disp->IsFloatingStyle() || 6669 aStyle->IsRootElementStyle() || AnonymousBoxIsBFC(aStyle); 6670 } 6671 6672 static bool EstablishesBFC(const nsBlockFrame* aFrame) { 6673 if (aFrame->HasAnyClassFlag(LayoutFrameClassFlags::BlockFormattingContext)) { 6674 return true; 6675 } 6676 6677 if (nsIFrame* parent = aFrame->GetParent()) { 6678 if (parent->IsFieldSetFrame()) { 6679 // A rendered legend always establishes a new formatting context, and so 6680 // does the fieldset content frame, so we can just return true here. 6681 // https://html.spec.whatwg.org/#rendered-legend 6682 return true; 6683 } 6684 6685 const auto wm = aFrame->GetWritingMode(); 6686 const auto parentWM = parent->GetWritingMode(); 6687 if (wm.GetBlockDir() != parentWM.GetBlockDir() || 6688 wm.IsVerticalSideways() != parentWM.IsVerticalSideways()) { 6689 // If a box has a different writing-mode value than its containing block 6690 // [...] if the box is a block container, then it establishes a new block 6691 // formatting context. 6692 // https://drafts.csswg.org/css-writing-modes/#block-flow 6693 return true; 6694 } 6695 } 6696 6697 if (aFrame->IsColumnSpan()) { 6698 return true; 6699 } 6700 6701 if (aFrame->IsContentAligned()) { 6702 return true; 6703 } 6704 6705 if (aFrame->IsSuppressedScrollableBlockForPrint()) { 6706 return true; 6707 } 6708 6709 const auto* style = aFrame->Style(); 6710 if (style->GetPseudoType() == PseudoStyleType::marker) { 6711 if (aFrame->GetParent() && 6712 aFrame->GetParent()->StyleList()->mListStylePosition == 6713 StyleListStylePosition::Outside) { 6714 // An outside ::marker needs to be an independent formatting context 6715 // to avoid being influenced by the float manager etc. 6716 return true; 6717 } 6718 } 6719 return StyleEstablishesBFC(style); 6720 } 6721 6722 void nsBlockFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) { 6723 nsContainerFrame::DidSetComputedStyle(aOldStyle); 6724 if (IsInSVGTextSubtree() && 6725 (StyleSVGReset()->HasNonScalingStroke() && 6726 (!aOldStyle || !aOldStyle->StyleSVGReset()->HasNonScalingStroke()))) { 6727 nsIFrame* textFrame = 6728 nsLayoutUtils::GetClosestFrameOfType(this, LayoutFrameType::SVGText); 6729 MOZ_ASSERT(textFrame, "Expecting to find an SVG text frame"); 6730 SVGUtils::UpdateNonScalingStrokeStateBit(textFrame); 6731 } 6732 if (!aOldStyle) { 6733 return; 6734 } 6735 6736 const bool isBFC = EstablishesBFC(this); 6737 if (HasAnyStateBits(NS_BLOCK_BFC) != isBFC) { 6738 if (MaybeHasFloats()) { 6739 // If the frame contains floats, this update may change their float 6740 // manager. Be safe by dirtying all descendant lines of the nearest 6741 // ancestor's float manager. 6742 RemoveStateBits(NS_BLOCK_BFC); 6743 MarkSameFloatManagerLinesDirty(this); 6744 } 6745 AddOrRemoveStateBits(NS_BLOCK_BFC, isBFC); 6746 } 6747 } 6748 6749 void nsBlockFrame::UpdateFirstLetterStyle(ServoRestyleState& aRestyleState) { 6750 nsIFrame* letterFrame = GetFirstLetter(); 6751 if (!letterFrame) { 6752 return; 6753 } 6754 6755 // Figure out what the right style parent is. This needs to match 6756 // nsCSSFrameConstructor::CreateLetterFrame. 6757 nsIFrame* inFlowFrame = letterFrame; 6758 if (inFlowFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { 6759 inFlowFrame = inFlowFrame->GetPlaceholderFrame(); 6760 } 6761 nsIFrame* styleParent = CorrectStyleParentFrame(inFlowFrame->GetParent(), 6762 PseudoStyleType::firstLetter); 6763 ComputedStyle* parentStyle = styleParent->Style(); 6764 RefPtr<ComputedStyle> firstLetterStyle = 6765 aRestyleState.StyleSet().ResolvePseudoElementStyle( 6766 *mContent->AsElement(), PseudoStyleType::firstLetter, nullptr, 6767 parentStyle); 6768 // Note that we don't need to worry about changehints for the continuation 6769 // styles: those will be handled by the styleParent already. 6770 RefPtr<ComputedStyle> continuationStyle = 6771 aRestyleState.StyleSet().ResolveStyleForFirstLetterContinuation( 6772 parentStyle); 6773 UpdateStyleOfOwnedChildFrame(letterFrame, firstLetterStyle, aRestyleState, 6774 Some(continuationStyle.get())); 6775 6776 // We also want to update the style on the textframe inside the first-letter. 6777 // We don't need to compute a changehint for this, though, since any changes 6778 // to it are handled by the first-letter anyway. 6779 nsIFrame* textFrame = letterFrame->PrincipalChildList().FirstChild(); 6780 RefPtr<ComputedStyle> firstTextStyle = 6781 aRestyleState.StyleSet().ResolveStyleForText(textFrame->GetContent(), 6782 firstLetterStyle); 6783 textFrame->SetComputedStyle(firstTextStyle); 6784 6785 // We don't need to update style for textFrame's continuations: it's already 6786 // set up to inherit from parentStyle, which is what we want. 6787 } 6788 6789 static nsIFrame* FindChildContaining(nsBlockFrame* aFrame, 6790 nsIFrame* aFindFrame) { 6791 NS_ASSERTION(aFrame, "must have frame"); 6792 nsIFrame* child; 6793 while (true) { 6794 nsIFrame* block = aFrame; 6795 do { 6796 child = nsLayoutUtils::FindChildContainingDescendant(block, aFindFrame); 6797 if (child) { 6798 break; 6799 } 6800 block = block->GetNextContinuation(); 6801 } while (block); 6802 if (!child) { 6803 return nullptr; 6804 } 6805 if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { 6806 break; 6807 } 6808 aFindFrame = child->GetPlaceholderFrame(); 6809 } 6810 6811 return child; 6812 } 6813 6814 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame, 6815 nsIFrame* aFindFrame, 6816 bool* aFoundValidLine) 6817 : mFrame(aFrame), mLineList(&aFrame->mLines) { 6818 *aFoundValidLine = false; 6819 6820 nsIFrame* child = FindChildContaining(aFrame, aFindFrame); 6821 if (!child) { 6822 return; 6823 } 6824 6825 LineIterator line_end = aFrame->LinesEnd(); 6826 mLine = aFrame->LinesBegin(); 6827 if (mLine != line_end && mLine.next() == line_end && 6828 !aFrame->HasOverflowLines()) { 6829 // The block has a single line - that must be it! 6830 *aFoundValidLine = true; 6831 return; 6832 } 6833 6834 // Try to use the cursor if it exists, otherwise fall back to the first line 6835 if (nsLineBox* const cursor = aFrame->GetLineCursorForQuery()) { 6836 mLine = line_end; 6837 // Perform a simultaneous forward and reverse search starting from the 6838 // line cursor. 6839 nsBlockFrame::LineIterator line = aFrame->LinesBeginFrom(cursor); 6840 nsBlockFrame::ReverseLineIterator rline = aFrame->LinesRBeginFrom(cursor); 6841 nsBlockFrame::ReverseLineIterator rline_end = aFrame->LinesREnd(); 6842 // rline is positioned on the line containing 'cursor', so it's not 6843 // rline_end. So we can safely increment it (i.e. move it to one line 6844 // earlier) to start searching there. 6845 ++rline; 6846 while (line != line_end || rline != rline_end) { 6847 if (line != line_end) { 6848 if (line->Contains(child)) { 6849 mLine = line; 6850 break; 6851 } 6852 ++line; 6853 } 6854 if (rline != rline_end) { 6855 if (rline->Contains(child)) { 6856 mLine = rline; 6857 break; 6858 } 6859 ++rline; 6860 } 6861 } 6862 if (mLine != line_end) { 6863 *aFoundValidLine = true; 6864 if (mLine != cursor) { 6865 aFrame->SetProperty(nsBlockFrame::LineCursorPropertyQuery(), mLine); 6866 } 6867 return; 6868 } 6869 } else { 6870 for (mLine = aFrame->LinesBegin(); mLine != line_end; ++mLine) { 6871 if (mLine->Contains(child)) { 6872 *aFoundValidLine = true; 6873 return; 6874 } 6875 } 6876 } 6877 // Didn't find the line 6878 MOZ_ASSERT(mLine == line_end, "mLine should be line_end at this point"); 6879 6880 // If we reach here, it means that we have not been able to find the 6881 // desired frame in our in-flow lines. So we should start looking at 6882 // our overflow lines. In order to do that, we set mLine to the end 6883 // iterator so that FindValidLine starts to look at overflow lines, 6884 // if any. 6885 6886 if (!FindValidLine()) { 6887 return; 6888 } 6889 6890 do { 6891 if (mLine->Contains(child)) { 6892 *aFoundValidLine = true; 6893 return; 6894 } 6895 } while (Next()); 6896 } 6897 6898 nsBlockFrame::LineIterator nsBlockInFlowLineIterator::End() { 6899 return mLineList->end(); 6900 } 6901 6902 bool nsBlockInFlowLineIterator::IsLastLineInList() { 6903 LineIterator end = End(); 6904 return mLine != end && mLine.next() == end; 6905 } 6906 6907 bool nsBlockInFlowLineIterator::Next() { 6908 ++mLine; 6909 return FindValidLine(); 6910 } 6911 6912 bool nsBlockInFlowLineIterator::Prev() { 6913 LineIterator begin = mLineList->begin(); 6914 if (mLine != begin) { 6915 --mLine; 6916 return true; 6917 } 6918 bool currentlyInOverflowLines = GetInOverflow(); 6919 while (true) { 6920 if (currentlyInOverflowLines) { 6921 mLineList = &mFrame->mLines; 6922 mLine = mLineList->end(); 6923 if (mLine != mLineList->begin()) { 6924 --mLine; 6925 return true; 6926 } 6927 } else { 6928 mFrame = static_cast<nsBlockFrame*>(mFrame->GetPrevInFlow()); 6929 if (!mFrame) { 6930 return false; 6931 } 6932 nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines(); 6933 if (overflowLines) { 6934 mLineList = &overflowLines->mLines; 6935 mLine = mLineList->end(); 6936 NS_ASSERTION(mLine != mLineList->begin(), "empty overflow line list?"); 6937 --mLine; 6938 return true; 6939 } 6940 } 6941 currentlyInOverflowLines = !currentlyInOverflowLines; 6942 } 6943 } 6944 6945 bool nsBlockInFlowLineIterator::FindValidLine() { 6946 LineIterator end = mLineList->end(); 6947 if (mLine != end) { 6948 return true; 6949 } 6950 bool currentlyInOverflowLines = GetInOverflow(); 6951 while (true) { 6952 if (currentlyInOverflowLines) { 6953 mFrame = static_cast<nsBlockFrame*>(mFrame->GetNextInFlow()); 6954 if (!mFrame) { 6955 return false; 6956 } 6957 mLineList = &mFrame->mLines; 6958 mLine = mLineList->begin(); 6959 if (mLine != mLineList->end()) { 6960 return true; 6961 } 6962 } else { 6963 nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines(); 6964 if (overflowLines) { 6965 mLineList = &overflowLines->mLines; 6966 mLine = mLineList->begin(); 6967 NS_ASSERTION(mLine != mLineList->end(), "empty overflow line list?"); 6968 return true; 6969 } 6970 } 6971 currentlyInOverflowLines = !currentlyInOverflowLines; 6972 } 6973 } 6974 6975 // This function removes aDeletedFrame and all its continuations. It 6976 // is optimized for deleting a whole series of frames. The easy 6977 // implementation would invoke itself recursively on 6978 // aDeletedFrame->GetNextContinuation, then locate the line containing 6979 // aDeletedFrame and remove aDeletedFrame from that line. But here we 6980 // start by locating aDeletedFrame and then scanning from that point 6981 // on looking for continuations. 6982 void nsBlockFrame::DoRemoveFrame(DestroyContext& aContext, 6983 nsIFrame* aDeletedFrame, uint32_t aFlags) { 6984 MOZ_ASSERT(!aDeletedFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW | 6985 NS_FRAME_IS_OVERFLOW_CONTAINER), 6986 "DoRemoveFrame() does not support removing out-of-flow frames or " 6987 "overflow containers!"); 6988 6989 // Find the line that contains deletedFrame. Start from the line cursor 6990 // (if available) and search to the end of the normal line list, then 6991 // from the start to the line cursor, and last the overflow lines. 6992 nsLineList::iterator line_start = mLines.begin(), line_end = mLines.end(); 6993 nsLineList::iterator line = line_start; 6994 6995 // We use the line cursor to attempt to optimize removal, but must ensure 6996 // it is cleared if lines change such that it may become invalid. 6997 bool found = false; 6998 if (nsLineBox* cursor = GetLineCursorForDisplay()) { 6999 for (line.SetPosition(cursor); line != line_end; ++line) { 7000 if (line->Contains(aDeletedFrame)) { 7001 found = true; 7002 break; 7003 } 7004 } 7005 if (!found) { 7006 // Setup for a shorter TryAllLines normal line search to avoid searching 7007 // the [cursor .. line_end] range again. 7008 line = line_start; 7009 line_end.SetPosition(cursor); 7010 } 7011 } 7012 7013 FrameLines* overflowLines = nullptr; 7014 bool searchingOverflowList = false; 7015 if (!found) { 7016 // Make sure we look in the overflow lines even if the normal line 7017 // list is empty. 7018 TryAllLines(&line, &line_start, &line_end, &searchingOverflowList, 7019 &overflowLines); 7020 while (line != line_end) { 7021 if (line->Contains(aDeletedFrame)) { 7022 break; 7023 } 7024 ++line; 7025 TryAllLines(&line, &line_start, &line_end, &searchingOverflowList, 7026 &overflowLines); 7027 } 7028 if (!searchingOverflowList && (GetStateBits() & NS_BLOCK_HAS_LINE_CURSOR)) { 7029 // Restore line_end since we shortened the search to the cursor. 7030 line_end = mLines.end(); 7031 // Clear our line cursors, since our normal line list may change. 7032 ClearLineCursors(); 7033 } 7034 } 7035 7036 if (line == line_end) { 7037 NS_ERROR("can't find deleted frame in lines"); 7038 return; 7039 } 7040 7041 if (!(aFlags & FRAMES_ARE_EMPTY)) { 7042 if (line != line_start) { 7043 line.prev()->MarkDirty(); 7044 line.prev()->SetInvalidateTextRuns(true); 7045 } else if (searchingOverflowList && !mLines.empty()) { 7046 mLines.back()->MarkDirty(); 7047 mLines.back()->SetInvalidateTextRuns(true); 7048 } 7049 } 7050 7051 while (line != line_end && aDeletedFrame) { 7052 MOZ_ASSERT(this == aDeletedFrame->GetParent(), "messed up delete code"); 7053 MOZ_ASSERT(line->Contains(aDeletedFrame), "frame not in line"); 7054 7055 if (!(aFlags & FRAMES_ARE_EMPTY)) { 7056 line->MarkDirty(); 7057 line->SetInvalidateTextRuns(true); 7058 } 7059 7060 // If the frame being deleted is the last one on the line then 7061 // optimize away the line->Contains(next-in-flow) call below. 7062 bool isLastFrameOnLine = 1 == line->GetChildCount(); 7063 if (!isLastFrameOnLine) { 7064 LineIterator next = line.next(); 7065 nsIFrame* lastFrame = 7066 next != line_end 7067 ? next->mFirstChild->GetPrevSibling() 7068 : (searchingOverflowList ? overflowLines->mFrames.LastChild() 7069 : mFrames.LastChild()); 7070 NS_ASSERTION(next == line_end || lastFrame == line->LastChild(), 7071 "unexpected line frames"); 7072 isLastFrameOnLine = lastFrame == aDeletedFrame; 7073 } 7074 7075 // Remove aDeletedFrame from the line 7076 if (line->mFirstChild == aDeletedFrame) { 7077 // We should be setting this to null if aDeletedFrame 7078 // is the only frame on the line. HOWEVER in that case 7079 // we will be removing the line anyway, see below. 7080 line->mFirstChild = aDeletedFrame->GetNextSibling(); 7081 } 7082 7083 // Hmm, this won't do anything if we're removing a frame in the first 7084 // overflow line... Hopefully doesn't matter 7085 --line; 7086 if (line != line_end && !line->IsBlock()) { 7087 // Since we just removed a frame that follows some inline 7088 // frames, we need to reflow the previous line. 7089 line->MarkDirty(); 7090 } 7091 ++line; 7092 7093 // Take aDeletedFrame out of the sibling list. Note that 7094 // prevSibling will only be nullptr when we are deleting the very 7095 // first frame in the main or overflow list. 7096 if (searchingOverflowList) { 7097 overflowLines->mFrames.RemoveFrame(aDeletedFrame); 7098 } else { 7099 mFrames.RemoveFrame(aDeletedFrame); 7100 } 7101 7102 // Update the child count of the line to be accurate 7103 line->NoteFrameRemoved(aDeletedFrame); 7104 7105 // Destroy frame; capture its next continuation first in case we need 7106 // to destroy that too. 7107 nsIFrame* deletedNextContinuation = 7108 (aFlags & REMOVE_FIXED_CONTINUATIONS) 7109 ? aDeletedFrame->GetNextContinuation() 7110 : aDeletedFrame->GetNextInFlow(); 7111 #ifdef NOISY_REMOVE_FRAME 7112 printf("DoRemoveFrame: %s line=%p frame=", 7113 searchingOverflowList ? "overflow" : "normal", line.get()); 7114 aDeletedFrame->ListTag(stdout); 7115 printf(" prevSibling=%p deletedNextContinuation=%p\n", 7116 aDeletedFrame->GetPrevSibling(), deletedNextContinuation); 7117 #endif 7118 7119 // If next-in-flow is an overflow container, must remove it first. 7120 // FIXME: Can we do this unconditionally? 7121 if (deletedNextContinuation && deletedNextContinuation->HasAnyStateBits( 7122 NS_FRAME_IS_OVERFLOW_CONTAINER)) { 7123 deletedNextContinuation->GetParent()->DeleteNextInFlowChild( 7124 aContext, deletedNextContinuation, false); 7125 deletedNextContinuation = nullptr; 7126 } 7127 7128 aDeletedFrame->Destroy(aContext); 7129 aDeletedFrame = deletedNextContinuation; 7130 7131 bool haveAdvancedToNextLine = false; 7132 // If line is empty, remove it now. 7133 if (0 == line->GetChildCount()) { 7134 #ifdef NOISY_REMOVE_FRAME 7135 printf("DoRemoveFrame: %s line=%p became empty so it will be removed\n", 7136 searchingOverflowList ? "overflow" : "normal", line.get()); 7137 #endif 7138 nsLineBox* cur = line; 7139 if (!searchingOverflowList) { 7140 line = mLines.erase(line); 7141 ClearLineCursors(); 7142 // Invalidate the space taken up by the line. 7143 // XXX We need to do this if we're removing a frame as a result of 7144 // a call to RemoveFrame(), but we may not need to do this in all 7145 // cases... 7146 #ifdef NOISY_BLOCK_INVALIDATE 7147 nsRect inkOverflow(cur->InkOverflowRect()); 7148 printf("%p invalidate 10 (%d, %d, %d, %d)\n", this, inkOverflow.x, 7149 inkOverflow.y, inkOverflow.width, inkOverflow.height); 7150 #endif 7151 } else { 7152 line = overflowLines->mLines.erase(line); 7153 if (overflowLines->mLines.empty()) { 7154 DestroyOverflowLines(); 7155 overflowLines = nullptr; 7156 // We just invalidated our iterators. Since we were in 7157 // the overflow lines list, which is now empty, set them 7158 // so we're at the end of the regular line list. 7159 line_start = mLines.begin(); 7160 line_end = mLines.end(); 7161 line = line_end; 7162 } 7163 } 7164 FreeLineBox(cur); 7165 7166 // If we're removing a line, ReflowDirtyLines isn't going to 7167 // know that it needs to slide lines unless something is marked 7168 // dirty. So mark the previous margin of the next line dirty if 7169 // there is one. 7170 if (line != line_end) { 7171 line->MarkPreviousMarginDirty(); 7172 } 7173 haveAdvancedToNextLine = true; 7174 } else { 7175 // Make the line that just lost a frame dirty, and advance to 7176 // the next line. 7177 if (!deletedNextContinuation || isLastFrameOnLine || 7178 !line->Contains(deletedNextContinuation)) { 7179 line->MarkDirty(); 7180 ++line; 7181 haveAdvancedToNextLine = true; 7182 } 7183 } 7184 7185 if (deletedNextContinuation) { 7186 // See if we should keep looking in the current flow's line list. 7187 if (deletedNextContinuation->GetParent() != this) { 7188 // The deceased frames continuation is not a child of the 7189 // current block. So break out of the loop so that we advance 7190 // to the next parent. 7191 // 7192 // If we have a continuation in a different block then all bets are 7193 // off regarding whether we are deleting frames without actual content, 7194 // so don't propagate FRAMES_ARE_EMPTY any further. 7195 aFlags &= ~FRAMES_ARE_EMPTY; 7196 break; 7197 } 7198 7199 // If we advanced to the next line then check if we should switch to the 7200 // overflow line list. 7201 if (haveAdvancedToNextLine) { 7202 if (line != line_end && !searchingOverflowList && 7203 !line->Contains(deletedNextContinuation)) { 7204 // We have advanced to the next *normal* line but the next-in-flow 7205 // is not there - force a switch to the overflow line list. 7206 line = line_end; 7207 } 7208 7209 TryAllLines(&line, &line_start, &line_end, &searchingOverflowList, 7210 &overflowLines); 7211 #ifdef NOISY_REMOVE_FRAME 7212 printf("DoRemoveFrame: now on %s line=%p\n", 7213 searchingOverflowList ? "overflow" : "normal", line.get()); 7214 #endif 7215 } 7216 } 7217 } 7218 7219 if (!(aFlags & FRAMES_ARE_EMPTY) && line.next() != line_end) { 7220 line.next()->MarkDirty(); 7221 line.next()->SetInvalidateTextRuns(true); 7222 } 7223 7224 #ifdef DEBUG 7225 VerifyLines(true); 7226 VerifyOverflowSituation(); 7227 #endif 7228 7229 // Advance to next flow block if the frame has more continuations. 7230 if (!aDeletedFrame) { 7231 return; 7232 } 7233 nsBlockFrame* nextBlock = do_QueryFrame(aDeletedFrame->GetParent()); 7234 NS_ASSERTION(nextBlock, "Our child's continuation's parent is not a block?"); 7235 uint32_t flags = (aFlags & REMOVE_FIXED_CONTINUATIONS); 7236 nextBlock->DoRemoveFrame(aContext, aDeletedFrame, flags); 7237 } 7238 7239 static bool FindBlockLineFor(nsIFrame* aChild, nsLineList::iterator aBegin, 7240 nsLineList::iterator aEnd, 7241 nsLineList::iterator* aResult) { 7242 MOZ_ASSERT(aChild->IsBlockOutside()); 7243 for (nsLineList::iterator line = aBegin; line != aEnd; ++line) { 7244 MOZ_ASSERT(line->GetChildCount() > 0); 7245 if (line->IsBlock() && line->mFirstChild == aChild) { 7246 MOZ_ASSERT(line->GetChildCount() == 1); 7247 *aResult = line; 7248 return true; 7249 } 7250 } 7251 return false; 7252 } 7253 7254 static bool FindInlineLineFor(nsIFrame* aChild, const nsFrameList& aFrameList, 7255 nsLineList::iterator aBegin, 7256 nsLineList::iterator aEnd, 7257 nsLineList::iterator* aResult) { 7258 MOZ_ASSERT(!aChild->IsBlockOutside()); 7259 for (nsLineList::iterator line = aBegin; line != aEnd; ++line) { 7260 MOZ_ASSERT(line->GetChildCount() > 0); 7261 if (!line->IsBlock()) { 7262 // Optimize by comparing the line's last child first. 7263 nsLineList::iterator next = line.next(); 7264 if (aChild == (next == aEnd ? aFrameList.LastChild() 7265 : next->mFirstChild->GetPrevSibling()) || 7266 line->Contains(aChild)) { 7267 *aResult = line; 7268 return true; 7269 } 7270 } 7271 } 7272 return false; 7273 } 7274 7275 static bool FindLineFor(nsIFrame* aChild, const nsFrameList& aFrameList, 7276 nsLineList::iterator aBegin, nsLineList::iterator aEnd, 7277 nsLineList::iterator* aResult) { 7278 return aChild->IsBlockOutside() 7279 ? FindBlockLineFor(aChild, aBegin, aEnd, aResult) 7280 : FindInlineLineFor(aChild, aFrameList, aBegin, aEnd, aResult); 7281 } 7282 7283 void nsBlockFrame::StealFrame(nsIFrame* aChild) { 7284 MOZ_ASSERT(aChild->GetParent() == this); 7285 7286 if (aChild->IsFloating()) { 7287 RemoveFloat(aChild); 7288 return; 7289 } 7290 7291 if (MaybeStealOverflowContainerFrame(aChild)) { 7292 return; 7293 } 7294 7295 MOZ_ASSERT(!aChild->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)); 7296 7297 nsLineList::iterator line; 7298 if (FindLineFor(aChild, mFrames, mLines.begin(), mLines.end(), &line)) { 7299 RemoveFrameFromLine(aChild, line, mFrames, mLines); 7300 } else { 7301 FrameLines* overflowLines = GetOverflowLines(); 7302 DebugOnly<bool> found; 7303 found = FindLineFor(aChild, overflowLines->mFrames, 7304 overflowLines->mLines.begin(), 7305 overflowLines->mLines.end(), &line); 7306 MOZ_ASSERT(found, "Why can't we find aChild in our overflow lines?"); 7307 RemoveFrameFromLine(aChild, line, overflowLines->mFrames, 7308 overflowLines->mLines); 7309 if (overflowLines->mLines.empty()) { 7310 DestroyOverflowLines(); 7311 } 7312 } 7313 } 7314 7315 void nsBlockFrame::RemoveFrameFromLine(nsIFrame* aChild, 7316 nsLineList::iterator aLine, 7317 nsFrameList& aFrameList, 7318 nsLineList& aLineList) { 7319 aFrameList.RemoveFrame(aChild); 7320 if (aChild == aLine->mFirstChild) { 7321 aLine->mFirstChild = aChild->GetNextSibling(); 7322 } 7323 aLine->NoteFrameRemoved(aChild); 7324 if (aLine->GetChildCount() > 0) { 7325 aLine->MarkDirty(); 7326 } else { 7327 // The line became empty - destroy it. 7328 nsLineBox* lineBox = aLine; 7329 aLine = aLineList.erase(aLine); 7330 if (aLine != aLineList.end()) { 7331 aLine->MarkPreviousMarginDirty(); 7332 } 7333 FreeLineBox(lineBox); 7334 ClearLineCursors(); 7335 } 7336 } 7337 7338 void nsBlockFrame::DeleteNextInFlowChild(DestroyContext& aContext, 7339 nsIFrame* aNextInFlow, 7340 bool aDeletingEmptyFrames) { 7341 MOZ_ASSERT(aNextInFlow->GetPrevInFlow(), "bad next-in-flow"); 7342 7343 if (aNextInFlow->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW | 7344 NS_FRAME_IS_OVERFLOW_CONTAINER)) { 7345 nsContainerFrame::DeleteNextInFlowChild(aContext, aNextInFlow, 7346 aDeletingEmptyFrames); 7347 } else { 7348 #ifdef DEBUG 7349 if (aDeletingEmptyFrames) { 7350 nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(aNextInFlow); 7351 } 7352 #endif 7353 DoRemoveFrame(aContext, aNextInFlow, 7354 aDeletingEmptyFrames ? FRAMES_ARE_EMPTY : 0); 7355 } 7356 } 7357 7358 const nsStyleText* nsBlockFrame::StyleTextForLineLayout() { 7359 // Return the pointer to an unmodified style text 7360 return StyleText(); 7361 } 7362 7363 void nsBlockFrame::ReflowFloat(BlockReflowState& aState, ReflowInput& aFloatRI, 7364 nsIFrame* aFloat, 7365 nsReflowStatus& aReflowStatus) { 7366 MOZ_ASSERT(aReflowStatus.IsEmpty(), 7367 "Caller should pass a fresh reflow status!"); 7368 MOZ_ASSERT(aFloat->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW), 7369 "aFloat must be an out-of-flow frame"); 7370 7371 WritingMode wm = aState.mReflowInput.GetWritingMode(); 7372 7373 // Setup a block reflow context to reflow the float. 7374 nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput); 7375 7376 nsIFrame* clearanceFrame = nullptr; 7377 do { 7378 CollapsingMargin margin; 7379 bool mayNeedRetry = false; 7380 aFloatRI.mDiscoveredClearance = nullptr; 7381 // Only first in flow gets a block-start margin. 7382 if (!aFloat->GetPrevInFlow()) { 7383 brc.ComputeCollapsedBStartMargin(aFloatRI, &margin, clearanceFrame, 7384 &mayNeedRetry); 7385 7386 if (mayNeedRetry && !clearanceFrame) { 7387 aFloatRI.mDiscoveredClearance = &clearanceFrame; 7388 // We don't need to push the float manager state because the the block 7389 // has its own float manager that will be destroyed and recreated 7390 } 7391 } 7392 7393 // When reflowing a float, aSpace argument doesn't matter because we pass 7394 // nullptr to aLine and we don't call nsBlockReflowContext::PlaceBlock() 7395 // later. 7396 brc.ReflowBlock(LogicalRect(wm), true, margin, 0, nullptr, aFloatRI, 7397 aReflowStatus, aState); 7398 } while (clearanceFrame); 7399 7400 if (aFloat->IsLetterFrame()) { 7401 // We never split floating first letters; an incomplete status for such 7402 // frames simply means that there is more content to be reflowed on the 7403 // line. 7404 if (aReflowStatus.IsIncomplete()) { 7405 aReflowStatus.Reset(); 7406 } 7407 } 7408 7409 NS_ASSERTION(aReflowStatus.IsFullyComplete() || 7410 aFloatRI.AvailableBSize() != NS_UNCONSTRAINEDSIZE, 7411 "The status can only be incomplete or overflow-incomplete if " 7412 "the available block-size is constrained!"); 7413 7414 if (aReflowStatus.NextInFlowNeedsReflow()) { 7415 aState.mReflowStatus.SetNextInFlowNeedsReflow(); 7416 } 7417 7418 const ReflowOutput& metrics = brc.GetMetrics(); 7419 7420 // Set the rect, make sure the view is properly sized and positioned, 7421 // and tell the frame we're done reflowing it 7422 // XXXldb This seems like the wrong place to be doing this -- shouldn't 7423 // we be doing this in BlockReflowState::FlowAndPlaceFloat after 7424 // we've positioned the float, and shouldn't we be doing the equivalent 7425 // of |PlaceFrameView| here? 7426 WritingMode metricsWM = metrics.GetWritingMode(); 7427 aFloat->SetSize(metricsWM, metrics.Size(metricsWM)); 7428 aFloat->DidReflow(aState.mPresContext, &aFloatRI); 7429 } 7430 7431 UsedClear nsBlockFrame::FindTrailingClear() { 7432 for (nsBlockFrame* b = this; b; 7433 b = static_cast<nsBlockFrame*>(b->GetPrevInFlow())) { 7434 auto endLine = b->LinesRBegin(); 7435 if (endLine != b->LinesREnd()) { 7436 return endLine->FloatClearTypeAfter(); 7437 } 7438 } 7439 return UsedClear::None; 7440 } 7441 7442 void nsBlockFrame::ReflowPushedFloats(BlockReflowState& aState, 7443 OverflowAreas& aOverflowAreas) { 7444 // Pushed floats live at the start of our float list; see comment 7445 // above nsBlockFrame::DrainPushedFloats. 7446 nsFrameList* floats = GetFloats(); 7447 nsIFrame* f = floats ? floats->FirstChild() : nullptr; 7448 nsIFrame* prev = nullptr; 7449 while (f && f->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW)) { 7450 MOZ_ASSERT(prev == f->GetPrevSibling()); 7451 // When we push a first-continuation float in a non-initial reflow, 7452 // it's possible that we end up with two continuations with the same 7453 // parent. This happens if, on the previous reflow of the block or 7454 // a previous reflow of the line containing the block, the float was 7455 // split between continuations A and B of the parent, but on the 7456 // current reflow, none of the float can fit in A. 7457 // 7458 // When this happens, we might even have the two continuations 7459 // out-of-order due to the management of the pushed floats. In 7460 // particular, if the float's placeholder was in a pushed line that 7461 // we reflowed before it was pushed, and we split the float during 7462 // that reflow, we might have the continuation of the float before 7463 // the float itself. (In the general case, however, it's correct 7464 // for floats in the pushed floats list to come before floats 7465 // anchored in pushed lines; however, in this case it's wrong. We 7466 // should probably find a way to fix it somehow, since it leads to 7467 // incorrect layout in some cases.) 7468 // 7469 // When we have these out-of-order continuations, we might hit the 7470 // next-continuation before the previous-continuation. When that 7471 // happens, just push it. When we reflow the next continuation, 7472 // we'll either pull all of its content back and destroy it (by 7473 // calling DeleteNextInFlowChild), or nsBlockFrame::SplitFloat will 7474 // pull it out of its current position and push it again (and 7475 // potentially repeat this cycle for the next continuation, although 7476 // hopefully then they'll be in the right order). 7477 // 7478 // We should also need this code for the in-order case if the first 7479 // continuation of a float gets moved across more than one 7480 // continuation of the containing block. In this case we'd manage 7481 // to push the second continuation without this check, but not the 7482 // third and later. 7483 nsIFrame* prevContinuation = f->GetPrevContinuation(); 7484 if (prevContinuation && prevContinuation->GetParent() == f->GetParent()) { 7485 floats->RemoveFrame(f); 7486 if (floats->IsEmpty()) { 7487 StealFloats()->Delete(PresShell()); 7488 floats = nullptr; 7489 } 7490 aState.AppendPushedFloatChain(f); 7491 if (!floats) { 7492 // The floats list becomes empty after removing |f|. Bail out. 7493 f = prev = nullptr; 7494 break; 7495 } 7496 // Even if we think |floats| is valid, AppendPushedFloatChain() can also 7497 // push |f|'s next-in-flows in our floats list to our pushed floats list. 7498 // If all the floats in the floats list are pushed, the floats list will 7499 // be deleted, and |floats| will be stale and poisoned. Therefore, we need 7500 // to get the floats list again to check its validity. 7501 floats = GetFloats(); 7502 if (!floats) { 7503 f = prev = nullptr; 7504 break; 7505 } 7506 f = !prev ? floats->FirstChild() : prev->GetNextSibling(); 7507 continue; 7508 } 7509 7510 // Always call FlowAndPlaceFloat; we might need to place this float if it 7511 // didn't belong to this block the last time it was reflowed. Note that if 7512 // the float doesn't get placed, we don't consider its overflow areas. 7513 // (Not-getting-placed means it didn't fit and we pushed it instead of 7514 // placing it, and its position could be stale.) 7515 if (aState.FlowAndPlaceFloat(f) == 7516 BlockReflowState::PlaceFloatResult::Placed) { 7517 ConsiderChildOverflow(aOverflowAreas, f); 7518 } 7519 7520 // If f is the only child in the floats list, pushing it to the pushed 7521 // floats list in FlowAndPlaceFloat() can result in the floats list being 7522 // deleted. Get the floats list again. 7523 floats = GetFloats(); 7524 if (!floats) { 7525 f = prev = nullptr; 7526 break; 7527 } 7528 7529 nsIFrame* next = !prev ? floats->FirstChild() : prev->GetNextSibling(); 7530 if (next == f) { 7531 // We didn't push |f| so its next-sibling is next. 7532 next = f->GetNextSibling(); 7533 prev = f; 7534 } // else: we did push |f| so |prev|'s new next-sibling is next. 7535 f = next; 7536 } 7537 7538 // If there are pushed or split floats, then we may need to continue BR 7539 // clearance 7540 if (auto [bCoord, result] = aState.ClearFloats(0, UsedClear::Both); 7541 result != ClearFloatsResult::BCoordNoChange) { 7542 (void)bCoord; 7543 if (auto* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow())) { 7544 aState.mTrailingClearFromPIF = prevBlock->FindTrailingClear(); 7545 } 7546 } 7547 } 7548 7549 void nsBlockFrame::RecoverFloats(nsFloatManager& aFloatManager, WritingMode aWM, 7550 const nsSize& aContainerSize) { 7551 // Recover our own floats 7552 nsIFrame* stop = nullptr; // Stop before we reach pushed floats that 7553 // belong to our next-in-flow 7554 const nsFrameList* floats = GetFloats(); 7555 for (nsIFrame* f = floats ? floats->FirstChild() : nullptr; f && f != stop; 7556 f = f->GetNextSibling()) { 7557 LogicalRect region = nsFloatManager::GetRegionFor(aWM, f, aContainerSize); 7558 aFloatManager.AddFloat(f, region, aWM, aContainerSize); 7559 if (!stop && f->GetNextInFlow()) { 7560 stop = f->GetNextInFlow(); 7561 } 7562 } 7563 7564 // Recurse into our overflow container children 7565 for (nsIFrame* oc = 7566 GetChildList(FrameChildListID::OverflowContainers).FirstChild(); 7567 oc; oc = oc->GetNextSibling()) { 7568 RecoverFloatsFor(oc, aFloatManager, aWM, aContainerSize); 7569 } 7570 7571 // Recurse into our normal children 7572 for (const auto& line : Lines()) { 7573 if (line.IsBlock()) { 7574 RecoverFloatsFor(line.mFirstChild, aFloatManager, aWM, aContainerSize); 7575 } 7576 } 7577 } 7578 7579 void nsBlockFrame::RecoverFloatsFor(nsIFrame* aFrame, 7580 nsFloatManager& aFloatManager, 7581 WritingMode aWM, 7582 const nsSize& aContainerSize) { 7583 MOZ_ASSERT(aFrame, "null frame"); 7584 7585 // Only blocks have floats 7586 nsBlockFrame* block = do_QueryFrame(aFrame); 7587 // Don't recover any state inside a block that has its own float manager 7588 // (we don't currently have any blocks like this, though, thanks to our 7589 // use of extra frames for 'overflow') 7590 if (block && !nsBlockFrame::BlockNeedsFloatManager(block)) { 7591 // If the element is relatively positioned, then adjust x and y 7592 // accordingly so that we consider relatively positioned frames 7593 // at their original position. 7594 7595 const LogicalRect rect = block->GetLogicalNormalRect(aWM, aContainerSize); 7596 nscoord lineLeft = rect.LineLeft(aWM, aContainerSize); 7597 nscoord blockStart = rect.BStart(aWM); 7598 aFloatManager.Translate(lineLeft, blockStart); 7599 block->RecoverFloats(aFloatManager, aWM, aContainerSize); 7600 aFloatManager.Translate(-lineLeft, -blockStart); 7601 } 7602 } 7603 7604 bool nsBlockFrame::HasPushedFloatsFromPrevContinuation() const { 7605 if (const nsFrameList* floats = GetFloats()) { 7606 // If we have pushed floats, then they should be at the beginning of our 7607 // float list. 7608 if (floats->FirstChild()->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW)) { 7609 return true; 7610 } 7611 #ifdef DEBUG 7612 // Double-check the above assertion that pushed floats should be at the 7613 // beginning of our floats list. 7614 for (nsIFrame* f : *floats) { 7615 NS_ASSERTION(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW), 7616 "pushed floats must be at the beginning of the float list"); 7617 } 7618 #endif 7619 } 7620 7621 // We may have a pending push of pushed floats, too. 7622 return HasPushedFloats(); 7623 } 7624 7625 ////////////////////////////////////////////////////////////////////// 7626 // Painting, event handling 7627 7628 #ifdef DEBUG 7629 static void ComputeInkOverflowArea(nsLineList& aLines, nscoord aWidth, 7630 nscoord aHeight, nsRect& aResult) { 7631 nscoord xa = 0, ya = 0, xb = aWidth, yb = aHeight; 7632 for (nsLineList::iterator line = aLines.begin(), line_end = aLines.end(); 7633 line != line_end; ++line) { 7634 // Compute min and max x/y values for the reflowed frame's 7635 // combined areas 7636 nsRect inkOverflow(line->InkOverflowRect()); 7637 nscoord x = inkOverflow.x; 7638 nscoord y = inkOverflow.y; 7639 nscoord xmost = x + inkOverflow.width; 7640 nscoord ymost = y + inkOverflow.height; 7641 if (x < xa) { 7642 xa = x; 7643 } 7644 if (xmost > xb) { 7645 xb = xmost; 7646 } 7647 if (y < ya) { 7648 ya = y; 7649 } 7650 if (ymost > yb) { 7651 yb = ymost; 7652 } 7653 } 7654 7655 aResult.x = xa; 7656 aResult.y = ya; 7657 aResult.width = xb - xa; 7658 aResult.height = yb - ya; 7659 } 7660 #endif 7661 7662 #ifdef DEBUG 7663 static void DebugOutputDrawLine(int32_t aDepth, nsLineBox* aLine, bool aDrawn) { 7664 if (nsBlockFrame::gNoisyDamageRepair) { 7665 nsIFrame::IndentBy(stdout, aDepth + 1); 7666 nsRect lineArea = aLine->InkOverflowRect(); 7667 printf("%s line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n", 7668 aDrawn ? "draw" : "skip", static_cast<void*>(aLine), aLine->IStart(), 7669 aLine->BStart(), aLine->ISize(), aLine->BSize(), lineArea.x, 7670 lineArea.y, lineArea.width, lineArea.height); 7671 } 7672 } 7673 #endif 7674 7675 static void DisplayLine(nsDisplayListBuilder* aBuilder, 7676 nsBlockFrame::LineIterator& aLine, 7677 const bool aLineInLine, const nsDisplayListSet& aLists, 7678 nsBlockFrame* aFrame, TextOverflow* aTextOverflow, 7679 uint32_t aLineNumberForTextOverflow, int32_t aDepth, 7680 int32_t& aDrawnLines, bool& aFoundLineClamp) { 7681 #ifdef DEBUG 7682 if (nsBlockFrame::gLamePaintMetrics) { 7683 aDrawnLines++; 7684 } 7685 const bool intersect = 7686 aLine->InkOverflowRect().Intersects(aBuilder->GetDirtyRect()); 7687 DebugOutputDrawLine(aDepth, aLine.get(), intersect); 7688 #endif 7689 7690 // Collect our line's display items in a temporary nsDisplayListCollection, 7691 // so that we can apply any "text-overflow" clipping to the entire collection 7692 // without affecting previous lines. 7693 nsDisplayListCollection collection(aBuilder); 7694 7695 // Block-level child backgrounds go on the blockBorderBackgrounds list ... 7696 // Inline-level child backgrounds go on the regular child content list. 7697 nsDisplayListSet childLists( 7698 collection, 7699 aLineInLine ? collection.Content() : collection.BlockBorderBackgrounds()); 7700 7701 auto flags = 7702 aLineInLine 7703 ? nsIFrame::DisplayChildFlags(nsIFrame::DisplayChildFlag::Inline) 7704 : nsIFrame::DisplayChildFlags(); 7705 7706 nsIFrame* kid = aLine->mFirstChild; 7707 int32_t n = aLine->GetChildCount(); 7708 while (--n >= 0) { 7709 aFrame->BuildDisplayListForChild(aBuilder, kid, childLists, flags); 7710 kid = kid->GetNextSibling(); 7711 } 7712 7713 if (aFrame->HasLineClampEllipsisDescendant() && !aLineInLine) { 7714 if (nsBlockFrame* f = GetAsLineClampDescendant(aLine->mFirstChild)) { 7715 if (f->HasLineClampEllipsis() || f->HasLineClampEllipsisDescendant()) { 7716 aFoundLineClamp = true; 7717 } 7718 } 7719 } 7720 7721 if (aTextOverflow && aLineInLine) { 7722 aTextOverflow->ProcessLine(collection, aLine.get(), 7723 aLineNumberForTextOverflow); 7724 } 7725 7726 collection.MoveTo(aLists); 7727 } 7728 7729 void nsBlockFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 7730 const nsDisplayListSet& aLists) { 7731 int32_t drawnLines; // Will only be used if set (gLamePaintMetrics). 7732 int32_t depth = 0; 7733 #ifdef DEBUG 7734 if (gNoisyDamageRepair) { 7735 nsRect dirty = aBuilder->GetDirtyRect(); 7736 depth = GetDepth(); 7737 nsRect ca; 7738 ::ComputeInkOverflowArea(mLines, mRect.width, mRect.height, ca); 7739 nsIFrame::IndentBy(stdout, depth); 7740 ListTag(stdout); 7741 printf(": bounds=%d,%d,%d,%d dirty(absolute)=%d,%d,%d,%d ca=%d,%d,%d,%d\n", 7742 mRect.x, mRect.y, mRect.width, mRect.height, dirty.x, dirty.y, 7743 dirty.width, dirty.height, ca.x, ca.y, ca.width, ca.height); 7744 } 7745 PRTime start = 0; // Initialize these variables to silence the compiler. 7746 if (gLamePaintMetrics) { 7747 start = PR_Now(); 7748 drawnLines = 0; 7749 } 7750 #endif 7751 7752 // TODO(heycam): Should we boost the load priority of any shape-outside 7753 // images using CATEGORY_DISPLAY, now that this block is being displayed? 7754 // We don't have a float manager here. 7755 7756 DisplayBorderBackgroundOutline(aBuilder, aLists); 7757 7758 if (HidesContent()) { 7759 return; 7760 } 7761 7762 if (GetPrevInFlow()) { 7763 DisplayOverflowContainers(aBuilder, aLists); 7764 DisplayPushedAbsoluteFrames(aBuilder, aLists); 7765 for (nsIFrame* f : GetChildList(FrameChildListID::Float)) { 7766 if (f->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW)) { 7767 BuildDisplayListForChild(aBuilder, f, aLists); 7768 } 7769 } 7770 } 7771 7772 aBuilder->MarkFramesForDisplayList(this, 7773 GetChildList(FrameChildListID::Float)); 7774 7775 if (nsIFrame* outsideMarker = GetOutsideMarker()) { 7776 // Display outside ::marker manually. 7777 BuildDisplayListForChild(aBuilder, outsideMarker, aLists); 7778 } 7779 7780 // Prepare for text-overflow processing. 7781 Maybe<TextOverflow> textOverflow = 7782 TextOverflow::WillProcessLines(aBuilder, this); 7783 7784 const bool hasDescendantPlaceHolders = 7785 HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) || 7786 ForceDescendIntoIfVisible() || aBuilder->GetIncludeAllOutOfFlows(); 7787 7788 const auto ShouldDescendIntoLine = [&](const nsRect& aLineArea) -> bool { 7789 // TODO(miko): Unfortunately |descendAlways| cannot be cached, because with 7790 // some frame trees, building display list for child lines can change it. 7791 // See bug 1552789. 7792 const bool descendAlways = 7793 HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) || 7794 aBuilder->GetIncludeAllOutOfFlows(); 7795 7796 return descendAlways || aLineArea.Intersects(aBuilder->GetDirtyRect()) || 7797 (ForceDescendIntoIfVisible() && 7798 aLineArea.Intersects(aBuilder->GetVisibleRect())); 7799 }; 7800 7801 Maybe<nscolor> backplateColor; 7802 7803 // We'll try to draw an accessibility backplate behind text (to ensure it's 7804 // readable over any possible background-images), if all of the following 7805 // hold: 7806 // (A) we are not honoring the document colors 7807 // (B) the backplate feature is preffed on 7808 // (C) the force color adjust property is set to auto 7809 if (PresContext()->ForcingColors() && 7810 StaticPrefs::browser_display_permit_backplate() && 7811 StyleText()->mForcedColorAdjust != StyleForcedColorAdjust::None) { 7812 backplateColor.emplace(GetBackplateColor(this)); 7813 } 7814 7815 const bool canUseCursor = [&] { 7816 if (hasDescendantPlaceHolders) { 7817 // Don't use the line cursor if we might have a descendant placeholder. It 7818 // might skip lines that contain placeholders but don't themselves 7819 // intersect with the dirty area. 7820 // 7821 // In particular, we really want to check ShouldDescendIntoFrame() 7822 // on all our child frames, but that might be expensive. So we 7823 // approximate it by checking it on |this|; if it's true for any 7824 // frame in our child list, it's also true for |this|. 7825 return false; 7826 } 7827 if (textOverflow.isSome()) { 7828 // Also skip the cursor if we're creating text overflow markers, since we 7829 // need to know what line number we're up to in order to generate unique 7830 // display item keys. 7831 return false; 7832 } 7833 if (backplateColor) { 7834 // Cursors should be skipped if we're drawing backplates behind text. When 7835 // backplating we consider consecutive runs of text as a whole, which 7836 // requires we iterate through all lines to find our backplate size. 7837 return false; 7838 } 7839 if ((HasLineClampEllipsis() || HasLineClampEllipsisDescendant()) && 7840 StaticPrefs::layout_css_webkit_line_clamp_skip_paint()) { 7841 // We can't use the cursor if we're in a line-clamping situation, and 7842 // we're configured to not paint its clamped content, as we need to know 7843 // whether we've hit the clamp point which requires iterating over all 7844 // lines. 7845 return false; 7846 } 7847 return true; 7848 }(); 7849 7850 nsLineBox* cursor = canUseCursor 7851 ? GetFirstLineContaining(aBuilder->GetDirtyRect().y) 7852 : nullptr; 7853 LineIterator line_end = LinesEnd(); 7854 7855 TextOverflow* textOverflowPtr = textOverflow.ptrOr(nullptr); 7856 bool foundClamp = false; 7857 7858 if (cursor) { 7859 for (LineIterator line = mLines.begin(cursor); line != line_end; ++line) { 7860 const nsRect lineArea = line->InkOverflowRect(); 7861 if (!lineArea.IsEmpty()) { 7862 // Because we have a cursor, the combinedArea.ys are non-decreasing. 7863 // Once we've passed aDirtyRect.YMost(), we can never see it again. 7864 if (lineArea.y >= aBuilder->GetDirtyRect().YMost()) { 7865 break; 7866 } 7867 MOZ_ASSERT(textOverflow.isNothing()); 7868 7869 if (ShouldDescendIntoLine(lineArea)) { 7870 DisplayLine(aBuilder, line, line->IsInline(), aLists, this, nullptr, 7871 0, depth, drawnLines, foundClamp); 7872 MOZ_ASSERT(!foundClamp || 7873 !StaticPrefs::layout_css_webkit_line_clamp_skip_paint()); 7874 } 7875 } 7876 } 7877 } else { 7878 bool nonDecreasingYs = true; 7879 uint32_t lineCount = 0; 7880 nscoord lastY = INT32_MIN; 7881 nscoord lastYMost = INT32_MIN; 7882 7883 // A frame's display list cannot contain more than one copy of a 7884 // given display item unless the items are uniquely identifiable. 7885 // Because backplate occasionally requires multiple 7886 // SolidColor items, we use an index (backplateIndex) to maintain 7887 // uniqueness among them. Note this is a mapping of index to 7888 // item, and the mapping is stable even if the dirty rect changes. 7889 uint16_t backplateIndex = 0; 7890 nsRect curBackplateArea; 7891 7892 auto AddBackplate = [&]() { 7893 aLists.BorderBackground()->AppendNewToTopWithIndex<nsDisplaySolidColor>( 7894 aBuilder, this, backplateIndex, curBackplateArea, 7895 backplateColor.value()); 7896 }; 7897 7898 for (LineIterator line = LinesBegin(); line != line_end; ++line) { 7899 const nsRect lineArea = line->InkOverflowRect(); 7900 const bool lineInLine = line->IsInline(); 7901 7902 if ((lineInLine && textOverflowPtr) || ShouldDescendIntoLine(lineArea)) { 7903 DisplayLine(aBuilder, line, lineInLine, aLists, this, textOverflowPtr, 7904 lineCount, depth, drawnLines, foundClamp); 7905 } 7906 7907 if (!lineInLine && !curBackplateArea.IsEmpty()) { 7908 // If we have encountered a non-inline line but were previously 7909 // forming a backplate, we should add the backplate to the display 7910 // list as-is and render future backplates disjointly. 7911 MOZ_ASSERT(backplateColor, 7912 "if this master switch is off, curBackplateArea " 7913 "must be empty and we shouldn't get here"); 7914 AddBackplate(); 7915 backplateIndex++; 7916 curBackplateArea = nsRect(); 7917 } 7918 7919 if (!lineArea.IsEmpty()) { 7920 if (lineArea.y < lastY || lineArea.YMost() < lastYMost) { 7921 nonDecreasingYs = false; 7922 } 7923 lastY = lineArea.y; 7924 lastYMost = lineArea.YMost(); 7925 if (lineInLine && backplateColor && LineHasVisibleInlineText(line)) { 7926 nsRect lineBackplate = GetLineTextArea(line, aBuilder) + 7927 aBuilder->ToReferenceFrame(this); 7928 if (curBackplateArea.IsEmpty()) { 7929 curBackplateArea = lineBackplate; 7930 } else { 7931 curBackplateArea.OrWith(lineBackplate); 7932 } 7933 } 7934 } 7935 foundClamp = foundClamp || line->HasLineClampEllipsis(); 7936 if (foundClamp && 7937 StaticPrefs::layout_css_webkit_line_clamp_skip_paint()) { 7938 break; 7939 } 7940 lineCount++; 7941 } 7942 7943 if (nonDecreasingYs && lineCount >= MIN_LINES_NEEDING_CURSOR) { 7944 SetupLineCursorForDisplay(); 7945 } 7946 7947 if (!curBackplateArea.IsEmpty()) { 7948 AddBackplate(); 7949 } 7950 } 7951 7952 if (textOverflow.isSome()) { 7953 // Put any text-overflow:ellipsis markers on top of the non-positioned 7954 // content of the block's lines. (If we ever start sorting the Content() 7955 // list this will end up in the wrong place.) 7956 aLists.Content()->AppendToTop(&textOverflow->GetMarkers()); 7957 } 7958 7959 #ifdef DEBUG 7960 if (gLamePaintMetrics) { 7961 PRTime end = PR_Now(); 7962 7963 int32_t numLines = mLines.size(); 7964 if (!numLines) { 7965 numLines = 1; 7966 } 7967 PRTime lines, deltaPerLine, delta; 7968 lines = int64_t(numLines); 7969 delta = end - start; 7970 deltaPerLine = delta / lines; 7971 7972 ListTag(stdout); 7973 char buf[400]; 7974 SprintfLiteral(buf, 7975 ": %" PRId64 " elapsed (%" PRId64 7976 " per line) lines=%d drawn=%d skip=%d", 7977 delta, deltaPerLine, numLines, drawnLines, 7978 numLines - drawnLines); 7979 printf("%s\n", buf); 7980 } 7981 #endif 7982 } 7983 7984 #ifdef ACCESSIBILITY 7985 a11y::AccType nsBlockFrame::AccessibleType() { 7986 if (IsTableCaption()) { 7987 return GetRect().IsEmpty() ? a11y::eNoType : a11y::eHTMLCaptionType; 7988 } 7989 7990 // block frame may be for <hr> 7991 if (mContent->IsHTMLElement(nsGkAtoms::hr)) { 7992 return a11y::eHTMLHRType; 7993 } 7994 7995 if (IsButtonLike()) { 7996 return a11y::eHTMLButtonType; 7997 } 7998 7999 if (HasMarker()) { 8000 // Create special list item accessible since we have a ::marker. 8001 return a11y::eHTMLLiType; 8002 } 8003 8004 // XXXsmaug What if we're in the shadow dom? 8005 if (!mContent->GetParent()) { 8006 // Don't create accessible objects for the root content node, they are 8007 // redundant with the nsDocAccessible object created with the document 8008 // node 8009 return a11y::eNoType; 8010 } 8011 8012 if (mContent == mContent->OwnerDoc()->GetBody()) { 8013 // Don't create accessible objects for the body, they are redundant with 8014 // the nsDocAccessible object created with the document node 8015 return a11y::eNoType; 8016 } 8017 8018 // Not a list item with a ::marker, treat as normal HTML container. 8019 return a11y::eHyperTextType; 8020 } 8021 #endif 8022 8023 void nsBlockFrame::SetupLineCursorForDisplay() { 8024 if (mLines.empty() || HasProperty(LineCursorPropertyDisplay())) { 8025 return; 8026 } 8027 8028 SetProperty(LineCursorPropertyDisplay(), mLines.front()); 8029 AddStateBits(NS_BLOCK_HAS_LINE_CURSOR); 8030 } 8031 8032 void nsBlockFrame::SetupLineCursorForQuery() { 8033 if (mLines.empty() || HasProperty(LineCursorPropertyQuery())) { 8034 return; 8035 } 8036 8037 SetProperty(LineCursorPropertyQuery(), mLines.front()); 8038 AddStateBits(NS_BLOCK_HAS_LINE_CURSOR); 8039 } 8040 8041 nsLineBox* nsBlockFrame::GetFirstLineContaining(nscoord y) { 8042 // Although this looks like a "querying" method, it is used by the 8043 // display-list building code, so uses the Display cursor. 8044 nsLineBox* property = GetLineCursorForDisplay(); 8045 if (!property) { 8046 return nullptr; 8047 } 8048 LineIterator cursor = mLines.begin(property); 8049 nsRect cursorArea = cursor->InkOverflowRect(); 8050 8051 while ((cursorArea.IsEmpty() || cursorArea.YMost() > y) && 8052 cursor != mLines.front()) { 8053 cursor = cursor.prev(); 8054 cursorArea = cursor->InkOverflowRect(); 8055 } 8056 while ((cursorArea.IsEmpty() || cursorArea.YMost() <= y) && 8057 cursor != mLines.back()) { 8058 cursor = cursor.next(); 8059 cursorArea = cursor->InkOverflowRect(); 8060 } 8061 8062 if (cursor.get() != property) { 8063 SetProperty(LineCursorPropertyDisplay(), cursor.get()); 8064 } 8065 8066 return cursor.get(); 8067 } 8068 8069 /* virtual */ 8070 void nsBlockFrame::ChildIsDirty(nsIFrame* aChild) { 8071 // See if the child is absolutely positioned 8072 if (aChild->IsAbsolutelyPositioned()) { 8073 // do nothing 8074 } else if (aChild == GetOutsideMarker()) { 8075 // The ::marker lives in the first line, unless the first line has 8076 // height 0 and there is a second line, in which case it lives 8077 // in the second line. 8078 LineIterator markerLine = LinesBegin(); 8079 if (markerLine != LinesEnd() && markerLine->BSize() == 0 && 8080 markerLine != mLines.back()) { 8081 markerLine = markerLine.next(); 8082 } 8083 8084 if (markerLine != LinesEnd()) { 8085 MarkLineDirty(markerLine, &mLines); 8086 } 8087 // otherwise we have an empty line list, and ReflowDirtyLines 8088 // will handle reflowing the ::marker. 8089 } else { 8090 // Note that we should go through our children to mark lines dirty 8091 // before the next reflow. Doing it now could make things O(N^2) 8092 // since finding the right line is O(N). 8093 // We don't need to worry about marking lines on the overflow list 8094 // as dirty; we're guaranteed to reflow them if we take them off the 8095 // overflow list. 8096 // However, we might have gotten a float, in which case we need to 8097 // reflow the line containing its placeholder. So find the 8098 // ancestor-or-self of the placeholder that's a child of the block, 8099 // and mark it as NS_FRAME_HAS_DIRTY_CHILDREN too, so that we mark 8100 // its line dirty when we handle NS_BLOCK_LOOK_FOR_DIRTY_FRAMES. 8101 // We need to take some care to handle the case where a float is in 8102 // a different continuation than its placeholder, including marking 8103 // an extra block with NS_BLOCK_LOOK_FOR_DIRTY_FRAMES. 8104 if (!aChild->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { 8105 AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES); 8106 } else { 8107 NS_ASSERTION(aChild->IsFloating(), "should be a float"); 8108 nsIFrame* thisFC = FirstContinuation(); 8109 nsIFrame* placeholderPath = aChild->GetPlaceholderFrame(); 8110 // SVG code sometimes sends FrameNeedsReflow notifications during 8111 // frame destruction, leading to null placeholders, but we're safe 8112 // ignoring those. 8113 if (placeholderPath) { 8114 for (;;) { 8115 nsIFrame* parent = placeholderPath->GetParent(); 8116 if (parent->GetContent() == mContent && 8117 parent->FirstContinuation() == thisFC) { 8118 parent->AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES); 8119 break; 8120 } 8121 placeholderPath = parent; 8122 } 8123 placeholderPath->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); 8124 } 8125 } 8126 } 8127 8128 nsContainerFrame::ChildIsDirty(aChild); 8129 } 8130 8131 void nsBlockFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, 8132 nsIFrame* aPrevInFlow) { 8133 // These are all the block specific frame bits, they are copied from 8134 // the prev-in-flow to a newly created next-in-flow, except for the 8135 // NS_BLOCK_FLAGS_NON_INHERITED_MASK bits below. 8136 constexpr nsFrameState NS_BLOCK_FLAGS_MASK = 8137 NS_BLOCK_BFC | NS_BLOCK_HAS_FIRST_LETTER_STYLE | 8138 NS_BLOCK_HAS_FIRST_LETTER_CHILD | NS_BLOCK_HAS_MARKER; 8139 8140 // This is the subset of NS_BLOCK_FLAGS_MASK that is NOT inherited 8141 // by default. They should only be set on the first-in-flow. 8142 constexpr nsFrameState NS_BLOCK_FLAGS_NON_INHERITED_MASK = 8143 NS_BLOCK_HAS_FIRST_LETTER_CHILD | NS_BLOCK_HAS_MARKER; 8144 8145 if (aPrevInFlow) { 8146 // Copy over the inherited block frame bits from the prev-in-flow. 8147 RemoveStateBits(NS_BLOCK_FLAGS_MASK); 8148 AddStateBits(aPrevInFlow->GetStateBits() & 8149 (NS_BLOCK_FLAGS_MASK & ~NS_BLOCK_FLAGS_NON_INHERITED_MASK)); 8150 } 8151 8152 nsContainerFrame::Init(aContent, aParent, aPrevInFlow); 8153 8154 if (!aPrevInFlow || 8155 aPrevInFlow->HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION)) { 8156 AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION); 8157 } 8158 8159 if (EstablishesBFC(this)) { 8160 AddStateBits(NS_BLOCK_BFC); 8161 } 8162 8163 if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER) && 8164 HasAnyStateBits(NS_BLOCK_BFC)) { 8165 AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT); 8166 } 8167 } 8168 8169 void nsBlockFrame::SetInitialChildList(ChildListID aListID, 8170 nsFrameList&& aChildList) { 8171 if (FrameChildListID::Float == aListID) { 8172 nsFrameList* floats = EnsureFloats(); 8173 *floats = std::move(aChildList); 8174 } else if (FrameChildListID::Principal == aListID) { 8175 #ifdef DEBUG 8176 // The only times a block that is an anonymous box is allowed to have a 8177 // first-letter frame are when it's the block inside a non-anonymous cell, 8178 // the block inside a fieldset, button or column set, or a scrolled content 8179 // block, except for <select>. Note that this means that blocks which are 8180 // the anonymous block in {ib} splits do NOT get first-letter frames. 8181 // Note that NS_BLOCK_HAS_FIRST_LETTER_STYLE gets set on all continuations 8182 // of the block. 8183 auto pseudo = Style()->GetPseudoType(); 8184 bool haveFirstLetterStyle = 8185 (pseudo == PseudoStyleType::NotPseudo || 8186 PseudoStyle::IsElementBackedPseudo(pseudo) || 8187 (pseudo == PseudoStyleType::cellContent && 8188 !GetParent()->Style()->IsPseudoOrAnonBox()) || 8189 pseudo == PseudoStyleType::fieldsetContent || 8190 pseudo == PseudoStyleType::columnContent || 8191 (pseudo == PseudoStyleType::scrolledContent && 8192 !GetParent()->IsListControlFrame()) || 8193 pseudo == PseudoStyleType::mozSVGText) && 8194 !IsMathMLFrame() && !IsColumnSetWrapperFrame() && 8195 !IsComboboxControlFrame() && 8196 RefPtr<ComputedStyle>(GetFirstLetterStyle(PresContext())) != nullptr; 8197 NS_ASSERTION(haveFirstLetterStyle == 8198 HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE), 8199 "NS_BLOCK_HAS_FIRST_LETTER_STYLE state out of sync"); 8200 #endif 8201 8202 AddFrames(std::move(aChildList), nullptr, nullptr); 8203 } else { 8204 nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList)); 8205 } 8206 } 8207 8208 void nsBlockFrame::SetMarkerFrameForListItem(nsIFrame* aMarkerFrame) { 8209 MOZ_ASSERT(aMarkerFrame); 8210 MOZ_ASSERT(!HasMarker(), "How can we have a ::marker frame already?"); 8211 8212 if (StyleList()->mListStylePosition == StyleListStylePosition::Inside) { 8213 SetProperty(InsideMarkerProperty(), aMarkerFrame); 8214 } else { 8215 SetProperty(OutsideMarkerProperty(), 8216 new (PresShell()) nsFrameList(aMarkerFrame, aMarkerFrame)); 8217 } 8218 AddStateBits(NS_BLOCK_HAS_MARKER); 8219 } 8220 8221 bool nsBlockFrame::MarkerIsEmpty(const nsIFrame* aMarker) const { 8222 MOZ_ASSERT(mContent->GetPrimaryFrame()->StyleDisplay()->IsListItem() && 8223 aMarker == GetOutsideMarker(), 8224 "should only care about an outside ::marker"); 8225 const nsStyleList* list = aMarker->StyleList(); 8226 return aMarker->StyleContent()->mContent.IsNone() || 8227 (list->mListStyleType.IsNone() && list->mListStyleImage.IsNone() && 8228 aMarker->StyleContent()->NonAltContentItems().IsEmpty()); 8229 } 8230 8231 bool nsBlockFrame::HasOutsideMarker() const { 8232 return HasMarker() && HasProperty(OutsideMarkerProperty()); 8233 } 8234 8235 void nsBlockFrame::ReflowOutsideMarker(nsIFrame* aMarkerFrame, 8236 BlockReflowState& aState, 8237 ReflowOutput& aMetrics, 8238 nscoord aLineTop) { 8239 const ReflowInput& ri = aState.mReflowInput; 8240 8241 WritingMode markerWM = aMarkerFrame->GetWritingMode(); 8242 LogicalSize availSize(markerWM); 8243 // Make up an inline-size since it doesn't really matter (XXX). 8244 availSize.ISize(markerWM) = aState.ContentISize(); 8245 availSize.BSize(markerWM) = NS_UNCONSTRAINEDSIZE; 8246 8247 ReflowInput reflowInput(aState.mPresContext, ri, aMarkerFrame, availSize, 8248 Nothing(), {}, {}, {ComputeSizeFlag::ShrinkWrap}); 8249 nsReflowStatus status; 8250 aMarkerFrame->Reflow(aState.mPresContext, aMetrics, reflowInput, status); 8251 8252 // Get the float available space using our saved state from before we 8253 // started reflowing the block, so that we ignore any floats inside 8254 // the block. 8255 // FIXME: aLineTop isn't actually set correctly by some callers, since 8256 // they reposition the line. 8257 LogicalRect floatAvailSpace = 8258 aState 8259 .GetFloatAvailableSpaceWithState(ri.GetWritingMode(), aLineTop, 8260 ShapeType::ShapeOutside, 8261 &aState.mFloatManagerStateBefore) 8262 .mRect; 8263 // FIXME (bug 25888): need to check the entire region that the first 8264 // line overlaps, not just the top pixel. 8265 8266 // Place the ::marker now. We want to place the ::marker relative to the 8267 // border-box of the associated block (using the right/left margin of 8268 // the ::marker frame as separation). However, if a line box would be 8269 // displaced by floats that are *outside* the associated block, we 8270 // want to displace it by the same amount. That is, we act as though 8271 // the edge of the floats is the content-edge of the block, and place 8272 // the ::marker at a position offset from there by the block's padding, 8273 // the block's border, and the ::marker frame's margin. 8274 8275 // IStart from floatAvailSpace gives us the content/float start edge 8276 // in the current writing mode. Then we subtract out the start 8277 // border/padding and the ::marker's width and margin to offset the position. 8278 WritingMode wm = ri.GetWritingMode(); 8279 // Get the ::marker's margin, converted to our writing mode so that we can 8280 // combine it with other logical values here. 8281 LogicalMargin markerMargin = reflowInput.ComputedLogicalMargin(wm); 8282 nscoord iStart = floatAvailSpace.IStart(wm) - 8283 ri.ComputedLogicalBorderPadding(wm).IStart(wm) - 8284 markerMargin.IEnd(wm) - aMetrics.ISize(wm); 8285 8286 // Approximate the ::marker's position; vertical alignment will provide 8287 // the final vertical location. We pass our writing-mode here, because 8288 // it may be different from the ::marker frame's mode. 8289 nscoord bStart = floatAvailSpace.BStart(wm); 8290 aMarkerFrame->SetRect( 8291 wm, 8292 LogicalRect(wm, iStart, bStart, aMetrics.ISize(wm), aMetrics.BSize(wm)), 8293 aState.ContainerSize()); 8294 aMarkerFrame->DidReflow(aState.mPresContext, &aState.mReflowInput); 8295 } 8296 8297 // This is used to scan frames for any float placeholders, add their 8298 // floats to the list represented by aList, and remove the 8299 // floats from whatever list they might be in. We don't search descendants 8300 // that are float containing blocks. Floats that or not children of 'this' 8301 // are ignored (they are not added to aList). 8302 void nsBlockFrame::DoCollectFloats(nsIFrame* aFrame, nsFrameList& aList, 8303 bool aCollectSiblings) { 8304 while (aFrame) { 8305 // Don't descend into float containing blocks. 8306 if (!aFrame->IsFloatContainingBlock()) { 8307 nsIFrame* outOfFlowFrame = 8308 aFrame->IsPlaceholderFrame() 8309 ? nsLayoutUtils::GetFloatFromPlaceholder(aFrame) 8310 : nullptr; 8311 while (outOfFlowFrame && outOfFlowFrame->GetParent() == this) { 8312 RemoveFloat(outOfFlowFrame); 8313 // Remove the NS_FRAME_IS_PUSHED_OUT_OF_FLOW bit, in case 8314 // |outOfFlowFrame| came from the PushedFloats list. 8315 outOfFlowFrame->RemoveStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW); 8316 aList.AppendFrame(nullptr, outOfFlowFrame); 8317 outOfFlowFrame = outOfFlowFrame->GetNextInFlow(); 8318 // FIXME: By not pulling floats whose parent is one of our 8319 // later siblings, are we risking the pushed floats getting 8320 // out-of-order? 8321 // XXXmats nsInlineFrame's lazy reparenting depends on NOT doing that. 8322 } 8323 8324 DoCollectFloats(aFrame->PrincipalChildList().FirstChild(), aList, true); 8325 DoCollectFloats( 8326 aFrame->GetChildList(FrameChildListID::Overflow).FirstChild(), aList, 8327 true); 8328 } 8329 if (!aCollectSiblings) { 8330 break; 8331 } 8332 aFrame = aFrame->GetNextSibling(); 8333 } 8334 } 8335 8336 void nsBlockFrame::CheckFloats(BlockReflowState& aState) { 8337 #ifdef DEBUG 8338 // If any line is still dirty, that must mean we're going to reflow this 8339 // block again soon (e.g. because we bailed out after noticing that 8340 // clearance was imposed), so don't worry if the floats are out of sync. 8341 bool anyLineDirty = false; 8342 8343 // Check that the float list is what we would have built 8344 AutoTArray<nsIFrame*, 8> lineFloats; 8345 for (auto& line : Lines()) { 8346 if (line.HasFloats()) { 8347 lineFloats.AppendElements(line.Floats()); 8348 } 8349 if (line.IsDirty()) { 8350 anyLineDirty = true; 8351 } 8352 } 8353 8354 AutoTArray<nsIFrame*, 8> storedFloats; 8355 bool equal = true; 8356 bool hasHiddenFloats = false; 8357 uint32_t i = 0; 8358 for (nsIFrame* f : GetChildList(FrameChildListID::Float)) { 8359 if (f->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW)) { 8360 continue; 8361 } 8362 // There are chances that the float children won't be added to lines, 8363 // because in nsBlockFrame::ReflowLine, it skips reflow line if the first 8364 // child of the line is IsHiddenByContentVisibilityOfInFlowParentForLayout. 8365 // There are also chances that the floats in line are out of date, for 8366 // instance, lines could reflow if 8367 // PresShell::IsForcingLayoutForHiddenContent, and after forcingLayout is 8368 // off, the reflow of lines could be skipped, but the floats are still in 8369 // there. Here we can't know whether the floats hidden by c-v are included 8370 // in the lines or not. So we use hasHiddenFloats to skip the float length 8371 // checking. 8372 if (!hasHiddenFloats && 8373 f->IsHiddenByContentVisibilityOfInFlowParentForLayout()) { 8374 hasHiddenFloats = true; 8375 } 8376 storedFloats.AppendElement(f); 8377 if (i < lineFloats.Length() && lineFloats.ElementAt(i) != f) { 8378 equal = false; 8379 } 8380 ++i; 8381 } 8382 8383 if ((!equal || lineFloats.Length() != storedFloats.Length()) && 8384 !anyLineDirty && !hasHiddenFloats) { 8385 NS_ERROR( 8386 "nsBlockFrame::CheckFloats: Explicit float list is out of sync with " 8387 "float cache"); 8388 } 8389 #endif 8390 8391 const nsFrameList* oofs = GetOverflowOutOfFlows(); 8392 if (oofs && oofs->NotEmpty()) { 8393 // Floats that were pushed should be removed from our float 8394 // manager. Otherwise the float manager's YMost or XMost might 8395 // be larger than necessary, causing this block to get an 8396 // incorrect desired height (or width). Some of these floats 8397 // may not actually have been added to the float manager because 8398 // they weren't reflowed before being pushed; that's OK, 8399 // RemoveRegions will ignore them. It is safe to do this here 8400 // because we know from here on the float manager will only be 8401 // used for its XMost and YMost, not to place new floats and 8402 // lines. 8403 aState.FloatManager()->RemoveTrailingRegions(oofs->FirstChild()); 8404 } 8405 } 8406 8407 void nsBlockFrame::IsMarginRoot(bool* aBStartMarginRoot, 8408 bool* aBEndMarginRoot) { 8409 nsIFrame* parent = GetParent(); 8410 if (!HasAnyStateBits(NS_BLOCK_BFC)) { 8411 if (!parent || parent->IsFloatContainingBlock()) { 8412 *aBStartMarginRoot = false; 8413 *aBEndMarginRoot = false; 8414 return; 8415 } 8416 } 8417 8418 if (parent && parent->IsColumnSetFrame()) { 8419 // The first column is a start margin root and the last column is an end 8420 // margin root. (If the column-set is split by a column-span:all box then 8421 // the first and last column in each column-set fragment are margin roots.) 8422 *aBStartMarginRoot = GetPrevInFlow() == nullptr; 8423 *aBEndMarginRoot = GetNextInFlow() == nullptr; 8424 return; 8425 } 8426 8427 *aBStartMarginRoot = true; 8428 *aBEndMarginRoot = true; 8429 } 8430 8431 /* static */ 8432 bool nsBlockFrame::BlockNeedsFloatManager(nsIFrame* aBlock) { 8433 MOZ_ASSERT(aBlock, "Must have a frame"); 8434 NS_ASSERTION(aBlock->IsBlockFrameOrSubclass(), "aBlock must be a block"); 8435 8436 nsIFrame* parent = aBlock->GetParent(); 8437 return aBlock->HasAnyStateBits(NS_BLOCK_BFC) || 8438 (parent && !parent->IsFloatContainingBlock()); 8439 } 8440 8441 /* static */ 8442 bool nsBlockFrame::BlockCanIntersectFloats(nsIFrame* aFrame) { 8443 // NS_BLOCK_BFC is block specific bit, check first as an optimization, it's 8444 // okay because we also check that it is a block frame. 8445 return !aFrame->HasAnyStateBits(NS_BLOCK_BFC) && !aFrame->IsReplaced() && 8446 aFrame->IsBlockFrameOrSubclass(); 8447 } 8448 8449 // Note that this width can vary based on the vertical position. 8450 // However, the cases where it varies are the cases where the width fits 8451 // in the available space given, which means that variation shouldn't 8452 // matter. 8453 /* static */ 8454 nsBlockFrame::FloatAvoidingISizeToClear nsBlockFrame::ISizeToClearPastFloats( 8455 const BlockReflowState& aState, const LogicalRect& aFloatAvailableSpace, 8456 nsIFrame* aFloatAvoidingBlock) { 8457 nscoord inlineStartOffset, inlineEndOffset; 8458 WritingMode wm = aState.mReflowInput.GetWritingMode(); 8459 8460 FloatAvoidingISizeToClear result; 8461 aState.ComputeFloatAvoidingOffsets(aFloatAvoidingBlock, aFloatAvailableSpace, 8462 inlineStartOffset, inlineEndOffset); 8463 nscoord availISize = 8464 aState.mContentArea.ISize(wm) - inlineStartOffset - inlineEndOffset; 8465 8466 // We actually don't want the min width here; see bug 427782; we only 8467 // want to displace if the width won't compute to a value small enough 8468 // to fit. 8469 // All we really need here is the result of ComputeSize, and we 8470 // could *almost* get that from an SizeComputationInput, except for the 8471 // last argument. 8472 WritingMode frWM = aFloatAvoidingBlock->GetWritingMode(); 8473 LogicalSize availSpace = 8474 LogicalSize(wm, availISize, NS_UNCONSTRAINEDSIZE).ConvertTo(frWM, wm); 8475 ReflowInput reflowInput(aState.mPresContext, aState.mReflowInput, 8476 aFloatAvoidingBlock, availSpace); 8477 result.borderBoxISize = 8478 reflowInput.ComputedSizeWithBorderPadding(wm).ISize(wm); 8479 8480 // Use the margins from sizingInput rather than reflowInput so that 8481 // they aren't reduced by ignoring margins in overconstrained cases. 8482 SizeComputationInput sizingInput(aFloatAvoidingBlock, 8483 aState.mReflowInput.mRenderingContext, wm, 8484 aState.mContentArea.ISize(wm)); 8485 const LogicalMargin computedMargin = sizingInput.ComputedLogicalMargin(wm); 8486 8487 nscoord marginISize = computedMargin.IStartEnd(wm); 8488 const auto iSize = reflowInput.mStylePosition->ISize( 8489 wm, AnchorPosResolutionParams::From(&reflowInput)); 8490 if (marginISize < 0 && 8491 (iSize->IsAuto() || iSize->BehavesLikeStretchOnInlineAxis())) { 8492 // If we get here, floatAvoidingBlock has a negative amount of inline-axis 8493 // margin and an 'auto' (or ~equivalently, -moz-available) inline 8494 // size. Under these circumstances, we use the margin to establish a 8495 // (positive) minimum size for the border-box, in order to satisfy the 8496 // equation in CSS2 10.3.3. That equation essentially simplifies to the 8497 // following: 8498 // 8499 // iSize of margins + iSize of borderBox = iSize of containingBlock 8500 // 8501 // ...where "iSize of borderBox" is the sum of floatAvoidingBlock's 8502 // inline-axis components of border, padding, and {width,height}. 8503 // 8504 // Right now, in the above equation, "iSize of margins" is the only term 8505 // that we know for sure. (And we also know that it's negative, since we 8506 // got here.) The other terms are as-yet unresolved, since the frame has an 8507 // 'auto' iSize, and since we aren't yet sure if we'll clear this frame 8508 // beyond floats or place it alongside them. 8509 // 8510 // However: we *do* know that the equation's "iSize of containingBlock" 8511 // term *must* be non-negative, since boxes' widths and heights generally 8512 // can't be negative in CSS. To satisfy that requirement, we can then 8513 // infer that the equation's "iSize of borderBox" term *must* be large 8514 // enough to cancel out the (known-to-be-negative) "iSize of margins" 8515 // term. Therefore, marginISize value (negated to make it positive) 8516 // establishes a lower-bound for how much inline-axis space our border-box 8517 // will really require in order to fit alongside any floats. 8518 // 8519 // XXXdholbert This explanation is admittedly a bit hand-wavy and may not 8520 // precisely match what any particular spec requires. It's the best 8521 // reasoning I could come up with to explain engines' behavior. Also, our 8522 // behavior with -moz-available doesn't seem particularly correct here, per 8523 // bug 1767217, though that's probably due to a bug elsewhere in our float 8524 // handling code... 8525 result.borderBoxISize = std::max(result.borderBoxISize, -marginISize); 8526 } 8527 8528 result.marginIStart = computedMargin.IStart(wm); 8529 return result; 8530 } 8531 8532 /* static */ 8533 nsBlockFrame* nsBlockFrame::GetNearestAncestorBlock(nsIFrame* aCandidate) { 8534 nsBlockFrame* block = nullptr; 8535 while (aCandidate) { 8536 block = do_QueryFrame(aCandidate); 8537 if (block) { 8538 // yay, candidate is a block! 8539 return block; 8540 } 8541 // Not a block. Check its parent next. 8542 aCandidate = aCandidate->GetParent(); 8543 } 8544 MOZ_ASSERT_UNREACHABLE("Fell off frame tree looking for ancestor block!"); 8545 return nullptr; 8546 } 8547 8548 nscoord nsBlockFrame::ComputeFinalBSize(BlockReflowState& aState, 8549 nscoord aBEndEdgeOfChildren) { 8550 const WritingMode wm = aState.mReflowInput.GetWritingMode(); 8551 8552 const nscoord effectiveContentBoxBSize = 8553 GetEffectiveComputedBSize(aState.mReflowInput, aState.mConsumedBSize); 8554 const nscoord blockStartBP = aState.BorderPadding().BStart(wm); 8555 const nscoord blockEndBP = aState.BorderPadding().BEnd(wm); 8556 8557 NS_ASSERTION( 8558 !IsTrueOverflowContainer() || (effectiveContentBoxBSize == 0 && 8559 blockStartBP == 0 && blockEndBP == 0), 8560 "An overflow container's effective content-box block-size, block-start " 8561 "BP, and block-end BP should all be zero!"); 8562 8563 const nscoord effectiveContentBoxBSizeWithBStartBP = 8564 NSCoordSaturatingAdd(blockStartBP, effectiveContentBoxBSize); 8565 const nscoord effectiveBorderBoxBSize = 8566 NSCoordSaturatingAdd(effectiveContentBoxBSizeWithBStartBP, blockEndBP); 8567 8568 if (HasColumnSpanSiblings()) { 8569 MOZ_ASSERT(LastInFlow()->GetNextContinuation(), 8570 "Frame constructor should've created column-span siblings!"); 8571 8572 // If a block is split by any column-spans, we calculate the final 8573 // block-size by shrinkwrapping our children's block-size for all the 8574 // fragments except for those after the final column-span, but we should 8575 // take no more than our effective border-box block-size. If there's any 8576 // leftover block-size, our next continuations will take up rest. 8577 // 8578 // We don't need to adjust aBri.mReflowStatus because our children's status 8579 // is the same as ours. 8580 return std::min(effectiveBorderBoxBSize, aBEndEdgeOfChildren); 8581 } 8582 8583 const nscoord availBSize = aState.mReflowInput.AvailableBSize(); 8584 if (availBSize == NS_UNCONSTRAINEDSIZE) { 8585 return effectiveBorderBoxBSize; 8586 } 8587 8588 // Save our children's reflow status. 8589 const bool isChildStatusComplete = aState.mReflowStatus.IsComplete(); 8590 if (isChildStatusComplete && effectiveContentBoxBSize > 0 && 8591 effectiveBorderBoxBSize > availBSize && 8592 ShouldAvoidBreakInside(aState.mReflowInput)) { 8593 aState.mReflowStatus.SetInlineLineBreakBeforeAndReset(); 8594 return effectiveBorderBoxBSize; 8595 } 8596 8597 const bool isBDBClone = 8598 aState.mReflowInput.mStyleBorder->mBoxDecorationBreak == 8599 StyleBoxDecorationBreak::Clone; 8600 8601 // The maximum value our content-box block-size can take within the given 8602 // available block-size. 8603 const nscoord maxContentBoxBSize = aState.ContentBSize(); 8604 8605 // The block-end edge of our content-box (relative to this frame's origin) if 8606 // we consumed the maximum block-size available to us (maxContentBoxBSize). 8607 const nscoord maxContentBoxBEnd = aState.ContentBEnd(); 8608 8609 // These variables are uninitialized intentionally so that the compiler can 8610 // check they are assigned in every if-else branch below. 8611 nscoord finalContentBoxBSizeWithBStartBP; 8612 bool isOurStatusComplete; 8613 8614 if (effectiveBorderBoxBSize <= availBSize) { 8615 // Our effective border-box block-size can fit in the available block-size, 8616 // so we are complete. 8617 finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP; 8618 isOurStatusComplete = true; 8619 } else if (effectiveContentBoxBSizeWithBStartBP <= maxContentBoxBEnd) { 8620 // Note: The following assertion should generally hold because, for 8621 // box-decoration-break:clone, this "else if" branch is mathematically 8622 // equivalent to the initial "if". 8623 NS_ASSERTION(!isBDBClone, 8624 "This else-if branch is handling a situation that's specific " 8625 "to box-decoration-break:slice, i.e. a case when we can skip " 8626 "our block-end border and padding!"); 8627 8628 // Our effective content-box block-size plus the block-start border and 8629 // padding can fit in the available block-size, but it cannot fit after 8630 // adding the block-end border and padding. Thus, we need a continuation 8631 // (unless we already weren't asking for any block-size, in which case we 8632 // stay complete to avoid looping forever). 8633 finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP; 8634 isOurStatusComplete = effectiveContentBoxBSize == 0; 8635 } else { 8636 // We aren't going to be able to fit our content-box in the space available 8637 // to it, which means we'll probably call ourselves incomplete to request a 8638 // continuation. But before making that decision, we check for certain 8639 // conditions which would force us to overflow beyond the available space -- 8640 // these might result in us actually being complete if we're forced to 8641 // overflow far enough. 8642 if (MOZ_UNLIKELY(aState.mReflowInput.mFlags.mIsTopOfPage && isBDBClone && 8643 maxContentBoxBSize <= 0 && 8644 aBEndEdgeOfChildren == blockStartBP)) { 8645 // In this rare case, we are at the top of page/column, we have 8646 // box-decoration-break:clone and zero available block-size for our 8647 // content-box (e.g. our own block-start border and padding already exceed 8648 // the available block-size), and we didn't lay out any child to consume 8649 // our content-box block-size. To ensure we make progress (avoid looping 8650 // forever), use 1px as our content-box block-size regardless of our 8651 // effective content-box block-size, in the spirit of 8652 // https://drafts.csswg.org/css-break/#breaking-rules. 8653 finalContentBoxBSizeWithBStartBP = blockStartBP + AppUnitsPerCSSPixel(); 8654 isOurStatusComplete = effectiveContentBoxBSize <= AppUnitsPerCSSPixel(); 8655 } else if (aBEndEdgeOfChildren > maxContentBoxBEnd) { 8656 // We have a unbreakable child whose block-end edge exceeds the available 8657 // block-size for children. 8658 if (aBEndEdgeOfChildren >= effectiveContentBoxBSizeWithBStartBP) { 8659 // The unbreakable child's block-end edge forces us to consume all of 8660 // our effective content-box block-size. 8661 finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP; 8662 8663 // Even though we've consumed all of our effective content-box 8664 // block-size, we may still need to report an incomplete status in order 8665 // to get another continuation, which will be responsible for laying out 8666 // & drawing our block-end border & padding. But if we have no such 8667 // border & padding, or if we're forced to apply that border & padding 8668 // on this frame due to box-decoration-break:clone, then we don't need 8669 // to bother with that additional continuation. 8670 isOurStatusComplete = (isBDBClone || blockEndBP == 0); 8671 } else { 8672 // The unbreakable child's block-end edge doesn't force us to consume 8673 // all of our effective content-box block-size. 8674 finalContentBoxBSizeWithBStartBP = aBEndEdgeOfChildren; 8675 isOurStatusComplete = false; 8676 } 8677 } else { 8678 // The children's block-end edge can fit in the content-box space that we 8679 // have available for it. Consume all the space that is available so that 8680 // our inline-start/inline-end borders extend all the way to the block-end 8681 // edge of column/page. 8682 finalContentBoxBSizeWithBStartBP = maxContentBoxBEnd; 8683 isOurStatusComplete = false; 8684 } 8685 } 8686 8687 nscoord finalBorderBoxBSize = finalContentBoxBSizeWithBStartBP; 8688 if (isOurStatusComplete) { 8689 finalBorderBoxBSize = NSCoordSaturatingAdd(finalBorderBoxBSize, blockEndBP); 8690 if (isChildStatusComplete) { 8691 // We want to use children's reflow status as ours, which can be overflow 8692 // incomplete. Suppress the urge to call aBri.mReflowStatus.Reset() here. 8693 } else { 8694 aState.mReflowStatus.SetOverflowIncomplete(); 8695 } 8696 } else { 8697 NS_ASSERTION(!IsTrueOverflowContainer(), 8698 "An overflow container should always be complete because of " 8699 "its zero border-box block-size!"); 8700 if (isBDBClone) { 8701 finalBorderBoxBSize = 8702 NSCoordSaturatingAdd(finalBorderBoxBSize, blockEndBP); 8703 } 8704 aState.mReflowStatus.SetIncomplete(); 8705 if (!GetNextInFlow()) { 8706 aState.mReflowStatus.SetNextInFlowNeedsReflow(); 8707 } 8708 } 8709 8710 return finalBorderBoxBSize; 8711 } 8712 8713 nsresult nsBlockFrame::ResolveBidi() { 8714 NS_ASSERTION(!GetPrevInFlow(), 8715 "ResolveBidi called on non-first continuation"); 8716 MOZ_ASSERT(PresContext()->BidiEnabled()); 8717 return nsBidiPresUtils::Resolve(this); 8718 } 8719 8720 void nsBlockFrame::UpdatePseudoElementStyles(ServoRestyleState& aRestyleState) { 8721 // first-letter needs to be updated before first-line, because first-line can 8722 // change the style of the first-letter. 8723 if (HasFirstLetterChild()) { 8724 UpdateFirstLetterStyle(aRestyleState); 8725 } 8726 8727 if (nsIFrame* firstLineFrame = GetFirstLineFrame()) { 8728 nsIFrame* styleParent = CorrectStyleParentFrame(firstLineFrame->GetParent(), 8729 PseudoStyleType::firstLine); 8730 8731 ComputedStyle* parentStyle = styleParent->Style(); 8732 RefPtr<ComputedStyle> firstLineStyle = 8733 aRestyleState.StyleSet().ResolvePseudoElementStyle( 8734 *mContent->AsElement(), PseudoStyleType::firstLine, nullptr, 8735 parentStyle); 8736 8737 // FIXME(bz): Can we make first-line continuations be non-inheriting anon 8738 // boxes? 8739 RefPtr<ComputedStyle> continuationStyle = 8740 aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle( 8741 PseudoStyleType::mozLineFrame, parentStyle); 8742 8743 UpdateStyleOfOwnedChildFrame(firstLineFrame, firstLineStyle, aRestyleState, 8744 Some(continuationStyle.get())); 8745 8746 // We also want to update the styles of the first-line's descendants. We 8747 // don't need to compute a changehint for this, though, since any changes to 8748 // them are handled by the first-line anyway. 8749 RestyleManager* manager = PresContext()->RestyleManager(); 8750 for (nsIFrame* kid : firstLineFrame->PrincipalChildList()) { 8751 manager->ReparentComputedStyleForFirstLine(kid); 8752 } 8753 } 8754 } 8755 8756 nsIFrame* nsBlockFrame::GetFirstLetter() const { 8757 if (!HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE)) { 8758 // Certainly no first-letter frame. 8759 return nullptr; 8760 } 8761 8762 return GetProperty(FirstLetterProperty()); 8763 } 8764 8765 nsIFrame* nsBlockFrame::GetFirstLineFrame() const { 8766 nsIFrame* maybeFirstLine = PrincipalChildList().FirstChild(); 8767 if (maybeFirstLine && maybeFirstLine->IsLineFrame()) { 8768 return maybeFirstLine; 8769 } 8770 8771 return nullptr; 8772 } 8773 8774 #ifdef DEBUG 8775 void nsBlockFrame::VerifyLines(bool aFinalCheckOK) { 8776 if (!gVerifyLines) { 8777 return; 8778 } 8779 if (mLines.empty()) { 8780 return; 8781 } 8782 8783 nsLineBox* cursor = GetLineCursorForQuery(); 8784 8785 // Add up the counts on each line. Also validate that IsFirstLine is 8786 // set properly. 8787 int32_t count = 0; 8788 for (const auto& line : Lines()) { 8789 if (&line == cursor) { 8790 cursor = nullptr; 8791 } 8792 if (aFinalCheckOK) { 8793 MOZ_ASSERT(line.GetChildCount(), "empty line"); 8794 if (line.IsBlock()) { 8795 NS_ASSERTION(1 == line.GetChildCount(), "bad first line"); 8796 } 8797 } 8798 count += line.GetChildCount(); 8799 } 8800 8801 // Then count the frames 8802 int32_t frameCount = 0; 8803 nsIFrame* frame = mLines.front()->mFirstChild; 8804 while (frame) { 8805 frameCount++; 8806 frame = frame->GetNextSibling(); 8807 } 8808 NS_ASSERTION(count == frameCount, "bad line list"); 8809 8810 // Next: test that each line has right number of frames on it 8811 for (LineIterator line = LinesBegin(), line_end = LinesEnd(); 8812 line != line_end;) { 8813 count = line->GetChildCount(); 8814 frame = line->mFirstChild; 8815 while (--count >= 0) { 8816 frame = frame->GetNextSibling(); 8817 } 8818 ++line; 8819 if ((line != line_end) && (0 != line->GetChildCount())) { 8820 NS_ASSERTION(frame == line->mFirstChild, "bad line list"); 8821 } 8822 } 8823 8824 if (cursor) { 8825 FrameLines* overflowLines = GetOverflowLines(); 8826 if (overflowLines) { 8827 LineIterator line = overflowLines->mLines.begin(); 8828 LineIterator line_end = overflowLines->mLines.end(); 8829 for (; line != line_end; ++line) { 8830 if (line == cursor) { 8831 cursor = nullptr; 8832 break; 8833 } 8834 } 8835 } 8836 } 8837 NS_ASSERTION(!cursor, "stale LineCursorProperty"); 8838 } 8839 8840 void nsBlockFrame::VerifyOverflowSituation() { 8841 // Overflow out-of-flows must not have a next-in-flow in floats list or 8842 // mFrames. 8843 nsFrameList* oofs = GetOverflowOutOfFlows(); 8844 if (oofs) { 8845 for (nsIFrame* f : *oofs) { 8846 nsIFrame* nif = f->GetNextInFlow(); 8847 MOZ_ASSERT(!nif || 8848 (!GetChildList(FrameChildListID::Float).ContainsFrame(nif) && 8849 !mFrames.ContainsFrame(nif))); 8850 } 8851 } 8852 8853 // Pushed floats must not have a next-in-flow in floats list or mFrames. 8854 oofs = GetPushedFloats(); 8855 if (oofs) { 8856 for (nsIFrame* f : *oofs) { 8857 nsIFrame* nif = f->GetNextInFlow(); 8858 MOZ_ASSERT(!nif || 8859 (!GetChildList(FrameChildListID::Float).ContainsFrame(nif) && 8860 !mFrames.ContainsFrame(nif))); 8861 } 8862 } 8863 8864 // A child float next-in-flow's parent must be |this| or a next-in-flow of 8865 // |this|. Later next-in-flows must have the same or later parents. 8866 ChildListID childLists[] = {FrameChildListID::Float, 8867 FrameChildListID::PushedFloats}; 8868 for (size_t i = 0; i < std::size(childLists); ++i) { 8869 const nsFrameList& children = GetChildList(childLists[i]); 8870 for (nsIFrame* f : children) { 8871 nsIFrame* parent = this; 8872 nsIFrame* nif = f->GetNextInFlow(); 8873 for (; nif; nif = nif->GetNextInFlow()) { 8874 bool found = false; 8875 for (nsIFrame* p = parent; p; p = p->GetNextInFlow()) { 8876 if (nif->GetParent() == p) { 8877 parent = p; 8878 found = true; 8879 break; 8880 } 8881 } 8882 MOZ_ASSERT( 8883 found, 8884 "next-in-flow is a child of parent earlier in the frame tree?"); 8885 } 8886 } 8887 } 8888 8889 nsBlockFrame* flow = static_cast<nsBlockFrame*>(FirstInFlow()); 8890 while (flow) { 8891 FrameLines* overflowLines = flow->GetOverflowLines(); 8892 if (overflowLines) { 8893 NS_ASSERTION(!overflowLines->mLines.empty(), 8894 "should not be empty if present"); 8895 NS_ASSERTION(overflowLines->mLines.front()->mFirstChild, 8896 "bad overflow lines"); 8897 NS_ASSERTION(overflowLines->mLines.front()->mFirstChild == 8898 overflowLines->mFrames.FirstChild(), 8899 "bad overflow frames / lines"); 8900 } 8901 auto checkCursor = [&](nsLineBox* cursor) -> bool { 8902 if (!cursor) { 8903 return true; 8904 } 8905 LineIterator line = flow->LinesBegin(); 8906 LineIterator line_end = flow->LinesEnd(); 8907 for (; line != line_end && line != cursor; ++line); 8908 if (line == line_end && overflowLines) { 8909 line = overflowLines->mLines.begin(); 8910 line_end = overflowLines->mLines.end(); 8911 for (; line != line_end && line != cursor; ++line); 8912 } 8913 return line != line_end; 8914 }; 8915 MOZ_ASSERT(checkCursor(flow->GetLineCursorForDisplay()), 8916 "stale LineCursorPropertyDisplay"); 8917 MOZ_ASSERT(checkCursor(flow->GetLineCursorForQuery()), 8918 "stale LineCursorPropertyQuery"); 8919 flow = static_cast<nsBlockFrame*>(flow->GetNextInFlow()); 8920 } 8921 } 8922 8923 int32_t nsBlockFrame::GetDepth() const { 8924 int32_t depth = 0; 8925 nsIFrame* parent = GetParent(); 8926 while (parent) { 8927 parent = parent->GetParent(); 8928 depth++; 8929 } 8930 return depth; 8931 } 8932 8933 already_AddRefed<ComputedStyle> nsBlockFrame::GetFirstLetterStyle( 8934 nsPresContext* aPresContext) { 8935 return aPresContext->StyleSet()->ProbePseudoElementStyle( 8936 *mContent->AsElement(), PseudoStyleType::firstLetter, nullptr, Style()); 8937 } 8938 #endif