Bug 1800044 [wpt PR 36907] - Fix same URL navigation test, a=testonly
[gecko.git] / layout / generic / nsBlockFrame.cpp
blob2e5e1af5d3605cc959f663b2700599d13d2308f0
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/. */
7 /*
8 * rendering object for CSS display:block, inline-block, and list-item
9 * boxes, also used for various anonymous boxes
12 #include "nsBlockFrame.h"
14 #include "gfxContext.h"
16 #include "mozilla/AppUnits.h"
17 #include "mozilla/ComputedStyle.h"
18 #include "mozilla/DebugOnly.h"
19 #include "mozilla/Maybe.h"
20 #include "mozilla/PresShell.h"
21 #include "mozilla/StaticPrefs_browser.h"
22 #include "mozilla/StaticPrefs_layout.h"
23 #include "mozilla/SVGUtils.h"
24 #include "mozilla/ToString.h"
25 #include "mozilla/UniquePtr.h"
27 #include "nsCRT.h"
28 #include "nsCOMPtr.h"
29 #include "nsCSSRendering.h"
30 #include "nsAbsoluteContainingBlock.h"
31 #include "nsBlockReflowContext.h"
32 #include "BlockReflowState.h"
33 #include "nsFontMetrics.h"
34 #include "nsGenericHTMLElement.h"
35 #include "nsLineBox.h"
36 #include "nsLineLayout.h"
37 #include "nsPlaceholderFrame.h"
38 #include "nsStyleConsts.h"
39 #include "nsFrameManager.h"
40 #include "nsPresContext.h"
41 #include "nsPresContextInlines.h"
42 #include "nsHTMLParts.h"
43 #include "nsGkAtoms.h"
44 #include "nsAttrValueInlines.h"
45 #include "mozilla/Sprintf.h"
46 #include "nsFloatManager.h"
47 #include "prenv.h"
48 #include "nsError.h"
49 #include "nsIScrollableFrame.h"
50 #include <algorithm>
51 #include "nsLayoutUtils.h"
52 #include "nsDisplayList.h"
53 #include "nsCSSAnonBoxes.h"
54 #include "nsCSSFrameConstructor.h"
55 #include "TextOverflow.h"
56 #include "nsIFrameInlines.h"
57 #include "CounterStyleManager.h"
58 #include "mozilla/dom/HTMLDetailsElement.h"
59 #include "mozilla/dom/HTMLSummaryElement.h"
60 #include "mozilla/dom/Selection.h"
61 #include "mozilla/PresShell.h"
62 #include "mozilla/RestyleManager.h"
63 #include "mozilla/ServoStyleSet.h"
64 #include "mozilla/Telemetry.h"
65 #include "nsFlexContainerFrame.h"
67 #include "nsBidiPresUtils.h"
69 #include <inttypes.h>
71 static const int MIN_LINES_NEEDING_CURSOR = 20;
73 using namespace mozilla;
74 using namespace mozilla::css;
75 using namespace mozilla::dom;
76 using namespace mozilla::layout;
77 using AbsPosReflowFlags = nsAbsoluteContainingBlock::AbsPosReflowFlags;
78 using ClearFloatsResult = BlockReflowState::ClearFloatsResult;
79 using ShapeType = nsFloatManager::ShapeType;
81 static void MarkAllDescendantLinesDirty(nsBlockFrame* aBlock) {
82 for (auto& line : aBlock->Lines()) {
83 if (line.IsBlock()) {
84 nsBlockFrame* bf = do_QueryFrame(line.mFirstChild);
85 if (bf) {
86 MarkAllDescendantLinesDirty(bf);
89 line.MarkDirty();
93 static void MarkSameFloatManagerLinesDirty(nsBlockFrame* aBlock) {
94 nsBlockFrame* blockWithFloatMgr = aBlock;
95 while (!blockWithFloatMgr->HasAnyStateBits(NS_BLOCK_FLOAT_MGR)) {
96 nsBlockFrame* bf = do_QueryFrame(blockWithFloatMgr->GetParent());
97 if (!bf) {
98 break;
100 blockWithFloatMgr = bf;
103 // Mark every line at and below the line where the float was
104 // dirty, and mark their lines dirty too. We could probably do
105 // something more efficient --- e.g., just dirty the lines that intersect
106 // the float vertically.
107 MarkAllDescendantLinesDirty(blockWithFloatMgr);
111 * Returns true if aFrame is a block that has one or more float children.
113 static bool BlockHasAnyFloats(nsIFrame* aFrame) {
114 nsBlockFrame* block = do_QueryFrame(aFrame);
115 if (!block) {
116 return false;
118 if (block->GetChildList(FrameChildListID::Float).FirstChild()) {
119 return true;
122 for (const auto& line : block->Lines()) {
123 if (line.IsBlock() && BlockHasAnyFloats(line.mFirstChild)) {
124 return true;
127 return false;
131 * Determines whether the given frame is visible or has
132 * visible children that participate in the same line. Frames
133 * that are not line participants do not have their
134 * children checked.
136 static bool FrameHasVisibleInlineContent(nsIFrame* aFrame) {
137 MOZ_ASSERT(aFrame, "Frame argument cannot be null");
139 if (aFrame->StyleVisibility()->IsVisible()) {
140 return true;
143 if (aFrame->IsFrameOfType(nsIFrame::eLineParticipant)) {
144 for (nsIFrame* kid : aFrame->PrincipalChildList()) {
145 if (kid->StyleVisibility()->IsVisible() ||
146 FrameHasVisibleInlineContent(kid)) {
147 return true;
151 return false;
155 * Determines whether any of the frames descended from the
156 * given line have inline content with 'visibility: visible'.
157 * This function calls FrameHasVisibleInlineContent to process
158 * each frame in the line's child list.
160 static bool LineHasVisibleInlineContent(nsLineBox* aLine) {
161 nsIFrame* kid = aLine->mFirstChild;
162 int32_t n = aLine->GetChildCount();
163 while (n-- > 0) {
164 if (FrameHasVisibleInlineContent(kid)) {
165 return true;
168 kid = kid->GetNextSibling();
171 return false;
175 * Iterates through the frame's in-flow children and
176 * unions the ink overflow of all text frames which
177 * participate in the line aFrame belongs to.
178 * If a child of aFrame is not a text frame,
179 * we recurse with the child as the aFrame argument.
180 * If aFrame isn't a line participant, we skip it entirely
181 * and return an empty rect.
182 * The resulting nsRect is offset relative to the parent of aFrame.
184 static nsRect GetFrameTextArea(nsIFrame* aFrame,
185 nsDisplayListBuilder* aBuilder) {
186 nsRect textArea;
187 if (const nsTextFrame* textFrame = do_QueryFrame(aFrame)) {
188 if (!textFrame->IsEntirelyWhitespace()) {
189 textArea = aFrame->InkOverflowRect();
191 } else if (aFrame->IsFrameOfType(nsIFrame::eLineParticipant)) {
192 for (nsIFrame* kid : aFrame->PrincipalChildList()) {
193 nsRect kidTextArea = GetFrameTextArea(kid, aBuilder);
194 textArea.OrWith(kidTextArea);
197 // add aFrame's position to keep textArea relative to aFrame's parent
198 return textArea + aFrame->GetPosition();
202 * Iterates through the line's children and
203 * unions the ink overflow of all text frames.
204 * GetFrameTextArea unions and returns the ink overflow
205 * from all line-participating text frames within the given child.
206 * The nsRect returned from GetLineTextArea is offset
207 * relative to the given line.
209 static nsRect GetLineTextArea(nsLineBox* aLine,
210 nsDisplayListBuilder* aBuilder) {
211 nsRect textArea;
212 nsIFrame* kid = aLine->mFirstChild;
213 int32_t n = aLine->GetChildCount();
214 while (n-- > 0) {
215 nsRect kidTextArea = GetFrameTextArea(kid, aBuilder);
216 textArea.OrWith(kidTextArea);
217 kid = kid->GetNextSibling();
220 return textArea;
224 * Starting with aFrame, iterates upward through parent frames and checks for
225 * non-transparent background colors. If one is found, we use that as our
226 * backplate color. Otheriwse, we use the default background color from
227 * our high contrast theme.
229 static nscolor GetBackplateColor(nsIFrame* aFrame) {
230 nsPresContext* pc = aFrame->PresContext();
231 nscolor currentBackgroundColor = NS_TRANSPARENT;
232 for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
233 // NOTE(emilio): We assume themed frames (frame->IsThemed()) have correct
234 // background-color information so as to compute the right backplate color.
236 // This holds because HTML widgets with author-specified backgrounds or
237 // borders disable theming. So as long as the UA-specified background colors
238 // match the actual theme (which they should because we always use system
239 // colors with the non-native theme, and native system colors should also
240 // match the native theme), then we're alright and we should compute an
241 // appropriate backplate color.
242 const auto* style = frame->Style();
243 if (style->StyleBackground()->IsTransparent(style)) {
244 continue;
246 bool drawImage = false, drawColor = false;
247 nscolor backgroundColor = nsCSSRendering::DetermineBackgroundColor(
248 pc, style, frame, drawImage, drawColor);
249 if (!drawColor && !drawImage) {
250 continue;
252 if (NS_GET_A(backgroundColor) == 0) {
253 // Even if there's a background image, if there's no background color we
254 // keep going up the frame tree, see bug 1723938.
255 continue;
257 if (NS_GET_A(currentBackgroundColor) == 0) {
258 // Try to avoid somewhat expensive math in the common case.
259 currentBackgroundColor = backgroundColor;
260 } else {
261 currentBackgroundColor =
262 NS_ComposeColors(backgroundColor, currentBackgroundColor);
264 if (NS_GET_A(currentBackgroundColor) == 0xff) {
265 // If fully opaque, we're done, otherwise keep going up blending with our
266 // background.
267 return currentBackgroundColor;
270 nscolor backgroundColor = aFrame->PresContext()->DefaultBackgroundColor();
271 if (NS_GET_A(currentBackgroundColor) == 0) {
272 return backgroundColor;
274 return NS_ComposeColors(backgroundColor, currentBackgroundColor);
277 #ifdef DEBUG
278 # include "nsBlockDebugFlags.h"
280 bool nsBlockFrame::gLamePaintMetrics;
281 bool nsBlockFrame::gLameReflowMetrics;
282 bool nsBlockFrame::gNoisy;
283 bool nsBlockFrame::gNoisyDamageRepair;
284 bool nsBlockFrame::gNoisyIntrinsic;
285 bool nsBlockFrame::gNoisyReflow;
286 bool nsBlockFrame::gReallyNoisyReflow;
287 bool nsBlockFrame::gNoisyFloatManager;
288 bool nsBlockFrame::gVerifyLines;
289 bool nsBlockFrame::gDisableResizeOpt;
291 int32_t nsBlockFrame::gNoiseIndent;
293 struct BlockDebugFlags {
294 const char* name;
295 bool* on;
298 static const BlockDebugFlags gFlags[] = {
299 {"reflow", &nsBlockFrame::gNoisyReflow},
300 {"really-noisy-reflow", &nsBlockFrame::gReallyNoisyReflow},
301 {"intrinsic", &nsBlockFrame::gNoisyIntrinsic},
302 {"float-manager", &nsBlockFrame::gNoisyFloatManager},
303 {"verify-lines", &nsBlockFrame::gVerifyLines},
304 {"damage-repair", &nsBlockFrame::gNoisyDamageRepair},
305 {"lame-paint-metrics", &nsBlockFrame::gLamePaintMetrics},
306 {"lame-reflow-metrics", &nsBlockFrame::gLameReflowMetrics},
307 {"disable-resize-opt", &nsBlockFrame::gDisableResizeOpt},
309 # define NUM_DEBUG_FLAGS (sizeof(gFlags) / sizeof(gFlags[0]))
311 static void ShowDebugFlags() {
312 printf("Here are the available GECKO_BLOCK_DEBUG_FLAGS:\n");
313 const BlockDebugFlags* bdf = gFlags;
314 const BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS;
315 for (; bdf < end; bdf++) {
316 printf(" %s\n", bdf->name);
318 printf("Note: GECKO_BLOCK_DEBUG_FLAGS is a comma separated list of flag\n");
319 printf("names (no whitespace)\n");
322 void nsBlockFrame::InitDebugFlags() {
323 static bool firstTime = true;
324 if (firstTime) {
325 firstTime = false;
326 char* flags = PR_GetEnv("GECKO_BLOCK_DEBUG_FLAGS");
327 if (flags) {
328 bool error = false;
329 for (;;) {
330 char* cm = strchr(flags, ',');
331 if (cm) {
332 *cm = '\0';
335 bool found = false;
336 const BlockDebugFlags* bdf = gFlags;
337 const BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS;
338 for (; bdf < end; bdf++) {
339 if (nsCRT::strcasecmp(bdf->name, flags) == 0) {
340 *(bdf->on) = true;
341 printf("nsBlockFrame: setting %s debug flag on\n", bdf->name);
342 gNoisy = true;
343 found = true;
344 break;
347 if (!found) {
348 error = true;
351 if (!cm) {
352 break;
354 *cm = ',';
355 flags = cm + 1;
357 if (error) {
358 ShowDebugFlags();
364 #endif
366 //----------------------------------------------------------------------
368 // Debugging support code
370 #ifdef DEBUG
371 const char* nsBlockFrame::kReflowCommandType[] = {
372 "ContentChanged", "StyleChanged", "ReflowDirty", "Timeout", "UserDefined",
375 const char* nsBlockFrame::LineReflowStatusToString(
376 LineReflowStatus aLineReflowStatus) const {
377 switch (aLineReflowStatus) {
378 case LineReflowStatus::OK:
379 return "LINE_REFLOW_OK";
380 case LineReflowStatus::Stop:
381 return "LINE_REFLOW_STOP";
382 case LineReflowStatus::RedoNoPull:
383 return "LINE_REFLOW_REDO_NO_PULL";
384 case LineReflowStatus::RedoMoreFloats:
385 return "LINE_REFLOW_REDO_MORE_FLOATS";
386 case LineReflowStatus::RedoNextBand:
387 return "LINE_REFLOW_REDO_NEXT_BAND";
388 case LineReflowStatus::Truncated:
389 return "LINE_REFLOW_TRUNCATED";
391 return "unknown";
394 #endif
396 #ifdef REFLOW_STATUS_COVERAGE
397 static void RecordReflowStatus(bool aChildIsBlock,
398 const nsReflowStatus& aFrameReflowStatus) {
399 static uint32_t record[2];
401 // 0: child-is-block
402 // 1: child-is-inline
403 int index = 0;
404 if (!aChildIsBlock) {
405 index |= 1;
408 // Compute new status
409 uint32_t newS = record[index];
410 if (aFrameReflowStatus.IsInlineBreak()) {
411 if (aFrameReflowStatus.IsInlineBreakBefore()) {
412 newS |= 1;
413 } else if (aFrameReflowStatus.IsIncomplete()) {
414 newS |= 2;
415 } else {
416 newS |= 4;
418 } else if (aFrameReflowStatus.IsIncomplete()) {
419 newS |= 8;
420 } else {
421 newS |= 16;
424 // Log updates to the status that yield different values
425 if (record[index] != newS) {
426 record[index] = newS;
427 printf("record(%d): %02x %02x\n", index, record[0], record[1]);
430 #endif
432 NS_DECLARE_FRAME_PROPERTY_WITH_DTOR_NEVER_CALLED(OverflowLinesProperty,
433 nsBlockFrame::FrameLines)
434 NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OverflowOutOfFlowsProperty)
435 NS_DECLARE_FRAME_PROPERTY_FRAMELIST(PushedFloatProperty)
436 NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OutsideMarkerProperty)
437 NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(InsideMarkerProperty, nsIFrame)
438 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(BlockEndEdgeOfChildrenProperty, nscoord)
440 //----------------------------------------------------------------------
442 nsBlockFrame* NS_NewBlockFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
443 return new (aPresShell) nsBlockFrame(aStyle, aPresShell->GetPresContext());
446 nsBlockFrame* NS_NewBlockFormattingContext(PresShell* aPresShell,
447 ComputedStyle* aComputedStyle) {
448 nsBlockFrame* blockFrame = NS_NewBlockFrame(aPresShell, aComputedStyle);
449 blockFrame->AddStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS);
450 return blockFrame;
453 NS_IMPL_FRAMEARENA_HELPERS(nsBlockFrame)
455 nsBlockFrame::~nsBlockFrame() = default;
457 void nsBlockFrame::AddSizeOfExcludingThisForTree(
458 nsWindowSizes& aWindowSizes) const {
459 nsContainerFrame::AddSizeOfExcludingThisForTree(aWindowSizes);
461 // Add the size of any nsLineBox::mFrames hashtables we might have:
462 for (const auto& line : Lines()) {
463 line.AddSizeOfExcludingThis(aWindowSizes);
465 const FrameLines* overflowLines = GetOverflowLines();
466 if (overflowLines) {
467 ConstLineIterator line = overflowLines->mLines.begin(),
468 line_end = overflowLines->mLines.end();
469 for (; line != line_end; ++line) {
470 line->AddSizeOfExcludingThis(aWindowSizes);
475 void nsBlockFrame::DestroyFrom(nsIFrame* aDestructRoot,
476 PostDestroyData& aPostDestroyData) {
477 ClearLineCursors();
478 DestroyAbsoluteFrames(aDestructRoot, aPostDestroyData);
479 mFloats.DestroyFramesFrom(aDestructRoot, aPostDestroyData);
480 nsPresContext* presContext = PresContext();
481 mozilla::PresShell* presShell = presContext->PresShell();
482 nsLineBox::DeleteLineList(presContext, mLines, aDestructRoot, &mFrames,
483 aPostDestroyData);
485 if (HasPushedFloats()) {
486 SafelyDestroyFrameListProp(aDestructRoot, aPostDestroyData, presShell,
487 PushedFloatProperty());
488 RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
491 // destroy overflow lines now
492 FrameLines* overflowLines = RemoveOverflowLines();
493 if (overflowLines) {
494 nsLineBox::DeleteLineList(presContext, overflowLines->mLines, aDestructRoot,
495 &overflowLines->mFrames, aPostDestroyData);
496 delete overflowLines;
499 if (HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
500 SafelyDestroyFrameListProp(aDestructRoot, aPostDestroyData, presShell,
501 OverflowOutOfFlowsProperty());
502 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
505 if (HasOutsideMarker()) {
506 SafelyDestroyFrameListProp(aDestructRoot, aPostDestroyData, presShell,
507 OutsideMarkerProperty());
508 RemoveStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER);
511 nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
514 /* virtual */
515 nsILineIterator* nsBlockFrame::GetLineIterator() {
516 nsLineIterator* iter = GetProperty(LineIteratorProperty());
517 if (!iter) {
518 const nsStyleVisibility* visibility = StyleVisibility();
519 iter = new nsLineIterator(mLines,
520 visibility->mDirection == StyleDirection::Rtl);
521 SetProperty(LineIteratorProperty(), iter);
523 return iter;
526 NS_QUERYFRAME_HEAD(nsBlockFrame)
527 NS_QUERYFRAME_ENTRY(nsBlockFrame)
528 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
530 #ifdef DEBUG_FRAME_DUMP
531 void nsBlockFrame::List(FILE* out, const char* aPrefix,
532 ListFlags aFlags) const {
533 nsCString str;
534 ListGeneric(str, aPrefix, aFlags);
536 fprintf_stderr(out, "%s <\n", str.get());
538 nsCString pfx(aPrefix);
539 pfx += " ";
541 // Output the lines
542 if (!mLines.empty()) {
543 ConstLineIterator line = LinesBegin(), line_end = LinesEnd();
544 for (; line != line_end; ++line) {
545 line->List(out, pfx.get(), aFlags);
549 // Output the overflow lines.
550 const FrameLines* overflowLines = GetOverflowLines();
551 if (overflowLines && !overflowLines->mLines.empty()) {
552 fprintf_stderr(out, "%sOverflow-lines %p/%p <\n", pfx.get(), overflowLines,
553 &overflowLines->mFrames);
554 nsCString nestedPfx(pfx);
555 nestedPfx += " ";
556 ConstLineIterator line = overflowLines->mLines.begin(),
557 line_end = overflowLines->mLines.end();
558 for (; line != line_end; ++line) {
559 line->List(out, nestedPfx.get(), aFlags);
561 fprintf_stderr(out, "%s>\n", pfx.get());
564 // skip the principal list - we printed the lines above
565 // skip the overflow list - we printed the overflow lines above
566 ChildListIDs skip = {FrameChildListID::Principal, FrameChildListID::Overflow};
567 ListChildLists(out, pfx.get(), aFlags, skip);
569 fprintf_stderr(out, "%s>\n", aPrefix);
572 nsresult nsBlockFrame::GetFrameName(nsAString& aResult) const {
573 return MakeFrameName(u"Block"_ns, aResult);
575 #endif
577 void nsBlockFrame::InvalidateFrame(uint32_t aDisplayItemKey,
578 bool aRebuildDisplayItems) {
579 if (SVGUtils::IsInSVGTextSubtree(this)) {
580 NS_ASSERTION(GetParent()->IsSVGTextFrame(),
581 "unexpected block frame in SVG text");
582 GetParent()->InvalidateFrame();
583 return;
585 nsContainerFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems);
588 void nsBlockFrame::InvalidateFrameWithRect(const nsRect& aRect,
589 uint32_t aDisplayItemKey,
590 bool aRebuildDisplayItems) {
591 if (SVGUtils::IsInSVGTextSubtree(this)) {
592 NS_ASSERTION(GetParent()->IsSVGTextFrame(),
593 "unexpected block frame in SVG text");
594 GetParent()->InvalidateFrame();
595 return;
597 nsContainerFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey,
598 aRebuildDisplayItems);
601 nscoord nsBlockFrame::GetLogicalBaseline(WritingMode aWM) const {
602 auto lastBaseline = BaselineBOffset(aWM, BaselineSharingGroup::Last,
603 AlignmentContext::Inline);
604 return BSize(aWM) - lastBaseline;
607 bool nsBlockFrame::GetNaturalBaselineBOffset(
608 mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup,
609 nscoord* aBaseline) const {
610 if (StyleDisplay()->IsContainLayout()) {
611 return false;
614 if (aBaselineGroup == BaselineSharingGroup::First) {
615 return nsLayoutUtils::GetFirstLineBaseline(aWM, this, aBaseline);
618 for (ConstReverseLineIterator line = LinesRBegin(), line_end = LinesREnd();
619 line != line_end; ++line) {
620 if (line->IsBlock()) {
621 nscoord offset;
622 nsIFrame* kid = line->mFirstChild;
623 if (!aWM.IsOrthogonalTo(kid->GetWritingMode()) &&
624 kid->GetVerticalAlignBaseline(aWM, &offset)) {
625 // Ignore relative positioning for baseline calculations.
626 const nsSize& sz = line->mContainerSize;
627 offset += kid->GetLogicalNormalPosition(aWM, sz).B(aWM);
628 *aBaseline = BSize(aWM) - offset;
629 return true;
631 } else {
632 // XXX Is this the right test? We have some bogus empty lines
633 // floating around, but IsEmpty is perhaps too weak.
634 if (line->BSize() != 0 || !line->IsEmpty()) {
635 *aBaseline = BSize(aWM) - (line->BStart() + line->GetLogicalAscent());
636 return true;
640 return false;
643 nscoord nsBlockFrame::GetCaretBaseline() const {
644 nsRect contentRect = GetContentRect();
645 nsMargin bp = GetUsedBorderAndPadding();
647 if (!mLines.empty()) {
648 ConstLineIterator line = LinesBegin();
649 if (!line->IsEmpty()) {
650 if (line->IsBlock()) {
651 return bp.top + line->mFirstChild->GetCaretBaseline();
653 return line->BStart() + line->GetLogicalAscent();
657 float inflation = nsLayoutUtils::FontSizeInflationFor(this);
658 RefPtr<nsFontMetrics> fm =
659 nsLayoutUtils::GetFontMetricsForFrame(this, inflation);
660 nscoord lineHeight = ReflowInput::CalcLineHeight(
661 GetContent(), Style(), PresContext(), contentRect.height, inflation);
662 const WritingMode wm = GetWritingMode();
663 return nsLayoutUtils::GetCenteredFontBaseline(fm, lineHeight,
664 wm.IsLineInverted()) +
665 bp.top;
668 /////////////////////////////////////////////////////////////////////////////
669 // Child frame enumeration
671 const nsFrameList& nsBlockFrame::GetChildList(ChildListID aListID) const {
672 switch (aListID) {
673 case FrameChildListID::Principal:
674 return mFrames;
675 case FrameChildListID::Overflow: {
676 FrameLines* overflowLines = GetOverflowLines();
677 return overflowLines ? overflowLines->mFrames : nsFrameList::EmptyList();
679 case FrameChildListID::Float:
680 return mFloats;
681 case FrameChildListID::OverflowOutOfFlow: {
682 const nsFrameList* list = GetOverflowOutOfFlows();
683 return list ? *list : nsFrameList::EmptyList();
685 case FrameChildListID::PushedFloats: {
686 const nsFrameList* list = GetPushedFloats();
687 return list ? *list : nsFrameList::EmptyList();
689 case FrameChildListID::Bullet: {
690 const nsFrameList* list = GetOutsideMarkerList();
691 return list ? *list : nsFrameList::EmptyList();
693 default:
694 return nsContainerFrame::GetChildList(aListID);
698 void nsBlockFrame::GetChildLists(nsTArray<ChildList>* aLists) const {
699 nsContainerFrame::GetChildLists(aLists);
700 FrameLines* overflowLines = GetOverflowLines();
701 if (overflowLines) {
702 overflowLines->mFrames.AppendIfNonempty(aLists, FrameChildListID::Overflow);
704 const nsFrameList* list = GetOverflowOutOfFlows();
705 if (list) {
706 list->AppendIfNonempty(aLists, FrameChildListID::OverflowOutOfFlow);
708 mFloats.AppendIfNonempty(aLists, FrameChildListID::Float);
709 list = GetOutsideMarkerList();
710 if (list) {
711 list->AppendIfNonempty(aLists, FrameChildListID::Bullet);
713 list = GetPushedFloats();
714 if (list) {
715 list->AppendIfNonempty(aLists, FrameChildListID::PushedFloats);
719 /* virtual */
720 bool nsBlockFrame::IsFloatContainingBlock() const { return true; }
723 * Remove the first line from aFromLines and adjust the associated frame list
724 * aFromFrames accordingly. The removed line is assigned to *aOutLine and
725 * a frame list with its frames is assigned to *aOutFrames, i.e. the frames
726 * that were extracted from the head of aFromFrames.
727 * aFromLines must contain at least one line, the line may be empty.
728 * @return true if aFromLines becomes empty
730 static bool RemoveFirstLine(nsLineList& aFromLines, nsFrameList& aFromFrames,
731 nsLineBox** aOutLine, nsFrameList* aOutFrames) {
732 nsLineList_iterator removedLine = aFromLines.begin();
733 *aOutLine = removedLine;
734 nsLineList_iterator next = aFromLines.erase(removedLine);
735 bool isLastLine = next == aFromLines.end();
736 nsIFrame* firstFrameInNextLine = isLastLine ? nullptr : next->mFirstChild;
737 *aOutFrames = aFromFrames.TakeFramesBefore(firstFrameInNextLine);
738 return isLastLine;
741 //////////////////////////////////////////////////////////////////////
742 // Reflow methods
744 /* virtual */
745 void nsBlockFrame::MarkIntrinsicISizesDirty() {
746 nsBlockFrame* dirtyBlock = static_cast<nsBlockFrame*>(FirstContinuation());
747 dirtyBlock->mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
748 dirtyBlock->mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN;
749 if (!HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION)) {
750 for (nsIFrame* frame = dirtyBlock; frame;
751 frame = frame->GetNextContinuation()) {
752 frame->AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
756 nsContainerFrame::MarkIntrinsicISizesDirty();
759 void nsBlockFrame::CheckIntrinsicCacheAgainstShrinkWrapState() {
760 nsPresContext* presContext = PresContext();
761 if (!nsLayoutUtils::FontSizeInflationEnabled(presContext)) {
762 return;
764 bool inflationEnabled = !presContext->mInflationDisabledForShrinkWrap;
765 if (inflationEnabled != HasAnyStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED)) {
766 mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
767 mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN;
768 if (inflationEnabled) {
769 AddStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED);
770 } else {
771 RemoveStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED);
776 /* virtual */
777 nscoord nsBlockFrame::GetMinISize(gfxContext* aRenderingContext) {
778 nsIFrame* firstInFlow = FirstContinuation();
779 if (firstInFlow != this) {
780 return firstInFlow->GetMinISize(aRenderingContext);
783 DISPLAY_MIN_INLINE_SIZE(this, mCachedMinISize);
785 CheckIntrinsicCacheAgainstShrinkWrapState();
787 if (mCachedMinISize != NS_INTRINSIC_ISIZE_UNKNOWN) {
788 return mCachedMinISize;
791 if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
792 mCachedMinISize = *containISize;
793 return mCachedMinISize;
796 #ifdef DEBUG
797 if (gNoisyIntrinsic) {
798 IndentBy(stdout, gNoiseIndent);
799 ListTag(stdout);
800 printf(": GetMinISize\n");
802 AutoNoisyIndenter indenter(gNoisyIntrinsic);
803 #endif
805 for (nsBlockFrame* curFrame = this; curFrame;
806 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
807 curFrame->LazyMarkLinesDirty();
810 if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
811 PresContext()->BidiEnabled()) {
812 ResolveBidi();
815 const bool whiteSpaceCanWrap = StyleText()->WhiteSpaceCanWrapStyle();
816 InlineMinISizeData data;
817 for (nsBlockFrame* curFrame = this; curFrame;
818 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
819 for (LineIterator line = curFrame->LinesBegin(),
820 line_end = curFrame->LinesEnd();
821 line != line_end; ++line) {
822 #ifdef DEBUG
823 if (gNoisyIntrinsic) {
824 IndentBy(stdout, gNoiseIndent);
825 printf("line (%s%s)\n", line->IsBlock() ? "block" : "inline",
826 line->IsEmpty() ? ", empty" : "");
828 AutoNoisyIndenter lineindent(gNoisyIntrinsic);
829 #endif
830 if (line->IsBlock()) {
831 data.ForceBreak();
832 data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(
833 aRenderingContext, line->mFirstChild, IntrinsicISizeType::MinISize);
834 data.ForceBreak();
835 } else {
836 if (!curFrame->GetPrevContinuation() &&
837 line == curFrame->LinesBegin()) {
838 data.mCurrentLine += StyleText()->mTextIndent.Resolve(0);
840 data.mLine = &line;
841 data.SetLineContainer(curFrame);
842 nsIFrame* kid = line->mFirstChild;
843 for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end;
844 ++i, kid = kid->GetNextSibling()) {
845 kid->AddInlineMinISize(aRenderingContext, &data);
846 if (whiteSpaceCanWrap && data.mTrailingWhitespace) {
847 data.OptionallyBreak();
851 #ifdef DEBUG
852 if (gNoisyIntrinsic) {
853 IndentBy(stdout, gNoiseIndent);
854 printf("min: [prevLines=%d currentLine=%d]\n", data.mPrevLines,
855 data.mCurrentLine);
857 #endif
860 data.ForceBreak();
862 mCachedMinISize = data.mPrevLines;
863 return mCachedMinISize;
866 /* virtual */
867 nscoord nsBlockFrame::GetPrefISize(gfxContext* aRenderingContext) {
868 nsIFrame* firstInFlow = FirstContinuation();
869 if (firstInFlow != this) {
870 return firstInFlow->GetPrefISize(aRenderingContext);
873 DISPLAY_PREF_INLINE_SIZE(this, mCachedPrefISize);
875 CheckIntrinsicCacheAgainstShrinkWrapState();
877 if (mCachedPrefISize != NS_INTRINSIC_ISIZE_UNKNOWN) {
878 return mCachedPrefISize;
881 if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
882 mCachedPrefISize = *containISize;
883 return mCachedPrefISize;
886 #ifdef DEBUG
887 if (gNoisyIntrinsic) {
888 IndentBy(stdout, gNoiseIndent);
889 ListTag(stdout);
890 printf(": GetPrefISize\n");
892 AutoNoisyIndenter indenter(gNoisyIntrinsic);
893 #endif
895 for (nsBlockFrame* curFrame = this; curFrame;
896 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
897 curFrame->LazyMarkLinesDirty();
900 if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
901 PresContext()->BidiEnabled()) {
902 ResolveBidi();
904 InlinePrefISizeData data;
905 for (nsBlockFrame* curFrame = this; curFrame;
906 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
907 for (LineIterator line = curFrame->LinesBegin(),
908 line_end = curFrame->LinesEnd();
909 line != line_end; ++line) {
910 #ifdef DEBUG
911 if (gNoisyIntrinsic) {
912 IndentBy(stdout, gNoiseIndent);
913 printf("line (%s%s)\n", line->IsBlock() ? "block" : "inline",
914 line->IsEmpty() ? ", empty" : "");
916 AutoNoisyIndenter lineindent(gNoisyIntrinsic);
917 #endif
918 if (line->IsBlock()) {
919 StyleClear clearType;
920 if (!data.mLineIsEmpty || BlockCanIntersectFloats(line->mFirstChild)) {
921 clearType = StyleClear::Both;
922 } else {
923 clearType = line->mFirstChild->StyleDisplay()->mClear;
925 data.ForceBreak(clearType);
926 data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(
927 aRenderingContext, line->mFirstChild,
928 IntrinsicISizeType::PrefISize);
929 data.ForceBreak();
930 } else {
931 if (!curFrame->GetPrevContinuation() &&
932 line == curFrame->LinesBegin()) {
933 nscoord indent = StyleText()->mTextIndent.Resolve(0);
934 data.mCurrentLine += indent;
935 // XXXmats should the test below be indent > 0?
936 if (indent != nscoord(0)) {
937 data.mLineIsEmpty = false;
940 data.mLine = &line;
941 data.SetLineContainer(curFrame);
942 nsIFrame* kid = line->mFirstChild;
943 for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end;
944 ++i, kid = kid->GetNextSibling()) {
945 kid->AddInlinePrefISize(aRenderingContext, &data);
948 #ifdef DEBUG
949 if (gNoisyIntrinsic) {
950 IndentBy(stdout, gNoiseIndent);
951 printf("pref: [prevLines=%d currentLine=%d]\n", data.mPrevLines,
952 data.mCurrentLine);
954 #endif
957 data.ForceBreak();
959 mCachedPrefISize = data.mPrevLines;
960 return mCachedPrefISize;
963 nsRect nsBlockFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const {
964 // be conservative
965 if (Style()->HasTextDecorationLines()) {
966 return InkOverflowRect();
968 return ComputeSimpleTightBounds(aDrawTarget);
971 /* virtual */
972 nsresult nsBlockFrame::GetPrefWidthTightBounds(gfxContext* aRenderingContext,
973 nscoord* aX, nscoord* aXMost) {
974 nsIFrame* firstInFlow = FirstContinuation();
975 if (firstInFlow != this) {
976 return firstInFlow->GetPrefWidthTightBounds(aRenderingContext, aX, aXMost);
979 *aX = 0;
980 *aXMost = 0;
982 nsresult rv;
983 InlinePrefISizeData data;
984 for (nsBlockFrame* curFrame = this; curFrame;
985 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
986 for (LineIterator line = curFrame->LinesBegin(),
987 line_end = curFrame->LinesEnd();
988 line != line_end; ++line) {
989 nscoord childX, childXMost;
990 if (line->IsBlock()) {
991 data.ForceBreak();
992 rv = line->mFirstChild->GetPrefWidthTightBounds(aRenderingContext,
993 &childX, &childXMost);
994 NS_ENSURE_SUCCESS(rv, rv);
995 *aX = std::min(*aX, childX);
996 *aXMost = std::max(*aXMost, childXMost);
997 } else {
998 if (!curFrame->GetPrevContinuation() &&
999 line == curFrame->LinesBegin()) {
1000 data.mCurrentLine += StyleText()->mTextIndent.Resolve(0);
1002 data.mLine = &line;
1003 data.SetLineContainer(curFrame);
1004 nsIFrame* kid = line->mFirstChild;
1005 for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end;
1006 ++i, kid = kid->GetNextSibling()) {
1007 rv = kid->GetPrefWidthTightBounds(aRenderingContext, &childX,
1008 &childXMost);
1009 NS_ENSURE_SUCCESS(rv, rv);
1010 *aX = std::min(*aX, data.mCurrentLine + childX);
1011 *aXMost = std::max(*aXMost, data.mCurrentLine + childXMost);
1012 kid->AddInlinePrefISize(aRenderingContext, &data);
1017 data.ForceBreak();
1019 return NS_OK;
1023 * Return whether aNewAvailableSpace is smaller *on either side*
1024 * (inline-start or inline-end) than aOldAvailableSpace, so that we know
1025 * if we need to redo layout on an line, replaced block, or block
1026 * formatting context, because its height (which we used to compute
1027 * aNewAvailableSpace) caused it to intersect additional floats.
1029 static bool AvailableSpaceShrunk(WritingMode aWM,
1030 const LogicalRect& aOldAvailableSpace,
1031 const LogicalRect& aNewAvailableSpace,
1032 bool aCanGrow /* debug-only */) {
1033 if (aNewAvailableSpace.ISize(aWM) == 0) {
1034 // Positions are not significant if the inline size is zero.
1035 return aOldAvailableSpace.ISize(aWM) != 0;
1037 if (aCanGrow) {
1038 NS_ASSERTION(
1039 aNewAvailableSpace.IStart(aWM) <= aOldAvailableSpace.IStart(aWM) ||
1040 aNewAvailableSpace.IEnd(aWM) <= aOldAvailableSpace.IEnd(aWM),
1041 "available space should not shrink on the start side and "
1042 "grow on the end side");
1043 NS_ASSERTION(
1044 aNewAvailableSpace.IStart(aWM) >= aOldAvailableSpace.IStart(aWM) ||
1045 aNewAvailableSpace.IEnd(aWM) >= aOldAvailableSpace.IEnd(aWM),
1046 "available space should not grow on the start side and "
1047 "shrink on the end side");
1048 } else {
1049 NS_ASSERTION(
1050 aOldAvailableSpace.IStart(aWM) <= aNewAvailableSpace.IStart(aWM) &&
1051 aOldAvailableSpace.IEnd(aWM) >= aNewAvailableSpace.IEnd(aWM),
1052 "available space should never grow");
1054 // Have we shrunk on either side?
1055 return aNewAvailableSpace.IStart(aWM) > aOldAvailableSpace.IStart(aWM) ||
1056 aNewAvailableSpace.IEnd(aWM) < aOldAvailableSpace.IEnd(aWM);
1059 static LogicalSize CalculateContainingBlockSizeForAbsolutes(
1060 WritingMode aWM, const ReflowInput& aReflowInput, LogicalSize aFrameSize) {
1061 // The issue here is that for a 'height' of 'auto' the reflow input
1062 // code won't know how to calculate the containing block height
1063 // because it's calculated bottom up. So we use our own computed
1064 // size as the dimensions.
1065 nsIFrame* frame = aReflowInput.mFrame;
1067 LogicalSize cbSize(aFrameSize);
1068 // Containing block is relative to the padding edge
1069 const LogicalMargin border = aReflowInput.ComputedLogicalBorder(aWM);
1070 cbSize.ISize(aWM) -= border.IStartEnd(aWM);
1071 cbSize.BSize(aWM) -= border.BStartEnd(aWM);
1073 if (frame->GetParent()->GetContent() != frame->GetContent() ||
1074 frame->GetParent()->IsCanvasFrame()) {
1075 return cbSize;
1078 // We are a wrapped frame for the content (and the wrapper is not the
1079 // canvas frame, whose size is not meaningful here).
1080 // Use the container's dimensions, if they have been precomputed.
1081 // XXX This is a hack! We really should be waiting until the outermost
1082 // frame is fully reflowed and using the resulting dimensions, even
1083 // if they're intrinsic.
1084 // In fact we should be attaching absolute children to the outermost
1085 // frame and not always sticking them in block frames.
1087 // First, find the reflow input for the outermost frame for this content.
1088 const ReflowInput* lastRI = &aReflowInput;
1089 DebugOnly<const ReflowInput*> lastButOneRI = &aReflowInput;
1090 while (lastRI->mParentReflowInput &&
1091 lastRI->mParentReflowInput->mFrame->GetContent() ==
1092 frame->GetContent()) {
1093 lastButOneRI = lastRI;
1094 lastRI = lastRI->mParentReflowInput;
1097 if (lastRI == &aReflowInput) {
1098 return cbSize;
1101 // For scroll containers, we can just use cbSize (which is the padding-box
1102 // size of the scrolled-content frame).
1103 if (nsIScrollableFrame* scrollFrame = do_QueryFrame(lastRI->mFrame)) {
1104 // Assert that we're not missing any frames between the abspos containing
1105 // block and the scroll container.
1106 // the parent.
1107 Unused << scrollFrame;
1108 MOZ_ASSERT(lastButOneRI == &aReflowInput);
1109 return cbSize;
1112 // Same for fieldsets, where the inner anonymous frame has the correct padding
1113 // area with the legend taken into account.
1114 if (lastRI->mFrame->IsFieldSetFrame()) {
1115 return cbSize;
1118 // We found a reflow input for the outermost wrapping frame, so use
1119 // its computed metrics if available, converted to our writing mode
1120 const LogicalSize lastRISize = lastRI->ComputedSize(aWM);
1121 const LogicalMargin lastRIPadding = lastRI->ComputedLogicalPadding(aWM);
1122 if (lastRISize.ISize(aWM) != NS_UNCONSTRAINEDSIZE) {
1123 cbSize.ISize(aWM) =
1124 std::max(0, lastRISize.ISize(aWM) + lastRIPadding.IStartEnd(aWM));
1126 if (lastRISize.BSize(aWM) != NS_UNCONSTRAINEDSIZE) {
1127 cbSize.BSize(aWM) =
1128 std::max(0, lastRISize.BSize(aWM) + lastRIPadding.BStartEnd(aWM));
1131 return cbSize;
1135 * Returns aFrame if it is a non-BFC block frame, and null otherwise.
1137 * This is used to determine whether to recurse into aFrame when applying
1138 * -webkit-line-clamp.
1140 static const nsBlockFrame* GetAsLineClampDescendant(const nsIFrame* aFrame) {
1141 if (const nsBlockFrame* block = do_QueryFrame(aFrame)) {
1142 if (!block->HasAllStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS)) {
1143 return block;
1146 return nullptr;
1149 static nsBlockFrame* GetAsLineClampDescendant(nsIFrame* aFrame) {
1150 return const_cast<nsBlockFrame*>(
1151 GetAsLineClampDescendant(const_cast<const nsIFrame*>(aFrame)));
1154 static bool IsLineClampRoot(const nsBlockFrame* aFrame) {
1155 if (!aFrame->StyleDisplay()->mWebkitLineClamp) {
1156 return false;
1159 if (!aFrame->HasAllStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS)) {
1160 return false;
1163 if (StaticPrefs::layout_css_webkit_line_clamp_block_enabled()) {
1164 return true;
1167 // For now, -webkit-box is the only thing allowed to be a line-clamp root.
1168 // Ideally we'd just make this work everywhere, but for now we're carrying
1169 // this forward as a limitation on the legacy -webkit-line-clamp feature,
1170 // since relaxing this limitation might create webcompat trouble.
1171 auto origDisplay = [&] {
1172 if (aFrame->Style()->GetPseudoType() == PseudoStyleType::scrolledContent) {
1173 // If we're the anonymous block inside the scroll frame, we need to look
1174 // at the original display of our parent frame.
1175 MOZ_ASSERT(aFrame->GetParent());
1176 const auto& parentDisp = *aFrame->GetParent()->StyleDisplay();
1177 MOZ_ASSERT(parentDisp.mWebkitLineClamp ==
1178 aFrame->StyleDisplay()->mWebkitLineClamp,
1179 ":-moz-scrolled-content should inherit -webkit-line-clamp, "
1180 "via rule in UA stylesheet");
1181 return parentDisp.mOriginalDisplay;
1183 return aFrame->StyleDisplay()->mOriginalDisplay;
1184 }();
1185 return nsStyleDisplay::DisplayInside(origDisplay) ==
1186 StyleDisplayInside::WebkitBox;
1189 bool nsBlockFrame::IsInLineClampContext() const {
1190 if (IsLineClampRoot(this)) {
1191 return true;
1193 const nsBlockFrame* cur = this;
1194 while (GetAsLineClampDescendant(cur)) {
1195 cur = do_QueryFrame(cur->GetParent());
1196 if (!cur) {
1197 return false;
1199 if (IsLineClampRoot(cur)) {
1200 return true;
1203 return false;
1207 * Iterator over all descendant inline line boxes, except for those that are
1208 * under an independent formatting context.
1210 class MOZ_RAII LineClampLineIterator {
1211 public:
1212 explicit LineClampLineIterator(nsBlockFrame* aFrame)
1213 : mCur(aFrame->LinesBegin()),
1214 mEnd(aFrame->LinesEnd()),
1215 mCurrentFrame(mCur == mEnd ? nullptr : aFrame) {
1216 if (mCur != mEnd && !mCur->IsInline()) {
1217 Advance();
1221 nsLineBox* GetCurrentLine() { return mCurrentFrame ? mCur.get() : nullptr; }
1222 nsBlockFrame* GetCurrentFrame() { return mCurrentFrame; }
1224 // Advances the iterator to the next line line.
1226 // Next() shouldn't be called once the iterator is at the end, which can be
1227 // checked for by GetCurrentLine() or GetCurrentFrame() returning null.
1228 void Next() {
1229 MOZ_ASSERT(mCur != mEnd && mCurrentFrame,
1230 "Don't call Next() when the iterator is at the end");
1231 ++mCur;
1232 Advance();
1235 private:
1236 void Advance() {
1237 for (;;) {
1238 if (mCur == mEnd) {
1239 // Reached the end of the current block. Pop the parent off the
1240 // stack; if there isn't one, then we've reached the end.
1241 if (mStack.IsEmpty()) {
1242 mCurrentFrame = nullptr;
1243 break;
1245 auto entry = mStack.PopLastElement();
1246 mCurrentFrame = entry.first;
1247 mCur = entry.second;
1248 mEnd = mCurrentFrame->LinesEnd();
1249 } else if (mCur->IsBlock()) {
1250 if (nsBlockFrame* child = GetAsLineClampDescendant(mCur->mFirstChild)) {
1251 nsBlockFrame::LineIterator next = mCur;
1252 ++next;
1253 mStack.AppendElement(std::make_pair(mCurrentFrame, next));
1254 mCur = child->LinesBegin();
1255 mEnd = child->LinesEnd();
1256 mCurrentFrame = child;
1257 } else {
1258 // Some kind of frame we shouldn't descend into.
1259 ++mCur;
1261 } else {
1262 MOZ_ASSERT(mCur->IsInline());
1263 break;
1268 // The current line within the current block.
1270 // When this is equal to mEnd, the iterator is at its end, and mCurrentFrame
1271 // is set to null.
1272 nsBlockFrame::LineIterator mCur;
1274 // The iterator end for the current block.
1275 nsBlockFrame::LineIterator mEnd;
1277 // The current block.
1278 nsBlockFrame* mCurrentFrame;
1280 // Stack of mCurrentFrame and mEnd values that we push and pop as we enter and
1281 // exist blocks.
1282 AutoTArray<std::pair<nsBlockFrame*, nsBlockFrame::LineIterator>, 8> mStack;
1285 static bool ClearLineClampEllipsis(nsBlockFrame* aFrame) {
1286 if (!aFrame->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS)) {
1287 for (nsIFrame* f : aFrame->PrincipalChildList()) {
1288 if (nsBlockFrame* child = GetAsLineClampDescendant(f)) {
1289 if (ClearLineClampEllipsis(child)) {
1290 return true;
1294 return false;
1297 aFrame->RemoveStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS);
1299 for (auto& line : aFrame->Lines()) {
1300 if (line.HasLineClampEllipsis()) {
1301 line.ClearHasLineClampEllipsis();
1302 return true;
1306 // We didn't find a line with the ellipsis; it must have been deleted already.
1307 return true;
1310 void nsBlockFrame::ClearLineClampEllipsis() { ::ClearLineClampEllipsis(this); }
1312 void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
1313 const ReflowInput& aReflowInput,
1314 nsReflowStatus& aStatus) {
1315 if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
1316 FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
1317 return;
1320 MarkInReflow();
1321 DO_GLOBAL_REFLOW_COUNT("nsBlockFrame");
1322 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
1323 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
1325 #ifdef DEBUG
1326 if (gNoisyReflow) {
1327 IndentBy(stdout, gNoiseIndent);
1328 ListTag(stdout);
1329 printf(": begin reflow availSize=%d,%d computedSize=%d,%d\n",
1330 aReflowInput.AvailableISize(), aReflowInput.AvailableBSize(),
1331 aReflowInput.ComputedISize(), aReflowInput.ComputedBSize());
1333 AutoNoisyIndenter indent(gNoisy);
1334 PRTime start = 0; // Initialize these variablies to silence the compiler.
1335 int32_t ctc = 0; // We only use these if they are set (gLameReflowMetrics).
1336 if (gLameReflowMetrics) {
1337 start = PR_Now();
1338 ctc = nsLineBox::GetCtorCount();
1340 #endif
1342 // ColumnSetWrapper's children depend on ColumnSetWrapper's block-size or
1343 // max-block-size because both affect the children's available block-size.
1344 if (IsColumnSetWrapperFrame()) {
1345 AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
1348 const ReflowInput* reflowInput = &aReflowInput;
1349 WritingMode wm = aReflowInput.GetWritingMode();
1350 const nscoord consumedBSize = CalcAndCacheConsumedBSize();
1351 const nscoord effectiveContentBoxBSize =
1352 GetEffectiveComputedBSize(aReflowInput, consumedBSize);
1353 Maybe<ReflowInput> mutableReflowInput;
1354 // If we have non-auto block size, we're clipping our kids and we fit,
1355 // make sure our kids fit too.
1356 const PhysicalAxes physicalBlockAxis =
1357 wm.IsVertical() ? PhysicalAxes::Horizontal : PhysicalAxes::Vertical;
1358 if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
1359 aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE &&
1360 (ShouldApplyOverflowClipping(aReflowInput.mStyleDisplay) &
1361 physicalBlockAxis)) {
1362 LogicalMargin blockDirExtras =
1363 aReflowInput.ComputedLogicalBorderPadding(wm);
1364 if (GetLogicalSkipSides().BStart()) {
1365 blockDirExtras.BStart(wm) = 0;
1366 } else {
1367 // Block-end margin never causes us to create continuations, so we
1368 // don't need to worry about whether it fits in its entirety.
1369 blockDirExtras.BStart(wm) +=
1370 aReflowInput.ComputedLogicalMargin(wm).BStart(wm);
1373 if (effectiveContentBoxBSize + blockDirExtras.BStartEnd(wm) <=
1374 aReflowInput.AvailableBSize()) {
1375 mutableReflowInput.emplace(aReflowInput);
1376 mutableReflowInput->SetAvailableBSize(NS_UNCONSTRAINEDSIZE);
1377 reflowInput = mutableReflowInput.ptr();
1381 // See comment below about oldSize. Use *only* for the
1382 // abs-pos-containing-block-size-change optimization!
1383 nsSize oldSize = GetSize();
1385 // Should we create a float manager?
1386 nsAutoFloatManager autoFloatManager(const_cast<ReflowInput&>(*reflowInput));
1388 // XXXldb If we start storing the float manager in the frame rather
1389 // than keeping it around only during reflow then we should create it
1390 // only when there are actually floats to manage. Otherwise things
1391 // like tables will gain significant bloat.
1392 bool needFloatManager = nsBlockFrame::BlockNeedsFloatManager(this);
1393 if (needFloatManager) {
1394 autoFloatManager.CreateFloatManager(aPresContext);
1397 // OK, some lines may be reflowed. Blow away any saved line cursor
1398 // because we may invalidate the nondecreasing
1399 // overflowArea.InkOverflow().y/yMost invariant, and we may even
1400 // delete the line with the line cursor.
1401 ClearLineCursors();
1403 if (IsFrameTreeTooDeep(*reflowInput, aMetrics, aStatus)) {
1404 return;
1407 #ifdef DEBUG
1408 // Between when we drain pushed floats and when we complete reflow,
1409 // we're allowed to have multiple continuations of the same float on
1410 // our floats list, since a first-in-flow might get pushed to a later
1411 // continuation of its containing block. But it's not permitted
1412 // outside that time.
1413 nsLayoutUtils::AssertNoDuplicateContinuations(this, mFloats);
1414 #endif
1416 // ALWAYS drain overflow. We never want to leave the previnflow's
1417 // overflow lines hanging around; block reflow depends on the
1418 // overflow line lists being cleared out between reflow passes.
1419 DrainOverflowLines();
1421 bool blockStartMarginRoot, blockEndMarginRoot;
1422 IsMarginRoot(&blockStartMarginRoot, &blockEndMarginRoot);
1424 BlockReflowState state(*reflowInput, aPresContext, this, blockStartMarginRoot,
1425 blockEndMarginRoot, needFloatManager, consumedBSize,
1426 effectiveContentBoxBSize);
1428 if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
1429 PresContext()->BidiEnabled()) {
1430 static_cast<nsBlockFrame*>(FirstContinuation())->ResolveBidi();
1433 // Handle paginated overflow (see nsContainerFrame.h)
1434 OverflowAreas ocBounds;
1435 nsReflowStatus ocStatus;
1436 if (GetPrevInFlow()) {
1437 ReflowOverflowContainerChildren(
1438 aPresContext, *reflowInput, ocBounds, ReflowChildFlags::Default,
1439 ocStatus, DefaultChildFrameMerge, Some(state.ContainerSize()));
1442 // Now that we're done cleaning up our overflow container lists, we can
1443 // give |state| its nsOverflowContinuationTracker.
1444 nsOverflowContinuationTracker tracker(this, false);
1445 state.mOverflowTracker = &tracker;
1447 // Drain & handle pushed floats
1448 DrainPushedFloats();
1449 OverflowAreas fcBounds;
1450 ReflowPushedFloats(state, fcBounds);
1452 // If we're not dirty (which means we'll mark everything dirty later)
1453 // and our inline-size has changed, mark the lines dirty that we need to
1454 // mark dirty for a resize reflow.
1455 if (!HasAnyStateBits(NS_FRAME_IS_DIRTY) && reflowInput->IsIResize()) {
1456 PrepareResizeReflow(state);
1459 // The same for percentage text-indent, except conditioned on the
1460 // parent resizing.
1461 if (!HasAnyStateBits(NS_FRAME_IS_DIRTY) && reflowInput->mCBReflowInput &&
1462 reflowInput->mCBReflowInput->IsIResize() &&
1463 StyleText()->mTextIndent.HasPercent() && !mLines.empty()) {
1464 mLines.front()->MarkDirty();
1467 LazyMarkLinesDirty();
1469 // Now reflow...
1470 ReflowDirtyLines(state);
1472 // If we have a next-in-flow, and that next-in-flow has pushed floats from
1473 // this frame from a previous iteration of reflow, then we should not return
1474 // a status with IsFullyComplete() equals to true, since we actually have
1475 // overflow, it's just already been handled.
1477 // NOTE: This really shouldn't happen, since we _should_ pull back our floats
1478 // and reflow them, but just in case it does, this is a safety precaution so
1479 // we don't end up with a placeholder pointing to frames that have already
1480 // been deleted as part of removing our next-in-flow.
1481 if (state.mReflowStatus.IsFullyComplete()) {
1482 nsBlockFrame* nif = static_cast<nsBlockFrame*>(GetNextInFlow());
1483 while (nif) {
1484 if (nif->HasPushedFloatsFromPrevContinuation()) {
1485 if (nif->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
1486 state.mReflowStatus.SetOverflowIncomplete();
1487 } else {
1488 state.mReflowStatus.SetIncomplete();
1490 break;
1493 nif = static_cast<nsBlockFrame*>(nif->GetNextInFlow());
1497 state.mReflowStatus.MergeCompletionStatusFrom(ocStatus);
1499 // If we end in a BR with clear and affected floats continue,
1500 // we need to continue, too.
1501 if (NS_UNCONSTRAINEDSIZE != reflowInput->AvailableBSize() &&
1502 state.mReflowStatus.IsComplete() &&
1503 state.FloatManager()->ClearContinues(FindTrailingClear())) {
1504 state.mReflowStatus.SetIncomplete();
1507 if (!state.mReflowStatus.IsFullyComplete()) {
1508 if (HasOverflowLines() || HasPushedFloats()) {
1509 state.mReflowStatus.SetNextInFlowNeedsReflow();
1512 #ifdef DEBUG_kipp
1513 ListTag(stdout);
1514 printf(": block is not fully complete\n");
1515 #endif
1518 // Place the ::marker's frame if it is placed next to a block child.
1520 // According to the CSS2 spec, section 12.6.1, the ::marker's box
1521 // participates in the height calculation of the list-item box's
1522 // first line box.
1524 // There are exactly two places a ::marker can be placed: near the
1525 // first or second line. It's only placed on the second line in a
1526 // rare case: an empty first line followed by a second line that
1527 // contains a block (example: <LI>\n<P>... ). This is where
1528 // the second case can happen.
1529 if (HasOutsideMarker() && !mLines.empty() &&
1530 (mLines.front()->IsBlock() ||
1531 (0 == mLines.front()->BSize() && mLines.front() != mLines.back() &&
1532 mLines.begin().next()->IsBlock()))) {
1533 // Reflow the ::marker's frame.
1534 ReflowOutput reflowOutput(aReflowInput);
1535 // XXX Use the entire line when we fix bug 25888.
1536 nsLayoutUtils::LinePosition position;
1537 WritingMode wm = aReflowInput.GetWritingMode();
1538 bool havePosition =
1539 nsLayoutUtils::GetFirstLinePosition(wm, this, &position);
1540 nscoord lineBStart =
1541 havePosition ? position.mBStart
1542 : reflowInput->ComputedLogicalBorderPadding(wm).BStart(wm);
1543 nsIFrame* marker = GetOutsideMarker();
1544 ReflowOutsideMarker(marker, state, reflowOutput, lineBStart);
1545 NS_ASSERTION(!MarkerIsEmpty() || reflowOutput.BSize(wm) == 0,
1546 "empty ::marker frame took up space");
1548 if (havePosition && !MarkerIsEmpty()) {
1549 // We have some lines to align the ::marker with.
1551 // Doing the alignment using the baseline will also cater for
1552 // ::markers that are placed next to a child block (bug 92896)
1554 // Tall ::markers won't look particularly nice here...
1555 LogicalRect bbox =
1556 marker->GetLogicalRect(wm, reflowOutput.PhysicalSize());
1557 const auto baselineGroup = BaselineSharingGroup::First;
1558 nscoord markerBaseline;
1559 if (MOZ_UNLIKELY(wm.IsOrthogonalTo(marker->GetWritingMode()) ||
1560 !marker->GetNaturalBaselineBOffset(wm, baselineGroup,
1561 &markerBaseline))) {
1562 // ::marker has no baseline in this axis: align with its margin-box end.
1563 markerBaseline =
1564 bbox.BSize(wm) + marker->GetLogicalUsedMargin(wm).BEnd(wm);
1566 bbox.BStart(wm) = position.mBaseline - markerBaseline;
1567 marker->SetRect(wm, bbox, reflowOutput.PhysicalSize());
1569 // Otherwise just leave the ::marker where it is, up against our
1570 // block-start padding.
1573 // Clear any existing -webkit-line-clamp ellipsis.
1574 if (aReflowInput.mStyleDisplay->mWebkitLineClamp) {
1575 ClearLineClampEllipsis();
1578 CheckFloats(state);
1580 // Compute our final size
1581 nscoord blockEndEdgeOfChildren;
1582 ComputeFinalSize(*reflowInput, state, aMetrics, &blockEndEdgeOfChildren);
1584 // If the block direction is right-to-left, we need to update the bounds of
1585 // lines that were placed relative to mContainerSize during reflow, as
1586 // we typically do not know the true container size until we've reflowed all
1587 // its children. So we use a dummy mContainerSize during reflow (see
1588 // BlockReflowState's constructor) and then fix up the positions of the
1589 // lines here, once the final block size is known.
1591 // Note that writing-mode:vertical-rl is the only case where the block
1592 // logical direction progresses in a negative physical direction, and
1593 // therefore block-dir coordinate conversion depends on knowing the width
1594 // of the coordinate space in order to translate between the logical and
1595 // physical origins.
1596 if (wm.IsVerticalRL()) {
1597 nsSize containerSize = aMetrics.PhysicalSize();
1598 nscoord deltaX = containerSize.width - state.ContainerSize().width;
1599 if (deltaX != 0) {
1600 // We compute our lines and markers' overflow areas later in
1601 // ComputeOverflowAreas(), so we don't need to adjust their overflow areas
1602 // here.
1603 const nsPoint physicalDelta(deltaX, 0);
1604 for (auto& line : Lines()) {
1605 UpdateLineContainerSize(&line, containerSize);
1607 fcBounds.Clear();
1608 for (nsIFrame* f : mFloats) {
1609 f->MovePositionBy(physicalDelta);
1610 ConsiderChildOverflow(fcBounds, f);
1612 nsFrameList* markerList = GetOutsideMarkerList();
1613 if (markerList) {
1614 for (nsIFrame* f : *markerList) {
1615 f->MovePositionBy(physicalDelta);
1618 if (nsFrameList* overflowContainers = GetOverflowContainers()) {
1619 ocBounds.Clear();
1620 for (nsIFrame* f : *overflowContainers) {
1621 f->MovePositionBy(physicalDelta);
1622 ConsiderChildOverflow(ocBounds, f);
1628 aMetrics.SetOverflowAreasToDesiredBounds();
1629 ComputeOverflowAreas(aMetrics.mOverflowAreas, blockEndEdgeOfChildren,
1630 reflowInput->mStyleDisplay);
1631 // Factor overflow container child bounds into the overflow area
1632 aMetrics.mOverflowAreas.UnionWith(ocBounds);
1633 // Factor pushed float child bounds into the overflow area
1634 aMetrics.mOverflowAreas.UnionWith(fcBounds);
1636 // Let the absolutely positioned container reflow any absolutely positioned
1637 // child frames that need to be reflowed, e.g., elements with a percentage
1638 // based width/height
1639 // We want to do this under either of two conditions:
1640 // 1. If we didn't do the incremental reflow above.
1641 // 2. If our size changed.
1642 // Even though it's the padding edge that's the containing block, we
1643 // can use our rect (the border edge) since if the border style
1644 // changed, the reflow would have been targeted at us so we'd satisfy
1645 // condition 1.
1646 // XXX checking oldSize is bogus, there are various reasons we might have
1647 // reflowed but our size might not have been changed to what we
1648 // asked for (e.g., we ended up being pushed to a new page)
1649 // When WillReflowAgainForClearance is true, we will reflow again without
1650 // resetting the size. Because of this, we must not reflow our abs-pos
1651 // children in that situation --- what we think is our "new size" will not be
1652 // our real new size. This also happens to be more efficient.
1653 WritingMode parentWM = aMetrics.GetWritingMode();
1654 if (HasAbsolutelyPositionedChildren()) {
1655 nsAbsoluteContainingBlock* absoluteContainer = GetAbsoluteContainingBlock();
1656 bool haveInterrupt = aPresContext->HasPendingInterrupt();
1657 if (reflowInput->WillReflowAgainForClearance() || haveInterrupt) {
1658 // Make sure that when we reflow again we'll actually reflow all the abs
1659 // pos frames that might conceivably depend on our size (or all of them,
1660 // if we're dirty right now and interrupted; in that case we also need
1661 // to mark them all with NS_FRAME_IS_DIRTY). Sadly, we can't do much
1662 // better than that, because we don't really know what our size will be,
1663 // and it might in fact not change on the followup reflow!
1664 if (haveInterrupt && HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
1665 absoluteContainer->MarkAllFramesDirty();
1666 } else {
1667 absoluteContainer->MarkSizeDependentFramesDirty();
1669 if (haveInterrupt) {
1670 // We're not going to reflow absolute frames; make sure to account for
1671 // their existing overflow areas, which is usually a side effect of this
1672 // reflow.
1674 // TODO(emilio): nsAbsoluteContainingBlock::Reflow already checks for
1675 // interrupt, can we just rely on it and unconditionally take the else
1676 // branch below? That's a bit more subtle / risky, since I don't see
1677 // what would reflow them in that case if they depended on our size.
1678 for (nsIFrame* kid = absoluteContainer->GetChildList().FirstChild();
1679 kid; kid = kid->GetNextSibling()) {
1680 ConsiderChildOverflow(aMetrics.mOverflowAreas, kid);
1683 } else {
1684 LogicalSize containingBlockSize =
1685 CalculateContainingBlockSizeForAbsolutes(parentWM, *reflowInput,
1686 aMetrics.Size(parentWM));
1688 // Mark frames that depend on changes we just made to this frame as dirty:
1689 // Now we can assume that the padding edge hasn't moved.
1690 // We need to reflow the absolutes if one of them depends on
1691 // its placeholder position, or the containing block size in a
1692 // direction in which the containing block size might have
1693 // changed.
1695 // XXX "width" and "height" in this block will become ISize and BSize
1696 // when nsAbsoluteContainingBlock is logicalized
1697 bool cbWidthChanged = aMetrics.Width() != oldSize.width;
1698 bool isRoot = !GetContent()->GetParent();
1699 // If isRoot and we have auto height, then we are the initial
1700 // containing block and the containing block height is the
1701 // viewport height, which can't change during incremental
1702 // reflow.
1703 bool cbHeightChanged =
1704 !(isRoot && NS_UNCONSTRAINEDSIZE == reflowInput->ComputedHeight()) &&
1705 aMetrics.Height() != oldSize.height;
1707 nsRect containingBlock(nsPoint(0, 0),
1708 containingBlockSize.GetPhysicalSize(parentWM));
1709 AbsPosReflowFlags flags = AbsPosReflowFlags::ConstrainHeight;
1710 if (cbWidthChanged) {
1711 flags |= AbsPosReflowFlags::CBWidthChanged;
1713 if (cbHeightChanged) {
1714 flags |= AbsPosReflowFlags::CBHeightChanged;
1716 // Setup the line cursor here to optimize line searching for
1717 // calculating hypothetical position of absolutely-positioned
1718 // frames.
1719 SetupLineCursorForQuery();
1720 absoluteContainer->Reflow(this, aPresContext, *reflowInput,
1721 state.mReflowStatus, containingBlock, flags,
1722 &aMetrics.mOverflowAreas);
1726 FinishAndStoreOverflow(&aMetrics, reflowInput->mStyleDisplay);
1728 aStatus = state.mReflowStatus;
1730 #ifdef DEBUG
1731 // Between when we drain pushed floats and when we complete reflow,
1732 // we're allowed to have multiple continuations of the same float on
1733 // our floats list, since a first-in-flow might get pushed to a later
1734 // continuation of its containing block. But it's not permitted
1735 // outside that time.
1736 nsLayoutUtils::AssertNoDuplicateContinuations(this, mFloats);
1738 if (gNoisyReflow) {
1739 IndentBy(stdout, gNoiseIndent);
1740 ListTag(stdout);
1741 printf(": status=%s metrics=%d,%d carriedMargin=%d",
1742 ToString(aStatus).c_str(), aMetrics.ISize(parentWM),
1743 aMetrics.BSize(parentWM), aMetrics.mCarriedOutBEndMargin.get());
1744 if (HasOverflowAreas()) {
1745 printf(" overflow-vis={%d,%d,%d,%d}", aMetrics.InkOverflow().x,
1746 aMetrics.InkOverflow().y, aMetrics.InkOverflow().width,
1747 aMetrics.InkOverflow().height);
1748 printf(" overflow-scr={%d,%d,%d,%d}", aMetrics.ScrollableOverflow().x,
1749 aMetrics.ScrollableOverflow().y,
1750 aMetrics.ScrollableOverflow().width,
1751 aMetrics.ScrollableOverflow().height);
1753 printf("\n");
1756 if (gLameReflowMetrics) {
1757 PRTime end = PR_Now();
1759 int32_t ectc = nsLineBox::GetCtorCount();
1760 int32_t numLines = mLines.size();
1761 if (!numLines) {
1762 numLines = 1;
1764 PRTime delta, perLineDelta, lines;
1765 lines = int64_t(numLines);
1766 delta = end - start;
1767 perLineDelta = delta / lines;
1769 ListTag(stdout);
1770 char buf[400];
1771 SprintfLiteral(buf,
1772 ": %" PRId64 " elapsed (%" PRId64
1773 " per line) (%d lines; %d new lines)",
1774 delta, perLineDelta, numLines, ectc - ctc);
1775 printf("%s\n", buf);
1777 #endif
1780 bool nsBlockFrame::CheckForCollapsedBEndMarginFromClearanceLine() {
1781 for (auto& line : Reversed(Lines())) {
1782 if (0 != line.BSize() || !line.CachedIsEmpty()) {
1783 return false;
1785 if (line.HasClearance()) {
1786 return true;
1789 return false;
1792 static nsLineBox* FindLineClampTarget(nsBlockFrame*& aFrame,
1793 StyleLineClamp aLineNumber) {
1794 MOZ_ASSERT(aLineNumber > 0);
1795 MOZ_ASSERT(!aFrame->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS),
1796 "Should have been removed earlier in nsBlockReflow::Reflow");
1798 nsLineBox* target = nullptr;
1799 nsBlockFrame* targetFrame = nullptr;
1800 bool foundFollowingLine = false;
1802 LineClampLineIterator iter(aFrame);
1804 while (nsLineBox* line = iter.GetCurrentLine()) {
1805 MOZ_ASSERT(!line->HasLineClampEllipsis(),
1806 "Should have been removed earlier in nsBlockFrame::Reflow");
1807 MOZ_ASSERT(!iter.GetCurrentFrame()->HasAnyStateBits(
1808 NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS),
1809 "Should have been removed earlier in nsBlockReflow::Reflow");
1811 // Don't count a line that only has collapsible white space (as might exist
1812 // after calling e.g. getBoxQuads).
1813 if (line->IsEmpty()) {
1814 iter.Next();
1815 continue;
1818 if (aLineNumber == 0) {
1819 // We already previously found our target line, and now we have
1820 // confirmed that there is another line after it.
1821 foundFollowingLine = true;
1822 break;
1825 if (--aLineNumber == 0) {
1826 // This is our target line. Continue looping to confirm that we
1827 // have another line after us.
1828 target = line;
1829 targetFrame = iter.GetCurrentFrame();
1832 iter.Next();
1835 if (!foundFollowingLine) {
1836 aFrame = nullptr;
1837 return nullptr;
1840 MOZ_ASSERT(target);
1841 MOZ_ASSERT(targetFrame);
1843 aFrame = targetFrame;
1844 return target;
1847 static nscoord ApplyLineClamp(const ReflowInput& aReflowInput,
1848 nsBlockFrame* aFrame,
1849 nscoord aContentBlockEndEdge) {
1850 if (!IsLineClampRoot(aFrame)) {
1851 return aContentBlockEndEdge;
1853 auto lineClamp = aReflowInput.mStyleDisplay->mWebkitLineClamp;
1854 nsBlockFrame* frame = aFrame;
1855 nsLineBox* line = FindLineClampTarget(frame, lineClamp);
1856 if (!line) {
1857 // The number of lines did not exceed the -webkit-line-clamp value.
1858 return aContentBlockEndEdge;
1861 // Mark the line as having an ellipsis so that TextOverflow will render it.
1862 line->SetHasLineClampEllipsis();
1863 frame->AddStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS);
1865 // Translate the b-end edge of the line up to aFrame's space.
1866 nscoord edge = line->BEnd();
1867 for (nsIFrame* f = frame; f != aFrame; f = f->GetParent()) {
1868 edge +=
1869 f->GetLogicalPosition(f->GetParent()->GetSize()).B(f->GetWritingMode());
1872 return edge;
1875 void nsBlockFrame::ComputeFinalSize(const ReflowInput& aReflowInput,
1876 BlockReflowState& aState,
1877 ReflowOutput& aMetrics,
1878 nscoord* aBEndEdgeOfChildren) {
1879 WritingMode wm = aState.mReflowInput.GetWritingMode();
1880 const LogicalMargin& borderPadding = aState.BorderPadding();
1881 #ifdef NOISY_FINAL_SIZE
1882 ListTag(stdout);
1883 printf(": mBCoord=%d mIsBEndMarginRoot=%s mPrevBEndMargin=%d bp=%d,%d\n",
1884 aState.mBCoord, aState.mFlags.mIsBEndMarginRoot ? "yes" : "no",
1885 aState.mPrevBEndMargin.get(), borderPadding.BStart(wm),
1886 borderPadding.BEnd(wm));
1887 #endif
1889 // Compute final inline size
1890 LogicalSize finalSize(wm);
1891 finalSize.ISize(wm) =
1892 NSCoordSaturatingAdd(NSCoordSaturatingAdd(borderPadding.IStart(wm),
1893 aReflowInput.ComputedISize()),
1894 borderPadding.IEnd(wm));
1896 // Return block-end margin information
1897 // rbs says he hit this assertion occasionally (see bug 86947), so
1898 // just set the margin to zero and we'll figure out why later
1899 // NS_ASSERTION(aMetrics.mCarriedOutBEndMargin.IsZero(),
1900 // "someone else set the margin");
1901 nscoord nonCarriedOutBDirMargin = 0;
1902 if (!aState.mFlags.mIsBEndMarginRoot) {
1903 // Apply rule from CSS 2.1 section 8.3.1. If we have some empty
1904 // line with clearance and a non-zero block-start margin and all
1905 // subsequent lines are empty, then we do not allow our children's
1906 // carried out block-end margin to be carried out of us and collapse
1907 // with our own block-end margin.
1908 if (CheckForCollapsedBEndMarginFromClearanceLine()) {
1909 // Convert the children's carried out margin to something that
1910 // we will include in our height
1911 nonCarriedOutBDirMargin = aState.mPrevBEndMargin.get();
1912 aState.mPrevBEndMargin.Zero();
1914 aMetrics.mCarriedOutBEndMargin = aState.mPrevBEndMargin;
1915 } else {
1916 aMetrics.mCarriedOutBEndMargin.Zero();
1919 nscoord blockEndEdgeOfChildren = aState.mBCoord + nonCarriedOutBDirMargin;
1920 // Shrink wrap our height around our contents.
1921 if (aState.mFlags.mIsBEndMarginRoot ||
1922 NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) {
1923 // When we are a block-end-margin root make sure that our last
1924 // child's block-end margin is fully applied. We also do this when
1925 // we have a computed height, since in that case the carried out
1926 // margin is not going to be applied anywhere, so we should note it
1927 // here to be included in the overflow area.
1928 // Apply the margin only if there's space for it.
1929 if (blockEndEdgeOfChildren < aState.mReflowInput.AvailableBSize()) {
1930 // Truncate block-end margin if it doesn't fit to our available BSize.
1931 blockEndEdgeOfChildren =
1932 std::min(blockEndEdgeOfChildren + aState.mPrevBEndMargin.get(),
1933 aState.mReflowInput.AvailableBSize());
1936 if (aState.mFlags.mBlockNeedsFloatManager) {
1937 // Include the float manager's state to properly account for the
1938 // block-end margin of any floated elements; e.g., inside a table cell.
1940 // Note: The block coordinate returned by ClearFloats is always greater than
1941 // or equal to blockEndEdgeOfChildren.
1942 std::tie(blockEndEdgeOfChildren, std::ignore) =
1943 aState.ClearFloats(blockEndEdgeOfChildren, StyleClear::Both);
1946 if (NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) {
1947 // Note: We don't use blockEndEdgeOfChildren because it includes the
1948 // previous margin.
1949 const nscoord contentBSizeWithBStartBP =
1950 aState.mBCoord + nonCarriedOutBDirMargin;
1952 // We don't care about ApplyLineClamp's return value (the line-clamped
1953 // content BSize) in this explicit-BSize codepath, but we do still need to
1954 // call ApplyLineClamp for ellipsis markers to be placed as-needed.
1955 ApplyLineClamp(aState.mReflowInput, this, contentBSizeWithBStartBP);
1957 finalSize.BSize(wm) = ComputeFinalBSize(aState, contentBSizeWithBStartBP);
1959 // If the content block-size is larger than the effective computed
1960 // block-size, we extend the block-size to contain all the content.
1961 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
1962 if (aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis()) {
1963 // Note: finalSize.BSize(wm) is the border-box size, so we compare it with
1964 // the content's block-size plus our border and padding..
1965 finalSize.BSize(wm) =
1966 std::max(finalSize.BSize(wm),
1967 contentBSizeWithBStartBP + borderPadding.BEnd(wm));
1970 // Don't carry out a block-end margin when our BSize is fixed.
1972 // Note: this also includes the case that aReflowInput.ComputedBSize() is
1973 // calculated from aspect-ratio. i.e. Don't carry out block margin-end if it
1974 // is replaced by the block size from aspect-ratio and inline size.
1975 aMetrics.mCarriedOutBEndMargin.Zero();
1976 } else {
1977 Maybe<nscoord> containBSize = ContainIntrinsicBSize(
1978 IsComboboxControlFrame() ? NS_UNCONSTRAINEDSIZE : 0);
1979 if (containBSize && *containBSize != NS_UNCONSTRAINEDSIZE) {
1980 // If we're size-containing in block axis and we don't have a specified
1981 // block size, then our final size should actually be computed from only
1982 // our border, padding and contain-intrinsic-block-size, ignoring the
1983 // actual contents. Hence this case is a simplified version of the case
1984 // below.
1986 // NOTE: We exempt the nsComboboxControlFrame subclass from taking this
1987 // special case when it has 'contain-intrinsic-block-size: none', because
1988 // comboboxes implicitly honors the size-containment behavior on its
1989 // nsComboboxDisplayFrame child (which it shrinkwraps) rather than on the
1990 // nsComboboxControlFrame. (Moreover, the DisplayFrame child doesn't even
1991 // need any special content-size-ignoring behavior in its reflow method,
1992 // because that method just resolves "auto" BSize values to one
1993 // line-height rather than by measuring its contents' BSize.)
1994 nscoord contentBSize = *containBSize;
1995 nscoord autoBSize =
1996 aReflowInput.ApplyMinMaxBSize(contentBSize, aState.mConsumedBSize);
1997 aMetrics.mCarriedOutBEndMargin.Zero();
1998 autoBSize += borderPadding.BStartEnd(wm);
1999 finalSize.BSize(wm) = autoBSize;
2000 } else if (aState.mReflowStatus.IsInlineBreakBefore()) {
2001 // Our parent is expected to push this frame to the next page/column so
2002 // what size we set here doesn't really matter.
2003 finalSize.BSize(wm) = aReflowInput.AvailableBSize();
2004 } else if (aState.mReflowStatus.IsComplete()) {
2005 const nscoord lineClampedContentBlockEndEdge =
2006 ApplyLineClamp(aReflowInput, this, blockEndEdgeOfChildren);
2008 const nscoord bpBStart = borderPadding.BStart(wm);
2009 const nscoord contentBSize = blockEndEdgeOfChildren - bpBStart;
2010 const nscoord lineClampedContentBSize =
2011 lineClampedContentBlockEndEdge - bpBStart;
2013 const nscoord autoBSize = aReflowInput.ApplyMinMaxBSize(
2014 lineClampedContentBSize, aState.mConsumedBSize);
2015 if (autoBSize != contentBSize) {
2016 // Our min-block-size, max-block-size, or -webkit-line-clamp value made
2017 // our bsize change. Don't carry out our kids' block-end margins.
2018 aMetrics.mCarriedOutBEndMargin.Zero();
2020 nscoord bSize = autoBSize + borderPadding.BStartEnd(wm);
2021 if (MOZ_UNLIKELY(autoBSize > contentBSize &&
2022 bSize > aReflowInput.AvailableBSize() &&
2023 aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE)) {
2024 // Applying `min-size` made us overflow our available size.
2025 // Clamp it and report that we're Incomplete, or BreakBefore if we have
2026 // 'break-inside: avoid' that is applicable.
2027 bSize = aReflowInput.AvailableBSize();
2028 if (ShouldAvoidBreakInside(aReflowInput)) {
2029 aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
2030 } else {
2031 aState.mReflowStatus.SetIncomplete();
2034 finalSize.BSize(wm) = bSize;
2035 } else {
2036 NS_ASSERTION(
2037 aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
2038 "Shouldn't be incomplete if availableBSize is UNCONSTRAINED.");
2039 nscoord bSize = std::max(aState.mBCoord, aReflowInput.AvailableBSize());
2040 if (aReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
2041 // This should never happen, but it does. See bug 414255
2042 bSize = aState.mBCoord;
2044 const nscoord maxBSize = aReflowInput.ComputedMaxBSize();
2045 if (maxBSize != NS_UNCONSTRAINEDSIZE &&
2046 aState.mConsumedBSize + bSize - borderPadding.BStart(wm) > maxBSize) {
2047 nscoord bEnd = std::max(0, maxBSize - aState.mConsumedBSize) +
2048 borderPadding.BStart(wm);
2049 // Note that |borderPadding| has GetSkipSides applied, so we ask
2050 // aReflowInput for the actual value we'd use on a last fragment here:
2051 bEnd += aReflowInput.ComputedLogicalBorderPadding(wm).BEnd(wm);
2052 if (bEnd <= aReflowInput.AvailableBSize()) {
2053 // We actually fit after applying `max-size` so we should be
2054 // Overflow-Incomplete instead.
2055 bSize = bEnd;
2056 aState.mReflowStatus.SetOverflowIncomplete();
2059 finalSize.BSize(wm) = bSize;
2063 if (IsTrueOverflowContainer()) {
2064 if (aState.mReflowStatus.IsIncomplete()) {
2065 // Overflow containers can only be overflow complete.
2066 // Note that auto height overflow containers have no normal children
2067 NS_ASSERTION(finalSize.BSize(wm) == 0,
2068 "overflow containers must be zero-block-size");
2069 aState.mReflowStatus.SetOverflowIncomplete();
2071 } else if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
2072 !aState.mReflowStatus.IsInlineBreakBefore() &&
2073 aState.mReflowStatus.IsComplete()) {
2074 // Currently only used for grid items, but could be used in other contexts.
2075 // The FragStretchBSizeProperty is our expected non-fragmented block-size
2076 // we should stretch to (for align-self:stretch etc). In some fragmentation
2077 // cases though, the last fragment (this frame since we're complete), needs
2078 // to have extra size applied because earlier fragments consumed too much of
2079 // our computed size due to overflowing their containing block. (E.g. this
2080 // ensures we fill the last row when a multi-row grid item is fragmented).
2081 bool found;
2082 nscoord bSize = GetProperty(FragStretchBSizeProperty(), &found);
2083 if (found) {
2084 finalSize.BSize(wm) = std::max(bSize, finalSize.BSize(wm));
2088 // Clamp the content size to fit within the margin-box clamp size, if any.
2089 if (MOZ_UNLIKELY(aReflowInput.mComputeSizeFlags.contains(
2090 ComputeSizeFlag::BClampMarginBoxMinSize)) &&
2091 aState.mReflowStatus.IsComplete()) {
2092 bool found;
2093 nscoord cbSize = GetProperty(BClampMarginBoxMinSizeProperty(), &found);
2094 if (found) {
2095 auto marginBoxBSize =
2096 finalSize.BSize(wm) +
2097 aReflowInput.ComputedLogicalMargin(wm).BStartEnd(wm);
2098 auto overflow = marginBoxBSize - cbSize;
2099 if (overflow > 0) {
2100 auto contentBSize = finalSize.BSize(wm) - borderPadding.BStartEnd(wm);
2101 auto newContentBSize = std::max(nscoord(0), contentBSize - overflow);
2102 // XXXmats deal with percentages better somehow?
2103 finalSize.BSize(wm) -= contentBSize - newContentBSize;
2108 // Screen out negative block sizes --- can happen due to integer overflows :-(
2109 finalSize.BSize(wm) = std::max(0, finalSize.BSize(wm));
2110 *aBEndEdgeOfChildren = blockEndEdgeOfChildren;
2112 if (blockEndEdgeOfChildren != finalSize.BSize(wm) - borderPadding.BEnd(wm)) {
2113 SetProperty(BlockEndEdgeOfChildrenProperty(), blockEndEdgeOfChildren);
2114 } else {
2115 RemoveProperty(BlockEndEdgeOfChildrenProperty());
2118 aMetrics.SetSize(wm, finalSize);
2120 #ifdef DEBUG_blocks
2121 if ((ABSURD_SIZE(aMetrics.Width()) || ABSURD_SIZE(aMetrics.Height())) &&
2122 !GetParent()->IsAbsurdSizeAssertSuppressed()) {
2123 ListTag(stdout);
2124 printf(": WARNING: desired:%d,%d\n", aMetrics.Width(), aMetrics.Height());
2126 #endif
2129 void nsBlockFrame::ConsiderBlockEndEdgeOfChildren(
2130 OverflowAreas& aOverflowAreas, nscoord aBEndEdgeOfChildren,
2131 const nsStyleDisplay* aDisplay) const {
2132 const auto wm = GetWritingMode();
2134 // Factor in the block-end edge of the children. Child frames will be added
2135 // to the overflow area as we iterate through the lines, but their margins
2136 // won't, so we need to account for block-end margins here.
2137 // REVIEW: For now, we do this for both visual and scrollable area,
2138 // although when we make scrollable overflow area not be a subset of
2139 // visual, we can change this.
2141 if (Style()->GetPseudoType() == PseudoStyleType::scrolledContent) {
2142 // If we are a scrolled inner frame, add our block-end padding to our
2143 // children's block-end edge.
2145 // Note: aBEndEdgeOfChildren already includes our own block-start padding
2146 // because it is relative to our block-start edge of our border-box, which
2147 // is the same as our padding-box here.
2148 MOZ_ASSERT(GetLogicalUsedBorderAndPadding(wm) == GetLogicalUsedPadding(wm),
2149 "A scrolled inner frame shouldn't have any border!");
2150 aBEndEdgeOfChildren += GetLogicalUsedPadding(wm).BEnd(wm);
2153 // XXX Currently, overflow areas are stored as physical rects, so we have
2154 // to handle writing modes explicitly here. If we change overflow rects
2155 // to be stored logically, this can be simplified again.
2156 if (wm.IsVertical()) {
2157 if (wm.IsVerticalLR()) {
2158 for (const auto otype : AllOverflowTypes()) {
2159 if (!(aDisplay->IsContainLayout() &&
2160 otype == OverflowType::Scrollable)) {
2161 // Layout containment should force all overflow to be ink (visual)
2162 // overflow, so if we're layout-contained, we only add our children's
2163 // block-end edge to the ink (visual) overflow -- not to the
2164 // scrollable overflow.
2165 nsRect& o = aOverflowAreas.Overflow(otype);
2166 o.width = std::max(o.XMost(), aBEndEdgeOfChildren) - o.x;
2169 } else {
2170 for (const auto otype : AllOverflowTypes()) {
2171 if (!(aDisplay->IsContainLayout() &&
2172 otype == OverflowType::Scrollable)) {
2173 nsRect& o = aOverflowAreas.Overflow(otype);
2174 nscoord xmost = o.XMost();
2175 o.x = std::min(o.x, xmost - aBEndEdgeOfChildren);
2176 o.width = xmost - o.x;
2180 } else {
2181 for (const auto otype : AllOverflowTypes()) {
2182 if (!(aDisplay->IsContainLayout() && otype == OverflowType::Scrollable)) {
2183 nsRect& o = aOverflowAreas.Overflow(otype);
2184 o.height = std::max(o.YMost(), aBEndEdgeOfChildren) - o.y;
2190 void nsBlockFrame::ComputeOverflowAreas(OverflowAreas& aOverflowAreas,
2191 nscoord aBEndEdgeOfChildren,
2192 const nsStyleDisplay* aDisplay) const {
2193 // XXX_perf: This can be done incrementally. It is currently one of
2194 // the things that makes incremental reflow O(N^2).
2195 auto overflowClipAxes = ShouldApplyOverflowClipping(aDisplay);
2196 auto overflowClipMargin = OverflowClipMargin(overflowClipAxes);
2197 if (overflowClipAxes == PhysicalAxes::Both &&
2198 overflowClipMargin == nsSize()) {
2199 return;
2202 // We rely here on our caller having called SetOverflowAreasToDesiredBounds().
2203 nsRect frameBounds = aOverflowAreas.ScrollableOverflow();
2205 for (const auto& line : Lines()) {
2206 if (aDisplay->IsContainLayout()) {
2207 // If we have layout containment, we should only consider our child's
2208 // ink overflow, leaving the scrollable regions of the parent
2209 // unaffected.
2210 // Note: scrollable overflow is a subset of ink overflow,
2211 // so this has the same affect as unioning the child's visual and
2212 // scrollable overflow with its parent's ink overflow.
2213 nsRect childVisualRect = line.InkOverflowRect();
2214 OverflowAreas childVisualArea = OverflowAreas(childVisualRect, nsRect());
2215 aOverflowAreas.UnionWith(childVisualArea);
2216 } else {
2217 aOverflowAreas.UnionWith(line.GetOverflowAreas());
2221 // Factor an outside ::marker in; normally the ::marker will be factored
2222 // into the line-box's overflow areas. However, if the line is a block
2223 // line then it won't; if there are no lines, it won't. So just
2224 // factor it in anyway (it can't hurt if it was already done).
2225 // XXXldb Can we just fix GetOverflowArea instead?
2226 if (nsIFrame* outsideMarker = GetOutsideMarker()) {
2227 aOverflowAreas.UnionAllWith(outsideMarker->GetRect());
2230 ConsiderBlockEndEdgeOfChildren(aOverflowAreas, aBEndEdgeOfChildren, aDisplay);
2232 if (overflowClipAxes != PhysicalAxes::None) {
2233 aOverflowAreas.ApplyClipping(frameBounds, overflowClipAxes,
2234 overflowClipMargin);
2237 #ifdef NOISY_OVERFLOW_AREAS
2238 printf("%s: InkOverflowArea=%s, ScrollableOverflowArea=%s\n", ListTag().get(),
2239 ToString(aOverflowAreas.InkOverflow()).c_str(),
2240 ToString(aOverflowAreas.ScrollableOverflow()).c_str());
2241 #endif
2244 void nsBlockFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas) {
2245 // We need to update the overflow areas of lines manually, as they
2246 // get cached and re-used otherwise. Lines aren't exposed as normal
2247 // frame children, so calling UnionChildOverflow alone will end up
2248 // using the old cached values.
2249 for (auto& line : Lines()) {
2250 nsRect bounds = line.GetPhysicalBounds();
2251 OverflowAreas lineAreas(bounds, bounds);
2253 int32_t n = line.GetChildCount();
2254 for (nsIFrame* lineFrame = line.mFirstChild; n > 0;
2255 lineFrame = lineFrame->GetNextSibling(), --n) {
2256 ConsiderChildOverflow(lineAreas, lineFrame);
2259 // Consider the overflow areas of the floats attached to the line as well
2260 if (line.HasFloats()) {
2261 for (nsIFrame* f : line.Floats()) {
2262 ConsiderChildOverflow(lineAreas, f);
2266 line.SetOverflowAreas(lineAreas);
2267 aOverflowAreas.UnionWith(lineAreas);
2270 // Union with child frames, skipping the principal and float lists
2271 // since we already handled those using the line boxes.
2272 nsLayoutUtils::UnionChildOverflow(
2273 this, aOverflowAreas,
2274 {FrameChildListID::Principal, FrameChildListID::Float});
2277 bool nsBlockFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) {
2278 bool found;
2279 nscoord blockEndEdgeOfChildren =
2280 GetProperty(BlockEndEdgeOfChildrenProperty(), &found);
2281 if (found) {
2282 ConsiderBlockEndEdgeOfChildren(aOverflowAreas, blockEndEdgeOfChildren,
2283 StyleDisplay());
2286 // Line cursor invariants depend on the overflow areas of the lines, so
2287 // we must clear the line cursor since those areas may have changed.
2288 ClearLineCursors();
2289 return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
2292 void nsBlockFrame::LazyMarkLinesDirty() {
2293 if (HasAnyStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES)) {
2294 for (LineIterator line = LinesBegin(), line_end = LinesEnd();
2295 line != line_end; ++line) {
2296 int32_t n = line->GetChildCount();
2297 for (nsIFrame* lineFrame = line->mFirstChild; n > 0;
2298 lineFrame = lineFrame->GetNextSibling(), --n) {
2299 if (lineFrame->IsSubtreeDirty()) {
2300 // NOTE: MarkLineDirty does more than just marking the line dirty.
2301 MarkLineDirty(line, &mLines);
2302 break;
2306 RemoveStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
2310 void nsBlockFrame::MarkLineDirty(LineIterator aLine,
2311 const nsLineList* aLineList) {
2312 // Mark aLine dirty
2313 aLine->MarkDirty();
2314 aLine->SetInvalidateTextRuns(true);
2315 #ifdef DEBUG
2316 if (gNoisyReflow) {
2317 IndentBy(stdout, gNoiseIndent);
2318 ListTag(stdout);
2319 printf(": mark line %p dirty\n", static_cast<void*>(aLine.get()));
2321 #endif
2323 // Mark previous line dirty if it's an inline line so that it can
2324 // maybe pullup something from the line just affected.
2325 // XXX We don't need to do this if aPrevLine ends in a break-after...
2326 if (aLine != aLineList->front() && aLine->IsInline() &&
2327 aLine.prev()->IsInline()) {
2328 aLine.prev()->MarkDirty();
2329 aLine.prev()->SetInvalidateTextRuns(true);
2330 #ifdef DEBUG
2331 if (gNoisyReflow) {
2332 IndentBy(stdout, gNoiseIndent);
2333 ListTag(stdout);
2334 printf(": mark prev-line %p dirty\n",
2335 static_cast<void*>(aLine.prev().get()));
2337 #endif
2342 * Test whether lines are certain to be aligned left so that we can make
2343 * resizing optimizations
2345 static inline bool IsAlignedLeft(StyleTextAlign aAlignment,
2346 StyleDirection aDirection,
2347 uint8_t aUnicodeBidi, nsIFrame* aFrame) {
2348 return SVGUtils::IsInSVGTextSubtree(aFrame) ||
2349 StyleTextAlign::Left == aAlignment ||
2350 (((StyleTextAlign::Start == aAlignment &&
2351 StyleDirection::Ltr == aDirection) ||
2352 (StyleTextAlign::End == aAlignment &&
2353 StyleDirection::Rtl == aDirection)) &&
2354 !(NS_STYLE_UNICODE_BIDI_PLAINTEXT & aUnicodeBidi));
2357 void nsBlockFrame::PrepareResizeReflow(BlockReflowState& aState) {
2358 // See if we can try and avoid marking all the lines as dirty
2359 // FIXME(emilio): This should be writing-mode aware, I guess.
2360 bool tryAndSkipLines =
2361 // The left content-edge must be a constant distance from the left
2362 // border-edge.
2363 !StylePadding()->mPadding.Get(eSideLeft).HasPercent();
2365 #ifdef DEBUG
2366 if (gDisableResizeOpt) {
2367 tryAndSkipLines = false;
2369 if (gNoisyReflow) {
2370 if (!tryAndSkipLines) {
2371 IndentBy(stdout, gNoiseIndent);
2372 ListTag(stdout);
2373 printf(": marking all lines dirty: availISize=%d\n",
2374 aState.mReflowInput.AvailableISize());
2377 #endif
2379 if (tryAndSkipLines) {
2380 WritingMode wm = aState.mReflowInput.GetWritingMode();
2381 nscoord newAvailISize =
2382 aState.mReflowInput.ComputedLogicalBorderPadding(wm).IStart(wm) +
2383 aState.mReflowInput.ComputedISize();
2385 #ifdef DEBUG
2386 if (gNoisyReflow) {
2387 IndentBy(stdout, gNoiseIndent);
2388 ListTag(stdout);
2389 printf(": trying to avoid marking all lines dirty\n");
2391 #endif
2393 for (LineIterator line = LinesBegin(), line_end = LinesEnd();
2394 line != line_end; ++line) {
2395 // We let child blocks make their own decisions the same
2396 // way we are here.
2397 bool isLastLine = line == mLines.back() && !GetNextInFlow();
2398 if (line->IsBlock() || line->HasFloats() ||
2399 (!isLastLine && !line->HasForcedLineBreakAfter()) ||
2400 ((isLastLine || !line->IsLineWrapped())) ||
2401 line->ResizeReflowOptimizationDisabled() ||
2402 line->IsImpactedByFloat() || (line->IEnd() > newAvailISize)) {
2403 line->MarkDirty();
2406 #ifdef REALLY_NOISY_REFLOW
2407 if (!line->IsBlock()) {
2408 printf("PrepareResizeReflow thinks line %p is %simpacted by floats\n",
2409 line.get(), line->IsImpactedByFloat() ? "" : "not ");
2411 #endif
2412 #ifdef DEBUG
2413 if (gNoisyReflow && !line->IsDirty()) {
2414 IndentBy(stdout, gNoiseIndent + 1);
2415 printf(
2416 "skipped: line=%p next=%p %s %s%s%s clearTypeBefore/After=%s/%s "
2417 "xmost=%d\n",
2418 static_cast<void*>(line.get()),
2419 static_cast<void*>(
2420 (line.next() != LinesEnd() ? line.next().get() : nullptr)),
2421 line->IsBlock() ? "block" : "inline",
2422 line->HasForcedLineBreakAfter() ? "has-break-after " : "",
2423 line->HasFloats() ? "has-floats " : "",
2424 line->IsImpactedByFloat() ? "impacted " : "",
2425 line->StyleClearToString(line->FloatClearTypeBefore()),
2426 line->StyleClearToString(line->FloatClearTypeAfter()),
2427 line->IEnd());
2429 #endif
2431 } else {
2432 // Mark everything dirty
2433 for (auto& line : Lines()) {
2434 line.MarkDirty();
2439 //----------------------------------------
2442 * Propagate reflow "damage" from from earlier lines to the current
2443 * line. The reflow damage comes from the following sources:
2444 * 1. The regions of float damage remembered during reflow.
2445 * 2. The combination of nonzero |aDeltaBCoord| and any impact by a
2446 * float, either the previous reflow or now.
2448 * When entering this function, |aLine| is still at its old position and
2449 * |aDeltaBCoord| indicates how much it will later be slid (assuming it
2450 * doesn't get marked dirty and reflowed entirely).
2452 void nsBlockFrame::PropagateFloatDamage(BlockReflowState& aState,
2453 nsLineBox* aLine,
2454 nscoord aDeltaBCoord) {
2455 nsFloatManager* floatManager = aState.FloatManager();
2456 NS_ASSERTION(
2457 (aState.mReflowInput.mParentReflowInput &&
2458 aState.mReflowInput.mParentReflowInput->mFloatManager == floatManager) ||
2459 aState.mReflowInput.mBlockDelta == 0,
2460 "Bad block delta passed in");
2462 // Check to see if there are any floats; if there aren't, there can't
2463 // be any float damage
2464 if (!floatManager->HasAnyFloats()) {
2465 return;
2468 // Check the damage region recorded in the float damage.
2469 if (floatManager->HasFloatDamage()) {
2470 // Need to check mBounds *and* mCombinedArea to find intersections
2471 // with aLine's floats
2472 nscoord lineBCoordBefore = aLine->BStart() + aDeltaBCoord;
2473 nscoord lineBCoordAfter = lineBCoordBefore + aLine->BSize();
2474 // Scrollable overflow should be sufficient for things that affect
2475 // layout.
2476 WritingMode wm = aState.mReflowInput.GetWritingMode();
2477 nsSize containerSize = aState.ContainerSize();
2478 LogicalRect overflow =
2479 aLine->GetOverflowArea(OverflowType::Scrollable, wm, containerSize);
2480 nscoord lineBCoordCombinedBefore = overflow.BStart(wm) + aDeltaBCoord;
2481 nscoord lineBCoordCombinedAfter =
2482 lineBCoordCombinedBefore + overflow.BSize(wm);
2484 bool isDirty =
2485 floatManager->IntersectsDamage(lineBCoordBefore, lineBCoordAfter) ||
2486 floatManager->IntersectsDamage(lineBCoordCombinedBefore,
2487 lineBCoordCombinedAfter);
2488 if (isDirty) {
2489 aLine->MarkDirty();
2490 return;
2494 // Check if the line is moving relative to the float manager
2495 if (aDeltaBCoord + aState.mReflowInput.mBlockDelta != 0) {
2496 if (aLine->IsBlock()) {
2497 // Unconditionally reflow sliding blocks; we only really need to reflow
2498 // if there's a float impacting this block, but the current float manager
2499 // makes it difficult to check that. Therefore, we let the child block
2500 // decide what it needs to reflow.
2501 aLine->MarkDirty();
2502 } else {
2503 bool wasImpactedByFloat = aLine->IsImpactedByFloat();
2504 nsFlowAreaRect floatAvailableSpace =
2505 aState.GetFloatAvailableSpaceForBSize(aLine->BStart() + aDeltaBCoord,
2506 aLine->BSize(), nullptr);
2508 #ifdef REALLY_NOISY_REFLOW
2509 printf("nsBlockFrame::PropagateFloatDamage %p was = %d, is=%d\n", this,
2510 wasImpactedByFloat, floatAvailableSpace.HasFloats());
2511 #endif
2513 // Mark the line dirty if it was or is affected by a float
2514 // We actually only really need to reflow if the amount of impact
2515 // changes, but that's not straightforward to check
2516 if (wasImpactedByFloat || floatAvailableSpace.HasFloats()) {
2517 aLine->MarkDirty();
2523 static bool LineHasClear(nsLineBox* aLine) {
2524 return aLine->IsBlock()
2525 ? (aLine->HasForcedLineBreakBefore() ||
2526 aLine->mFirstChild->HasAnyStateBits(
2527 NS_BLOCK_HAS_CLEAR_CHILDREN) ||
2528 !nsBlockFrame::BlockCanIntersectFloats(aLine->mFirstChild))
2529 : aLine->HasFloatClearTypeAfter();
2533 * Reparent a whole list of floats from aOldParent to this block. The
2534 * floats might be taken from aOldParent's overflow list. They will be
2535 * removed from the list. They end up appended to our mFloats list.
2537 void nsBlockFrame::ReparentFloats(nsIFrame* aFirstFrame,
2538 nsBlockFrame* aOldParent,
2539 bool aReparentSiblings) {
2540 nsFrameList list;
2541 aOldParent->CollectFloats(aFirstFrame, list, aReparentSiblings);
2542 if (list.NotEmpty()) {
2543 for (nsIFrame* f : list) {
2544 MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
2545 "CollectFloats should've removed that bit");
2546 ReparentFrame(f, aOldParent, this);
2548 mFloats.AppendFrames(nullptr, std::move(list));
2552 static void DumpLine(const BlockReflowState& aState, nsLineBox* aLine,
2553 nscoord aDeltaBCoord, int32_t aDeltaIndent) {
2554 #ifdef DEBUG
2555 if (nsBlockFrame::gNoisyReflow) {
2556 nsRect ovis(aLine->InkOverflowRect());
2557 nsRect oscr(aLine->ScrollableOverflowRect());
2558 nsBlockFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent + aDeltaIndent);
2559 printf(
2560 "line=%p mBCoord=%d dirty=%s oldBounds={%d,%d,%d,%d} "
2561 "oldoverflow-vis={%d,%d,%d,%d} oldoverflow-scr={%d,%d,%d,%d} "
2562 "deltaBCoord=%d mPrevBEndMargin=%d childCount=%d\n",
2563 static_cast<void*>(aLine), aState.mBCoord,
2564 aLine->IsDirty() ? "yes" : "no", aLine->IStart(), aLine->BStart(),
2565 aLine->ISize(), aLine->BSize(), ovis.x, ovis.y, ovis.width, ovis.height,
2566 oscr.x, oscr.y, oscr.width, oscr.height, aDeltaBCoord,
2567 aState.mPrevBEndMargin.get(), aLine->GetChildCount());
2569 #endif
2572 static bool LinesAreEmpty(const nsLineList& aList) {
2573 for (const auto& line : aList) {
2574 if (!line.IsEmpty()) {
2575 return false;
2578 return true;
2581 void nsBlockFrame::ReflowDirtyLines(BlockReflowState& aState) {
2582 bool keepGoing = true;
2583 bool repositionViews = false; // should we really need this?
2584 bool foundAnyClears = aState.mTrailingClearFromPIF != StyleClear::None;
2585 bool willReflowAgain = false;
2587 #ifdef DEBUG
2588 if (gNoisyReflow) {
2589 IndentBy(stdout, gNoiseIndent);
2590 ListTag(stdout);
2591 printf(": reflowing dirty lines");
2592 printf(" computedISize=%d\n", aState.mReflowInput.ComputedISize());
2594 AutoNoisyIndenter indent(gNoisyReflow);
2595 #endif
2597 bool selfDirty = HasAnyStateBits(NS_FRAME_IS_DIRTY) ||
2598 (aState.mReflowInput.IsBResize() &&
2599 HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE));
2601 // Reflow our last line if our availableBSize has increased
2602 // so that we (and our last child) pull up content as necessary
2603 if (aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
2604 GetNextInFlow() &&
2605 aState.mReflowInput.AvailableBSize() >
2606 GetLogicalSize().BSize(aState.mReflowInput.GetWritingMode())) {
2607 LineIterator lastLine = LinesEnd();
2608 if (lastLine != LinesBegin()) {
2609 --lastLine;
2610 lastLine->MarkDirty();
2613 // the amount by which we will slide the current line if it is not
2614 // dirty
2615 nscoord deltaBCoord = 0;
2617 // whether we did NOT reflow the previous line and thus we need to
2618 // recompute the carried out margin before the line if we want to
2619 // reflow it or if its previous margin is dirty
2620 bool needToRecoverState = false;
2621 // Float continuations were reflowed in ReflowPushedFloats
2622 bool reflowedFloat =
2623 mFloats.NotEmpty() &&
2624 mFloats.FirstChild()->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT);
2625 bool lastLineMovedUp = false;
2626 // We save up information about BR-clearance here
2627 StyleClear inlineFloatClearType = aState.mTrailingClearFromPIF;
2629 LineIterator line = LinesBegin(), line_end = LinesEnd();
2631 // Determine if children of this frame could have breaks between them for
2632 // page names.
2634 // We need to check for paginated layout, the named-page pref, and if the
2635 // available block-size is constrained.
2637 // Note that we need to check for paginated layout as named-pages are only
2638 // used during paginated reflow. We need to additionally check for
2639 // unconstrained block-size to avoid introducing fragmentation breaks during
2640 // "measuring" reflows within an overall paginated reflow, and to avoid
2641 // fragmentation in monolithic containers like 'inline-block'.
2643 // Because we can only break for named pages using Class A breakpoints, we
2644 // also need to check that the block flow direction of the containing frame
2645 // of these items (which is this block) is parallel to that of this page.
2646 // See: https://www.w3.org/TR/css-break-3/#btw-blocks
2647 const nsPresContext* const presCtx = aState.mPresContext;
2648 const bool canBreakForPageNames =
2649 aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
2650 presCtx->IsPaginated() && StaticPrefs::layout_css_named_pages_enabled() &&
2651 presCtx->GetPresShell()->GetRootFrame()->GetWritingMode().IsVertical() ==
2652 GetWritingMode().IsVertical();
2654 // Reflow the lines that are already ours
2655 for (; line != line_end; ++line, aState.AdvanceToNextLine()) {
2656 DumpLine(aState, line, deltaBCoord, 0);
2657 #ifdef DEBUG
2658 AutoNoisyIndenter indent2(gNoisyReflow);
2659 #endif
2661 if (selfDirty) {
2662 line->MarkDirty();
2665 // This really sucks, but we have to look inside any blocks that have clear
2666 // elements inside them.
2667 // XXX what can we do smarter here?
2668 if (!line->IsDirty() && line->IsBlock() &&
2669 line->mFirstChild->HasAnyStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN)) {
2670 line->MarkDirty();
2673 nsIFrame* floatAvoidingBlock = nullptr;
2674 if (line->IsBlock() &&
2675 !nsBlockFrame::BlockCanIntersectFloats(line->mFirstChild)) {
2676 floatAvoidingBlock = line->mFirstChild;
2679 // We have to reflow the line if it's a block whose clearance
2680 // might have changed, so detect that.
2681 if (!line->IsDirty() &&
2682 (line->HasForcedLineBreakBefore() || floatAvoidingBlock)) {
2683 nscoord curBCoord = aState.mBCoord;
2684 // See where we would be after applying any clearance due to
2685 // BRs.
2686 if (inlineFloatClearType != StyleClear::None) {
2687 std::tie(curBCoord, std::ignore) =
2688 aState.ClearFloats(curBCoord, inlineFloatClearType);
2691 auto [newBCoord, result] = aState.ClearFloats(
2692 curBCoord, line->FloatClearTypeBefore(), floatAvoidingBlock);
2694 if (line->HasClearance()) {
2695 // Reflow the line if it might not have clearance anymore.
2696 if (result == ClearFloatsResult::BCoordNoChange
2697 // aState.mBCoord is the clearance point which should be the
2698 // block-start border-edge of the block frame. If sliding the
2699 // block by deltaBCoord isn't going to put it in the predicted
2700 // position, then we'd better reflow the line.
2701 || newBCoord != line->BStart() + deltaBCoord) {
2702 line->MarkDirty();
2704 } else {
2705 // Reflow the line if the line might have clearance now.
2706 if (result != ClearFloatsResult::BCoordNoChange) {
2707 line->MarkDirty();
2712 // We might have to reflow a line that is after a clearing BR.
2713 if (inlineFloatClearType != StyleClear::None) {
2714 std::tie(aState.mBCoord, std::ignore) =
2715 aState.ClearFloats(aState.mBCoord, inlineFloatClearType);
2716 if (aState.mBCoord != line->BStart() + deltaBCoord) {
2717 // SlideLine is not going to put the line where the clearance
2718 // put it. Reflow the line to be sure.
2719 line->MarkDirty();
2721 inlineFloatClearType = StyleClear::None;
2724 bool previousMarginWasDirty = line->IsPreviousMarginDirty();
2725 if (previousMarginWasDirty) {
2726 // If the previous margin is dirty, reflow the current line
2727 line->MarkDirty();
2728 line->ClearPreviousMarginDirty();
2729 } else if (aState.ContentBSize() != NS_UNCONSTRAINEDSIZE) {
2730 const nscoord scrollableOverflowBEnd =
2731 LogicalRect(line->mWritingMode, line->ScrollableOverflowRect(),
2732 line->mContainerSize)
2733 .BEnd(line->mWritingMode);
2734 if (scrollableOverflowBEnd + deltaBCoord > aState.ContentBEnd()) {
2735 // Lines that aren't dirty but get slid past our available block-size
2736 // constraint must be reflowed.
2737 line->MarkDirty();
2741 if (!line->IsDirty()) {
2742 const bool isPaginated =
2743 // Last column can be reflowed unconstrained during column balancing.
2744 // Hence the additional NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR bit check
2745 // as a fail-safe fallback.
2746 aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE ||
2747 HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) ||
2748 // Table can also be reflowed unconstrained during printing.
2749 aState.mPresContext->IsPaginated();
2750 if (isPaginated) {
2751 // We are in a paginated context, i.e. in columns or pages.
2752 const bool mayContainFloats =
2753 line->IsBlock() || line->HasFloats() || line->HadFloatPushed();
2754 if (mayContainFloats) {
2755 // The following if-else conditions check whether this line -- which
2756 // might have floats in its subtree, or has floats as direct children,
2757 // or had floats pushed -- needs to be reflowed.
2758 if (deltaBCoord != 0 || aState.mReflowInput.IsBResize()) {
2759 // The distance to the block-end edge might have changed. Reflow the
2760 // line both because the breakpoints within its floats may have
2761 // changed and because we might have to push/pull the floats in
2762 // their entirety.
2763 line->MarkDirty();
2764 } else if (HasPushedFloats()) {
2765 // We had pushed floats which haven't been drained by our
2766 // next-in-flow, which means our parent is currently reflowing us
2767 // again due to clearance without creating a next-in-flow for us.
2768 // Reflow the line to redo the floats split logic to correctly set
2769 // our reflow status.
2770 line->MarkDirty();
2771 } else if (aState.mReflowInput.mFlags.mMustReflowPlaceholders) {
2772 // Reflow the line (that may containing a float's placeholder frame)
2773 // if our parent tells us to do so.
2774 line->MarkDirty();
2775 } else if (aState.mReflowInput.mFlags.mMovedBlockFragments) {
2776 // Our parent's line containing us moved to a different fragment.
2777 // Reflow the line because the decision about whether the float fits
2778 // may be different in a different fragment.
2779 line->MarkDirty();
2785 if (!line->IsDirty()) {
2786 // See if there's any reflow damage that requires that we mark the
2787 // line dirty.
2788 PropagateFloatDamage(aState, line, deltaBCoord);
2791 // If the container size has changed, reset mContainerSize. If the
2792 // line's writing mode is not ltr, or if the line is not left-aligned, also
2793 // mark the line dirty.
2794 if (aState.ContainerSize() != line->mContainerSize) {
2795 line->mContainerSize = aState.ContainerSize();
2797 const bool isLastLine = line == mLines.back() && !GetNextInFlow();
2798 const auto align = isLastLine ? StyleText()->TextAlignForLastLine()
2799 : StyleText()->mTextAlign;
2800 if (line->mWritingMode.IsVertical() || line->mWritingMode.IsBidiRTL() ||
2801 !IsAlignedLeft(align, StyleVisibility()->mDirection,
2802 StyleTextReset()->mUnicodeBidi, this)) {
2803 line->MarkDirty();
2807 // Check for a page break caused by CSS named pages.
2809 // We should break for named pages when two frames meet at a class A
2810 // breakpoint, where the first frame has a different end page value to the
2811 // second frame's start page value. canBreakForPageNames is true iff
2812 // children of this frame can form class A breakpoints, and that we are not
2813 // in a measurement reflow or in a monolithic container such as
2814 // 'inline-block'.
2816 // We specifically do not want to cause a page-break for named pages when
2817 // we are at the top of a page. This would otherwise happen when the
2818 // previous sibling is an nsPageBreakFrame, or all previous siblings on the
2819 // current page are zero-height. The latter may not be per-spec, but is
2820 // compatible with Chrome's implementation of named pages.
2821 const nsAtom* nextPageName = nullptr;
2822 bool shouldBreakForPageName = false;
2823 if (canBreakForPageNames && (!aState.mReflowInput.mFlags.mIsTopOfPage ||
2824 !aState.IsAdjacentWithBStart())) {
2825 const nsIFrame* const frame = line->mFirstChild;
2826 if (const nsIFrame* const prevFrame = frame->GetPrevSibling()) {
2827 if (!frame->IsPlaceholderFrame() && !prevFrame->IsPlaceholderFrame()) {
2828 nextPageName = frame->GetStartPageValue();
2829 if (nextPageName != prevFrame->GetEndPageValue()) {
2830 shouldBreakForPageName = true;
2831 line->MarkDirty();
2837 if (needToRecoverState && line->IsDirty()) {
2838 // We need to reconstruct the block-end margin only if we didn't
2839 // reflow the previous line and we do need to reflow (or repair
2840 // the block-start position of) the next line.
2841 aState.ReconstructMarginBefore(line);
2844 bool reflowedPrevLine = !needToRecoverState;
2845 if (needToRecoverState) {
2846 needToRecoverState = false;
2848 // Update aState.mPrevChild as if we had reflowed all of the frames in
2849 // this line.
2850 if (line->IsDirty()) {
2851 NS_ASSERTION(
2852 line->mFirstChild->GetPrevSibling() == line.prev()->LastChild(),
2853 "unexpected line frames");
2854 aState.mPrevChild = line->mFirstChild->GetPrevSibling();
2858 // Now repair the line and update |aState.mBCoord| by calling
2859 // |ReflowLine| or |SlideLine|.
2860 // If we're going to reflow everything again, then no need to reflow
2861 // the dirty line ... unless the line has floats, in which case we'd
2862 // better reflow it now to refresh its float cache, which may contain
2863 // dangling frame pointers! Ugh! This reflow of the line may be
2864 // incorrect because we skipped reflowing previous lines (e.g., floats
2865 // may be placed incorrectly), but that's OK because we'll mark the
2866 // line dirty below under "if (aState.mReflowInput.mDiscoveredClearance..."
2867 if (line->IsDirty() && (line->HasFloats() || !willReflowAgain)) {
2868 lastLineMovedUp = true;
2870 bool maybeReflowingForFirstTime =
2871 line->IStart() == 0 && line->BStart() == 0 && line->ISize() == 0 &&
2872 line->BSize() == 0;
2874 // Compute the dirty lines "before" BEnd, after factoring in
2875 // the running deltaBCoord value - the running value is implicit in
2876 // aState.mBCoord.
2877 nscoord oldB = line->BStart();
2878 nscoord oldBMost = line->BEnd();
2880 NS_ASSERTION(!willReflowAgain || !line->IsBlock(),
2881 "Don't reflow blocks while willReflowAgain is true, reflow "
2882 "of block abs-pos children depends on this");
2884 if (shouldBreakForPageName) {
2885 // Immediately fragment for page-name. It is possible we could break
2886 // out of the loop right here, but this should make it more similar to
2887 // what happens when reflow causes fragmentation.
2888 PushTruncatedLine(aState, line, &keepGoing);
2889 PresShell()->FrameConstructor()->SetNextPageContentFramePageName(
2890 nextPageName ? nextPageName : GetAutoPageValue());
2891 } else {
2892 // Reflow the dirty line. If it's an incremental reflow, then force
2893 // it to invalidate the dirty area if necessary
2894 ReflowLine(aState, line, &keepGoing);
2897 if (aState.mReflowInput.WillReflowAgainForClearance()) {
2898 line->MarkDirty();
2899 willReflowAgain = true;
2900 // Note that once we've entered this state, every line that gets here
2901 // (e.g. because it has floats) gets marked dirty and reflowed again.
2902 // in the next pass. This is important, see above.
2905 if (line->HasFloats()) {
2906 reflowedFloat = true;
2909 if (!keepGoing) {
2910 DumpLine(aState, line, deltaBCoord, -1);
2911 if (0 == line->GetChildCount()) {
2912 DeleteLine(aState, line, line_end);
2914 break;
2917 // Test to see whether the margin that should be carried out
2918 // to the next line (NL) might have changed. In ReflowBlockFrame
2919 // we call nextLine->MarkPreviousMarginDirty if the block's
2920 // actual carried-out block-end margin changed. So here we only
2921 // need to worry about the following effects:
2922 // 1) the line was just created, and it might now be blocking
2923 // a carried-out block-end margin from previous lines that
2924 // used to reach NL from reaching NL
2925 // 2) the line used to be empty, and is now not empty,
2926 // thus blocking a carried-out block-end margin from previous lines
2927 // that used to reach NL from reaching NL
2928 // 3) the line wasn't empty, but now is, so a carried-out
2929 // block-end margin from previous lines that didn't used to reach NL
2930 // now does
2931 // 4) the line might have changed in a way that affects NL's
2932 // ShouldApplyBStartMargin decision. The three things that matter
2933 // are the line's emptiness, its adjacency to the block-start edge of the
2934 // block, and whether it has clearance (the latter only matters if the
2935 // block was and is adjacent to the block-start and empty).
2937 // If the line is empty now, we can't reliably tell if the line was empty
2938 // before, so we just assume it was and do
2939 // nextLine->MarkPreviousMarginDirty. This means the checks in 4) are
2940 // redundant; if the line is empty now we don't need to check 4), but if
2941 // the line is not empty now and we're sure it wasn't empty before, any
2942 // adjacency and clearance changes are irrelevant to the result of
2943 // nextLine->ShouldApplyBStartMargin.
2944 if (line.next() != LinesEnd()) {
2945 bool maybeWasEmpty = oldB == line.next()->BStart();
2946 bool isEmpty = line->CachedIsEmpty();
2947 if (maybeReflowingForFirstTime /*1*/ ||
2948 (isEmpty || maybeWasEmpty) /*2/3/4*/) {
2949 line.next()->MarkPreviousMarginDirty();
2950 // since it's marked dirty, nobody will care about |deltaBCoord|
2954 // If the line was just reflowed for the first time, then its
2955 // old mBounds cannot be trusted so this deltaBCoord computation is
2956 // bogus. But that's OK because we just did
2957 // MarkPreviousMarginDirty on the next line which will force it
2958 // to be reflowed, so this computation of deltaBCoord will not be
2959 // used.
2960 deltaBCoord = line->BEnd() - oldBMost;
2962 // Now do an interrupt check. We want to do this only in the case when we
2963 // actually reflow the line, so that if we get back in here we'll get
2964 // further on the reflow before interrupting.
2965 aState.mPresContext->CheckForInterrupt(this);
2966 } else {
2967 aState.mOverflowTracker->Skip(line->mFirstChild, aState.mReflowStatus);
2968 // Nop except for blocks (we don't create overflow container
2969 // continuations for any inlines atm), so only checking mFirstChild
2970 // is enough
2972 lastLineMovedUp = deltaBCoord < 0;
2974 if (deltaBCoord != 0) {
2975 SlideLine(aState, line, deltaBCoord);
2976 } else {
2977 repositionViews = true;
2980 NS_ASSERTION(!line->IsDirty() || !line->HasFloats(),
2981 "Possibly stale float cache here!");
2982 if (willReflowAgain && line->IsBlock()) {
2983 // If we're going to reflow everything again, and this line is a block,
2984 // then there is no need to recover float state. The line may contain
2985 // other lines with floats, but in that case RecoverStateFrom would only
2986 // add floats to the float manager. We don't need to do that because
2987 // everything's going to get reflowed again "for real". Calling
2988 // RecoverStateFrom in this situation could be lethal because the
2989 // block's descendant lines may have float caches containing dangling
2990 // frame pointers. Ugh!
2991 // If this line is inline, then we need to recover its state now
2992 // to make sure that we don't forget to move its floats by deltaBCoord.
2993 } else {
2994 // XXX EVIL O(N^2) EVIL
2995 aState.RecoverStateFrom(line, deltaBCoord);
2998 // Keep mBCoord up to date in case we're propagating reflow damage
2999 // and also because our final height may depend on it. If the
3000 // line is inlines, then only update mBCoord if the line is not
3001 // empty, because that's what PlaceLine does. (Empty blocks may
3002 // want to update mBCoord, e.g. if they have clearance.)
3003 if (line->IsBlock() || !line->CachedIsEmpty()) {
3004 aState.mBCoord = line->BEnd();
3007 needToRecoverState = true;
3009 if (reflowedPrevLine && !line->IsBlock() &&
3010 aState.mPresContext->HasPendingInterrupt()) {
3011 // Need to make sure to pull overflows from any prev-in-flows
3012 for (nsIFrame* inlineKid = line->mFirstChild; inlineKid;
3013 inlineKid = inlineKid->PrincipalChildList().FirstChild()) {
3014 inlineKid->PullOverflowsFromPrevInFlow();
3019 // Record if we need to clear floats before reflowing the next
3020 // line. Note that inlineFloatClearType will be handled and
3021 // cleared before the next line is processed, so there is no
3022 // need to combine break types here.
3023 if (line->HasFloatClearTypeAfter()) {
3024 inlineFloatClearType = line->FloatClearTypeAfter();
3027 if (LineHasClear(line.get())) {
3028 foundAnyClears = true;
3031 DumpLine(aState, line, deltaBCoord, -1);
3033 if (aState.mPresContext->HasPendingInterrupt()) {
3034 willReflowAgain = true;
3035 // Another option here might be to leave |line| clean if
3036 // !HasPendingInterrupt() before the CheckForInterrupt() call, since in
3037 // that case the line really did reflow as it should have. Not sure
3038 // whether that would be safe, so doing this for now instead. Also not
3039 // sure whether we really want to mark all lines dirty after an
3040 // interrupt, but until we get better at propagating float damage we
3041 // really do need to do it this way; see comments inside MarkLineDirty.
3042 MarkLineDirtyForInterrupt(line);
3046 // Handle BR-clearance from the last line of the block
3047 if (inlineFloatClearType != StyleClear::None) {
3048 std::tie(aState.mBCoord, std::ignore) =
3049 aState.ClearFloats(aState.mBCoord, inlineFloatClearType);
3052 if (needToRecoverState) {
3053 // Is this expensive?
3054 aState.ReconstructMarginBefore(line);
3056 // Update aState.mPrevChild as if we had reflowed all of the frames in
3057 // the last line.
3058 NS_ASSERTION(line == line_end || line->mFirstChild->GetPrevSibling() ==
3059 line.prev()->LastChild(),
3060 "unexpected line frames");
3061 aState.mPrevChild = line == line_end ? mFrames.LastChild()
3062 : line->mFirstChild->GetPrevSibling();
3065 // Should we really have to do this?
3066 if (repositionViews) {
3067 nsContainerFrame::PlaceFrameView(this);
3070 // We can skip trying to pull up the next line if our height is constrained
3071 // (so we can report being incomplete) and there is no next in flow or we
3072 // were told not to or we know it will be futile, i.e.,
3073 // -- the next in flow is not changing
3074 // -- and we cannot have added more space for its first line to be
3075 // pulled up into,
3076 // -- it's an incremental reflow of a descendant
3077 // -- and we didn't reflow any floats (so the available space
3078 // didn't change)
3079 // -- my chain of next-in-flows either has no first line, or its first
3080 // line isn't dirty.
3081 bool heightConstrained =
3082 aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE;
3083 bool skipPull = willReflowAgain && heightConstrained;
3084 if (!skipPull && heightConstrained && aState.mNextInFlow &&
3085 (aState.mReflowInput.mFlags.mNextInFlowUntouched && !lastLineMovedUp &&
3086 !HasAnyStateBits(NS_FRAME_IS_DIRTY) && !reflowedFloat)) {
3087 // We'll place lineIter at the last line of this block, so that
3088 // nsBlockInFlowLineIterator::Next() will take us to the first
3089 // line of my next-in-flow-chain. (But first, check that I
3090 // have any lines -- if I don't, just bail out of this
3091 // optimization.)
3092 LineIterator lineIter = this->LinesEnd();
3093 if (lineIter != this->LinesBegin()) {
3094 lineIter--; // I have lines; step back from dummy iterator to last line.
3095 nsBlockInFlowLineIterator bifLineIter(this, lineIter);
3097 // Check for next-in-flow-chain's first line.
3098 // (First, see if there is such a line, and second, see if it's clean)
3099 if (!bifLineIter.Next() || !bifLineIter.GetLine()->IsDirty()) {
3100 skipPull = true;
3105 if (skipPull && aState.mNextInFlow) {
3106 NS_ASSERTION(heightConstrained, "Height should be constrained here\n");
3107 if (aState.mNextInFlow->IsTrueOverflowContainer()) {
3108 aState.mReflowStatus.SetOverflowIncomplete();
3109 } else {
3110 aState.mReflowStatus.SetIncomplete();
3114 if (!skipPull && aState.mNextInFlow) {
3115 // Pull data from a next-in-flow if there's still room for more
3116 // content here.
3117 while (keepGoing && aState.mNextInFlow) {
3118 // Grab first line from our next-in-flow
3119 nsBlockFrame* nextInFlow = aState.mNextInFlow;
3120 nsLineBox* pulledLine;
3121 nsFrameList pulledFrames;
3122 if (!nextInFlow->mLines.empty()) {
3123 RemoveFirstLine(nextInFlow->mLines, nextInFlow->mFrames, &pulledLine,
3124 &pulledFrames);
3125 } else {
3126 // Grab an overflow line if there are any
3127 FrameLines* overflowLines = nextInFlow->GetOverflowLines();
3128 if (!overflowLines) {
3129 aState.mNextInFlow =
3130 static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow());
3131 continue;
3133 bool last =
3134 RemoveFirstLine(overflowLines->mLines, overflowLines->mFrames,
3135 &pulledLine, &pulledFrames);
3136 if (last) {
3137 nextInFlow->DestroyOverflowLines();
3141 if (pulledFrames.IsEmpty()) {
3142 // The line is empty. Try the next one.
3143 NS_ASSERTION(
3144 pulledLine->GetChildCount() == 0 && !pulledLine->mFirstChild,
3145 "bad empty line");
3146 nextInFlow->FreeLineBox(pulledLine);
3147 continue;
3150 if (nextInFlow->MaybeHasLineCursor()) {
3151 if (pulledLine == nextInFlow->GetLineCursorForDisplay()) {
3152 nextInFlow->ClearLineCursorForDisplay();
3154 if (pulledLine == nextInFlow->GetLineCursorForQuery()) {
3155 nextInFlow->ClearLineCursorForQuery();
3158 ReparentFrames(pulledFrames, nextInFlow, this);
3159 pulledLine->SetMovedFragments();
3161 NS_ASSERTION(pulledFrames.LastChild() == pulledLine->LastChild(),
3162 "Unexpected last frame");
3163 NS_ASSERTION(aState.mPrevChild || mLines.empty(),
3164 "should have a prevchild here");
3165 NS_ASSERTION(aState.mPrevChild == mFrames.LastChild(),
3166 "Incorrect aState.mPrevChild before inserting line at end");
3168 // Shift pulledLine's frames into our mFrames list.
3169 mFrames.AppendFrames(nullptr, std::move(pulledFrames));
3171 // Add line to our line list, and set its last child as our new prev-child
3172 line = mLines.before_insert(LinesEnd(), pulledLine);
3173 aState.mPrevChild = mFrames.LastChild();
3175 // Reparent floats whose placeholders are in the line.
3176 ReparentFloats(pulledLine->mFirstChild, nextInFlow, true);
3178 DumpLine(aState, pulledLine, deltaBCoord, 0);
3179 #ifdef DEBUG
3180 AutoNoisyIndenter indent2(gNoisyReflow);
3181 #endif
3183 if (aState.mPresContext->HasPendingInterrupt()) {
3184 MarkLineDirtyForInterrupt(line);
3185 } else {
3186 // Now reflow it and any lines that it makes during it's reflow
3187 // (we have to loop here because reflowing the line may cause a new
3188 // line to be created; see SplitLine's callers for examples of
3189 // when this happens).
3190 while (line != LinesEnd()) {
3191 ReflowLine(aState, line, &keepGoing);
3193 if (aState.mReflowInput.WillReflowAgainForClearance()) {
3194 line->MarkDirty();
3195 keepGoing = false;
3196 aState.mReflowStatus.SetIncomplete();
3197 break;
3200 DumpLine(aState, line, deltaBCoord, -1);
3201 if (!keepGoing) {
3202 if (0 == line->GetChildCount()) {
3203 DeleteLine(aState, line, line_end);
3205 break;
3208 if (LineHasClear(line.get())) {
3209 foundAnyClears = true;
3212 if (aState.mPresContext->CheckForInterrupt(this)) {
3213 MarkLineDirtyForInterrupt(line);
3214 break;
3217 // If this is an inline frame then its time to stop
3218 ++line;
3219 aState.AdvanceToNextLine();
3224 if (aState.mReflowStatus.IsIncomplete()) {
3225 aState.mReflowStatus.SetNextInFlowNeedsReflow();
3226 } // XXXfr shouldn't set this flag when nextinflow has no lines
3229 // Handle an odd-ball case: a list-item with no lines
3230 if (mLines.empty() && HasOutsideMarker()) {
3231 ReflowOutput metrics(aState.mReflowInput);
3232 nsIFrame* marker = GetOutsideMarker();
3233 WritingMode wm = aState.mReflowInput.GetWritingMode();
3234 ReflowOutsideMarker(
3235 marker, aState, metrics,
3236 aState.mReflowInput.ComputedPhysicalBorderPadding().top);
3237 NS_ASSERTION(!MarkerIsEmpty() || metrics.BSize(wm) == 0,
3238 "empty ::marker frame took up space");
3240 if (!MarkerIsEmpty()) {
3241 // There are no lines so we have to fake up some y motion so that
3242 // we end up with *some* height.
3243 // (Note: if we're layout-contained, we have to be sure to leave our
3244 // ReflowOutput's BlockStartAscent() (i.e. the baseline) untouched,
3245 // because layout-contained frames have no baseline.)
3246 if (!aState.mReflowInput.mStyleDisplay->IsContainLayout() &&
3247 metrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
3248 nscoord ascent;
3249 WritingMode wm = aState.mReflowInput.GetWritingMode();
3250 if (nsLayoutUtils::GetFirstLineBaseline(wm, marker, &ascent)) {
3251 metrics.SetBlockStartAscent(ascent);
3252 } else {
3253 metrics.SetBlockStartAscent(metrics.BSize(wm));
3257 RefPtr<nsFontMetrics> fm =
3258 nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
3260 nscoord minAscent = nsLayoutUtils::GetCenteredFontBaseline(
3261 fm, aState.mMinLineHeight, wm.IsLineInverted());
3262 nscoord minDescent = aState.mMinLineHeight - minAscent;
3264 aState.mBCoord +=
3265 std::max(minAscent, metrics.BlockStartAscent()) +
3266 std::max(minDescent, metrics.BSize(wm) - metrics.BlockStartAscent());
3268 nscoord offset = minAscent - metrics.BlockStartAscent();
3269 if (offset > 0) {
3270 marker->SetRect(marker->GetRect() + nsPoint(0, offset));
3275 if (LinesAreEmpty(mLines) && ShouldHaveLineIfEmpty()) {
3276 aState.mBCoord += aState.mMinLineHeight;
3279 if (foundAnyClears) {
3280 AddStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN);
3281 } else {
3282 RemoveStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN);
3285 #ifdef DEBUG
3286 VerifyLines(true);
3287 VerifyOverflowSituation();
3288 if (gNoisyReflow) {
3289 IndentBy(stdout, gNoiseIndent - 1);
3290 ListTag(stdout);
3291 printf(": done reflowing dirty lines (status=%s)\n",
3292 ToString(aState.mReflowStatus).c_str());
3294 #endif
3297 void nsBlockFrame::MarkLineDirtyForInterrupt(nsLineBox* aLine) {
3298 aLine->MarkDirty();
3300 // Just checking NS_FRAME_IS_DIRTY is ok, because we've already
3301 // marked the lines that need to be marked dirty based on our
3302 // vertical resize stuff. So we'll definitely reflow all those kids;
3303 // the only question is how they should behave.
3304 if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
3305 // Mark all our child frames dirty so we make sure to reflow them
3306 // later.
3307 int32_t n = aLine->GetChildCount();
3308 for (nsIFrame* f = aLine->mFirstChild; n > 0;
3309 f = f->GetNextSibling(), --n) {
3310 f->MarkSubtreeDirty();
3312 // And mark all the floats whose reflows we might be skipping dirty too.
3313 if (aLine->HasFloats()) {
3314 for (nsIFrame* f : aLine->Floats()) {
3315 f->MarkSubtreeDirty();
3318 } else {
3319 // Dirty all the descendant lines of block kids to handle float damage,
3320 // since our nsFloatManager will go away by the next time we're reflowing.
3321 // XXXbz Can we do something more like what PropagateFloatDamage does?
3322 // Would need to sort out the exact business with mBlockDelta for that....
3323 // This marks way too much dirty. If we ever make this better, revisit
3324 // which lines we mark dirty in the interrupt case in ReflowDirtyLines.
3325 nsBlockFrame* bf = do_QueryFrame(aLine->mFirstChild);
3326 if (bf) {
3327 MarkAllDescendantLinesDirty(bf);
3332 void nsBlockFrame::DeleteLine(BlockReflowState& aState,
3333 nsLineList::iterator aLine,
3334 nsLineList::iterator aLineEnd) {
3335 MOZ_ASSERT(0 == aLine->GetChildCount(), "can't delete !empty line");
3336 if (0 == aLine->GetChildCount()) {
3337 NS_ASSERTION(aState.mCurrentLine == aLine,
3338 "using function more generally than designed, "
3339 "but perhaps OK now");
3340 nsLineBox* line = aLine;
3341 aLine = mLines.erase(aLine);
3342 FreeLineBox(line);
3343 // Mark the previous margin of the next line dirty since we need to
3344 // recompute its top position.
3345 if (aLine != aLineEnd) {
3346 aLine->MarkPreviousMarginDirty();
3352 * Reflow a line. The line will either contain a single block frame
3353 * or contain 1 or more inline frames. aKeepReflowGoing indicates
3354 * whether or not the caller should continue to reflow more lines.
3356 void nsBlockFrame::ReflowLine(BlockReflowState& aState, LineIterator aLine,
3357 bool* aKeepReflowGoing) {
3358 MOZ_ASSERT(aLine->GetChildCount(), "reflowing empty line");
3360 // Setup the line-layout for the new line
3361 aState.mCurrentLine = aLine;
3362 aLine->ClearDirty();
3363 aLine->InvalidateCachedIsEmpty();
3364 aLine->ClearHadFloatPushed();
3366 // If this line contains a single block that is hidden by `content-visibility`
3367 // don't reflow the line. If this line contains inlines and the first one is
3368 // hidden by `content-visibility`, all of them are, so avoid reflow in that
3369 // case as well.
3370 nsIFrame* firstChild = aLine->mFirstChild;
3371 if (firstChild->IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
3372 return;
3375 // Now that we know what kind of line we have, reflow it
3376 if (aLine->IsBlock()) {
3377 ReflowBlockFrame(aState, aLine, aKeepReflowGoing);
3378 } else {
3379 aLine->SetLineWrapped(false);
3380 ReflowInlineFrames(aState, aLine, aKeepReflowGoing);
3382 // Store the line's float edges for overflow marker analysis if needed.
3383 aLine->ClearFloatEdges();
3384 if (aState.mFlags.mCanHaveOverflowMarkers) {
3385 WritingMode wm = aLine->mWritingMode;
3386 nsFlowAreaRect r = aState.GetFloatAvailableSpaceForBSize(
3387 aLine->BStart(), aLine->BSize(), nullptr);
3388 if (r.HasFloats()) {
3389 LogicalRect so = aLine->GetOverflowArea(OverflowType::Scrollable, wm,
3390 aLine->mContainerSize);
3391 nscoord s = r.mRect.IStart(wm);
3392 nscoord e = r.mRect.IEnd(wm);
3393 if (so.IEnd(wm) > e || so.IStart(wm) < s) {
3394 // This line is overlapping a float - store the edges marking the area
3395 // between the floats for text-overflow analysis.
3396 aLine->SetFloatEdges(s, e);
3402 aLine->ClearMovedFragments();
3405 nsIFrame* nsBlockFrame::PullFrame(BlockReflowState& aState,
3406 LineIterator aLine) {
3407 // First check our remaining lines.
3408 if (LinesEnd() != aLine.next()) {
3409 return PullFrameFrom(aLine, this, aLine.next());
3412 NS_ASSERTION(
3413 !GetOverflowLines(),
3414 "Our overflow lines should have been removed at the start of reflow");
3416 // Try each next-in-flow.
3417 nsBlockFrame* nextInFlow = aState.mNextInFlow;
3418 while (nextInFlow) {
3419 if (nextInFlow->mLines.empty()) {
3420 nextInFlow->DrainSelfOverflowList();
3422 if (!nextInFlow->mLines.empty()) {
3423 return PullFrameFrom(aLine, nextInFlow, nextInFlow->mLines.begin());
3425 nextInFlow = static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow());
3426 aState.mNextInFlow = nextInFlow;
3429 return nullptr;
3432 nsIFrame* nsBlockFrame::PullFrameFrom(nsLineBox* aLine,
3433 nsBlockFrame* aFromContainer,
3434 nsLineList::iterator aFromLine) {
3435 nsLineBox* fromLine = aFromLine;
3436 MOZ_ASSERT(fromLine, "bad line to pull from");
3437 MOZ_ASSERT(fromLine->GetChildCount(), "empty line");
3438 MOZ_ASSERT(aLine->GetChildCount(), "empty line");
3440 NS_ASSERTION(fromLine->IsBlock() == fromLine->mFirstChild->IsBlockOutside(),
3441 "Disagreement about whether it's a block or not");
3443 if (fromLine->IsBlock()) {
3444 // If our line is not empty and the child in aFromLine is a block
3445 // then we cannot pull up the frame into this line. In this case
3446 // we stop pulling.
3447 return nullptr;
3449 // Take frame from fromLine
3450 nsIFrame* frame = fromLine->mFirstChild;
3451 nsIFrame* newFirstChild = frame->GetNextSibling();
3453 if (aFromContainer != this) {
3454 // The frame is being pulled from a next-in-flow; therefore we
3455 // need to add it to our sibling list.
3456 MOZ_ASSERT(aLine == mLines.back());
3457 MOZ_ASSERT(aFromLine == aFromContainer->mLines.begin(),
3458 "should only pull from first line");
3459 aFromContainer->mFrames.RemoveFrame(frame);
3461 // When pushing and pulling frames we need to check for whether any
3462 // views need to be reparented.
3463 ReparentFrame(frame, aFromContainer, this);
3464 mFrames.AppendFrame(nullptr, frame);
3466 // The frame might have (or contain) floats that need to be brought
3467 // over too. (pass 'false' since there are no siblings to check)
3468 ReparentFloats(frame, aFromContainer, false);
3469 } else {
3470 MOZ_ASSERT(aLine == aFromLine.prev());
3473 aLine->NoteFrameAdded(frame);
3474 fromLine->NoteFrameRemoved(frame);
3476 if (fromLine->GetChildCount() > 0) {
3477 // Mark line dirty now that we pulled a child
3478 fromLine->MarkDirty();
3479 fromLine->mFirstChild = newFirstChild;
3480 } else {
3481 // Free up the fromLine now that it's empty.
3482 // Its bounds might need to be redrawn, though.
3483 if (aFromLine.next() != aFromContainer->mLines.end()) {
3484 aFromLine.next()->MarkPreviousMarginDirty();
3486 aFromContainer->mLines.erase(aFromLine);
3487 // aFromLine is now invalid
3488 aFromContainer->FreeLineBox(fromLine);
3491 #ifdef DEBUG
3492 VerifyLines(true);
3493 VerifyOverflowSituation();
3494 #endif
3496 return frame;
3499 void nsBlockFrame::SlideLine(BlockReflowState& aState, nsLineBox* aLine,
3500 nscoord aDeltaBCoord) {
3501 MOZ_ASSERT(aDeltaBCoord != 0, "why slide a line nowhere?");
3503 // Adjust line state
3504 aLine->SlideBy(aDeltaBCoord, aState.ContainerSize());
3506 // Adjust the frames in the line
3507 MoveChildFramesOfLine(aLine, aDeltaBCoord);
3510 void nsBlockFrame::UpdateLineContainerSize(nsLineBox* aLine,
3511 const nsSize& aNewContainerSize) {
3512 if (aNewContainerSize == aLine->mContainerSize) {
3513 return;
3516 // Adjust line state
3517 nsSize sizeDelta = aLine->UpdateContainerSize(aNewContainerSize);
3519 // Changing container width only matters if writing mode is vertical-rl
3520 if (GetWritingMode().IsVerticalRL()) {
3521 MoveChildFramesOfLine(aLine, sizeDelta.width);
3525 void nsBlockFrame::MoveChildFramesOfLine(nsLineBox* aLine,
3526 nscoord aDeltaBCoord) {
3527 // Adjust the frames in the line
3528 nsIFrame* kid = aLine->mFirstChild;
3529 if (!kid) {
3530 return;
3533 WritingMode wm = GetWritingMode();
3534 LogicalPoint translation(wm, 0, aDeltaBCoord);
3536 if (aLine->IsBlock()) {
3537 if (aDeltaBCoord) {
3538 kid->MovePositionBy(wm, translation);
3541 // Make sure the frame's view and any child views are updated
3542 nsContainerFrame::PlaceFrameView(kid);
3543 } else {
3544 // Adjust the block-dir coordinate of the frames in the line.
3545 // Note: we need to re-position views even if aDeltaBCoord is 0, because
3546 // one of our parent frames may have moved and so the view's position
3547 // relative to its parent may have changed.
3548 int32_t n = aLine->GetChildCount();
3549 while (--n >= 0) {
3550 if (aDeltaBCoord) {
3551 kid->MovePositionBy(wm, translation);
3553 // Make sure the frame's view and any child views are updated
3554 nsContainerFrame::PlaceFrameView(kid);
3555 kid = kid->GetNextSibling();
3560 static inline bool IsNonAutoNonZeroBSize(const StyleSize& aCoord) {
3561 // The "extremum length" values (see ExtremumLength) were originally aimed at
3562 // inline-size (or width, as it was before logicalization). For now, let them
3563 // return false here, so we treat them like 'auto' pending a real
3564 // implementation. (See bug 1126420.)
3566 // FIXME (bug 567039, bug 527285) This isn't correct for the 'fill' value,
3567 // which should more likely (but not necessarily, depending on the available
3568 // space) be returning true.
3569 if (aCoord.BehavesLikeInitialValueOnBlockAxis()) {
3570 return false;
3572 MOZ_ASSERT(aCoord.IsLengthPercentage());
3573 // If we evaluate the length/percent/calc at a percentage basis of
3574 // both nscoord_MAX and 0, and it's zero both ways, then it's a zero
3575 // length, percent, or combination thereof. Test > 0 so we clamp
3576 // negative calc() results to 0.
3577 return aCoord.AsLengthPercentage().Resolve(nscoord_MAX) > 0 ||
3578 aCoord.AsLengthPercentage().Resolve(0) > 0;
3581 /* virtual */
3582 bool nsBlockFrame::IsSelfEmpty() {
3583 if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
3584 return true;
3587 // Blocks which are margin-roots (including inline-blocks) cannot be treated
3588 // as empty for margin-collapsing and other purposes. They're more like
3589 // replaced elements.
3590 if (HasAnyStateBits(NS_BLOCK_MARGIN_ROOT)) {
3591 return false;
3594 WritingMode wm = GetWritingMode();
3595 const nsStylePosition* position = StylePosition();
3597 if (IsNonAutoNonZeroBSize(position->MinBSize(wm)) ||
3598 IsNonAutoNonZeroBSize(position->BSize(wm))) {
3599 return false;
3602 // FIXME: Bug 1646100 - Take intrinsic size into account.
3603 // FIXME: Handle the case that both inline and block sizes are auto.
3604 // https://github.com/w3c/csswg-drafts/issues/5060.
3605 // Note: block-size could be zero or auto/intrinsic keywords here.
3606 if (position->BSize(wm).BehavesLikeInitialValueOnBlockAxis() &&
3607 position->mAspectRatio.HasFiniteRatio()) {
3608 return false;
3611 const nsStyleBorder* border = StyleBorder();
3612 const nsStylePadding* padding = StylePadding();
3614 if (border->GetComputedBorderWidth(wm.PhysicalSide(eLogicalSideBStart)) !=
3615 0 ||
3616 border->GetComputedBorderWidth(wm.PhysicalSide(eLogicalSideBEnd)) != 0 ||
3617 !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBStart(wm)) ||
3618 !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBEnd(wm))) {
3619 return false;
3622 if (HasOutsideMarker() && !MarkerIsEmpty()) {
3623 return false;
3626 return true;
3629 bool nsBlockFrame::CachedIsEmpty() {
3630 if (!IsSelfEmpty()) {
3631 return false;
3633 for (auto& line : mLines) {
3634 if (!line.CachedIsEmpty()) {
3635 return false;
3638 return true;
3641 bool nsBlockFrame::IsEmpty() {
3642 if (!IsSelfEmpty()) {
3643 return false;
3646 return LinesAreEmpty(mLines);
3649 bool nsBlockFrame::ShouldApplyBStartMargin(BlockReflowState& aState,
3650 nsLineBox* aLine) {
3651 if (aLine->mFirstChild->IsPageBreakFrame()) {
3652 // A page break frame consumes margins adjacent to it.
3653 // https://drafts.csswg.org/css-break/#break-margins
3654 return false;
3657 if (aState.mFlags.mShouldApplyBStartMargin) {
3658 // Apply short-circuit check to avoid searching the line list
3659 return true;
3662 if (!aState.IsAdjacentWithBStart()) {
3663 // If we aren't at the start block-coordinate then something of non-zero
3664 // height must have been placed. Therefore the childs block-start margin
3665 // applies.
3666 aState.mFlags.mShouldApplyBStartMargin = true;
3667 return true;
3670 // Determine if this line is "essentially" the first line
3671 LineIterator line = LinesBegin();
3672 if (aState.mFlags.mHasLineAdjacentToTop) {
3673 line = aState.mLineAdjacentToTop;
3675 while (line != aLine) {
3676 if (!line->CachedIsEmpty() || line->HasClearance()) {
3677 // A line which precedes aLine is non-empty, or has clearance,
3678 // so therefore the block-start margin applies.
3679 aState.mFlags.mShouldApplyBStartMargin = true;
3680 return true;
3682 // No need to apply the block-start margin if the line has floats. We
3683 // should collapse anyway (bug 44419)
3684 ++line;
3685 aState.mFlags.mHasLineAdjacentToTop = true;
3686 aState.mLineAdjacentToTop = line;
3689 // The line being reflowed is "essentially" the first line in the
3690 // block. Therefore its block-start margin will be collapsed by the
3691 // generational collapsing logic with its parent (us).
3692 return false;
3695 void nsBlockFrame::ReflowBlockFrame(BlockReflowState& aState,
3696 LineIterator aLine,
3697 bool* aKeepReflowGoing) {
3698 MOZ_ASSERT(*aKeepReflowGoing, "bad caller");
3700 nsIFrame* frame = aLine->mFirstChild;
3701 if (!frame) {
3702 NS_ASSERTION(false, "program error - unexpected empty line");
3703 return;
3706 // Prepare the block reflow engine
3707 nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput);
3709 StyleClear clearType = frame->StyleDisplay()->mClear;
3710 if (aState.mTrailingClearFromPIF != StyleClear::None) {
3711 clearType = nsLayoutUtils::CombineClearType(clearType,
3712 aState.mTrailingClearFromPIF);
3713 aState.mTrailingClearFromPIF = StyleClear::None;
3716 // Clear past floats before the block if the clear style is not none
3717 aLine->ClearForcedLineBreak();
3718 if (clearType != StyleClear::None) {
3719 aLine->SetForcedLineBreakBefore(clearType);
3722 // See if we should apply the block-start margin. If the block frame being
3723 // reflowed is a continuation, then we don't apply its block-start margin
3724 // because it's not significant. Otherwise, dig deeper.
3725 bool applyBStartMargin =
3726 !frame->GetPrevContinuation() && ShouldApplyBStartMargin(aState, aLine);
3727 if (applyBStartMargin) {
3728 // The HasClearance setting is only valid if ShouldApplyBStartMargin
3729 // returned false (in which case the block-start margin-root set our
3730 // clearance flag). Otherwise clear it now. We'll set it later on
3731 // ourselves if necessary.
3732 aLine->ClearHasClearance();
3734 bool treatWithClearance = aLine->HasClearance();
3736 bool mightClearFloats = clearType != StyleClear::None;
3737 nsIFrame* floatAvoidingBlock = nullptr;
3738 if (!nsBlockFrame::BlockCanIntersectFloats(frame)) {
3739 mightClearFloats = true;
3740 floatAvoidingBlock = frame;
3743 // If our block-start margin was counted as part of some parent's block-start
3744 // margin collapse, and we are being speculatively reflowed assuming this
3745 // frame DID NOT need clearance, then we need to check that
3746 // assumption.
3747 if (!treatWithClearance && !applyBStartMargin && mightClearFloats &&
3748 aState.mReflowInput.mDiscoveredClearance) {
3749 nscoord curBCoord = aState.mBCoord + aState.mPrevBEndMargin.get();
3750 if (auto [clearBCoord, result] =
3751 aState.ClearFloats(curBCoord, clearType, floatAvoidingBlock);
3752 result != ClearFloatsResult::BCoordNoChange) {
3753 Unused << clearBCoord;
3755 // Only record the first frame that requires clearance
3756 if (!*aState.mReflowInput.mDiscoveredClearance) {
3757 *aState.mReflowInput.mDiscoveredClearance = frame;
3759 aState.mPrevChild = frame;
3760 // Exactly what we do now is flexible since we'll definitely be
3761 // reflowed.
3762 return;
3765 if (treatWithClearance) {
3766 applyBStartMargin = true;
3769 nsIFrame* clearanceFrame = nullptr;
3770 const nscoord startingBCoord = aState.mBCoord;
3771 const nsCollapsingMargin incomingMargin = aState.mPrevBEndMargin;
3772 nscoord clearance;
3773 // Save the original position of the frame so that we can reposition
3774 // its view as needed.
3775 nsPoint originalPosition = frame->GetPosition();
3776 while (true) {
3777 clearance = 0;
3778 nscoord bStartMargin = 0;
3779 bool mayNeedRetry = false;
3780 bool clearedFloats = false;
3781 bool clearedPushedOrSplitFloat = false;
3782 if (applyBStartMargin) {
3783 // Precompute the blocks block-start margin value so that we can get the
3784 // correct available space (there might be a float that's
3785 // already been placed below the aState.mPrevBEndMargin
3787 // Setup a reflowInput to get the style computed block-start margin
3788 // value. We'll use a reason of `resize' so that we don't fudge
3789 // any incremental reflow input.
3791 // The availSpace here is irrelevant to our needs - all we want
3792 // out if this setup is the block-start margin value which doesn't depend
3793 // on the childs available space.
3794 // XXX building a complete ReflowInput just to get the block-start
3795 // margin seems like a waste. And we do this for almost every block!
3796 WritingMode wm = frame->GetWritingMode();
3797 LogicalSize availSpace = aState.ContentSize(wm);
3798 ReflowInput reflowInput(aState.mPresContext, aState.mReflowInput, frame,
3799 availSpace);
3801 if (treatWithClearance) {
3802 aState.mBCoord += aState.mPrevBEndMargin.get();
3803 aState.mPrevBEndMargin.Zero();
3806 // Now compute the collapsed margin-block-start value into
3807 // aState.mPrevBEndMargin, assuming that all child margins
3808 // collapse down to clearanceFrame.
3809 brc.ComputeCollapsedBStartMargin(reflowInput, &aState.mPrevBEndMargin,
3810 clearanceFrame, &mayNeedRetry);
3812 // XXX optimization; we could check the collapsing children to see if they
3813 // are sure to require clearance, and so avoid retrying them
3815 if (clearanceFrame) {
3816 // Don't allow retries on the second pass. The clearance decisions for
3817 // the blocks whose block-start margins collapse with ours are now
3818 // fixed.
3819 mayNeedRetry = false;
3822 if (!treatWithClearance && !clearanceFrame && mightClearFloats) {
3823 // We don't know if we need clearance and this is the first,
3824 // optimistic pass. So determine whether *this block* needs
3825 // clearance. Note that we do not allow the decision for whether
3826 // this block has clearance to change on the second pass; that
3827 // decision is only allowed to be made under the optimistic
3828 // first pass.
3829 nscoord curBCoord = aState.mBCoord + aState.mPrevBEndMargin.get();
3830 if (auto [clearBCoord, result] =
3831 aState.ClearFloats(curBCoord, clearType, floatAvoidingBlock);
3832 result != ClearFloatsResult::BCoordNoChange) {
3833 Unused << clearBCoord;
3835 // Looks like we need clearance and we didn't know about it already.
3836 // So recompute collapsed margin
3837 treatWithClearance = true;
3838 // Remember this decision, needed for incremental reflow
3839 aLine->SetHasClearance();
3841 // Apply incoming margins
3842 aState.mBCoord += aState.mPrevBEndMargin.get();
3843 aState.mPrevBEndMargin.Zero();
3845 // Compute the collapsed margin again, ignoring the incoming margin
3846 // this time
3847 mayNeedRetry = false;
3848 brc.ComputeCollapsedBStartMargin(reflowInput, &aState.mPrevBEndMargin,
3849 clearanceFrame, &mayNeedRetry);
3853 // Temporarily advance the running block-direction value so that the
3854 // GetFloatAvailableSpace method will return the right available space.
3855 // This undone as soon as the horizontal margins are computed.
3856 bStartMargin = aState.mPrevBEndMargin.get();
3858 if (treatWithClearance) {
3859 nscoord currentBCoord = aState.mBCoord;
3860 // advance mBCoord to the clear position.
3861 auto [clearBCoord, result] =
3862 aState.ClearFloats(aState.mBCoord, clearType, floatAvoidingBlock);
3863 aState.mBCoord = clearBCoord;
3865 clearedFloats = result != ClearFloatsResult::BCoordNoChange;
3866 clearedPushedOrSplitFloat =
3867 result == ClearFloatsResult::FloatsPushedOrSplit;
3869 // Compute clearance. It's the amount we need to add to the block-start
3870 // border-edge of the frame, after applying collapsed margins
3871 // from the frame and its children, to get it to line up with
3872 // the block-end of the floats. The former is
3873 // currentBCoord + bStartMargin, the latter is the current
3874 // aState.mBCoord.
3875 // Note that negative clearance is possible
3876 clearance = aState.mBCoord - (currentBCoord + bStartMargin);
3878 // Add clearance to our block-start margin while we compute available
3879 // space for the frame
3880 bStartMargin += clearance;
3882 // Note that aState.mBCoord should stay where it is: at the block-start
3883 // border-edge of the frame
3884 } else {
3885 // Advance aState.mBCoord to the block-start border-edge of the frame.
3886 aState.mBCoord += bStartMargin;
3890 aLine->SetLineIsImpactedByFloat(false);
3892 // Here aState.mBCoord is the block-start border-edge of the block.
3893 // Compute the available space for the block
3894 nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace();
3895 WritingMode wm = aState.mReflowInput.GetWritingMode();
3896 LogicalRect availSpace = aState.ComputeBlockAvailSpace(
3897 frame, floatAvailableSpace, (floatAvoidingBlock));
3899 // The check for
3900 // (!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats)
3901 // is to some degree out of paranoia: if we reliably eat up block-start
3902 // margins at the top of the page as we ought to, it wouldn't be
3903 // needed.
3904 if ((!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats) &&
3905 (availSpace.BSize(wm) < 0 || clearedPushedOrSplitFloat)) {
3906 // We know already that this child block won't fit on this
3907 // page/column due to the block-start margin or the clearance. So we
3908 // need to get out of here now. (If we don't, most blocks will handle
3909 // things fine, and report break-before, but zero-height blocks
3910 // won't, and will thus make their parent overly-large and force
3911 // *it* to be pushed in its entirety.)
3912 aState.mBCoord = startingBCoord;
3913 aState.mPrevBEndMargin = incomingMargin;
3914 if (ShouldAvoidBreakInside(aState.mReflowInput)) {
3915 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
3916 } else {
3917 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
3919 return;
3922 // Now put the block-dir coordinate back to the start of the
3923 // block-start-margin + clearance.
3924 aState.mBCoord -= bStartMargin;
3925 availSpace.BStart(wm) -= bStartMargin;
3926 if (NS_UNCONSTRAINEDSIZE != availSpace.BSize(wm)) {
3927 availSpace.BSize(wm) += bStartMargin;
3930 // Construct the reflow input for the block.
3931 Maybe<ReflowInput> childReflowInput;
3932 Maybe<LogicalSize> cbSize;
3933 LogicalSize availSize = availSpace.Size(wm);
3934 bool columnSetWrapperHasNoBSizeLeft = false;
3935 if (Style()->GetPseudoType() == PseudoStyleType::columnContent) {
3936 // Calculate the multicol containing block's block size so that the
3937 // children with percentage block size get correct percentage basis.
3938 const ReflowInput* cbReflowInput =
3939 aState.mReflowInput.mParentReflowInput->mCBReflowInput;
3940 MOZ_ASSERT(cbReflowInput->mFrame->StyleColumn()->IsColumnContainerStyle(),
3941 "Get unexpected reflow input of multicol containing block!");
3943 // Use column-width as the containing block's inline-size, i.e. the column
3944 // content's computed inline-size.
3945 cbSize.emplace(LogicalSize(wm, aState.mReflowInput.ComputedISize(),
3946 cbReflowInput->ComputedBSize())
3947 .ConvertTo(frame->GetWritingMode(), wm));
3949 // If a ColumnSetWrapper is in a balancing column content, it may be
3950 // pushed or pulled back and forth between column contents. Always add
3951 // NS_FRAME_HAS_DIRTY_CHILDREN bit to it so that its ColumnSet children
3952 // can have a chance to reflow under current block size constraint.
3953 if (aState.mReflowInput.mFlags.mIsColumnBalancing &&
3954 frame->IsColumnSetWrapperFrame()) {
3955 frame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
3957 } else if (IsColumnSetWrapperFrame()) {
3958 // If we are reflowing our ColumnSet children, we want to apply our block
3959 // size constraint to the available block size when constructing reflow
3960 // input for ColumnSet so that ColumnSet can use it to compute its max
3961 // column block size.
3962 if (frame->IsColumnSetFrame()) {
3963 nscoord contentBSize = aState.mReflowInput.ComputedBSize();
3964 if (aState.mReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) {
3965 contentBSize =
3966 std::min(contentBSize, aState.mReflowInput.ComputedMaxBSize());
3968 if (contentBSize != NS_UNCONSTRAINEDSIZE) {
3969 // To get the remaining content block-size, subtract the content
3970 // block-size consumed by our previous continuations.
3971 contentBSize -= aState.mConsumedBSize;
3973 // ColumnSet is not the outermost frame in the column container, so it
3974 // cannot have any margin. We don't need to consider any margin that
3975 // can be generated by "box-decoration-break: clone" as we do in
3976 // BlockReflowState::ComputeBlockAvailSpace().
3977 const nscoord availContentBSize = std::max(
3978 0, contentBSize - (aState.mBCoord - aState.ContentBStart()));
3979 if (availSize.BSize(wm) >= availContentBSize) {
3980 availSize.BSize(wm) = availContentBSize;
3981 columnSetWrapperHasNoBSizeLeft = true;
3987 childReflowInput.emplace(aState.mPresContext, aState.mReflowInput, frame,
3988 availSize.ConvertTo(frame->GetWritingMode(), wm),
3989 cbSize);
3991 childReflowInput->mFlags.mColumnSetWrapperHasNoBSizeLeft =
3992 columnSetWrapperHasNoBSizeLeft;
3994 if (aLine->MovedFragments()) {
3995 // We only need to set this the first reflow, since if we reflow
3996 // again (and replace childReflowInput) we'll be reflowing it
3997 // again in the same fragment as the previous time.
3998 childReflowInput->mFlags.mMovedBlockFragments = true;
4001 nsFloatManager::SavedState floatManagerState;
4002 nsReflowStatus frameReflowStatus;
4003 do {
4004 if (floatAvailableSpace.HasFloats()) {
4005 // Set if floatAvailableSpace.HasFloats() is true for any
4006 // iteration of the loop.
4007 aLine->SetLineIsImpactedByFloat(true);
4010 // We might need to store into mDiscoveredClearance later if it's
4011 // currently null; we want to overwrite any writes that
4012 // brc.ReflowBlock() below does, so we need to remember now
4013 // whether it's empty.
4014 const bool shouldStoreClearance =
4015 aState.mReflowInput.mDiscoveredClearance &&
4016 !*aState.mReflowInput.mDiscoveredClearance;
4018 // Reflow the block into the available space
4019 if (mayNeedRetry || floatAvoidingBlock) {
4020 aState.FloatManager()->PushState(&floatManagerState);
4023 if (mayNeedRetry) {
4024 childReflowInput->mDiscoveredClearance = &clearanceFrame;
4025 } else if (!applyBStartMargin) {
4026 childReflowInput->mDiscoveredClearance =
4027 aState.mReflowInput.mDiscoveredClearance;
4030 frameReflowStatus.Reset();
4031 brc.ReflowBlock(availSpace, applyBStartMargin, aState.mPrevBEndMargin,
4032 clearance, aLine.get(), *childReflowInput,
4033 frameReflowStatus, aState);
4035 if (frameReflowStatus.IsInlineBreakBefore()) {
4036 // No need to retry this loop if there is a break opportunity before the
4037 // child block.
4038 break;
4041 // Now the block has a height. Using that height, get the
4042 // available space again and call ComputeBlockAvailSpace again.
4043 // If ComputeBlockAvailSpace gives a different result, we need to
4044 // reflow again.
4045 if (!floatAvoidingBlock) {
4046 break;
4049 LogicalRect oldFloatAvailableSpaceRect(floatAvailableSpace.mRect);
4050 floatAvailableSpace = aState.GetFloatAvailableSpaceForBSize(
4051 aState.mBCoord + bStartMargin, brc.GetMetrics().BSize(wm),
4052 &floatManagerState);
4053 NS_ASSERTION(floatAvailableSpace.mRect.BStart(wm) ==
4054 oldFloatAvailableSpaceRect.BStart(wm),
4055 "yikes");
4056 // Restore the height to the position of the next band.
4057 floatAvailableSpace.mRect.BSize(wm) =
4058 oldFloatAvailableSpaceRect.BSize(wm);
4059 // Determine whether the available space shrunk on either side,
4060 // because (the first time round) we now know the block's height,
4061 // and it may intersect additional floats, or (on later
4062 // iterations) because narrowing the width relative to the
4063 // previous time may cause the block to become taller. Note that
4064 // since we're reflowing the block, narrowing the width might also
4065 // make it shorter, so we must pass aCanGrow as true.
4066 if (!AvailableSpaceShrunk(wm, oldFloatAvailableSpaceRect,
4067 floatAvailableSpace.mRect, true)) {
4068 // The size and position we chose before are fine (i.e., they
4069 // don't cause intersecting with floats that requires a change
4070 // in size or position), so we're done.
4071 break;
4074 bool advanced = false;
4075 if (!aState.FloatAvoidingBlockFitsInAvailSpace(floatAvoidingBlock,
4076 floatAvailableSpace)) {
4077 // Advance to the next band.
4078 nscoord newBCoord = aState.mBCoord;
4079 if (aState.AdvanceToNextBand(floatAvailableSpace.mRect, &newBCoord)) {
4080 advanced = true;
4082 // ClearFloats might be able to advance us further once we're there.
4083 std::tie(aState.mBCoord, std::ignore) =
4084 aState.ClearFloats(newBCoord, StyleClear::None, floatAvoidingBlock);
4086 // Start over with a new available space rect at the new height.
4087 floatAvailableSpace = aState.GetFloatAvailableSpaceWithState(
4088 aState.mBCoord, ShapeType::ShapeOutside, &floatManagerState);
4091 const LogicalRect oldAvailSpace = availSpace;
4092 availSpace = aState.ComputeBlockAvailSpace(frame, floatAvailableSpace,
4093 (floatAvoidingBlock));
4095 if (!advanced && availSpace.IsEqualEdges(oldAvailSpace)) {
4096 break;
4099 // We need another reflow.
4100 aState.FloatManager()->PopState(&floatManagerState);
4102 if (!treatWithClearance && !applyBStartMargin &&
4103 aState.mReflowInput.mDiscoveredClearance) {
4104 // We set shouldStoreClearance above to record only the first
4105 // frame that requires clearance.
4106 if (shouldStoreClearance) {
4107 *aState.mReflowInput.mDiscoveredClearance = frame;
4109 aState.mPrevChild = frame;
4110 // Exactly what we do now is flexible since we'll definitely be
4111 // reflowed.
4112 return;
4115 if (advanced) {
4116 // We're pushing down the border-box, so we don't apply margin anymore.
4117 // This should never cause us to move up since the call to
4118 // GetFloatAvailableSpaceForBSize above included the margin.
4119 applyBStartMargin = false;
4120 bStartMargin = 0;
4121 treatWithClearance = true; // avoid hitting test above
4122 clearance = 0;
4125 childReflowInput.reset();
4126 childReflowInput.emplace(
4127 aState.mPresContext, aState.mReflowInput, frame,
4128 availSpace.Size(wm).ConvertTo(frame->GetWritingMode(), wm));
4129 } while (true);
4131 if (mayNeedRetry && clearanceFrame) {
4132 // Found a clearance frame, so we need to reflow |frame| a second time.
4133 // Restore the states and start over again.
4134 aState.FloatManager()->PopState(&floatManagerState);
4135 aState.mBCoord = startingBCoord;
4136 aState.mPrevBEndMargin = incomingMargin;
4137 continue;
4140 aState.mPrevChild = frame;
4142 if (childReflowInput->WillReflowAgainForClearance()) {
4143 // If an ancestor of ours is going to reflow for clearance, we
4144 // need to avoid calling PlaceBlock, because it unsets dirty bits
4145 // on the child block (both itself, and through its call to
4146 // nsIFrame::DidReflow), and those dirty bits imply dirtiness for
4147 // all of the child block, including the lines it didn't reflow.
4148 NS_ASSERTION(originalPosition == frame->GetPosition(),
4149 "we need to call PositionChildViews");
4150 return;
4153 #if defined(REFLOW_STATUS_COVERAGE)
4154 RecordReflowStatus(true, frameReflowStatus);
4155 #endif
4157 if (frameReflowStatus.IsInlineBreakBefore()) {
4158 // None of the child block fits.
4159 if (ShouldAvoidBreakInside(aState.mReflowInput)) {
4160 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
4161 } else {
4162 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
4164 } else {
4165 // Note: line-break-after a block is a nop
4167 // Try to place the child block.
4168 // Don't force the block to fit if we have positive clearance, because
4169 // pushing it to the next page would give it more room.
4170 // Don't force the block to fit if it's impacted by a float. If it is,
4171 // then pushing it to the next page would give it more room. Note that
4172 // isImpacted doesn't include impact from the block's own floats.
4173 bool forceFit = aState.IsAdjacentWithBStart() && clearance <= 0 &&
4174 !floatAvailableSpace.HasFloats();
4175 nsCollapsingMargin collapsedBEndMargin;
4176 OverflowAreas overflowAreas;
4177 *aKeepReflowGoing =
4178 brc.PlaceBlock(*childReflowInput, forceFit, aLine.get(),
4179 collapsedBEndMargin, overflowAreas, frameReflowStatus);
4180 if (!frameReflowStatus.IsFullyComplete() &&
4181 ShouldAvoidBreakInside(aState.mReflowInput)) {
4182 *aKeepReflowGoing = false;
4183 aLine->MarkDirty();
4186 if (aLine->SetCarriedOutBEndMargin(collapsedBEndMargin)) {
4187 LineIterator nextLine = aLine;
4188 ++nextLine;
4189 if (nextLine != LinesEnd()) {
4190 nextLine->MarkPreviousMarginDirty();
4194 aLine->SetOverflowAreas(overflowAreas);
4195 if (*aKeepReflowGoing) {
4196 // Some of the child block fit
4198 // Advance to new Y position
4199 nscoord newBCoord = aLine->BEnd();
4200 aState.mBCoord = newBCoord;
4202 // Continue the block frame now if it didn't completely fit in
4203 // the available space.
4204 if (!frameReflowStatus.IsFullyComplete()) {
4205 bool madeContinuation = CreateContinuationFor(aState, nullptr, frame);
4207 nsIFrame* nextFrame = frame->GetNextInFlow();
4208 NS_ASSERTION(nextFrame,
4209 "We're supposed to have a next-in-flow by now");
4211 if (frameReflowStatus.IsIncomplete()) {
4212 // If nextFrame used to be an overflow container, make it a normal
4213 // block
4214 if (!madeContinuation &&
4215 nextFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
4216 nsOverflowContinuationTracker::AutoFinish fini(
4217 aState.mOverflowTracker, frame);
4218 nsContainerFrame* parent = nextFrame->GetParent();
4219 parent->StealFrame(nextFrame);
4220 if (parent != this) {
4221 ReparentFrame(nextFrame, parent, this);
4223 mFrames.InsertFrame(nullptr, frame, nextFrame);
4224 madeContinuation = true; // needs to be added to mLines
4225 nextFrame->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
4226 frameReflowStatus.SetNextInFlowNeedsReflow();
4229 // Push continuation to a new line, but only if we actually made
4230 // one.
4231 if (madeContinuation) {
4232 nsLineBox* line = NewLineBox(nextFrame, true);
4233 mLines.after_insert(aLine, line);
4236 PushTruncatedLine(aState, aLine.next(), aKeepReflowGoing);
4238 // If we need to reflow the continuation of the block child,
4239 // then we'd better reflow our continuation
4240 if (frameReflowStatus.NextInFlowNeedsReflow()) {
4241 aState.mReflowStatus.SetNextInFlowNeedsReflow();
4242 // We also need to make that continuation's line dirty so
4243 // it gets reflowed when we reflow our next in flow. The
4244 // nif's line must always be either a line of the nif's
4245 // parent block (only if we didn't make a continuation) or
4246 // else one of our own overflow lines. In the latter case
4247 // the line is already marked dirty, so just handle the
4248 // first case.
4249 if (!madeContinuation) {
4250 nsBlockFrame* nifBlock = do_QueryFrame(nextFrame->GetParent());
4251 NS_ASSERTION(
4252 nifBlock,
4253 "A block's child's next in flow's parent must be a block!");
4254 for (auto& line : nifBlock->Lines()) {
4255 if (line.Contains(nextFrame)) {
4256 line.MarkDirty();
4257 break;
4263 // The block-end margin for a block is only applied on the last
4264 // flow block. Since we just continued the child block frame,
4265 // we know that line->mFirstChild is not the last flow block
4266 // therefore zero out the running margin value.
4267 #ifdef NOISY_BLOCK_DIR_MARGINS
4268 ListTag(stdout);
4269 printf(": reflow incomplete, frame=");
4270 frame->ListTag(stdout);
4271 printf(" prevBEndMargin=%d, setting to zero\n",
4272 aState.mPrevBEndMargin.get());
4273 #endif
4274 aState.mPrevBEndMargin.Zero();
4275 } else { // frame is complete but its overflow is not complete
4276 // Disconnect the next-in-flow and put it in our overflow tracker
4277 if (!madeContinuation &&
4278 !nextFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
4279 // It already exists, but as a normal next-in-flow, so we need
4280 // to dig it out of the child lists.
4281 nextFrame->GetParent()->StealFrame(nextFrame);
4282 } else if (madeContinuation) {
4283 mFrames.RemoveFrame(nextFrame);
4286 // Put it in our overflow list
4287 aState.mOverflowTracker->Insert(nextFrame, frameReflowStatus);
4288 aState.mReflowStatus.MergeCompletionStatusFrom(frameReflowStatus);
4290 #ifdef NOISY_BLOCK_DIR_MARGINS
4291 ListTag(stdout);
4292 printf(": reflow complete but overflow incomplete for ");
4293 frame->ListTag(stdout);
4294 printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n",
4295 aState.mPrevBEndMargin.get(), collapsedBEndMargin.get());
4296 #endif
4297 aState.mPrevBEndMargin = collapsedBEndMargin;
4299 } else { // frame is fully complete
4300 #ifdef NOISY_BLOCK_DIR_MARGINS
4301 ListTag(stdout);
4302 printf(": reflow complete for ");
4303 frame->ListTag(stdout);
4304 printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n",
4305 aState.mPrevBEndMargin.get(), collapsedBEndMargin.get());
4306 #endif
4307 aState.mPrevBEndMargin = collapsedBEndMargin;
4309 #ifdef NOISY_BLOCK_DIR_MARGINS
4310 ListTag(stdout);
4311 printf(": frame=");
4312 frame->ListTag(stdout);
4313 printf(" carriedOutBEndMargin=%d collapsedBEndMargin=%d => %d\n",
4314 brc.GetCarriedOutBEndMargin().get(), collapsedBEndMargin.get(),
4315 aState.mPrevBEndMargin.get());
4316 #endif
4317 } else {
4318 if (!frameReflowStatus.IsFullyComplete()) {
4319 // The frame reported an incomplete status, but then it also didn't
4320 // fit. This means we need to reflow it again so that it can
4321 // (again) report the incomplete status.
4322 frame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
4325 if ((aLine == mLines.front() && !GetPrevInFlow()) ||
4326 ShouldAvoidBreakInside(aState.mReflowInput)) {
4327 // If it's our very first line *or* we're not at the top of the page
4328 // and we have page-break-inside:avoid, then we need to be pushed to
4329 // our parent's next-in-flow.
4330 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
4331 } else {
4332 // Push the line that didn't fit and any lines that follow it
4333 // to our next-in-flow.
4334 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
4338 break; // out of the reflow retry loop
4341 // Now that we've got its final position all figured out, position any child
4342 // views it may have. Note that the case when frame has a view got handled
4343 // by FinishReflowChild, but that function didn't have the coordinates needed
4344 // to correctly decide whether to reposition child views.
4345 if (originalPosition != frame->GetPosition() && !frame->HasView()) {
4346 nsContainerFrame::PositionChildViews(frame);
4349 #ifdef DEBUG
4350 VerifyLines(true);
4351 #endif
4354 void nsBlockFrame::ReflowInlineFrames(BlockReflowState& aState,
4355 LineIterator aLine,
4356 bool* aKeepReflowGoing) {
4357 *aKeepReflowGoing = true;
4359 aLine->SetLineIsImpactedByFloat(false);
4361 // Setup initial coordinate system for reflowing the inline frames
4362 // into. Apply a previous block frame's block-end margin first.
4363 if (ShouldApplyBStartMargin(aState, aLine)) {
4364 aState.mBCoord += aState.mPrevBEndMargin.get();
4366 nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace();
4368 LineReflowStatus lineReflowStatus;
4369 do {
4370 nscoord availableSpaceBSize = 0;
4371 aState.mLineBSize.reset();
4372 do {
4373 bool allowPullUp = true;
4374 nsIFrame* forceBreakInFrame = nullptr;
4375 int32_t forceBreakOffset = -1;
4376 gfxBreakPriority forceBreakPriority = gfxBreakPriority::eNoBreak;
4377 do {
4378 nsFloatManager::SavedState floatManagerState;
4379 aState.FloatManager()->PushState(&floatManagerState);
4381 // Once upon a time we allocated the first 30 nsLineLayout objects
4382 // on the stack, and then we switched to the heap. At that time
4383 // these objects were large (1100 bytes on a 32 bit system).
4384 // Then the nsLineLayout object was shrunk to 156 bytes by
4385 // removing some internal buffers. Given that it is so much
4386 // smaller, the complexity of 2 different ways of allocating
4387 // no longer makes sense. Now we always allocate on the stack.
4388 nsLineLayout lineLayout(aState.mPresContext, aState.FloatManager(),
4389 aState.mReflowInput, &aLine, nullptr);
4390 lineLayout.Init(&aState, aState.mMinLineHeight, aState.mLineNumber);
4391 if (forceBreakInFrame) {
4392 lineLayout.ForceBreakAtPosition(forceBreakInFrame, forceBreakOffset);
4394 DoReflowInlineFrames(aState, lineLayout, aLine, floatAvailableSpace,
4395 availableSpaceBSize, &floatManagerState,
4396 aKeepReflowGoing, &lineReflowStatus, allowPullUp);
4397 lineLayout.EndLineReflow();
4399 if (LineReflowStatus::RedoNoPull == lineReflowStatus ||
4400 LineReflowStatus::RedoMoreFloats == lineReflowStatus ||
4401 LineReflowStatus::RedoNextBand == lineReflowStatus) {
4402 if (lineLayout.NeedsBackup()) {
4403 NS_ASSERTION(!forceBreakInFrame,
4404 "Backing up twice; this should never be necessary");
4405 // If there is no saved break position, then this will set
4406 // set forceBreakInFrame to null and we won't back up, which is
4407 // correct.
4408 forceBreakInFrame = lineLayout.GetLastOptionalBreakPosition(
4409 &forceBreakOffset, &forceBreakPriority);
4410 } else {
4411 forceBreakInFrame = nullptr;
4413 // restore the float manager state
4414 aState.FloatManager()->PopState(&floatManagerState);
4415 // Clear out float lists
4416 aState.mCurrentLineFloats.Clear();
4417 aState.mBelowCurrentLineFloats.Clear();
4418 aState.mNoWrapFloats.Clear();
4421 // Don't allow pullup on a subsequent LineReflowStatus::RedoNoPull pass
4422 allowPullUp = false;
4423 } while (LineReflowStatus::RedoNoPull == lineReflowStatus);
4424 } while (LineReflowStatus::RedoMoreFloats == lineReflowStatus);
4425 } while (LineReflowStatus::RedoNextBand == lineReflowStatus);
4428 void nsBlockFrame::SetBreakBeforeStatusBeforeLine(BlockReflowState& aState,
4429 LineIterator aLine,
4430 bool* aKeepReflowGoing) {
4431 aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
4432 // Reflow the line again when we reflow at our new position.
4433 aLine->MarkDirty();
4434 *aKeepReflowGoing = false;
4437 void nsBlockFrame::PushTruncatedLine(BlockReflowState& aState,
4438 LineIterator aLine,
4439 bool* aKeepReflowGoing) {
4440 PushLines(aState, aLine.prev());
4441 *aKeepReflowGoing = false;
4442 aState.mReflowStatus.SetIncomplete();
4445 void nsBlockFrame::DoReflowInlineFrames(
4446 BlockReflowState& aState, nsLineLayout& aLineLayout, LineIterator aLine,
4447 nsFlowAreaRect& aFloatAvailableSpace, nscoord& aAvailableSpaceBSize,
4448 nsFloatManager::SavedState* aFloatStateBeforeLine, bool* aKeepReflowGoing,
4449 LineReflowStatus* aLineReflowStatus, bool aAllowPullUp) {
4450 // Forget all of the floats on the line
4451 aLine->ClearFloats();
4452 aState.mFloatOverflowAreas.Clear();
4454 // We need to set this flag on the line if any of our reflow passes
4455 // are impacted by floats.
4456 if (aFloatAvailableSpace.HasFloats()) {
4457 aLine->SetLineIsImpactedByFloat(true);
4459 #ifdef REALLY_NOISY_REFLOW
4460 printf("nsBlockFrame::DoReflowInlineFrames %p impacted = %d\n", this,
4461 aFloatAvailableSpace.HasFloats());
4462 #endif
4464 WritingMode outerWM = aState.mReflowInput.GetWritingMode();
4465 WritingMode lineWM = WritingModeForLine(outerWM, aLine->mFirstChild);
4466 LogicalRect lineRect = aFloatAvailableSpace.mRect.ConvertTo(
4467 lineWM, outerWM, aState.ContainerSize());
4469 nscoord iStart = lineRect.IStart(lineWM);
4470 nscoord availISize = lineRect.ISize(lineWM);
4471 nscoord availBSize;
4472 if (aState.mReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
4473 availBSize = NS_UNCONSTRAINEDSIZE;
4474 } else {
4475 /* XXX get the height right! */
4476 availBSize = lineRect.BSize(lineWM);
4479 // Make sure to enable resize optimization before we call BeginLineReflow
4480 // because it might get disabled there
4481 aLine->EnableResizeReflowOptimization();
4483 aLineLayout.BeginLineReflow(iStart, aState.mBCoord, availISize, availBSize,
4484 aFloatAvailableSpace.HasFloats(),
4485 false, /*XXX isTopOfPage*/
4486 lineWM, aState.mContainerSize);
4488 aState.mFlags.mIsLineLayoutEmpty = false;
4490 // XXX Unfortunately we need to know this before reflowing the first
4491 // inline frame in the line. FIX ME.
4492 if ((0 == aLineLayout.GetLineNumber()) &&
4493 (NS_BLOCK_HAS_FIRST_LETTER_CHILD & mState) &&
4494 (NS_BLOCK_HAS_FIRST_LETTER_STYLE & mState)) {
4495 aLineLayout.SetFirstLetterStyleOK(true);
4497 NS_ASSERTION(
4498 !((NS_BLOCK_HAS_FIRST_LETTER_CHILD & mState) && GetPrevContinuation()),
4499 "first letter child bit should only be on first continuation");
4501 // Reflow the frames that are already on the line first
4502 LineReflowStatus lineReflowStatus = LineReflowStatus::OK;
4503 int32_t i;
4504 nsIFrame* frame = aLine->mFirstChild;
4506 if (aFloatAvailableSpace.HasFloats()) {
4507 // There is a soft break opportunity at the start of the line, because
4508 // we can always move this line down below float(s).
4509 if (aLineLayout.NotifyOptionalBreakPosition(
4510 frame, 0, true, gfxBreakPriority::eNormalBreak)) {
4511 lineReflowStatus = LineReflowStatus::RedoNextBand;
4515 // need to repeatedly call GetChildCount here, because the child
4516 // count can change during the loop!
4517 for (i = 0;
4518 LineReflowStatus::OK == lineReflowStatus && i < aLine->GetChildCount();
4519 i++, frame = frame->GetNextSibling()) {
4520 ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus);
4521 if (LineReflowStatus::OK != lineReflowStatus) {
4522 // It is possible that one or more of next lines are empty
4523 // (because of DeleteNextInFlowChild). If so, delete them now
4524 // in case we are finished.
4525 ++aLine;
4526 while ((aLine != LinesEnd()) && (0 == aLine->GetChildCount())) {
4527 // XXX Is this still necessary now that DeleteNextInFlowChild
4528 // uses DoRemoveFrame?
4529 nsLineBox* toremove = aLine;
4530 aLine = mLines.erase(aLine);
4531 NS_ASSERTION(nullptr == toremove->mFirstChild, "bad empty line");
4532 FreeLineBox(toremove);
4534 --aLine;
4536 NS_ASSERTION(lineReflowStatus != LineReflowStatus::Truncated,
4537 "ReflowInlineFrame should never determine that a line "
4538 "needs to go to the next page/column");
4542 // Don't pull up new frames into lines with continuation placeholders
4543 if (aAllowPullUp) {
4544 // Pull frames and reflow them until we can't
4545 while (LineReflowStatus::OK == lineReflowStatus) {
4546 frame = PullFrame(aState, aLine);
4547 if (!frame) {
4548 break;
4551 while (LineReflowStatus::OK == lineReflowStatus) {
4552 int32_t oldCount = aLine->GetChildCount();
4553 ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus);
4554 if (aLine->GetChildCount() != oldCount) {
4555 // We just created a continuation for aFrame AND its going
4556 // to end up on this line (e.g. :first-letter
4557 // situation). Therefore we have to loop here before trying
4558 // to pull another frame.
4559 frame = frame->GetNextSibling();
4560 } else {
4561 break;
4567 aState.mFlags.mIsLineLayoutEmpty = aLineLayout.LineIsEmpty();
4569 // We only need to backup if the line isn't going to be reflowed again anyway
4570 bool needsBackup = aLineLayout.NeedsBackup() &&
4571 (lineReflowStatus == LineReflowStatus::Stop ||
4572 lineReflowStatus == LineReflowStatus::OK);
4573 if (needsBackup && aLineLayout.HaveForcedBreakPosition()) {
4574 NS_WARNING(
4575 "We shouldn't be backing up more than once! "
4576 "Someone must have set a break opportunity beyond the available width, "
4577 "even though there were better break opportunities before it");
4578 needsBackup = false;
4580 if (needsBackup) {
4581 // We need to try backing up to before a text run
4582 // XXX It's possible, in fact not unusual, for the break opportunity to
4583 // already be the end of the line. We should detect that and optimize to not
4584 // re-do the line.
4585 if (aLineLayout.HasOptionalBreakPosition()) {
4586 // We can back up!
4587 lineReflowStatus = LineReflowStatus::RedoNoPull;
4589 } else {
4590 // In case we reflow this line again, remember that we don't
4591 // need to force any breaking
4592 aLineLayout.ClearOptionalBreakPosition();
4595 if (LineReflowStatus::RedoNextBand == lineReflowStatus) {
4596 // This happens only when we have a line that is impacted by
4597 // floats and the first element in the line doesn't fit with
4598 // the floats.
4600 // If there's block space available, we either try to reflow the line
4601 // past the current band (if it's non-zero and the band definitely won't
4602 // widen around a shape-outside), otherwise we try one pixel down. If
4603 // there's no block space available, we push the line to the next
4604 // page/column.
4605 NS_ASSERTION(
4606 NS_UNCONSTRAINEDSIZE != aFloatAvailableSpace.mRect.BSize(outerWM),
4607 "unconstrained block size on totally empty line");
4609 // See the analogous code for blocks in BlockReflowState::ClearFloats.
4610 nscoord bandBSize = aFloatAvailableSpace.mRect.BSize(outerWM);
4611 if (bandBSize > 0 ||
4612 NS_UNCONSTRAINEDSIZE == aState.mReflowInput.AvailableBSize()) {
4613 NS_ASSERTION(bandBSize == 0 || aFloatAvailableSpace.HasFloats(),
4614 "redo line on totally empty line with non-empty band...");
4615 // We should never hit this case if we've placed floats on the
4616 // line; if we have, then the GetFloatAvailableSpace call is wrong
4617 // and needs to happen after the caller pops the float manager
4618 // state.
4619 aState.FloatManager()->AssertStateMatches(aFloatStateBeforeLine);
4621 if (!aFloatAvailableSpace.MayWiden() && bandBSize > 0) {
4622 // Move it down far enough to clear the current band.
4623 aState.mBCoord += bandBSize;
4624 } else {
4625 // Move it down by one dev pixel.
4626 aState.mBCoord += aState.mPresContext->DevPixelsToAppUnits(1);
4629 aFloatAvailableSpace = aState.GetFloatAvailableSpace();
4630 } else {
4631 // There's nowhere to retry placing the line, so we want to push
4632 // it to the next page/column where its contents can fit not
4633 // next to a float.
4634 lineReflowStatus = LineReflowStatus::Truncated;
4635 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
4638 // XXX: a small optimization can be done here when paginating:
4639 // if the new Y coordinate is past the end of the block then
4640 // push the line and return now instead of later on after we are
4641 // past the float.
4642 } else if (LineReflowStatus::Truncated != lineReflowStatus &&
4643 LineReflowStatus::RedoNoPull != lineReflowStatus) {
4644 // If we are propagating out a break-before status then there is
4645 // no point in placing the line.
4646 if (!aState.mReflowStatus.IsInlineBreakBefore()) {
4647 if (!PlaceLine(aState, aLineLayout, aLine, aFloatStateBeforeLine,
4648 aFloatAvailableSpace, aAvailableSpaceBSize,
4649 aKeepReflowGoing)) {
4650 lineReflowStatus = LineReflowStatus::RedoMoreFloats;
4651 // PlaceLine already called GetFloatAvailableSpaceForBSize or its
4652 // variant for us.
4656 #ifdef DEBUG
4657 if (gNoisyReflow) {
4658 printf("Line reflow status = %s\n",
4659 LineReflowStatusToString(lineReflowStatus));
4661 #endif
4663 if (aLineLayout.GetDirtyNextLine()) {
4664 // aLine may have been pushed to the overflow lines.
4665 FrameLines* overflowLines = GetOverflowLines();
4666 // We can't just compare iterators front() to aLine here, since they may be
4667 // in different lists.
4668 bool pushedToOverflowLines =
4669 overflowLines && overflowLines->mLines.front() == aLine.get();
4670 if (pushedToOverflowLines) {
4671 // aLine is stale, it's associated with the main line list but it should
4672 // be associated with the overflow line list now
4673 aLine = overflowLines->mLines.begin();
4675 nsBlockInFlowLineIterator iter(this, aLine, pushedToOverflowLines);
4676 if (iter.Next() && iter.GetLine()->IsInline()) {
4677 iter.GetLine()->MarkDirty();
4678 if (iter.GetContainer() != this) {
4679 aState.mReflowStatus.SetNextInFlowNeedsReflow();
4684 *aLineReflowStatus = lineReflowStatus;
4688 * Reflow an inline frame. The reflow status is mapped from the frames
4689 * reflow status to the lines reflow status (not to our reflow status).
4690 * The line reflow status is simple: true means keep placing frames
4691 * on the line; false means don't (the line is done). If the line
4692 * has some sort of breaking affect then aLine's break-type will be set
4693 * to something other than StyleClear::None.
4695 void nsBlockFrame::ReflowInlineFrame(BlockReflowState& aState,
4696 nsLineLayout& aLineLayout,
4697 LineIterator aLine, nsIFrame* aFrame,
4698 LineReflowStatus* aLineReflowStatus) {
4699 MOZ_ASSERT(aFrame);
4700 *aLineReflowStatus = LineReflowStatus::OK;
4702 #ifdef NOISY_FIRST_LETTER
4703 ListTag(stdout);
4704 printf(": reflowing ");
4705 aFrame->ListTag(stdout);
4706 printf(" reflowingFirstLetter=%s\n",
4707 aLineLayout.GetFirstLetterStyleOK() ? "on" : "off");
4708 #endif
4710 if (aFrame->IsPlaceholderFrame()) {
4711 auto ph = static_cast<nsPlaceholderFrame*>(aFrame);
4712 ph->ForgetLineIsEmptySoFar();
4715 // Reflow the inline frame
4716 nsReflowStatus frameReflowStatus;
4717 bool pushedFrame;
4718 aLineLayout.ReflowFrame(aFrame, frameReflowStatus, nullptr, pushedFrame);
4720 if (frameReflowStatus.NextInFlowNeedsReflow()) {
4721 aLineLayout.SetDirtyNextLine();
4724 #ifdef REALLY_NOISY_REFLOW
4725 aFrame->ListTag(stdout);
4726 printf(": status=%s\n", ToString(frameReflowStatus).c_str());
4727 #endif
4729 #if defined(REFLOW_STATUS_COVERAGE)
4730 RecordReflowStatus(false, frameReflowStatus);
4731 #endif
4733 // Send post-reflow notification
4734 aState.mPrevChild = aFrame;
4736 /* XXX
4737 This is where we need to add logic to handle some odd behavior.
4738 For one thing, we should usually place at least one thing next
4739 to a left float, even when that float takes up all the width on a line.
4740 see bug 22496
4743 // Process the child frames reflow status. There are 5 cases:
4744 // complete, not-complete, break-before, break-after-complete,
4745 // break-after-not-complete. There are two situations: we are a
4746 // block or we are an inline. This makes a total of 10 cases
4747 // (fortunately, there is some overlap).
4748 aLine->ClearForcedLineBreak();
4749 if (frameReflowStatus.IsInlineBreak() ||
4750 aState.mTrailingClearFromPIF != StyleClear::None) {
4751 // Always abort the line reflow (because a line break is the
4752 // minimal amount of break we do).
4753 *aLineReflowStatus = LineReflowStatus::Stop;
4755 // XXX what should aLine's break-type be set to in all these cases?
4756 if (frameReflowStatus.IsInlineBreakBefore()) {
4757 // Break-before cases.
4758 if (aFrame == aLine->mFirstChild) {
4759 // If we break before the first frame on the line then we must
4760 // be trying to place content where there's no room (e.g. on a
4761 // line with wide floats). Inform the caller to reflow the
4762 // line after skipping past a float.
4763 *aLineReflowStatus = LineReflowStatus::RedoNextBand;
4764 } else {
4765 // It's not the first child on this line so go ahead and split
4766 // the line. We will see the frame again on the next-line.
4767 SplitLine(aState, aLineLayout, aLine, aFrame, aLineReflowStatus);
4769 // If we're splitting the line because the frame didn't fit and it
4770 // was pushed, then mark the line as having word wrapped. We need to
4771 // know that if we're shrink wrapping our width
4772 if (pushedFrame) {
4773 aLine->SetLineWrapped(true);
4776 } else {
4777 MOZ_ASSERT(frameReflowStatus.IsInlineBreakAfter() ||
4778 aState.mTrailingClearFromPIF != StyleClear::None,
4779 "We should've handled inline break-before in the if-branch!");
4781 // If a float split and its prev-in-flow was followed by a <BR>, then
4782 // combine the <BR>'s float clear type with the inline's float clear type
4783 // (the inline will be the very next frame after the split float).
4784 StyleClear clearType = frameReflowStatus.FloatClearType();
4785 if (aState.mTrailingClearFromPIF != StyleClear::None) {
4786 clearType = nsLayoutUtils::CombineClearType(
4787 clearType, aState.mTrailingClearFromPIF);
4788 aState.mTrailingClearFromPIF = StyleClear::None;
4790 // Break-after cases
4791 if (clearType != StyleClear::None || aLineLayout.GetLineEndsInBR()) {
4792 aLine->SetForcedLineBreakAfter(clearType);
4794 if (frameReflowStatus.IsComplete()) {
4795 // Split line, but after the frame just reflowed
4796 SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(),
4797 aLineReflowStatus);
4799 if (frameReflowStatus.IsInlineBreakAfter() &&
4800 !aLineLayout.GetLineEndsInBR()) {
4801 aLineLayout.SetDirtyNextLine();
4807 if (!frameReflowStatus.IsFullyComplete()) {
4808 // Create a continuation for the incomplete frame. Note that the
4809 // frame may already have a continuation.
4810 CreateContinuationFor(aState, aLine, aFrame);
4812 // Remember that the line has wrapped
4813 if (!aLineLayout.GetLineEndsInBR()) {
4814 aLine->SetLineWrapped(true);
4817 // If we just ended a first-letter frame or reflowed a placeholder then
4818 // don't split the line and don't stop the line reflow...
4819 // But if we are going to stop anyways we'd better split the line.
4820 if ((!frameReflowStatus.FirstLetterComplete() &&
4821 !aFrame->IsPlaceholderFrame()) ||
4822 *aLineReflowStatus == LineReflowStatus::Stop) {
4823 // Split line after the current frame
4824 *aLineReflowStatus = LineReflowStatus::Stop;
4825 SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(),
4826 aLineReflowStatus);
4831 bool nsBlockFrame::CreateContinuationFor(BlockReflowState& aState,
4832 nsLineBox* aLine, nsIFrame* aFrame) {
4833 nsIFrame* newFrame = nullptr;
4835 if (!aFrame->GetNextInFlow()) {
4836 newFrame =
4837 PresShell()->FrameConstructor()->CreateContinuingFrame(aFrame, this);
4839 mFrames.InsertFrame(nullptr, aFrame, newFrame);
4841 if (aLine) {
4842 aLine->NoteFrameAdded(newFrame);
4845 #ifdef DEBUG
4846 VerifyLines(false);
4847 #endif
4848 return !!newFrame;
4851 void nsBlockFrame::SplitFloat(BlockReflowState& aState, nsIFrame* aFloat,
4852 const nsReflowStatus& aFloatStatus) {
4853 MOZ_ASSERT(!aFloatStatus.IsFullyComplete(),
4854 "why split the frame if it's fully complete?");
4855 MOZ_ASSERT(aState.mBlock == this);
4857 nsIFrame* nextInFlow = aFloat->GetNextInFlow();
4858 if (nextInFlow) {
4859 nsContainerFrame* oldParent = nextInFlow->GetParent();
4860 oldParent->StealFrame(nextInFlow);
4861 if (oldParent != this) {
4862 ReparentFrame(nextInFlow, oldParent, this);
4864 if (!aFloatStatus.IsOverflowIncomplete()) {
4865 nextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
4867 } else {
4868 nextInFlow =
4869 PresShell()->FrameConstructor()->CreateContinuingFrame(aFloat, this);
4871 if (aFloatStatus.IsOverflowIncomplete()) {
4872 nextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
4875 StyleFloat floatStyle = aFloat->StyleDisplay()->mFloat;
4876 if (floatStyle == StyleFloat::Left) {
4877 aState.FloatManager()->SetSplitLeftFloatAcrossBreak();
4878 } else {
4879 MOZ_ASSERT(floatStyle == StyleFloat::Right, "Unexpected float side!");
4880 aState.FloatManager()->SetSplitRightFloatAcrossBreak();
4883 aState.AppendPushedFloatChain(nextInFlow);
4884 if (MOZ_LIKELY(!HasAnyStateBits(NS_BLOCK_FLOAT_MGR)) ||
4885 MOZ_UNLIKELY(IsTrueOverflowContainer())) {
4886 aState.mReflowStatus.SetOverflowIncomplete();
4887 } else {
4888 aState.mReflowStatus.SetIncomplete();
4892 static bool CheckPlaceholderInLine(nsIFrame* aBlock, nsLineBox* aLine,
4893 nsIFrame* aFloat) {
4894 if (!aFloat) {
4895 return true;
4897 NS_ASSERTION(!aFloat->GetPrevContinuation(),
4898 "float in a line should never be a continuation");
4899 NS_ASSERTION(!aFloat->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
4900 "float in a line should never be a pushed float");
4901 nsIFrame* ph = aFloat->FirstInFlow()->GetPlaceholderFrame();
4902 for (nsIFrame* f = ph; f; f = f->GetParent()) {
4903 if (f->GetParent() == aBlock) {
4904 return aLine->Contains(f);
4907 NS_ASSERTION(false, "aBlock is not an ancestor of aFrame!");
4908 return true;
4911 void nsBlockFrame::SplitLine(BlockReflowState& aState,
4912 nsLineLayout& aLineLayout, LineIterator aLine,
4913 nsIFrame* aFrame,
4914 LineReflowStatus* aLineReflowStatus) {
4915 MOZ_ASSERT(aLine->IsInline(), "illegal SplitLine on block line");
4917 int32_t pushCount =
4918 aLine->GetChildCount() - aLineLayout.GetCurrentSpanCount();
4919 MOZ_ASSERT(pushCount >= 0, "bad push count");
4921 #ifdef DEBUG
4922 if (gNoisyReflow) {
4923 nsIFrame::IndentBy(stdout, gNoiseIndent);
4924 printf("split line: from line=%p pushCount=%d aFrame=",
4925 static_cast<void*>(aLine.get()), pushCount);
4926 if (aFrame) {
4927 aFrame->ListTag(stdout);
4928 } else {
4929 printf("(null)");
4931 printf("\n");
4932 if (gReallyNoisyReflow) {
4933 aLine->List(stdout, gNoiseIndent + 1);
4936 #endif
4938 if (0 != pushCount) {
4939 MOZ_ASSERT(aLine->GetChildCount() > pushCount, "bad push");
4940 MOZ_ASSERT(nullptr != aFrame, "whoops");
4941 #ifdef DEBUG
4943 nsIFrame* f = aFrame;
4944 int32_t count = pushCount;
4945 while (f && count > 0) {
4946 f = f->GetNextSibling();
4947 --count;
4949 NS_ASSERTION(count == 0, "Not enough frames to push");
4951 #endif
4953 // Put frames being split out into their own line
4954 nsLineBox* newLine = NewLineBox(aLine, aFrame, pushCount);
4955 mLines.after_insert(aLine, newLine);
4956 #ifdef DEBUG
4957 if (gReallyNoisyReflow) {
4958 newLine->List(stdout, gNoiseIndent + 1);
4960 #endif
4962 // Let line layout know that some frames are no longer part of its
4963 // state.
4964 aLineLayout.SplitLineTo(aLine->GetChildCount());
4966 // If floats have been placed whose placeholders have been pushed to the new
4967 // line, we need to reflow the old line again. We don't want to look at the
4968 // frames in the new line, because as a large paragraph is laid out the
4969 // we'd get O(N^2) performance. So instead we just check that the last
4970 // float and the last below-current-line float are still in aLine.
4971 if (!CheckPlaceholderInLine(
4972 this, aLine,
4973 aLine->HasFloats() ? aLine->Floats().LastElement() : nullptr) ||
4974 !CheckPlaceholderInLine(
4975 this, aLine,
4976 aState.mBelowCurrentLineFloats.SafeLastElement(nullptr))) {
4977 *aLineReflowStatus = LineReflowStatus::RedoNoPull;
4980 #ifdef DEBUG
4981 VerifyLines(true);
4982 #endif
4986 bool nsBlockFrame::IsLastLine(BlockReflowState& aState, LineIterator aLine) {
4987 while (++aLine != LinesEnd()) {
4988 // There is another line
4989 if (0 != aLine->GetChildCount()) {
4990 // If the next line is a block line then this line is the last in a
4991 // group of inline lines.
4992 return aLine->IsBlock();
4994 // The next line is empty, try the next one
4997 // XXX Not sure about this part
4998 // Try our next-in-flows lines to answer the question
4999 nsBlockFrame* nextInFlow = (nsBlockFrame*)GetNextInFlow();
5000 while (nullptr != nextInFlow) {
5001 for (const auto& line : nextInFlow->Lines()) {
5002 if (0 != line.GetChildCount()) {
5003 return line.IsBlock();
5006 nextInFlow = (nsBlockFrame*)nextInFlow->GetNextInFlow();
5009 // This is the last line - so don't allow justification
5010 return true;
5013 bool nsBlockFrame::PlaceLine(BlockReflowState& aState,
5014 nsLineLayout& aLineLayout, LineIterator aLine,
5015 nsFloatManager::SavedState* aFloatStateBeforeLine,
5016 nsFlowAreaRect& aFlowArea,
5017 nscoord& aAvailableSpaceBSize,
5018 bool* aKeepReflowGoing) {
5019 // Try to position the floats in a nowrap context.
5020 aLineLayout.FlushNoWrapFloats();
5022 // Trim extra white-space from the line before placing the frames
5023 aLineLayout.TrimTrailingWhiteSpace();
5025 // Vertically align the frames on this line.
5027 // According to the CSS2 spec, section 12.6.1, the "marker" box
5028 // participates in the height calculation of the list-item box's
5029 // first line box.
5031 // There are exactly two places a ::marker can be placed: near the
5032 // first or second line. It's only placed on the second line in a
5033 // rare case: when the first line is empty.
5034 WritingMode wm = aState.mReflowInput.GetWritingMode();
5035 bool addedMarker = false;
5036 if (HasOutsideMarker() &&
5037 ((aLine == mLines.front() &&
5038 (!aLineLayout.IsZeroBSize() || (aLine == mLines.back()))) ||
5039 (mLines.front() != mLines.back() && 0 == mLines.front()->BSize() &&
5040 aLine == mLines.begin().next()))) {
5041 ReflowOutput metrics(aState.mReflowInput);
5042 nsIFrame* marker = GetOutsideMarker();
5043 ReflowOutsideMarker(marker, aState, metrics, aState.mBCoord);
5044 NS_ASSERTION(!MarkerIsEmpty() || metrics.BSize(wm) == 0,
5045 "empty ::marker frame took up space");
5046 aLineLayout.AddMarkerFrame(marker, metrics);
5047 addedMarker = true;
5049 aLineLayout.VerticalAlignLine();
5051 // We want to consider the floats in the current line when determining
5052 // whether the float available space is shrunk. If mLineBSize doesn't
5053 // exist, we are in the first pass trying to place the line. Calling
5054 // GetFloatAvailableSpace() like we did in BlockReflowState::AddFloat()
5055 // for UpdateBand().
5057 // floatAvailableSpaceWithOldLineBSize is the float available space with
5058 // the old BSize, but including the floats that were added in this line.
5059 LogicalRect floatAvailableSpaceWithOldLineBSize =
5060 aState.mLineBSize.isNothing()
5061 ? aState.GetFloatAvailableSpace(aLine->BStart()).mRect
5062 : aState
5063 .GetFloatAvailableSpaceForBSize(
5064 aLine->BStart(), aState.mLineBSize.value(), nullptr)
5065 .mRect;
5067 // As we redo for floats, we can't reduce the amount of BSize we're
5068 // checking.
5069 aAvailableSpaceBSize = std::max(aAvailableSpaceBSize, aLine->BSize());
5070 LogicalRect floatAvailableSpaceWithLineBSize =
5071 aState
5072 .GetFloatAvailableSpaceForBSize(aLine->BStart(), aAvailableSpaceBSize,
5073 nullptr)
5074 .mRect;
5076 // If the available space between the floats is smaller now that we
5077 // know the BSize, return false (and cause another pass with
5078 // LineReflowStatus::RedoMoreFloats). We ensure aAvailableSpaceBSize
5079 // never decreases, which means that we can't reduce the set of floats
5080 // we intersect, which means that the available space cannot grow.
5081 if (AvailableSpaceShrunk(wm, floatAvailableSpaceWithOldLineBSize,
5082 floatAvailableSpaceWithLineBSize, false)) {
5083 // Prepare data for redoing the line.
5084 aState.mLineBSize = Some(aLine->BSize());
5086 // Since we want to redo the line, we update aFlowArea by using the
5087 // aFloatStateBeforeLine, which is the float manager's state before the
5088 // line is placed.
5089 LogicalRect oldFloatAvailableSpace(aFlowArea.mRect);
5090 aFlowArea = aState.GetFloatAvailableSpaceForBSize(
5091 aLine->BStart(), aAvailableSpaceBSize, aFloatStateBeforeLine);
5093 NS_ASSERTION(
5094 aFlowArea.mRect.BStart(wm) == oldFloatAvailableSpace.BStart(wm),
5095 "yikes");
5096 // Restore the BSize to the position of the next band.
5097 aFlowArea.mRect.BSize(wm) = oldFloatAvailableSpace.BSize(wm);
5099 // Enforce both IStart() and IEnd() never move outwards to prevent
5100 // infinite grow-shrink loops.
5101 const nscoord iStartDiff =
5102 aFlowArea.mRect.IStart(wm) - oldFloatAvailableSpace.IStart(wm);
5103 const nscoord iEndDiff =
5104 aFlowArea.mRect.IEnd(wm) - oldFloatAvailableSpace.IEnd(wm);
5105 if (iStartDiff < 0) {
5106 aFlowArea.mRect.IStart(wm) -= iStartDiff;
5107 aFlowArea.mRect.ISize(wm) += iStartDiff;
5109 if (iEndDiff > 0) {
5110 aFlowArea.mRect.ISize(wm) -= iEndDiff;
5113 return false;
5116 #ifdef DEBUG
5117 if (!GetParent()->IsAbsurdSizeAssertSuppressed()) {
5118 static nscoord lastHeight = 0;
5119 if (ABSURD_SIZE(aLine->BStart())) {
5120 lastHeight = aLine->BStart();
5121 if (abs(aLine->BStart() - lastHeight) > ABSURD_COORD / 10) {
5122 nsIFrame::ListTag(stdout);
5123 printf(": line=%p y=%d line.bounds.height=%d\n",
5124 static_cast<void*>(aLine.get()), aLine->BStart(),
5125 aLine->BSize());
5127 } else {
5128 lastHeight = 0;
5131 #endif
5133 // Only block frames horizontally align their children because
5134 // inline frames "shrink-wrap" around their children (therefore
5135 // there is no extra horizontal space).
5136 const nsStyleText* styleText = StyleText();
5139 * We don't care checking for IsLastLine properly if we don't care (if it
5140 * can't change the used text-align value for the line).
5142 * In other words, isLastLine really means isLastLineAndWeCare.
5144 const bool isLastLine =
5145 !SVGUtils::IsInSVGTextSubtree(this) &&
5146 styleText->TextAlignForLastLine() != styleText->mTextAlign &&
5147 (aLineLayout.GetLineEndsInBR() || IsLastLine(aState, aLine));
5149 aLineLayout.TextAlignLine(aLine, isLastLine);
5151 // From here on, pfd->mBounds rectangles are incorrect because bidi
5152 // might have moved frames around!
5153 OverflowAreas overflowAreas;
5154 aLineLayout.RelativePositionFrames(overflowAreas);
5155 aLine->SetOverflowAreas(overflowAreas);
5156 if (addedMarker) {
5157 aLineLayout.RemoveMarkerFrame(GetOutsideMarker());
5160 // Inline lines do not have margins themselves; however they are
5161 // impacted by prior block margins. If this line ends up having some
5162 // height then we zero out the previous block-end margin value that was
5163 // already applied to the line's starting Y coordinate. Otherwise we
5164 // leave it be so that the previous blocks block-end margin can be
5165 // collapsed with a block that follows.
5166 nscoord newBCoord;
5168 if (!aLine->CachedIsEmpty()) {
5169 // This line has some height. Therefore the application of the
5170 // previous-bottom-margin should stick.
5171 aState.mPrevBEndMargin.Zero();
5172 newBCoord = aLine->BEnd();
5173 } else {
5174 // Don't let the previous-bottom-margin value affect the newBCoord
5175 // coordinate (it was applied in ReflowInlineFrames speculatively)
5176 // since the line is empty.
5177 // We already called |ShouldApplyBStartMargin|, and if we applied it
5178 // then mShouldApplyBStartMargin is set.
5179 nscoord dy = aState.mFlags.mShouldApplyBStartMargin
5180 ? -aState.mPrevBEndMargin.get()
5181 : 0;
5182 newBCoord = aState.mBCoord + dy;
5185 if (!aState.mReflowStatus.IsFullyComplete() &&
5186 ShouldAvoidBreakInside(aState.mReflowInput)) {
5187 aLine->AppendFloats(std::move(aState.mCurrentLineFloats));
5188 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
5189 return true;
5192 // See if the line fit (our first line always does).
5193 if (mLines.front() != aLine &&
5194 aState.ContentBSize() != NS_UNCONSTRAINEDSIZE &&
5195 newBCoord > aState.ContentBEnd()) {
5196 NS_ASSERTION(aState.mCurrentLine == aLine, "oops");
5197 if (ShouldAvoidBreakInside(aState.mReflowInput)) {
5198 // All our content doesn't fit, start on the next page.
5199 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
5200 } else {
5201 // Push aLine and all of its children and anything else that
5202 // follows to our next-in-flow.
5203 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
5205 return true;
5208 // Note that any early return before this update of aState.mBCoord
5209 // must either (a) return false or (b) set aKeepReflowGoing to false.
5210 // Otherwise we'll keep reflowing later lines at an incorrect
5211 // position, and we might not come back and clean up the damage later.
5212 aState.mBCoord = newBCoord;
5214 // Add the already placed current-line floats to the line
5215 aLine->AppendFloats(std::move(aState.mCurrentLineFloats));
5217 // Any below current line floats to place?
5218 if (!aState.mBelowCurrentLineFloats.IsEmpty()) {
5219 // Reflow the below-current-line floats, which places on the line's
5220 // float list.
5221 aState.PlaceBelowCurrentLineFloats(aLine);
5224 // When a line has floats, factor them into the overflow areas computations.
5225 if (aLine->HasFloats()) {
5226 // Union the float overflow areas (stored in aState) and the value computed
5227 // by the line layout code.
5228 OverflowAreas lineOverflowAreas = aState.mFloatOverflowAreas;
5229 lineOverflowAreas.UnionWith(aLine->GetOverflowAreas());
5230 aLine->SetOverflowAreas(lineOverflowAreas);
5232 #ifdef NOISY_OVERFLOW_AREAS
5233 printf("%s: Line %p, InkOverflowRect=%s, ScrollableOverflowRect=%s\n",
5234 ListTag().get(), aLine.get(),
5235 ToString(aLine->InkOverflowRect()).c_str(),
5236 ToString(aLine->ScrollableOverflowRect()).c_str());
5237 #endif
5240 // Apply break-after clearing if necessary
5241 // This must stay in sync with |ReflowDirtyLines|.
5242 if (aLine->HasFloatClearTypeAfter()) {
5243 std::tie(aState.mBCoord, std::ignore) =
5244 aState.ClearFloats(aState.mBCoord, aLine->FloatClearTypeAfter());
5246 return true;
5249 void nsBlockFrame::PushLines(BlockReflowState& aState,
5250 nsLineList::iterator aLineBefore) {
5251 // NOTE: aLineBefore is always a normal line, not an overflow line.
5252 // The following expression will assert otherwise.
5253 DebugOnly<bool> check = aLineBefore == mLines.begin();
5255 nsLineList::iterator overBegin(aLineBefore.next());
5257 // PushTruncatedPlaceholderLine sometimes pushes the first line. Ugh.
5258 bool firstLine = overBegin == LinesBegin();
5260 if (overBegin != LinesEnd()) {
5261 // Remove floats in the lines from mFloats
5262 nsFrameList floats;
5263 CollectFloats(overBegin->mFirstChild, floats, true);
5265 if (floats.NotEmpty()) {
5266 #ifdef DEBUG
5267 for (nsIFrame* f : floats) {
5268 MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
5269 "CollectFloats should've removed that bit");
5271 #endif
5272 // Push the floats onto the front of the overflow out-of-flows list
5273 nsAutoOOFFrameList oofs(this);
5274 oofs.mList.InsertFrames(nullptr, nullptr, std::move(floats));
5277 // overflow lines can already exist in some cases, in particular,
5278 // when shrinkwrapping and we discover that the shrinkwap causes
5279 // the height of some child block to grow which creates additional
5280 // overflowing content. In such cases we must prepend the new
5281 // overflow to the existing overflow.
5282 FrameLines* overflowLines = RemoveOverflowLines();
5283 if (!overflowLines) {
5284 // XXXldb use presshell arena!
5285 overflowLines = new FrameLines();
5287 if (overflowLines) {
5288 nsIFrame* lineBeforeLastFrame;
5289 if (firstLine) {
5290 lineBeforeLastFrame = nullptr; // removes all frames
5291 } else {
5292 nsIFrame* f = overBegin->mFirstChild;
5293 lineBeforeLastFrame = f ? f->GetPrevSibling() : mFrames.LastChild();
5294 NS_ASSERTION(!f || lineBeforeLastFrame == aLineBefore->LastChild(),
5295 "unexpected line frames");
5297 nsFrameList pushedFrames = mFrames.TakeFramesAfter(lineBeforeLastFrame);
5298 overflowLines->mFrames.InsertFrames(nullptr, nullptr,
5299 std::move(pushedFrames));
5301 overflowLines->mLines.splice(overflowLines->mLines.begin(), mLines,
5302 overBegin, LinesEnd());
5303 NS_ASSERTION(!overflowLines->mLines.empty(), "should not be empty");
5304 // this takes ownership but it won't delete it immediately so we
5305 // can keep using it.
5306 SetOverflowLines(overflowLines);
5308 // Mark all the overflow lines dirty so that they get reflowed when
5309 // they are pulled up by our next-in-flow.
5311 // XXXldb Can this get called O(N) times making the whole thing O(N^2)?
5312 for (LineIterator line = overflowLines->mLines.begin(),
5313 line_end = overflowLines->mLines.end();
5314 line != line_end; ++line) {
5315 line->MarkDirty();
5316 line->MarkPreviousMarginDirty();
5317 line->SetMovedFragments();
5318 line->SetBoundsEmpty();
5319 if (line->HasFloats()) {
5320 line->ClearFloats();
5326 #ifdef DEBUG
5327 VerifyOverflowSituation();
5328 #endif
5331 // The overflowLines property is stored as a pointer to a line list,
5332 // which must be deleted. However, the following functions all maintain
5333 // the invariant that the property is never set if the list is empty.
5335 bool nsBlockFrame::DrainOverflowLines() {
5336 #ifdef DEBUG
5337 VerifyOverflowSituation();
5338 #endif
5340 // Steal the prev-in-flow's overflow lines and prepend them.
5341 bool didFindOverflow = false;
5342 nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow());
5343 if (prevBlock) {
5344 prevBlock->ClearLineCursors();
5345 FrameLines* overflowLines = prevBlock->RemoveOverflowLines();
5346 if (overflowLines) {
5347 // Make all the frames on the overflow line list mine.
5348 ReparentFrames(overflowLines->mFrames, prevBlock, this);
5350 // Collect overflow containers from our OverflowContainers list that are
5351 // continuations from the frames we picked up from our prev-in-flow, then
5352 // prepend those to ExcessOverflowContainers to ensure the continuations
5353 // are ordered.
5354 if (GetOverflowContainers()) {
5355 nsFrameList ocContinuations;
5356 for (auto* f : overflowLines->mFrames) {
5357 auto* cont = f;
5358 bool done = false;
5359 while (!done && (cont = cont->GetNextContinuation()) &&
5360 cont->GetParent() == this) {
5361 bool onlyChild = !cont->GetPrevSibling() && !cont->GetNextSibling();
5362 if (cont->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER) &&
5363 TryRemoveFrame(OverflowContainersProperty(), cont)) {
5364 ocContinuations.AppendFrame(nullptr, cont);
5365 done = onlyChild;
5366 continue;
5368 break;
5370 if (done) {
5371 break;
5374 if (!ocContinuations.IsEmpty()) {
5375 if (nsFrameList* eoc = GetExcessOverflowContainers()) {
5376 eoc->InsertFrames(nullptr, nullptr, std::move(ocContinuations));
5377 } else {
5378 SetExcessOverflowContainers(std::move(ocContinuations));
5383 // Make the overflow out-of-flow frames mine too.
5384 nsAutoOOFFrameList oofs(prevBlock);
5385 if (oofs.mList.NotEmpty()) {
5386 // In case we own any next-in-flows of any of the drained frames, then
5387 // move those to the PushedFloat list.
5388 nsFrameList pushedFloats;
5389 for (nsIFrame* f : oofs.mList) {
5390 nsIFrame* nif = f->GetNextInFlow();
5391 for (; nif && nif->GetParent() == this; nif = nif->GetNextInFlow()) {
5392 MOZ_ASSERT(nif->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT));
5393 RemoveFloat(nif);
5394 pushedFloats.AppendFrame(nullptr, nif);
5397 ReparentFrames(oofs.mList, prevBlock, this);
5398 mFloats.InsertFrames(nullptr, nullptr, std::move(oofs.mList));
5399 if (!pushedFloats.IsEmpty()) {
5400 nsFrameList* pf = EnsurePushedFloats();
5401 pf->InsertFrames(nullptr, nullptr, std::move(pushedFloats));
5405 if (!mLines.empty()) {
5406 // Remember to recompute the margins on the first line. This will
5407 // also recompute the correct deltaBCoord if necessary.
5408 mLines.front()->MarkPreviousMarginDirty();
5410 // The overflow lines have already been marked dirty and their previous
5411 // margins marked dirty also.
5413 // Prepend the overflow frames/lines to our principal list.
5414 mFrames.InsertFrames(nullptr, nullptr, std::move(overflowLines->mFrames));
5415 mLines.splice(mLines.begin(), overflowLines->mLines);
5416 NS_ASSERTION(overflowLines->mLines.empty(), "splice should empty list");
5417 delete overflowLines;
5418 didFindOverflow = true;
5422 // Now append our own overflow lines.
5423 return DrainSelfOverflowList() || didFindOverflow;
5426 bool nsBlockFrame::DrainSelfOverflowList() {
5427 UniquePtr<FrameLines> ourOverflowLines(RemoveOverflowLines());
5428 if (!ourOverflowLines) {
5429 return false;
5432 // No need to reparent frames in our own overflow lines/oofs, because they're
5433 // already ours. But we should put overflow floats back in mFloats.
5434 // (explicit scope to remove the OOF list before VerifyOverflowSituation)
5436 nsAutoOOFFrameList oofs(this);
5437 if (oofs.mList.NotEmpty()) {
5438 #ifdef DEBUG
5439 for (nsIFrame* f : oofs.mList) {
5440 MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
5441 "CollectFloats should've removed that bit");
5443 #endif
5444 // The overflow floats go after our regular floats.
5445 mFloats.AppendFrames(nullptr, std::move(oofs).mList);
5448 if (!ourOverflowLines->mLines.empty()) {
5449 mFrames.AppendFrames(nullptr, std::move(ourOverflowLines->mFrames));
5450 mLines.splice(mLines.end(), ourOverflowLines->mLines);
5453 #ifdef DEBUG
5454 VerifyOverflowSituation();
5455 #endif
5456 return true;
5460 * Pushed floats are floats whose placeholders are in a previous
5461 * continuation. They might themselves be next-continuations of a float
5462 * that partially fit in an earlier continuation, or they might be the
5463 * first continuation of a float that couldn't be placed at all.
5465 * Pushed floats live permanently at the beginning of a block's float
5466 * list, where they must live *before* any floats whose placeholders are
5467 * in that block.
5469 * Temporarily, during reflow, they also live on the pushed floats list,
5470 * which only holds them between (a) when one continuation pushes them to
5471 * its pushed floats list because they don't fit and (b) when the next
5472 * continuation pulls them onto the beginning of its float list.
5474 * DrainPushedFloats sets up pushed floats the way we need them at the
5475 * start of reflow; they are then reflowed by ReflowPushedFloats (which
5476 * might push some of them on). Floats with placeholders in this block
5477 * are reflowed by (BlockReflowState/nsLineLayout)::AddFloat, which
5478 * also maintains these invariants.
5480 * DrainSelfPushedFloats moves any pushed floats from this block's own
5481 * PushedFloats list back into mFloats. DrainPushedFloats additionally
5482 * moves frames from its prev-in-flow's PushedFloats list into mFloats.
5484 void nsBlockFrame::DrainSelfPushedFloats() {
5485 // If we're getting reflowed multiple times without our
5486 // next-continuation being reflowed, we might need to pull back floats
5487 // that we just put in the list to be pushed to our next-in-flow.
5488 // We don't want to pull back any next-in-flows of floats on our own
5489 // float list, and we only need to pull back first-in-flows whose
5490 // placeholders were in earlier blocks (since first-in-flows whose
5491 // placeholders are in this block will get pulled appropriately by
5492 // AddFloat, and will then be more likely to be in the correct order).
5493 // FIXME: What if there's a continuation in our pushed floats list
5494 // whose prev-in-flow is in a previous continuation of this block
5495 // rather than this block? Might we need to pull it back so we don't
5496 // report ourselves complete?
5497 // FIXME: Maybe we should just pull all of them back?
5498 nsPresContext* presContext = PresContext();
5499 nsFrameList* ourPushedFloats = GetPushedFloats();
5500 if (ourPushedFloats) {
5501 // When we pull back floats, we want to put them with the pushed
5502 // floats, which must live at the start of our float list, but we
5503 // want them at the end of those pushed floats.
5504 // FIXME: This isn't quite right! What if they're all pushed floats?
5505 nsIFrame* insertionPrevSibling = nullptr; /* beginning of list */
5506 for (nsIFrame* f = mFloats.FirstChild();
5507 f && f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT);
5508 f = f->GetNextSibling()) {
5509 insertionPrevSibling = f;
5512 for (nsIFrame *f = ourPushedFloats->LastChild(), *next; f; f = next) {
5513 next = f->GetPrevSibling();
5515 if (f->GetPrevContinuation()) {
5516 // FIXME
5517 } else {
5518 nsPlaceholderFrame* placeholder = f->GetPlaceholderFrame();
5519 nsIFrame* floatOriginalParent =
5520 presContext->PresShell()
5521 ->FrameConstructor()
5522 ->GetFloatContainingBlock(placeholder);
5523 if (floatOriginalParent != this) {
5524 // This is a first continuation that was pushed from one of our
5525 // previous continuations. Take it out of the pushed floats
5526 // list and put it in our floats list, before any of our
5527 // floats, but after other pushed floats.
5528 ourPushedFloats->RemoveFrame(f);
5529 mFloats.InsertFrame(nullptr, insertionPrevSibling, f);
5534 if (ourPushedFloats->IsEmpty()) {
5535 RemovePushedFloats()->Delete(presContext->PresShell());
5540 void nsBlockFrame::DrainPushedFloats() {
5541 DrainSelfPushedFloats();
5543 // After our prev-in-flow has completed reflow, it may have a pushed
5544 // floats list, containing floats that we need to own. Take these.
5545 nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow());
5546 if (prevBlock) {
5547 AutoFrameListPtr list(PresContext(), prevBlock->RemovePushedFloats());
5548 if (list && list->NotEmpty()) {
5549 mFloats.InsertFrames(this, nullptr, std::move(*list));
5554 nsBlockFrame::FrameLines* nsBlockFrame::GetOverflowLines() const {
5555 if (!HasOverflowLines()) {
5556 return nullptr;
5558 FrameLines* prop = GetProperty(OverflowLinesProperty());
5559 NS_ASSERTION(
5560 prop && !prop->mLines.empty() &&
5561 prop->mLines.front()->GetChildCount() == 0
5562 ? prop->mFrames.IsEmpty()
5563 : prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(),
5564 "value should always be stored and non-empty when state set");
5565 return prop;
5568 nsBlockFrame::FrameLines* nsBlockFrame::RemoveOverflowLines() {
5569 if (!HasOverflowLines()) {
5570 return nullptr;
5572 FrameLines* prop = TakeProperty(OverflowLinesProperty());
5573 NS_ASSERTION(
5574 prop && !prop->mLines.empty() &&
5575 prop->mLines.front()->GetChildCount() == 0
5576 ? prop->mFrames.IsEmpty()
5577 : prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(),
5578 "value should always be stored and non-empty when state set");
5579 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
5580 return prop;
5583 void nsBlockFrame::DestroyOverflowLines() {
5584 NS_ASSERTION(HasOverflowLines(), "huh?");
5585 FrameLines* prop = TakeProperty(OverflowLinesProperty());
5586 NS_ASSERTION(prop && prop->mLines.empty(),
5587 "value should always be stored but empty when destroying");
5588 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
5589 delete prop;
5592 // This takes ownership of aOverflowLines.
5593 // XXX We should allocate overflowLines from presShell arena!
5594 void nsBlockFrame::SetOverflowLines(FrameLines* aOverflowLines) {
5595 NS_ASSERTION(aOverflowLines, "null lines");
5596 NS_ASSERTION(!aOverflowLines->mLines.empty(), "empty lines");
5597 NS_ASSERTION(aOverflowLines->mLines.front()->mFirstChild ==
5598 aOverflowLines->mFrames.FirstChild(),
5599 "invalid overflow lines / frames");
5600 NS_ASSERTION(!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_LINES),
5601 "Overwriting existing overflow lines");
5603 // Verify that we won't overwrite an existing overflow list
5604 NS_ASSERTION(!GetProperty(OverflowLinesProperty()), "existing overflow list");
5605 SetProperty(OverflowLinesProperty(), aOverflowLines);
5606 AddStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
5609 nsFrameList* nsBlockFrame::GetOverflowOutOfFlows() const {
5610 if (!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
5611 return nullptr;
5613 nsFrameList* result = GetProperty(OverflowOutOfFlowsProperty());
5614 NS_ASSERTION(result, "value should always be non-empty when state set");
5615 return result;
5618 void nsBlockFrame::SetOverflowOutOfFlows(nsFrameList&& aList,
5619 nsFrameList* aPropValue) {
5620 MOZ_ASSERT(
5621 HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS) == !!aPropValue,
5622 "state does not match value");
5624 if (aList.IsEmpty()) {
5625 if (!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
5626 return;
5628 nsFrameList* list = TakeProperty(OverflowOutOfFlowsProperty());
5629 NS_ASSERTION(aPropValue == list, "prop value mismatch");
5630 list->Clear();
5631 list->Delete(PresShell());
5632 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
5633 } else if (HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
5634 NS_ASSERTION(aPropValue == GetProperty(OverflowOutOfFlowsProperty()),
5635 "prop value mismatch");
5636 *aPropValue = std::move(aList);
5637 } else {
5638 SetProperty(OverflowOutOfFlowsProperty(),
5639 new (PresShell()) nsFrameList(std::move(aList)));
5640 AddStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
5644 nsIFrame* nsBlockFrame::GetInsideMarker() const {
5645 if (!HasInsideMarker()) {
5646 return nullptr;
5648 NS_ASSERTION(!HasOutsideMarker(), "invalid marker state");
5649 nsIFrame* frame = GetProperty(InsideMarkerProperty());
5650 NS_ASSERTION(frame, "bogus inside ::marker frame");
5651 return frame;
5654 nsIFrame* nsBlockFrame::GetOutsideMarker() const {
5655 nsFrameList* list = GetOutsideMarkerList();
5656 return list ? list->FirstChild() : nullptr;
5659 nsFrameList* nsBlockFrame::GetOutsideMarkerList() const {
5660 if (!HasOutsideMarker()) {
5661 return nullptr;
5663 NS_ASSERTION(!HasInsideMarker(), "invalid marker state");
5664 nsFrameList* list = GetProperty(OutsideMarkerProperty());
5665 NS_ASSERTION(list && list->GetLength() == 1, "bogus outside ::marker list");
5666 return list;
5669 nsFrameList* nsBlockFrame::GetPushedFloats() const {
5670 if (!HasPushedFloats()) {
5671 return nullptr;
5673 nsFrameList* result = GetProperty(PushedFloatProperty());
5674 NS_ASSERTION(result, "value should always be non-empty when state set");
5675 return result;
5678 nsFrameList* nsBlockFrame::EnsurePushedFloats() {
5679 nsFrameList* result = GetPushedFloats();
5680 if (result) {
5681 return result;
5684 result = new (PresShell()) nsFrameList;
5685 SetProperty(PushedFloatProperty(), result);
5686 AddStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
5688 return result;
5691 nsFrameList* nsBlockFrame::RemovePushedFloats() {
5692 if (!HasPushedFloats()) {
5693 return nullptr;
5695 nsFrameList* result = TakeProperty(PushedFloatProperty());
5696 RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
5697 NS_ASSERTION(result, "value should always be non-empty when state set");
5698 return result;
5701 //////////////////////////////////////////////////////////////////////
5702 // Frame list manipulation routines
5704 void nsBlockFrame::AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) {
5705 if (aFrameList.IsEmpty()) {
5706 return;
5708 if (aListID != FrameChildListID::Principal) {
5709 if (FrameChildListID::Float == aListID) {
5710 DrainSelfPushedFloats(); // ensure the last frame is in mFloats
5711 mFloats.AppendFrames(nullptr, std::move(aFrameList));
5712 return;
5714 MOZ_ASSERT(FrameChildListID::NoReflowPrincipal == aListID,
5715 "unexpected child list");
5718 // Find the proper last-child for where the append should go
5719 nsIFrame* lastKid = mFrames.LastChild();
5720 NS_ASSERTION(
5721 (mLines.empty() ? nullptr : mLines.back()->LastChild()) == lastKid,
5722 "out-of-sync mLines / mFrames");
5724 #ifdef NOISY_REFLOW_REASON
5725 ListTag(stdout);
5726 printf(": append ");
5727 for (nsIFrame* frame : aFrameList) {
5728 frame->ListTag(out);
5730 if (lastKid) {
5731 printf(" after ");
5732 lastKid->ListTag(stdout);
5734 printf("\n");
5735 #endif
5737 if (SVGUtils::IsInSVGTextSubtree(this)) {
5738 MOZ_ASSERT(GetParent()->IsSVGTextFrame(),
5739 "unexpected block frame in SVG text");
5740 // Workaround for bug 1399425 in case this bit has been removed from the
5741 // SVGTextFrame just before the parser adds more descendant nodes.
5742 GetParent()->AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY);
5745 AddFrames(std::move(aFrameList), lastKid, nullptr);
5746 if (aListID != FrameChildListID::NoReflowPrincipal) {
5747 PresShell()->FrameNeedsReflow(
5748 this, IntrinsicDirty::TreeChange,
5749 NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
5753 void nsBlockFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
5754 const nsLineList::iterator* aPrevFrameLine,
5755 nsFrameList&& aFrameList) {
5756 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
5757 "inserting after sibling frame with different parent");
5759 if (aListID != FrameChildListID::Principal) {
5760 if (FrameChildListID::Float == aListID) {
5761 DrainSelfPushedFloats(); // ensure aPrevFrame is in mFloats
5762 mFloats.InsertFrames(this, aPrevFrame, std::move(aFrameList));
5763 return;
5765 MOZ_ASSERT(FrameChildListID::NoReflowPrincipal == aListID,
5766 "unexpected child list");
5769 #ifdef NOISY_REFLOW_REASON
5770 ListTag(stdout);
5771 printf(": insert ");
5772 for (nsIFrame* frame : aFrameList) {
5773 frame->ListTag(out);
5775 if (aPrevFrame) {
5776 printf(" after ");
5777 aPrevFrame->ListTag(stdout);
5779 printf("\n");
5780 #endif
5782 AddFrames(std::move(aFrameList), aPrevFrame, aPrevFrameLine);
5783 if (aListID != FrameChildListID::NoReflowPrincipal) {
5784 PresShell()->FrameNeedsReflow(
5785 this, IntrinsicDirty::TreeChange,
5786 NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
5790 void nsBlockFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) {
5791 #ifdef NOISY_REFLOW_REASON
5792 ListTag(stdout);
5793 printf(": remove ");
5794 aOldFrame->ListTag(stdout);
5795 printf("\n");
5796 #endif
5798 if (aListID == FrameChildListID::Principal) {
5799 bool hasFloats = BlockHasAnyFloats(aOldFrame);
5800 DoRemoveFrame(aOldFrame, REMOVE_FIXED_CONTINUATIONS);
5801 if (hasFloats) {
5802 MarkSameFloatManagerLinesDirty(this);
5804 } else if (FrameChildListID::Float == aListID) {
5805 // Make sure to mark affected lines dirty for the float frame
5806 // we are removing; this way is a bit messy, but so is the rest of the code.
5807 // See bug 390762.
5808 NS_ASSERTION(!aOldFrame->GetPrevContinuation(),
5809 "RemoveFrame should not be called on pushed floats.");
5810 for (nsIFrame* f = aOldFrame;
5811 f && !f->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
5812 f = f->GetNextContinuation()) {
5813 MarkSameFloatManagerLinesDirty(
5814 static_cast<nsBlockFrame*>(f->GetParent()));
5816 DoRemoveOutOfFlowFrame(aOldFrame);
5817 } else if (FrameChildListID::NoReflowPrincipal == aListID) {
5818 // Skip the call to |FrameNeedsReflow| below by returning now.
5819 DoRemoveFrame(aOldFrame, REMOVE_FIXED_CONTINUATIONS);
5820 return;
5821 } else {
5822 MOZ_CRASH("unexpected child list");
5825 PresShell()->FrameNeedsReflow(
5826 this, IntrinsicDirty::TreeChange,
5827 NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
5830 static bool ShouldPutNextSiblingOnNewLine(nsIFrame* aLastFrame) {
5831 LayoutFrameType type = aLastFrame->Type();
5832 if (type == LayoutFrameType::Br) {
5833 return true;
5835 // XXX the TEXT_OFFSETS_NEED_FIXING check is a wallpaper for bug 822910.
5836 if (type == LayoutFrameType::Text &&
5837 !aLastFrame->HasAnyStateBits(TEXT_OFFSETS_NEED_FIXING)) {
5838 return aLastFrame->HasSignificantTerminalNewline();
5840 return false;
5843 void nsBlockFrame::AddFrames(nsFrameList&& aFrameList, nsIFrame* aPrevSibling,
5844 const nsLineList::iterator* aPrevSiblingLine) {
5845 // Clear our line cursor, since our lines may change.
5846 ClearLineCursors();
5848 if (aFrameList.IsEmpty()) {
5849 return;
5852 // Attempt to find the line that contains the previous sibling
5853 nsLineList* lineList = &mLines;
5854 nsFrameList* frames = &mFrames;
5855 nsLineList::iterator prevSibLine;
5856 int32_t prevSiblingIndex;
5857 if (aPrevSiblingLine) {
5858 MOZ_ASSERT(aPrevSibling);
5859 prevSibLine = *aPrevSiblingLine;
5860 FrameLines* overflowLines = GetOverflowLines();
5861 MOZ_ASSERT(prevSibLine.IsInSameList(mLines.begin()) ||
5862 (overflowLines &&
5863 prevSibLine.IsInSameList(overflowLines->mLines.begin())),
5864 "must be one of our line lists");
5865 if (overflowLines) {
5866 // We need to find out which list it's actually in. Assume that
5867 // *if* we have overflow lines, that our primary lines aren't
5868 // huge, but our overflow lines might be.
5869 nsLineList::iterator line = mLines.begin(), lineEnd = mLines.end();
5870 while (line != lineEnd) {
5871 if (line == prevSibLine) {
5872 break;
5874 ++line;
5876 if (line == lineEnd) {
5877 // By elimination, the line must be in our overflow lines.
5878 lineList = &overflowLines->mLines;
5879 frames = &overflowLines->mFrames;
5883 nsLineList::iterator nextLine = prevSibLine.next();
5884 nsIFrame* lastFrameInLine = nextLine == lineList->end()
5885 ? frames->LastChild()
5886 : nextLine->mFirstChild->GetPrevSibling();
5887 prevSiblingIndex = prevSibLine->RIndexOf(aPrevSibling, lastFrameInLine);
5888 MOZ_ASSERT(prevSiblingIndex >= 0,
5889 "aPrevSibling must be in aPrevSiblingLine");
5890 } else {
5891 prevSibLine = lineList->end();
5892 prevSiblingIndex = -1;
5893 if (aPrevSibling) {
5894 // XXX_perf This is technically O(N^2) in some cases, but by using
5895 // RFind instead of Find, we make it O(N) in the most common case,
5896 // which is appending content.
5898 // Find the line that contains the previous sibling
5899 if (!nsLineBox::RFindLineContaining(aPrevSibling, lineList->begin(),
5900 prevSibLine, mFrames.LastChild(),
5901 &prevSiblingIndex)) {
5902 // Not in mLines - try overflow lines.
5903 FrameLines* overflowLines = GetOverflowLines();
5904 bool found = false;
5905 if (overflowLines) {
5906 prevSibLine = overflowLines->mLines.end();
5907 prevSiblingIndex = -1;
5908 found = nsLineBox::RFindLineContaining(
5909 aPrevSibling, overflowLines->mLines.begin(), prevSibLine,
5910 overflowLines->mFrames.LastChild(), &prevSiblingIndex);
5912 if (MOZ_LIKELY(found)) {
5913 lineList = &overflowLines->mLines;
5914 frames = &overflowLines->mFrames;
5915 } else {
5916 // Note: defensive code! RFindLineContaining must not return
5917 // false in this case, so if it does...
5918 MOZ_ASSERT_UNREACHABLE("prev sibling not in line list");
5919 aPrevSibling = nullptr;
5920 prevSibLine = lineList->end();
5926 // Find the frame following aPrevSibling so that we can join up the
5927 // two lists of frames.
5928 if (aPrevSibling) {
5929 // Split line containing aPrevSibling in two if the insertion
5930 // point is somewhere in the middle of the line.
5931 int32_t rem = prevSibLine->GetChildCount() - prevSiblingIndex - 1;
5932 if (rem) {
5933 // Split the line in two where the frame(s) are being inserted.
5934 nsLineBox* line =
5935 NewLineBox(prevSibLine, aPrevSibling->GetNextSibling(), rem);
5936 lineList->after_insert(prevSibLine, line);
5937 // Mark prevSibLine dirty and as needing textrun invalidation, since
5938 // we may be breaking up text in the line. Its previous line may also
5939 // need to be invalidated because it may be able to pull some text up.
5940 MarkLineDirty(prevSibLine, lineList);
5941 // The new line will also need its textruns recomputed because of the
5942 // frame changes.
5943 line->MarkDirty();
5944 line->SetInvalidateTextRuns(true);
5946 } else if (!lineList->empty()) {
5947 lineList->front()->MarkDirty();
5948 lineList->front()->SetInvalidateTextRuns(true);
5950 const nsFrameList::Slice& newFrames =
5951 frames->InsertFrames(nullptr, aPrevSibling, std::move(aFrameList));
5953 // Walk through the new frames being added and update the line data
5954 // structures to fit.
5955 for (nsIFrame* newFrame : newFrames) {
5956 NS_ASSERTION(!aPrevSibling || aPrevSibling->GetNextSibling() == newFrame,
5957 "Unexpected aPrevSibling");
5958 NS_ASSERTION(
5959 !newFrame->IsPlaceholderFrame() ||
5960 (!newFrame->IsAbsolutelyPositioned() && !newFrame->IsFloating()),
5961 "Placeholders should not float or be positioned");
5963 bool isBlock = newFrame->IsBlockOutside();
5965 // If the frame is a block frame, or if there is no previous line or if the
5966 // previous line is a block line we need to make a new line. We also make
5967 // a new line, as an optimization, in the two cases we know we'll need it:
5968 // if the previous line ended with a <br>, or if it has significant
5969 // whitespace and ended in a newline.
5970 if (isBlock || prevSibLine == lineList->end() || prevSibLine->IsBlock() ||
5971 (aPrevSibling && ShouldPutNextSiblingOnNewLine(aPrevSibling))) {
5972 // Create a new line for the frame and add its line to the line
5973 // list.
5974 nsLineBox* line = NewLineBox(newFrame, isBlock);
5975 if (prevSibLine != lineList->end()) {
5976 // Append new line after prevSibLine
5977 lineList->after_insert(prevSibLine, line);
5978 ++prevSibLine;
5979 } else {
5980 // New line is going before the other lines
5981 lineList->push_front(line);
5982 prevSibLine = lineList->begin();
5984 } else {
5985 prevSibLine->NoteFrameAdded(newFrame);
5986 // We're adding inline content to prevSibLine, so we need to mark it
5987 // dirty, ensure its textruns are recomputed, and possibly do the same
5988 // to its previous line since that line may be able to pull content up.
5989 MarkLineDirty(prevSibLine, lineList);
5992 aPrevSibling = newFrame;
5995 #ifdef DEBUG
5996 MOZ_ASSERT(aFrameList.IsEmpty());
5997 VerifyLines(true);
5998 #endif
6001 nsContainerFrame* nsBlockFrame::GetRubyContentPseudoFrame() {
6002 auto* firstChild = PrincipalChildList().FirstChild();
6003 if (firstChild && firstChild->IsRubyFrame() &&
6004 firstChild->Style()->GetPseudoType() ==
6005 mozilla::PseudoStyleType::blockRubyContent) {
6006 return static_cast<nsContainerFrame*>(firstChild);
6008 return nullptr;
6011 nsContainerFrame* nsBlockFrame::GetContentInsertionFrame() {
6012 // 'display:block ruby' use the inner (Ruby) frame for insertions.
6013 if (auto* rubyContentPseudoFrame = GetRubyContentPseudoFrame()) {
6014 return rubyContentPseudoFrame;
6016 return this;
6019 void nsBlockFrame::AppendDirectlyOwnedAnonBoxes(
6020 nsTArray<OwnedAnonBox>& aResult) {
6021 if (auto* rubyContentPseudoFrame = GetRubyContentPseudoFrame()) {
6022 aResult.AppendElement(OwnedAnonBox(rubyContentPseudoFrame));
6026 void nsBlockFrame::RemoveFloatFromFloatCache(nsIFrame* aFloat) {
6027 // Find which line contains the float, so we can update
6028 // the float cache.
6029 for (auto& line : Lines()) {
6030 if (line.IsInline() && line.RemoveFloat(aFloat)) {
6031 break;
6036 void nsBlockFrame::RemoveFloat(nsIFrame* aFloat) {
6037 #ifdef DEBUG
6038 // Floats live in mFloats, or in the PushedFloat or OverflowOutOfFlows
6039 // frame list properties.
6040 if (!mFloats.ContainsFrame(aFloat)) {
6041 MOZ_ASSERT(
6042 (GetOverflowOutOfFlows() &&
6043 GetOverflowOutOfFlows()->ContainsFrame(aFloat)) ||
6044 (GetPushedFloats() && GetPushedFloats()->ContainsFrame(aFloat)),
6045 "aFloat is not our child or on an unexpected frame list");
6047 #endif
6049 if (mFloats.StartRemoveFrame(aFloat)) {
6050 return;
6053 nsFrameList* list = GetPushedFloats();
6054 if (list && list->ContinueRemoveFrame(aFloat)) {
6055 #if 0
6056 // XXXmats not yet - need to investigate BlockReflowState::mPushedFloats
6057 // first so we don't leave it pointing to a deleted list.
6058 if (list->IsEmpty()) {
6059 delete RemovePushedFloats();
6061 #endif
6062 return;
6066 nsAutoOOFFrameList oofs(this);
6067 if (oofs.mList.ContinueRemoveFrame(aFloat)) {
6068 return;
6073 void nsBlockFrame::DoRemoveOutOfFlowFrame(nsIFrame* aFrame) {
6074 // The containing block is always the parent of aFrame.
6075 nsBlockFrame* block = (nsBlockFrame*)aFrame->GetParent();
6077 // Remove aFrame from the appropriate list.
6078 if (aFrame->IsAbsolutelyPositioned()) {
6079 // This also deletes the next-in-flows
6080 block->GetAbsoluteContainingBlock()->RemoveFrame(
6081 block, FrameChildListID::Absolute, aFrame);
6082 } else {
6083 // First remove aFrame's next-in-flows.
6084 nsIFrame* nif = aFrame->GetNextInFlow();
6085 if (nif) {
6086 nif->GetParent()->DeleteNextInFlowChild(nif, false);
6088 // Now remove aFrame from its child list and Destroy it.
6089 block->RemoveFloatFromFloatCache(aFrame);
6090 block->RemoveFloat(aFrame);
6091 aFrame->Destroy();
6096 * This helps us iterate over the list of all normal + overflow lines
6098 void nsBlockFrame::TryAllLines(nsLineList::iterator* aIterator,
6099 nsLineList::iterator* aStartIterator,
6100 nsLineList::iterator* aEndIterator,
6101 bool* aInOverflowLines,
6102 FrameLines** aOverflowLines) {
6103 if (*aIterator == *aEndIterator) {
6104 if (!*aInOverflowLines) {
6105 // Try the overflow lines
6106 *aInOverflowLines = true;
6107 FrameLines* lines = GetOverflowLines();
6108 if (lines) {
6109 *aStartIterator = lines->mLines.begin();
6110 *aIterator = *aStartIterator;
6111 *aEndIterator = lines->mLines.end();
6112 *aOverflowLines = lines;
6118 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
6119 LineIterator aLine)
6120 : mFrame(aFrame), mLine(aLine), mLineList(&aFrame->mLines) {
6121 // This will assert if aLine isn't in mLines of aFrame:
6122 DebugOnly<bool> check = aLine == mFrame->LinesBegin();
6125 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
6126 LineIterator aLine,
6127 bool aInOverflow)
6128 : mFrame(aFrame),
6129 mLine(aLine),
6130 mLineList(aInOverflow ? &aFrame->GetOverflowLines()->mLines
6131 : &aFrame->mLines) {}
6133 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
6134 bool* aFoundValidLine)
6135 : mFrame(aFrame), mLineList(&aFrame->mLines) {
6136 mLine = aFrame->LinesBegin();
6137 *aFoundValidLine = FindValidLine();
6140 void nsBlockFrame::UpdateFirstLetterStyle(ServoRestyleState& aRestyleState) {
6141 nsIFrame* letterFrame = GetFirstLetter();
6142 if (!letterFrame) {
6143 return;
6146 // Figure out what the right style parent is. This needs to match
6147 // nsCSSFrameConstructor::CreateLetterFrame.
6148 nsIFrame* inFlowFrame = letterFrame;
6149 if (inFlowFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
6150 inFlowFrame = inFlowFrame->GetPlaceholderFrame();
6152 nsIFrame* styleParent = CorrectStyleParentFrame(inFlowFrame->GetParent(),
6153 PseudoStyleType::firstLetter);
6154 ComputedStyle* parentStyle = styleParent->Style();
6155 RefPtr<ComputedStyle> firstLetterStyle =
6156 aRestyleState.StyleSet().ResolvePseudoElementStyle(
6157 *mContent->AsElement(), PseudoStyleType::firstLetter, parentStyle);
6158 // Note that we don't need to worry about changehints for the continuation
6159 // styles: those will be handled by the styleParent already.
6160 RefPtr<ComputedStyle> continuationStyle =
6161 aRestyleState.StyleSet().ResolveStyleForFirstLetterContinuation(
6162 parentStyle);
6163 UpdateStyleOfOwnedChildFrame(letterFrame, firstLetterStyle, aRestyleState,
6164 Some(continuationStyle.get()));
6166 // We also want to update the style on the textframe inside the first-letter.
6167 // We don't need to compute a changehint for this, though, since any changes
6168 // to it are handled by the first-letter anyway.
6169 nsIFrame* textFrame = letterFrame->PrincipalChildList().FirstChild();
6170 RefPtr<ComputedStyle> firstTextStyle =
6171 aRestyleState.StyleSet().ResolveStyleForText(textFrame->GetContent(),
6172 firstLetterStyle);
6173 textFrame->SetComputedStyle(firstTextStyle);
6175 // We don't need to update style for textFrame's continuations: it's already
6176 // set up to inherit from parentStyle, which is what we want.
6179 static nsIFrame* FindChildContaining(nsBlockFrame* aFrame,
6180 nsIFrame* aFindFrame) {
6181 NS_ASSERTION(aFrame, "must have frame");
6182 nsIFrame* child;
6183 while (true) {
6184 nsIFrame* block = aFrame;
6185 do {
6186 child = nsLayoutUtils::FindChildContainingDescendant(block, aFindFrame);
6187 if (child) {
6188 break;
6190 block = block->GetNextContinuation();
6191 } while (block);
6192 if (!child) {
6193 return nullptr;
6195 if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
6196 break;
6198 aFindFrame = child->GetPlaceholderFrame();
6201 return child;
6204 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
6205 nsIFrame* aFindFrame,
6206 bool* aFoundValidLine)
6207 : mFrame(aFrame), mLineList(&aFrame->mLines) {
6208 *aFoundValidLine = false;
6210 nsIFrame* child = FindChildContaining(aFrame, aFindFrame);
6211 if (!child) {
6212 return;
6215 LineIterator line_end = aFrame->LinesEnd();
6216 mLine = aFrame->LinesBegin();
6217 if (mLine != line_end && mLine.next() == line_end &&
6218 !aFrame->HasOverflowLines()) {
6219 // The block has a single line - that must be it!
6220 *aFoundValidLine = true;
6221 return;
6224 // Try to use the cursor if it exists, otherwise fall back to the first line
6225 if (nsLineBox* const cursor = aFrame->GetLineCursorForQuery()) {
6226 mLine = line_end;
6227 // Perform a simultaneous forward and reverse search starting from the
6228 // line cursor.
6229 nsBlockFrame::LineIterator line = aFrame->LinesBeginFrom(cursor);
6230 nsBlockFrame::ReverseLineIterator rline = aFrame->LinesRBeginFrom(cursor);
6231 nsBlockFrame::ReverseLineIterator rline_end = aFrame->LinesREnd();
6232 // rline is positioned on the line containing 'cursor', so it's not
6233 // rline_end. So we can safely increment it (i.e. move it to one line
6234 // earlier) to start searching there.
6235 ++rline;
6236 while (line != line_end || rline != rline_end) {
6237 if (line != line_end) {
6238 if (line->Contains(child)) {
6239 mLine = line;
6240 break;
6242 ++line;
6244 if (rline != rline_end) {
6245 if (rline->Contains(child)) {
6246 mLine = rline;
6247 break;
6249 ++rline;
6252 if (mLine != line_end) {
6253 *aFoundValidLine = true;
6254 if (mLine != cursor) {
6255 aFrame->SetProperty(nsBlockFrame::LineCursorPropertyQuery(), mLine);
6257 return;
6259 } else {
6260 for (mLine = aFrame->LinesBegin(); mLine != line_end; ++mLine) {
6261 if (mLine->Contains(child)) {
6262 *aFoundValidLine = true;
6263 return;
6267 // Didn't find the line
6268 MOZ_ASSERT(mLine == line_end, "mLine should be line_end at this point");
6270 // If we reach here, it means that we have not been able to find the
6271 // desired frame in our in-flow lines. So we should start looking at
6272 // our overflow lines. In order to do that, we set mLine to the end
6273 // iterator so that FindValidLine starts to look at overflow lines,
6274 // if any.
6276 if (!FindValidLine()) {
6277 return;
6280 do {
6281 if (mLine->Contains(child)) {
6282 *aFoundValidLine = true;
6283 return;
6285 } while (Next());
6288 nsBlockFrame::LineIterator nsBlockInFlowLineIterator::End() {
6289 return mLineList->end();
6292 bool nsBlockInFlowLineIterator::IsLastLineInList() {
6293 LineIterator end = End();
6294 return mLine != end && mLine.next() == end;
6297 bool nsBlockInFlowLineIterator::Next() {
6298 ++mLine;
6299 return FindValidLine();
6302 bool nsBlockInFlowLineIterator::Prev() {
6303 LineIterator begin = mLineList->begin();
6304 if (mLine != begin) {
6305 --mLine;
6306 return true;
6308 bool currentlyInOverflowLines = GetInOverflow();
6309 while (true) {
6310 if (currentlyInOverflowLines) {
6311 mLineList = &mFrame->mLines;
6312 mLine = mLineList->end();
6313 if (mLine != mLineList->begin()) {
6314 --mLine;
6315 return true;
6317 } else {
6318 mFrame = static_cast<nsBlockFrame*>(mFrame->GetPrevInFlow());
6319 if (!mFrame) {
6320 return false;
6322 nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines();
6323 if (overflowLines) {
6324 mLineList = &overflowLines->mLines;
6325 mLine = mLineList->end();
6326 NS_ASSERTION(mLine != mLineList->begin(), "empty overflow line list?");
6327 --mLine;
6328 return true;
6331 currentlyInOverflowLines = !currentlyInOverflowLines;
6335 bool nsBlockInFlowLineIterator::FindValidLine() {
6336 LineIterator end = mLineList->end();
6337 if (mLine != end) {
6338 return true;
6340 bool currentlyInOverflowLines = GetInOverflow();
6341 while (true) {
6342 if (currentlyInOverflowLines) {
6343 mFrame = static_cast<nsBlockFrame*>(mFrame->GetNextInFlow());
6344 if (!mFrame) {
6345 return false;
6347 mLineList = &mFrame->mLines;
6348 mLine = mLineList->begin();
6349 if (mLine != mLineList->end()) {
6350 return true;
6352 } else {
6353 nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines();
6354 if (overflowLines) {
6355 mLineList = &overflowLines->mLines;
6356 mLine = mLineList->begin();
6357 NS_ASSERTION(mLine != mLineList->end(), "empty overflow line list?");
6358 return true;
6361 currentlyInOverflowLines = !currentlyInOverflowLines;
6365 // This function removes aDeletedFrame and all its continuations. It
6366 // is optimized for deleting a whole series of frames. The easy
6367 // implementation would invoke itself recursively on
6368 // aDeletedFrame->GetNextContinuation, then locate the line containing
6369 // aDeletedFrame and remove aDeletedFrame from that line. But here we
6370 // start by locating aDeletedFrame and then scanning from that point
6371 // on looking for continuations.
6372 void nsBlockFrame::DoRemoveFrameInternal(nsIFrame* aDeletedFrame,
6373 uint32_t aFlags,
6374 PostDestroyData& aPostDestroyData) {
6375 // Clear our line cursor, since our lines may change.
6376 ClearLineCursors();
6378 if (aDeletedFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW |
6379 NS_FRAME_IS_OVERFLOW_CONTAINER)) {
6380 if (!aDeletedFrame->GetPrevInFlow()) {
6381 NS_ASSERTION(aDeletedFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
6382 "Expected out-of-flow frame");
6383 DoRemoveOutOfFlowFrame(aDeletedFrame);
6384 } else {
6385 nsContainerFrame::DeleteNextInFlowChild(aDeletedFrame,
6386 (aFlags & FRAMES_ARE_EMPTY) != 0);
6388 return;
6391 // Find the line that contains deletedFrame
6392 nsLineList::iterator line_start = mLines.begin(), line_end = mLines.end();
6393 nsLineList::iterator line = line_start;
6394 FrameLines* overflowLines = nullptr;
6395 bool searchingOverflowList = false;
6396 // Make sure we look in the overflow lines even if the normal line
6397 // list is empty
6398 TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
6399 &overflowLines);
6400 while (line != line_end) {
6401 if (line->Contains(aDeletedFrame)) {
6402 break;
6404 ++line;
6405 TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
6406 &overflowLines);
6409 if (line == line_end) {
6410 NS_ERROR("can't find deleted frame in lines");
6411 return;
6414 if (!(aFlags & FRAMES_ARE_EMPTY)) {
6415 if (line != line_start) {
6416 line.prev()->MarkDirty();
6417 line.prev()->SetInvalidateTextRuns(true);
6418 } else if (searchingOverflowList && !mLines.empty()) {
6419 mLines.back()->MarkDirty();
6420 mLines.back()->SetInvalidateTextRuns(true);
6424 while (line != line_end && aDeletedFrame) {
6425 MOZ_ASSERT(this == aDeletedFrame->GetParent(), "messed up delete code");
6426 MOZ_ASSERT(line->Contains(aDeletedFrame), "frame not in line");
6428 if (!(aFlags & FRAMES_ARE_EMPTY)) {
6429 line->MarkDirty();
6430 line->SetInvalidateTextRuns(true);
6433 // If the frame being deleted is the last one on the line then
6434 // optimize away the line->Contains(next-in-flow) call below.
6435 bool isLastFrameOnLine = 1 == line->GetChildCount();
6436 if (!isLastFrameOnLine) {
6437 LineIterator next = line.next();
6438 nsIFrame* lastFrame =
6439 next != line_end
6440 ? next->mFirstChild->GetPrevSibling()
6441 : (searchingOverflowList ? overflowLines->mFrames.LastChild()
6442 : mFrames.LastChild());
6443 NS_ASSERTION(next == line_end || lastFrame == line->LastChild(),
6444 "unexpected line frames");
6445 isLastFrameOnLine = lastFrame == aDeletedFrame;
6448 // Remove aDeletedFrame from the line
6449 if (line->mFirstChild == aDeletedFrame) {
6450 // We should be setting this to null if aDeletedFrame
6451 // is the only frame on the line. HOWEVER in that case
6452 // we will be removing the line anyway, see below.
6453 line->mFirstChild = aDeletedFrame->GetNextSibling();
6456 // Hmm, this won't do anything if we're removing a frame in the first
6457 // overflow line... Hopefully doesn't matter
6458 --line;
6459 if (line != line_end && !line->IsBlock()) {
6460 // Since we just removed a frame that follows some inline
6461 // frames, we need to reflow the previous line.
6462 line->MarkDirty();
6464 ++line;
6466 // Take aDeletedFrame out of the sibling list. Note that
6467 // prevSibling will only be nullptr when we are deleting the very
6468 // first frame in the main or overflow list.
6469 if (searchingOverflowList) {
6470 overflowLines->mFrames.RemoveFrame(aDeletedFrame);
6471 } else {
6472 mFrames.RemoveFrame(aDeletedFrame);
6475 // Update the child count of the line to be accurate
6476 line->NoteFrameRemoved(aDeletedFrame);
6478 // Destroy frame; capture its next continuation first in case we need
6479 // to destroy that too.
6480 nsIFrame* deletedNextContinuation =
6481 (aFlags & REMOVE_FIXED_CONTINUATIONS)
6482 ? aDeletedFrame->GetNextContinuation()
6483 : aDeletedFrame->GetNextInFlow();
6484 #ifdef NOISY_REMOVE_FRAME
6485 printf("DoRemoveFrame: %s line=%p frame=",
6486 searchingOverflowList ? "overflow" : "normal", line.get());
6487 aDeletedFrame->ListTag(stdout);
6488 printf(" prevSibling=%p deletedNextContinuation=%p\n",
6489 aDeletedFrame->GetPrevSibling(), deletedNextContinuation);
6490 #endif
6492 // If next-in-flow is an overflow container, must remove it first.
6493 if (deletedNextContinuation && deletedNextContinuation->HasAnyStateBits(
6494 NS_FRAME_IS_OVERFLOW_CONTAINER)) {
6495 deletedNextContinuation->GetParent()->DeleteNextInFlowChild(
6496 deletedNextContinuation, false);
6497 deletedNextContinuation = nullptr;
6500 aDeletedFrame->DestroyFrom(aDeletedFrame, aPostDestroyData);
6501 aDeletedFrame = deletedNextContinuation;
6503 bool haveAdvancedToNextLine = false;
6504 // If line is empty, remove it now.
6505 if (0 == line->GetChildCount()) {
6506 #ifdef NOISY_REMOVE_FRAME
6507 printf("DoRemoveFrame: %s line=%p became empty so it will be removed\n",
6508 searchingOverflowList ? "overflow" : "normal", line.get());
6509 #endif
6510 nsLineBox* cur = line;
6511 if (!searchingOverflowList) {
6512 line = mLines.erase(line);
6513 // Invalidate the space taken up by the line.
6514 // XXX We need to do this if we're removing a frame as a result of
6515 // a call to RemoveFrame(), but we may not need to do this in all
6516 // cases...
6517 #ifdef NOISY_BLOCK_INVALIDATE
6518 nsRect inkOverflow(cur->InkOverflowRect());
6519 printf("%p invalidate 10 (%d, %d, %d, %d)\n", this, inkOverflow.x,
6520 inkOverflow.y, inkOverflow.width, inkOverflow.height);
6521 #endif
6522 } else {
6523 line = overflowLines->mLines.erase(line);
6524 if (overflowLines->mLines.empty()) {
6525 DestroyOverflowLines();
6526 overflowLines = nullptr;
6527 // We just invalidated our iterators. Since we were in
6528 // the overflow lines list, which is now empty, set them
6529 // so we're at the end of the regular line list.
6530 line_start = mLines.begin();
6531 line_end = mLines.end();
6532 line = line_end;
6535 FreeLineBox(cur);
6537 // If we're removing a line, ReflowDirtyLines isn't going to
6538 // know that it needs to slide lines unless something is marked
6539 // dirty. So mark the previous margin of the next line dirty if
6540 // there is one.
6541 if (line != line_end) {
6542 line->MarkPreviousMarginDirty();
6544 haveAdvancedToNextLine = true;
6545 } else {
6546 // Make the line that just lost a frame dirty, and advance to
6547 // the next line.
6548 if (!deletedNextContinuation || isLastFrameOnLine ||
6549 !line->Contains(deletedNextContinuation)) {
6550 line->MarkDirty();
6551 ++line;
6552 haveAdvancedToNextLine = true;
6556 if (deletedNextContinuation) {
6557 // See if we should keep looking in the current flow's line list.
6558 if (deletedNextContinuation->GetParent() != this) {
6559 // The deceased frames continuation is not a child of the
6560 // current block. So break out of the loop so that we advance
6561 // to the next parent.
6563 // If we have a continuation in a different block then all bets are
6564 // off regarding whether we are deleting frames without actual content,
6565 // so don't propagate FRAMES_ARE_EMPTY any further.
6566 aFlags &= ~FRAMES_ARE_EMPTY;
6567 break;
6570 // If we advanced to the next line then check if we should switch to the
6571 // overflow line list.
6572 if (haveAdvancedToNextLine) {
6573 if (line != line_end && !searchingOverflowList &&
6574 !line->Contains(deletedNextContinuation)) {
6575 // We have advanced to the next *normal* line but the next-in-flow
6576 // is not there - force a switch to the overflow line list.
6577 line = line_end;
6580 TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
6581 &overflowLines);
6582 #ifdef NOISY_REMOVE_FRAME
6583 printf("DoRemoveFrame: now on %s line=%p\n",
6584 searchingOverflowList ? "overflow" : "normal", line.get());
6585 #endif
6590 if (!(aFlags & FRAMES_ARE_EMPTY) && line.next() != line_end) {
6591 line.next()->MarkDirty();
6592 line.next()->SetInvalidateTextRuns(true);
6595 #ifdef DEBUG
6596 VerifyLines(true);
6597 VerifyOverflowSituation();
6598 #endif
6600 // Advance to next flow block if the frame has more continuations.
6601 if (!aDeletedFrame) {
6602 return;
6604 nsBlockFrame* nextBlock = do_QueryFrame(aDeletedFrame->GetParent());
6605 NS_ASSERTION(nextBlock, "Our child's continuation's parent is not a block?");
6606 uint32_t flags = (aFlags & REMOVE_FIXED_CONTINUATIONS);
6607 nextBlock->DoRemoveFrameInternal(aDeletedFrame, flags, aPostDestroyData);
6610 static bool FindBlockLineFor(nsIFrame* aChild, nsLineList::iterator aBegin,
6611 nsLineList::iterator aEnd,
6612 nsLineList::iterator* aResult) {
6613 MOZ_ASSERT(aChild->IsBlockOutside());
6614 for (nsLineList::iterator line = aBegin; line != aEnd; ++line) {
6615 MOZ_ASSERT(line->GetChildCount() > 0);
6616 if (line->IsBlock() && line->mFirstChild == aChild) {
6617 MOZ_ASSERT(line->GetChildCount() == 1);
6618 *aResult = line;
6619 return true;
6622 return false;
6625 static bool FindInlineLineFor(nsIFrame* aChild, const nsFrameList& aFrameList,
6626 nsLineList::iterator aBegin,
6627 nsLineList::iterator aEnd,
6628 nsLineList::iterator* aResult) {
6629 MOZ_ASSERT(!aChild->IsBlockOutside());
6630 for (nsLineList::iterator line = aBegin; line != aEnd; ++line) {
6631 MOZ_ASSERT(line->GetChildCount() > 0);
6632 if (!line->IsBlock()) {
6633 // Optimize by comparing the line's last child first.
6634 nsLineList::iterator next = line.next();
6635 if (aChild == (next == aEnd ? aFrameList.LastChild()
6636 : next->mFirstChild->GetPrevSibling()) ||
6637 line->Contains(aChild)) {
6638 *aResult = line;
6639 return true;
6643 return false;
6646 static bool FindLineFor(nsIFrame* aChild, const nsFrameList& aFrameList,
6647 nsLineList::iterator aBegin, nsLineList::iterator aEnd,
6648 nsLineList::iterator* aResult) {
6649 return aChild->IsBlockOutside()
6650 ? FindBlockLineFor(aChild, aBegin, aEnd, aResult)
6651 : FindInlineLineFor(aChild, aFrameList, aBegin, aEnd, aResult);
6654 void nsBlockFrame::StealFrame(nsIFrame* aChild) {
6655 MOZ_ASSERT(aChild->GetParent() == this);
6657 if (aChild->IsFloating()) {
6658 RemoveFloat(aChild);
6659 return;
6662 if (MaybeStealOverflowContainerFrame(aChild)) {
6663 return;
6666 MOZ_ASSERT(!aChild->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
6668 nsLineList::iterator line;
6669 if (FindLineFor(aChild, mFrames, mLines.begin(), mLines.end(), &line)) {
6670 RemoveFrameFromLine(aChild, line, mFrames, mLines);
6671 } else {
6672 FrameLines* overflowLines = GetOverflowLines();
6673 DebugOnly<bool> found;
6674 found = FindLineFor(aChild, overflowLines->mFrames,
6675 overflowLines->mLines.begin(),
6676 overflowLines->mLines.end(), &line);
6677 MOZ_ASSERT(found, "Why can't we find aChild in our overflow lines?");
6678 RemoveFrameFromLine(aChild, line, overflowLines->mFrames,
6679 overflowLines->mLines);
6680 if (overflowLines->mLines.empty()) {
6681 DestroyOverflowLines();
6686 void nsBlockFrame::RemoveFrameFromLine(nsIFrame* aChild,
6687 nsLineList::iterator aLine,
6688 nsFrameList& aFrameList,
6689 nsLineList& aLineList) {
6690 aFrameList.RemoveFrame(aChild);
6691 if (aChild == aLine->mFirstChild) {
6692 aLine->mFirstChild = aChild->GetNextSibling();
6694 aLine->NoteFrameRemoved(aChild);
6695 if (aLine->GetChildCount() > 0) {
6696 aLine->MarkDirty();
6697 } else {
6698 // The line became empty - destroy it.
6699 nsLineBox* lineBox = aLine;
6700 aLine = aLineList.erase(aLine);
6701 if (aLine != aLineList.end()) {
6702 aLine->MarkPreviousMarginDirty();
6704 FreeLineBox(lineBox);
6708 void nsBlockFrame::DeleteNextInFlowChild(nsIFrame* aNextInFlow,
6709 bool aDeletingEmptyFrames) {
6710 MOZ_ASSERT(aNextInFlow->GetPrevInFlow(), "bad next-in-flow");
6712 if (aNextInFlow->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW |
6713 NS_FRAME_IS_OVERFLOW_CONTAINER)) {
6714 nsContainerFrame::DeleteNextInFlowChild(aNextInFlow, aDeletingEmptyFrames);
6715 } else {
6716 #ifdef DEBUG
6717 if (aDeletingEmptyFrames) {
6718 nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(aNextInFlow);
6720 #endif
6721 DoRemoveFrame(aNextInFlow, aDeletingEmptyFrames ? FRAMES_ARE_EMPTY : 0);
6725 const nsStyleText* nsBlockFrame::StyleTextForLineLayout() {
6726 // Return the pointer to an unmodified style text
6727 return StyleText();
6730 void nsBlockFrame::ReflowFloat(BlockReflowState& aState, ReflowInput& aFloatRI,
6731 nsIFrame* aFloat,
6732 nsReflowStatus& aReflowStatus) {
6733 MOZ_ASSERT(aReflowStatus.IsEmpty(),
6734 "Caller should pass a fresh reflow status!");
6735 MOZ_ASSERT(aFloat->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
6736 "aFloat must be an out-of-flow frame");
6738 WritingMode wm = aState.mReflowInput.GetWritingMode();
6740 // Setup a block reflow context to reflow the float.
6741 nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput);
6743 nsIFrame* clearanceFrame = nullptr;
6744 do {
6745 nsCollapsingMargin margin;
6746 bool mayNeedRetry = false;
6747 aFloatRI.mDiscoveredClearance = nullptr;
6748 // Only first in flow gets a block-start margin.
6749 if (!aFloat->GetPrevInFlow()) {
6750 brc.ComputeCollapsedBStartMargin(aFloatRI, &margin, clearanceFrame,
6751 &mayNeedRetry);
6753 if (mayNeedRetry && !clearanceFrame) {
6754 aFloatRI.mDiscoveredClearance = &clearanceFrame;
6755 // We don't need to push the float manager state because the the block
6756 // has its own float manager that will be destroyed and recreated
6760 // When reflowing a float, aSpace argument doesn't matter because we pass
6761 // nullptr to aLine and we don't call nsBlockReflowContext::PlaceBlock()
6762 // later.
6763 brc.ReflowBlock(LogicalRect(wm), true, margin, 0, nullptr, aFloatRI,
6764 aReflowStatus, aState);
6765 } while (clearanceFrame);
6767 if (aFloat->IsLetterFrame()) {
6768 // We never split floating first letters; an incomplete status for such
6769 // frames simply means that there is more content to be reflowed on the
6770 // line.
6771 if (aReflowStatus.IsIncomplete()) {
6772 aReflowStatus.Reset();
6776 NS_ASSERTION(aReflowStatus.IsFullyComplete() ||
6777 aFloatRI.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
6778 "The status can only be incomplete or overflow-incomplete if "
6779 "the available block-size is constrained!");
6781 if (aReflowStatus.NextInFlowNeedsReflow()) {
6782 aState.mReflowStatus.SetNextInFlowNeedsReflow();
6785 const ReflowOutput& metrics = brc.GetMetrics();
6787 // Set the rect, make sure the view is properly sized and positioned,
6788 // and tell the frame we're done reflowing it
6789 // XXXldb This seems like the wrong place to be doing this -- shouldn't
6790 // we be doing this in BlockReflowState::FlowAndPlaceFloat after
6791 // we've positioned the float, and shouldn't we be doing the equivalent
6792 // of |PlaceFrameView| here?
6793 WritingMode metricsWM = metrics.GetWritingMode();
6794 aFloat->SetSize(metricsWM, metrics.Size(metricsWM));
6795 if (aFloat->HasView()) {
6796 nsContainerFrame::SyncFrameViewAfterReflow(
6797 aState.mPresContext, aFloat, aFloat->GetView(), metrics.InkOverflow(),
6798 ReflowChildFlags::NoMoveView);
6800 aFloat->DidReflow(aState.mPresContext, &aFloatRI);
6803 StyleClear nsBlockFrame::FindTrailingClear() {
6804 for (nsBlockFrame* b = this; b;
6805 b = static_cast<nsBlockFrame*>(b->GetPrevInFlow())) {
6806 auto endLine = b->LinesRBegin();
6807 if (endLine != b->LinesREnd()) {
6808 return endLine->FloatClearTypeAfter();
6811 return StyleClear::None;
6814 void nsBlockFrame::ReflowPushedFloats(BlockReflowState& aState,
6815 OverflowAreas& aOverflowAreas) {
6816 // Pushed floats live at the start of our float list; see comment
6817 // above nsBlockFrame::DrainPushedFloats.
6818 nsIFrame* f = mFloats.FirstChild();
6819 nsIFrame* prev = nullptr;
6820 while (f && f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
6821 MOZ_ASSERT(prev == f->GetPrevSibling());
6822 // When we push a first-continuation float in a non-initial reflow,
6823 // it's possible that we end up with two continuations with the same
6824 // parent. This happens if, on the previous reflow of the block or
6825 // a previous reflow of the line containing the block, the float was
6826 // split between continuations A and B of the parent, but on the
6827 // current reflow, none of the float can fit in A.
6829 // When this happens, we might even have the two continuations
6830 // out-of-order due to the management of the pushed floats. In
6831 // particular, if the float's placeholder was in a pushed line that
6832 // we reflowed before it was pushed, and we split the float during
6833 // that reflow, we might have the continuation of the float before
6834 // the float itself. (In the general case, however, it's correct
6835 // for floats in the pushed floats list to come before floats
6836 // anchored in pushed lines; however, in this case it's wrong. We
6837 // should probably find a way to fix it somehow, since it leads to
6838 // incorrect layout in some cases.)
6840 // When we have these out-of-order continuations, we might hit the
6841 // next-continuation before the previous-continuation. When that
6842 // happens, just push it. When we reflow the next continuation,
6843 // we'll either pull all of its content back and destroy it (by
6844 // calling DeleteNextInFlowChild), or nsBlockFrame::SplitFloat will
6845 // pull it out of its current position and push it again (and
6846 // potentially repeat this cycle for the next continuation, although
6847 // hopefully then they'll be in the right order).
6849 // We should also need this code for the in-order case if the first
6850 // continuation of a float gets moved across more than one
6851 // continuation of the containing block. In this case we'd manage
6852 // to push the second continuation without this check, but not the
6853 // third and later.
6854 nsIFrame* prevContinuation = f->GetPrevContinuation();
6855 if (prevContinuation && prevContinuation->GetParent() == f->GetParent()) {
6856 mFloats.RemoveFrame(f);
6857 aState.AppendPushedFloatChain(f);
6858 f = !prev ? mFloats.FirstChild() : prev->GetNextSibling();
6859 continue;
6862 // Always call FlowAndPlaceFloat; we might need to place this float if it
6863 // didn't belong to this block the last time it was reflowed. Note that if
6864 // the float doesn't get placed, we don't consider its overflow areas.
6865 // (Not-getting-placed means it didn't fit and we pushed it instead of
6866 // placing it, and its position could be stale.)
6867 if (aState.FlowAndPlaceFloat(f) ==
6868 BlockReflowState::PlaceFloatResult::Placed) {
6869 ConsiderChildOverflow(aOverflowAreas, f);
6872 nsIFrame* next = !prev ? mFloats.FirstChild() : prev->GetNextSibling();
6873 if (next == f) {
6874 // We didn't push |f| so its next-sibling is next.
6875 next = f->GetNextSibling();
6876 prev = f;
6877 } // else: we did push |f| so |prev|'s new next-sibling is next.
6878 f = next;
6881 // If there are pushed or split floats, then we may need to continue BR
6882 // clearance
6883 if (auto [bCoord, result] = aState.ClearFloats(0, StyleClear::Both);
6884 result != ClearFloatsResult::BCoordNoChange) {
6885 Unused << bCoord;
6886 if (auto* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow())) {
6887 aState.mTrailingClearFromPIF = prevBlock->FindTrailingClear();
6892 void nsBlockFrame::RecoverFloats(nsFloatManager& aFloatManager, WritingMode aWM,
6893 const nsSize& aContainerSize) {
6894 // Recover our own floats
6895 nsIFrame* stop = nullptr; // Stop before we reach pushed floats that
6896 // belong to our next-in-flow
6897 for (nsIFrame* f = mFloats.FirstChild(); f && f != stop;
6898 f = f->GetNextSibling()) {
6899 LogicalRect region = nsFloatManager::GetRegionFor(aWM, f, aContainerSize);
6900 aFloatManager.AddFloat(f, region, aWM, aContainerSize);
6901 if (!stop && f->GetNextInFlow()) {
6902 stop = f->GetNextInFlow();
6906 // Recurse into our overflow container children
6907 for (nsIFrame* oc =
6908 GetChildList(FrameChildListID::OverflowContainers).FirstChild();
6909 oc; oc = oc->GetNextSibling()) {
6910 RecoverFloatsFor(oc, aFloatManager, aWM, aContainerSize);
6913 // Recurse into our normal children
6914 for (const auto& line : Lines()) {
6915 if (line.IsBlock()) {
6916 RecoverFloatsFor(line.mFirstChild, aFloatManager, aWM, aContainerSize);
6921 void nsBlockFrame::RecoverFloatsFor(nsIFrame* aFrame,
6922 nsFloatManager& aFloatManager,
6923 WritingMode aWM,
6924 const nsSize& aContainerSize) {
6925 MOZ_ASSERT(aFrame, "null frame");
6927 // Only blocks have floats
6928 nsBlockFrame* block = do_QueryFrame(aFrame);
6929 // Don't recover any state inside a block that has its own float manager
6930 // (we don't currently have any blocks like this, though, thanks to our
6931 // use of extra frames for 'overflow')
6932 if (block && !nsBlockFrame::BlockNeedsFloatManager(block)) {
6933 // If the element is relatively positioned, then adjust x and y
6934 // accordingly so that we consider relatively positioned frames
6935 // at their original position.
6937 LogicalRect rect(aWM, block->GetNormalRect(), aContainerSize);
6938 nscoord lineLeft = rect.LineLeft(aWM, aContainerSize);
6939 nscoord blockStart = rect.BStart(aWM);
6940 aFloatManager.Translate(lineLeft, blockStart);
6941 block->RecoverFloats(aFloatManager, aWM, aContainerSize);
6942 aFloatManager.Translate(-lineLeft, -blockStart);
6946 bool nsBlockFrame::HasPushedFloatsFromPrevContinuation() const {
6947 if (!mFloats.IsEmpty()) {
6948 // If we have pushed floats, then they should be at the beginning of our
6949 // float list.
6950 if (mFloats.FirstChild()->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
6951 return true;
6955 #ifdef DEBUG
6956 // Double-check the above assertion that pushed floats should be at the
6957 // beginning of our floats list.
6958 for (nsIFrame* f : mFloats) {
6959 NS_ASSERTION(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
6960 "pushed floats must be at the beginning of the float list");
6962 #endif
6964 // We may have a pending push of pushed floats too:
6965 if (HasPushedFloats()) {
6966 // XXX we can return 'true' here once we make HasPushedFloats
6967 // not lie. (see nsBlockFrame::RemoveFloat)
6968 auto* pushedFloats = GetPushedFloats();
6969 return pushedFloats && !pushedFloats->IsEmpty();
6971 return false;
6974 //////////////////////////////////////////////////////////////////////
6975 // Painting, event handling
6977 #ifdef DEBUG
6978 static void ComputeInkOverflowArea(nsLineList& aLines, nscoord aWidth,
6979 nscoord aHeight, nsRect& aResult) {
6980 nscoord xa = 0, ya = 0, xb = aWidth, yb = aHeight;
6981 for (nsLineList::iterator line = aLines.begin(), line_end = aLines.end();
6982 line != line_end; ++line) {
6983 // Compute min and max x/y values for the reflowed frame's
6984 // combined areas
6985 nsRect inkOverflow(line->InkOverflowRect());
6986 nscoord x = inkOverflow.x;
6987 nscoord y = inkOverflow.y;
6988 nscoord xmost = x + inkOverflow.width;
6989 nscoord ymost = y + inkOverflow.height;
6990 if (x < xa) {
6991 xa = x;
6993 if (xmost > xb) {
6994 xb = xmost;
6996 if (y < ya) {
6997 ya = y;
6999 if (ymost > yb) {
7000 yb = ymost;
7004 aResult.x = xa;
7005 aResult.y = ya;
7006 aResult.width = xb - xa;
7007 aResult.height = yb - ya;
7009 #endif
7011 #ifdef DEBUG
7012 static void DebugOutputDrawLine(int32_t aDepth, nsLineBox* aLine, bool aDrawn) {
7013 if (nsBlockFrame::gNoisyDamageRepair) {
7014 nsIFrame::IndentBy(stdout, aDepth + 1);
7015 nsRect lineArea = aLine->InkOverflowRect();
7016 printf("%s line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
7017 aDrawn ? "draw" : "skip", static_cast<void*>(aLine), aLine->IStart(),
7018 aLine->BStart(), aLine->ISize(), aLine->BSize(), lineArea.x,
7019 lineArea.y, lineArea.width, lineArea.height);
7022 #endif
7024 static void DisplayLine(nsDisplayListBuilder* aBuilder,
7025 nsBlockFrame::LineIterator& aLine,
7026 const bool aLineInLine, const nsDisplayListSet& aLists,
7027 nsBlockFrame* aFrame, TextOverflow* aTextOverflow,
7028 uint32_t aLineNumberForTextOverflow, int32_t aDepth,
7029 int32_t& aDrawnLines) {
7030 #ifdef DEBUG
7031 if (nsBlockFrame::gLamePaintMetrics) {
7032 aDrawnLines++;
7034 const bool intersect =
7035 aLine->InkOverflowRect().Intersects(aBuilder->GetDirtyRect());
7036 DebugOutputDrawLine(aDepth, aLine.get(), intersect);
7037 #endif
7039 // Collect our line's display items in a temporary nsDisplayListCollection,
7040 // so that we can apply any "text-overflow" clipping to the entire collection
7041 // without affecting previous lines.
7042 nsDisplayListCollection collection(aBuilder);
7044 // Block-level child backgrounds go on the blockBorderBackgrounds list ...
7045 // Inline-level child backgrounds go on the regular child content list.
7046 nsDisplayListSet childLists(
7047 collection,
7048 aLineInLine ? collection.Content() : collection.BlockBorderBackgrounds());
7050 auto flags =
7051 aLineInLine
7052 ? nsIFrame::DisplayChildFlags(nsIFrame::DisplayChildFlag::Inline)
7053 : nsIFrame::DisplayChildFlags();
7055 nsIFrame* kid = aLine->mFirstChild;
7056 int32_t n = aLine->GetChildCount();
7057 while (--n >= 0) {
7058 aFrame->BuildDisplayListForChild(aBuilder, kid, childLists, flags);
7059 kid = kid->GetNextSibling();
7062 if (aTextOverflow && aLineInLine) {
7063 aTextOverflow->ProcessLine(collection, aLine.get(),
7064 aLineNumberForTextOverflow);
7067 collection.MoveTo(aLists);
7070 void nsBlockFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
7071 const nsDisplayListSet& aLists) {
7072 int32_t drawnLines; // Will only be used if set (gLamePaintMetrics).
7073 int32_t depth = 0;
7074 #ifdef DEBUG
7075 if (gNoisyDamageRepair) {
7076 nsRect dirty = aBuilder->GetDirtyRect();
7077 depth = GetDepth();
7078 nsRect ca;
7079 ::ComputeInkOverflowArea(mLines, mRect.width, mRect.height, ca);
7080 nsIFrame::IndentBy(stdout, depth);
7081 ListTag(stdout);
7082 printf(": bounds=%d,%d,%d,%d dirty(absolute)=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
7083 mRect.x, mRect.y, mRect.width, mRect.height, dirty.x, dirty.y,
7084 dirty.width, dirty.height, ca.x, ca.y, ca.width, ca.height);
7086 PRTime start = 0; // Initialize these variables to silence the compiler.
7087 if (gLamePaintMetrics) {
7088 start = PR_Now();
7089 drawnLines = 0;
7091 #endif
7093 // TODO(heycam): Should we boost the load priority of any shape-outside
7094 // images using CATEGORY_DISPLAY, now that this block is being displayed?
7095 // We don't have a float manager here.
7097 DisplayBorderBackgroundOutline(aBuilder, aLists);
7099 if (GetPrevInFlow()) {
7100 DisplayOverflowContainers(aBuilder, aLists);
7101 for (nsIFrame* f : mFloats) {
7102 if (f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
7103 BuildDisplayListForChild(aBuilder, f, aLists);
7108 aBuilder->MarkFramesForDisplayList(this, mFloats);
7110 if (HasOutsideMarker()) {
7111 // Display outside ::marker manually.
7112 BuildDisplayListForChild(aBuilder, GetOutsideMarker(), aLists);
7115 // Prepare for text-overflow processing.
7116 Maybe<TextOverflow> textOverflow =
7117 TextOverflow::WillProcessLines(aBuilder, this);
7119 const bool hasDescendantPlaceHolders =
7120 HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) ||
7121 ForceDescendIntoIfVisible() || aBuilder->GetIncludeAllOutOfFlows();
7123 const auto ShouldDescendIntoLine = [&](const nsRect& aLineArea) -> bool {
7124 // TODO(miko): Unfortunately |descendAlways| cannot be cached, because with
7125 // some frame trees, building display list for child lines can change it.
7126 // See bug 1552789.
7127 const bool descendAlways =
7128 HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) ||
7129 aBuilder->GetIncludeAllOutOfFlows();
7131 return descendAlways || aLineArea.Intersects(aBuilder->GetDirtyRect()) ||
7132 (ForceDescendIntoIfVisible() &&
7133 aLineArea.Intersects(aBuilder->GetVisibleRect()));
7136 Maybe<nscolor> backplateColor;
7138 // We'll try to draw an accessibility backplate behind text (to ensure it's
7139 // readable over any possible background-images), if all of the following
7140 // hold:
7141 // (A) the backplate feature is preffed on
7142 // (B) we are not honoring the document colors
7143 if (StaticPrefs::browser_display_permit_backplate() &&
7144 PresContext()->ForcingColors() && !IsComboboxControlFrame()) {
7145 backplateColor.emplace(GetBackplateColor(this));
7148 // Don't use the line cursor if we might have a descendant placeholder ...
7149 // it might skip lines that contain placeholders but don't themselves
7150 // intersect with the dirty area.
7151 // In particular, we really want to check ShouldDescendIntoFrame()
7152 // on all our child frames, but that might be expensive. So we
7153 // approximate it by checking it on |this|; if it's true for any
7154 // frame in our child list, it's also true for |this|.
7155 // Also skip the cursor if we're creating text overflow markers,
7156 // since we need to know what line number we're up to in order
7157 // to generate unique display item keys.
7158 // Lastly, the cursor should be skipped if we're drawing
7159 // backplates behind text. When backplating we consider consecutive
7160 // runs of text as a whole, which requires we iterate through all lines
7161 // to find our backplate size.
7162 nsLineBox* cursor =
7163 (hasDescendantPlaceHolders || textOverflow.isSome() || backplateColor)
7164 ? nullptr
7165 : GetFirstLineContaining(aBuilder->GetDirtyRect().y);
7166 LineIterator line_end = LinesEnd();
7168 TextOverflow* textOverflowPtr = textOverflow.ptrOr(nullptr);
7170 if (cursor) {
7171 for (LineIterator line = mLines.begin(cursor); line != line_end; ++line) {
7172 const nsRect lineArea = line->InkOverflowRect();
7173 if (!lineArea.IsEmpty()) {
7174 // Because we have a cursor, the combinedArea.ys are non-decreasing.
7175 // Once we've passed aDirtyRect.YMost(), we can never see it again.
7176 if (lineArea.y >= aBuilder->GetDirtyRect().YMost()) {
7177 break;
7179 MOZ_ASSERT(textOverflow.isNothing());
7181 if (ShouldDescendIntoLine(lineArea)) {
7182 DisplayLine(aBuilder, line, line->IsInline(), aLists, this, nullptr,
7183 0, depth, drawnLines);
7187 } else {
7188 bool nonDecreasingYs = true;
7189 uint32_t lineCount = 0;
7190 nscoord lastY = INT32_MIN;
7191 nscoord lastYMost = INT32_MIN;
7193 // A frame's display list cannot contain more than one copy of a
7194 // given display item unless the items are uniquely identifiable.
7195 // Because backplate occasionally requires multiple
7196 // SolidColor items, we use an index (backplateIndex) to maintain
7197 // uniqueness among them. Note this is a mapping of index to
7198 // item, and the mapping is stable even if the dirty rect changes.
7199 uint16_t backplateIndex = 0;
7200 nsRect curBackplateArea;
7202 auto AddBackplate = [&]() {
7203 aLists.BorderBackground()->AppendNewToTopWithIndex<nsDisplaySolidColor>(
7204 aBuilder, this, backplateIndex, curBackplateArea,
7205 backplateColor.value());
7208 for (LineIterator line = LinesBegin(); line != line_end; ++line) {
7209 const nsRect lineArea = line->InkOverflowRect();
7210 const bool lineInLine = line->IsInline();
7212 if ((lineInLine && textOverflowPtr) || ShouldDescendIntoLine(lineArea)) {
7213 DisplayLine(aBuilder, line, lineInLine, aLists, this, textOverflowPtr,
7214 lineCount, depth, drawnLines);
7217 if (!lineInLine && !curBackplateArea.IsEmpty()) {
7218 // If we have encountered a non-inline line but were previously
7219 // forming a backplate, we should add the backplate to the display
7220 // list as-is and render future backplates disjointly.
7221 MOZ_ASSERT(backplateColor,
7222 "if this master switch is off, curBackplateArea "
7223 "must be empty and we shouldn't get here");
7224 AddBackplate();
7225 backplateIndex++;
7226 curBackplateArea = nsRect();
7229 if (!lineArea.IsEmpty()) {
7230 if (lineArea.y < lastY || lineArea.YMost() < lastYMost) {
7231 nonDecreasingYs = false;
7233 lastY = lineArea.y;
7234 lastYMost = lineArea.YMost();
7235 if (lineInLine && backplateColor && LineHasVisibleInlineContent(line)) {
7236 nsRect lineBackplate = GetLineTextArea(line, aBuilder) +
7237 aBuilder->ToReferenceFrame(this);
7238 if (curBackplateArea.IsEmpty()) {
7239 curBackplateArea = lineBackplate;
7240 } else {
7241 curBackplateArea.OrWith(lineBackplate);
7245 lineCount++;
7248 if (nonDecreasingYs && lineCount >= MIN_LINES_NEEDING_CURSOR) {
7249 SetupLineCursorForDisplay();
7252 if (!curBackplateArea.IsEmpty()) {
7253 AddBackplate();
7257 if (textOverflow.isSome()) {
7258 // Put any text-overflow:ellipsis markers on top of the non-positioned
7259 // content of the block's lines. (If we ever start sorting the Content()
7260 // list this will end up in the wrong place.)
7261 aLists.Content()->AppendToTop(&textOverflow->GetMarkers());
7264 #ifdef DEBUG
7265 if (gLamePaintMetrics) {
7266 PRTime end = PR_Now();
7268 int32_t numLines = mLines.size();
7269 if (!numLines) {
7270 numLines = 1;
7272 PRTime lines, deltaPerLine, delta;
7273 lines = int64_t(numLines);
7274 delta = end - start;
7275 deltaPerLine = delta / lines;
7277 ListTag(stdout);
7278 char buf[400];
7279 SprintfLiteral(buf,
7280 ": %" PRId64 " elapsed (%" PRId64
7281 " per line) lines=%d drawn=%d skip=%d",
7282 delta, deltaPerLine, numLines, drawnLines,
7283 numLines - drawnLines);
7284 printf("%s\n", buf);
7286 #endif
7289 #ifdef ACCESSIBILITY
7290 a11y::AccType nsBlockFrame::AccessibleType() {
7291 if (IsTableCaption()) {
7292 return GetRect().IsEmpty() ? a11y::eNoType : a11y::eHTMLCaptionType;
7295 // block frame may be for <hr>
7296 if (mContent->IsHTMLElement(nsGkAtoms::hr)) {
7297 return a11y::eHTMLHRType;
7300 if (!HasMarker() || !PresContext()) {
7301 // XXXsmaug What if we're in the shadow dom?
7302 if (!mContent->GetParent()) {
7303 // Don't create accessible objects for the root content node, they are
7304 // redundant with the nsDocAccessible object created with the document
7305 // node
7306 return a11y::eNoType;
7309 if (mContent == mContent->OwnerDoc()->GetBody()) {
7310 // Don't create accessible objects for the body, they are redundant with
7311 // the nsDocAccessible object created with the document node
7312 return a11y::eNoType;
7315 // Not a list item with a ::marker, treat as normal HTML container.
7316 return a11y::eHyperTextType;
7319 // Create special list item accessible since we have a ::marker.
7320 return a11y::eHTMLLiType;
7322 #endif
7324 void nsBlockFrame::SetupLineCursorForDisplay() {
7325 if (mLines.empty() || HasProperty(LineCursorPropertyDisplay())) {
7326 return;
7329 SetProperty(LineCursorPropertyDisplay(), mLines.front());
7330 AddStateBits(NS_BLOCK_HAS_LINE_CURSOR);
7333 void nsBlockFrame::SetupLineCursorForQuery() {
7334 if (mLines.empty() || HasProperty(LineCursorPropertyQuery())) {
7335 return;
7338 SetProperty(LineCursorPropertyQuery(), mLines.front());
7339 AddStateBits(NS_BLOCK_HAS_LINE_CURSOR);
7342 nsLineBox* nsBlockFrame::GetFirstLineContaining(nscoord y) {
7343 // Although this looks like a "querying" method, it is used by the
7344 // display-list building code, so uses the Display cursor.
7345 nsLineBox* property = GetLineCursorForDisplay();
7346 if (!property) {
7347 return nullptr;
7349 LineIterator cursor = mLines.begin(property);
7350 nsRect cursorArea = cursor->InkOverflowRect();
7352 while ((cursorArea.IsEmpty() || cursorArea.YMost() > y) &&
7353 cursor != mLines.front()) {
7354 cursor = cursor.prev();
7355 cursorArea = cursor->InkOverflowRect();
7357 while ((cursorArea.IsEmpty() || cursorArea.YMost() <= y) &&
7358 cursor != mLines.back()) {
7359 cursor = cursor.next();
7360 cursorArea = cursor->InkOverflowRect();
7363 if (cursor.get() != property) {
7364 SetProperty(LineCursorPropertyDisplay(), cursor.get());
7367 return cursor.get();
7370 /* virtual */
7371 void nsBlockFrame::ChildIsDirty(nsIFrame* aChild) {
7372 // See if the child is absolutely positioned
7373 if (aChild->IsAbsolutelyPositioned()) {
7374 // do nothing
7375 } else if (aChild == GetOutsideMarker()) {
7376 // The ::marker lives in the first line, unless the first line has
7377 // height 0 and there is a second line, in which case it lives
7378 // in the second line.
7379 LineIterator markerLine = LinesBegin();
7380 if (markerLine != LinesEnd() && markerLine->BSize() == 0 &&
7381 markerLine != mLines.back()) {
7382 markerLine = markerLine.next();
7385 if (markerLine != LinesEnd()) {
7386 MarkLineDirty(markerLine, &mLines);
7388 // otherwise we have an empty line list, and ReflowDirtyLines
7389 // will handle reflowing the ::marker.
7390 } else {
7391 // Note that we should go through our children to mark lines dirty
7392 // before the next reflow. Doing it now could make things O(N^2)
7393 // since finding the right line is O(N).
7394 // We don't need to worry about marking lines on the overflow list
7395 // as dirty; we're guaranteed to reflow them if we take them off the
7396 // overflow list.
7397 // However, we might have gotten a float, in which case we need to
7398 // reflow the line containing its placeholder. So find the
7399 // ancestor-or-self of the placeholder that's a child of the block,
7400 // and mark it as NS_FRAME_HAS_DIRTY_CHILDREN too, so that we mark
7401 // its line dirty when we handle NS_BLOCK_LOOK_FOR_DIRTY_FRAMES.
7402 // We need to take some care to handle the case where a float is in
7403 // a different continuation than its placeholder, including marking
7404 // an extra block with NS_BLOCK_LOOK_FOR_DIRTY_FRAMES.
7405 if (!aChild->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
7406 AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
7407 } else {
7408 NS_ASSERTION(aChild->IsFloating(), "should be a float");
7409 nsIFrame* thisFC = FirstContinuation();
7410 nsIFrame* placeholderPath = aChild->GetPlaceholderFrame();
7411 // SVG code sometimes sends FrameNeedsReflow notifications during
7412 // frame destruction, leading to null placeholders, but we're safe
7413 // ignoring those.
7414 if (placeholderPath) {
7415 for (;;) {
7416 nsIFrame* parent = placeholderPath->GetParent();
7417 if (parent->GetContent() == mContent &&
7418 parent->FirstContinuation() == thisFC) {
7419 parent->AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
7420 break;
7422 placeholderPath = parent;
7424 placeholderPath->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
7429 nsContainerFrame::ChildIsDirty(aChild);
7432 void nsBlockFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
7433 nsIFrame* aPrevInFlow) {
7434 // These are all the block specific frame bits, they are copied from
7435 // the prev-in-flow to a newly created next-in-flow, except for the
7436 // NS_BLOCK_FLAGS_NON_INHERITED_MASK bits below.
7437 constexpr nsFrameState NS_BLOCK_FLAGS_MASK =
7438 NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS |
7439 NS_BLOCK_CLIP_PAGINATED_OVERFLOW | NS_BLOCK_HAS_FIRST_LETTER_STYLE |
7440 NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER | NS_BLOCK_HAS_FIRST_LETTER_CHILD |
7441 NS_BLOCK_FRAME_HAS_INSIDE_MARKER;
7443 // This is the subset of NS_BLOCK_FLAGS_MASK that is NOT inherited
7444 // by default. They should only be set on the first-in-flow.
7445 constexpr nsFrameState NS_BLOCK_FLAGS_NON_INHERITED_MASK =
7446 NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER | NS_BLOCK_HAS_FIRST_LETTER_CHILD |
7447 NS_BLOCK_FRAME_HAS_INSIDE_MARKER;
7449 if (aPrevInFlow) {
7450 // Copy over the inherited block frame bits from the prev-in-flow.
7451 RemoveStateBits(NS_BLOCK_FLAGS_MASK);
7452 AddStateBits(aPrevInFlow->GetStateBits() &
7453 (NS_BLOCK_FLAGS_MASK & ~NS_BLOCK_FLAGS_NON_INHERITED_MASK));
7456 nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
7458 if (!aPrevInFlow ||
7459 aPrevInFlow->HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION)) {
7460 AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
7463 // A display:flow-root box establishes a block formatting context.
7465 // If a box has a different writing-mode value than its containing block:
7466 // ...
7467 // If the box is a block container, then it establishes a new block
7468 // formatting context.
7469 // (https://drafts.csswg.org/css-writing-modes/#block-flow)
7471 // If the box has contain: paint or contain:layout (or contain:strict),
7472 // then it should also establish a formatting context.
7474 // Per spec, a column-span always establishes a new block formatting context.
7475 if (StyleDisplay()->mDisplay == mozilla::StyleDisplay::FlowRoot ||
7476 (GetParent() &&
7477 (GetWritingMode().GetBlockDir() !=
7478 GetParent()->GetWritingMode().GetBlockDir() ||
7479 GetWritingMode().IsVerticalSideways() !=
7480 GetParent()->GetWritingMode().IsVerticalSideways())) ||
7481 StyleDisplay()->IsContainPaint() || StyleDisplay()->IsContainLayout() ||
7482 IsColumnSpan()) {
7483 AddStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS);
7486 if (HasAllStateBits(NS_FRAME_FONT_INFLATION_CONTAINER | NS_BLOCK_FLOAT_MGR)) {
7487 AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
7491 void nsBlockFrame::SetInitialChildList(ChildListID aListID,
7492 nsFrameList&& aChildList) {
7493 if (FrameChildListID::Float == aListID) {
7494 mFloats = std::move(aChildList);
7495 } else if (FrameChildListID::Principal == aListID) {
7496 #ifdef DEBUG
7497 // The only times a block that is an anonymous box is allowed to have a
7498 // first-letter frame are when it's the block inside a non-anonymous cell,
7499 // the block inside a fieldset, button or column set, or a scrolled content
7500 // block, except for <select>. Note that this means that blocks which are
7501 // the anonymous block in {ib} splits do NOT get first-letter frames.
7502 // Note that NS_BLOCK_HAS_FIRST_LETTER_STYLE gets set on all continuations
7503 // of the block.
7504 auto pseudo = Style()->GetPseudoType();
7505 bool haveFirstLetterStyle =
7506 (pseudo == PseudoStyleType::NotPseudo ||
7507 (pseudo == PseudoStyleType::cellContent &&
7508 !GetParent()->Style()->IsPseudoOrAnonBox()) ||
7509 pseudo == PseudoStyleType::fieldsetContent ||
7510 pseudo == PseudoStyleType::buttonContent ||
7511 pseudo == PseudoStyleType::columnContent ||
7512 (pseudo == PseudoStyleType::scrolledContent &&
7513 !GetParent()->IsListControlFrame()) ||
7514 pseudo == PseudoStyleType::mozSVGText) &&
7515 !IsComboboxControlFrame() && !IsFrameOfType(eMathML) &&
7516 !IsColumnSetWrapperFrame() &&
7517 RefPtr<ComputedStyle>(GetFirstLetterStyle(PresContext())) != nullptr;
7518 NS_ASSERTION(haveFirstLetterStyle ==
7519 ((mState & NS_BLOCK_HAS_FIRST_LETTER_STYLE) != 0),
7520 "NS_BLOCK_HAS_FIRST_LETTER_STYLE state out of sync");
7521 #endif
7523 AddFrames(std::move(aChildList), nullptr, nullptr);
7524 } else {
7525 nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
7529 void nsBlockFrame::SetMarkerFrameForListItem(nsIFrame* aMarkerFrame) {
7530 MOZ_ASSERT(aMarkerFrame);
7531 MOZ_ASSERT(!HasAnyStateBits(NS_BLOCK_FRAME_HAS_INSIDE_MARKER |
7532 NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER),
7533 "How can we have a ::marker frame already?");
7535 if (StyleList()->mListStylePosition == NS_STYLE_LIST_STYLE_POSITION_INSIDE) {
7536 SetProperty(InsideMarkerProperty(), aMarkerFrame);
7537 AddStateBits(NS_BLOCK_FRAME_HAS_INSIDE_MARKER);
7538 } else {
7539 if (nsBlockFrame* marker = do_QueryFrame(aMarkerFrame)) {
7540 // An outside ::marker needs to be an independent formatting context
7541 // to avoid being influenced by the float manager etc.
7542 marker->AddStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS);
7544 SetProperty(OutsideMarkerProperty(),
7545 new (PresShell()) nsFrameList(aMarkerFrame, aMarkerFrame));
7546 AddStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER);
7550 bool nsBlockFrame::MarkerIsEmpty() const {
7551 NS_ASSERTION(mContent->GetPrimaryFrame()->StyleDisplay()->IsListItem() &&
7552 HasOutsideMarker(),
7553 "should only care when we have an outside ::marker");
7554 nsIFrame* marker = GetMarker();
7555 const nsStyleList* list = marker->StyleList();
7556 return marker->StyleContent()->mContent.IsNone() ||
7557 (list->mCounterStyle.IsNone() && list->mListStyleImage.IsNone() &&
7558 marker->StyleContent()->ContentCount() == 0);
7561 void nsBlockFrame::ReflowOutsideMarker(nsIFrame* aMarkerFrame,
7562 BlockReflowState& aState,
7563 ReflowOutput& aMetrics,
7564 nscoord aLineTop) {
7565 const ReflowInput& ri = aState.mReflowInput;
7567 WritingMode markerWM = aMarkerFrame->GetWritingMode();
7568 LogicalSize availSize(markerWM);
7569 // Make up an inline-size since it doesn't really matter (XXX).
7570 availSize.ISize(markerWM) = aState.ContentISize();
7571 availSize.BSize(markerWM) = NS_UNCONSTRAINEDSIZE;
7573 ReflowInput reflowInput(aState.mPresContext, ri, aMarkerFrame, availSize,
7574 Nothing(), {}, {}, {ComputeSizeFlag::ShrinkWrap});
7575 nsReflowStatus status;
7576 aMarkerFrame->Reflow(aState.mPresContext, aMetrics, reflowInput, status);
7578 // Get the float available space using our saved state from before we
7579 // started reflowing the block, so that we ignore any floats inside
7580 // the block.
7581 // FIXME: aLineTop isn't actually set correctly by some callers, since
7582 // they reposition the line.
7583 LogicalRect floatAvailSpace =
7584 aState
7585 .GetFloatAvailableSpaceWithState(aLineTop, ShapeType::ShapeOutside,
7586 &aState.mFloatManagerStateBefore)
7587 .mRect;
7588 // FIXME (bug 25888): need to check the entire region that the first
7589 // line overlaps, not just the top pixel.
7591 // Place the ::marker now. We want to place the ::marker relative to the
7592 // border-box of the associated block (using the right/left margin of
7593 // the ::marker frame as separation). However, if a line box would be
7594 // displaced by floats that are *outside* the associated block, we
7595 // want to displace it by the same amount. That is, we act as though
7596 // the edge of the floats is the content-edge of the block, and place
7597 // the ::marker at a position offset from there by the block's padding,
7598 // the block's border, and the ::marker frame's margin.
7600 // IStart from floatAvailSpace gives us the content/float start edge
7601 // in the current writing mode. Then we subtract out the start
7602 // border/padding and the ::marker's width and margin to offset the position.
7603 WritingMode wm = ri.GetWritingMode();
7604 // Get the ::marker's margin, converted to our writing mode so that we can
7605 // combine it with other logical values here.
7606 LogicalMargin markerMargin = reflowInput.ComputedLogicalMargin(wm);
7607 nscoord iStart = floatAvailSpace.IStart(wm) -
7608 ri.ComputedLogicalBorderPadding(wm).IStart(wm) -
7609 markerMargin.IEnd(wm) - aMetrics.ISize(wm);
7611 // Approximate the ::marker's position; vertical alignment will provide
7612 // the final vertical location. We pass our writing-mode here, because
7613 // it may be different from the ::marker frame's mode.
7614 nscoord bStart = floatAvailSpace.BStart(wm);
7615 aMarkerFrame->SetRect(
7617 LogicalRect(wm, iStart, bStart, aMetrics.ISize(wm), aMetrics.BSize(wm)),
7618 aState.ContainerSize());
7619 aMarkerFrame->DidReflow(aState.mPresContext, &aState.mReflowInput);
7622 // This is used to scan frames for any float placeholders, add their
7623 // floats to the list represented by aList, and remove the
7624 // floats from whatever list they might be in. We don't search descendants
7625 // that are float containing blocks. Floats that or not children of 'this'
7626 // are ignored (they are not added to aList).
7627 void nsBlockFrame::DoCollectFloats(nsIFrame* aFrame, nsFrameList& aList,
7628 bool aCollectSiblings) {
7629 while (aFrame) {
7630 // Don't descend into float containing blocks.
7631 if (!aFrame->IsFloatContainingBlock()) {
7632 nsIFrame* outOfFlowFrame =
7633 aFrame->IsPlaceholderFrame()
7634 ? nsLayoutUtils::GetFloatFromPlaceholder(aFrame)
7635 : nullptr;
7636 while (outOfFlowFrame && outOfFlowFrame->GetParent() == this) {
7637 RemoveFloat(outOfFlowFrame);
7638 // Remove the IS_PUSHED_FLOAT bit, in case |outOfFlowFrame| came from
7639 // the PushedFloats list.
7640 outOfFlowFrame->RemoveStateBits(NS_FRAME_IS_PUSHED_FLOAT);
7641 aList.AppendFrame(nullptr, outOfFlowFrame);
7642 outOfFlowFrame = outOfFlowFrame->GetNextInFlow();
7643 // FIXME: By not pulling floats whose parent is one of our
7644 // later siblings, are we risking the pushed floats getting
7645 // out-of-order?
7646 // XXXmats nsInlineFrame's lazy reparenting depends on NOT doing that.
7649 DoCollectFloats(aFrame->PrincipalChildList().FirstChild(), aList, true);
7650 DoCollectFloats(
7651 aFrame->GetChildList(FrameChildListID::Overflow).FirstChild(), aList,
7652 true);
7654 if (!aCollectSiblings) {
7655 break;
7657 aFrame = aFrame->GetNextSibling();
7661 void nsBlockFrame::CheckFloats(BlockReflowState& aState) {
7662 #ifdef DEBUG
7663 // If any line is still dirty, that must mean we're going to reflow this
7664 // block again soon (e.g. because we bailed out after noticing that
7665 // clearance was imposed), so don't worry if the floats are out of sync.
7666 bool anyLineDirty = false;
7668 // Check that the float list is what we would have built
7669 AutoTArray<nsIFrame*, 8> lineFloats;
7670 for (auto& line : Lines()) {
7671 if (line.HasFloats()) {
7672 lineFloats.AppendElements(line.Floats());
7674 if (line.IsDirty()) {
7675 anyLineDirty = true;
7679 AutoTArray<nsIFrame*, 8> storedFloats;
7680 bool equal = true;
7681 uint32_t i = 0;
7682 for (nsIFrame* f : mFloats) {
7683 if (f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
7684 continue;
7686 storedFloats.AppendElement(f);
7687 if (i < lineFloats.Length() && lineFloats.ElementAt(i) != f) {
7688 equal = false;
7690 ++i;
7693 if ((!equal || lineFloats.Length() != storedFloats.Length()) &&
7694 !anyLineDirty) {
7695 NS_ERROR(
7696 "nsBlockFrame::CheckFloats: Explicit float list is out of sync with "
7697 "float cache");
7698 # if defined(DEBUG_roc)
7699 nsIFrame::RootFrameList(PresContext(), stdout, 0);
7700 for (i = 0; i < lineFloats.Length(); ++i) {
7701 printf("Line float: %p\n", lineFloats.ElementAt(i));
7703 for (i = 0; i < storedFloats.Length(); ++i) {
7704 printf("Stored float: %p\n", storedFloats.ElementAt(i));
7706 # endif
7708 #endif
7710 const nsFrameList* oofs = GetOverflowOutOfFlows();
7711 if (oofs && oofs->NotEmpty()) {
7712 // Floats that were pushed should be removed from our float
7713 // manager. Otherwise the float manager's YMost or XMost might
7714 // be larger than necessary, causing this block to get an
7715 // incorrect desired height (or width). Some of these floats
7716 // may not actually have been added to the float manager because
7717 // they weren't reflowed before being pushed; that's OK,
7718 // RemoveRegions will ignore them. It is safe to do this here
7719 // because we know from here on the float manager will only be
7720 // used for its XMost and YMost, not to place new floats and
7721 // lines.
7722 aState.FloatManager()->RemoveTrailingRegions(oofs->FirstChild());
7726 void nsBlockFrame::IsMarginRoot(bool* aBStartMarginRoot,
7727 bool* aBEndMarginRoot) {
7728 nsIFrame* parent = GetParent();
7729 if (!HasAnyStateBits(NS_BLOCK_MARGIN_ROOT)) {
7730 if (!parent || parent->IsFloatContainingBlock()) {
7731 *aBStartMarginRoot = false;
7732 *aBEndMarginRoot = false;
7733 return;
7737 if (parent && parent->IsColumnSetFrame()) {
7738 // The first column is a start margin root and the last column is an end
7739 // margin root. (If the column-set is split by a column-span:all box then
7740 // the first and last column in each column-set fragment are margin roots.)
7741 *aBStartMarginRoot = GetPrevInFlow() == nullptr;
7742 *aBEndMarginRoot = GetNextInFlow() == nullptr;
7743 return;
7746 *aBStartMarginRoot = true;
7747 *aBEndMarginRoot = true;
7750 /* static */
7751 bool nsBlockFrame::BlockNeedsFloatManager(nsIFrame* aBlock) {
7752 MOZ_ASSERT(aBlock, "Must have a frame");
7753 NS_ASSERTION(aBlock->IsBlockFrameOrSubclass(), "aBlock must be a block");
7755 nsIFrame* parent = aBlock->GetParent();
7756 return aBlock->HasAnyStateBits(NS_BLOCK_FLOAT_MGR) ||
7757 (parent && !parent->IsFloatContainingBlock());
7760 /* static */
7761 bool nsBlockFrame::BlockCanIntersectFloats(nsIFrame* aFrame) {
7762 return aFrame->IsBlockFrameOrSubclass() &&
7763 !aFrame->IsFrameOfType(nsIFrame::eReplaced) &&
7764 !aFrame->HasAnyStateBits(NS_BLOCK_FLOAT_MGR);
7767 // Note that this width can vary based on the vertical position.
7768 // However, the cases where it varies are the cases where the width fits
7769 // in the available space given, which means that variation shouldn't
7770 // matter.
7771 /* static */
7772 nsBlockFrame::FloatAvoidingISizeToClear nsBlockFrame::ISizeToClearPastFloats(
7773 const BlockReflowState& aState, const LogicalRect& aFloatAvailableSpace,
7774 nsIFrame* aFloatAvoidingBlock) {
7775 nscoord inlineStartOffset, inlineEndOffset;
7776 WritingMode wm = aState.mReflowInput.GetWritingMode();
7778 FloatAvoidingISizeToClear result;
7779 aState.ComputeFloatAvoidingOffsets(aFloatAvoidingBlock, aFloatAvailableSpace,
7780 inlineStartOffset, inlineEndOffset);
7781 nscoord availISize =
7782 aState.mContentArea.ISize(wm) - inlineStartOffset - inlineEndOffset;
7784 // We actually don't want the min width here; see bug 427782; we only
7785 // want to displace if the width won't compute to a value small enough
7786 // to fit.
7787 // All we really need here is the result of ComputeSize, and we
7788 // could *almost* get that from an SizeComputationInput, except for the
7789 // last argument.
7790 WritingMode frWM = aFloatAvoidingBlock->GetWritingMode();
7791 LogicalSize availSpace =
7792 LogicalSize(wm, availISize, NS_UNCONSTRAINEDSIZE).ConvertTo(frWM, wm);
7793 ReflowInput reflowInput(aState.mPresContext, aState.mReflowInput,
7794 aFloatAvoidingBlock, availSpace);
7795 result.borderBoxISize =
7796 reflowInput.ComputedSizeWithBorderPadding(wm).ISize(wm);
7798 // Use the margins from sizingInput rather than reflowInput so that
7799 // they aren't reduced by ignoring margins in overconstrained cases.
7800 SizeComputationInput sizingInput(aFloatAvoidingBlock,
7801 aState.mReflowInput.mRenderingContext, wm,
7802 aState.mContentArea.ISize(wm));
7803 const LogicalMargin computedMargin = sizingInput.ComputedLogicalMargin(wm);
7805 nscoord marginISize = computedMargin.IStartEnd(wm);
7806 const auto& iSize = reflowInput.mStylePosition->ISize(wm);
7807 if (marginISize < 0 && (iSize.IsAuto() || iSize.IsMozAvailable())) {
7808 // If we get here, floatAvoidingBlock has a negative amount of inline-axis
7809 // margin and an 'auto' (or ~equivalently, -moz-available) inline
7810 // size. Under these circumstances, we use the margin to establish a
7811 // (positive) minimum size for the border-box, in order to satisfy the
7812 // equation in CSS2 10.3.3. That equation essentially simplifies to the
7813 // following:
7815 // iSize of margins + iSize of borderBox = iSize of containingBlock
7817 // ...where "iSize of borderBox" is the sum of floatAvoidingBlock's
7818 // inline-axis components of border, padding, and {width,height}.
7820 // Right now, in the above equation, "iSize of margins" is the only term
7821 // that we know for sure. (And we also know that it's negative, since we
7822 // got here.) The other terms are as-yet unresolved, since the frame has an
7823 // 'auto' iSize, and since we aren't yet sure if we'll clear this frame
7824 // beyond floats or place it alongside them.
7826 // However: we *do* know that the equation's "iSize of containingBlock"
7827 // term *must* be non-negative, since boxes' widths and heights generally
7828 // can't be negative in CSS. To satisfy that requirement, we can then
7829 // infer that the equation's "iSize of borderBox" term *must* be large
7830 // enough to cancel out the (known-to-be-negative) "iSize of margins"
7831 // term. Therefore, marginISize value (negated to make it positive)
7832 // establishes a lower-bound for how much inline-axis space our border-box
7833 // will really require in order to fit alongside any floats.
7835 // XXXdholbert This explanation is admittedly a bit hand-wavy and may not
7836 // precisely match what any particular spec requires. It's the best
7837 // reasoning I could come up with to explain engines' behavior. Also, our
7838 // behavior with -moz-available doesn't seem particularly correct here, per
7839 // bug 1767217, though that's probably due to a bug elsewhere in our float
7840 // handling code...
7841 result.borderBoxISize = std::max(result.borderBoxISize, -marginISize);
7844 result.marginIStart = computedMargin.IStart(wm);
7845 return result;
7848 /* static */
7849 nsBlockFrame* nsBlockFrame::GetNearestAncestorBlock(nsIFrame* aCandidate) {
7850 nsBlockFrame* block = nullptr;
7851 while (aCandidate) {
7852 block = do_QueryFrame(aCandidate);
7853 if (block) {
7854 // yay, candidate is a block!
7855 return block;
7857 // Not a block. Check its parent next.
7858 aCandidate = aCandidate->GetParent();
7860 MOZ_ASSERT_UNREACHABLE("Fell off frame tree looking for ancestor block!");
7861 return nullptr;
7864 nscoord nsBlockFrame::ComputeFinalBSize(BlockReflowState& aState,
7865 nscoord aBEndEdgeOfChildren) {
7866 const WritingMode wm = aState.mReflowInput.GetWritingMode();
7868 const nscoord effectiveContentBoxBSize =
7869 GetEffectiveComputedBSize(aState.mReflowInput, aState.mConsumedBSize);
7870 const nscoord blockStartBP = aState.BorderPadding().BStart(wm);
7871 const nscoord blockEndBP = aState.BorderPadding().BEnd(wm);
7873 NS_ASSERTION(
7874 !IsTrueOverflowContainer() || (effectiveContentBoxBSize == 0 &&
7875 blockStartBP == 0 && blockEndBP == 0),
7876 "An overflow container's effective content-box block-size, block-start "
7877 "BP, and block-end BP should all be zero!");
7879 const nscoord effectiveContentBoxBSizeWithBStartBP =
7880 NSCoordSaturatingAdd(blockStartBP, effectiveContentBoxBSize);
7881 const nscoord effectiveBorderBoxBSize =
7882 NSCoordSaturatingAdd(effectiveContentBoxBSizeWithBStartBP, blockEndBP);
7884 if (HasColumnSpanSiblings()) {
7885 MOZ_ASSERT(LastInFlow()->GetNextContinuation(),
7886 "Frame constructor should've created column-span siblings!");
7888 // If a block is split by any column-spans, we calculate the final
7889 // block-size by shrinkwrapping our children's block-size for all the
7890 // fragments except for those after the final column-span, but we should
7891 // take no more than our effective border-box block-size. If there's any
7892 // leftover block-size, our next continuations will take up rest.
7894 // We don't need to adjust aBri.mReflowStatus because our children's status
7895 // is the same as ours.
7896 return std::min(effectiveBorderBoxBSize, aBEndEdgeOfChildren);
7899 const nscoord availBSize = aState.mReflowInput.AvailableBSize();
7900 if (availBSize == NS_UNCONSTRAINEDSIZE) {
7901 return effectiveBorderBoxBSize;
7904 // Save our children's reflow status.
7905 const bool isChildStatusComplete = aState.mReflowStatus.IsComplete();
7906 if (isChildStatusComplete && effectiveContentBoxBSize > 0 &&
7907 effectiveBorderBoxBSize > availBSize &&
7908 ShouldAvoidBreakInside(aState.mReflowInput)) {
7909 aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
7910 return effectiveBorderBoxBSize;
7913 const bool isBDBClone =
7914 aState.mReflowInput.mStyleBorder->mBoxDecorationBreak ==
7915 StyleBoxDecorationBreak::Clone;
7917 // The maximum value our content-box block-size can take within the given
7918 // available block-size.
7919 const nscoord maxContentBoxBSize = aState.ContentBSize();
7921 // The block-end edge of our content-box (relative to this frame's origin) if
7922 // we consumed the maximum block-size available to us (maxContentBoxBSize).
7923 const nscoord maxContentBoxBEnd = aState.ContentBEnd();
7925 // These variables are uninitialized intentionally so that the compiler can
7926 // check they are assigned in every if-else branch below.
7927 nscoord finalContentBoxBSizeWithBStartBP;
7928 bool isOurStatusComplete;
7930 if (effectiveBorderBoxBSize <= availBSize) {
7931 // Our effective border-box block-size can fit in the available block-size,
7932 // so we are complete.
7933 finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP;
7934 isOurStatusComplete = true;
7935 } else if (effectiveContentBoxBSizeWithBStartBP <= maxContentBoxBEnd) {
7936 // Note: The following assertion should generally hold because, for
7937 // box-decoration-break:clone, this "else if" branch is mathematically
7938 // equivalent to the initial "if".
7939 NS_ASSERTION(!isBDBClone,
7940 "This else-if branch is handling a situation that's specific "
7941 "to box-decoration-break:slice, i.e. a case when we can skip "
7942 "our block-end border and padding!");
7944 // Our effective content-box block-size plus the block-start border and
7945 // padding can fit in the available block-size, but it cannot fit after
7946 // adding the block-end border and padding. Thus, we need a continuation
7947 // (unless we already weren't asking for any block-size, in which case we
7948 // stay complete to avoid looping forever).
7949 finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP;
7950 isOurStatusComplete = effectiveContentBoxBSize == 0;
7951 } else {
7952 // We aren't going to be able to fit our content-box in the space available
7953 // to it, which means we'll probably call ourselves incomplete to request a
7954 // continuation. But before making that decision, we check for certain
7955 // conditions which would force us to overflow beyond the available space --
7956 // these might result in us actually being complete if we're forced to
7957 // overflow far enough.
7958 if (MOZ_UNLIKELY(aState.mReflowInput.mFlags.mIsTopOfPage && isBDBClone &&
7959 maxContentBoxBSize <= 0 &&
7960 aBEndEdgeOfChildren == blockStartBP)) {
7961 // In this rare case, we are at the top of page/column, we have
7962 // box-decoration-break:clone and zero available block-size for our
7963 // content-box (e.g. our own block-start border and padding already exceed
7964 // the available block-size), and we didn't lay out any child to consume
7965 // our content-box block-size. To ensure we make progress (avoid looping
7966 // forever), use 1px as our content-box block-size regardless of our
7967 // effective content-box block-size, in the spirit of
7968 // https://drafts.csswg.org/css-break/#breaking-rules.
7969 finalContentBoxBSizeWithBStartBP = blockStartBP + AppUnitsPerCSSPixel();
7970 isOurStatusComplete = effectiveContentBoxBSize <= AppUnitsPerCSSPixel();
7971 } else if (aBEndEdgeOfChildren > maxContentBoxBEnd) {
7972 // We have a unbreakable child whose block-end edge exceeds the available
7973 // block-size for children.
7974 if (aBEndEdgeOfChildren >= effectiveContentBoxBSizeWithBStartBP) {
7975 // The unbreakable child's block-end edge forces us to consume all of
7976 // our effective content-box block-size.
7977 finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP;
7979 // Even though we've consumed all of our effective content-box
7980 // block-size, we may still need to report an incomplete status in order
7981 // to get another continuation, which will be responsible for laying out
7982 // & drawing our block-end border & padding. But if we have no such
7983 // border & padding, or if we're forced to apply that border & padding
7984 // on this frame due to box-decoration-break:clone, then we don't need
7985 // to bother with that additional continuation.
7986 isOurStatusComplete = (isBDBClone || blockEndBP == 0);
7987 } else {
7988 // The unbreakable child's block-end edge doesn't force us to consume
7989 // all of our effective content-box block-size.
7990 finalContentBoxBSizeWithBStartBP = aBEndEdgeOfChildren;
7991 isOurStatusComplete = false;
7993 } else {
7994 // The children's block-end edge can fit in the content-box space that we
7995 // have available for it. Consume all the space that is available so that
7996 // our inline-start/inline-end borders extend all the way to the block-end
7997 // edge of column/page.
7998 finalContentBoxBSizeWithBStartBP = maxContentBoxBEnd;
7999 isOurStatusComplete = false;
8003 nscoord finalBorderBoxBSize = finalContentBoxBSizeWithBStartBP;
8004 if (isOurStatusComplete) {
8005 finalBorderBoxBSize = NSCoordSaturatingAdd(finalBorderBoxBSize, blockEndBP);
8006 if (isChildStatusComplete) {
8007 // We want to use children's reflow status as ours, which can be overflow
8008 // incomplete. Suppress the urge to call aBri.mReflowStatus.Reset() here.
8009 } else {
8010 aState.mReflowStatus.SetOverflowIncomplete();
8012 } else {
8013 NS_ASSERTION(!IsTrueOverflowContainer(),
8014 "An overflow container should always be complete because of "
8015 "its zero border-box block-size!");
8016 if (isBDBClone) {
8017 finalBorderBoxBSize =
8018 NSCoordSaturatingAdd(finalBorderBoxBSize, blockEndBP);
8020 aState.mReflowStatus.SetIncomplete();
8021 if (!GetNextInFlow()) {
8022 aState.mReflowStatus.SetNextInFlowNeedsReflow();
8026 return finalBorderBoxBSize;
8029 nsresult nsBlockFrame::ResolveBidi() {
8030 NS_ASSERTION(!GetPrevInFlow(),
8031 "ResolveBidi called on non-first continuation");
8032 MOZ_ASSERT(PresContext()->BidiEnabled());
8033 return nsBidiPresUtils::Resolve(this);
8036 void nsBlockFrame::UpdatePseudoElementStyles(ServoRestyleState& aRestyleState) {
8037 // first-letter needs to be updated before first-line, because first-line can
8038 // change the style of the first-letter.
8039 if (HasFirstLetterChild()) {
8040 UpdateFirstLetterStyle(aRestyleState);
8043 if (nsIFrame* firstLineFrame = GetFirstLineFrame()) {
8044 nsIFrame* styleParent = CorrectStyleParentFrame(firstLineFrame->GetParent(),
8045 PseudoStyleType::firstLine);
8047 ComputedStyle* parentStyle = styleParent->Style();
8048 RefPtr<ComputedStyle> firstLineStyle =
8049 aRestyleState.StyleSet().ResolvePseudoElementStyle(
8050 *mContent->AsElement(), PseudoStyleType::firstLine, parentStyle);
8052 // FIXME(bz): Can we make first-line continuations be non-inheriting anon
8053 // boxes?
8054 RefPtr<ComputedStyle> continuationStyle =
8055 aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle(
8056 PseudoStyleType::mozLineFrame, parentStyle);
8058 UpdateStyleOfOwnedChildFrame(firstLineFrame, firstLineStyle, aRestyleState,
8059 Some(continuationStyle.get()));
8061 // We also want to update the styles of the first-line's descendants. We
8062 // don't need to compute a changehint for this, though, since any changes to
8063 // them are handled by the first-line anyway.
8064 RestyleManager* manager = PresContext()->RestyleManager();
8065 for (nsIFrame* kid : firstLineFrame->PrincipalChildList()) {
8066 manager->ReparentComputedStyleForFirstLine(kid);
8071 nsIFrame* nsBlockFrame::GetFirstLetter() const {
8072 if (!HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE)) {
8073 // Certainly no first-letter frame.
8074 return nullptr;
8077 return GetProperty(FirstLetterProperty());
8080 nsIFrame* nsBlockFrame::GetFirstLineFrame() const {
8081 nsIFrame* maybeFirstLine = PrincipalChildList().FirstChild();
8082 if (maybeFirstLine && maybeFirstLine->IsLineFrame()) {
8083 return maybeFirstLine;
8086 return nullptr;
8089 #ifdef DEBUG
8090 void nsBlockFrame::VerifyLines(bool aFinalCheckOK) {
8091 if (!gVerifyLines) {
8092 return;
8094 if (mLines.empty()) {
8095 return;
8098 nsLineBox* cursor = GetLineCursorForQuery();
8100 // Add up the counts on each line. Also validate that IsFirstLine is
8101 // set properly.
8102 int32_t count = 0;
8103 for (const auto& line : Lines()) {
8104 if (&line == cursor) {
8105 cursor = nullptr;
8107 if (aFinalCheckOK) {
8108 MOZ_ASSERT(line.GetChildCount(), "empty line");
8109 if (line.IsBlock()) {
8110 NS_ASSERTION(1 == line.GetChildCount(), "bad first line");
8113 count += line.GetChildCount();
8116 // Then count the frames
8117 int32_t frameCount = 0;
8118 nsIFrame* frame = mLines.front()->mFirstChild;
8119 while (frame) {
8120 frameCount++;
8121 frame = frame->GetNextSibling();
8123 NS_ASSERTION(count == frameCount, "bad line list");
8125 // Next: test that each line has right number of frames on it
8126 for (LineIterator line = LinesBegin(), line_end = LinesEnd();
8127 line != line_end;) {
8128 count = line->GetChildCount();
8129 frame = line->mFirstChild;
8130 while (--count >= 0) {
8131 frame = frame->GetNextSibling();
8133 ++line;
8134 if ((line != line_end) && (0 != line->GetChildCount())) {
8135 NS_ASSERTION(frame == line->mFirstChild, "bad line list");
8139 if (cursor) {
8140 FrameLines* overflowLines = GetOverflowLines();
8141 if (overflowLines) {
8142 LineIterator line = overflowLines->mLines.begin();
8143 LineIterator line_end = overflowLines->mLines.end();
8144 for (; line != line_end; ++line) {
8145 if (line == cursor) {
8146 cursor = nullptr;
8147 break;
8152 NS_ASSERTION(!cursor, "stale LineCursorProperty");
8155 void nsBlockFrame::VerifyOverflowSituation() {
8156 // Overflow out-of-flows must not have a next-in-flow in mFloats or mFrames.
8157 nsFrameList* oofs = GetOverflowOutOfFlows();
8158 if (oofs) {
8159 for (nsIFrame* f : *oofs) {
8160 nsIFrame* nif = f->GetNextInFlow();
8161 MOZ_ASSERT(!nif ||
8162 (!mFloats.ContainsFrame(nif) && !mFrames.ContainsFrame(nif)));
8166 // Pushed floats must not have a next-in-flow in mFloats or mFrames.
8167 oofs = GetPushedFloats();
8168 if (oofs) {
8169 for (nsIFrame* f : *oofs) {
8170 nsIFrame* nif = f->GetNextInFlow();
8171 MOZ_ASSERT(!nif ||
8172 (!mFloats.ContainsFrame(nif) && !mFrames.ContainsFrame(nif)));
8176 // A child float next-in-flow's parent must be |this| or a next-in-flow of
8177 // |this|. Later next-in-flows must have the same or later parents.
8178 ChildListID childLists[] = {FrameChildListID::Float,
8179 FrameChildListID::PushedFloats};
8180 for (size_t i = 0; i < ArrayLength(childLists); ++i) {
8181 const nsFrameList& children = GetChildList(childLists[i]);
8182 for (nsIFrame* f : children) {
8183 nsIFrame* parent = this;
8184 nsIFrame* nif = f->GetNextInFlow();
8185 for (; nif; nif = nif->GetNextInFlow()) {
8186 bool found = false;
8187 for (nsIFrame* p = parent; p; p = p->GetNextInFlow()) {
8188 if (nif->GetParent() == p) {
8189 parent = p;
8190 found = true;
8191 break;
8194 MOZ_ASSERT(
8195 found,
8196 "next-in-flow is a child of parent earlier in the frame tree?");
8201 nsBlockFrame* flow = static_cast<nsBlockFrame*>(FirstInFlow());
8202 while (flow) {
8203 FrameLines* overflowLines = flow->GetOverflowLines();
8204 if (overflowLines) {
8205 NS_ASSERTION(!overflowLines->mLines.empty(),
8206 "should not be empty if present");
8207 NS_ASSERTION(overflowLines->mLines.front()->mFirstChild,
8208 "bad overflow lines");
8209 NS_ASSERTION(overflowLines->mLines.front()->mFirstChild ==
8210 overflowLines->mFrames.FirstChild(),
8211 "bad overflow frames / lines");
8213 auto checkCursor = [&](nsLineBox* cursor) -> bool {
8214 if (!cursor) {
8215 return true;
8217 LineIterator line = flow->LinesBegin();
8218 LineIterator line_end = flow->LinesEnd();
8219 for (; line != line_end && line != cursor; ++line)
8221 if (line == line_end && overflowLines) {
8222 line = overflowLines->mLines.begin();
8223 line_end = overflowLines->mLines.end();
8224 for (; line != line_end && line != cursor; ++line)
8227 return line != line_end;
8229 MOZ_ASSERT(checkCursor(flow->GetLineCursorForDisplay()),
8230 "stale LineCursorPropertyDisplay");
8231 MOZ_ASSERT(checkCursor(flow->GetLineCursorForQuery()),
8232 "stale LineCursorPropertyQuery");
8233 flow = static_cast<nsBlockFrame*>(flow->GetNextInFlow());
8237 int32_t nsBlockFrame::GetDepth() const {
8238 int32_t depth = 0;
8239 nsIFrame* parent = GetParent();
8240 while (parent) {
8241 parent = parent->GetParent();
8242 depth++;
8244 return depth;
8247 already_AddRefed<ComputedStyle> nsBlockFrame::GetFirstLetterStyle(
8248 nsPresContext* aPresContext) {
8249 return aPresContext->StyleSet()->ProbePseudoElementStyle(
8250 *mContent->AsElement(), PseudoStyleType::firstLetter, Style());
8252 #endif