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/. */
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/Baseline.h"
18 #include "mozilla/ComputedStyle.h"
19 #include "mozilla/DebugOnly.h"
20 #include "mozilla/Maybe.h"
21 #include "mozilla/PresShell.h"
22 #include "mozilla/ScrollContainerFrame.h"
23 #include "mozilla/StaticPrefs_browser.h"
24 #include "mozilla/StaticPrefs_layout.h"
25 #include "mozilla/ToString.h"
26 #include "mozilla/UniquePtr.h"
30 #include "nsCSSRendering.h"
31 #include "nsAbsoluteContainingBlock.h"
32 #include "nsBlockReflowContext.h"
33 #include "BlockReflowState.h"
34 #include "nsFontMetrics.h"
35 #include "nsGenericHTMLElement.h"
36 #include "nsLineBox.h"
37 #include "nsLineLayout.h"
38 #include "nsPlaceholderFrame.h"
39 #include "nsStyleConsts.h"
40 #include "nsFrameManager.h"
41 #include "nsPresContext.h"
42 #include "nsPresContextInlines.h"
43 #include "nsHTMLParts.h"
44 #include "nsGkAtoms.h"
45 #include "mozilla/Sprintf.h"
46 #include "nsFloatManager.h"
50 #include "nsLayoutUtils.h"
51 #include "nsDisplayList.h"
52 #include "nsCSSFrameConstructor.h"
53 #include "TextOverflow.h"
54 #include "nsIFrameInlines.h"
55 #include "CounterStyleManager.h"
56 #include "mozilla/dom/Selection.h"
57 #include "mozilla/PresShell.h"
58 #include "mozilla/RestyleManager.h"
59 #include "mozilla/ServoStyleSet.h"
60 #include "nsFlexContainerFrame.h"
62 #include "nsBidiPresUtils.h"
66 static const int MIN_LINES_NEEDING_CURSOR
= 20;
68 using namespace mozilla
;
69 using namespace mozilla::css
;
70 using namespace mozilla::dom
;
71 using namespace mozilla::layout
;
72 using AbsPosReflowFlags
= nsAbsoluteContainingBlock::AbsPosReflowFlags
;
73 using ClearFloatsResult
= BlockReflowState::ClearFloatsResult
;
74 using ShapeType
= nsFloatManager::ShapeType
;
76 static void MarkAllDescendantLinesDirty(nsBlockFrame
* aBlock
) {
77 for (auto& line
: aBlock
->Lines()) {
79 nsBlockFrame
* bf
= do_QueryFrame(line
.mFirstChild
);
81 MarkAllDescendantLinesDirty(bf
);
88 static void MarkSameFloatManagerLinesDirty(nsBlockFrame
* aBlock
) {
89 nsBlockFrame
* blockWithFloatMgr
= aBlock
;
90 while (!blockWithFloatMgr
->HasAnyStateBits(NS_BLOCK_BFC
)) {
91 nsBlockFrame
* bf
= do_QueryFrame(blockWithFloatMgr
->GetParent());
95 blockWithFloatMgr
= bf
;
98 // Mark every line at and below the line where the float was
99 // dirty, and mark their lines dirty too. We could probably do
100 // something more efficient --- e.g., just dirty the lines that intersect
101 // the float vertically.
102 MarkAllDescendantLinesDirty(blockWithFloatMgr
);
106 * Returns true if aFrame is a block that has one or more float children.
108 static bool BlockHasAnyFloats(nsIFrame
* aFrame
) {
109 nsBlockFrame
* block
= do_QueryFrame(aFrame
);
113 if (block
->GetChildList(FrameChildListID::Float
).FirstChild()) {
117 for (const auto& line
: block
->Lines()) {
118 if (line
.IsBlock() && BlockHasAnyFloats(line
.mFirstChild
)) {
125 // Determines whether the given frame is visible text or has visible text that
126 // participate in the same line. Frames that are not line participants do not
127 // have their children checked.
128 static bool FrameHasVisibleInlineText(nsIFrame
* aFrame
) {
129 MOZ_ASSERT(aFrame
, "Frame argument cannot be null");
130 if (!aFrame
->IsLineParticipant()) {
133 if (aFrame
->IsTextFrame()) {
134 return aFrame
->StyleVisibility()->IsVisible() &&
135 NS_GET_A(aFrame
->StyleText()->mWebkitTextFillColor
.CalcColor(
138 for (nsIFrame
* kid
: aFrame
->PrincipalChildList()) {
139 if (FrameHasVisibleInlineText(kid
)) {
146 // Determines whether any of the frames from the given line have visible text.
147 static bool LineHasVisibleInlineText(nsLineBox
* aLine
) {
148 nsIFrame
* kid
= aLine
->mFirstChild
;
149 int32_t n
= aLine
->GetChildCount();
151 if (FrameHasVisibleInlineText(kid
)) {
154 kid
= kid
->GetNextSibling();
160 * Iterates through the frame's in-flow children and
161 * unions the ink overflow of all text frames which
162 * participate in the line aFrame belongs to.
163 * If a child of aFrame is not a text frame,
164 * we recurse with the child as the aFrame argument.
165 * If aFrame isn't a line participant, we skip it entirely
166 * and return an empty rect.
167 * The resulting nsRect is offset relative to the parent of aFrame.
169 static nsRect
GetFrameTextArea(nsIFrame
* aFrame
,
170 nsDisplayListBuilder
* aBuilder
) {
172 if (const nsTextFrame
* textFrame
= do_QueryFrame(aFrame
)) {
173 if (!textFrame
->IsEntirelyWhitespace()) {
174 textArea
= aFrame
->InkOverflowRect();
176 } else if (aFrame
->IsLineParticipant()) {
177 for (nsIFrame
* kid
: aFrame
->PrincipalChildList()) {
178 nsRect kidTextArea
= GetFrameTextArea(kid
, aBuilder
);
179 textArea
.OrWith(kidTextArea
);
182 // add aFrame's position to keep textArea relative to aFrame's parent
183 return textArea
+ aFrame
->GetPosition();
187 * Iterates through the line's children and
188 * unions the ink overflow of all text frames.
189 * GetFrameTextArea unions and returns the ink overflow
190 * from all line-participating text frames within the given child.
191 * The nsRect returned from GetLineTextArea is offset
192 * relative to the given line.
194 static nsRect
GetLineTextArea(nsLineBox
* aLine
,
195 nsDisplayListBuilder
* aBuilder
) {
197 nsIFrame
* kid
= aLine
->mFirstChild
;
198 int32_t n
= aLine
->GetChildCount();
200 nsRect kidTextArea
= GetFrameTextArea(kid
, aBuilder
);
201 textArea
.OrWith(kidTextArea
);
202 kid
= kid
->GetNextSibling();
209 * Starting with aFrame, iterates upward through parent frames and checks for
210 * non-transparent background colors. If one is found, we use that as our
211 * backplate color. Otheriwse, we use the default background color from
212 * our high contrast theme.
214 static nscolor
GetBackplateColor(nsIFrame
* aFrame
) {
215 nsPresContext
* pc
= aFrame
->PresContext();
216 nscolor currentBackgroundColor
= NS_TRANSPARENT
;
217 for (nsIFrame
* frame
= aFrame
; frame
; frame
= frame
->GetParent()) {
218 // NOTE(emilio): We assume themed frames (frame->IsThemed()) have correct
219 // background-color information so as to compute the right backplate color.
221 // This holds because HTML widgets with author-specified backgrounds or
222 // borders disable theming. So as long as the UA-specified background colors
223 // match the actual theme (which they should because we always use system
224 // colors with the non-native theme, and native system colors should also
225 // match the native theme), then we're alright and we should compute an
226 // appropriate backplate color.
227 const auto* style
= frame
->Style();
228 if (style
->StyleBackground()->IsTransparent(style
)) {
231 bool drawImage
= false, drawColor
= false;
232 nscolor backgroundColor
= nsCSSRendering::DetermineBackgroundColor(
233 pc
, style
, frame
, drawImage
, drawColor
);
234 if (!drawColor
&& !drawImage
) {
237 if (NS_GET_A(backgroundColor
) == 0) {
238 // Even if there's a background image, if there's no background color we
239 // keep going up the frame tree, see bug 1723938.
242 if (NS_GET_A(currentBackgroundColor
) == 0) {
243 // Try to avoid somewhat expensive math in the common case.
244 currentBackgroundColor
= backgroundColor
;
246 currentBackgroundColor
=
247 NS_ComposeColors(backgroundColor
, currentBackgroundColor
);
249 if (NS_GET_A(currentBackgroundColor
) == 0xff) {
250 // If fully opaque, we're done, otherwise keep going up blending with our
252 return currentBackgroundColor
;
255 nscolor backgroundColor
= aFrame
->PresContext()->DefaultBackgroundColor();
256 if (NS_GET_A(currentBackgroundColor
) == 0) {
257 return backgroundColor
;
259 return NS_ComposeColors(backgroundColor
, currentBackgroundColor
);
263 # include "nsBlockDebugFlags.h"
265 bool nsBlockFrame::gLamePaintMetrics
;
266 bool nsBlockFrame::gLameReflowMetrics
;
267 bool nsBlockFrame::gNoisy
;
268 bool nsBlockFrame::gNoisyDamageRepair
;
269 bool nsBlockFrame::gNoisyIntrinsic
;
270 bool nsBlockFrame::gNoisyReflow
;
271 bool nsBlockFrame::gReallyNoisyReflow
;
272 bool nsBlockFrame::gNoisyFloatManager
;
273 bool nsBlockFrame::gVerifyLines
;
274 bool nsBlockFrame::gDisableResizeOpt
;
276 int32_t nsBlockFrame::gNoiseIndent
;
278 struct BlockDebugFlags
{
283 static const BlockDebugFlags gFlags
[] = {
284 {"reflow", &nsBlockFrame::gNoisyReflow
},
285 {"really-noisy-reflow", &nsBlockFrame::gReallyNoisyReflow
},
286 {"intrinsic", &nsBlockFrame::gNoisyIntrinsic
},
287 {"float-manager", &nsBlockFrame::gNoisyFloatManager
},
288 {"verify-lines", &nsBlockFrame::gVerifyLines
},
289 {"damage-repair", &nsBlockFrame::gNoisyDamageRepair
},
290 {"lame-paint-metrics", &nsBlockFrame::gLamePaintMetrics
},
291 {"lame-reflow-metrics", &nsBlockFrame::gLameReflowMetrics
},
292 {"disable-resize-opt", &nsBlockFrame::gDisableResizeOpt
},
294 # define NUM_DEBUG_FLAGS (sizeof(gFlags) / sizeof(gFlags[0]))
296 static void ShowDebugFlags() {
297 printf("Here are the available GECKO_BLOCK_DEBUG_FLAGS:\n");
298 const BlockDebugFlags
* bdf
= gFlags
;
299 const BlockDebugFlags
* end
= gFlags
+ NUM_DEBUG_FLAGS
;
300 for (; bdf
< end
; bdf
++) {
301 printf(" %s\n", bdf
->name
);
303 printf("Note: GECKO_BLOCK_DEBUG_FLAGS is a comma separated list of flag\n");
304 printf("names (no whitespace)\n");
307 void nsBlockFrame::InitDebugFlags() {
308 static bool firstTime
= true;
311 char* flags
= PR_GetEnv("GECKO_BLOCK_DEBUG_FLAGS");
315 char* cm
= strchr(flags
, ',');
321 const BlockDebugFlags
* bdf
= gFlags
;
322 const BlockDebugFlags
* end
= gFlags
+ NUM_DEBUG_FLAGS
;
323 for (; bdf
< end
; bdf
++) {
324 if (nsCRT::strcasecmp(bdf
->name
, flags
) == 0) {
326 printf("nsBlockFrame: setting %s debug flag on\n", bdf
->name
);
351 //----------------------------------------------------------------------
353 // Debugging support code
356 const char* nsBlockFrame::kReflowCommandType
[] = {
357 "ContentChanged", "StyleChanged", "ReflowDirty", "Timeout", "UserDefined",
360 const char* nsBlockFrame::LineReflowStatusToString(
361 LineReflowStatus aLineReflowStatus
) const {
362 switch (aLineReflowStatus
) {
363 case LineReflowStatus::OK
:
364 return "LINE_REFLOW_OK";
365 case LineReflowStatus::Stop
:
366 return "LINE_REFLOW_STOP";
367 case LineReflowStatus::RedoNoPull
:
368 return "LINE_REFLOW_REDO_NO_PULL";
369 case LineReflowStatus::RedoMoreFloats
:
370 return "LINE_REFLOW_REDO_MORE_FLOATS";
371 case LineReflowStatus::RedoNextBand
:
372 return "LINE_REFLOW_REDO_NEXT_BAND";
373 case LineReflowStatus::Truncated
:
374 return "LINE_REFLOW_TRUNCATED";
381 #ifdef REFLOW_STATUS_COVERAGE
382 static void RecordReflowStatus(bool aChildIsBlock
,
383 const nsReflowStatus
& aFrameReflowStatus
) {
384 static uint32_t record
[2];
387 // 1: child-is-inline
389 if (!aChildIsBlock
) {
393 // Compute new status
394 uint32_t newS
= record
[index
];
395 if (aFrameReflowStatus
.IsInlineBreak()) {
396 if (aFrameReflowStatus
.IsInlineBreakBefore()) {
398 } else if (aFrameReflowStatus
.IsIncomplete()) {
403 } else if (aFrameReflowStatus
.IsIncomplete()) {
409 // Log updates to the status that yield different values
410 if (record
[index
] != newS
) {
411 record
[index
] = newS
;
412 printf("record(%d): %02x %02x\n", index
, record
[0], record
[1]);
417 NS_DECLARE_FRAME_PROPERTY_WITH_DTOR_NEVER_CALLED(OverflowLinesProperty
,
418 nsBlockFrame::FrameLines
)
419 NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OverflowOutOfFlowsProperty
)
420 NS_DECLARE_FRAME_PROPERTY_FRAMELIST(FloatsProperty
)
421 NS_DECLARE_FRAME_PROPERTY_FRAMELIST(PushedFloatsProperty
)
422 NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OutsideMarkerProperty
)
423 NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(InsideMarkerProperty
, nsIFrame
)
424 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(BlockEndEdgeOfChildrenProperty
, nscoord
)
426 //----------------------------------------------------------------------
428 nsBlockFrame
* NS_NewBlockFrame(PresShell
* aPresShell
, ComputedStyle
* aStyle
) {
429 return new (aPresShell
) nsBlockFrame(aStyle
, aPresShell
->GetPresContext());
432 NS_IMPL_FRAMEARENA_HELPERS(nsBlockFrame
)
434 nsBlockFrame::~nsBlockFrame() = default;
436 void nsBlockFrame::AddSizeOfExcludingThisForTree(
437 nsWindowSizes
& aWindowSizes
) const {
438 nsContainerFrame::AddSizeOfExcludingThisForTree(aWindowSizes
);
440 // Add the size of any nsLineBox::mFrames hashtables we might have:
441 for (const auto& line
: Lines()) {
442 line
.AddSizeOfExcludingThis(aWindowSizes
);
444 const FrameLines
* overflowLines
= GetOverflowLines();
446 ConstLineIterator line
= overflowLines
->mLines
.begin(),
447 line_end
= overflowLines
->mLines
.end();
448 for (; line
!= line_end
; ++line
) {
449 line
->AddSizeOfExcludingThis(aWindowSizes
);
454 void nsBlockFrame::Destroy(DestroyContext
& aContext
) {
456 DestroyAbsoluteFrames(aContext
);
457 nsPresContext
* presContext
= PresContext();
458 mozilla::PresShell
* presShell
= presContext
->PresShell();
460 SafelyDestroyFrameListProp(aContext
, presShell
, FloatsProperty());
461 RemoveStateBits(NS_BLOCK_HAS_FLOATS
);
463 nsLineBox::DeleteLineList(presContext
, mLines
, &mFrames
, aContext
);
465 if (HasPushedFloats()) {
466 SafelyDestroyFrameListProp(aContext
, presShell
, PushedFloatsProperty());
467 RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS
);
470 // destroy overflow lines now
471 FrameLines
* overflowLines
= RemoveOverflowLines();
473 nsLineBox::DeleteLineList(presContext
, overflowLines
->mLines
,
474 &overflowLines
->mFrames
, aContext
);
475 delete overflowLines
;
478 if (HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS
)) {
479 SafelyDestroyFrameListProp(aContext
, presShell
,
480 OverflowOutOfFlowsProperty());
481 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS
);
484 if (HasOutsideMarker()) {
485 SafelyDestroyFrameListProp(aContext
, presShell
, OutsideMarkerProperty());
486 RemoveStateBits(NS_BLOCK_HAS_OUTSIDE_MARKER
);
489 nsContainerFrame::Destroy(aContext
);
493 nsILineIterator
* nsBlockFrame::GetLineIterator() {
494 nsLineIterator
* iter
= GetProperty(LineIteratorProperty());
496 const nsStyleVisibility
* visibility
= StyleVisibility();
497 iter
= new nsLineIterator(mLines
,
498 visibility
->mDirection
== StyleDirection::Rtl
);
499 SetProperty(LineIteratorProperty(), iter
);
504 NS_QUERYFRAME_HEAD(nsBlockFrame
)
505 NS_QUERYFRAME_ENTRY(nsBlockFrame
)
506 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
508 #ifdef DEBUG_FRAME_DUMP
509 void nsBlockFrame::List(FILE* out
, const char* aPrefix
,
510 ListFlags aFlags
) const {
512 ListGeneric(str
, aPrefix
, aFlags
);
514 fprintf_stderr(out
, "%s <\n", str
.get());
516 nsCString
pfx(aPrefix
);
520 if (!mLines
.empty()) {
521 ConstLineIterator line
= LinesBegin(), line_end
= LinesEnd();
522 for (; line
!= line_end
; ++line
) {
523 line
->List(out
, pfx
.get(), aFlags
);
527 // Output the overflow lines.
528 const FrameLines
* overflowLines
= GetOverflowLines();
529 if (overflowLines
&& !overflowLines
->mLines
.empty()) {
530 fprintf_stderr(out
, "%sOverflow-lines %p/%p <\n", pfx
.get(), overflowLines
,
531 &overflowLines
->mFrames
);
532 nsCString
nestedPfx(pfx
);
534 ConstLineIterator line
= overflowLines
->mLines
.begin(),
535 line_end
= overflowLines
->mLines
.end();
536 for (; line
!= line_end
; ++line
) {
537 line
->List(out
, nestedPfx
.get(), aFlags
);
539 fprintf_stderr(out
, "%s>\n", pfx
.get());
542 // skip the principal list - we printed the lines above
543 // skip the overflow list - we printed the overflow lines above
544 ChildListIDs skip
= {FrameChildListID::Principal
, FrameChildListID::Overflow
};
545 ListChildLists(out
, pfx
.get(), aFlags
, skip
);
547 fprintf_stderr(out
, "%s>\n", aPrefix
);
550 nsresult
nsBlockFrame::GetFrameName(nsAString
& aResult
) const {
551 return MakeFrameName(u
"Block"_ns
, aResult
);
555 void nsBlockFrame::InvalidateFrame(uint32_t aDisplayItemKey
,
556 bool aRebuildDisplayItems
) {
557 if (IsInSVGTextSubtree()) {
558 NS_ASSERTION(GetParent()->IsSVGTextFrame(),
559 "unexpected block frame in SVG text");
560 GetParent()->InvalidateFrame();
563 nsContainerFrame::InvalidateFrame(aDisplayItemKey
, aRebuildDisplayItems
);
566 void nsBlockFrame::InvalidateFrameWithRect(const nsRect
& aRect
,
567 uint32_t aDisplayItemKey
,
568 bool aRebuildDisplayItems
) {
569 if (IsInSVGTextSubtree()) {
570 NS_ASSERTION(GetParent()->IsSVGTextFrame(),
571 "unexpected block frame in SVG text");
572 GetParent()->InvalidateFrame();
575 nsContainerFrame::InvalidateFrameWithRect(aRect
, aDisplayItemKey
,
576 aRebuildDisplayItems
);
579 nscoord
nsBlockFrame::SynthesizeFallbackBaseline(
580 WritingMode aWM
, BaselineSharingGroup aBaselineGroup
) const {
581 return Baseline::SynthesizeBOffsetFromMarginBox(this, aWM
, aBaselineGroup
);
584 template <typename LineIteratorType
>
585 Maybe
<nscoord
> nsBlockFrame::GetBaselineBOffset(
586 LineIteratorType aStart
, LineIteratorType aEnd
, WritingMode aWM
,
587 BaselineSharingGroup aBaselineGroup
,
588 BaselineExportContext aExportContext
) const {
589 MOZ_ASSERT((std::is_same_v
<LineIteratorType
, ConstLineIterator
> &&
590 aBaselineGroup
== BaselineSharingGroup::First
) ||
591 (std::is_same_v
<LineIteratorType
, ConstReverseLineIterator
> &&
592 aBaselineGroup
== BaselineSharingGroup::Last
),
593 "Iterator direction must match baseline sharing group.");
594 for (auto line
= aStart
; line
!= aEnd
; ++line
) {
595 if (!line
->IsBlock()) {
596 // XXX Is this the right test? We have some bogus empty lines
597 // floating around, but IsEmpty is perhaps too weak.
598 if (line
->BSize() != 0 || !line
->IsEmpty()) {
599 const auto ascent
= line
->BStart() + line
->GetLogicalAscent();
600 if (aBaselineGroup
== BaselineSharingGroup::Last
) {
601 return Some(BSize(aWM
) - ascent
);
607 nsIFrame
* kid
= line
->mFirstChild
;
608 if (aWM
.IsOrthogonalTo(kid
->GetWritingMode())) {
611 if (aExportContext
== BaselineExportContext::LineLayout
&&
612 kid
->IsTableWrapperFrame()) {
613 // `<table>` in inline-block context does not export any baseline.
616 const auto kidBaselineGroup
=
617 aExportContext
== BaselineExportContext::LineLayout
618 ? kid
->GetDefaultBaselineSharingGroup()
620 const auto kidBaseline
=
621 kid
->GetNaturalBaselineBOffset(aWM
, kidBaselineGroup
, aExportContext
);
625 auto result
= *kidBaseline
;
626 if (kidBaselineGroup
== BaselineSharingGroup::Last
) {
627 result
= kid
->BSize(aWM
) - result
;
629 // Ignore relative positioning for baseline calculations.
630 const nsSize
& sz
= line
->mContainerSize
;
631 result
+= kid
->GetLogicalNormalPosition(aWM
, sz
).B(aWM
);
632 if (aBaselineGroup
== BaselineSharingGroup::Last
) {
633 return Some(BSize(aWM
) - result
);
640 Maybe
<nscoord
> nsBlockFrame::GetNaturalBaselineBOffset(
641 WritingMode aWM
, BaselineSharingGroup aBaselineGroup
,
642 BaselineExportContext aExportContext
) const {
643 if (StyleDisplay()->IsContainLayout()) {
647 if (aBaselineGroup
== BaselineSharingGroup::First
) {
648 return GetBaselineBOffset(LinesBegin(), LinesEnd(), aWM
, aBaselineGroup
,
652 return GetBaselineBOffset(LinesRBegin(), LinesREnd(), aWM
, aBaselineGroup
,
656 nscoord
nsBlockFrame::GetCaretBaseline() const {
657 nsRect contentRect
= GetContentRect();
658 nsMargin bp
= GetUsedBorderAndPadding();
660 if (!mLines
.empty()) {
661 ConstLineIterator line
= LinesBegin();
662 if (!line
->IsEmpty()) {
663 if (line
->IsBlock()) {
664 return bp
.top
+ line
->mFirstChild
->GetCaretBaseline();
666 return line
->BStart() + line
->GetLogicalAscent();
670 float inflation
= nsLayoutUtils::FontSizeInflationFor(this);
671 RefPtr
<nsFontMetrics
> fm
=
672 nsLayoutUtils::GetFontMetricsForFrame(this, inflation
);
673 nscoord lineHeight
= ReflowInput::CalcLineHeight(
674 *Style(), PresContext(), GetContent(), contentRect
.height
, inflation
);
675 const WritingMode wm
= GetWritingMode();
676 return nsLayoutUtils::GetCenteredFontBaseline(fm
, lineHeight
,
677 wm
.IsLineInverted()) +
681 /////////////////////////////////////////////////////////////////////////////
682 // Child frame enumeration
684 const nsFrameList
& nsBlockFrame::GetChildList(ChildListID aListID
) const {
686 case FrameChildListID::Principal
:
688 case FrameChildListID::Overflow
: {
689 FrameLines
* overflowLines
= GetOverflowLines();
690 return overflowLines
? overflowLines
->mFrames
: nsFrameList::EmptyList();
692 case FrameChildListID::OverflowOutOfFlow
: {
693 const nsFrameList
* list
= GetOverflowOutOfFlows();
694 return list
? *list
: nsFrameList::EmptyList();
696 case FrameChildListID::Float
: {
697 const nsFrameList
* list
= GetFloats();
698 return list
? *list
: nsFrameList::EmptyList();
700 case FrameChildListID::PushedFloats
: {
701 const nsFrameList
* list
= GetPushedFloats();
702 return list
? *list
: nsFrameList::EmptyList();
704 case FrameChildListID::Bullet
: {
705 const nsFrameList
* list
= GetOutsideMarkerList();
706 return list
? *list
: nsFrameList::EmptyList();
709 return nsContainerFrame::GetChildList(aListID
);
713 void nsBlockFrame::GetChildLists(nsTArray
<ChildList
>* aLists
) const {
714 nsContainerFrame::GetChildLists(aLists
);
715 FrameLines
* overflowLines
= GetOverflowLines();
717 overflowLines
->mFrames
.AppendIfNonempty(aLists
, FrameChildListID::Overflow
);
719 if (const nsFrameList
* list
= GetOverflowOutOfFlows()) {
720 list
->AppendIfNonempty(aLists
, FrameChildListID::OverflowOutOfFlow
);
722 if (const nsFrameList
* list
= GetOutsideMarkerList()) {
723 list
->AppendIfNonempty(aLists
, FrameChildListID::Bullet
);
725 if (const nsFrameList
* list
= GetFloats()) {
726 list
->AppendIfNonempty(aLists
, FrameChildListID::Float
);
728 if (const nsFrameList
* list
= GetPushedFloats()) {
729 list
->AppendIfNonempty(aLists
, FrameChildListID::PushedFloats
);
734 bool nsBlockFrame::IsFloatContainingBlock() const { return true; }
737 * Remove the first line from aFromLines and adjust the associated frame list
738 * aFromFrames accordingly. The removed line is assigned to *aOutLine and
739 * a frame list with its frames is assigned to *aOutFrames, i.e. the frames
740 * that were extracted from the head of aFromFrames.
741 * aFromLines must contain at least one line, the line may be empty.
742 * @return true if aFromLines becomes empty
744 static bool RemoveFirstLine(nsLineList
& aFromLines
, nsFrameList
& aFromFrames
,
745 nsLineBox
** aOutLine
, nsFrameList
* aOutFrames
) {
746 LineListIterator removedLine
= aFromLines
.begin();
747 *aOutLine
= removedLine
;
748 LineListIterator next
= aFromLines
.erase(removedLine
);
749 bool isLastLine
= next
== aFromLines
.end();
750 nsIFrame
* firstFrameInNextLine
= isLastLine
? nullptr : next
->mFirstChild
;
751 *aOutFrames
= aFromFrames
.TakeFramesBefore(firstFrameInNextLine
);
755 //////////////////////////////////////////////////////////////////////
759 void nsBlockFrame::MarkIntrinsicISizesDirty() {
760 nsBlockFrame
* dirtyBlock
= static_cast<nsBlockFrame
*>(FirstContinuation());
761 dirtyBlock
->mCachedMinISize
= NS_INTRINSIC_ISIZE_UNKNOWN
;
762 dirtyBlock
->mCachedPrefISize
= NS_INTRINSIC_ISIZE_UNKNOWN
;
763 if (!HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION
)) {
764 for (nsIFrame
* frame
= dirtyBlock
; frame
;
765 frame
= frame
->GetNextContinuation()) {
766 frame
->AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION
);
770 nsContainerFrame::MarkIntrinsicISizesDirty();
773 void nsBlockFrame::CheckIntrinsicCacheAgainstShrinkWrapState() {
774 nsPresContext
* presContext
= PresContext();
775 if (!nsLayoutUtils::FontSizeInflationEnabled(presContext
)) {
778 bool inflationEnabled
= !presContext
->mInflationDisabledForShrinkWrap
;
779 if (inflationEnabled
!= HasAnyStateBits(NS_BLOCK_INTRINSICS_INFLATED
)) {
780 mCachedMinISize
= NS_INTRINSIC_ISIZE_UNKNOWN
;
781 mCachedPrefISize
= NS_INTRINSIC_ISIZE_UNKNOWN
;
782 AddOrRemoveStateBits(NS_BLOCK_INTRINSICS_INFLATED
, inflationEnabled
);
786 // Whether this line is indented by the text-indent amount.
787 bool nsBlockFrame::TextIndentAppliesTo(const LineIterator
& aLine
) const {
788 const auto& textIndent
= StyleText()->mTextIndent
;
790 bool isFirstLineOrAfterHardBreak
= [&] {
791 if (aLine
!= LinesBegin()) {
792 // If not the first line of the block, but 'each-line' is in effect,
793 // check if the previous line was not wrapped.
794 return textIndent
.each_line
&& !aLine
.prev()->IsLineWrapped();
796 if (nsBlockFrame
* prevBlock
= do_QueryFrame(GetPrevInFlow())) {
797 // There's a prev-in-flow, so this only counts as a first-line if
798 // 'each-line' and the prev-in-flow's last line was not wrapped.
799 return textIndent
.each_line
&&
800 (prevBlock
->Lines().empty() ||
801 !prevBlock
->LinesEnd().prev()->IsLineWrapped());
806 // The 'hanging' option inverts which lines are/aren't indented.
807 return isFirstLineOrAfterHardBreak
!= textIndent
.hanging
;
810 nscoord
nsBlockFrame::IntrinsicISize(const IntrinsicSizeInput
& aInput
,
811 IntrinsicISizeType aType
) {
812 nsIFrame
* firstCont
= FirstContinuation();
813 if (firstCont
!= this) {
814 return firstCont
->IntrinsicISize(aInput
, aType
);
817 CheckIntrinsicCacheAgainstShrinkWrapState();
819 if (aType
== IntrinsicISizeType::MinISize
) {
820 if (mCachedMinISize
== NS_INTRINSIC_ISIZE_UNKNOWN
) {
821 mCachedMinISize
= MinISize(aInput
);
823 return mCachedMinISize
;
826 if (mCachedPrefISize
== NS_INTRINSIC_ISIZE_UNKNOWN
) {
827 mCachedPrefISize
= PrefISize(aInput
);
829 return mCachedPrefISize
;
833 nscoord
nsBlockFrame::MinISize(const IntrinsicSizeInput
& aInput
) {
834 if (Maybe
<nscoord
> containISize
= ContainIntrinsicISize()) {
835 return *containISize
;
839 if (gNoisyIntrinsic
) {
840 IndentBy(stdout
, gNoiseIndent
);
842 printf(": MinISize\n");
844 AutoNoisyIndenter
indenter(gNoisyIntrinsic
);
847 for (nsBlockFrame
* curFrame
= this; curFrame
;
848 curFrame
= static_cast<nsBlockFrame
*>(curFrame
->GetNextContinuation())) {
849 curFrame
->LazyMarkLinesDirty();
852 if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION
) &&
853 PresContext()->BidiEnabled()) {
857 const bool whiteSpaceCanWrap
= StyleText()->WhiteSpaceCanWrapStyle();
858 InlineMinISizeData data
;
859 for (nsBlockFrame
* curFrame
= this; curFrame
;
860 curFrame
= static_cast<nsBlockFrame
*>(curFrame
->GetNextContinuation())) {
861 for (LineIterator line
= curFrame
->LinesBegin(),
862 line_end
= curFrame
->LinesEnd();
863 line
!= line_end
; ++line
) {
865 if (gNoisyIntrinsic
) {
866 IndentBy(stdout
, gNoiseIndent
);
867 printf("line (%s%s)\n", line
->IsBlock() ? "block" : "inline",
868 line
->IsEmpty() ? ", empty" : "");
870 AutoNoisyIndenter
lineindent(gNoisyIntrinsic
);
872 if (line
->IsBlock()) {
874 nsIFrame
* kid
= line
->mFirstChild
;
875 const IntrinsicSizeInput
kidInput(aInput
, kid
->GetWritingMode(),
877 data
.mCurrentLine
= nsLayoutUtils::IntrinsicForContainer(
878 kidInput
.mContext
, kid
, IntrinsicISizeType::MinISize
,
879 kidInput
.mPercentageBasisForChildren
);
882 if (!curFrame
->GetPrevContinuation() && TextIndentAppliesTo(line
)) {
883 data
.mCurrentLine
+= StyleText()->mTextIndent
.length
.Resolve(0);
886 data
.SetLineContainer(curFrame
);
887 nsIFrame
* kid
= line
->mFirstChild
;
888 for (int32_t i
= 0, i_end
= line
->GetChildCount(); i
!= i_end
;
889 ++i
, kid
= kid
->GetNextSibling()) {
890 const IntrinsicSizeInput
kidInput(aInput
, kid
->GetWritingMode(),
892 kid
->AddInlineMinISize(kidInput
, &data
);
893 if (whiteSpaceCanWrap
&& data
.mTrailingWhitespace
) {
894 data
.OptionallyBreak();
899 if (gNoisyIntrinsic
) {
900 IndentBy(stdout
, gNoiseIndent
);
901 printf("min: [prevLines=%d currentLine=%d]\n", data
.mPrevLines
,
908 return data
.mPrevLines
;
912 nscoord
nsBlockFrame::PrefISize(const IntrinsicSizeInput
& aInput
) {
913 if (Maybe
<nscoord
> containISize
= ContainIntrinsicISize()) {
914 return *containISize
;
918 if (gNoisyIntrinsic
) {
919 IndentBy(stdout
, gNoiseIndent
);
921 printf(": PrefISize\n");
923 AutoNoisyIndenter
indenter(gNoisyIntrinsic
);
926 for (nsBlockFrame
* curFrame
= this; curFrame
;
927 curFrame
= static_cast<nsBlockFrame
*>(curFrame
->GetNextContinuation())) {
928 curFrame
->LazyMarkLinesDirty();
931 if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION
) &&
932 PresContext()->BidiEnabled()) {
935 InlinePrefISizeData data
;
936 for (nsBlockFrame
* curFrame
= this; curFrame
;
937 curFrame
= static_cast<nsBlockFrame
*>(curFrame
->GetNextContinuation())) {
938 for (LineIterator line
= curFrame
->LinesBegin(),
939 line_end
= curFrame
->LinesEnd();
940 line
!= line_end
; ++line
) {
942 if (gNoisyIntrinsic
) {
943 IndentBy(stdout
, gNoiseIndent
);
944 printf("line (%s%s)\n", line
->IsBlock() ? "block" : "inline",
945 line
->IsEmpty() ? ", empty" : "");
947 AutoNoisyIndenter
lineindent(gNoisyIntrinsic
);
949 if (line
->IsBlock()) {
950 nsIFrame
* kid
= line
->mFirstChild
;
951 StyleClear clearType
;
952 if (!data
.mLineIsEmpty
|| BlockCanIntersectFloats(kid
)) {
953 clearType
= StyleClear::Both
;
955 clearType
= kid
->StyleDisplay()->mClear
;
957 data
.ForceBreak(clearType
);
958 const IntrinsicSizeInput
kidInput(aInput
, kid
->GetWritingMode(),
960 data
.mCurrentLine
= nsLayoutUtils::IntrinsicForContainer(
961 kidInput
.mContext
, kid
, IntrinsicISizeType::PrefISize
,
962 kidInput
.mPercentageBasisForChildren
);
965 if (!curFrame
->GetPrevContinuation() && TextIndentAppliesTo(line
)) {
966 nscoord indent
= StyleText()->mTextIndent
.length
.Resolve(0);
967 data
.mCurrentLine
+= indent
;
968 // XXXmats should the test below be indent > 0?
969 if (indent
!= nscoord(0)) {
970 data
.mLineIsEmpty
= false;
974 data
.SetLineContainer(curFrame
);
975 nsIFrame
* kid
= line
->mFirstChild
;
976 for (int32_t i
= 0, i_end
= line
->GetChildCount(); i
!= i_end
;
977 ++i
, kid
= kid
->GetNextSibling()) {
978 const IntrinsicSizeInput
kidInput(aInput
, kid
->GetWritingMode(),
980 kid
->AddInlinePrefISize(kidInput
, &data
);
984 if (gNoisyIntrinsic
) {
985 IndentBy(stdout
, gNoiseIndent
);
986 printf("pref: [prevLines=%d currentLine=%d]\n", data
.mPrevLines
,
993 return data
.mPrevLines
;
996 nsRect
nsBlockFrame::ComputeTightBounds(DrawTarget
* aDrawTarget
) const {
998 if (Style()->HasTextDecorationLines()) {
999 return InkOverflowRect();
1001 return ComputeSimpleTightBounds(aDrawTarget
);
1005 nsresult
nsBlockFrame::GetPrefWidthTightBounds(gfxContext
* aRenderingContext
,
1006 nscoord
* aX
, nscoord
* aXMost
) {
1007 nsIFrame
* firstInFlow
= FirstContinuation();
1008 if (firstInFlow
!= this) {
1009 return firstInFlow
->GetPrefWidthTightBounds(aRenderingContext
, aX
, aXMost
);
1016 InlinePrefISizeData data
;
1017 for (nsBlockFrame
* curFrame
= this; curFrame
;
1018 curFrame
= static_cast<nsBlockFrame
*>(curFrame
->GetNextContinuation())) {
1019 for (LineIterator line
= curFrame
->LinesBegin(),
1020 line_end
= curFrame
->LinesEnd();
1021 line
!= line_end
; ++line
) {
1022 nscoord childX
, childXMost
;
1023 if (line
->IsBlock()) {
1025 rv
= line
->mFirstChild
->GetPrefWidthTightBounds(aRenderingContext
,
1026 &childX
, &childXMost
);
1027 NS_ENSURE_SUCCESS(rv
, rv
);
1028 *aX
= std::min(*aX
, childX
);
1029 *aXMost
= std::max(*aXMost
, childXMost
);
1031 if (!curFrame
->GetPrevContinuation() && TextIndentAppliesTo(line
)) {
1032 data
.mCurrentLine
+= StyleText()->mTextIndent
.length
.Resolve(0);
1035 data
.SetLineContainer(curFrame
);
1036 nsIFrame
* kid
= line
->mFirstChild
;
1037 // Per comment in nsIFrame::GetPrefWidthTightBounds(), the function is
1038 // only implemented for nsBlockFrame and nsTextFrame and is used to
1039 // determine the intrinsic inline sizes of MathML token elements. These
1040 // elements shouldn't have percentage block sizes that require a
1041 // percentage basis for resolution.
1042 const IntrinsicSizeInput
kidInput(aRenderingContext
, Nothing(),
1044 for (int32_t i
= 0, i_end
= line
->GetChildCount(); i
!= i_end
;
1045 ++i
, kid
= kid
->GetNextSibling()) {
1046 rv
= kid
->GetPrefWidthTightBounds(aRenderingContext
, &childX
,
1048 NS_ENSURE_SUCCESS(rv
, rv
);
1049 *aX
= std::min(*aX
, data
.mCurrentLine
+ childX
);
1050 *aXMost
= std::max(*aXMost
, data
.mCurrentLine
+ childXMost
);
1051 kid
->AddInlinePrefISize(kidInput
, &data
);
1062 * Return whether aNewAvailableSpace is smaller *on either side*
1063 * (inline-start or inline-end) than aOldAvailableSpace, so that we know
1064 * if we need to redo layout on an line, replaced block, or block
1065 * formatting context, because its height (which we used to compute
1066 * aNewAvailableSpace) caused it to intersect additional floats.
1068 static bool AvailableSpaceShrunk(WritingMode aWM
,
1069 const LogicalRect
& aOldAvailableSpace
,
1070 const LogicalRect
& aNewAvailableSpace
,
1071 bool aCanGrow
/* debug-only */) {
1072 if (aNewAvailableSpace
.ISize(aWM
) == 0) {
1073 // Positions are not significant if the inline size is zero.
1074 return aOldAvailableSpace
.ISize(aWM
) != 0;
1078 aNewAvailableSpace
.IStart(aWM
) <= aOldAvailableSpace
.IStart(aWM
) ||
1079 aNewAvailableSpace
.IEnd(aWM
) <= aOldAvailableSpace
.IEnd(aWM
),
1080 "available space should not shrink on the start side and "
1081 "grow on the end side");
1083 aNewAvailableSpace
.IStart(aWM
) >= aOldAvailableSpace
.IStart(aWM
) ||
1084 aNewAvailableSpace
.IEnd(aWM
) >= aOldAvailableSpace
.IEnd(aWM
),
1085 "available space should not grow on the start side and "
1086 "shrink on the end side");
1089 aOldAvailableSpace
.IStart(aWM
) <= aNewAvailableSpace
.IStart(aWM
) &&
1090 aOldAvailableSpace
.IEnd(aWM
) >= aNewAvailableSpace
.IEnd(aWM
),
1091 "available space should never grow");
1093 // Have we shrunk on either side?
1094 return aNewAvailableSpace
.IStart(aWM
) > aOldAvailableSpace
.IStart(aWM
) ||
1095 aNewAvailableSpace
.IEnd(aWM
) < aOldAvailableSpace
.IEnd(aWM
);
1098 static LogicalSize
CalculateContainingBlockSizeForAbsolutes(
1099 WritingMode aWM
, const ReflowInput
& aReflowInput
, LogicalSize aFrameSize
) {
1100 // The issue here is that for a 'height' of 'auto' the reflow input
1101 // code won't know how to calculate the containing block height
1102 // because it's calculated bottom up. So we use our own computed
1103 // size as the dimensions.
1104 nsIFrame
* frame
= aReflowInput
.mFrame
;
1106 LogicalSize
cbSize(aFrameSize
);
1107 // Containing block is relative to the padding edge
1108 const LogicalMargin border
= aReflowInput
.ComputedLogicalBorder(aWM
);
1109 cbSize
.ISize(aWM
) -= border
.IStartEnd(aWM
);
1110 cbSize
.BSize(aWM
) -= border
.BStartEnd(aWM
);
1112 if (frame
->GetParent()->GetContent() != frame
->GetContent() ||
1113 frame
->GetParent()->IsCanvasFrame()) {
1117 // We are a wrapped frame for the content (and the wrapper is not the
1118 // canvas frame, whose size is not meaningful here).
1119 // Use the container's dimensions, if they have been precomputed.
1120 // XXX This is a hack! We really should be waiting until the outermost
1121 // frame is fully reflowed and using the resulting dimensions, even
1122 // if they're intrinsic.
1123 // In fact we should be attaching absolute children to the outermost
1124 // frame and not always sticking them in block frames.
1126 // First, find the reflow input for the outermost frame for this content.
1127 const ReflowInput
* lastRI
= &aReflowInput
;
1128 DebugOnly
<const ReflowInput
*> lastButOneRI
= &aReflowInput
;
1129 while (lastRI
->mParentReflowInput
&&
1130 lastRI
->mParentReflowInput
->mFrame
->GetContent() ==
1131 frame
->GetContent()) {
1132 lastButOneRI
= lastRI
;
1133 lastRI
= lastRI
->mParentReflowInput
;
1136 if (lastRI
== &aReflowInput
) {
1140 // For scroll containers, we can just use cbSize (which is the padding-box
1141 // size of the scrolled-content frame).
1142 if (lastRI
->mFrame
->IsScrollContainerOrSubclass()) {
1143 // Assert that we're not missing any frames between the abspos containing
1144 // block and the scroll container.
1146 MOZ_ASSERT(lastButOneRI
== &aReflowInput
);
1150 // Same for fieldsets, where the inner anonymous frame has the correct padding
1151 // area with the legend taken into account.
1152 if (lastRI
->mFrame
->IsFieldSetFrame()) {
1156 // We found a reflow input for the outermost wrapping frame, so use
1157 // its computed metrics if available, converted to our writing mode
1158 const LogicalSize lastRISize
= lastRI
->ComputedSize(aWM
);
1159 const LogicalMargin lastRIPadding
= lastRI
->ComputedLogicalPadding(aWM
);
1160 if (lastRISize
.ISize(aWM
) != NS_UNCONSTRAINEDSIZE
) {
1162 std::max(0, lastRISize
.ISize(aWM
) + lastRIPadding
.IStartEnd(aWM
));
1164 if (lastRISize
.BSize(aWM
) != NS_UNCONSTRAINEDSIZE
) {
1166 std::max(0, lastRISize
.BSize(aWM
) + lastRIPadding
.BStartEnd(aWM
));
1173 * Returns aFrame if it is a non-BFC block frame, and null otherwise.
1175 * This is used to determine whether to recurse into aFrame when applying
1176 * -webkit-line-clamp.
1178 static const nsBlockFrame
* GetAsLineClampDescendant(const nsIFrame
* aFrame
) {
1179 if (const nsBlockFrame
* block
= do_QueryFrame(aFrame
)) {
1180 if (!block
->HasAnyStateBits(NS_BLOCK_BFC
)) {
1187 static nsBlockFrame
* GetAsLineClampDescendant(nsIFrame
* aFrame
) {
1188 return const_cast<nsBlockFrame
*>(
1189 GetAsLineClampDescendant(const_cast<const nsIFrame
*>(aFrame
)));
1192 static bool IsLineClampRoot(const nsBlockFrame
* aFrame
) {
1193 if (!aFrame
->StyleDisplay()->mWebkitLineClamp
) {
1197 if (!aFrame
->HasAnyStateBits(NS_BLOCK_BFC
)) {
1201 if (StaticPrefs::layout_css_webkit_line_clamp_block_enabled() ||
1202 aFrame
->PresContext()->Document()->ChromeRulesEnabled()) {
1206 // For now, -webkit-box is the only thing allowed to be a line-clamp root.
1207 // Ideally we'd just make this work everywhere, but for now we're carrying
1208 // this forward as a limitation on the legacy -webkit-line-clamp feature,
1209 // since relaxing this limitation might create webcompat trouble.
1210 auto origDisplay
= [&] {
1211 if (aFrame
->Style()->GetPseudoType() == PseudoStyleType::scrolledContent
) {
1212 // If we're the anonymous block inside the scroll frame, we need to look
1213 // at the original display of our parent frame.
1214 MOZ_ASSERT(aFrame
->GetParent());
1215 const auto& parentDisp
= *aFrame
->GetParent()->StyleDisplay();
1216 MOZ_ASSERT(parentDisp
.mWebkitLineClamp
==
1217 aFrame
->StyleDisplay()->mWebkitLineClamp
,
1218 ":-moz-scrolled-content should inherit -webkit-line-clamp, "
1219 "via rule in UA stylesheet");
1220 return parentDisp
.mOriginalDisplay
;
1222 return aFrame
->StyleDisplay()->mOriginalDisplay
;
1224 return origDisplay
.Inside() == StyleDisplayInside::WebkitBox
;
1227 bool nsBlockFrame::IsInLineClampContext() const {
1228 if (IsLineClampRoot(this)) {
1231 const nsBlockFrame
* cur
= this;
1232 while (GetAsLineClampDescendant(cur
)) {
1233 cur
= do_QueryFrame(cur
->GetParent());
1237 if (IsLineClampRoot(cur
)) {
1244 bool nsBlockFrame::MaybeHasFloats() const {
1248 if (HasPushedFloats()) {
1251 // For the OverflowOutOfFlowsProperty I think we do enforce that, but it's
1252 // a mix of out-of-flow frames, so that's why the method name has "Maybe".
1253 return HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS
);
1257 * Iterator over all descendant inline line boxes, except for those that are
1258 * under an independent formatting context.
1260 class MOZ_RAII LineClampLineIterator
{
1262 explicit LineClampLineIterator(nsBlockFrame
* aFrame
)
1263 : mCur(aFrame
->LinesBegin()),
1264 mEnd(aFrame
->LinesEnd()),
1265 mCurrentFrame(mCur
== mEnd
? nullptr : aFrame
) {
1266 if (mCur
!= mEnd
&& !mCur
->IsInline()) {
1271 nsLineBox
* GetCurrentLine() { return mCurrentFrame
? mCur
.get() : nullptr; }
1272 nsBlockFrame
* GetCurrentFrame() { return mCurrentFrame
; }
1274 // Advances the iterator to the next line line.
1276 // Next() shouldn't be called once the iterator is at the end, which can be
1277 // checked for by GetCurrentLine() or GetCurrentFrame() returning null.
1279 MOZ_ASSERT(mCur
!= mEnd
&& mCurrentFrame
,
1280 "Don't call Next() when the iterator is at the end");
1289 // Reached the end of the current block. Pop the parent off the
1290 // stack; if there isn't one, then we've reached the end.
1291 if (mStack
.IsEmpty()) {
1292 mCurrentFrame
= nullptr;
1295 auto entry
= mStack
.PopLastElement();
1296 mCurrentFrame
= entry
.first
;
1297 mCur
= entry
.second
;
1298 mEnd
= mCurrentFrame
->LinesEnd();
1299 } else if (mCur
->IsBlock()) {
1300 if (nsBlockFrame
* child
= GetAsLineClampDescendant(mCur
->mFirstChild
)) {
1301 nsBlockFrame::LineIterator next
= mCur
;
1303 mStack
.AppendElement(std::make_pair(mCurrentFrame
, next
));
1304 mCur
= child
->LinesBegin();
1305 mEnd
= child
->LinesEnd();
1306 mCurrentFrame
= child
;
1308 // Some kind of frame we shouldn't descend into.
1312 MOZ_ASSERT(mCur
->IsInline());
1318 // The current line within the current block.
1320 // When this is equal to mEnd, the iterator is at its end, and mCurrentFrame
1322 nsBlockFrame::LineIterator mCur
;
1324 // The iterator end for the current block.
1325 nsBlockFrame::LineIterator mEnd
;
1327 // The current block.
1328 nsBlockFrame
* mCurrentFrame
;
1330 // Stack of mCurrentFrame and mEnd values that we push and pop as we enter and
1332 AutoTArray
<std::pair
<nsBlockFrame
*, nsBlockFrame::LineIterator
>, 8> mStack
;
1335 static bool ClearLineClampEllipsis(nsBlockFrame
* aFrame
) {
1336 if (!aFrame
->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS
)) {
1337 for (nsIFrame
* f
: aFrame
->PrincipalChildList()) {
1338 if (nsBlockFrame
* child
= GetAsLineClampDescendant(f
)) {
1339 if (ClearLineClampEllipsis(child
)) {
1347 aFrame
->RemoveStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS
);
1349 for (auto& line
: aFrame
->Lines()) {
1350 if (line
.HasLineClampEllipsis()) {
1351 line
.ClearHasLineClampEllipsis();
1356 // We didn't find a line with the ellipsis; it must have been deleted already.
1360 void nsBlockFrame::ClearLineClampEllipsis() { ::ClearLineClampEllipsis(this); }
1362 void nsBlockFrame::Reflow(nsPresContext
* aPresContext
, ReflowOutput
& aMetrics
,
1363 const ReflowInput
& aReflowInput
,
1364 nsReflowStatus
& aStatus
) {
1365 if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
1366 FinishAndStoreOverflow(&aMetrics
, aReflowInput
.mStyleDisplay
);
1371 DO_GLOBAL_REFLOW_COUNT("nsBlockFrame");
1372 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
1376 IndentBy(stdout
, gNoiseIndent
);
1378 printf(": begin reflow availSize=%d,%d computedSize=%d,%d\n",
1379 aReflowInput
.AvailableISize(), aReflowInput
.AvailableBSize(),
1380 aReflowInput
.ComputedISize(), aReflowInput
.ComputedBSize());
1382 AutoNoisyIndenter
indent(gNoisy
);
1383 PRTime start
= 0; // Initialize these variablies to silence the compiler.
1384 int32_t ctc
= 0; // We only use these if they are set (gLameReflowMetrics).
1385 if (gLameReflowMetrics
) {
1387 ctc
= nsLineBox::GetCtorCount();
1391 // ColumnSetWrapper's children depend on ColumnSetWrapper's block-size or
1392 // max-block-size because both affect the children's available block-size.
1393 if (IsColumnSetWrapperFrame()) {
1394 AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE
);
1397 Maybe
<nscoord
> restoreReflowInputAvailBSize
;
1398 auto MaybeRestore
= MakeScopeExit([&] {
1399 if (MOZ_UNLIKELY(restoreReflowInputAvailBSize
)) {
1400 const_cast<ReflowInput
&>(aReflowInput
)
1401 .SetAvailableBSize(*restoreReflowInputAvailBSize
);
1405 WritingMode wm
= aReflowInput
.GetWritingMode();
1406 const nscoord consumedBSize
= CalcAndCacheConsumedBSize();
1407 const nscoord effectiveContentBoxBSize
=
1408 GetEffectiveComputedBSize(aReflowInput
, consumedBSize
);
1409 // If we have non-auto block size, we're clipping our kids and we fit,
1410 // make sure our kids fit too.
1411 if (aReflowInput
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
&&
1412 aReflowInput
.ComputedBSize() != NS_UNCONSTRAINEDSIZE
&&
1413 ShouldApplyOverflowClipping(aReflowInput
.mStyleDisplay
)
1414 .contains(wm
.PhysicalAxis(LogicalAxis::Block
))) {
1415 LogicalMargin blockDirExtras
=
1416 aReflowInput
.ComputedLogicalBorderPadding(wm
);
1417 if (GetLogicalSkipSides().BStart()) {
1418 blockDirExtras
.BStart(wm
) = 0;
1420 // Block-end margin never causes us to create continuations, so we
1421 // don't need to worry about whether it fits in its entirety.
1422 blockDirExtras
.BStart(wm
) +=
1423 aReflowInput
.ComputedLogicalMargin(wm
).BStart(wm
);
1426 if (effectiveContentBoxBSize
+ blockDirExtras
.BStartEnd(wm
) <=
1427 aReflowInput
.AvailableBSize()) {
1428 restoreReflowInputAvailBSize
.emplace(aReflowInput
.AvailableBSize());
1429 const_cast<ReflowInput
&>(aReflowInput
)
1430 .SetAvailableBSize(NS_UNCONSTRAINEDSIZE
);
1434 if (IsFrameTreeTooDeep(aReflowInput
, aMetrics
, aStatus
)) {
1438 // OK, some lines may be reflowed. Blow away any saved line cursor
1439 // because we may invalidate the nondecreasing
1440 // overflowArea.InkOverflow().y/yMost invariant, and we may even
1441 // delete the line with the line cursor.
1444 // See comment below about oldSize. Use *only* for the
1445 // abs-pos-containing-block-size-change optimization!
1446 nsSize oldSize
= GetSize();
1448 // Should we create a float manager?
1449 nsAutoFloatManager
autoFloatManager(const_cast<ReflowInput
&>(aReflowInput
));
1451 // XXXldb If we start storing the float manager in the frame rather
1452 // than keeping it around only during reflow then we should create it
1453 // only when there are actually floats to manage. Otherwise things
1454 // like tables will gain significant bloat.
1455 bool needFloatManager
= nsBlockFrame::BlockNeedsFloatManager(this);
1456 if (needFloatManager
) {
1457 autoFloatManager
.CreateFloatManager(aPresContext
);
1460 if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION
) &&
1461 PresContext()->BidiEnabled()) {
1462 static_cast<nsBlockFrame
*>(FirstContinuation())->ResolveBidi();
1465 // Whether to apply text-wrap: balance behavior.
1467 StyleText()->mTextWrapStyle
== StyleTextWrapStyle::Balance
&&
1468 !GetPrevContinuation();
1470 // Struct used to hold the "target" number of lines or clamp position to
1471 // maintain when doing text-wrap: balance.
1472 struct BalanceTarget
{
1473 // If line-clamp is in effect, mContent and mOffset indicate the starting
1474 // position of the first line after the clamp limit, and mBlockCoord is the
1475 // block-axis offset of its position.
1476 // If line-clamp is not in use, mContent is null, mOffset is the total
1477 // number of lines that the block must contain, and mBlockCoord is its end
1478 // edge in the block direction.
1479 nsIContent
* mContent
= nullptr;
1480 int32_t mOffset
= -1;
1481 nscoord mBlockCoord
= 0;
1483 bool operator==(const BalanceTarget
& aOther
) const {
1484 return mContent
== aOther
.mContent
&& mOffset
== aOther
.mOffset
&&
1485 mBlockCoord
== aOther
.mBlockCoord
;
1487 bool operator!=(const BalanceTarget
& aOther
) const {
1488 return !(*this == aOther
);
1492 BalanceTarget balanceTarget
;
1494 // Helpers for text-wrap: balance implementation:
1496 // Count the number of lines in the mLines list, but return -1 (to suppress
1497 // balancing) instead if the count is going to exceed aLimit, or if we
1498 // encounter a block.
1499 auto countLinesUpTo
= [&](int32_t aLimit
) -> int32_t {
1501 for (auto iter
= mLines
.begin(); iter
!= mLines
.end(); ++iter
) {
1502 if (++n
> aLimit
|| iter
->IsBlock()) {
1509 // Return a BalanceTarget record representing the position at which line-clamp
1510 // will take effect for the current line list. Only to be used when there are
1511 // enough lines that the clamp will apply.
1512 auto getClampPosition
= [&](uint32_t aClampCount
) -> BalanceTarget
{
1513 MOZ_ASSERT(aClampCount
< mLines
.size());
1514 auto iter
= mLines
.begin();
1515 for (uint32_t i
= 0; i
< aClampCount
; i
++) {
1518 nsIFrame
* firstChild
= iter
->mFirstChild
;
1520 return BalanceTarget
{};
1522 nsIContent
* content
= firstChild
->GetContent();
1524 return BalanceTarget
{};
1527 if (firstChild
->IsTextFrame()) {
1528 auto* textFrame
= static_cast<nsTextFrame
*>(firstChild
);
1529 offset
= textFrame
->GetContentOffset();
1531 return BalanceTarget
{content
, offset
, iter
.get()->BStart()};
1534 // "balancing" is implemented by shortening the effective inline-size of the
1535 // lines, so that content will tend to be pushed down to fill later lines of
1536 // the block. `balanceInset` is the current amount of "inset" to apply, and
1537 // `balanceStep` is the increment to adjust it by for the next iteration.
1538 nscoord balanceStep
= 0;
1540 // text-wrap: balance loop, executed only once if balancing is not required.
1541 nsReflowStatus reflowStatus
;
1542 TrialReflowState
trialState(consumedBSize
, effectiveContentBoxBSize
,
1545 // Save the initial floatManager state for repeated trial reflows.
1546 // We'll restore (and re-save) the initial state each time we repeat the
1548 nsFloatManager::SavedState floatManagerState
;
1549 aReflowInput
.mFloatManager
->PushState(&floatManagerState
);
1551 aMetrics
= ReflowOutput(aMetrics
.GetWritingMode());
1553 TrialReflow(aPresContext
, aMetrics
, aReflowInput
, trialState
);
1555 // Do we need to start a `text-wrap: balance` iteration?
1558 // Don't try to balance an incomplete block, or if we had to use an
1559 // overflow-wrap break position in the initial reflow.
1560 if (!reflowStatus
.IsFullyComplete() || trialState
.mUsedOverflowWrap
) {
1563 balanceTarget
.mOffset
=
1564 countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit());
1565 if (balanceTarget
.mOffset
< 2) {
1566 // If there are less than 2 lines, or the number exceeds the limit,
1567 // no balancing is needed; just break from the balance loop.
1570 balanceTarget
.mBlockCoord
= mLines
.back()->BEnd();
1571 // Initialize the amount of inset to try, and the iteration step size.
1572 balanceStep
= aReflowInput
.ComputedISize() / balanceTarget
.mOffset
;
1573 trialState
.ResetForBalance(balanceStep
);
1576 // If -webkit-line-clamp is in effect, then we need to maintain the
1577 // content location at which clamping occurs, rather than the total
1578 // number of lines in the block.
1579 if (StaticPrefs::layout_css_text_wrap_balance_after_clamp_enabled() &&
1580 IsLineClampRoot(this)) {
1581 uint32_t lineClampCount
= aReflowInput
.mStyleDisplay
->mWebkitLineClamp
;
1582 if (uint32_t(balanceTarget
.mOffset
) > lineClampCount
) {
1583 auto t
= getClampPosition(lineClampCount
);
1590 // Restore initial floatManager state for a new trial with updated inset.
1591 aReflowInput
.mFloatManager
->PopState(&floatManagerState
);
1595 // Helper to determine whether the current trial succeeded (i.e. was able
1596 // to fit the content into the expected number of lines).
1597 auto trialSucceeded
= [&]() -> bool {
1598 if (!reflowStatus
.IsFullyComplete() || trialState
.mUsedOverflowWrap
) {
1601 if (balanceTarget
.mContent
) {
1602 auto t
= getClampPosition(aReflowInput
.mStyleDisplay
->mWebkitLineClamp
);
1603 return t
== balanceTarget
;
1606 countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit());
1607 return numLines
== balanceTarget
.mOffset
&&
1608 mLines
.back()->BEnd() == balanceTarget
.mBlockCoord
;
1611 // If we're in the process of a balance operation, check whether we've
1612 // inset by too much and either increase or reduce the inset for the next
1614 if (balanceStep
> 0) {
1615 if (trialSucceeded()) {
1616 trialState
.ResetForBalance(balanceStep
);
1618 trialState
.ResetForBalance(-balanceStep
);
1622 aReflowInput
.mFloatManager
->PopState(&floatManagerState
);
1626 // If we were attempting to balance, check whether the final iteration was
1627 // successful, and if not, back up by one step.
1628 if (balanceTarget
.mOffset
>= 0) {
1629 if (!trialState
.mInset
|| trialSucceeded()) {
1632 trialState
.ResetForBalance(-1);
1634 aReflowInput
.mFloatManager
->PopState(&floatManagerState
);
1638 // If we reach here, no balancing was required, so just exit; we don't
1639 // reset (pop) the floatManager state because this is the reflow we're
1640 // going to keep. So the saved state is just dropped.
1642 } // End of text-wrap: balance retry loop
1644 // If the block direction is right-to-left, we need to update the bounds of
1645 // lines that were placed relative to mContainerSize during reflow, as
1646 // we typically do not know the true container size until we've reflowed all
1647 // its children. So we use a dummy mContainerSize during reflow (see
1648 // BlockReflowState's constructor) and then fix up the positions of the
1649 // lines here, once the final block size is known.
1651 // Note that writing-mode:vertical-rl is the only case where the block
1652 // logical direction progresses in a negative physical direction, and
1653 // therefore block-dir coordinate conversion depends on knowing the width
1654 // of the coordinate space in order to translate between the logical and
1655 // physical origins.
1656 if (aReflowInput
.GetWritingMode().IsVerticalRL()) {
1657 nsSize containerSize
= aMetrics
.PhysicalSize();
1658 nscoord deltaX
= containerSize
.width
- trialState
.mContainerWidth
;
1660 // We compute our lines and markers' overflow areas later in
1661 // ComputeOverflowAreas(), so we don't need to adjust their overflow areas
1663 const nsPoint
physicalDelta(deltaX
, 0);
1664 for (auto& line
: Lines()) {
1665 UpdateLineContainerSize(&line
, containerSize
);
1667 trialState
.mFcBounds
.Clear();
1668 if (nsFrameList
* floats
= GetFloats()) {
1669 for (nsIFrame
* f
: *floats
) {
1670 f
->MovePositionBy(physicalDelta
);
1671 ConsiderChildOverflow(trialState
.mFcBounds
, f
);
1674 nsFrameList
* markerList
= GetOutsideMarkerList();
1676 for (nsIFrame
* f
: *markerList
) {
1677 f
->MovePositionBy(physicalDelta
);
1680 if (nsFrameList
* overflowContainers
= GetOverflowContainers()) {
1681 trialState
.mOcBounds
.Clear();
1682 for (nsIFrame
* f
: *overflowContainers
) {
1683 f
->MovePositionBy(physicalDelta
);
1684 ConsiderChildOverflow(trialState
.mOcBounds
, f
);
1690 aMetrics
.SetOverflowAreasToDesiredBounds();
1691 ComputeOverflowAreas(aMetrics
.mOverflowAreas
,
1692 trialState
.mBlockEndEdgeOfChildren
,
1693 aReflowInput
.mStyleDisplay
);
1694 // Factor overflow container child bounds into the overflow area
1695 aMetrics
.mOverflowAreas
.UnionWith(trialState
.mOcBounds
);
1696 // Factor pushed float child bounds into the overflow area
1697 aMetrics
.mOverflowAreas
.UnionWith(trialState
.mFcBounds
);
1699 // Let the absolutely positioned container reflow any absolutely positioned
1700 // child frames that need to be reflowed, e.g., elements with a percentage
1701 // based width/height
1702 // We want to do this under either of two conditions:
1703 // 1. If we didn't do the incremental reflow above.
1704 // 2. If our size changed.
1705 // Even though it's the padding edge that's the containing block, we
1706 // can use our rect (the border edge) since if the border style
1707 // changed, the reflow would have been targeted at us so we'd satisfy
1709 // XXX checking oldSize is bogus, there are various reasons we might have
1710 // reflowed but our size might not have been changed to what we
1711 // asked for (e.g., we ended up being pushed to a new page)
1712 // When WillReflowAgainForClearance is true, we will reflow again without
1713 // resetting the size. Because of this, we must not reflow our abs-pos
1714 // children in that situation --- what we think is our "new size" will not be
1715 // our real new size. This also happens to be more efficient.
1716 WritingMode parentWM
= aMetrics
.GetWritingMode();
1717 if (HasAbsolutelyPositionedChildren()) {
1718 nsAbsoluteContainingBlock
* absoluteContainer
= GetAbsoluteContainingBlock();
1719 bool haveInterrupt
= aPresContext
->HasPendingInterrupt();
1720 if (aReflowInput
.WillReflowAgainForClearance() || haveInterrupt
) {
1721 // Make sure that when we reflow again we'll actually reflow all the abs
1722 // pos frames that might conceivably depend on our size (or all of them,
1723 // if we're dirty right now and interrupted; in that case we also need
1724 // to mark them all with NS_FRAME_IS_DIRTY). Sadly, we can't do much
1725 // better than that, because we don't really know what our size will be,
1726 // and it might in fact not change on the followup reflow!
1727 if (haveInterrupt
&& HasAnyStateBits(NS_FRAME_IS_DIRTY
)) {
1728 absoluteContainer
->MarkAllFramesDirty();
1730 absoluteContainer
->MarkSizeDependentFramesDirty();
1732 if (haveInterrupt
) {
1733 // We're not going to reflow absolute frames; make sure to account for
1734 // their existing overflow areas, which is usually a side effect of this
1737 // TODO(emilio): nsAbsoluteContainingBlock::Reflow already checks for
1738 // interrupt, can we just rely on it and unconditionally take the else
1739 // branch below? That's a bit more subtle / risky, since I don't see
1740 // what would reflow them in that case if they depended on our size.
1741 for (nsIFrame
* kid
= absoluteContainer
->GetChildList().FirstChild();
1742 kid
; kid
= kid
->GetNextSibling()) {
1743 ConsiderChildOverflow(aMetrics
.mOverflowAreas
, kid
);
1747 LogicalSize containingBlockSize
=
1748 CalculateContainingBlockSizeForAbsolutes(parentWM
, aReflowInput
,
1749 aMetrics
.Size(parentWM
));
1751 // Mark frames that depend on changes we just made to this frame as dirty:
1752 // Now we can assume that the padding edge hasn't moved.
1753 // We need to reflow the absolutes if one of them depends on
1754 // its placeholder position, or the containing block size in a
1755 // direction in which the containing block size might have
1758 // XXX "width" and "height" in this block will become ISize and BSize
1759 // when nsAbsoluteContainingBlock is logicalized
1760 bool cbWidthChanged
= aMetrics
.Width() != oldSize
.width
;
1761 bool isRoot
= !GetContent()->GetParent();
1762 // If isRoot and we have auto height, then we are the initial
1763 // containing block and the containing block height is the
1764 // viewport height, which can't change during incremental
1766 bool cbHeightChanged
=
1767 !(isRoot
&& NS_UNCONSTRAINEDSIZE
== aReflowInput
.ComputedHeight()) &&
1768 aMetrics
.Height() != oldSize
.height
;
1770 nsRect
containingBlock(nsPoint(0, 0),
1771 containingBlockSize
.GetPhysicalSize(parentWM
));
1772 AbsPosReflowFlags flags
= AbsPosReflowFlags::ConstrainHeight
;
1773 if (cbWidthChanged
) {
1774 flags
|= AbsPosReflowFlags::CBWidthChanged
;
1776 if (cbHeightChanged
) {
1777 flags
|= AbsPosReflowFlags::CBHeightChanged
;
1779 // Setup the line cursor here to optimize line searching for
1780 // calculating hypothetical position of absolutely-positioned
1782 SetupLineCursorForQuery();
1783 absoluteContainer
->Reflow(this, aPresContext
, aReflowInput
, reflowStatus
,
1784 containingBlock
, flags
,
1785 &aMetrics
.mOverflowAreas
);
1789 FinishAndStoreOverflow(&aMetrics
, aReflowInput
.mStyleDisplay
);
1791 aStatus
= reflowStatus
;
1794 // Between when we drain pushed floats and when we complete reflow,
1795 // we're allowed to have multiple continuations of the same float on
1796 // our floats list, since a first-in-flow might get pushed to a later
1797 // continuation of its containing block. But it's not permitted
1798 // outside that time.
1799 nsLayoutUtils::AssertNoDuplicateContinuations(
1800 this, GetChildList(FrameChildListID::Float
));
1803 IndentBy(stdout
, gNoiseIndent
);
1805 printf(": status=%s metrics=%d,%d carriedMargin=%d",
1806 ToString(aStatus
).c_str(), aMetrics
.ISize(parentWM
),
1807 aMetrics
.BSize(parentWM
), aMetrics
.mCarriedOutBEndMargin
.Get());
1808 if (HasOverflowAreas()) {
1809 printf(" overflow-vis={%d,%d,%d,%d}", aMetrics
.InkOverflow().x
,
1810 aMetrics
.InkOverflow().y
, aMetrics
.InkOverflow().width
,
1811 aMetrics
.InkOverflow().height
);
1812 printf(" overflow-scr={%d,%d,%d,%d}", aMetrics
.ScrollableOverflow().x
,
1813 aMetrics
.ScrollableOverflow().y
,
1814 aMetrics
.ScrollableOverflow().width
,
1815 aMetrics
.ScrollableOverflow().height
);
1820 if (gLameReflowMetrics
) {
1821 PRTime end
= PR_Now();
1823 int32_t ectc
= nsLineBox::GetCtorCount();
1824 int32_t numLines
= mLines
.size();
1828 PRTime delta
, perLineDelta
, lines
;
1829 lines
= int64_t(numLines
);
1830 delta
= end
- start
;
1831 perLineDelta
= delta
/ lines
;
1836 ": %" PRId64
" elapsed (%" PRId64
1837 " per line) (%d lines; %d new lines)",
1838 delta
, perLineDelta
, numLines
, ectc
- ctc
);
1839 printf("%s\n", buf
);
1844 nsReflowStatus
nsBlockFrame::TrialReflow(nsPresContext
* aPresContext
,
1845 ReflowOutput
& aMetrics
,
1846 const ReflowInput
& aReflowInput
,
1847 TrialReflowState
& aTrialState
) {
1849 // Between when we drain pushed floats and when we complete reflow,
1850 // we're allowed to have multiple continuations of the same float on
1851 // our floats list, since a first-in-flow might get pushed to a later
1852 // continuation of its containing block. But it's not permitted
1853 // outside that time.
1854 nsLayoutUtils::AssertNoDuplicateContinuations(
1855 this, GetChildList(FrameChildListID::Float
));
1858 // ALWAYS drain overflow. We never want to leave the previnflow's
1859 // overflow lines hanging around; block reflow depends on the
1860 // overflow line lists being cleared out between reflow passes.
1861 DrainOverflowLines();
1863 bool blockStartMarginRoot
, blockEndMarginRoot
;
1864 IsMarginRoot(&blockStartMarginRoot
, &blockEndMarginRoot
);
1866 BlockReflowState
state(aReflowInput
, aPresContext
, this, blockStartMarginRoot
,
1867 blockEndMarginRoot
, aTrialState
.mNeedFloatManager
,
1868 aTrialState
.mConsumedBSize
,
1869 aTrialState
.mEffectiveContentBoxBSize
,
1870 aTrialState
.mInset
);
1872 // Handle paginated overflow (see nsContainerFrame.h)
1873 nsReflowStatus ocStatus
;
1874 if (GetPrevInFlow()) {
1875 ReflowOverflowContainerChildren(
1876 aPresContext
, aReflowInput
, aTrialState
.mOcBounds
,
1877 ReflowChildFlags::Default
, ocStatus
, DefaultChildFrameMerge
,
1878 Some(state
.ContainerSize()));
1881 // Now that we're done cleaning up our overflow container lists, we can
1882 // give |state| its nsOverflowContinuationTracker.
1883 nsOverflowContinuationTracker
tracker(this, false);
1884 state
.mOverflowTracker
= &tracker
;
1886 // Drain & handle pushed floats
1887 DrainPushedFloats();
1888 ReflowPushedFloats(state
, aTrialState
.mFcBounds
);
1890 // If we're not dirty (which means we'll mark everything dirty later)
1891 // and our inline-size has changed, mark the lines dirty that we need to
1892 // mark dirty for a resize reflow.
1893 if (!HasAnyStateBits(NS_FRAME_IS_DIRTY
) && aReflowInput
.IsIResize()) {
1894 PrepareResizeReflow(state
);
1897 // The same for percentage text-indent, except conditioned on the
1899 if (!HasAnyStateBits(NS_FRAME_IS_DIRTY
) && aReflowInput
.mCBReflowInput
&&
1900 aReflowInput
.mCBReflowInput
->IsIResize() &&
1901 StyleText()->mTextIndent
.length
.HasPercent() && !mLines
.empty()) {
1902 mLines
.front()->MarkDirty();
1905 // For text-wrap:balance trials, we need to reflow all the lines even if
1906 // they're not all "dirty".
1907 if (aTrialState
.mBalancing
) {
1908 MarkAllDescendantLinesDirty(this);
1910 LazyMarkLinesDirty();
1914 aTrialState
.mUsedOverflowWrap
= ReflowDirtyLines(state
);
1916 // If we have a next-in-flow, and that next-in-flow has pushed floats from
1917 // this frame from a previous iteration of reflow, then we should not return
1918 // a status with IsFullyComplete() equals to true, since we actually have
1919 // overflow, it's just already been handled.
1921 // NOTE: This really shouldn't happen, since we _should_ pull back our floats
1922 // and reflow them, but just in case it does, this is a safety precaution so
1923 // we don't end up with a placeholder pointing to frames that have already
1924 // been deleted as part of removing our next-in-flow.
1925 if (state
.mReflowStatus
.IsFullyComplete()) {
1926 nsBlockFrame
* nif
= static_cast<nsBlockFrame
*>(GetNextInFlow());
1928 if (nif
->HasPushedFloatsFromPrevContinuation()) {
1929 if (nif
->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER
)) {
1930 state
.mReflowStatus
.SetOverflowIncomplete();
1932 state
.mReflowStatus
.SetIncomplete();
1937 nif
= static_cast<nsBlockFrame
*>(nif
->GetNextInFlow());
1941 state
.mReflowStatus
.MergeCompletionStatusFrom(ocStatus
);
1943 // If we end in a BR with clear and affected floats continue,
1944 // we need to continue, too.
1945 if (NS_UNCONSTRAINEDSIZE
!= aReflowInput
.AvailableBSize() &&
1946 state
.mReflowStatus
.IsComplete() &&
1947 state
.FloatManager()->ClearContinues(FindTrailingClear())) {
1948 state
.mReflowStatus
.SetIncomplete();
1951 if (!state
.mReflowStatus
.IsFullyComplete()) {
1952 if (HasOverflowLines() || HasPushedFloats()) {
1953 state
.mReflowStatus
.SetNextInFlowNeedsReflow();
1957 // Place the ::marker's frame if it is placed next to a block child.
1959 // According to the CSS2 spec, section 12.6.1, the ::marker's box
1960 // participates in the height calculation of the list-item box's
1963 // There are exactly two places a ::marker can be placed: near the
1964 // first or second line. It's only placed on the second line in a
1965 // rare case: an empty first line followed by a second line that
1966 // contains a block (example: <LI>\n<P>... ). This is where
1967 // the second case can happen.
1968 if (HasOutsideMarker() && !mLines
.empty() &&
1969 (mLines
.front()->IsBlock() ||
1970 (0 == mLines
.front()->BSize() && mLines
.front() != mLines
.back() &&
1971 mLines
.begin().next()->IsBlock()))) {
1972 // Reflow the ::marker's frame.
1973 ReflowOutput
reflowOutput(aReflowInput
);
1974 // XXX Use the entire line when we fix bug 25888.
1975 nsLayoutUtils::LinePosition position
;
1976 WritingMode wm
= aReflowInput
.GetWritingMode();
1978 nsLayoutUtils::GetFirstLinePosition(wm
, this, &position
);
1979 nscoord lineBStart
=
1980 havePosition
? position
.mBStart
1981 : aReflowInput
.ComputedLogicalBorderPadding(wm
).BStart(wm
);
1982 nsIFrame
* marker
= GetOutsideMarker();
1983 ReflowOutsideMarker(marker
, state
, reflowOutput
, lineBStart
);
1984 NS_ASSERTION(!MarkerIsEmpty() || reflowOutput
.BSize(wm
) == 0,
1985 "empty ::marker frame took up space");
1987 if (havePosition
&& !MarkerIsEmpty()) {
1988 // We have some lines to align the ::marker with.
1990 // Doing the alignment using the baseline will also cater for
1991 // ::markers that are placed next to a child block (bug 92896)
1993 // Tall ::markers won't look particularly nice here...
1995 marker
->GetLogicalRect(wm
, reflowOutput
.PhysicalSize());
1996 const auto baselineGroup
= BaselineSharingGroup::First
;
1997 Maybe
<nscoord
> result
;
1998 if (MOZ_LIKELY(!wm
.IsOrthogonalTo(marker
->GetWritingMode()))) {
1999 result
= marker
->GetNaturalBaselineBOffset(
2000 wm
, baselineGroup
, BaselineExportContext::LineLayout
);
2002 const auto markerBaseline
= result
.valueOrFrom([bbox
, wm
, marker
]() {
2003 return bbox
.BSize(wm
) + marker
->GetLogicalUsedMargin(wm
).BEnd(wm
);
2005 bbox
.BStart(wm
) = position
.mBaseline
- markerBaseline
;
2006 marker
->SetRect(wm
, bbox
, reflowOutput
.PhysicalSize());
2008 // Otherwise just leave the ::marker where it is, up against our
2009 // block-start padding.
2012 // Clear any existing -webkit-line-clamp ellipsis.
2013 if (aReflowInput
.mStyleDisplay
->mWebkitLineClamp
) {
2014 ClearLineClampEllipsis();
2019 // Compute our final size (for this trial layout)
2020 aTrialState
.mBlockEndEdgeOfChildren
=
2021 ComputeFinalSize(aReflowInput
, state
, aMetrics
);
2022 aTrialState
.mContainerWidth
= state
.ContainerSize().width
;
2025 AlignContent(state
, aMetrics
, aTrialState
.mBlockEndEdgeOfChildren
);
2027 return state
.mReflowStatus
;
2030 bool nsBlockFrame::CheckForCollapsedBEndMarginFromClearanceLine() {
2031 for (auto& line
: Reversed(Lines())) {
2032 if (0 != line
.BSize() || !line
.CachedIsEmpty()) {
2035 if (line
.HasClearance()) {
2042 static nsLineBox
* FindLineClampTarget(nsBlockFrame
*& aFrame
,
2043 StyleLineClamp aLineNumber
) {
2044 MOZ_ASSERT(aLineNumber
> 0);
2045 MOZ_ASSERT(!aFrame
->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS
),
2046 "Should have been removed earlier in nsBlockReflow::Reflow");
2048 nsLineBox
* target
= nullptr;
2049 nsBlockFrame
* targetFrame
= nullptr;
2050 bool foundFollowingLine
= false;
2052 LineClampLineIterator
iter(aFrame
);
2054 while (nsLineBox
* line
= iter
.GetCurrentLine()) {
2055 MOZ_ASSERT(!line
->HasLineClampEllipsis(),
2056 "Should have been removed earlier in nsBlockFrame::Reflow");
2057 MOZ_ASSERT(!iter
.GetCurrentFrame()->HasAnyStateBits(
2058 NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS
),
2059 "Should have been removed earlier in nsBlockReflow::Reflow");
2061 // Don't count a line that only has collapsible white space (as might exist
2062 // after calling e.g. getBoxQuads).
2063 if (line
->IsEmpty()) {
2068 if (aLineNumber
== 0) {
2069 // We already previously found our target line, and now we have
2070 // confirmed that there is another line after it.
2071 foundFollowingLine
= true;
2075 if (--aLineNumber
== 0) {
2076 // This is our target line. Continue looping to confirm that we
2077 // have another line after us.
2079 targetFrame
= iter
.GetCurrentFrame();
2085 if (!foundFollowingLine
) {
2091 MOZ_ASSERT(targetFrame
);
2093 aFrame
= targetFrame
;
2097 static nscoord
ApplyLineClamp(const ReflowInput
& aReflowInput
,
2098 nsBlockFrame
* aFrame
,
2099 nscoord aContentBlockEndEdge
) {
2100 if (!IsLineClampRoot(aFrame
)) {
2101 return aContentBlockEndEdge
;
2103 auto lineClamp
= aReflowInput
.mStyleDisplay
->mWebkitLineClamp
;
2104 nsBlockFrame
* frame
= aFrame
;
2105 nsLineBox
* line
= FindLineClampTarget(frame
, lineClamp
);
2107 // The number of lines did not exceed the -webkit-line-clamp value.
2108 return aContentBlockEndEdge
;
2111 // Mark the line as having an ellipsis so that TextOverflow will render it.
2112 line
->SetHasLineClampEllipsis();
2113 frame
->AddStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS
);
2115 // Translate the b-end edge of the line up to aFrame's space.
2116 nscoord edge
= line
->BEnd();
2117 for (nsIFrame
* f
= frame
; f
!= aFrame
; f
= f
->GetParent()) {
2119 f
->GetLogicalPosition(f
->GetParent()->GetSize()).B(f
->GetWritingMode());
2125 nscoord
nsBlockFrame::ComputeFinalSize(const ReflowInput
& aReflowInput
,
2126 BlockReflowState
& aState
,
2127 ReflowOutput
& aMetrics
) {
2128 WritingMode wm
= aState
.mReflowInput
.GetWritingMode();
2129 const LogicalMargin
& borderPadding
= aState
.BorderPadding();
2130 #ifdef NOISY_FINAL_SIZE
2132 printf(": mBCoord=%d mIsBEndMarginRoot=%s mPrevBEndMargin=%d bp=%d,%d\n",
2133 aState
.mBCoord
, aState
.mFlags
.mIsBEndMarginRoot
? "yes" : "no",
2134 aState
.mPrevBEndMargin
.get(), borderPadding
.BStart(wm
),
2135 borderPadding
.BEnd(wm
));
2138 // Compute final inline size
2139 LogicalSize
finalSize(wm
);
2140 finalSize
.ISize(wm
) =
2141 NSCoordSaturatingAdd(NSCoordSaturatingAdd(borderPadding
.IStart(wm
),
2142 aReflowInput
.ComputedISize()),
2143 borderPadding
.IEnd(wm
));
2145 // Return block-end margin information
2146 // rbs says he hit this assertion occasionally (see bug 86947), so
2147 // just set the margin to zero and we'll figure out why later
2148 // NS_ASSERTION(aMetrics.mCarriedOutBEndMargin.IsZero(),
2149 // "someone else set the margin");
2150 nscoord nonCarriedOutBDirMargin
= 0;
2151 if (!aState
.mFlags
.mIsBEndMarginRoot
) {
2152 // Apply rule from CSS 2.1 section 8.3.1. If we have some empty
2153 // line with clearance and a non-zero block-start margin and all
2154 // subsequent lines are empty, then we do not allow our children's
2155 // carried out block-end margin to be carried out of us and collapse
2156 // with our own block-end margin.
2157 if (CheckForCollapsedBEndMarginFromClearanceLine()) {
2158 // Convert the children's carried out margin to something that
2159 // we will include in our height
2160 nonCarriedOutBDirMargin
= aState
.mPrevBEndMargin
.Get();
2161 aState
.mPrevBEndMargin
.Zero();
2163 aMetrics
.mCarriedOutBEndMargin
= aState
.mPrevBEndMargin
;
2165 aMetrics
.mCarriedOutBEndMargin
.Zero();
2168 nscoord blockEndEdgeOfChildren
= aState
.mBCoord
+ nonCarriedOutBDirMargin
;
2169 // Shrink wrap our height around our contents.
2170 if (aState
.mFlags
.mIsBEndMarginRoot
||
2171 NS_UNCONSTRAINEDSIZE
!= aReflowInput
.ComputedBSize()) {
2172 // When we are a block-end-margin root make sure that our last
2173 // child's block-end margin is fully applied. We also do this when
2174 // we have a computed height, since in that case the carried out
2175 // margin is not going to be applied anywhere, so we should note it
2176 // here to be included in the overflow area.
2177 // Apply the margin only if there's space for it.
2178 if (blockEndEdgeOfChildren
< aState
.mReflowInput
.AvailableBSize()) {
2179 // Truncate block-end margin if it doesn't fit to our available BSize.
2180 blockEndEdgeOfChildren
=
2181 std::min(blockEndEdgeOfChildren
+ aState
.mPrevBEndMargin
.Get(),
2182 aState
.mReflowInput
.AvailableBSize());
2185 if (aState
.mFlags
.mBlockNeedsFloatManager
) {
2186 // Include the float manager's state to properly account for the
2187 // block-end margin of any floated elements; e.g., inside a table cell.
2189 // Note: The block coordinate returned by ClearFloats is always greater than
2190 // or equal to blockEndEdgeOfChildren.
2191 std::tie(blockEndEdgeOfChildren
, std::ignore
) =
2192 aState
.ClearFloats(blockEndEdgeOfChildren
, StyleClear::Both
);
2195 // undo cached alignment shift for sizing purposes
2196 // (we used shifted positions because the float manager uses them)
2197 blockEndEdgeOfChildren
-= aState
.mAlignContentShift
;
2198 aState
.UndoAlignContentShift();
2200 if (NS_UNCONSTRAINEDSIZE
!= aReflowInput
.ComputedBSize()) {
2201 // Note: We don't use blockEndEdgeOfChildren because it includes the
2203 const nscoord contentBSizeWithBStartBP
=
2204 aState
.mBCoord
+ nonCarriedOutBDirMargin
;
2206 // We don't care about ApplyLineClamp's return value (the line-clamped
2207 // content BSize) in this explicit-BSize codepath, but we do still need to
2208 // call ApplyLineClamp for ellipsis markers to be placed as-needed.
2209 ApplyLineClamp(aState
.mReflowInput
, this, contentBSizeWithBStartBP
);
2211 finalSize
.BSize(wm
) = ComputeFinalBSize(aState
, contentBSizeWithBStartBP
);
2213 // If the content block-size is larger than the effective computed
2214 // block-size, we extend the block-size to contain all the content.
2215 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
2216 if (aReflowInput
.ShouldApplyAutomaticMinimumOnBlockAxis()) {
2217 // Note: finalSize.BSize(wm) is the border-box size, so we compare it with
2218 // the content's block-size plus our border and padding..
2219 finalSize
.BSize(wm
) =
2220 std::max(finalSize
.BSize(wm
),
2221 contentBSizeWithBStartBP
+ borderPadding
.BEnd(wm
));
2223 // The size should be capped by its maximum block size.
2224 if (aReflowInput
.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE
) {
2225 finalSize
.BSize(wm
) =
2226 std::min(finalSize
.BSize(wm
), aReflowInput
.ComputedMaxBSize() +
2227 borderPadding
.BStartEnd(wm
));
2231 // Don't carry out a block-end margin when our BSize is fixed.
2233 // Note: this also includes the case that aReflowInput.ComputedBSize() is
2234 // calculated from aspect-ratio. i.e. Don't carry out block margin-end if it
2235 // is replaced by the block size from aspect-ratio and inline size.
2236 aMetrics
.mCarriedOutBEndMargin
.Zero();
2237 } else if (Maybe
<nscoord
> containBSize
= ContainIntrinsicBSize()) {
2238 // If we're size-containing in block axis and we don't have a specified
2239 // block size, then our final size should actually be computed from only
2240 // our border, padding and contain-intrinsic-block-size, ignoring the
2241 // actual contents. Hence this case is a simplified version of the case
2243 nscoord contentBSize
= *containBSize
;
2245 aReflowInput
.ApplyMinMaxBSize(contentBSize
, aState
.mConsumedBSize
);
2246 aMetrics
.mCarriedOutBEndMargin
.Zero();
2247 autoBSize
+= borderPadding
.BStartEnd(wm
);
2248 finalSize
.BSize(wm
) = autoBSize
;
2249 } else if (aState
.mReflowStatus
.IsInlineBreakBefore()) {
2250 // Our parent is expected to push this frame to the next page/column so
2251 // what size we set here doesn't really matter.
2252 finalSize
.BSize(wm
) = aReflowInput
.AvailableBSize();
2253 } else if (aState
.mReflowStatus
.IsComplete()) {
2254 const nscoord lineClampedContentBlockEndEdge
=
2255 ApplyLineClamp(aReflowInput
, this, blockEndEdgeOfChildren
);
2257 const nscoord bpBStart
= borderPadding
.BStart(wm
);
2258 const nscoord contentBSize
= blockEndEdgeOfChildren
- bpBStart
;
2259 const nscoord lineClampedContentBSize
=
2260 lineClampedContentBlockEndEdge
- bpBStart
;
2262 const nscoord autoBSize
= aReflowInput
.ApplyMinMaxBSize(
2263 lineClampedContentBSize
, aState
.mConsumedBSize
);
2264 if (autoBSize
!= contentBSize
) {
2265 // Our min-block-size, max-block-size, or -webkit-line-clamp value made
2266 // our bsize change. Don't carry out our kids' block-end margins.
2267 aMetrics
.mCarriedOutBEndMargin
.Zero();
2269 nscoord bSize
= autoBSize
+ borderPadding
.BStartEnd(wm
);
2270 if (MOZ_UNLIKELY(autoBSize
> contentBSize
&&
2271 bSize
> aReflowInput
.AvailableBSize() &&
2272 aReflowInput
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
)) {
2273 // Applying `min-size` made us overflow our available size.
2274 // Clamp it and report that we're Incomplete, or BreakBefore if we have
2275 // 'break-inside: avoid' that is applicable.
2276 bSize
= aReflowInput
.AvailableBSize();
2277 if (ShouldAvoidBreakInside(aReflowInput
)) {
2278 aState
.mReflowStatus
.SetInlineLineBreakBeforeAndReset();
2280 aState
.mReflowStatus
.SetIncomplete();
2283 finalSize
.BSize(wm
) = bSize
;
2285 NS_ASSERTION(aReflowInput
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
,
2286 "Shouldn't be incomplete if availableBSize is UNCONSTRAINED.");
2287 nscoord bSize
= std::max(aState
.mBCoord
, aReflowInput
.AvailableBSize());
2288 if (aReflowInput
.AvailableBSize() == NS_UNCONSTRAINEDSIZE
) {
2289 // This should never happen, but it does. See bug 414255
2290 bSize
= aState
.mBCoord
;
2292 const nscoord maxBSize
= aReflowInput
.ComputedMaxBSize();
2293 if (maxBSize
!= NS_UNCONSTRAINEDSIZE
&&
2294 aState
.mConsumedBSize
+ bSize
- borderPadding
.BStart(wm
) > maxBSize
) {
2295 // Compute this fragment's block-size, with the max-block-size
2296 // constraint taken into consideration.
2297 const nscoord clampedBSizeWithoutEndBP
=
2298 std::max(0, maxBSize
- aState
.mConsumedBSize
) +
2299 borderPadding
.BStart(wm
);
2300 const nscoord clampedBSize
=
2301 clampedBSizeWithoutEndBP
+ borderPadding
.BEnd(wm
);
2302 if (clampedBSize
<= aReflowInput
.AvailableBSize()) {
2303 // We actually fit after applying `max-size` so we should be
2304 // Overflow-Incomplete instead.
2305 bSize
= clampedBSize
;
2306 aState
.mReflowStatus
.SetOverflowIncomplete();
2308 // We cannot fit after applying `max-size` with our block-end BP, so
2309 // we should draw it in our next continuation.
2310 bSize
= clampedBSizeWithoutEndBP
;
2313 finalSize
.BSize(wm
) = bSize
;
2316 if (IsTrueOverflowContainer()) {
2317 if (aState
.mReflowStatus
.IsIncomplete()) {
2318 // Overflow containers can only be overflow complete.
2319 // Note that auto height overflow containers have no normal children
2320 NS_ASSERTION(finalSize
.BSize(wm
) == 0,
2321 "overflow containers must be zero-block-size");
2322 aState
.mReflowStatus
.SetOverflowIncomplete();
2324 } else if (aReflowInput
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
&&
2325 !aState
.mReflowStatus
.IsInlineBreakBefore() &&
2326 aState
.mReflowStatus
.IsComplete()) {
2327 // Currently only used for grid items, but could be used in other contexts.
2328 // The FragStretchBSizeProperty is our expected non-fragmented block-size
2329 // we should stretch to (for align-self:stretch etc). In some fragmentation
2330 // cases though, the last fragment (this frame since we're complete), needs
2331 // to have extra size applied because earlier fragments consumed too much of
2332 // our computed size due to overflowing their containing block. (E.g. this
2333 // ensures we fill the last row when a multi-row grid item is fragmented).
2335 nscoord bSize
= GetProperty(FragStretchBSizeProperty(), &found
);
2337 finalSize
.BSize(wm
) = std::max(bSize
, finalSize
.BSize(wm
));
2341 // Clamp the content size to fit within the margin-box clamp size, if any.
2342 if (MOZ_UNLIKELY(aReflowInput
.mComputeSizeFlags
.contains(
2343 ComputeSizeFlag::BClampMarginBoxMinSize
)) &&
2344 aState
.mReflowStatus
.IsComplete()) {
2346 nscoord cbSize
= GetProperty(BClampMarginBoxMinSizeProperty(), &found
);
2348 auto marginBoxBSize
=
2349 finalSize
.BSize(wm
) +
2350 aReflowInput
.ComputedLogicalMargin(wm
).BStartEnd(wm
);
2351 auto overflow
= marginBoxBSize
- cbSize
;
2353 auto contentBSize
= finalSize
.BSize(wm
) - borderPadding
.BStartEnd(wm
);
2354 auto newContentBSize
= std::max(nscoord(0), contentBSize
- overflow
);
2355 // XXXmats deal with percentages better somehow?
2356 finalSize
.BSize(wm
) -= contentBSize
- newContentBSize
;
2361 // Screen out negative block sizes --- can happen due to integer overflows :-(
2362 finalSize
.BSize(wm
) = std::max(0, finalSize
.BSize(wm
));
2364 if (blockEndEdgeOfChildren
!= finalSize
.BSize(wm
) - borderPadding
.BEnd(wm
)) {
2365 SetProperty(BlockEndEdgeOfChildrenProperty(), blockEndEdgeOfChildren
);
2367 RemoveProperty(BlockEndEdgeOfChildrenProperty());
2370 aMetrics
.SetSize(wm
, finalSize
);
2372 return blockEndEdgeOfChildren
;
2375 void nsBlockFrame::AlignContent(BlockReflowState
& aState
,
2376 ReflowOutput
& aMetrics
,
2377 nscoord aBEndEdgeOfChildren
) {
2378 if (!StaticPrefs::layout_css_align_content_blocks_enabled()) {
2382 StyleAlignFlags alignment
= StylePosition()->mAlignContent
.primary
;
2383 alignment
&= ~StyleAlignFlags::FLAG_BITS
;
2386 const bool isCentered
= alignment
== StyleAlignFlags::CENTER
||
2387 alignment
== StyleAlignFlags::SPACE_AROUND
||
2388 alignment
== StyleAlignFlags::SPACE_EVENLY
;
2389 const bool isEndAlign
= alignment
== StyleAlignFlags::END
||
2390 alignment
== StyleAlignFlags::FLEX_END
||
2391 alignment
== StyleAlignFlags::LAST_BASELINE
;
2392 if (!isEndAlign
&& !isCentered
&& !aState
.mAlignContentShift
) {
2393 // desired shift = 0, no cached shift to undo
2397 // NOTE: ComputeFinalSize already called aState.UndoAlignContentShift(),
2398 // so metrics no longer include cached shift.
2399 // NOTE: Content is currently positioned at cached shift
2400 // NOTE: Content has been fragmented against 0-shift assumption.
2404 WritingMode wm
= aState
.mReflowInput
.GetWritingMode();
2405 if ((isCentered
|| isEndAlign
) && !mLines
.empty() &&
2406 aState
.mReflowStatus
.IsFullyComplete() && !GetPrevInFlow()) {
2407 nscoord availB
= aState
.mReflowInput
.AvailableBSize();
2408 nscoord endB
= aMetrics
.BSize(wm
) - aState
.BorderPadding().BEnd(wm
);
2409 shift
= std::min(availB
, endB
) - aBEndEdgeOfChildren
;
2411 // note: these measures all include start BP, so it subtracts out
2412 if (!(StylePosition()->mAlignContent
.primary
& StyleAlignFlags::UNSAFE
)) {
2413 shift
= std::max(0, shift
);
2419 // else: zero shift if start-aligned or if fragmented
2421 nscoord delta
= shift
- aState
.mAlignContentShift
;
2424 LogicalPoint
translation(wm
, 0, delta
);
2425 for (nsLineBox
& line
: Lines()) {
2426 SlideLine(aState
, &line
, delta
);
2428 for (nsIFrame
* kid
: GetChildList(FrameChildListID::Float
)) {
2429 kid
->MovePositionBy(wm
, translation
);
2430 nsContainerFrame::PlaceFrameView(kid
);
2432 if (HasOutsideMarker() && !mLines
.empty()) {
2433 nsIFrame
* marker
= GetOutsideMarker();
2434 marker
->MovePositionBy(wm
, translation
);
2440 SetProperty(AlignContentShift(), shift
);
2442 RemoveProperty(AlignContentShift());
2446 void nsBlockFrame::ConsiderBlockEndEdgeOfChildren(
2447 OverflowAreas
& aOverflowAreas
, nscoord aBEndEdgeOfChildren
,
2448 const nsStyleDisplay
* aDisplay
) const {
2449 const auto wm
= GetWritingMode();
2451 // Factor in the block-end edge of the children. Child frames will be added
2452 // to the overflow area as we iterate through the lines, but their margins
2453 // won't, so we need to account for block-end margins here.
2454 // REVIEW: For now, we do this for both visual and scrollable area,
2455 // although when we make scrollable overflow area not be a subset of
2456 // visual, we can change this.
2458 if (Style()->GetPseudoType() == PseudoStyleType::scrolledContent
) {
2459 // If we are a scrolled inner frame, add our block-end padding to our
2460 // children's block-end edge.
2462 // Note: aBEndEdgeOfChildren already includes our own block-start padding
2463 // because it is relative to our block-start edge of our border-box, which
2464 // is the same as our padding-box here.
2465 MOZ_ASSERT(GetLogicalUsedBorderAndPadding(wm
) == GetLogicalUsedPadding(wm
),
2466 "A scrolled inner frame shouldn't have any border!");
2467 aBEndEdgeOfChildren
+= GetLogicalUsedPadding(wm
).BEnd(wm
);
2470 // XXX Currently, overflow areas are stored as physical rects, so we have
2471 // to handle writing modes explicitly here. If we change overflow rects
2472 // to be stored logically, this can be simplified again.
2473 if (wm
.IsVertical()) {
2474 if (wm
.IsVerticalLR()) {
2475 for (const auto otype
: AllOverflowTypes()) {
2476 if (!(aDisplay
->IsContainLayout() &&
2477 otype
== OverflowType::Scrollable
)) {
2478 // Layout containment should force all overflow to be ink (visual)
2479 // overflow, so if we're layout-contained, we only add our children's
2480 // block-end edge to the ink (visual) overflow -- not to the
2481 // scrollable overflow.
2482 nsRect
& o
= aOverflowAreas
.Overflow(otype
);
2483 o
.width
= std::max(o
.XMost(), aBEndEdgeOfChildren
) - o
.x
;
2487 for (const auto otype
: AllOverflowTypes()) {
2488 if (!(aDisplay
->IsContainLayout() &&
2489 otype
== OverflowType::Scrollable
)) {
2490 nsRect
& o
= aOverflowAreas
.Overflow(otype
);
2491 nscoord xmost
= o
.XMost();
2492 o
.x
= std::min(o
.x
, xmost
- aBEndEdgeOfChildren
);
2493 o
.width
= xmost
- o
.x
;
2498 for (const auto otype
: AllOverflowTypes()) {
2499 if (!(aDisplay
->IsContainLayout() && otype
== OverflowType::Scrollable
)) {
2500 nsRect
& o
= aOverflowAreas
.Overflow(otype
);
2501 o
.height
= std::max(o
.YMost(), aBEndEdgeOfChildren
) - o
.y
;
2507 void nsBlockFrame::ComputeOverflowAreas(OverflowAreas
& aOverflowAreas
,
2508 nscoord aBEndEdgeOfChildren
,
2509 const nsStyleDisplay
* aDisplay
) const {
2510 // XXX_perf: This can be done incrementally. It is currently one of
2511 // the things that makes incremental reflow O(N^2).
2512 auto overflowClipAxes
= ShouldApplyOverflowClipping(aDisplay
);
2513 auto overflowClipMargin
= OverflowClipMargin(overflowClipAxes
);
2514 if (overflowClipAxes
== kPhysicalAxesBoth
&& overflowClipMargin
== nsSize()) {
2518 // We rely here on our caller having called SetOverflowAreasToDesiredBounds().
2519 nsRect frameBounds
= aOverflowAreas
.ScrollableOverflow();
2521 for (const auto& line
: Lines()) {
2522 if (aDisplay
->IsContainLayout()) {
2523 // If we have layout containment, we should only consider our child's
2524 // ink overflow, leaving the scrollable regions of the parent
2526 // Note: scrollable overflow is a subset of ink overflow,
2527 // so this has the same affect as unioning the child's visual and
2528 // scrollable overflow with its parent's ink overflow.
2529 nsRect childVisualRect
= line
.InkOverflowRect();
2530 OverflowAreas childVisualArea
= OverflowAreas(childVisualRect
, nsRect());
2531 aOverflowAreas
.UnionWith(childVisualArea
);
2533 aOverflowAreas
.UnionWith(line
.GetOverflowAreas());
2537 // Factor an outside ::marker in; normally the ::marker will be factored
2538 // into the line-box's overflow areas. However, if the line is a block
2539 // line then it won't; if there are no lines, it won't. So just
2540 // factor it in anyway (it can't hurt if it was already done).
2541 // XXXldb Can we just fix GetOverflowArea instead?
2542 if (nsIFrame
* outsideMarker
= GetOutsideMarker()) {
2543 aOverflowAreas
.UnionAllWith(outsideMarker
->GetRect());
2546 ConsiderBlockEndEdgeOfChildren(aOverflowAreas
, aBEndEdgeOfChildren
, aDisplay
);
2548 if (!overflowClipAxes
.isEmpty()) {
2549 aOverflowAreas
.ApplyClipping(frameBounds
, overflowClipAxes
,
2550 overflowClipMargin
);
2553 #ifdef NOISY_OVERFLOW_AREAS
2554 printf("%s: InkOverflowArea=%s, ScrollableOverflowArea=%s\n", ListTag().get(),
2555 ToString(aOverflowAreas
.InkOverflow()).c_str(),
2556 ToString(aOverflowAreas
.ScrollableOverflow()).c_str());
2560 void nsBlockFrame::UnionChildOverflow(OverflowAreas
& aOverflowAreas
,
2561 bool aAsIfScrolled
) {
2562 // We need to update the overflow areas of lines manually, as they
2563 // get cached and re-used otherwise. Lines aren't exposed as normal
2564 // frame children, so calling UnionChildOverflow alone will end up
2565 // using the old cached values.
2566 for (auto& line
: Lines()) {
2567 nsRect bounds
= line
.GetPhysicalBounds();
2568 OverflowAreas
lineAreas(bounds
, bounds
);
2570 int32_t n
= line
.GetChildCount();
2571 for (nsIFrame
* lineFrame
= line
.mFirstChild
; n
> 0;
2572 lineFrame
= lineFrame
->GetNextSibling(), --n
) {
2573 ConsiderChildOverflow(lineAreas
, lineFrame
);
2576 // Consider the overflow areas of the floats attached to the line as well
2577 if (line
.HasFloats()) {
2578 for (nsIFrame
* f
: line
.Floats()) {
2579 ConsiderChildOverflow(lineAreas
, f
);
2583 line
.SetOverflowAreas(lineAreas
);
2584 aOverflowAreas
.UnionWith(lineAreas
);
2587 // Union with child frames, skipping the principal and float lists
2588 // since we already handled those using the line boxes.
2589 nsLayoutUtils::UnionChildOverflow(
2590 this, aOverflowAreas
,
2591 {FrameChildListID::Principal
, FrameChildListID::Float
});
2594 bool nsBlockFrame::ComputeCustomOverflow(OverflowAreas
& aOverflowAreas
) {
2596 nscoord blockEndEdgeOfChildren
=
2597 GetProperty(BlockEndEdgeOfChildrenProperty(), &found
);
2599 ConsiderBlockEndEdgeOfChildren(aOverflowAreas
, blockEndEdgeOfChildren
,
2603 // Line cursor invariants depend on the overflow areas of the lines, so
2604 // we must clear the line cursor since those areas may have changed.
2606 return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas
);
2609 void nsBlockFrame::LazyMarkLinesDirty() {
2610 if (HasAnyStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES
)) {
2611 for (LineIterator line
= LinesBegin(), line_end
= LinesEnd();
2612 line
!= line_end
; ++line
) {
2613 int32_t n
= line
->GetChildCount();
2614 for (nsIFrame
* lineFrame
= line
->mFirstChild
; n
> 0;
2615 lineFrame
= lineFrame
->GetNextSibling(), --n
) {
2616 if (lineFrame
->IsSubtreeDirty()) {
2617 // NOTE: MarkLineDirty does more than just marking the line dirty.
2618 MarkLineDirty(line
, &mLines
);
2623 RemoveStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES
);
2627 void nsBlockFrame::MarkLineDirty(LineIterator aLine
,
2628 const nsLineList
* aLineList
) {
2631 aLine
->SetInvalidateTextRuns(true);
2634 IndentBy(stdout
, gNoiseIndent
);
2636 printf(": mark line %p dirty\n", static_cast<void*>(aLine
.get()));
2640 // Mark previous line dirty if it's an inline line so that it can
2641 // maybe pullup something from the line just affected.
2642 // XXX We don't need to do this if aPrevLine ends in a break-after...
2643 if (aLine
!= aLineList
->front() && aLine
->IsInline() &&
2644 aLine
.prev()->IsInline()) {
2645 aLine
.prev()->MarkDirty();
2646 aLine
.prev()->SetInvalidateTextRuns(true);
2649 IndentBy(stdout
, gNoiseIndent
);
2651 printf(": mark prev-line %p dirty\n",
2652 static_cast<void*>(aLine
.prev().get()));
2659 * Test whether lines are certain to be aligned left so that we can make
2660 * resizing optimizations
2662 static inline bool IsAlignedLeft(StyleTextAlign aAlignment
,
2663 StyleDirection aDirection
,
2664 StyleUnicodeBidi aUnicodeBidi
,
2666 return aFrame
->IsInSVGTextSubtree() || StyleTextAlign::Left
== aAlignment
||
2667 (((StyleTextAlign::Start
== aAlignment
&&
2668 StyleDirection::Ltr
== aDirection
) ||
2669 (StyleTextAlign::End
== aAlignment
&&
2670 StyleDirection::Rtl
== aDirection
)) &&
2671 aUnicodeBidi
!= StyleUnicodeBidi::Plaintext
);
2674 void nsBlockFrame::PrepareResizeReflow(BlockReflowState
& aState
) {
2675 // See if we can try and avoid marking all the lines as dirty
2676 // FIXME(emilio): This should be writing-mode aware, I guess.
2677 bool tryAndSkipLines
=
2678 // The left content-edge must be a constant distance from the left
2680 !StylePadding()->mPadding
.Get(eSideLeft
).HasPercent();
2683 if (gDisableResizeOpt
) {
2684 tryAndSkipLines
= false;
2687 if (!tryAndSkipLines
) {
2688 IndentBy(stdout
, gNoiseIndent
);
2690 printf(": marking all lines dirty: availISize=%d\n",
2691 aState
.mReflowInput
.AvailableISize());
2696 if (tryAndSkipLines
) {
2697 WritingMode wm
= aState
.mReflowInput
.GetWritingMode();
2698 nscoord newAvailISize
=
2699 aState
.mReflowInput
.ComputedLogicalBorderPadding(wm
).IStart(wm
) +
2700 aState
.mReflowInput
.ComputedISize();
2704 IndentBy(stdout
, gNoiseIndent
);
2706 printf(": trying to avoid marking all lines dirty\n");
2710 for (LineIterator line
= LinesBegin(), line_end
= LinesEnd();
2711 line
!= line_end
; ++line
) {
2712 // We let child blocks make their own decisions the same
2714 bool isLastLine
= line
== mLines
.back() && !GetNextInFlow();
2715 if (line
->IsBlock() || line
->HasFloats() ||
2716 (!isLastLine
&& !line
->HasForcedLineBreakAfter()) ||
2717 ((isLastLine
|| !line
->IsLineWrapped())) ||
2718 line
->ResizeReflowOptimizationDisabled() ||
2719 line
->IsImpactedByFloat() || (line
->IEnd() > newAvailISize
)) {
2723 #ifdef REALLY_NOISY_REFLOW
2724 if (!line
->IsBlock()) {
2725 printf("PrepareResizeReflow thinks line %p is %simpacted by floats\n",
2726 line
.get(), line
->IsImpactedByFloat() ? "" : "not ");
2730 if (gNoisyReflow
&& !line
->IsDirty()) {
2731 IndentBy(stdout
, gNoiseIndent
+ 1);
2733 "skipped: line=%p next=%p %s %s%s%s clearTypeBefore/After=%s/%s "
2735 static_cast<void*>(line
.get()),
2737 (line
.next() != LinesEnd() ? line
.next().get() : nullptr)),
2738 line
->IsBlock() ? "block" : "inline",
2739 line
->HasForcedLineBreakAfter() ? "has-break-after " : "",
2740 line
->HasFloats() ? "has-floats " : "",
2741 line
->IsImpactedByFloat() ? "impacted " : "",
2742 line
->StyleClearToString(line
->FloatClearTypeBefore()),
2743 line
->StyleClearToString(line
->FloatClearTypeAfter()),
2749 // Mark everything dirty
2750 for (auto& line
: Lines()) {
2756 //----------------------------------------
2759 * Propagate reflow "damage" from from earlier lines to the current
2760 * line. The reflow damage comes from the following sources:
2761 * 1. The regions of float damage remembered during reflow.
2762 * 2. The combination of nonzero |aDeltaBCoord| and any impact by a
2763 * float, either the previous reflow or now.
2765 * When entering this function, |aLine| is still at its old position and
2766 * |aDeltaBCoord| indicates how much it will later be slid (assuming it
2767 * doesn't get marked dirty and reflowed entirely).
2769 void nsBlockFrame::PropagateFloatDamage(BlockReflowState
& aState
,
2771 nscoord aDeltaBCoord
) {
2772 nsFloatManager
* floatManager
= aState
.FloatManager();
2774 (aState
.mReflowInput
.mParentReflowInput
&&
2775 aState
.mReflowInput
.mParentReflowInput
->mFloatManager
== floatManager
) ||
2776 aState
.mReflowInput
.mBlockDelta
== 0,
2777 "Bad block delta passed in");
2779 // Check to see if there are any floats; if there aren't, there can't
2780 // be any float damage
2781 if (!floatManager
->HasAnyFloats()) {
2785 // Check the damage region recorded in the float damage.
2786 if (floatManager
->HasFloatDamage()) {
2787 // Need to check mBounds *and* mCombinedArea to find intersections
2788 // with aLine's floats
2789 nscoord lineBCoordBefore
= aLine
->BStart() + aDeltaBCoord
;
2790 nscoord lineBCoordAfter
= lineBCoordBefore
+ aLine
->BSize();
2791 // Scrollable overflow should be sufficient for things that affect
2793 WritingMode wm
= aState
.mReflowInput
.GetWritingMode();
2794 nsSize containerSize
= aState
.ContainerSize();
2795 LogicalRect overflow
=
2796 aLine
->GetOverflowArea(OverflowType::Scrollable
, wm
, containerSize
);
2797 nscoord lineBCoordCombinedBefore
= overflow
.BStart(wm
) + aDeltaBCoord
;
2798 nscoord lineBCoordCombinedAfter
=
2799 lineBCoordCombinedBefore
+ overflow
.BSize(wm
);
2802 floatManager
->IntersectsDamage(lineBCoordBefore
, lineBCoordAfter
) ||
2803 floatManager
->IntersectsDamage(lineBCoordCombinedBefore
,
2804 lineBCoordCombinedAfter
);
2811 // Check if the line is moving relative to the float manager
2812 if (aDeltaBCoord
+ aState
.mReflowInput
.mBlockDelta
!= 0) {
2813 if (aLine
->IsBlock()) {
2814 // Unconditionally reflow sliding blocks; we only really need to reflow
2815 // if there's a float impacting this block, but the current float manager
2816 // makes it difficult to check that. Therefore, we let the child block
2817 // decide what it needs to reflow.
2820 bool wasImpactedByFloat
= aLine
->IsImpactedByFloat();
2821 nsFlowAreaRect floatAvailableSpace
=
2822 aState
.GetFloatAvailableSpaceForBSize(aLine
->BStart() + aDeltaBCoord
,
2823 aLine
->BSize(), nullptr);
2825 #ifdef REALLY_NOISY_REFLOW
2826 printf("nsBlockFrame::PropagateFloatDamage %p was = %d, is=%d\n", this,
2827 wasImpactedByFloat
, floatAvailableSpace
.HasFloats());
2830 // Mark the line dirty if it was or is affected by a float
2831 // We actually only really need to reflow if the amount of impact
2832 // changes, but that's not straightforward to check
2833 if (wasImpactedByFloat
|| floatAvailableSpace
.HasFloats()) {
2840 static bool LineHasClear(nsLineBox
* aLine
) {
2841 return aLine
->IsBlock()
2842 ? (aLine
->HasFloatClearTypeBefore() ||
2843 aLine
->mFirstChild
->HasAnyStateBits(
2844 NS_BLOCK_HAS_CLEAR_CHILDREN
) ||
2845 !nsBlockFrame::BlockCanIntersectFloats(aLine
->mFirstChild
))
2846 : aLine
->HasFloatClearTypeAfter();
2850 * Reparent a whole list of floats from aOldParent to this block. The
2851 * floats might be taken from aOldParent's overflow list. They will be
2852 * removed from the list. They end up appended to our floats list.
2854 void nsBlockFrame::ReparentFloats(nsIFrame
* aFirstFrame
,
2855 nsBlockFrame
* aOldParent
,
2856 bool aReparentSiblings
) {
2858 aOldParent
->CollectFloats(aFirstFrame
, list
, aReparentSiblings
);
2859 if (list
.NotEmpty()) {
2860 for (nsIFrame
* f
: list
) {
2861 MOZ_ASSERT(!f
->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT
),
2862 "CollectFloats should've removed that bit");
2863 ReparentFrame(f
, aOldParent
, this);
2865 EnsureFloats()->AppendFrames(nullptr, std::move(list
));
2869 static void DumpLine(const BlockReflowState
& aState
, nsLineBox
* aLine
,
2870 nscoord aDeltaBCoord
, int32_t aDeltaIndent
) {
2872 if (nsBlockFrame::gNoisyReflow
) {
2873 nsRect
ovis(aLine
->InkOverflowRect());
2874 nsRect
oscr(aLine
->ScrollableOverflowRect());
2875 nsBlockFrame::IndentBy(stdout
, nsBlockFrame::gNoiseIndent
+ aDeltaIndent
);
2877 "line=%p mBCoord=%d dirty=%s oldBounds={%d,%d,%d,%d} "
2878 "oldoverflow-vis={%d,%d,%d,%d} oldoverflow-scr={%d,%d,%d,%d} "
2879 "deltaBCoord=%d mPrevBEndMargin=%d childCount=%d\n",
2880 static_cast<void*>(aLine
), aState
.mBCoord
,
2881 aLine
->IsDirty() ? "yes" : "no", aLine
->IStart(), aLine
->BStart(),
2882 aLine
->ISize(), aLine
->BSize(), ovis
.x
, ovis
.y
, ovis
.width
, ovis
.height
,
2883 oscr
.x
, oscr
.y
, oscr
.width
, oscr
.height
, aDeltaBCoord
,
2884 aState
.mPrevBEndMargin
.Get(), aLine
->GetChildCount());
2889 static bool LinesAreEmpty(const nsLineList
& aList
) {
2890 for (const auto& line
: aList
) {
2891 if (!line
.IsEmpty()) {
2898 bool nsBlockFrame::ReflowDirtyLines(BlockReflowState
& aState
) {
2899 bool keepGoing
= true;
2900 bool repositionViews
= false; // should we really need this?
2901 bool foundAnyClears
= aState
.mTrailingClearFromPIF
!= StyleClear::None
;
2902 bool willReflowAgain
= false;
2903 bool usedOverflowWrap
= false;
2907 IndentBy(stdout
, gNoiseIndent
);
2909 printf(": reflowing dirty lines");
2910 printf(" computedISize=%d\n", aState
.mReflowInput
.ComputedISize());
2912 AutoNoisyIndenter
indent(gNoisyReflow
);
2915 bool selfDirty
= HasAnyStateBits(NS_FRAME_IS_DIRTY
) ||
2916 (aState
.mReflowInput
.IsBResize() &&
2917 HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE
));
2919 // Reflow our last line if our availableBSize has increased
2920 // so that we (and our last child) pull up content as necessary
2921 if (aState
.mReflowInput
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
&&
2923 aState
.mReflowInput
.AvailableBSize() >
2924 GetLogicalSize().BSize(aState
.mReflowInput
.GetWritingMode())) {
2925 LineIterator lastLine
= LinesEnd();
2926 if (lastLine
!= LinesBegin()) {
2928 lastLine
->MarkDirty();
2931 // the amount by which we will slide the current line if it is not
2933 nscoord deltaBCoord
= 0;
2935 // whether we did NOT reflow the previous line and thus we need to
2936 // recompute the carried out margin before the line if we want to
2937 // reflow it or if its previous margin is dirty
2938 bool needToRecoverState
= false;
2939 // Float continuations were reflowed in ReflowPushedFloats
2940 bool reflowedFloat
=
2942 GetFloats()->FirstChild()->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT
);
2943 bool lastLineMovedUp
= false;
2944 // We save up information about BR-clearance here
2945 StyleClear inlineFloatClearType
= aState
.mTrailingClearFromPIF
;
2947 LineIterator line
= LinesBegin(), line_end
= LinesEnd();
2949 // Determine if children of this frame could have breaks between them for
2952 // We need to check for paginated layout, the named-page pref, and if the
2953 // available block-size is constrained.
2955 // Note that we need to check for paginated layout as named-pages are only
2956 // used during paginated reflow. We need to additionally check for
2957 // unconstrained block-size to avoid introducing fragmentation breaks during
2958 // "measuring" reflows within an overall paginated reflow, and to avoid
2959 // fragmentation in monolithic containers like 'inline-block'.
2961 // Because we can only break for named pages using Class A breakpoints, we
2962 // also need to check that the block flow direction of the containing frame
2963 // of these items (which is this block) is parallel to that of this page.
2964 // See: https://www.w3.org/TR/css-break-3/#btw-blocks
2965 const nsPresContext
* const presCtx
= aState
.mPresContext
;
2966 const bool canBreakForPageNames
=
2967 aState
.mReflowInput
.mFlags
.mCanHaveClassABreakpoints
&&
2968 aState
.mReflowInput
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
&&
2969 presCtx
->GetPresShell()->GetRootFrame()->GetWritingMode().IsVertical() ==
2970 GetWritingMode().IsVertical();
2972 // ReflowInput.mFlags.mCanHaveClassABreakpoints should respect the named
2973 // pages pref and presCtx->IsPaginated, so we did not explicitly check these
2974 // above when setting canBreakForPageNames.
2975 if (canBreakForPageNames
) {
2976 MOZ_ASSERT(presCtx
->IsPaginated(),
2977 "canBreakForPageNames should not be set during non-paginated "
2981 // Reflow the lines that are already ours
2982 for (; line
!= line_end
; ++line
, aState
.AdvanceToNextLine()) {
2983 DumpLine(aState
, line
, deltaBCoord
, 0);
2985 AutoNoisyIndenter
indent2(gNoisyReflow
);
2992 // This really sucks, but we have to look inside any blocks that have clear
2993 // elements inside them.
2994 // XXX what can we do smarter here?
2995 if (!line
->IsDirty() && line
->IsBlock() &&
2996 line
->mFirstChild
->HasAnyStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN
) &&
2997 aState
.FloatManager()->HasAnyFloats()) {
3001 nsIFrame
* floatAvoidingBlock
= nullptr;
3002 if (line
->IsBlock() &&
3003 !nsBlockFrame::BlockCanIntersectFloats(line
->mFirstChild
)) {
3004 floatAvoidingBlock
= line
->mFirstChild
;
3007 // We have to reflow the line if it's a block whose clearance
3008 // might have changed, so detect that.
3009 if (!line
->IsDirty() &&
3010 (line
->HasFloatClearTypeBefore() || floatAvoidingBlock
)) {
3011 nscoord curBCoord
= aState
.mBCoord
;
3012 // See where we would be after applying any clearance due to
3014 if (inlineFloatClearType
!= StyleClear::None
) {
3015 std::tie(curBCoord
, std::ignore
) =
3016 aState
.ClearFloats(curBCoord
, inlineFloatClearType
);
3019 auto [newBCoord
, result
] = aState
.ClearFloats(
3020 curBCoord
, line
->FloatClearTypeBefore(), floatAvoidingBlock
);
3022 if (line
->HasClearance()) {
3023 // Reflow the line if it might not have clearance anymore.
3024 if (result
== ClearFloatsResult::BCoordNoChange
3025 // aState.mBCoord is the clearance point which should be the
3026 // block-start border-edge of the block frame. If sliding the
3027 // block by deltaBCoord isn't going to put it in the predicted
3028 // position, then we'd better reflow the line.
3029 || newBCoord
!= line
->BStart() + deltaBCoord
) {
3033 // Reflow the line if the line might have clearance now.
3034 if (result
!= ClearFloatsResult::BCoordNoChange
) {
3040 // We might have to reflow a line that is after a clearing BR.
3041 if (inlineFloatClearType
!= StyleClear::None
) {
3042 std::tie(aState
.mBCoord
, std::ignore
) =
3043 aState
.ClearFloats(aState
.mBCoord
, inlineFloatClearType
);
3044 if (aState
.mBCoord
!= line
->BStart() + deltaBCoord
) {
3045 // SlideLine is not going to put the line where the clearance
3046 // put it. Reflow the line to be sure.
3049 inlineFloatClearType
= StyleClear::None
;
3052 bool previousMarginWasDirty
= line
->IsPreviousMarginDirty();
3053 if (previousMarginWasDirty
) {
3054 // If the previous margin is dirty, reflow the current line
3056 line
->ClearPreviousMarginDirty();
3057 } else if (aState
.ContentBSize() != NS_UNCONSTRAINEDSIZE
) {
3058 const nscoord scrollableOverflowBEnd
=
3059 LogicalRect(line
->mWritingMode
, line
->ScrollableOverflowRect(),
3060 line
->mContainerSize
)
3061 .BEnd(line
->mWritingMode
);
3062 if (scrollableOverflowBEnd
+ deltaBCoord
> aState
.ContentBEnd()) {
3063 // Lines that aren't dirty but get slid past our available block-size
3064 // constraint must be reflowed.
3069 if (!line
->IsDirty()) {
3070 const bool isPaginated
=
3071 // Last column can be reflowed unconstrained during column balancing.
3072 // Hence the additional NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR bit check
3073 // as a fail-safe fallback.
3074 aState
.mReflowInput
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
||
3075 HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR
) ||
3076 // Table can also be reflowed unconstrained during printing.
3077 aState
.mPresContext
->IsPaginated();
3079 // We are in a paginated context, i.e. in columns or pages.
3080 const bool mayContainFloats
=
3081 line
->IsBlock() || line
->HasFloats() || line
->HadFloatPushed();
3082 if (mayContainFloats
) {
3083 // The following if-else conditions check whether this line -- which
3084 // might have floats in its subtree, or has floats as direct children,
3085 // or had floats pushed -- needs to be reflowed.
3086 if (deltaBCoord
!= 0 || aState
.mReflowInput
.IsBResize()) {
3087 // The distance to the block-end edge might have changed. Reflow the
3088 // line both because the breakpoints within its floats may have
3089 // changed and because we might have to push/pull the floats in
3092 } else if (HasPushedFloats()) {
3093 // We had pushed floats which haven't been drained by our
3094 // next-in-flow, which means our parent is currently reflowing us
3095 // again due to clearance without creating a next-in-flow for us.
3096 // Reflow the line to redo the floats split logic to correctly set
3097 // our reflow status.
3099 } else if (aState
.mReflowInput
.mFlags
.mMustReflowPlaceholders
) {
3100 // Reflow the line (that may containing a float's placeholder frame)
3101 // if our parent tells us to do so.
3103 } else if (aState
.mReflowInput
.mFlags
.mMovedBlockFragments
) {
3104 // Our parent's line containing us moved to a different fragment.
3105 // Reflow the line because the decision about whether the float fits
3106 // may be different in a different fragment.
3113 if (!line
->IsDirty()) {
3114 // See if there's any reflow damage that requires that we mark the
3116 PropagateFloatDamage(aState
, line
, deltaBCoord
);
3119 // If the container size has changed, reset mContainerSize. If the
3120 // line's writing mode is not ltr, or if the line is not left-aligned, also
3121 // mark the line dirty.
3122 if (aState
.ContainerSize() != line
->mContainerSize
) {
3123 line
->mContainerSize
= aState
.ContainerSize();
3125 const bool isLastLine
= line
== mLines
.back() && !GetNextInFlow();
3126 const auto align
= isLastLine
? StyleText()->TextAlignForLastLine()
3127 : StyleText()->mTextAlign
;
3128 if (line
->mWritingMode
.IsVertical() || line
->mWritingMode
.IsBidiRTL() ||
3129 !IsAlignedLeft(align
, StyleVisibility()->mDirection
,
3130 StyleTextReset()->mUnicodeBidi
, this)) {
3135 // Check for a page break caused by CSS named pages.
3137 // We should break for named pages when two frames meet at a class A
3138 // breakpoint, where the first frame has a different end page value to the
3139 // second frame's start page value. canBreakForPageNames is true iff
3140 // children of this frame can form class A breakpoints, and that we are not
3141 // in a measurement reflow or in a monolithic container such as
3144 // We specifically do not want to cause a page-break for named pages when
3145 // we are at the top of a page. This would otherwise happen when the
3146 // previous sibling is an nsPageBreakFrame, or all previous siblings on the
3147 // current page are zero-height. The latter may not be per-spec, but is
3148 // compatible with Chrome's implementation of named pages.
3149 const nsAtom
* nextPageName
= nullptr;
3150 bool shouldBreakForPageName
= false;
3151 if (canBreakForPageNames
&& (!aState
.mReflowInput
.mFlags
.mIsTopOfPage
||
3152 !aState
.IsAdjacentWithBStart())) {
3153 const nsIFrame
* const frame
= line
->mFirstChild
;
3154 if (!frame
->IsPlaceholderFrame() && !frame
->IsPageBreakFrame()) {
3155 nextPageName
= frame
->GetStartPageValue();
3156 // Walk back to the last frame that isn't a placeholder.
3157 const nsIFrame
* prevFrame
= frame
->GetPrevSibling();
3158 while (prevFrame
&& prevFrame
->IsPlaceholderFrame()) {
3159 prevFrame
= prevFrame
->GetPrevSibling();
3161 if (prevFrame
&& prevFrame
->GetEndPageValue() != nextPageName
) {
3162 shouldBreakForPageName
= true;
3168 if (needToRecoverState
&& line
->IsDirty()) {
3169 // We need to reconstruct the block-end margin only if we didn't
3170 // reflow the previous line and we do need to reflow (or repair
3171 // the block-start position of) the next line.
3172 aState
.ReconstructMarginBefore(line
);
3175 bool reflowedPrevLine
= !needToRecoverState
;
3176 if (needToRecoverState
) {
3177 needToRecoverState
= false;
3179 // Update aState.mPrevChild as if we had reflowed all of the frames in
3181 if (line
->IsDirty()) {
3183 line
->mFirstChild
->GetPrevSibling() == line
.prev()->LastChild(),
3184 "unexpected line frames");
3185 aState
.mPrevChild
= line
->mFirstChild
->GetPrevSibling();
3189 // Now repair the line and update |aState.mBCoord| by calling
3190 // |ReflowLine| or |SlideLine|.
3191 // If we're going to reflow everything again, then no need to reflow
3192 // the dirty line ... unless the line has floats, in which case we'd
3193 // better reflow it now to refresh its float cache, which may contain
3194 // dangling frame pointers! Ugh! This reflow of the line may be
3195 // incorrect because we skipped reflowing previous lines (e.g., floats
3196 // may be placed incorrectly), but that's OK because we'll mark the
3197 // line dirty below under "if (aState.mReflowInput.mDiscoveredClearance..."
3198 if (line
->IsDirty() && (line
->HasFloats() || !willReflowAgain
)) {
3199 lastLineMovedUp
= true;
3201 bool maybeReflowingForFirstTime
=
3202 line
->IStart() == 0 && line
->BStart() == 0 && line
->ISize() == 0 &&
3205 // Compute the dirty lines "before" BEnd, after factoring in
3206 // the running deltaBCoord value - the running value is implicit in
3208 nscoord oldB
= line
->BStart();
3209 nscoord oldBMost
= line
->BEnd();
3211 NS_ASSERTION(!willReflowAgain
|| !line
->IsBlock(),
3212 "Don't reflow blocks while willReflowAgain is true, reflow "
3213 "of block abs-pos children depends on this");
3215 if (shouldBreakForPageName
) {
3216 // Immediately fragment for page-name. It is possible we could break
3217 // out of the loop right here, but this should make it more similar to
3218 // what happens when reflow causes fragmentation.
3219 // Set the page name, so that PushTruncatedLine does not need to
3220 // recalculate the new page name.
3221 PresShell()->FrameConstructor()->SetNextPageContentFramePageName(
3222 nextPageName
? nextPageName
: GetAutoPageValue());
3223 PushTruncatedLine(aState
, line
, &keepGoing
,
3224 ComputeNewPageNameIfNeeded::No
);
3226 // Reflow the dirty line. If it's an incremental reflow, then force
3227 // it to invalidate the dirty area if necessary
3228 usedOverflowWrap
|= ReflowLine(aState
, line
, &keepGoing
);
3231 if (aState
.mReflowInput
.WillReflowAgainForClearance()) {
3233 willReflowAgain
= true;
3234 // Note that once we've entered this state, every line that gets here
3235 // (e.g. because it has floats) gets marked dirty and reflowed again.
3236 // in the next pass. This is important, see above.
3239 if (line
->HasFloats()) {
3240 reflowedFloat
= true;
3244 DumpLine(aState
, line
, deltaBCoord
, -1);
3245 if (0 == line
->GetChildCount()) {
3246 DeleteLine(aState
, line
, line_end
);
3251 // Test to see whether the margin that should be carried out
3252 // to the next line (NL) might have changed. In ReflowBlockFrame
3253 // we call nextLine->MarkPreviousMarginDirty if the block's
3254 // actual carried-out block-end margin changed. So here we only
3255 // need to worry about the following effects:
3256 // 1) the line was just created, and it might now be blocking
3257 // a carried-out block-end margin from previous lines that
3258 // used to reach NL from reaching NL
3259 // 2) the line used to be empty, and is now not empty,
3260 // thus blocking a carried-out block-end margin from previous lines
3261 // that used to reach NL from reaching NL
3262 // 3) the line wasn't empty, but now is, so a carried-out
3263 // block-end margin from previous lines that didn't used to reach NL
3265 // 4) the line might have changed in a way that affects NL's
3266 // ShouldApplyBStartMargin decision. The three things that matter
3267 // are the line's emptiness, its adjacency to the block-start edge of the
3268 // block, and whether it has clearance (the latter only matters if the
3269 // block was and is adjacent to the block-start and empty).
3271 // If the line is empty now, we can't reliably tell if the line was empty
3272 // before, so we just assume it was and do
3273 // nextLine->MarkPreviousMarginDirty. This means the checks in 4) are
3274 // redundant; if the line is empty now we don't need to check 4), but if
3275 // the line is not empty now and we're sure it wasn't empty before, any
3276 // adjacency and clearance changes are irrelevant to the result of
3277 // nextLine->ShouldApplyBStartMargin.
3278 if (line
.next() != LinesEnd()) {
3279 bool maybeWasEmpty
= oldB
== line
.next()->BStart();
3280 bool isEmpty
= line
->CachedIsEmpty();
3281 if (maybeReflowingForFirstTime
/*1*/ ||
3282 (isEmpty
|| maybeWasEmpty
) /*2/3/4*/) {
3283 line
.next()->MarkPreviousMarginDirty();
3284 // since it's marked dirty, nobody will care about |deltaBCoord|
3288 // If the line was just reflowed for the first time, then its
3289 // old mBounds cannot be trusted so this deltaBCoord computation is
3290 // bogus. But that's OK because we just did
3291 // MarkPreviousMarginDirty on the next line which will force it
3292 // to be reflowed, so this computation of deltaBCoord will not be
3294 deltaBCoord
= line
->BEnd() - oldBMost
;
3296 // Now do an interrupt check. We want to do this only in the case when we
3297 // actually reflow the line, so that if we get back in here we'll get
3298 // further on the reflow before interrupting.
3299 aState
.mPresContext
->CheckForInterrupt(this);
3301 aState
.mOverflowTracker
->Skip(line
->mFirstChild
, aState
.mReflowStatus
);
3302 // Nop except for blocks (we don't create overflow container
3303 // continuations for any inlines atm), so only checking mFirstChild
3306 lastLineMovedUp
= deltaBCoord
< 0;
3308 if (deltaBCoord
!= 0) {
3309 SlideLine(aState
, line
, deltaBCoord
);
3311 repositionViews
= true;
3314 NS_ASSERTION(!line
->IsDirty() || !line
->HasFloats(),
3315 "Possibly stale float cache here!");
3316 if (willReflowAgain
&& line
->IsBlock()) {
3317 // If we're going to reflow everything again, and this line is a block,
3318 // then there is no need to recover float state. The line may contain
3319 // other lines with floats, but in that case RecoverStateFrom would only
3320 // add floats to the float manager. We don't need to do that because
3321 // everything's going to get reflowed again "for real". Calling
3322 // RecoverStateFrom in this situation could be lethal because the
3323 // block's descendant lines may have float caches containing dangling
3324 // frame pointers. Ugh!
3325 // If this line is inline, then we need to recover its state now
3326 // to make sure that we don't forget to move its floats by deltaBCoord.
3328 // XXX EVIL O(N^2) EVIL
3329 aState
.RecoverStateFrom(line
, deltaBCoord
);
3332 // Keep mBCoord up to date in case we're propagating reflow damage
3333 // and also because our final height may depend on it. If the
3334 // line is inlines, then only update mBCoord if the line is not
3335 // empty, because that's what PlaceLine does. (Empty blocks may
3336 // want to update mBCoord, e.g. if they have clearance.)
3337 if (line
->IsBlock() || !line
->CachedIsEmpty()) {
3338 aState
.mBCoord
= line
->BEnd();
3341 needToRecoverState
= true;
3343 if (reflowedPrevLine
&& !line
->IsBlock() &&
3344 aState
.mPresContext
->HasPendingInterrupt()) {
3345 // Need to make sure to pull overflows from any prev-in-flows
3346 for (nsIFrame
* inlineKid
= line
->mFirstChild
; inlineKid
;
3347 inlineKid
= inlineKid
->PrincipalChildList().FirstChild()) {
3348 inlineKid
->PullOverflowsFromPrevInFlow();
3353 // Record if we need to clear floats before reflowing the next
3354 // line. Note that inlineFloatClearType will be handled and
3355 // cleared before the next line is processed, so there is no
3356 // need to combine break types here.
3357 if (line
->HasFloatClearTypeAfter()) {
3358 inlineFloatClearType
= line
->FloatClearTypeAfter();
3361 if (LineHasClear(line
.get())) {
3362 foundAnyClears
= true;
3365 DumpLine(aState
, line
, deltaBCoord
, -1);
3367 if (aState
.mPresContext
->HasPendingInterrupt()) {
3368 willReflowAgain
= true;
3369 // Another option here might be to leave |line| clean if
3370 // !HasPendingInterrupt() before the CheckForInterrupt() call, since in
3371 // that case the line really did reflow as it should have. Not sure
3372 // whether that would be safe, so doing this for now instead. Also not
3373 // sure whether we really want to mark all lines dirty after an
3374 // interrupt, but until we get better at propagating float damage we
3375 // really do need to do it this way; see comments inside MarkLineDirty.
3376 MarkLineDirtyForInterrupt(line
);
3380 // Handle BR-clearance from the last line of the block
3381 if (inlineFloatClearType
!= StyleClear::None
) {
3382 std::tie(aState
.mBCoord
, std::ignore
) =
3383 aState
.ClearFloats(aState
.mBCoord
, inlineFloatClearType
);
3386 if (needToRecoverState
) {
3387 // Is this expensive?
3388 aState
.ReconstructMarginBefore(line
);
3390 // Update aState.mPrevChild as if we had reflowed all of the frames in
3392 NS_ASSERTION(line
== line_end
|| line
->mFirstChild
->GetPrevSibling() ==
3393 line
.prev()->LastChild(),
3394 "unexpected line frames");
3395 aState
.mPrevChild
= line
== line_end
? mFrames
.LastChild()
3396 : line
->mFirstChild
->GetPrevSibling();
3399 // Should we really have to do this?
3400 if (repositionViews
) {
3401 nsContainerFrame::PlaceFrameView(this);
3404 // We can skip trying to pull up the next line if our height is constrained
3405 // (so we can report being incomplete) and there is no next in flow or we
3406 // were told not to or we know it will be futile, i.e.,
3407 // -- the next in flow is not changing
3408 // -- and we cannot have added more space for its first line to be
3410 // -- it's an incremental reflow of a descendant
3411 // -- and we didn't reflow any floats (so the available space
3413 // -- my chain of next-in-flows either has no first line, or its first
3414 // line isn't dirty.
3415 bool heightConstrained
=
3416 aState
.mReflowInput
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
;
3417 bool skipPull
= willReflowAgain
&& heightConstrained
;
3418 if (!skipPull
&& heightConstrained
&& aState
.mNextInFlow
&&
3419 (aState
.mReflowInput
.mFlags
.mNextInFlowUntouched
&& !lastLineMovedUp
&&
3420 !HasAnyStateBits(NS_FRAME_IS_DIRTY
) && !reflowedFloat
)) {
3421 // We'll place lineIter at the last line of this block, so that
3422 // nsBlockInFlowLineIterator::Next() will take us to the first
3423 // line of my next-in-flow-chain. (But first, check that I
3424 // have any lines -- if I don't, just bail out of this
3426 LineIterator lineIter
= this->LinesEnd();
3427 if (lineIter
!= this->LinesBegin()) {
3428 lineIter
--; // I have lines; step back from dummy iterator to last line.
3429 nsBlockInFlowLineIterator
bifLineIter(this, lineIter
);
3431 // Check for next-in-flow-chain's first line.
3432 // (First, see if there is such a line, and second, see if it's clean)
3433 if (!bifLineIter
.Next() || !bifLineIter
.GetLine()->IsDirty()) {
3439 if (skipPull
&& aState
.mNextInFlow
) {
3440 NS_ASSERTION(heightConstrained
, "Height should be constrained here\n");
3441 if (aState
.mNextInFlow
->IsTrueOverflowContainer()) {
3442 aState
.mReflowStatus
.SetOverflowIncomplete();
3444 aState
.mReflowStatus
.SetIncomplete();
3448 if (!skipPull
&& aState
.mNextInFlow
) {
3449 // Pull data from a next-in-flow if there's still room for more
3451 while (keepGoing
&& aState
.mNextInFlow
) {
3452 // Grab first line from our next-in-flow
3453 nsBlockFrame
* nextInFlow
= aState
.mNextInFlow
;
3454 nsLineBox
* pulledLine
;
3455 nsFrameList pulledFrames
;
3456 if (!nextInFlow
->mLines
.empty()) {
3457 RemoveFirstLine(nextInFlow
->mLines
, nextInFlow
->mFrames
, &pulledLine
,
3461 // Grab an overflow line if there are any
3462 FrameLines
* overflowLines
= nextInFlow
->GetOverflowLines();
3463 if (!overflowLines
) {
3464 aState
.mNextInFlow
=
3465 static_cast<nsBlockFrame
*>(nextInFlow
->GetNextInFlow());
3469 RemoveFirstLine(overflowLines
->mLines
, overflowLines
->mFrames
,
3470 &pulledLine
, &pulledFrames
);
3472 nextInFlow
->DestroyOverflowLines();
3476 if (pulledFrames
.IsEmpty()) {
3477 // The line is empty. Try the next one.
3479 pulledLine
->GetChildCount() == 0 && !pulledLine
->mFirstChild
,
3481 nextInFlow
->FreeLineBox(pulledLine
);
3485 if (nextInFlow
->MaybeHasLineCursor()) {
3486 if (pulledLine
== nextInFlow
->GetLineCursorForDisplay()) {
3487 nextInFlow
->ClearLineCursorForDisplay();
3489 if (pulledLine
== nextInFlow
->GetLineCursorForQuery()) {
3490 nextInFlow
->ClearLineCursorForQuery();
3493 ReparentFrames(pulledFrames
, nextInFlow
, this);
3494 pulledLine
->SetMovedFragments();
3496 NS_ASSERTION(pulledFrames
.LastChild() == pulledLine
->LastChild(),
3497 "Unexpected last frame");
3498 NS_ASSERTION(aState
.mPrevChild
|| mLines
.empty(),
3499 "should have a prevchild here");
3500 NS_ASSERTION(aState
.mPrevChild
== mFrames
.LastChild(),
3501 "Incorrect aState.mPrevChild before inserting line at end");
3503 // Shift pulledLine's frames into our mFrames list.
3504 mFrames
.AppendFrames(nullptr, std::move(pulledFrames
));
3506 // Add line to our line list, and set its last child as our new prev-child
3507 line
= mLines
.before_insert(LinesEnd(), pulledLine
);
3508 aState
.mPrevChild
= mFrames
.LastChild();
3510 // Reparent floats whose placeholders are in the line.
3511 ReparentFloats(pulledLine
->mFirstChild
, nextInFlow
, true);
3513 DumpLine(aState
, pulledLine
, deltaBCoord
, 0);
3515 AutoNoisyIndenter
indent2(gNoisyReflow
);
3518 if (aState
.mPresContext
->HasPendingInterrupt()) {
3519 MarkLineDirtyForInterrupt(line
);
3521 // Now reflow it and any lines that it makes during it's reflow
3522 // (we have to loop here because reflowing the line may cause a new
3523 // line to be created; see SplitLine's callers for examples of
3524 // when this happens).
3525 while (line
!= LinesEnd()) {
3526 usedOverflowWrap
|= ReflowLine(aState
, line
, &keepGoing
);
3528 if (aState
.mReflowInput
.WillReflowAgainForClearance()) {
3531 aState
.mReflowStatus
.SetIncomplete();
3535 DumpLine(aState
, line
, deltaBCoord
, -1);
3537 if (0 == line
->GetChildCount()) {
3538 DeleteLine(aState
, line
, line_end
);
3543 if (LineHasClear(line
.get())) {
3544 foundAnyClears
= true;
3547 if (aState
.mPresContext
->CheckForInterrupt(this)) {
3548 MarkLineDirtyForInterrupt(line
);
3552 // If this is an inline frame then its time to stop
3554 aState
.AdvanceToNextLine();
3559 if (aState
.mReflowStatus
.IsIncomplete()) {
3560 aState
.mReflowStatus
.SetNextInFlowNeedsReflow();
3561 } // XXXfr shouldn't set this flag when nextinflow has no lines
3564 // Handle an odd-ball case: a list-item with no lines
3565 if (mLines
.empty() && HasOutsideMarker()) {
3566 ReflowOutput
metrics(aState
.mReflowInput
);
3567 nsIFrame
* marker
= GetOutsideMarker();
3568 WritingMode wm
= aState
.mReflowInput
.GetWritingMode();
3569 ReflowOutsideMarker(
3570 marker
, aState
, metrics
,
3571 aState
.mReflowInput
.ComputedPhysicalBorderPadding().top
);
3572 NS_ASSERTION(!MarkerIsEmpty() || metrics
.BSize(wm
) == 0,
3573 "empty ::marker frame took up space");
3575 if (!MarkerIsEmpty()) {
3576 // There are no lines so we have to fake up some y motion so that
3577 // we end up with *some* height.
3578 // (Note: if we're layout-contained, we have to be sure to leave our
3579 // ReflowOutput's BlockStartAscent() (i.e. the baseline) untouched,
3580 // because layout-contained frames have no baseline.)
3581 if (!aState
.mReflowInput
.mStyleDisplay
->IsContainLayout() &&
3582 metrics
.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE
) {
3584 WritingMode wm
= aState
.mReflowInput
.GetWritingMode();
3585 if (nsLayoutUtils::GetFirstLineBaseline(wm
, marker
, &ascent
)) {
3586 metrics
.SetBlockStartAscent(ascent
);
3588 metrics
.SetBlockStartAscent(metrics
.BSize(wm
));
3592 RefPtr
<nsFontMetrics
> fm
=
3593 nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
3595 nscoord minAscent
= nsLayoutUtils::GetCenteredFontBaseline(
3596 fm
, aState
.mMinLineHeight
, wm
.IsLineInverted());
3597 nscoord minDescent
= aState
.mMinLineHeight
- minAscent
;
3600 std::max(minAscent
, metrics
.BlockStartAscent()) +
3601 std::max(minDescent
, metrics
.BSize(wm
) - metrics
.BlockStartAscent());
3603 nscoord offset
= minAscent
- metrics
.BlockStartAscent();
3605 marker
->SetRect(marker
->GetRect() + nsPoint(0, offset
));
3610 if (LinesAreEmpty(mLines
) && ShouldHaveLineIfEmpty()) {
3611 aState
.mBCoord
+= aState
.mMinLineHeight
;
3614 if (foundAnyClears
) {
3615 AddStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN
);
3617 RemoveStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN
);
3622 VerifyOverflowSituation();
3624 IndentBy(stdout
, gNoiseIndent
- 1);
3626 printf(": done reflowing dirty lines (status=%s)\n",
3627 ToString(aState
.mReflowStatus
).c_str());
3631 return usedOverflowWrap
;
3634 void nsBlockFrame::MarkLineDirtyForInterrupt(nsLineBox
* aLine
) {
3637 // Just checking NS_FRAME_IS_DIRTY is ok, because we've already
3638 // marked the lines that need to be marked dirty based on our
3639 // vertical resize stuff. So we'll definitely reflow all those kids;
3640 // the only question is how they should behave.
3641 if (HasAnyStateBits(NS_FRAME_IS_DIRTY
)) {
3642 // Mark all our child frames dirty so we make sure to reflow them
3644 int32_t n
= aLine
->GetChildCount();
3645 for (nsIFrame
* f
= aLine
->mFirstChild
; n
> 0;
3646 f
= f
->GetNextSibling(), --n
) {
3647 f
->MarkSubtreeDirty();
3649 // And mark all the floats whose reflows we might be skipping dirty too.
3650 if (aLine
->HasFloats()) {
3651 for (nsIFrame
* f
: aLine
->Floats()) {
3652 f
->MarkSubtreeDirty();
3656 // Dirty all the descendant lines of block kids to handle float damage,
3657 // since our nsFloatManager will go away by the next time we're reflowing.
3658 // XXXbz Can we do something more like what PropagateFloatDamage does?
3659 // Would need to sort out the exact business with mBlockDelta for that....
3660 // This marks way too much dirty. If we ever make this better, revisit
3661 // which lines we mark dirty in the interrupt case in ReflowDirtyLines.
3662 nsBlockFrame
* bf
= do_QueryFrame(aLine
->mFirstChild
);
3664 MarkAllDescendantLinesDirty(bf
);
3669 void nsBlockFrame::DeleteLine(BlockReflowState
& aState
,
3670 nsLineList::iterator aLine
,
3671 nsLineList::iterator aLineEnd
) {
3672 MOZ_ASSERT(0 == aLine
->GetChildCount(), "can't delete !empty line");
3673 if (0 == aLine
->GetChildCount()) {
3674 NS_ASSERTION(aState
.mCurrentLine
== aLine
,
3675 "using function more generally than designed, "
3676 "but perhaps OK now");
3677 nsLineBox
* line
= aLine
;
3678 aLine
= mLines
.erase(aLine
);
3681 // Mark the previous margin of the next line dirty since we need to
3682 // recompute its top position.
3683 if (aLine
!= aLineEnd
) {
3684 aLine
->MarkPreviousMarginDirty();
3690 * Reflow a line. The line will either contain a single block frame
3691 * or contain 1 or more inline frames. aKeepReflowGoing indicates
3692 * whether or not the caller should continue to reflow more lines.
3693 * Returns true if the reflow used an overflow-wrap breakpoint.
3695 bool nsBlockFrame::ReflowLine(BlockReflowState
& aState
, LineIterator aLine
,
3696 bool* aKeepReflowGoing
) {
3697 MOZ_ASSERT(aLine
->GetChildCount(), "reflowing empty line");
3699 // Setup the line-layout for the new line
3700 aState
.mCurrentLine
= aLine
;
3701 aLine
->ClearDirty();
3702 aLine
->InvalidateCachedIsEmpty();
3703 aLine
->ClearHadFloatPushed();
3705 // If this line contains a single block that is hidden by `content-visibility`
3706 // don't reflow the line. If this line contains inlines and the first one is
3707 // hidden by `content-visibility`, all of them are, so avoid reflow in that
3709 // For frames that own anonymous children, even the first child is hidden by
3710 // `content-visibility`, there could be some anonymous children need reflow,
3711 // so we don't skip reflow this line.
3712 nsIFrame
* firstChild
= aLine
->mFirstChild
;
3713 if (firstChild
->IsHiddenByContentVisibilityOfInFlowParentForLayout() &&
3714 !HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES
)) {
3718 // Now that we know what kind of line we have, reflow it
3719 bool usedOverflowWrap
= false;
3720 if (aLine
->IsBlock()) {
3721 ReflowBlockFrame(aState
, aLine
, aKeepReflowGoing
);
3723 aLine
->SetLineWrapped(false);
3724 usedOverflowWrap
= ReflowInlineFrames(aState
, aLine
, aKeepReflowGoing
);
3726 // Store the line's float edges for overflow marker analysis if needed.
3727 aLine
->ClearFloatEdges();
3728 if (aState
.mFlags
.mCanHaveOverflowMarkers
) {
3729 WritingMode wm
= aLine
->mWritingMode
;
3730 nsFlowAreaRect r
= aState
.GetFloatAvailableSpaceForBSize(
3731 aLine
->BStart(), aLine
->BSize(), nullptr);
3732 if (r
.HasFloats()) {
3733 LogicalRect so
= aLine
->GetOverflowArea(OverflowType::Scrollable
, wm
,
3734 aLine
->mContainerSize
);
3735 nscoord s
= r
.mRect
.IStart(wm
);
3736 nscoord e
= r
.mRect
.IEnd(wm
);
3737 if (so
.IEnd(wm
) > e
|| so
.IStart(wm
) < s
) {
3738 // This line is overlapping a float - store the edges marking the area
3739 // between the floats for text-overflow analysis.
3740 aLine
->SetFloatEdges(s
, e
);
3746 aLine
->ClearMovedFragments();
3748 return usedOverflowWrap
;
3751 nsIFrame
* nsBlockFrame::PullFrame(BlockReflowState
& aState
,
3752 LineIterator aLine
) {
3753 // First check our remaining lines.
3754 if (LinesEnd() != aLine
.next()) {
3755 return PullFrameFrom(aLine
, this, aLine
.next());
3759 !GetOverflowLines(),
3760 "Our overflow lines should have been removed at the start of reflow");
3762 // Try each next-in-flow.
3763 nsBlockFrame
* nextInFlow
= aState
.mNextInFlow
;
3764 while (nextInFlow
) {
3765 if (nextInFlow
->mLines
.empty()) {
3766 nextInFlow
->DrainSelfOverflowList();
3768 if (!nextInFlow
->mLines
.empty()) {
3769 return PullFrameFrom(aLine
, nextInFlow
, nextInFlow
->mLines
.begin());
3771 nextInFlow
= static_cast<nsBlockFrame
*>(nextInFlow
->GetNextInFlow());
3772 aState
.mNextInFlow
= nextInFlow
;
3778 nsIFrame
* nsBlockFrame::PullFrameFrom(nsLineBox
* aLine
,
3779 nsBlockFrame
* aFromContainer
,
3780 nsLineList::iterator aFromLine
) {
3781 nsLineBox
* fromLine
= aFromLine
;
3782 MOZ_ASSERT(fromLine
, "bad line to pull from");
3783 MOZ_ASSERT(fromLine
->GetChildCount(), "empty line");
3784 MOZ_ASSERT(aLine
->GetChildCount(), "empty line");
3785 MOZ_ASSERT(!HasProperty(LineIteratorProperty()),
3786 "Shouldn't have line iterators mid-reflow");
3788 NS_ASSERTION(fromLine
->IsBlock() == fromLine
->mFirstChild
->IsBlockOutside(),
3789 "Disagreement about whether it's a block or not");
3791 if (fromLine
->IsBlock()) {
3792 // If our line is not empty and the child in aFromLine is a block
3793 // then we cannot pull up the frame into this line. In this case
3797 // Take frame from fromLine
3798 nsIFrame
* frame
= fromLine
->mFirstChild
;
3799 nsIFrame
* newFirstChild
= frame
->GetNextSibling();
3801 if (aFromContainer
!= this) {
3802 // The frame is being pulled from a next-in-flow; therefore we need to add
3803 // it to our sibling list.
3804 MOZ_ASSERT(aLine
== mLines
.back());
3805 MOZ_ASSERT(aFromLine
== aFromContainer
->mLines
.begin(),
3806 "should only pull from first line");
3807 aFromContainer
->mFrames
.RemoveFrame(frame
);
3809 // When pushing and pulling frames we need to check for whether any
3810 // views need to be reparented.
3811 ReparentFrame(frame
, aFromContainer
, this);
3812 mFrames
.AppendFrame(nullptr, frame
);
3814 // The frame might have (or contain) floats that need to be brought
3815 // over too. (pass 'false' since there are no siblings to check)
3816 ReparentFloats(frame
, aFromContainer
, false);
3818 MOZ_ASSERT(aLine
== aFromLine
.prev());
3821 aLine
->NoteFrameAdded(frame
);
3822 fromLine
->NoteFrameRemoved(frame
);
3824 if (fromLine
->GetChildCount() > 0) {
3825 // Mark line dirty now that we pulled a child
3826 fromLine
->MarkDirty();
3827 fromLine
->mFirstChild
= newFirstChild
;
3829 // Free up the fromLine now that it's empty.
3830 // Its bounds might need to be redrawn, though.
3831 if (aFromLine
.next() != aFromContainer
->mLines
.end()) {
3832 aFromLine
.next()->MarkPreviousMarginDirty();
3834 aFromContainer
->mLines
.erase(aFromLine
);
3835 // aFromLine is now invalid
3836 aFromContainer
->FreeLineBox(fromLine
);
3841 VerifyOverflowSituation();
3847 void nsBlockFrame::SlideLine(BlockReflowState
& aState
, nsLineBox
* aLine
,
3848 nscoord aDeltaBCoord
) {
3849 MOZ_ASSERT(aDeltaBCoord
!= 0, "why slide a line nowhere?");
3851 // Adjust line state
3852 aLine
->SlideBy(aDeltaBCoord
, aState
.ContainerSize());
3854 // Adjust the frames in the line
3855 MoveChildFramesOfLine(aLine
, aDeltaBCoord
);
3858 void nsBlockFrame::UpdateLineContainerSize(nsLineBox
* aLine
,
3859 const nsSize
& aNewContainerSize
) {
3860 if (aNewContainerSize
== aLine
->mContainerSize
) {
3864 // Adjust line state
3865 nsSize sizeDelta
= aLine
->UpdateContainerSize(aNewContainerSize
);
3867 // Changing container width only matters if writing mode is vertical-rl
3868 if (GetWritingMode().IsVerticalRL()) {
3869 MoveChildFramesOfLine(aLine
, sizeDelta
.width
);
3873 void nsBlockFrame::MoveChildFramesOfLine(nsLineBox
* aLine
,
3874 nscoord aDeltaBCoord
) {
3875 // Adjust the frames in the line
3876 nsIFrame
* kid
= aLine
->mFirstChild
;
3881 WritingMode wm
= GetWritingMode();
3882 LogicalPoint
translation(wm
, 0, aDeltaBCoord
);
3884 if (aLine
->IsBlock()) {
3886 kid
->MovePositionBy(wm
, translation
);
3889 // Make sure the frame's view and any child views are updated
3890 nsContainerFrame::PlaceFrameView(kid
);
3892 // Adjust the block-dir coordinate of the frames in the line.
3893 // Note: we need to re-position views even if aDeltaBCoord is 0, because
3894 // one of our parent frames may have moved and so the view's position
3895 // relative to its parent may have changed.
3896 int32_t n
= aLine
->GetChildCount();
3899 kid
->MovePositionBy(wm
, translation
);
3901 // Make sure the frame's view and any child views are updated
3902 nsContainerFrame::PlaceFrameView(kid
);
3903 kid
= kid
->GetNextSibling();
3908 static inline bool IsNonAutoNonZeroBSize(const StyleSize
& aCoord
) {
3909 // The "extremum length" values (see ExtremumLength) were originally aimed at
3910 // inline-size (or width, as it was before logicalization). For now, let them
3911 // return false here, so we treat them like 'auto' pending a real
3912 // implementation. (See bug 1126420.)
3914 // FIXME (bug 567039, bug 527285) This isn't correct for the 'fill' value,
3915 // which should more likely (but not necessarily, depending on the available
3916 // space) be returning true.
3917 if (aCoord
.BehavesLikeInitialValueOnBlockAxis()) {
3920 MOZ_ASSERT(aCoord
.IsLengthPercentage());
3921 // If we evaluate the length/percent/calc at a percentage basis of
3922 // both nscoord_MAX and 0, and it's zero both ways, then it's a zero
3923 // length, percent, or combination thereof. Test > 0 so we clamp
3924 // negative calc() results to 0.
3925 return aCoord
.AsLengthPercentage().Resolve(nscoord_MAX
) > 0 ||
3926 aCoord
.AsLengthPercentage().Resolve(0) > 0;
3930 bool nsBlockFrame::IsSelfEmpty() {
3931 if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
3935 // Blocks which are margin-roots (including inline-blocks) cannot be treated
3936 // as empty for margin-collapsing and other purposes. They're more like
3937 // replaced elements.
3938 if (HasAnyStateBits(NS_BLOCK_BFC
)) {
3942 WritingMode wm
= GetWritingMode();
3943 const nsStylePosition
* position
= StylePosition();
3945 if (IsNonAutoNonZeroBSize(position
->MinBSize(wm
)) ||
3946 IsNonAutoNonZeroBSize(position
->BSize(wm
))) {
3950 // FIXME: Bug 1646100 - Take intrinsic size into account.
3951 // FIXME: Handle the case that both inline and block sizes are auto.
3952 // https://github.com/w3c/csswg-drafts/issues/5060.
3953 // Note: block-size could be zero or auto/intrinsic keywords here.
3954 if (position
->BSize(wm
).BehavesLikeInitialValueOnBlockAxis() &&
3955 position
->mAspectRatio
.HasFiniteRatio()) {
3959 const nsStyleBorder
* border
= StyleBorder();
3960 const nsStylePadding
* padding
= StylePadding();
3962 if (border
->GetComputedBorderWidth(wm
.PhysicalSide(LogicalSide::BStart
)) !=
3964 border
->GetComputedBorderWidth(wm
.PhysicalSide(LogicalSide::BEnd
)) != 0 ||
3965 !nsLayoutUtils::IsPaddingZero(padding
->mPadding
.GetBStart(wm
)) ||
3966 !nsLayoutUtils::IsPaddingZero(padding
->mPadding
.GetBEnd(wm
))) {
3970 if (HasOutsideMarker() && !MarkerIsEmpty()) {
3977 bool nsBlockFrame::CachedIsEmpty() {
3978 if (!IsSelfEmpty()) {
3981 for (auto& line
: mLines
) {
3982 if (!line
.CachedIsEmpty()) {
3989 bool nsBlockFrame::IsEmpty() {
3990 if (!IsSelfEmpty()) {
3994 return LinesAreEmpty(mLines
);
3997 bool nsBlockFrame::ShouldApplyBStartMargin(BlockReflowState
& aState
,
3999 if (aLine
->mFirstChild
->IsPageBreakFrame()) {
4000 // A page break frame consumes margins adjacent to it.
4001 // https://drafts.csswg.org/css-break/#break-margins
4005 if (aState
.mFlags
.mShouldApplyBStartMargin
) {
4006 // Apply short-circuit check to avoid searching the line list
4010 if (!aState
.IsAdjacentWithBStart()) {
4011 // If we aren't at the start block-coordinate then something of non-zero
4012 // height must have been placed. Therefore the childs block-start margin
4014 aState
.mFlags
.mShouldApplyBStartMargin
= true;
4018 // Determine if this line is "essentially" the first line
4019 LineIterator line
= LinesBegin();
4020 if (aState
.mFlags
.mHasLineAdjacentToTop
) {
4021 line
= aState
.mLineAdjacentToTop
;
4023 while (line
!= aLine
) {
4024 if (!line
->CachedIsEmpty() || line
->HasClearance()) {
4025 // A line which precedes aLine is non-empty, or has clearance,
4026 // so therefore the block-start margin applies.
4027 aState
.mFlags
.mShouldApplyBStartMargin
= true;
4030 // No need to apply the block-start margin if the line has floats. We
4031 // should collapse anyway (bug 44419)
4033 aState
.mFlags
.mHasLineAdjacentToTop
= true;
4034 aState
.mLineAdjacentToTop
= line
;
4037 // The line being reflowed is "essentially" the first line in the
4038 // block. Therefore its block-start margin will be collapsed by the
4039 // generational collapsing logic with its parent (us).
4043 void nsBlockFrame::ReflowBlockFrame(BlockReflowState
& aState
,
4045 bool* aKeepReflowGoing
) {
4046 MOZ_ASSERT(*aKeepReflowGoing
, "bad caller");
4048 nsIFrame
* frame
= aLine
->mFirstChild
;
4050 NS_ASSERTION(false, "program error - unexpected empty line");
4054 // If the previous frame was a page-break-frame, then preemptively push this
4055 // frame to the next page.
4056 // This is primarily important for the placeholders for abspos frames, which
4057 // measure as zero height and then would be placed on this page.
4058 if (aState
.ContentBSize() != NS_UNCONSTRAINEDSIZE
) {
4059 const nsIFrame
* const prev
= frame
->GetPrevSibling();
4060 if (prev
&& prev
->IsPageBreakFrame()) {
4061 PushTruncatedLine(aState
, aLine
, aKeepReflowGoing
);
4066 // Prepare the block reflow engine
4067 nsBlockReflowContext
brc(aState
.mPresContext
, aState
.mReflowInput
);
4069 StyleClear clearType
= frame
->StyleDisplay()->mClear
;
4070 if (aState
.mTrailingClearFromPIF
!= StyleClear::None
) {
4071 clearType
= nsLayoutUtils::CombineClearType(clearType
,
4072 aState
.mTrailingClearFromPIF
);
4073 aState
.mTrailingClearFromPIF
= StyleClear::None
;
4076 // Clear past floats before the block if the clear style is not none
4077 aLine
->ClearForcedLineBreak();
4078 if (clearType
!= StyleClear::None
) {
4079 aLine
->SetFloatClearTypeBefore(clearType
);
4082 // See if we should apply the block-start margin. If the block frame being
4083 // reflowed is a continuation, then we don't apply its block-start margin
4084 // because it's not significant. Otherwise, dig deeper.
4085 bool applyBStartMargin
=
4086 !frame
->GetPrevContinuation() && ShouldApplyBStartMargin(aState
, aLine
);
4087 if (applyBStartMargin
) {
4088 // The HasClearance setting is only valid if ShouldApplyBStartMargin
4089 // returned false (in which case the block-start margin-root set our
4090 // clearance flag). Otherwise clear it now. We'll set it later on
4091 // ourselves if necessary.
4092 aLine
->ClearHasClearance();
4094 bool treatWithClearance
= aLine
->HasClearance();
4096 bool mightClearFloats
= clearType
!= StyleClear::None
;
4097 nsIFrame
* floatAvoidingBlock
= nullptr;
4098 if (!nsBlockFrame::BlockCanIntersectFloats(frame
)) {
4099 mightClearFloats
= true;
4100 floatAvoidingBlock
= frame
;
4103 // If our block-start margin was counted as part of some parent's block-start
4104 // margin collapse, and we are being speculatively reflowed assuming this
4105 // frame DID NOT need clearance, then we need to check that
4107 if (!treatWithClearance
&& !applyBStartMargin
&& mightClearFloats
&&
4108 aState
.mReflowInput
.mDiscoveredClearance
) {
4109 nscoord curBCoord
= aState
.mBCoord
+ aState
.mPrevBEndMargin
.Get();
4110 if (auto [clearBCoord
, result
] =
4111 aState
.ClearFloats(curBCoord
, clearType
, floatAvoidingBlock
);
4112 result
!= ClearFloatsResult::BCoordNoChange
) {
4113 Unused
<< clearBCoord
;
4115 // Only record the first frame that requires clearance
4116 if (!*aState
.mReflowInput
.mDiscoveredClearance
) {
4117 *aState
.mReflowInput
.mDiscoveredClearance
= frame
;
4119 aState
.mPrevChild
= frame
;
4120 // Exactly what we do now is flexible since we'll definitely be
4125 if (treatWithClearance
) {
4126 applyBStartMargin
= true;
4129 nsIFrame
* clearanceFrame
= nullptr;
4130 const nscoord startingBCoord
= aState
.mBCoord
;
4131 const CollapsingMargin incomingMargin
= aState
.mPrevBEndMargin
;
4133 // Save the original position of the frame so that we can reposition
4134 // its view as needed.
4135 nsPoint originalPosition
= frame
->GetPosition();
4138 nscoord bStartMargin
= 0;
4139 bool mayNeedRetry
= false;
4140 bool clearedFloats
= false;
4141 bool clearedPushedOrSplitFloat
= false;
4142 if (applyBStartMargin
) {
4143 // Precompute the blocks block-start margin value so that we can get the
4144 // correct available space (there might be a float that's
4145 // already been placed below the aState.mPrevBEndMargin
4147 // Setup a reflowInput to get the style computed block-start margin
4148 // value. We'll use a reason of `resize' so that we don't fudge
4149 // any incremental reflow input.
4151 // The availSpace here is irrelevant to our needs - all we want
4152 // out if this setup is the block-start margin value which doesn't depend
4153 // on the childs available space.
4154 // XXX building a complete ReflowInput just to get the block-start
4155 // margin seems like a waste. And we do this for almost every block!
4156 WritingMode wm
= frame
->GetWritingMode();
4157 LogicalSize availSpace
= aState
.ContentSize(wm
);
4158 ReflowInput
reflowInput(aState
.mPresContext
, aState
.mReflowInput
, frame
,
4161 if (treatWithClearance
) {
4162 aState
.mBCoord
+= aState
.mPrevBEndMargin
.Get();
4163 aState
.mPrevBEndMargin
.Zero();
4166 // Now compute the collapsed margin-block-start value into
4167 // aState.mPrevBEndMargin, assuming that all child margins
4168 // collapse down to clearanceFrame.
4169 brc
.ComputeCollapsedBStartMargin(reflowInput
, &aState
.mPrevBEndMargin
,
4170 clearanceFrame
, &mayNeedRetry
);
4172 // XXX optimization; we could check the collapsing children to see if they
4173 // are sure to require clearance, and so avoid retrying them
4175 if (clearanceFrame
) {
4176 // Don't allow retries on the second pass. The clearance decisions for
4177 // the blocks whose block-start margins collapse with ours are now
4179 mayNeedRetry
= false;
4182 if (!treatWithClearance
&& !clearanceFrame
&& mightClearFloats
) {
4183 // We don't know if we need clearance and this is the first,
4184 // optimistic pass. So determine whether *this block* needs
4185 // clearance. Note that we do not allow the decision for whether
4186 // this block has clearance to change on the second pass; that
4187 // decision is only allowed to be made under the optimistic
4189 nscoord curBCoord
= aState
.mBCoord
+ aState
.mPrevBEndMargin
.Get();
4190 if (auto [clearBCoord
, result
] =
4191 aState
.ClearFloats(curBCoord
, clearType
, floatAvoidingBlock
);
4192 result
!= ClearFloatsResult::BCoordNoChange
) {
4193 Unused
<< clearBCoord
;
4195 // Looks like we need clearance and we didn't know about it already.
4196 // So recompute collapsed margin
4197 treatWithClearance
= true;
4198 // Remember this decision, needed for incremental reflow
4199 aLine
->SetHasClearance();
4201 // Apply incoming margins
4202 aState
.mBCoord
+= aState
.mPrevBEndMargin
.Get();
4203 aState
.mPrevBEndMargin
.Zero();
4205 // Compute the collapsed margin again, ignoring the incoming margin
4207 mayNeedRetry
= false;
4208 brc
.ComputeCollapsedBStartMargin(reflowInput
, &aState
.mPrevBEndMargin
,
4209 clearanceFrame
, &mayNeedRetry
);
4213 // Temporarily advance the running block-direction value so that the
4214 // GetFloatAvailableSpace method will return the right available space.
4215 // This undone as soon as the horizontal margins are computed.
4216 bStartMargin
= aState
.mPrevBEndMargin
.Get();
4218 if (treatWithClearance
) {
4219 nscoord currentBCoord
= aState
.mBCoord
;
4220 // advance mBCoord to the clear position.
4221 auto [clearBCoord
, result
] =
4222 aState
.ClearFloats(aState
.mBCoord
, clearType
, floatAvoidingBlock
);
4223 aState
.mBCoord
= clearBCoord
;
4225 clearedFloats
= result
!= ClearFloatsResult::BCoordNoChange
;
4226 clearedPushedOrSplitFloat
=
4227 result
== ClearFloatsResult::FloatsPushedOrSplit
;
4229 // Compute clearance. It's the amount we need to add to the block-start
4230 // border-edge of the frame, after applying collapsed margins
4231 // from the frame and its children, to get it to line up with
4232 // the block-end of the floats. The former is
4233 // currentBCoord + bStartMargin, the latter is the current
4235 // Note that negative clearance is possible
4236 clearance
= aState
.mBCoord
- (currentBCoord
+ bStartMargin
);
4238 // Add clearance to our block-start margin while we compute available
4239 // space for the frame
4240 bStartMargin
+= clearance
;
4242 // Note that aState.mBCoord should stay where it is: at the block-start
4243 // border-edge of the frame
4245 // Advance aState.mBCoord to the block-start border-edge of the frame.
4246 aState
.mBCoord
+= bStartMargin
;
4250 aLine
->SetLineIsImpactedByFloat(false);
4252 // Here aState.mBCoord is the block-start border-edge of the block.
4253 // Compute the available space for the block
4254 nsFlowAreaRect floatAvailableSpace
= aState
.GetFloatAvailableSpace();
4255 WritingMode wm
= aState
.mReflowInput
.GetWritingMode();
4256 LogicalRect availSpace
= aState
.ComputeBlockAvailSpace(
4257 frame
, floatAvailableSpace
, (floatAvoidingBlock
));
4260 // (!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats)
4261 // is to some degree out of paranoia: if we reliably eat up block-start
4262 // margins at the top of the page as we ought to, it wouldn't be
4264 if ((!aState
.mReflowInput
.mFlags
.mIsTopOfPage
|| clearedFloats
) &&
4265 (availSpace
.BSize(wm
) < 0 || clearedPushedOrSplitFloat
)) {
4266 // We know already that this child block won't fit on this
4267 // page/column due to the block-start margin or the clearance. So we
4268 // need to get out of here now. (If we don't, most blocks will handle
4269 // things fine, and report break-before, but zero-height blocks
4270 // won't, and will thus make their parent overly-large and force
4271 // *it* to be pushed in its entirety.)
4272 aState
.mBCoord
= startingBCoord
;
4273 aState
.mPrevBEndMargin
= incomingMargin
;
4274 if (ShouldAvoidBreakInside(aState
.mReflowInput
)) {
4275 SetBreakBeforeStatusBeforeLine(aState
, aLine
, aKeepReflowGoing
);
4277 PushTruncatedLine(aState
, aLine
, aKeepReflowGoing
);
4282 // Now put the block-dir coordinate back to the start of the
4283 // block-start-margin + clearance.
4284 aState
.mBCoord
-= bStartMargin
;
4285 availSpace
.BStart(wm
) -= bStartMargin
;
4286 if (NS_UNCONSTRAINEDSIZE
!= availSpace
.BSize(wm
)) {
4287 availSpace
.BSize(wm
) += bStartMargin
;
4290 // Construct the reflow input for the block.
4291 Maybe
<ReflowInput
> childReflowInput
;
4292 Maybe
<LogicalSize
> cbSize
;
4293 LogicalSize availSize
= availSpace
.Size(wm
);
4294 bool columnSetWrapperHasNoBSizeLeft
= false;
4295 if (Style()->GetPseudoType() == PseudoStyleType::columnContent
) {
4296 // Calculate the multicol containing block's block size so that the
4297 // children with percentage block size get correct percentage basis.
4298 const ReflowInput
* cbReflowInput
=
4299 aState
.mReflowInput
.mParentReflowInput
->mCBReflowInput
;
4300 MOZ_ASSERT(cbReflowInput
->mFrame
->StyleColumn()->IsColumnContainerStyle(),
4301 "Get unexpected reflow input of multicol containing block!");
4303 // Use column-width as the containing block's inline-size, i.e. the column
4304 // content's computed inline-size.
4305 cbSize
.emplace(LogicalSize(wm
, aState
.mReflowInput
.ComputedISize(),
4306 cbReflowInput
->ComputedBSize())
4307 .ConvertTo(frame
->GetWritingMode(), wm
));
4309 // If a ColumnSetWrapper is in a balancing column content, it may be
4310 // pushed or pulled back and forth between column contents. Always add
4311 // NS_FRAME_HAS_DIRTY_CHILDREN bit to it so that its ColumnSet children
4312 // can have a chance to reflow under current block size constraint.
4313 if (aState
.mReflowInput
.mFlags
.mIsColumnBalancing
&&
4314 frame
->IsColumnSetWrapperFrame()) {
4315 frame
->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN
);
4317 } else if (IsColumnSetWrapperFrame()) {
4318 // If we are reflowing our ColumnSet children, we want to apply our block
4319 // size constraint to the available block size when constructing reflow
4320 // input for ColumnSet so that ColumnSet can use it to compute its max
4321 // column block size.
4322 if (frame
->IsColumnSetFrame()) {
4323 nscoord contentBSize
= aState
.mReflowInput
.ComputedBSize();
4324 if (aState
.mReflowInput
.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE
) {
4326 std::min(contentBSize
, aState
.mReflowInput
.ComputedMaxBSize());
4328 if (contentBSize
!= NS_UNCONSTRAINEDSIZE
) {
4329 // To get the remaining content block-size, subtract the content
4330 // block-size consumed by our previous continuations.
4331 contentBSize
-= aState
.mConsumedBSize
;
4333 // ColumnSet is not the outermost frame in the column container, so it
4334 // cannot have any margin. We don't need to consider any margin that
4335 // can be generated by "box-decoration-break: clone" as we do in
4336 // BlockReflowState::ComputeBlockAvailSpace().
4337 const nscoord availContentBSize
= std::max(
4338 0, contentBSize
- (aState
.mBCoord
- aState
.ContentBStart()));
4339 if (availSize
.BSize(wm
) >= availContentBSize
) {
4340 availSize
.BSize(wm
) = availContentBSize
;
4341 columnSetWrapperHasNoBSizeLeft
= true;
4347 childReflowInput
.emplace(aState
.mPresContext
, aState
.mReflowInput
, frame
,
4348 availSize
.ConvertTo(frame
->GetWritingMode(), wm
),
4351 childReflowInput
->mFlags
.mColumnSetWrapperHasNoBSizeLeft
=
4352 columnSetWrapperHasNoBSizeLeft
;
4354 if (aLine
->MovedFragments()) {
4355 // We only need to set this the first reflow, since if we reflow
4356 // again (and replace childReflowInput) we'll be reflowing it
4357 // again in the same fragment as the previous time.
4358 childReflowInput
->mFlags
.mMovedBlockFragments
= true;
4361 nsFloatManager::SavedState floatManagerState
;
4362 nsReflowStatus frameReflowStatus
;
4364 if (floatAvailableSpace
.HasFloats()) {
4365 // Set if floatAvailableSpace.HasFloats() is true for any
4366 // iteration of the loop.
4367 aLine
->SetLineIsImpactedByFloat(true);
4370 // We might need to store into mDiscoveredClearance later if it's
4371 // currently null; we want to overwrite any writes that
4372 // brc.ReflowBlock() below does, so we need to remember now
4373 // whether it's empty.
4374 const bool shouldStoreClearance
=
4375 aState
.mReflowInput
.mDiscoveredClearance
&&
4376 !*aState
.mReflowInput
.mDiscoveredClearance
;
4378 // Reflow the block into the available space
4379 if (mayNeedRetry
|| floatAvoidingBlock
) {
4380 aState
.FloatManager()->PushState(&floatManagerState
);
4384 childReflowInput
->mDiscoveredClearance
= &clearanceFrame
;
4385 } else if (!applyBStartMargin
) {
4386 childReflowInput
->mDiscoveredClearance
=
4387 aState
.mReflowInput
.mDiscoveredClearance
;
4390 frameReflowStatus
.Reset();
4391 brc
.ReflowBlock(availSpace
, applyBStartMargin
, aState
.mPrevBEndMargin
,
4392 clearance
, aLine
.get(), *childReflowInput
,
4393 frameReflowStatus
, aState
);
4395 if (frameReflowStatus
.IsInlineBreakBefore()) {
4396 // No need to retry this loop if there is a break opportunity before the
4401 // Now the block has a height. Using that height, get the
4402 // available space again and call ComputeBlockAvailSpace again.
4403 // If ComputeBlockAvailSpace gives a different result, we need to
4405 if (!floatAvoidingBlock
) {
4409 LogicalRect
oldFloatAvailableSpaceRect(floatAvailableSpace
.mRect
);
4410 floatAvailableSpace
= aState
.GetFloatAvailableSpaceForBSize(
4411 aState
.mBCoord
+ bStartMargin
, brc
.GetMetrics().BSize(wm
),
4412 &floatManagerState
);
4413 NS_ASSERTION(floatAvailableSpace
.mRect
.BStart(wm
) ==
4414 oldFloatAvailableSpaceRect
.BStart(wm
),
4416 // Restore the height to the position of the next band.
4417 floatAvailableSpace
.mRect
.BSize(wm
) =
4418 oldFloatAvailableSpaceRect
.BSize(wm
);
4419 // Determine whether the available space shrunk on either side,
4420 // because (the first time round) we now know the block's height,
4421 // and it may intersect additional floats, or (on later
4422 // iterations) because narrowing the width relative to the
4423 // previous time may cause the block to become taller. Note that
4424 // since we're reflowing the block, narrowing the width might also
4425 // make it shorter, so we must pass aCanGrow as true.
4426 if (!AvailableSpaceShrunk(wm
, oldFloatAvailableSpaceRect
,
4427 floatAvailableSpace
.mRect
, true)) {
4428 // The size and position we chose before are fine (i.e., they
4429 // don't cause intersecting with floats that requires a change
4430 // in size or position), so we're done.
4434 bool advanced
= false;
4435 if (!aState
.FloatAvoidingBlockFitsInAvailSpace(floatAvoidingBlock
,
4436 floatAvailableSpace
)) {
4437 // Advance to the next band.
4438 nscoord newBCoord
= aState
.mBCoord
;
4439 if (aState
.AdvanceToNextBand(floatAvailableSpace
.mRect
, &newBCoord
)) {
4442 // ClearFloats might be able to advance us further once we're there.
4443 std::tie(aState
.mBCoord
, std::ignore
) =
4444 aState
.ClearFloats(newBCoord
, StyleClear::None
, floatAvoidingBlock
);
4446 // Start over with a new available space rect at the new height.
4447 floatAvailableSpace
= aState
.GetFloatAvailableSpaceWithState(
4448 aState
.mBCoord
, ShapeType::ShapeOutside
, &floatManagerState
);
4451 const LogicalRect oldAvailSpace
= availSpace
;
4452 availSpace
= aState
.ComputeBlockAvailSpace(frame
, floatAvailableSpace
,
4453 (floatAvoidingBlock
));
4455 if (!advanced
&& availSpace
.IsEqualEdges(oldAvailSpace
)) {
4459 // We need another reflow.
4460 aState
.FloatManager()->PopState(&floatManagerState
);
4462 if (!treatWithClearance
&& !applyBStartMargin
&&
4463 aState
.mReflowInput
.mDiscoveredClearance
) {
4464 // We set shouldStoreClearance above to record only the first
4465 // frame that requires clearance.
4466 if (shouldStoreClearance
) {
4467 *aState
.mReflowInput
.mDiscoveredClearance
= frame
;
4469 aState
.mPrevChild
= frame
;
4470 // Exactly what we do now is flexible since we'll definitely be
4476 // We're pushing down the border-box, so we don't apply margin anymore.
4477 // This should never cause us to move up since the call to
4478 // GetFloatAvailableSpaceForBSize above included the margin.
4479 applyBStartMargin
= false;
4481 treatWithClearance
= true; // avoid hitting test above
4485 childReflowInput
.reset();
4486 childReflowInput
.emplace(
4487 aState
.mPresContext
, aState
.mReflowInput
, frame
,
4488 availSpace
.Size(wm
).ConvertTo(frame
->GetWritingMode(), wm
));
4491 if (mayNeedRetry
&& clearanceFrame
) {
4492 // Found a clearance frame, so we need to reflow |frame| a second time.
4493 // Restore the states and start over again.
4494 aState
.FloatManager()->PopState(&floatManagerState
);
4495 aState
.mBCoord
= startingBCoord
;
4496 aState
.mPrevBEndMargin
= incomingMargin
;
4500 aState
.mPrevChild
= frame
;
4502 if (childReflowInput
->WillReflowAgainForClearance()) {
4503 // If an ancestor of ours is going to reflow for clearance, we
4504 // need to avoid calling PlaceBlock, because it unsets dirty bits
4505 // on the child block (both itself, and through its call to
4506 // nsIFrame::DidReflow), and those dirty bits imply dirtiness for
4507 // all of the child block, including the lines it didn't reflow.
4508 NS_ASSERTION(originalPosition
== frame
->GetPosition(),
4509 "we need to call PositionChildViews");
4513 #if defined(REFLOW_STATUS_COVERAGE)
4514 RecordReflowStatus(true, frameReflowStatus
);
4517 if (frameReflowStatus
.IsInlineBreakBefore()) {
4518 // None of the child block fits.
4519 if (ShouldAvoidBreakInside(aState
.mReflowInput
)) {
4520 SetBreakBeforeStatusBeforeLine(aState
, aLine
, aKeepReflowGoing
);
4522 PushTruncatedLine(aState
, aLine
, aKeepReflowGoing
);
4525 // Note: line-break-after a block is a nop
4527 // Try to place the child block.
4528 // Don't force the block to fit if we have positive clearance, because
4529 // pushing it to the next page would give it more room.
4530 // Don't force the block to fit if it's impacted by a float. If it is,
4531 // then pushing it to the next page would give it more room. Note that
4532 // isImpacted doesn't include impact from the block's own floats.
4533 bool forceFit
= aState
.IsAdjacentWithBStart() && clearance
<= 0 &&
4534 !floatAvailableSpace
.HasFloats();
4535 CollapsingMargin collapsedBEndMargin
;
4536 OverflowAreas overflowAreas
;
4538 brc
.PlaceBlock(*childReflowInput
, forceFit
, aLine
.get(),
4539 collapsedBEndMargin
, overflowAreas
, frameReflowStatus
);
4540 if (!frameReflowStatus
.IsFullyComplete() &&
4541 ShouldAvoidBreakInside(aState
.mReflowInput
)) {
4542 *aKeepReflowGoing
= false;
4546 if (aLine
->SetCarriedOutBEndMargin(collapsedBEndMargin
)) {
4547 LineIterator nextLine
= aLine
;
4549 if (nextLine
!= LinesEnd()) {
4550 nextLine
->MarkPreviousMarginDirty();
4554 aLine
->SetOverflowAreas(overflowAreas
);
4555 if (*aKeepReflowGoing
) {
4556 // Some of the child block fit
4558 // Advance to new Y position
4559 nscoord newBCoord
= aLine
->BEnd();
4560 aState
.mBCoord
= newBCoord
;
4562 // Continue the block frame now if it didn't completely fit in
4563 // the available space.
4564 if (!frameReflowStatus
.IsFullyComplete()) {
4565 bool madeContinuation
= CreateContinuationFor(aState
, nullptr, frame
);
4567 nsIFrame
* nextFrame
= frame
->GetNextInFlow();
4568 NS_ASSERTION(nextFrame
,
4569 "We're supposed to have a next-in-flow by now");
4571 if (frameReflowStatus
.IsIncomplete()) {
4572 // If nextFrame used to be an overflow container, make it a normal
4574 if (!madeContinuation
&&
4575 nextFrame
->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER
)) {
4576 nsOverflowContinuationTracker::AutoFinish
fini(
4577 aState
.mOverflowTracker
, frame
);
4578 nsContainerFrame
* parent
= nextFrame
->GetParent();
4579 parent
->StealFrame(nextFrame
);
4580 if (parent
!= this) {
4581 ReparentFrame(nextFrame
, parent
, this);
4583 mFrames
.InsertFrame(nullptr, frame
, nextFrame
);
4584 madeContinuation
= true; // needs to be added to mLines
4585 nextFrame
->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER
);
4586 frameReflowStatus
.SetNextInFlowNeedsReflow();
4589 // Push continuation to a new line, but only if we actually made
4591 if (madeContinuation
) {
4592 nsLineBox
* line
= NewLineBox(nextFrame
, true);
4593 mLines
.after_insert(aLine
, line
);
4596 PushTruncatedLine(aState
, aLine
.next(), aKeepReflowGoing
);
4598 // If we need to reflow the continuation of the block child,
4599 // then we'd better reflow our continuation
4600 if (frameReflowStatus
.NextInFlowNeedsReflow()) {
4601 aState
.mReflowStatus
.SetNextInFlowNeedsReflow();
4602 // We also need to make that continuation's line dirty so
4603 // it gets reflowed when we reflow our next in flow. The
4604 // nif's line must always be either a line of the nif's
4605 // parent block (only if we didn't make a continuation) or
4606 // else one of our own overflow lines. In the latter case
4607 // the line is already marked dirty, so just handle the
4609 if (!madeContinuation
) {
4610 nsBlockFrame
* nifBlock
= do_QueryFrame(nextFrame
->GetParent());
4613 "A block's child's next in flow's parent must be a block!");
4614 for (auto& line
: nifBlock
->Lines()) {
4615 if (line
.Contains(nextFrame
)) {
4623 // The block-end margin for a block is only applied on the last
4624 // flow block. Since we just continued the child block frame,
4625 // we know that line->mFirstChild is not the last flow block
4626 // therefore zero out the running margin value.
4627 #ifdef NOISY_BLOCK_DIR_MARGINS
4629 printf(": reflow incomplete, frame=");
4630 frame
->ListTag(stdout
);
4631 printf(" prevBEndMargin=%d, setting to zero\n",
4632 aState
.mPrevBEndMargin
.get());
4634 aState
.mPrevBEndMargin
.Zero();
4635 } else { // frame is complete but its overflow is not complete
4636 // Disconnect the next-in-flow and put it in our overflow tracker
4637 if (!madeContinuation
&&
4638 !nextFrame
->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER
)) {
4639 // It already exists, but as a normal next-in-flow, so we need
4640 // to dig it out of the child lists.
4641 nextFrame
->GetParent()->StealFrame(nextFrame
);
4642 } else if (madeContinuation
) {
4643 mFrames
.RemoveFrame(nextFrame
);
4646 // Put it in our overflow list
4647 aState
.mOverflowTracker
->Insert(nextFrame
, frameReflowStatus
);
4648 aState
.mReflowStatus
.MergeCompletionStatusFrom(frameReflowStatus
);
4650 #ifdef NOISY_BLOCK_DIR_MARGINS
4652 printf(": reflow complete but overflow incomplete for ");
4653 frame
->ListTag(stdout
);
4654 printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n",
4655 aState
.mPrevBEndMargin
.get(), collapsedBEndMargin
.get());
4657 aState
.mPrevBEndMargin
= collapsedBEndMargin
;
4659 } else { // frame is fully complete
4660 #ifdef NOISY_BLOCK_DIR_MARGINS
4662 printf(": reflow complete for ");
4663 frame
->ListTag(stdout
);
4664 printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n",
4665 aState
.mPrevBEndMargin
.get(), collapsedBEndMargin
.get());
4667 aState
.mPrevBEndMargin
= collapsedBEndMargin
;
4669 #ifdef NOISY_BLOCK_DIR_MARGINS
4672 frame
->ListTag(stdout
);
4673 printf(" carriedOutBEndMargin=%d collapsedBEndMargin=%d => %d\n",
4674 brc
.GetCarriedOutBEndMargin().get(), collapsedBEndMargin
.get(),
4675 aState
.mPrevBEndMargin
.get());
4678 if (!frameReflowStatus
.IsFullyComplete()) {
4679 // The frame reported an incomplete status, but then it also didn't
4680 // fit. This means we need to reflow it again so that it can
4681 // (again) report the incomplete status.
4682 frame
->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN
);
4685 if ((aLine
== mLines
.front() && !GetPrevInFlow()) ||
4686 ShouldAvoidBreakInside(aState
.mReflowInput
)) {
4687 // If it's our very first line *or* we're not at the top of the page
4688 // and we have page-break-inside:avoid, then we need to be pushed to
4689 // our parent's next-in-flow.
4690 SetBreakBeforeStatusBeforeLine(aState
, aLine
, aKeepReflowGoing
);
4692 // Push the line that didn't fit and any lines that follow it
4693 // to our next-in-flow.
4694 PushTruncatedLine(aState
, aLine
, aKeepReflowGoing
);
4698 break; // out of the reflow retry loop
4701 // Now that we've got its final position all figured out, position any child
4702 // views it may have. Note that the case when frame has a view got handled
4703 // by FinishReflowChild, but that function didn't have the coordinates needed
4704 // to correctly decide whether to reposition child views.
4705 if (originalPosition
!= frame
->GetPosition() && !frame
->HasView()) {
4706 nsContainerFrame::PositionChildViews(frame
);
4714 // Returns true if an overflow-wrap break was used.
4715 bool nsBlockFrame::ReflowInlineFrames(BlockReflowState
& aState
,
4717 bool* aKeepReflowGoing
) {
4718 *aKeepReflowGoing
= true;
4719 bool usedOverflowWrap
= false;
4721 aLine
->SetLineIsImpactedByFloat(false);
4723 // Setup initial coordinate system for reflowing the inline frames
4724 // into. Apply a previous block frame's block-end margin first.
4725 if (ShouldApplyBStartMargin(aState
, aLine
)) {
4726 aState
.mBCoord
+= aState
.mPrevBEndMargin
.Get();
4728 nsFlowAreaRect floatAvailableSpace
= aState
.GetFloatAvailableSpace();
4730 LineReflowStatus lineReflowStatus
;
4732 nscoord availableSpaceBSize
= 0;
4733 aState
.mLineBSize
.reset();
4735 bool allowPullUp
= true;
4736 nsIFrame
* forceBreakInFrame
= nullptr;
4737 int32_t forceBreakOffset
= -1;
4738 gfxBreakPriority forceBreakPriority
= gfxBreakPriority::eNoBreak
;
4740 nsFloatManager::SavedState floatManagerState
;
4741 aState
.FloatManager()->PushState(&floatManagerState
);
4743 // Once upon a time we allocated the first 30 nsLineLayout objects
4744 // on the stack, and then we switched to the heap. At that time
4745 // these objects were large (1100 bytes on a 32 bit system).
4746 // Then the nsLineLayout object was shrunk to 156 bytes by
4747 // removing some internal buffers. Given that it is so much
4748 // smaller, the complexity of 2 different ways of allocating
4749 // no longer makes sense. Now we always allocate on the stack.
4750 nsLineLayout
lineLayout(aState
.mPresContext
, aState
.FloatManager(),
4751 aState
.mReflowInput
, &aLine
, nullptr);
4752 lineLayout
.Init(&aState
, aState
.mMinLineHeight
, aState
.mLineNumber
);
4753 if (forceBreakInFrame
) {
4754 lineLayout
.ForceBreakAtPosition(forceBreakInFrame
, forceBreakOffset
);
4756 DoReflowInlineFrames(aState
, lineLayout
, aLine
, floatAvailableSpace
,
4757 availableSpaceBSize
, &floatManagerState
,
4758 aKeepReflowGoing
, &lineReflowStatus
, allowPullUp
);
4759 usedOverflowWrap
= lineLayout
.EndLineReflow();
4761 if (LineReflowStatus::RedoNoPull
== lineReflowStatus
||
4762 LineReflowStatus::RedoMoreFloats
== lineReflowStatus
||
4763 LineReflowStatus::RedoNextBand
== lineReflowStatus
) {
4764 if (lineLayout
.NeedsBackup()) {
4765 NS_ASSERTION(!forceBreakInFrame
,
4766 "Backing up twice; this should never be necessary");
4767 // If there is no saved break position, then this will set
4768 // set forceBreakInFrame to null and we won't back up, which is
4770 forceBreakInFrame
= lineLayout
.GetLastOptionalBreakPosition(
4771 &forceBreakOffset
, &forceBreakPriority
);
4773 forceBreakInFrame
= nullptr;
4775 // restore the float manager state
4776 aState
.FloatManager()->PopState(&floatManagerState
);
4777 // Clear out float lists
4778 aState
.mCurrentLineFloats
.Clear();
4779 aState
.mBelowCurrentLineFloats
.Clear();
4780 aState
.mNoWrapFloats
.Clear();
4783 // Don't allow pullup on a subsequent LineReflowStatus::RedoNoPull pass
4784 allowPullUp
= false;
4785 } while (LineReflowStatus::RedoNoPull
== lineReflowStatus
);
4786 } while (LineReflowStatus::RedoMoreFloats
== lineReflowStatus
);
4787 } while (LineReflowStatus::RedoNextBand
== lineReflowStatus
);
4789 return usedOverflowWrap
;
4792 void nsBlockFrame::SetBreakBeforeStatusBeforeLine(BlockReflowState
& aState
,
4794 bool* aKeepReflowGoing
) {
4795 aState
.mReflowStatus
.SetInlineLineBreakBeforeAndReset();
4796 // Reflow the line again when we reflow at our new position.
4798 *aKeepReflowGoing
= false;
4801 void nsBlockFrame::PushTruncatedLine(
4802 BlockReflowState
& aState
, LineIterator aLine
, bool* aKeepReflowGoing
,
4803 ComputeNewPageNameIfNeeded aComputeNewPageName
) {
4804 PushLines(aState
, aLine
.prev());
4805 *aKeepReflowGoing
= false;
4807 if (aComputeNewPageName
== ComputeNewPageNameIfNeeded::Yes
) {
4808 // mCanHaveClassABreakpoints can only be true during paginated reflow, and
4809 // we expect this function to only be called when the available bsize is
4811 const WritingMode wm
= GetWritingMode();
4812 const bool canBreakForPageNames
=
4813 aState
.mReflowInput
.mFlags
.mCanHaveClassABreakpoints
&&
4814 !PresShell()->GetRootFrame()->GetWritingMode().IsOrthogonalTo(wm
);
4815 if (canBreakForPageNames
) {
4816 PresShell()->FrameConstructor()->MaybeSetNextPageContentFramePageName(
4817 aLine
->mFirstChild
);
4820 aState
.mReflowStatus
.SetIncomplete();
4823 void nsBlockFrame::DoReflowInlineFrames(
4824 BlockReflowState
& aState
, nsLineLayout
& aLineLayout
, LineIterator aLine
,
4825 nsFlowAreaRect
& aFloatAvailableSpace
, nscoord
& aAvailableSpaceBSize
,
4826 nsFloatManager::SavedState
* aFloatStateBeforeLine
, bool* aKeepReflowGoing
,
4827 LineReflowStatus
* aLineReflowStatus
, bool aAllowPullUp
) {
4828 // Forget all of the floats on the line
4829 aLine
->ClearFloats();
4830 aState
.mFloatOverflowAreas
.Clear();
4832 // We need to set this flag on the line if any of our reflow passes
4833 // are impacted by floats.
4834 if (aFloatAvailableSpace
.HasFloats()) {
4835 aLine
->SetLineIsImpactedByFloat(true);
4837 #ifdef REALLY_NOISY_REFLOW
4838 printf("nsBlockFrame::DoReflowInlineFrames %p impacted = %d\n", this,
4839 aFloatAvailableSpace
.HasFloats());
4842 WritingMode outerWM
= aState
.mReflowInput
.GetWritingMode();
4843 WritingMode lineWM
= WritingModeForLine(outerWM
, aLine
->mFirstChild
);
4844 LogicalRect lineRect
= aFloatAvailableSpace
.mRect
.ConvertTo(
4845 lineWM
, outerWM
, aState
.ContainerSize());
4847 nscoord iStart
= lineRect
.IStart(lineWM
);
4848 nscoord availISize
= lineRect
.ISize(lineWM
);
4850 if (aState
.mReflowInput
.AvailableBSize() == NS_UNCONSTRAINEDSIZE
) {
4851 availBSize
= NS_UNCONSTRAINEDSIZE
;
4853 /* XXX get the height right! */
4854 availBSize
= lineRect
.BSize(lineWM
);
4857 // Make sure to enable resize optimization before we call BeginLineReflow
4858 // because it might get disabled there
4859 aLine
->EnableResizeReflowOptimization();
4861 aLineLayout
.BeginLineReflow(
4862 iStart
, aState
.mBCoord
, availISize
, availBSize
,
4863 aFloatAvailableSpace
.HasFloats(), false, /*XXX isTopOfPage*/
4864 lineWM
, aState
.mContainerSize
, aState
.mInsetForBalance
);
4866 aState
.mFlags
.mIsLineLayoutEmpty
= false;
4868 // XXX Unfortunately we need to know this before reflowing the first
4869 // inline frame in the line. FIX ME.
4870 if (0 == aLineLayout
.GetLineNumber() &&
4871 HasAllStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD
|
4872 NS_BLOCK_HAS_FIRST_LETTER_STYLE
)) {
4873 aLineLayout
.SetFirstLetterStyleOK(true);
4875 NS_ASSERTION(!(HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD
) &&
4876 GetPrevContinuation()),
4877 "first letter child bit should only be on first continuation");
4879 // Reflow the frames that are already on the line first
4880 LineReflowStatus lineReflowStatus
= LineReflowStatus::OK
;
4882 nsIFrame
* frame
= aLine
->mFirstChild
;
4884 if (aFloatAvailableSpace
.HasFloats()) {
4885 // There is a soft break opportunity at the start of the line, because
4886 // we can always move this line down below float(s).
4887 if (aLineLayout
.NotifyOptionalBreakPosition(
4888 frame
, 0, true, gfxBreakPriority::eNormalBreak
)) {
4889 lineReflowStatus
= LineReflowStatus::RedoNextBand
;
4893 // need to repeatedly call GetChildCount here, because the child
4894 // count can change during the loop!
4896 LineReflowStatus::OK
== lineReflowStatus
&& i
< aLine
->GetChildCount();
4897 i
++, frame
= frame
->GetNextSibling()) {
4898 SetLineCursorForDisplay(aLine
);
4899 ReflowInlineFrame(aState
, aLineLayout
, aLine
, frame
, &lineReflowStatus
);
4900 if (LineReflowStatus::OK
!= lineReflowStatus
) {
4901 // It is possible that one or more of next lines are empty
4902 // (because of DeleteNextInFlowChild). If so, delete them now
4903 // in case we are finished.
4905 while ((aLine
!= LinesEnd()) && (0 == aLine
->GetChildCount())) {
4906 // XXX Is this still necessary now that DeleteNextInFlowChild
4907 // uses DoRemoveFrame?
4908 nsLineBox
* toremove
= aLine
;
4909 aLine
= mLines
.erase(aLine
);
4910 NS_ASSERTION(nullptr == toremove
->mFirstChild
, "bad empty line");
4911 FreeLineBox(toremove
);
4916 NS_ASSERTION(lineReflowStatus
!= LineReflowStatus::Truncated
,
4917 "ReflowInlineFrame should never determine that a line "
4918 "needs to go to the next page/column");
4922 // Don't pull up new frames into lines with continuation placeholders
4924 // Pull frames and reflow them until we can't
4925 while (LineReflowStatus::OK
== lineReflowStatus
) {
4926 frame
= PullFrame(aState
, aLine
);
4931 while (LineReflowStatus::OK
== lineReflowStatus
) {
4932 int32_t oldCount
= aLine
->GetChildCount();
4933 SetLineCursorForDisplay(aLine
);
4934 ReflowInlineFrame(aState
, aLineLayout
, aLine
, frame
, &lineReflowStatus
);
4935 if (aLine
->GetChildCount() != oldCount
) {
4936 // We just created a continuation for aFrame AND its going
4937 // to end up on this line (e.g. :first-letter
4938 // situation). Therefore we have to loop here before trying
4939 // to pull another frame.
4940 frame
= frame
->GetNextSibling();
4949 aState
.mFlags
.mIsLineLayoutEmpty
= aLineLayout
.LineIsEmpty();
4951 // We only need to backup if the line isn't going to be reflowed again anyway
4952 bool needsBackup
= aLineLayout
.NeedsBackup() &&
4953 (lineReflowStatus
== LineReflowStatus::Stop
||
4954 lineReflowStatus
== LineReflowStatus::OK
);
4955 if (needsBackup
&& aLineLayout
.HaveForcedBreakPosition()) {
4957 "We shouldn't be backing up more than once! "
4958 "Someone must have set a break opportunity beyond the available width, "
4959 "even though there were better break opportunities before it");
4960 needsBackup
= false;
4963 // We need to try backing up to before a text run
4964 // XXX It's possible, in fact not unusual, for the break opportunity to
4965 // already be the end of the line. We should detect that and optimize to not
4967 if (aLineLayout
.HasOptionalBreakPosition()) {
4969 lineReflowStatus
= LineReflowStatus::RedoNoPull
;
4972 // In case we reflow this line again, remember that we don't
4973 // need to force any breaking
4974 aLineLayout
.ClearOptionalBreakPosition();
4977 if (LineReflowStatus::RedoNextBand
== lineReflowStatus
) {
4978 // This happens only when we have a line that is impacted by
4979 // floats and the first element in the line doesn't fit with
4982 // If there's block space available, we either try to reflow the line
4983 // past the current band (if it's non-zero and the band definitely won't
4984 // widen around a shape-outside), otherwise we try one pixel down. If
4985 // there's no block space available, we push the line to the next
4988 NS_UNCONSTRAINEDSIZE
!= aFloatAvailableSpace
.mRect
.BSize(outerWM
),
4989 "unconstrained block size on totally empty line");
4991 // See the analogous code for blocks in BlockReflowState::ClearFloats.
4992 nscoord bandBSize
= aFloatAvailableSpace
.mRect
.BSize(outerWM
);
4993 if (bandBSize
> 0 ||
4994 NS_UNCONSTRAINEDSIZE
== aState
.mReflowInput
.AvailableBSize()) {
4995 NS_ASSERTION(bandBSize
== 0 || aFloatAvailableSpace
.HasFloats(),
4996 "redo line on totally empty line with non-empty band...");
4997 // We should never hit this case if we've placed floats on the
4998 // line; if we have, then the GetFloatAvailableSpace call is wrong
4999 // and needs to happen after the caller pops the float manager
5001 aState
.FloatManager()->AssertStateMatches(aFloatStateBeforeLine
);
5003 if (!aFloatAvailableSpace
.MayWiden() && bandBSize
> 0) {
5004 // Move it down far enough to clear the current band.
5005 aState
.mBCoord
+= bandBSize
;
5007 // Move it down by one dev pixel.
5008 aState
.mBCoord
+= aState
.mPresContext
->DevPixelsToAppUnits(1);
5011 aFloatAvailableSpace
= aState
.GetFloatAvailableSpace();
5013 // There's nowhere to retry placing the line, so we want to push
5014 // it to the next page/column where its contents can fit not
5016 lineReflowStatus
= LineReflowStatus::Truncated
;
5017 PushTruncatedLine(aState
, aLine
, aKeepReflowGoing
);
5020 // XXX: a small optimization can be done here when paginating:
5021 // if the new Y coordinate is past the end of the block then
5022 // push the line and return now instead of later on after we are
5024 } else if (LineReflowStatus::Truncated
!= lineReflowStatus
&&
5025 LineReflowStatus::RedoNoPull
!= lineReflowStatus
) {
5026 // If we are propagating out a break-before status then there is
5027 // no point in placing the line.
5028 if (!aState
.mReflowStatus
.IsInlineBreakBefore()) {
5029 if (!PlaceLine(aState
, aLineLayout
, aLine
, aFloatStateBeforeLine
,
5030 aFloatAvailableSpace
, aAvailableSpaceBSize
,
5031 aKeepReflowGoing
)) {
5032 lineReflowStatus
= LineReflowStatus::RedoMoreFloats
;
5033 // PlaceLine already called GetFloatAvailableSpaceForBSize or its
5040 printf("Line reflow status = %s\n",
5041 LineReflowStatusToString(lineReflowStatus
));
5045 if (aLineLayout
.GetDirtyNextLine()) {
5046 // aLine may have been pushed to the overflow lines.
5047 FrameLines
* overflowLines
= GetOverflowLines();
5048 // We can't just compare iterators front() to aLine here, since they may be
5049 // in different lists.
5050 bool pushedToOverflowLines
=
5051 overflowLines
&& overflowLines
->mLines
.front() == aLine
.get();
5052 if (pushedToOverflowLines
) {
5053 // aLine is stale, it's associated with the main line list but it should
5054 // be associated with the overflow line list now
5055 aLine
= overflowLines
->mLines
.begin();
5057 nsBlockInFlowLineIterator
iter(this, aLine
, pushedToOverflowLines
);
5058 if (iter
.Next() && iter
.GetLine()->IsInline()) {
5059 iter
.GetLine()->MarkDirty();
5060 if (iter
.GetContainer() != this) {
5061 aState
.mReflowStatus
.SetNextInFlowNeedsReflow();
5066 *aLineReflowStatus
= lineReflowStatus
;
5070 * Reflow an inline frame. The reflow status is mapped from the frames
5071 * reflow status to the lines reflow status (not to our reflow status).
5072 * The line reflow status is simple: true means keep placing frames
5073 * on the line; false means don't (the line is done). If the line
5074 * has some sort of breaking affect then aLine's break-type will be set
5075 * to something other than StyleClear::None.
5077 void nsBlockFrame::ReflowInlineFrame(BlockReflowState
& aState
,
5078 nsLineLayout
& aLineLayout
,
5079 LineIterator aLine
, nsIFrame
* aFrame
,
5080 LineReflowStatus
* aLineReflowStatus
) {
5082 *aLineReflowStatus
= LineReflowStatus::OK
;
5084 #ifdef NOISY_FIRST_LETTER
5086 printf(": reflowing ");
5087 aFrame
->ListTag(stdout
);
5088 printf(" reflowingFirstLetter=%s\n",
5089 aLineLayout
.GetFirstLetterStyleOK() ? "on" : "off");
5092 if (aFrame
->IsPlaceholderFrame()) {
5093 auto ph
= static_cast<nsPlaceholderFrame
*>(aFrame
);
5094 ph
->ForgetLineIsEmptySoFar();
5097 // Reflow the inline frame
5098 nsReflowStatus frameReflowStatus
;
5100 aLineLayout
.ReflowFrame(aFrame
, frameReflowStatus
, nullptr, pushedFrame
);
5102 if (frameReflowStatus
.NextInFlowNeedsReflow()) {
5103 aLineLayout
.SetDirtyNextLine();
5106 #ifdef REALLY_NOISY_REFLOW
5107 aFrame
->ListTag(stdout
);
5108 printf(": status=%s\n", ToString(frameReflowStatus
).c_str());
5111 #if defined(REFLOW_STATUS_COVERAGE)
5112 RecordReflowStatus(false, frameReflowStatus
);
5115 // Send post-reflow notification
5116 aState
.mPrevChild
= aFrame
;
5119 This is where we need to add logic to handle some odd behavior.
5120 For one thing, we should usually place at least one thing next
5121 to a left float, even when that float takes up all the width on a line.
5125 // Process the child frames reflow status. There are 5 cases:
5126 // complete, not-complete, break-before, break-after-complete,
5127 // break-after-not-complete. There are two situations: we are a
5128 // block or we are an inline. This makes a total of 10 cases
5129 // (fortunately, there is some overlap).
5130 aLine
->ClearForcedLineBreak();
5131 if (frameReflowStatus
.IsInlineBreak() ||
5132 aState
.mTrailingClearFromPIF
!= StyleClear::None
) {
5133 // Always abort the line reflow (because a line break is the
5134 // minimal amount of break we do).
5135 *aLineReflowStatus
= LineReflowStatus::Stop
;
5137 // XXX what should aLine's break-type be set to in all these cases?
5138 if (frameReflowStatus
.IsInlineBreakBefore()) {
5139 // Break-before cases.
5140 if (aFrame
== aLine
->mFirstChild
) {
5141 // If we break before the first frame on the line then we must
5142 // be trying to place content where there's no room (e.g. on a
5143 // line with wide floats). Inform the caller to reflow the
5144 // line after skipping past a float.
5145 *aLineReflowStatus
= LineReflowStatus::RedoNextBand
;
5147 // It's not the first child on this line so go ahead and split
5148 // the line. We will see the frame again on the next-line.
5149 SplitLine(aState
, aLineLayout
, aLine
, aFrame
, aLineReflowStatus
);
5151 // If we're splitting the line because the frame didn't fit and it
5152 // was pushed, then mark the line as having word wrapped. We need to
5153 // know that if we're shrink wrapping our width
5155 aLine
->SetLineWrapped(true);
5159 MOZ_ASSERT(frameReflowStatus
.IsInlineBreakAfter() ||
5160 aState
.mTrailingClearFromPIF
!= StyleClear::None
,
5161 "We should've handled inline break-before in the if-branch!");
5163 // If a float split and its prev-in-flow was followed by a <BR>, then
5164 // combine the <BR>'s float clear type with the inline's float clear type
5165 // (the inline will be the very next frame after the split float).
5166 StyleClear clearType
= frameReflowStatus
.FloatClearType();
5167 if (aState
.mTrailingClearFromPIF
!= StyleClear::None
) {
5168 clearType
= nsLayoutUtils::CombineClearType(
5169 clearType
, aState
.mTrailingClearFromPIF
);
5170 aState
.mTrailingClearFromPIF
= StyleClear::None
;
5172 // Break-after cases
5173 if (clearType
!= StyleClear::None
|| aLineLayout
.GetLineEndsInBR()) {
5174 aLine
->SetForcedLineBreakAfter(clearType
);
5176 if (frameReflowStatus
.IsComplete()) {
5177 // Split line, but after the frame just reflowed
5178 SplitLine(aState
, aLineLayout
, aLine
, aFrame
->GetNextSibling(),
5181 if (frameReflowStatus
.IsInlineBreakAfter() &&
5182 !aLineLayout
.GetLineEndsInBR()) {
5183 aLineLayout
.SetDirtyNextLine();
5189 if (!frameReflowStatus
.IsFullyComplete()) {
5190 // Create a continuation for the incomplete frame. Note that the
5191 // frame may already have a continuation.
5192 CreateContinuationFor(aState
, aLine
, aFrame
);
5194 // Remember that the line has wrapped
5195 if (!aLineLayout
.GetLineEndsInBR()) {
5196 aLine
->SetLineWrapped(true);
5199 // If we just ended a first-letter frame or reflowed a placeholder then
5200 // don't split the line and don't stop the line reflow...
5201 // But if we are going to stop anyways we'd better split the line.
5202 if ((!frameReflowStatus
.FirstLetterComplete() &&
5203 !aFrame
->IsPlaceholderFrame()) ||
5204 *aLineReflowStatus
== LineReflowStatus::Stop
) {
5205 // Split line after the current frame
5206 *aLineReflowStatus
= LineReflowStatus::Stop
;
5207 SplitLine(aState
, aLineLayout
, aLine
, aFrame
->GetNextSibling(),
5213 bool nsBlockFrame::CreateContinuationFor(BlockReflowState
& aState
,
5214 nsLineBox
* aLine
, nsIFrame
* aFrame
) {
5215 nsIFrame
* newFrame
= nullptr;
5217 if (!aFrame
->GetNextInFlow()) {
5219 PresShell()->FrameConstructor()->CreateContinuingFrame(aFrame
, this);
5221 mFrames
.InsertFrame(nullptr, aFrame
, newFrame
);
5224 aLine
->NoteFrameAdded(newFrame
);
5233 void nsBlockFrame::SplitFloat(BlockReflowState
& aState
, nsIFrame
* aFloat
,
5234 const nsReflowStatus
& aFloatStatus
) {
5235 MOZ_ASSERT(!aFloatStatus
.IsFullyComplete(),
5236 "why split the frame if it's fully complete?");
5237 MOZ_ASSERT(aState
.mBlock
== this);
5239 nsIFrame
* nextInFlow
= aFloat
->GetNextInFlow();
5241 nsContainerFrame
* oldParent
= nextInFlow
->GetParent();
5242 oldParent
->StealFrame(nextInFlow
);
5243 if (oldParent
!= this) {
5244 ReparentFrame(nextInFlow
, oldParent
, this);
5246 if (!aFloatStatus
.IsOverflowIncomplete()) {
5247 nextInFlow
->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER
);
5251 PresShell()->FrameConstructor()->CreateContinuingFrame(aFloat
, this);
5253 if (aFloatStatus
.IsOverflowIncomplete()) {
5254 nextInFlow
->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER
);
5257 StyleFloat floatStyle
= aFloat
->StyleDisplay()->mFloat
;
5258 if (floatStyle
== StyleFloat::Left
) {
5259 aState
.FloatManager()->SetSplitLeftFloatAcrossBreak();
5261 MOZ_ASSERT(floatStyle
== StyleFloat::Right
, "Unexpected float side!");
5262 aState
.FloatManager()->SetSplitRightFloatAcrossBreak();
5265 aState
.AppendPushedFloatChain(nextInFlow
);
5266 if (MOZ_LIKELY(!HasAnyStateBits(NS_BLOCK_BFC
)) ||
5267 MOZ_UNLIKELY(IsTrueOverflowContainer())) {
5268 aState
.mReflowStatus
.SetOverflowIncomplete();
5270 aState
.mReflowStatus
.SetIncomplete();
5274 static bool CheckPlaceholderInLine(nsIFrame
* aBlock
, nsLineBox
* aLine
,
5279 NS_ASSERTION(!aFloat
->GetPrevContinuation(),
5280 "float in a line should never be a continuation");
5281 NS_ASSERTION(!aFloat
->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT
),
5282 "float in a line should never be a pushed float");
5283 nsIFrame
* ph
= aFloat
->FirstInFlow()->GetPlaceholderFrame();
5284 for (nsIFrame
* f
= ph
; f
; f
= f
->GetParent()) {
5285 if (f
->GetParent() == aBlock
) {
5286 return aLine
->Contains(f
);
5289 NS_ASSERTION(false, "aBlock is not an ancestor of aFrame!");
5293 void nsBlockFrame::SplitLine(BlockReflowState
& aState
,
5294 nsLineLayout
& aLineLayout
, LineIterator aLine
,
5296 LineReflowStatus
* aLineReflowStatus
) {
5297 MOZ_ASSERT(aLine
->IsInline(), "illegal SplitLine on block line");
5300 aLine
->GetChildCount() - aLineLayout
.GetCurrentSpanCount();
5301 MOZ_ASSERT(pushCount
>= 0, "bad push count");
5305 nsIFrame::IndentBy(stdout
, gNoiseIndent
);
5306 printf("split line: from line=%p pushCount=%d aFrame=",
5307 static_cast<void*>(aLine
.get()), pushCount
);
5309 aFrame
->ListTag(stdout
);
5314 if (gReallyNoisyReflow
) {
5315 aLine
->List(stdout
, gNoiseIndent
+ 1);
5320 if (0 != pushCount
) {
5321 MOZ_ASSERT(aLine
->GetChildCount() > pushCount
, "bad push");
5322 MOZ_ASSERT(nullptr != aFrame
, "whoops");
5325 nsIFrame
* f
= aFrame
;
5326 int32_t count
= pushCount
;
5327 while (f
&& count
> 0) {
5328 f
= f
->GetNextSibling();
5331 NS_ASSERTION(count
== 0, "Not enough frames to push");
5335 // Put frames being split out into their own line
5336 nsLineBox
* newLine
= NewLineBox(aLine
, aFrame
, pushCount
);
5337 mLines
.after_insert(aLine
, newLine
);
5339 if (gReallyNoisyReflow
) {
5340 newLine
->List(stdout
, gNoiseIndent
+ 1);
5344 // Let line layout know that some frames are no longer part of its
5346 aLineLayout
.SplitLineTo(aLine
->GetChildCount());
5348 // If floats have been placed whose placeholders have been pushed to the new
5349 // line, we need to reflow the old line again. We don't want to look at the
5350 // frames in the new line, because as a large paragraph is laid out the
5351 // we'd get O(N^2) performance. So instead we just check that the last
5352 // float and the last below-current-line float are still in aLine.
5353 if (!CheckPlaceholderInLine(
5355 aLine
->HasFloats() ? aLine
->Floats().LastElement() : nullptr) ||
5356 !CheckPlaceholderInLine(
5358 aState
.mBelowCurrentLineFloats
.SafeLastElement(nullptr))) {
5359 *aLineReflowStatus
= LineReflowStatus::RedoNoPull
;
5368 bool nsBlockFrame::IsLastLine(BlockReflowState
& aState
, LineIterator aLine
) {
5369 while (++aLine
!= LinesEnd()) {
5370 // There is another line
5371 if (0 != aLine
->GetChildCount()) {
5372 // If the next line is a block line then this line is the last in a
5373 // group of inline lines.
5374 return aLine
->IsBlock();
5376 // The next line is empty, try the next one
5379 // Try our next-in-flows lines to answer the question
5380 nsBlockFrame
* nextInFlow
= (nsBlockFrame
*)GetNextInFlow();
5381 while (nullptr != nextInFlow
) {
5382 for (const auto& line
: nextInFlow
->Lines()) {
5383 if (0 != line
.GetChildCount()) {
5384 return line
.IsBlock();
5387 nextInFlow
= (nsBlockFrame
*)nextInFlow
->GetNextInFlow();
5390 // This is the last line - so don't allow justification
5394 bool nsBlockFrame::PlaceLine(BlockReflowState
& aState
,
5395 nsLineLayout
& aLineLayout
, LineIterator aLine
,
5396 nsFloatManager::SavedState
* aFloatStateBeforeLine
,
5397 nsFlowAreaRect
& aFlowArea
,
5398 nscoord
& aAvailableSpaceBSize
,
5399 bool* aKeepReflowGoing
) {
5400 // Try to position the floats in a nowrap context.
5401 aLineLayout
.FlushNoWrapFloats();
5403 // Trim extra white-space from the line before placing the frames
5404 aLineLayout
.TrimTrailingWhiteSpace();
5406 // Vertically align the frames on this line.
5408 // According to the CSS2 spec, section 12.6.1, the "marker" box
5409 // participates in the height calculation of the list-item box's
5412 // There are exactly two places a ::marker can be placed: near the
5413 // first or second line. It's only placed on the second line in a
5414 // rare case: when the first line is empty.
5415 WritingMode wm
= aState
.mReflowInput
.GetWritingMode();
5416 bool addedMarker
= false;
5417 if (HasOutsideMarker() &&
5418 ((aLine
== mLines
.front() &&
5419 (!aLineLayout
.IsZeroBSize() || (aLine
== mLines
.back()))) ||
5420 (mLines
.front() != mLines
.back() && 0 == mLines
.front()->BSize() &&
5421 aLine
== mLines
.begin().next()))) {
5422 ReflowOutput
metrics(aState
.mReflowInput
);
5423 nsIFrame
* marker
= GetOutsideMarker();
5424 ReflowOutsideMarker(marker
, aState
, metrics
, aState
.mBCoord
);
5425 NS_ASSERTION(!MarkerIsEmpty() || metrics
.BSize(wm
) == 0,
5426 "empty ::marker frame took up space");
5427 aLineLayout
.AddMarkerFrame(marker
, metrics
);
5430 aLineLayout
.VerticalAlignLine();
5432 // We want to consider the floats in the current line when determining
5433 // whether the float available space is shrunk. If mLineBSize doesn't
5434 // exist, we are in the first pass trying to place the line. Calling
5435 // GetFloatAvailableSpace() like we did in BlockReflowState::AddFloat()
5436 // for UpdateBand().
5438 // floatAvailableSpaceWithOldLineBSize is the float available space with
5439 // the old BSize, but including the floats that were added in this line.
5440 LogicalRect floatAvailableSpaceWithOldLineBSize
=
5441 aState
.mLineBSize
.isNothing()
5442 ? aState
.GetFloatAvailableSpace(aLine
->BStart()).mRect
5444 .GetFloatAvailableSpaceForBSize(
5445 aLine
->BStart(), aState
.mLineBSize
.value(), nullptr)
5448 // As we redo for floats, we can't reduce the amount of BSize we're
5450 aAvailableSpaceBSize
= std::max(aAvailableSpaceBSize
, aLine
->BSize());
5451 LogicalRect floatAvailableSpaceWithLineBSize
=
5453 .GetFloatAvailableSpaceForBSize(aLine
->BStart(), aAvailableSpaceBSize
,
5457 // If the available space between the floats is smaller now that we
5458 // know the BSize, return false (and cause another pass with
5459 // LineReflowStatus::RedoMoreFloats). We ensure aAvailableSpaceBSize
5460 // never decreases, which means that we can't reduce the set of floats
5461 // we intersect, which means that the available space cannot grow.
5462 if (AvailableSpaceShrunk(wm
, floatAvailableSpaceWithOldLineBSize
,
5463 floatAvailableSpaceWithLineBSize
, false)) {
5464 // Prepare data for redoing the line.
5465 aState
.mLineBSize
= Some(aLine
->BSize());
5467 // Since we want to redo the line, we update aFlowArea by using the
5468 // aFloatStateBeforeLine, which is the float manager's state before the
5470 LogicalRect
oldFloatAvailableSpace(aFlowArea
.mRect
);
5471 aFlowArea
= aState
.GetFloatAvailableSpaceForBSize(
5472 aLine
->BStart(), aAvailableSpaceBSize
, aFloatStateBeforeLine
);
5475 aFlowArea
.mRect
.BStart(wm
) == oldFloatAvailableSpace
.BStart(wm
),
5477 // Restore the BSize to the position of the next band.
5478 aFlowArea
.mRect
.BSize(wm
) = oldFloatAvailableSpace
.BSize(wm
);
5480 // Enforce both IStart() and IEnd() never move outwards to prevent
5481 // infinite grow-shrink loops.
5482 const nscoord iStartDiff
=
5483 aFlowArea
.mRect
.IStart(wm
) - oldFloatAvailableSpace
.IStart(wm
);
5484 const nscoord iEndDiff
=
5485 aFlowArea
.mRect
.IEnd(wm
) - oldFloatAvailableSpace
.IEnd(wm
);
5486 if (iStartDiff
< 0) {
5487 aFlowArea
.mRect
.IStart(wm
) -= iStartDiff
;
5488 aFlowArea
.mRect
.ISize(wm
) += iStartDiff
;
5491 aFlowArea
.mRect
.ISize(wm
) -= iEndDiff
;
5498 if (!GetParent()->IsAbsurdSizeAssertSuppressed()) {
5499 static nscoord lastHeight
= 0;
5500 if (ABSURD_SIZE(aLine
->BStart())) {
5501 lastHeight
= aLine
->BStart();
5502 if (abs(aLine
->BStart() - lastHeight
) > ABSURD_COORD
/ 10) {
5503 nsIFrame::ListTag(stdout
);
5504 printf(": line=%p y=%d line.bounds.height=%d\n",
5505 static_cast<void*>(aLine
.get()), aLine
->BStart(),
5514 // Only block frames horizontally align their children because
5515 // inline frames "shrink-wrap" around their children (therefore
5516 // there is no extra horizontal space).
5517 const nsStyleText
* styleText
= StyleText();
5520 * We don't care checking for IsLastLine properly if we don't care (if it
5521 * can't change the used text-align value for the line).
5523 * In other words, isLastLine really means isLastLineAndWeCare.
5525 const bool isLastLine
=
5526 !IsInSVGTextSubtree() &&
5527 styleText
->TextAlignForLastLine() != styleText
->mTextAlign
&&
5528 (aLineLayout
.GetLineEndsInBR() || IsLastLine(aState
, aLine
));
5530 aLineLayout
.TextAlignLine(aLine
, isLastLine
);
5532 // From here on, pfd->mBounds rectangles are incorrect because bidi
5533 // might have moved frames around!
5534 OverflowAreas overflowAreas
;
5535 aLineLayout
.RelativePositionFrames(overflowAreas
);
5536 aLine
->SetOverflowAreas(overflowAreas
);
5538 aLineLayout
.RemoveMarkerFrame(GetOutsideMarker());
5541 // Inline lines do not have margins themselves; however they are
5542 // impacted by prior block margins. If this line ends up having some
5543 // height then we zero out the previous block-end margin value that was
5544 // already applied to the line's starting Y coordinate. Otherwise we
5545 // leave it be so that the previous blocks block-end margin can be
5546 // collapsed with a block that follows.
5549 if (!aLine
->CachedIsEmpty()) {
5550 // This line has some height. Therefore the application of the
5551 // previous-bottom-margin should stick.
5552 aState
.mPrevBEndMargin
.Zero();
5553 newBCoord
= aLine
->BEnd();
5555 // Don't let the previous-bottom-margin value affect the newBCoord
5556 // coordinate (it was applied in ReflowInlineFrames speculatively)
5557 // since the line is empty.
5558 // We already called |ShouldApplyBStartMargin|, and if we applied it
5559 // then mShouldApplyBStartMargin is set.
5560 nscoord dy
= aState
.mFlags
.mShouldApplyBStartMargin
5561 ? -aState
.mPrevBEndMargin
.Get()
5563 newBCoord
= aState
.mBCoord
+ dy
;
5566 if (!aState
.mReflowStatus
.IsFullyComplete() &&
5567 ShouldAvoidBreakInside(aState
.mReflowInput
)) {
5568 aLine
->AppendFloats(std::move(aState
.mCurrentLineFloats
));
5569 SetBreakBeforeStatusBeforeLine(aState
, aLine
, aKeepReflowGoing
);
5573 // See if the line fit (our first line always does).
5574 if (mLines
.front() != aLine
&&
5575 aState
.ContentBSize() != NS_UNCONSTRAINEDSIZE
&&
5576 newBCoord
> aState
.ContentBEnd()) {
5577 NS_ASSERTION(aState
.mCurrentLine
== aLine
, "oops");
5578 if (ShouldAvoidBreakInside(aState
.mReflowInput
)) {
5579 // All our content doesn't fit, start on the next page.
5580 SetBreakBeforeStatusBeforeLine(aState
, aLine
, aKeepReflowGoing
);
5582 // Push aLine and all of its children and anything else that
5583 // follows to our next-in-flow.
5584 PushTruncatedLine(aState
, aLine
, aKeepReflowGoing
);
5589 // Note that any early return before this update of aState.mBCoord
5590 // must either (a) return false or (b) set aKeepReflowGoing to false.
5591 // Otherwise we'll keep reflowing later lines at an incorrect
5592 // position, and we might not come back and clean up the damage later.
5593 aState
.mBCoord
= newBCoord
;
5595 // Add the already placed current-line floats to the line
5596 aLine
->AppendFloats(std::move(aState
.mCurrentLineFloats
));
5598 // Any below current line floats to place?
5599 if (!aState
.mBelowCurrentLineFloats
.IsEmpty()) {
5600 // Reflow the below-current-line floats, which places on the line's
5602 aState
.PlaceBelowCurrentLineFloats(aLine
);
5605 // When a line has floats, factor them into the overflow areas computations.
5606 if (aLine
->HasFloats()) {
5607 // Union the float overflow areas (stored in aState) and the value computed
5608 // by the line layout code.
5609 OverflowAreas lineOverflowAreas
= aState
.mFloatOverflowAreas
;
5610 lineOverflowAreas
.UnionWith(aLine
->GetOverflowAreas());
5611 aLine
->SetOverflowAreas(lineOverflowAreas
);
5613 #ifdef NOISY_OVERFLOW_AREAS
5614 printf("%s: Line %p, InkOverflowRect=%s, ScrollableOverflowRect=%s\n",
5615 ListTag().get(), aLine
.get(),
5616 ToString(aLine
->InkOverflowRect()).c_str(),
5617 ToString(aLine
->ScrollableOverflowRect()).c_str());
5621 // Apply break-after clearing if necessary
5622 // This must stay in sync with |ReflowDirtyLines|.
5623 if (aLine
->HasFloatClearTypeAfter()) {
5624 std::tie(aState
.mBCoord
, std::ignore
) =
5625 aState
.ClearFloats(aState
.mBCoord
, aLine
->FloatClearTypeAfter());
5630 void nsBlockFrame::PushLines(BlockReflowState
& aState
,
5631 nsLineList::iterator aLineBefore
) {
5632 // NOTE: aLineBefore is always a normal line, not an overflow line.
5633 // The following expression will assert otherwise.
5634 DebugOnly
<bool> check
= aLineBefore
== mLines
.begin();
5636 nsLineList::iterator
overBegin(aLineBefore
.next());
5638 // PushTruncatedPlaceholderLine sometimes pushes the first line. Ugh.
5639 bool firstLine
= overBegin
== LinesBegin();
5641 if (overBegin
!= LinesEnd()) {
5642 // Remove floats in the lines from floats list.
5644 CollectFloats(overBegin
->mFirstChild
, floats
, true);
5646 if (floats
.NotEmpty()) {
5648 for (nsIFrame
* f
: floats
) {
5649 MOZ_ASSERT(!f
->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT
),
5650 "CollectFloats should've removed that bit");
5653 // Push the floats onto the front of the overflow out-of-flows list
5654 nsAutoOOFFrameList
oofs(this);
5655 oofs
.mList
.InsertFrames(nullptr, nullptr, std::move(floats
));
5658 // overflow lines can already exist in some cases, in particular,
5659 // when shrinkwrapping and we discover that the shrinkwap causes
5660 // the height of some child block to grow which creates additional
5661 // overflowing content. In such cases we must prepend the new
5662 // overflow to the existing overflow.
5663 FrameLines
* overflowLines
= RemoveOverflowLines();
5664 if (!overflowLines
) {
5665 // XXXldb use presshell arena!
5666 overflowLines
= new FrameLines();
5668 if (overflowLines
) {
5669 nsIFrame
* lineBeforeLastFrame
;
5671 lineBeforeLastFrame
= nullptr; // removes all frames
5673 nsIFrame
* f
= overBegin
->mFirstChild
;
5674 lineBeforeLastFrame
= f
? f
->GetPrevSibling() : mFrames
.LastChild();
5675 NS_ASSERTION(!f
|| lineBeforeLastFrame
== aLineBefore
->LastChild(),
5676 "unexpected line frames");
5678 nsFrameList pushedFrames
= mFrames
.TakeFramesAfter(lineBeforeLastFrame
);
5679 overflowLines
->mFrames
.InsertFrames(nullptr, nullptr,
5680 std::move(pushedFrames
));
5682 overflowLines
->mLines
.splice(overflowLines
->mLines
.begin(), mLines
,
5683 overBegin
, LinesEnd());
5684 NS_ASSERTION(!overflowLines
->mLines
.empty(), "should not be empty");
5685 // this takes ownership but it won't delete it immediately so we
5686 // can keep using it.
5687 SetOverflowLines(overflowLines
);
5689 // Mark all the overflow lines dirty so that they get reflowed when
5690 // they are pulled up by our next-in-flow.
5692 nsLineBox
* cursor
= GetLineCursorForDisplay();
5694 // XXXldb Can this get called O(N) times making the whole thing O(N^2)?
5695 for (LineIterator line
= overflowLines
->mLines
.begin(),
5696 line_end
= overflowLines
->mLines
.end();
5697 line
!= line_end
; ++line
) {
5698 if (line
== cursor
) {
5702 line
->MarkPreviousMarginDirty();
5703 line
->SetMovedFragments();
5704 line
->SetBoundsEmpty();
5705 if (line
->HasFloats()) {
5706 line
->ClearFloats();
5713 VerifyOverflowSituation();
5717 // The overflowLines property is stored as a pointer to a line list,
5718 // which must be deleted. However, the following functions all maintain
5719 // the invariant that the property is never set if the list is empty.
5721 bool nsBlockFrame::DrainOverflowLines() {
5723 VerifyOverflowSituation();
5726 // Steal the prev-in-flow's overflow lines and prepend them.
5727 bool didFindOverflow
= false;
5728 nsBlockFrame
* prevBlock
= static_cast<nsBlockFrame
*>(GetPrevInFlow());
5730 prevBlock
->ClearLineCursors();
5731 FrameLines
* overflowLines
= prevBlock
->RemoveOverflowLines();
5732 if (overflowLines
) {
5733 // Make all the frames on the overflow line list mine.
5734 ReparentFrames(overflowLines
->mFrames
, prevBlock
, this);
5736 // Collect overflow containers from our OverflowContainers list that are
5737 // continuations from the frames we picked up from our prev-in-flow, then
5738 // prepend those to ExcessOverflowContainers to ensure the continuations
5740 if (GetOverflowContainers()) {
5741 nsFrameList ocContinuations
;
5742 for (auto* f
: overflowLines
->mFrames
) {
5745 while (!done
&& (cont
= cont
->GetNextContinuation()) &&
5746 cont
->GetParent() == this) {
5747 bool onlyChild
= !cont
->GetPrevSibling() && !cont
->GetNextSibling();
5748 if (cont
->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER
) &&
5749 TryRemoveFrame(OverflowContainersProperty(), cont
)) {
5750 ocContinuations
.AppendFrame(nullptr, cont
);
5760 if (!ocContinuations
.IsEmpty()) {
5761 if (nsFrameList
* eoc
= GetExcessOverflowContainers()) {
5762 eoc
->InsertFrames(nullptr, nullptr, std::move(ocContinuations
));
5764 SetExcessOverflowContainers(std::move(ocContinuations
));
5769 // Make the overflow out-of-flow frames mine too.
5770 nsAutoOOFFrameList
oofs(prevBlock
);
5771 if (oofs
.mList
.NotEmpty()) {
5772 // In case we own any next-in-flows of any of the drained frames, then
5773 // move those to the PushedFloat list.
5774 nsFrameList pushedFloats
;
5775 for (nsIFrame
* f
: oofs
.mList
) {
5776 nsIFrame
* nif
= f
->GetNextInFlow();
5777 for (; nif
&& nif
->GetParent() == this; nif
= nif
->GetNextInFlow()) {
5778 MOZ_ASSERT(nif
->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT
));
5780 pushedFloats
.AppendFrame(nullptr, nif
);
5783 ReparentFrames(oofs
.mList
, prevBlock
, this);
5784 EnsureFloats()->InsertFrames(nullptr, nullptr, std::move(oofs
.mList
));
5785 if (!pushedFloats
.IsEmpty()) {
5786 nsFrameList
* pf
= EnsurePushedFloats();
5787 pf
->InsertFrames(nullptr, nullptr, std::move(pushedFloats
));
5791 if (!mLines
.empty()) {
5792 // Remember to recompute the margins on the first line. This will
5793 // also recompute the correct deltaBCoord if necessary.
5794 mLines
.front()->MarkPreviousMarginDirty();
5796 // The overflow lines have already been marked dirty and their previous
5797 // margins marked dirty also.
5799 // Prepend the overflow frames/lines to our principal list.
5800 mFrames
.InsertFrames(nullptr, nullptr, std::move(overflowLines
->mFrames
));
5801 mLines
.splice(mLines
.begin(), overflowLines
->mLines
);
5802 NS_ASSERTION(overflowLines
->mLines
.empty(), "splice should empty list");
5803 delete overflowLines
;
5804 didFindOverflow
= true;
5808 // Now append our own overflow lines.
5809 return DrainSelfOverflowList() || didFindOverflow
;
5812 bool nsBlockFrame::DrainSelfOverflowList() {
5813 UniquePtr
<FrameLines
> ourOverflowLines(RemoveOverflowLines());
5814 if (!ourOverflowLines
) {
5818 // No need to reparent frames in our own overflow lines/oofs, because they're
5819 // already ours. But we should put overflow floats back in our floats list.
5820 // (explicit scope to remove the OOF list before VerifyOverflowSituation)
5822 nsAutoOOFFrameList
oofs(this);
5823 if (oofs
.mList
.NotEmpty()) {
5825 for (nsIFrame
* f
: oofs
.mList
) {
5826 MOZ_ASSERT(!f
->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT
),
5827 "CollectFloats should've removed that bit");
5830 // The overflow floats go after our regular floats.
5831 EnsureFloats()->AppendFrames(nullptr, std::move(oofs
).mList
);
5834 if (!ourOverflowLines
->mLines
.empty()) {
5835 mFrames
.AppendFrames(nullptr, std::move(ourOverflowLines
->mFrames
));
5836 mLines
.splice(mLines
.end(), ourOverflowLines
->mLines
);
5840 VerifyOverflowSituation();
5846 * Pushed floats are floats whose placeholders are in a previous
5847 * continuation. They might themselves be next-continuations of a float
5848 * that partially fit in an earlier continuation, or they might be the
5849 * first continuation of a float that couldn't be placed at all.
5851 * Pushed floats live permanently at the beginning of a block's float
5852 * list, where they must live *before* any floats whose placeholders are
5855 * Temporarily, during reflow, they also live on the pushed floats list,
5856 * which only holds them between (a) when one continuation pushes them to
5857 * its pushed floats list because they don't fit and (b) when the next
5858 * continuation pulls them onto the beginning of its float list.
5860 * DrainPushedFloats sets up pushed floats the way we need them at the
5861 * start of reflow; they are then reflowed by ReflowPushedFloats (which
5862 * might push some of them on). Floats with placeholders in this block
5863 * are reflowed by (BlockReflowState/nsLineLayout)::AddFloat, which
5864 * also maintains these invariants.
5866 * DrainSelfPushedFloats moves any pushed floats from this block's own
5867 * pushed floats list back into floats list. DrainPushedFloats additionally
5868 * moves frames from its prev-in-flow's pushed floats list into floats list.
5870 void nsBlockFrame::DrainSelfPushedFloats() {
5871 // If we're getting reflowed multiple times without our
5872 // next-continuation being reflowed, we might need to pull back floats
5873 // that we just put in the list to be pushed to our next-in-flow.
5874 // We don't want to pull back any next-in-flows of floats on our own
5875 // float list, and we only need to pull back first-in-flows whose
5876 // placeholders were in earlier blocks (since first-in-flows whose
5877 // placeholders are in this block will get pulled appropriately by
5878 // AddFloat, and will then be more likely to be in the correct order).
5879 mozilla::PresShell
* presShell
= PresShell();
5880 nsFrameList
* ourPushedFloats
= GetPushedFloats();
5881 if (ourPushedFloats
) {
5882 nsFrameList
* floats
= GetFloats();
5884 // When we pull back floats, we want to put them with the pushed
5885 // floats, which must live at the start of our float list, but we
5886 // want them at the end of those pushed floats.
5887 // FIXME: This isn't quite right! What if they're all pushed floats?
5888 nsIFrame
* insertionPrevSibling
= nullptr; /* beginning of list */
5889 for (nsIFrame
* f
= floats
? floats
->FirstChild() : nullptr;
5890 f
&& f
->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT
);
5891 f
= f
->GetNextSibling()) {
5892 insertionPrevSibling
= f
;
5895 nsIFrame
* f
= ourPushedFloats
->LastChild();
5897 nsIFrame
* prevSibling
= f
->GetPrevSibling();
5899 nsPlaceholderFrame
* placeholder
= f
->GetPlaceholderFrame();
5900 nsIFrame
* floatOriginalParent
=
5901 presShell
->FrameConstructor()->GetFloatContainingBlock(placeholder
);
5902 if (floatOriginalParent
!= this) {
5903 // This is a first continuation that was pushed from one of our
5904 // previous continuations. Take it out of the pushed floats
5905 // list and put it in our floats list, before any of our
5906 // floats, but after other pushed floats.
5907 ourPushedFloats
->RemoveFrame(f
);
5909 floats
= EnsureFloats();
5911 floats
->InsertFrame(nullptr, insertionPrevSibling
, f
);
5917 if (ourPushedFloats
->IsEmpty()) {
5918 StealPushedFloats()->Delete(presShell
);
5923 void nsBlockFrame::DrainPushedFloats() {
5924 DrainSelfPushedFloats();
5926 // After our prev-in-flow has completed reflow, it may have a pushed
5927 // floats list, containing floats that we need to own. Take these.
5928 nsBlockFrame
* prevBlock
= static_cast<nsBlockFrame
*>(GetPrevInFlow());
5930 AutoFrameListPtr
list(PresContext(), prevBlock
->StealPushedFloats());
5931 if (list
&& list
->NotEmpty()) {
5932 EnsureFloats()->InsertFrames(this, nullptr, std::move(*list
));
5937 nsBlockFrame::FrameLines
* nsBlockFrame::GetOverflowLines() const {
5938 if (!HasOverflowLines()) {
5941 FrameLines
* prop
= GetProperty(OverflowLinesProperty());
5943 prop
&& !prop
->mLines
.empty() &&
5944 prop
->mLines
.front()->GetChildCount() == 0
5945 ? prop
->mFrames
.IsEmpty()
5946 : prop
->mLines
.front()->mFirstChild
== prop
->mFrames
.FirstChild(),
5947 "value should always be stored and non-empty when state set");
5951 nsBlockFrame::FrameLines
* nsBlockFrame::RemoveOverflowLines() {
5952 if (!HasOverflowLines()) {
5955 FrameLines
* prop
= TakeProperty(OverflowLinesProperty());
5957 prop
&& !prop
->mLines
.empty() &&
5958 prop
->mLines
.front()->GetChildCount() == 0
5959 ? prop
->mFrames
.IsEmpty()
5960 : prop
->mLines
.front()->mFirstChild
== prop
->mFrames
.FirstChild(),
5961 "value should always be stored and non-empty when state set");
5962 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES
);
5966 void nsBlockFrame::DestroyOverflowLines() {
5967 NS_ASSERTION(HasOverflowLines(), "huh?");
5968 FrameLines
* prop
= TakeProperty(OverflowLinesProperty());
5969 NS_ASSERTION(prop
&& prop
->mLines
.empty(),
5970 "value should always be stored but empty when destroying");
5971 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES
);
5975 // This takes ownership of aOverflowLines.
5976 // XXX We should allocate overflowLines from presShell arena!
5977 void nsBlockFrame::SetOverflowLines(FrameLines
* aOverflowLines
) {
5978 NS_ASSERTION(aOverflowLines
, "null lines");
5979 NS_ASSERTION(!aOverflowLines
->mLines
.empty(), "empty lines");
5980 NS_ASSERTION(aOverflowLines
->mLines
.front()->mFirstChild
==
5981 aOverflowLines
->mFrames
.FirstChild(),
5982 "invalid overflow lines / frames");
5983 NS_ASSERTION(!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_LINES
),
5984 "Overwriting existing overflow lines");
5986 // Verify that we won't overwrite an existing overflow list
5987 NS_ASSERTION(!GetProperty(OverflowLinesProperty()), "existing overflow list");
5988 SetProperty(OverflowLinesProperty(), aOverflowLines
);
5989 AddStateBits(NS_BLOCK_HAS_OVERFLOW_LINES
);
5992 nsFrameList
* nsBlockFrame::GetOverflowOutOfFlows() const {
5993 if (!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS
)) {
5996 nsFrameList
* result
= GetProperty(OverflowOutOfFlowsProperty());
5997 NS_ASSERTION(result
, "value should always be non-empty when state set");
6001 void nsBlockFrame::SetOverflowOutOfFlows(nsFrameList
&& aList
,
6002 nsFrameList
* aPropValue
) {
6004 HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS
) == !!aPropValue
,
6005 "state does not match value");
6007 if (aList
.IsEmpty()) {
6008 if (!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS
)) {
6011 nsFrameList
* list
= TakeProperty(OverflowOutOfFlowsProperty());
6012 NS_ASSERTION(aPropValue
== list
, "prop value mismatch");
6014 list
->Delete(PresShell());
6015 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS
);
6016 } else if (HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS
)) {
6017 NS_ASSERTION(aPropValue
== GetProperty(OverflowOutOfFlowsProperty()),
6018 "prop value mismatch");
6019 *aPropValue
= std::move(aList
);
6021 SetProperty(OverflowOutOfFlowsProperty(),
6022 new (PresShell()) nsFrameList(std::move(aList
)));
6023 AddStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS
);
6027 nsIFrame
* nsBlockFrame::GetInsideMarker() const {
6028 if (!HasInsideMarker()) {
6031 NS_ASSERTION(!HasOutsideMarker(), "invalid marker state");
6032 nsIFrame
* frame
= GetProperty(InsideMarkerProperty());
6033 NS_ASSERTION(frame
, "bogus inside ::marker frame");
6037 nsIFrame
* nsBlockFrame::GetOutsideMarker() const {
6038 nsFrameList
* list
= GetOutsideMarkerList();
6039 return list
? list
->FirstChild() : nullptr;
6042 nsFrameList
* nsBlockFrame::GetOutsideMarkerList() const {
6043 if (!HasOutsideMarker()) {
6046 NS_ASSERTION(!HasInsideMarker(), "invalid marker state");
6047 nsFrameList
* list
= GetProperty(OutsideMarkerProperty());
6048 NS_ASSERTION(list
&& list
->GetLength() == 1, "bogus outside ::marker list");
6052 bool nsBlockFrame::HasFloats() const {
6053 const bool isStateBitSet
= HasAnyStateBits(NS_BLOCK_HAS_FLOATS
);
6055 isStateBitSet
== HasProperty(FloatsProperty()),
6056 "State bit should accurately reflect presence/absence of the property!");
6057 return isStateBitSet
;
6060 nsFrameList
* nsBlockFrame::GetFloats() const {
6064 nsFrameList
* list
= GetProperty(FloatsProperty());
6065 MOZ_ASSERT(list
, "List should always be valid when the property is set!");
6066 MOZ_ASSERT(list
->NotEmpty(),
6067 "Someone forgot to delete the list when it is empty!");
6071 nsFrameList
* nsBlockFrame::EnsureFloats() {
6072 nsFrameList
* list
= GetFloats();
6076 list
= new (PresShell()) nsFrameList
;
6077 SetProperty(FloatsProperty(), list
);
6078 AddStateBits(NS_BLOCK_HAS_FLOATS
);
6082 nsFrameList
* nsBlockFrame::StealFloats() {
6086 nsFrameList
* list
= TakeProperty(FloatsProperty());
6087 RemoveStateBits(NS_BLOCK_HAS_FLOATS
);
6088 MOZ_ASSERT(list
, "List should always be valid when the property is set!");
6092 bool nsBlockFrame::HasPushedFloats() const {
6093 const bool isStateBitSet
= HasAnyStateBits(NS_BLOCK_HAS_PUSHED_FLOATS
);
6095 isStateBitSet
== HasProperty(PushedFloatsProperty()),
6096 "State bit should accurately reflect presence/absence of the property!");
6097 return isStateBitSet
;
6100 nsFrameList
* nsBlockFrame::GetPushedFloats() const {
6101 if (!HasPushedFloats()) {
6104 nsFrameList
* list
= GetProperty(PushedFloatsProperty());
6105 MOZ_ASSERT(list
, "List should always be valid when the property is set!");
6106 MOZ_ASSERT(list
->NotEmpty(),
6107 "Someone forgot to delete the list when it is empty!");
6111 nsFrameList
* nsBlockFrame::EnsurePushedFloats() {
6112 nsFrameList
* result
= GetPushedFloats();
6117 result
= new (PresShell()) nsFrameList
;
6118 SetProperty(PushedFloatsProperty(), result
);
6119 AddStateBits(NS_BLOCK_HAS_PUSHED_FLOATS
);
6124 nsFrameList
* nsBlockFrame::StealPushedFloats() {
6125 if (!HasPushedFloats()) {
6128 nsFrameList
* list
= TakeProperty(PushedFloatsProperty());
6129 RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS
);
6130 MOZ_ASSERT(list
, "List should always be valid when the property is set!");
6134 //////////////////////////////////////////////////////////////////////
6135 // Frame list manipulation routines
6137 void nsBlockFrame::AppendFrames(ChildListID aListID
, nsFrameList
&& aFrameList
) {
6138 if (aFrameList
.IsEmpty()) {
6141 if (aListID
!= FrameChildListID::Principal
) {
6142 if (FrameChildListID::Float
== aListID
) {
6143 DrainSelfPushedFloats(); // ensure the last frame is in floats list.
6144 EnsureFloats()->AppendFrames(nullptr, std::move(aFrameList
));
6147 MOZ_ASSERT(FrameChildListID::NoReflowPrincipal
== aListID
,
6148 "unexpected child list");
6151 // Find the proper last-child for where the append should go
6152 nsIFrame
* lastKid
= mFrames
.LastChild();
6154 (mLines
.empty() ? nullptr : mLines
.back()->LastChild()) == lastKid
,
6155 "out-of-sync mLines / mFrames");
6157 #ifdef NOISY_REFLOW_REASON
6159 printf(": append ");
6160 for (nsIFrame
* frame
: aFrameList
) {
6161 frame
->ListTag(stdout
);
6165 lastKid
->ListTag(stdout
);
6170 if (IsInSVGTextSubtree()) {
6171 MOZ_ASSERT(GetParent()->IsSVGTextFrame(),
6172 "unexpected block frame in SVG text");
6173 // Workaround for bug 1399425 in case this bit has been removed from the
6174 // SVGTextFrame just before the parser adds more descendant nodes.
6175 GetParent()->AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY
);
6178 AddFrames(std::move(aFrameList
), lastKid
, nullptr);
6179 if (aListID
!= FrameChildListID::NoReflowPrincipal
) {
6180 PresShell()->FrameNeedsReflow(
6181 this, IntrinsicDirty::FrameAndAncestors
,
6182 NS_FRAME_HAS_DIRTY_CHILDREN
); // XXX sufficient?
6186 void nsBlockFrame::InsertFrames(ChildListID aListID
, nsIFrame
* aPrevFrame
,
6187 const nsLineList::iterator
* aPrevFrameLine
,
6188 nsFrameList
&& aFrameList
) {
6189 NS_ASSERTION(!aPrevFrame
|| aPrevFrame
->GetParent() == this,
6190 "inserting after sibling frame with different parent");
6192 if (aListID
!= FrameChildListID::Principal
) {
6193 if (FrameChildListID::Float
== aListID
) {
6194 DrainSelfPushedFloats(); // ensure aPrevFrame is in floats list.
6195 EnsureFloats()->InsertFrames(this, aPrevFrame
, std::move(aFrameList
));
6198 MOZ_ASSERT(FrameChildListID::NoReflowPrincipal
== aListID
,
6199 "unexpected child list");
6202 #ifdef NOISY_REFLOW_REASON
6204 printf(": insert ");
6205 for (nsIFrame
* frame
: aFrameList
) {
6206 frame
->ListTag(stdout
);
6210 aPrevFrame
->ListTag(stdout
);
6215 AddFrames(std::move(aFrameList
), aPrevFrame
, aPrevFrameLine
);
6216 if (aListID
!= FrameChildListID::NoReflowPrincipal
) {
6217 PresShell()->FrameNeedsReflow(
6218 this, IntrinsicDirty::FrameAndAncestors
,
6219 NS_FRAME_HAS_DIRTY_CHILDREN
); // XXX sufficient?
6223 void nsBlockFrame::RemoveFrame(DestroyContext
& aContext
, ChildListID aListID
,
6224 nsIFrame
* aOldFrame
) {
6225 #ifdef NOISY_REFLOW_REASON
6227 printf(": remove ");
6228 aOldFrame
->ListTag(stdout
);
6232 if (aListID
== FrameChildListID::Principal
) {
6233 bool hasFloats
= BlockHasAnyFloats(aOldFrame
);
6234 DoRemoveFrame(aContext
, aOldFrame
, REMOVE_FIXED_CONTINUATIONS
);
6236 MarkSameFloatManagerLinesDirty(this);
6238 } else if (FrameChildListID::Float
== aListID
) {
6239 // Make sure to mark affected lines dirty for the float frame
6240 // we are removing; this way is a bit messy, but so is the rest of the code.
6242 NS_ASSERTION(!aOldFrame
->GetPrevContinuation(),
6243 "RemoveFrame should not be called on pushed floats.");
6244 for (nsIFrame
* f
= aOldFrame
;
6245 f
&& !f
->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER
);
6246 f
= f
->GetNextContinuation()) {
6247 MarkSameFloatManagerLinesDirty(
6248 static_cast<nsBlockFrame
*>(f
->GetParent()));
6250 DoRemoveOutOfFlowFrame(aContext
, aOldFrame
);
6251 } else if (FrameChildListID::NoReflowPrincipal
== aListID
) {
6252 // Skip the call to |FrameNeedsReflow| below by returning now.
6253 DoRemoveFrame(aContext
, aOldFrame
, REMOVE_FIXED_CONTINUATIONS
);
6256 MOZ_CRASH("unexpected child list");
6259 PresShell()->FrameNeedsReflow(
6260 this, IntrinsicDirty::FrameAndAncestors
,
6261 NS_FRAME_HAS_DIRTY_CHILDREN
); // XXX sufficient?
6264 static bool ShouldPutNextSiblingOnNewLine(nsIFrame
* aLastFrame
) {
6265 LayoutFrameType type
= aLastFrame
->Type();
6266 if (type
== LayoutFrameType::Br
) {
6269 // XXX the TEXT_OFFSETS_NEED_FIXING check is a wallpaper for bug 822910.
6270 if (type
== LayoutFrameType::Text
&&
6271 !aLastFrame
->HasAnyStateBits(TEXT_OFFSETS_NEED_FIXING
)) {
6272 return aLastFrame
->HasSignificantTerminalNewline();
6277 void nsBlockFrame::AddFrames(nsFrameList
&& aFrameList
, nsIFrame
* aPrevSibling
,
6278 const nsLineList::iterator
* aPrevSiblingLine
) {
6279 // Clear our line cursor, since our lines may change.
6282 if (aFrameList
.IsEmpty()) {
6286 // Attempt to find the line that contains the previous sibling
6287 nsLineList
* lineList
= &mLines
;
6288 nsFrameList
* frames
= &mFrames
;
6289 nsLineList::iterator prevSibLine
;
6290 int32_t prevSiblingIndex
;
6291 if (aPrevSiblingLine
) {
6292 MOZ_ASSERT(aPrevSibling
);
6293 prevSibLine
= *aPrevSiblingLine
;
6294 FrameLines
* overflowLines
= GetOverflowLines();
6295 MOZ_ASSERT(prevSibLine
.IsInSameList(mLines
.begin()) ||
6297 prevSibLine
.IsInSameList(overflowLines
->mLines
.begin())),
6298 "must be one of our line lists");
6299 if (overflowLines
) {
6300 // We need to find out which list it's actually in. Assume that
6301 // *if* we have overflow lines, that our primary lines aren't
6302 // huge, but our overflow lines might be.
6303 nsLineList::iterator line
= mLines
.begin(), lineEnd
= mLines
.end();
6304 while (line
!= lineEnd
) {
6305 if (line
== prevSibLine
) {
6310 if (line
== lineEnd
) {
6311 // By elimination, the line must be in our overflow lines.
6312 lineList
= &overflowLines
->mLines
;
6313 frames
= &overflowLines
->mFrames
;
6317 nsLineList::iterator nextLine
= prevSibLine
.next();
6318 nsIFrame
* lastFrameInLine
= nextLine
== lineList
->end()
6319 ? frames
->LastChild()
6320 : nextLine
->mFirstChild
->GetPrevSibling();
6321 prevSiblingIndex
= prevSibLine
->RIndexOf(aPrevSibling
, lastFrameInLine
);
6322 MOZ_ASSERT(prevSiblingIndex
>= 0,
6323 "aPrevSibling must be in aPrevSiblingLine");
6325 prevSibLine
= lineList
->end();
6326 prevSiblingIndex
= -1;
6328 // XXX_perf This is technically O(N^2) in some cases, but by using
6329 // RFind instead of Find, we make it O(N) in the most common case,
6330 // which is appending content.
6332 // Find the line that contains the previous sibling
6333 if (!nsLineBox::RFindLineContaining(aPrevSibling
, lineList
->begin(),
6334 prevSibLine
, mFrames
.LastChild(),
6335 &prevSiblingIndex
)) {
6336 // Not in mLines - try overflow lines.
6337 FrameLines
* overflowLines
= GetOverflowLines();
6339 if (overflowLines
) {
6340 prevSibLine
= overflowLines
->mLines
.end();
6341 prevSiblingIndex
= -1;
6342 found
= nsLineBox::RFindLineContaining(
6343 aPrevSibling
, overflowLines
->mLines
.begin(), prevSibLine
,
6344 overflowLines
->mFrames
.LastChild(), &prevSiblingIndex
);
6346 if (MOZ_LIKELY(found
)) {
6347 lineList
= &overflowLines
->mLines
;
6348 frames
= &overflowLines
->mFrames
;
6350 // Note: defensive code! RFindLineContaining must not return
6351 // false in this case, so if it does...
6352 MOZ_ASSERT_UNREACHABLE("prev sibling not in line list");
6353 aPrevSibling
= nullptr;
6354 prevSibLine
= lineList
->end();
6360 // Find the frame following aPrevSibling so that we can join up the
6361 // two lists of frames.
6363 // Split line containing aPrevSibling in two if the insertion
6364 // point is somewhere in the middle of the line.
6365 int32_t rem
= prevSibLine
->GetChildCount() - prevSiblingIndex
- 1;
6367 // Split the line in two where the frame(s) are being inserted.
6369 NewLineBox(prevSibLine
, aPrevSibling
->GetNextSibling(), rem
);
6370 lineList
->after_insert(prevSibLine
, line
);
6371 // Mark prevSibLine dirty and as needing textrun invalidation, since
6372 // we may be breaking up text in the line. Its previous line may also
6373 // need to be invalidated because it may be able to pull some text up.
6374 MarkLineDirty(prevSibLine
, lineList
);
6375 // The new line will also need its textruns recomputed because of the
6378 line
->SetInvalidateTextRuns(true);
6380 } else if (!lineList
->empty()) {
6381 lineList
->front()->MarkDirty();
6382 lineList
->front()->SetInvalidateTextRuns(true);
6384 const nsFrameList::Slice
& newFrames
=
6385 frames
->InsertFrames(nullptr, aPrevSibling
, std::move(aFrameList
));
6387 // Walk through the new frames being added and update the line data
6388 // structures to fit.
6389 for (nsIFrame
* newFrame
: newFrames
) {
6390 NS_ASSERTION(!aPrevSibling
|| aPrevSibling
->GetNextSibling() == newFrame
,
6391 "Unexpected aPrevSibling");
6393 !newFrame
->IsPlaceholderFrame() ||
6394 (!newFrame
->IsAbsolutelyPositioned() && !newFrame
->IsFloating()),
6395 "Placeholders should not float or be positioned");
6397 bool isBlock
= newFrame
->IsBlockOutside();
6399 // If the frame is a block frame, or if there is no previous line or if the
6400 // previous line is a block line we need to make a new line. We also make
6401 // a new line, as an optimization, in the two cases we know we'll need it:
6402 // if the previous line ended with a <br>, or if it has significant
6403 // whitespace and ended in a newline.
6404 if (isBlock
|| prevSibLine
== lineList
->end() || prevSibLine
->IsBlock() ||
6405 (aPrevSibling
&& ShouldPutNextSiblingOnNewLine(aPrevSibling
))) {
6406 // Create a new line for the frame and add its line to the line
6408 nsLineBox
* line
= NewLineBox(newFrame
, isBlock
);
6409 if (prevSibLine
!= lineList
->end()) {
6410 // Append new line after prevSibLine
6411 lineList
->after_insert(prevSibLine
, line
);
6414 // New line is going before the other lines
6415 lineList
->push_front(line
);
6416 prevSibLine
= lineList
->begin();
6419 prevSibLine
->NoteFrameAdded(newFrame
);
6420 // We're adding inline content to prevSibLine, so we need to mark it
6421 // dirty, ensure its textruns are recomputed, and possibly do the same
6422 // to its previous line since that line may be able to pull content up.
6423 MarkLineDirty(prevSibLine
, lineList
);
6426 aPrevSibling
= newFrame
;
6430 MOZ_ASSERT(aFrameList
.IsEmpty());
6435 nsContainerFrame
* nsBlockFrame::GetRubyContentPseudoFrame() {
6436 auto* firstChild
= PrincipalChildList().FirstChild();
6437 if (firstChild
&& firstChild
->IsRubyFrame() &&
6438 firstChild
->Style()->GetPseudoType() ==
6439 PseudoStyleType::blockRubyContent
) {
6440 return static_cast<nsContainerFrame
*>(firstChild
);
6445 nsContainerFrame
* nsBlockFrame::GetContentInsertionFrame() {
6446 // 'display:block ruby' use the inner (Ruby) frame for insertions.
6447 if (auto* rubyContentPseudoFrame
= GetRubyContentPseudoFrame()) {
6448 return rubyContentPseudoFrame
;
6453 void nsBlockFrame::AppendDirectlyOwnedAnonBoxes(
6454 nsTArray
<OwnedAnonBox
>& aResult
) {
6455 if (auto* rubyContentPseudoFrame
= GetRubyContentPseudoFrame()) {
6456 aResult
.AppendElement(OwnedAnonBox(rubyContentPseudoFrame
));
6460 void nsBlockFrame::RemoveFloatFromFloatCache(nsIFrame
* aFloat
) {
6461 // Find which line contains the float, so we can update
6463 for (auto& line
: Lines()) {
6464 if (line
.IsInline() && line
.RemoveFloat(aFloat
)) {
6470 void nsBlockFrame::RemoveFloat(nsIFrame
* aFloat
) {
6473 // Floats live in floats list, pushed floats list, or overflow out-of-flow
6476 GetChildList(FrameChildListID::Float
).ContainsFrame(aFloat
) ||
6477 GetChildList(FrameChildListID::PushedFloats
).ContainsFrame(aFloat
) ||
6478 GetChildList(FrameChildListID::OverflowOutOfFlow
)
6479 .ContainsFrame(aFloat
),
6480 "aFloat is not our child or on an unexpected frame list");
6482 bool didStartRemovingFloat
= false;
6483 if (nsFrameList
* floats
= GetFloats()) {
6484 didStartRemovingFloat
= true;
6485 if (floats
->StartRemoveFrame(aFloat
)) {
6486 if (floats
->IsEmpty()) {
6487 StealFloats()->Delete(PresShell());
6493 if (nsFrameList
* pushedFloats
= GetPushedFloats()) {
6495 if (didStartRemovingFloat
) {
6496 found
= pushedFloats
->ContinueRemoveFrame(aFloat
);
6498 didStartRemovingFloat
= true;
6499 found
= pushedFloats
->StartRemoveFrame(aFloat
);
6502 if (pushedFloats
->IsEmpty()) {
6503 StealPushedFloats()->Delete(PresShell());
6510 nsAutoOOFFrameList
oofs(this);
6511 if (didStartRemovingFloat
? oofs
.mList
.ContinueRemoveFrame(aFloat
)
6512 : oofs
.mList
.StartRemoveFrame(aFloat
)) {
6518 void nsBlockFrame::DoRemoveOutOfFlowFrame(DestroyContext
& aContext
,
6520 // The containing block is always the parent of aFrame.
6521 nsBlockFrame
* block
= (nsBlockFrame
*)aFrame
->GetParent();
6523 // Remove aFrame from the appropriate list.
6524 if (aFrame
->IsAbsolutelyPositioned()) {
6525 // This also deletes the next-in-flows
6526 block
->GetAbsoluteContainingBlock()->RemoveFrame(
6527 aContext
, FrameChildListID::Absolute
, aFrame
);
6529 // First remove aFrame's next-in-flows.
6530 if (nsIFrame
* nif
= aFrame
->GetNextInFlow()) {
6531 nif
->GetParent()->DeleteNextInFlowChild(aContext
, nif
, false);
6533 // Now remove aFrame from its child list and Destroy it.
6534 block
->RemoveFloatFromFloatCache(aFrame
);
6535 block
->RemoveFloat(aFrame
);
6536 aFrame
->Destroy(aContext
);
6541 * This helps us iterate over the list of all normal + overflow lines
6543 void nsBlockFrame::TryAllLines(nsLineList::iterator
* aIterator
,
6544 nsLineList::iterator
* aStartIterator
,
6545 nsLineList::iterator
* aEndIterator
,
6546 bool* aInOverflowLines
,
6547 FrameLines
** aOverflowLines
) {
6548 if (*aIterator
== *aEndIterator
) {
6549 if (!*aInOverflowLines
) {
6550 // Try the overflow lines
6551 *aInOverflowLines
= true;
6552 FrameLines
* lines
= GetOverflowLines();
6554 *aStartIterator
= lines
->mLines
.begin();
6555 *aIterator
= *aStartIterator
;
6556 *aEndIterator
= lines
->mLines
.end();
6557 *aOverflowLines
= lines
;
6563 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame
* aFrame
,
6565 : mFrame(aFrame
), mLine(aLine
), mLineList(&aFrame
->mLines
) {
6566 // This will assert if aLine isn't in mLines of aFrame:
6567 DebugOnly
<bool> check
= aLine
== mFrame
->LinesBegin();
6570 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame
* aFrame
,
6575 mLineList(aInOverflow
? &aFrame
->GetOverflowLines()->mLines
6576 : &aFrame
->mLines
) {}
6578 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame
* aFrame
,
6579 bool* aFoundValidLine
)
6580 : mFrame(aFrame
), mLineList(&aFrame
->mLines
) {
6581 mLine
= aFrame
->LinesBegin();
6582 *aFoundValidLine
= FindValidLine();
6585 static bool AnonymousBoxIsBFC(const ComputedStyle
* aStyle
) {
6586 switch (aStyle
->GetPseudoType()) {
6587 case PseudoStyleType::fieldsetContent
:
6588 case PseudoStyleType::columnContent
:
6589 case PseudoStyleType::buttonContent
:
6590 case PseudoStyleType::cellContent
:
6591 case PseudoStyleType::scrolledContent
:
6592 case PseudoStyleType::anonymousItem
:
6599 static bool StyleEstablishesBFC(const ComputedStyle
* aStyle
) {
6600 // paint/layout containment boxes and multi-column containers establish an
6601 // independent formatting context.
6602 // https://drafts.csswg.org/css-contain/#containment-paint
6603 // https://drafts.csswg.org/css-contain/#containment-layout
6604 // https://drafts.csswg.org/css-align/#distribution-block
6605 // https://drafts.csswg.org/css-multicol/#columns
6606 const auto* disp
= aStyle
->StyleDisplay();
6607 return disp
->IsContainPaint() || disp
->IsContainLayout() ||
6608 disp
->DisplayInside() == StyleDisplayInside::FlowRoot
||
6609 disp
->IsAbsolutelyPositionedStyle() || disp
->IsFloatingStyle() ||
6610 aStyle
->StylePosition()->mAlignContent
.primary
!=
6611 StyleAlignFlags::NORMAL
||
6612 aStyle
->IsRootElementStyle() || AnonymousBoxIsBFC(aStyle
);
6615 static bool EstablishesBFC(const nsBlockFrame
* aFrame
) {
6616 if (aFrame
->HasAnyClassFlag(LayoutFrameClassFlags::BlockFormattingContext
)) {
6620 if (nsIFrame
* parent
= aFrame
->GetParent()) {
6621 if (parent
->IsFieldSetFrame()) {
6622 // A rendered legend always establishes a new formatting context, and so
6623 // does the fieldset content frame, so we can just return true here.
6624 // https://html.spec.whatwg.org/#rendered-legend
6628 const auto wm
= aFrame
->GetWritingMode();
6629 const auto parentWM
= parent
->GetWritingMode();
6630 if (wm
.GetBlockDir() != parentWM
.GetBlockDir() ||
6631 wm
.IsVerticalSideways() != parentWM
.IsVerticalSideways()) {
6632 // If a box has a different writing-mode value than its containing block
6633 // [...] if the box is a block container, then it establishes a new block
6634 // formatting context.
6635 // https://drafts.csswg.org/css-writing-modes/#block-flow
6640 if (aFrame
->IsColumnSpan()) {
6644 if (aFrame
->IsSuppressedScrollableBlockForPrint()) {
6648 const auto* style
= aFrame
->Style();
6649 if (style
->GetPseudoType() == PseudoStyleType::marker
) {
6650 if (aFrame
->GetParent() &&
6651 aFrame
->GetParent()->StyleList()->mListStylePosition
==
6652 StyleListStylePosition::Outside
) {
6653 // An outside ::marker needs to be an independent formatting context
6654 // to avoid being influenced by the float manager etc.
6658 return StyleEstablishesBFC(style
);
6661 void nsBlockFrame::DidSetComputedStyle(ComputedStyle
* aOldStyle
) {
6662 nsContainerFrame::DidSetComputedStyle(aOldStyle
);
6667 const bool isBFC
= EstablishesBFC(this);
6668 if (HasAnyStateBits(NS_BLOCK_BFC
) != isBFC
) {
6669 if (MaybeHasFloats()) {
6670 // If the frame contains floats, this update may change their float
6671 // manager. Be safe by dirtying all descendant lines of the nearest
6672 // ancestor's float manager.
6673 RemoveStateBits(NS_BLOCK_BFC
);
6674 MarkSameFloatManagerLinesDirty(this);
6676 AddOrRemoveStateBits(NS_BLOCK_BFC
, isBFC
);
6680 void nsBlockFrame::UpdateFirstLetterStyle(ServoRestyleState
& aRestyleState
) {
6681 nsIFrame
* letterFrame
= GetFirstLetter();
6686 // Figure out what the right style parent is. This needs to match
6687 // nsCSSFrameConstructor::CreateLetterFrame.
6688 nsIFrame
* inFlowFrame
= letterFrame
;
6689 if (inFlowFrame
->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW
)) {
6690 inFlowFrame
= inFlowFrame
->GetPlaceholderFrame();
6692 nsIFrame
* styleParent
= CorrectStyleParentFrame(inFlowFrame
->GetParent(),
6693 PseudoStyleType::firstLetter
);
6694 ComputedStyle
* parentStyle
= styleParent
->Style();
6695 RefPtr
<ComputedStyle
> firstLetterStyle
=
6696 aRestyleState
.StyleSet().ResolvePseudoElementStyle(
6697 *mContent
->AsElement(), PseudoStyleType::firstLetter
, nullptr,
6699 // Note that we don't need to worry about changehints for the continuation
6700 // styles: those will be handled by the styleParent already.
6701 RefPtr
<ComputedStyle
> continuationStyle
=
6702 aRestyleState
.StyleSet().ResolveStyleForFirstLetterContinuation(
6704 UpdateStyleOfOwnedChildFrame(letterFrame
, firstLetterStyle
, aRestyleState
,
6705 Some(continuationStyle
.get()));
6707 // We also want to update the style on the textframe inside the first-letter.
6708 // We don't need to compute a changehint for this, though, since any changes
6709 // to it are handled by the first-letter anyway.
6710 nsIFrame
* textFrame
= letterFrame
->PrincipalChildList().FirstChild();
6711 RefPtr
<ComputedStyle
> firstTextStyle
=
6712 aRestyleState
.StyleSet().ResolveStyleForText(textFrame
->GetContent(),
6714 textFrame
->SetComputedStyle(firstTextStyle
);
6716 // We don't need to update style for textFrame's continuations: it's already
6717 // set up to inherit from parentStyle, which is what we want.
6720 static nsIFrame
* FindChildContaining(nsBlockFrame
* aFrame
,
6721 nsIFrame
* aFindFrame
) {
6722 NS_ASSERTION(aFrame
, "must have frame");
6725 nsIFrame
* block
= aFrame
;
6727 child
= nsLayoutUtils::FindChildContainingDescendant(block
, aFindFrame
);
6731 block
= block
->GetNextContinuation();
6736 if (!child
->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW
)) {
6739 aFindFrame
= child
->GetPlaceholderFrame();
6745 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame
* aFrame
,
6746 nsIFrame
* aFindFrame
,
6747 bool* aFoundValidLine
)
6748 : mFrame(aFrame
), mLineList(&aFrame
->mLines
) {
6749 *aFoundValidLine
= false;
6751 nsIFrame
* child
= FindChildContaining(aFrame
, aFindFrame
);
6756 LineIterator line_end
= aFrame
->LinesEnd();
6757 mLine
= aFrame
->LinesBegin();
6758 if (mLine
!= line_end
&& mLine
.next() == line_end
&&
6759 !aFrame
->HasOverflowLines()) {
6760 // The block has a single line - that must be it!
6761 *aFoundValidLine
= true;
6765 // Try to use the cursor if it exists, otherwise fall back to the first line
6766 if (nsLineBox
* const cursor
= aFrame
->GetLineCursorForQuery()) {
6768 // Perform a simultaneous forward and reverse search starting from the
6770 nsBlockFrame::LineIterator line
= aFrame
->LinesBeginFrom(cursor
);
6771 nsBlockFrame::ReverseLineIterator rline
= aFrame
->LinesRBeginFrom(cursor
);
6772 nsBlockFrame::ReverseLineIterator rline_end
= aFrame
->LinesREnd();
6773 // rline is positioned on the line containing 'cursor', so it's not
6774 // rline_end. So we can safely increment it (i.e. move it to one line
6775 // earlier) to start searching there.
6777 while (line
!= line_end
|| rline
!= rline_end
) {
6778 if (line
!= line_end
) {
6779 if (line
->Contains(child
)) {
6785 if (rline
!= rline_end
) {
6786 if (rline
->Contains(child
)) {
6793 if (mLine
!= line_end
) {
6794 *aFoundValidLine
= true;
6795 if (mLine
!= cursor
) {
6796 aFrame
->SetProperty(nsBlockFrame::LineCursorPropertyQuery(), mLine
);
6801 for (mLine
= aFrame
->LinesBegin(); mLine
!= line_end
; ++mLine
) {
6802 if (mLine
->Contains(child
)) {
6803 *aFoundValidLine
= true;
6808 // Didn't find the line
6809 MOZ_ASSERT(mLine
== line_end
, "mLine should be line_end at this point");
6811 // If we reach here, it means that we have not been able to find the
6812 // desired frame in our in-flow lines. So we should start looking at
6813 // our overflow lines. In order to do that, we set mLine to the end
6814 // iterator so that FindValidLine starts to look at overflow lines,
6817 if (!FindValidLine()) {
6822 if (mLine
->Contains(child
)) {
6823 *aFoundValidLine
= true;
6829 nsBlockFrame::LineIterator
nsBlockInFlowLineIterator::End() {
6830 return mLineList
->end();
6833 bool nsBlockInFlowLineIterator::IsLastLineInList() {
6834 LineIterator end
= End();
6835 return mLine
!= end
&& mLine
.next() == end
;
6838 bool nsBlockInFlowLineIterator::Next() {
6840 return FindValidLine();
6843 bool nsBlockInFlowLineIterator::Prev() {
6844 LineIterator begin
= mLineList
->begin();
6845 if (mLine
!= begin
) {
6849 bool currentlyInOverflowLines
= GetInOverflow();
6851 if (currentlyInOverflowLines
) {
6852 mLineList
= &mFrame
->mLines
;
6853 mLine
= mLineList
->end();
6854 if (mLine
!= mLineList
->begin()) {
6859 mFrame
= static_cast<nsBlockFrame
*>(mFrame
->GetPrevInFlow());
6863 nsBlockFrame::FrameLines
* overflowLines
= mFrame
->GetOverflowLines();
6864 if (overflowLines
) {
6865 mLineList
= &overflowLines
->mLines
;
6866 mLine
= mLineList
->end();
6867 NS_ASSERTION(mLine
!= mLineList
->begin(), "empty overflow line list?");
6872 currentlyInOverflowLines
= !currentlyInOverflowLines
;
6876 bool nsBlockInFlowLineIterator::FindValidLine() {
6877 LineIterator end
= mLineList
->end();
6881 bool currentlyInOverflowLines
= GetInOverflow();
6883 if (currentlyInOverflowLines
) {
6884 mFrame
= static_cast<nsBlockFrame
*>(mFrame
->GetNextInFlow());
6888 mLineList
= &mFrame
->mLines
;
6889 mLine
= mLineList
->begin();
6890 if (mLine
!= mLineList
->end()) {
6894 nsBlockFrame::FrameLines
* overflowLines
= mFrame
->GetOverflowLines();
6895 if (overflowLines
) {
6896 mLineList
= &overflowLines
->mLines
;
6897 mLine
= mLineList
->begin();
6898 NS_ASSERTION(mLine
!= mLineList
->end(), "empty overflow line list?");
6902 currentlyInOverflowLines
= !currentlyInOverflowLines
;
6906 // This function removes aDeletedFrame and all its continuations. It
6907 // is optimized for deleting a whole series of frames. The easy
6908 // implementation would invoke itself recursively on
6909 // aDeletedFrame->GetNextContinuation, then locate the line containing
6910 // aDeletedFrame and remove aDeletedFrame from that line. But here we
6911 // start by locating aDeletedFrame and then scanning from that point
6912 // on looking for continuations.
6913 void nsBlockFrame::DoRemoveFrame(DestroyContext
& aContext
,
6914 nsIFrame
* aDeletedFrame
, uint32_t aFlags
) {
6915 // We use the line cursor to attempt to optimize removal, but must ensure
6916 // it is cleared if lines change such that it may become invalid.
6918 if (aDeletedFrame
->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW
|
6919 NS_FRAME_IS_OVERFLOW_CONTAINER
)) {
6920 if (!aDeletedFrame
->GetPrevInFlow()) {
6921 NS_ASSERTION(aDeletedFrame
->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW
),
6922 "Expected out-of-flow frame");
6923 DoRemoveOutOfFlowFrame(aContext
, aDeletedFrame
);
6925 // FIXME(emilio): aContext is lost here, maybe it's not a big deal?
6926 nsContainerFrame::DeleteNextInFlowChild(aContext
, aDeletedFrame
,
6927 (aFlags
& FRAMES_ARE_EMPTY
) != 0);
6932 // Find the line that contains deletedFrame. Start from the line cursor
6933 // (if available) and search to the end of the normal line list, then
6934 // from the start to the line cursor, and last the overflow lines.
6935 nsLineList::iterator line_start
= mLines
.begin(), line_end
= mLines
.end();
6936 nsLineList::iterator line
= line_start
;
6939 if (nsLineBox
* cursor
= GetLineCursorForDisplay()) {
6940 for (line
.SetPosition(cursor
); line
!= line_end
; ++line
) {
6941 if (line
->Contains(aDeletedFrame
)) {
6947 // Setup for a shorter TryAllLines normal line search to avoid searching
6948 // the [cursor .. line_end] range again.
6950 line_end
.SetPosition(cursor
);
6954 FrameLines
* overflowLines
= nullptr;
6955 bool searchingOverflowList
= false;
6957 // Make sure we look in the overflow lines even if the normal line
6959 TryAllLines(&line
, &line_start
, &line_end
, &searchingOverflowList
,
6961 while (line
!= line_end
) {
6962 if (line
->Contains(aDeletedFrame
)) {
6966 TryAllLines(&line
, &line_start
, &line_end
, &searchingOverflowList
,
6969 if (!searchingOverflowList
&& (GetStateBits() & NS_BLOCK_HAS_LINE_CURSOR
)) {
6970 // Restore line_end since we shortened the search to the cursor.
6971 line_end
= mLines
.end();
6972 // Clear our line cursors, since our normal line list may change.
6977 if (line
== line_end
) {
6978 NS_ERROR("can't find deleted frame in lines");
6982 if (!(aFlags
& FRAMES_ARE_EMPTY
)) {
6983 if (line
!= line_start
) {
6984 line
.prev()->MarkDirty();
6985 line
.prev()->SetInvalidateTextRuns(true);
6986 } else if (searchingOverflowList
&& !mLines
.empty()) {
6987 mLines
.back()->MarkDirty();
6988 mLines
.back()->SetInvalidateTextRuns(true);
6992 while (line
!= line_end
&& aDeletedFrame
) {
6993 MOZ_ASSERT(this == aDeletedFrame
->GetParent(), "messed up delete code");
6994 MOZ_ASSERT(line
->Contains(aDeletedFrame
), "frame not in line");
6996 if (!(aFlags
& FRAMES_ARE_EMPTY
)) {
6998 line
->SetInvalidateTextRuns(true);
7001 // If the frame being deleted is the last one on the line then
7002 // optimize away the line->Contains(next-in-flow) call below.
7003 bool isLastFrameOnLine
= 1 == line
->GetChildCount();
7004 if (!isLastFrameOnLine
) {
7005 LineIterator next
= line
.next();
7006 nsIFrame
* lastFrame
=
7008 ? next
->mFirstChild
->GetPrevSibling()
7009 : (searchingOverflowList
? overflowLines
->mFrames
.LastChild()
7010 : mFrames
.LastChild());
7011 NS_ASSERTION(next
== line_end
|| lastFrame
== line
->LastChild(),
7012 "unexpected line frames");
7013 isLastFrameOnLine
= lastFrame
== aDeletedFrame
;
7016 // Remove aDeletedFrame from the line
7017 if (line
->mFirstChild
== aDeletedFrame
) {
7018 // We should be setting this to null if aDeletedFrame
7019 // is the only frame on the line. HOWEVER in that case
7020 // we will be removing the line anyway, see below.
7021 line
->mFirstChild
= aDeletedFrame
->GetNextSibling();
7024 // Hmm, this won't do anything if we're removing a frame in the first
7025 // overflow line... Hopefully doesn't matter
7027 if (line
!= line_end
&& !line
->IsBlock()) {
7028 // Since we just removed a frame that follows some inline
7029 // frames, we need to reflow the previous line.
7034 // Take aDeletedFrame out of the sibling list. Note that
7035 // prevSibling will only be nullptr when we are deleting the very
7036 // first frame in the main or overflow list.
7037 if (searchingOverflowList
) {
7038 overflowLines
->mFrames
.RemoveFrame(aDeletedFrame
);
7040 mFrames
.RemoveFrame(aDeletedFrame
);
7043 // Update the child count of the line to be accurate
7044 line
->NoteFrameRemoved(aDeletedFrame
);
7046 // Destroy frame; capture its next continuation first in case we need
7047 // to destroy that too.
7048 nsIFrame
* deletedNextContinuation
=
7049 (aFlags
& REMOVE_FIXED_CONTINUATIONS
)
7050 ? aDeletedFrame
->GetNextContinuation()
7051 : aDeletedFrame
->GetNextInFlow();
7052 #ifdef NOISY_REMOVE_FRAME
7053 printf("DoRemoveFrame: %s line=%p frame=",
7054 searchingOverflowList
? "overflow" : "normal", line
.get());
7055 aDeletedFrame
->ListTag(stdout
);
7056 printf(" prevSibling=%p deletedNextContinuation=%p\n",
7057 aDeletedFrame
->GetPrevSibling(), deletedNextContinuation
);
7060 // If next-in-flow is an overflow container, must remove it first.
7061 // FIXME: Can we do this unconditionally?
7062 if (deletedNextContinuation
&& deletedNextContinuation
->HasAnyStateBits(
7063 NS_FRAME_IS_OVERFLOW_CONTAINER
)) {
7064 deletedNextContinuation
->GetParent()->DeleteNextInFlowChild(
7065 aContext
, deletedNextContinuation
, false);
7066 deletedNextContinuation
= nullptr;
7069 aDeletedFrame
->Destroy(aContext
);
7070 aDeletedFrame
= deletedNextContinuation
;
7072 bool haveAdvancedToNextLine
= false;
7073 // If line is empty, remove it now.
7074 if (0 == line
->GetChildCount()) {
7075 #ifdef NOISY_REMOVE_FRAME
7076 printf("DoRemoveFrame: %s line=%p became empty so it will be removed\n",
7077 searchingOverflowList
? "overflow" : "normal", line
.get());
7079 nsLineBox
* cur
= line
;
7080 if (!searchingOverflowList
) {
7081 line
= mLines
.erase(line
);
7083 // Invalidate the space taken up by the line.
7084 // XXX We need to do this if we're removing a frame as a result of
7085 // a call to RemoveFrame(), but we may not need to do this in all
7087 #ifdef NOISY_BLOCK_INVALIDATE
7088 nsRect
inkOverflow(cur
->InkOverflowRect());
7089 printf("%p invalidate 10 (%d, %d, %d, %d)\n", this, inkOverflow
.x
,
7090 inkOverflow
.y
, inkOverflow
.width
, inkOverflow
.height
);
7093 line
= overflowLines
->mLines
.erase(line
);
7094 if (overflowLines
->mLines
.empty()) {
7095 DestroyOverflowLines();
7096 overflowLines
= nullptr;
7097 // We just invalidated our iterators. Since we were in
7098 // the overflow lines list, which is now empty, set them
7099 // so we're at the end of the regular line list.
7100 line_start
= mLines
.begin();
7101 line_end
= mLines
.end();
7107 // If we're removing a line, ReflowDirtyLines isn't going to
7108 // know that it needs to slide lines unless something is marked
7109 // dirty. So mark the previous margin of the next line dirty if
7111 if (line
!= line_end
) {
7112 line
->MarkPreviousMarginDirty();
7114 haveAdvancedToNextLine
= true;
7116 // Make the line that just lost a frame dirty, and advance to
7118 if (!deletedNextContinuation
|| isLastFrameOnLine
||
7119 !line
->Contains(deletedNextContinuation
)) {
7122 haveAdvancedToNextLine
= true;
7126 if (deletedNextContinuation
) {
7127 // See if we should keep looking in the current flow's line list.
7128 if (deletedNextContinuation
->GetParent() != this) {
7129 // The deceased frames continuation is not a child of the
7130 // current block. So break out of the loop so that we advance
7131 // to the next parent.
7133 // If we have a continuation in a different block then all bets are
7134 // off regarding whether we are deleting frames without actual content,
7135 // so don't propagate FRAMES_ARE_EMPTY any further.
7136 aFlags
&= ~FRAMES_ARE_EMPTY
;
7140 // If we advanced to the next line then check if we should switch to the
7141 // overflow line list.
7142 if (haveAdvancedToNextLine
) {
7143 if (line
!= line_end
&& !searchingOverflowList
&&
7144 !line
->Contains(deletedNextContinuation
)) {
7145 // We have advanced to the next *normal* line but the next-in-flow
7146 // is not there - force a switch to the overflow line list.
7150 TryAllLines(&line
, &line_start
, &line_end
, &searchingOverflowList
,
7152 #ifdef NOISY_REMOVE_FRAME
7153 printf("DoRemoveFrame: now on %s line=%p\n",
7154 searchingOverflowList
? "overflow" : "normal", line
.get());
7160 if (!(aFlags
& FRAMES_ARE_EMPTY
) && line
.next() != line_end
) {
7161 line
.next()->MarkDirty();
7162 line
.next()->SetInvalidateTextRuns(true);
7167 VerifyOverflowSituation();
7170 // Advance to next flow block if the frame has more continuations.
7171 if (!aDeletedFrame
) {
7174 nsBlockFrame
* nextBlock
= do_QueryFrame(aDeletedFrame
->GetParent());
7175 NS_ASSERTION(nextBlock
, "Our child's continuation's parent is not a block?");
7176 uint32_t flags
= (aFlags
& REMOVE_FIXED_CONTINUATIONS
);
7177 nextBlock
->DoRemoveFrame(aContext
, aDeletedFrame
, flags
);
7180 static bool FindBlockLineFor(nsIFrame
* aChild
, nsLineList::iterator aBegin
,
7181 nsLineList::iterator aEnd
,
7182 nsLineList::iterator
* aResult
) {
7183 MOZ_ASSERT(aChild
->IsBlockOutside());
7184 for (nsLineList::iterator line
= aBegin
; line
!= aEnd
; ++line
) {
7185 MOZ_ASSERT(line
->GetChildCount() > 0);
7186 if (line
->IsBlock() && line
->mFirstChild
== aChild
) {
7187 MOZ_ASSERT(line
->GetChildCount() == 1);
7195 static bool FindInlineLineFor(nsIFrame
* aChild
, const nsFrameList
& aFrameList
,
7196 nsLineList::iterator aBegin
,
7197 nsLineList::iterator aEnd
,
7198 nsLineList::iterator
* aResult
) {
7199 MOZ_ASSERT(!aChild
->IsBlockOutside());
7200 for (nsLineList::iterator line
= aBegin
; line
!= aEnd
; ++line
) {
7201 MOZ_ASSERT(line
->GetChildCount() > 0);
7202 if (!line
->IsBlock()) {
7203 // Optimize by comparing the line's last child first.
7204 nsLineList::iterator next
= line
.next();
7205 if (aChild
== (next
== aEnd
? aFrameList
.LastChild()
7206 : next
->mFirstChild
->GetPrevSibling()) ||
7207 line
->Contains(aChild
)) {
7216 static bool FindLineFor(nsIFrame
* aChild
, const nsFrameList
& aFrameList
,
7217 nsLineList::iterator aBegin
, nsLineList::iterator aEnd
,
7218 nsLineList::iterator
* aResult
) {
7219 return aChild
->IsBlockOutside()
7220 ? FindBlockLineFor(aChild
, aBegin
, aEnd
, aResult
)
7221 : FindInlineLineFor(aChild
, aFrameList
, aBegin
, aEnd
, aResult
);
7224 void nsBlockFrame::StealFrame(nsIFrame
* aChild
) {
7225 MOZ_ASSERT(aChild
->GetParent() == this);
7227 if (aChild
->IsFloating()) {
7228 RemoveFloat(aChild
);
7232 if (MaybeStealOverflowContainerFrame(aChild
)) {
7236 MOZ_ASSERT(!aChild
->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW
));
7238 nsLineList::iterator line
;
7239 if (FindLineFor(aChild
, mFrames
, mLines
.begin(), mLines
.end(), &line
)) {
7240 RemoveFrameFromLine(aChild
, line
, mFrames
, mLines
);
7242 FrameLines
* overflowLines
= GetOverflowLines();
7243 DebugOnly
<bool> found
;
7244 found
= FindLineFor(aChild
, overflowLines
->mFrames
,
7245 overflowLines
->mLines
.begin(),
7246 overflowLines
->mLines
.end(), &line
);
7247 MOZ_ASSERT(found
, "Why can't we find aChild in our overflow lines?");
7248 RemoveFrameFromLine(aChild
, line
, overflowLines
->mFrames
,
7249 overflowLines
->mLines
);
7250 if (overflowLines
->mLines
.empty()) {
7251 DestroyOverflowLines();
7256 void nsBlockFrame::RemoveFrameFromLine(nsIFrame
* aChild
,
7257 nsLineList::iterator aLine
,
7258 nsFrameList
& aFrameList
,
7259 nsLineList
& aLineList
) {
7260 aFrameList
.RemoveFrame(aChild
);
7261 if (aChild
== aLine
->mFirstChild
) {
7262 aLine
->mFirstChild
= aChild
->GetNextSibling();
7264 aLine
->NoteFrameRemoved(aChild
);
7265 if (aLine
->GetChildCount() > 0) {
7268 // The line became empty - destroy it.
7269 nsLineBox
* lineBox
= aLine
;
7270 aLine
= aLineList
.erase(aLine
);
7271 if (aLine
!= aLineList
.end()) {
7272 aLine
->MarkPreviousMarginDirty();
7274 FreeLineBox(lineBox
);
7279 void nsBlockFrame::DeleteNextInFlowChild(DestroyContext
& aContext
,
7280 nsIFrame
* aNextInFlow
,
7281 bool aDeletingEmptyFrames
) {
7282 MOZ_ASSERT(aNextInFlow
->GetPrevInFlow(), "bad next-in-flow");
7284 if (aNextInFlow
->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW
|
7285 NS_FRAME_IS_OVERFLOW_CONTAINER
)) {
7286 nsContainerFrame::DeleteNextInFlowChild(aContext
, aNextInFlow
,
7287 aDeletingEmptyFrames
);
7290 if (aDeletingEmptyFrames
) {
7291 nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(aNextInFlow
);
7294 DoRemoveFrame(aContext
, aNextInFlow
,
7295 aDeletingEmptyFrames
? FRAMES_ARE_EMPTY
: 0);
7299 const nsStyleText
* nsBlockFrame::StyleTextForLineLayout() {
7300 // Return the pointer to an unmodified style text
7304 void nsBlockFrame::ReflowFloat(BlockReflowState
& aState
, ReflowInput
& aFloatRI
,
7306 nsReflowStatus
& aReflowStatus
) {
7307 MOZ_ASSERT(aReflowStatus
.IsEmpty(),
7308 "Caller should pass a fresh reflow status!");
7309 MOZ_ASSERT(aFloat
->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW
),
7310 "aFloat must be an out-of-flow frame");
7312 WritingMode wm
= aState
.mReflowInput
.GetWritingMode();
7314 // Setup a block reflow context to reflow the float.
7315 nsBlockReflowContext
brc(aState
.mPresContext
, aState
.mReflowInput
);
7317 nsIFrame
* clearanceFrame
= nullptr;
7319 CollapsingMargin margin
;
7320 bool mayNeedRetry
= false;
7321 aFloatRI
.mDiscoveredClearance
= nullptr;
7322 // Only first in flow gets a block-start margin.
7323 if (!aFloat
->GetPrevInFlow()) {
7324 brc
.ComputeCollapsedBStartMargin(aFloatRI
, &margin
, clearanceFrame
,
7327 if (mayNeedRetry
&& !clearanceFrame
) {
7328 aFloatRI
.mDiscoveredClearance
= &clearanceFrame
;
7329 // We don't need to push the float manager state because the the block
7330 // has its own float manager that will be destroyed and recreated
7334 // When reflowing a float, aSpace argument doesn't matter because we pass
7335 // nullptr to aLine and we don't call nsBlockReflowContext::PlaceBlock()
7337 brc
.ReflowBlock(LogicalRect(wm
), true, margin
, 0, nullptr, aFloatRI
,
7338 aReflowStatus
, aState
);
7339 } while (clearanceFrame
);
7341 if (aFloat
->IsLetterFrame()) {
7342 // We never split floating first letters; an incomplete status for such
7343 // frames simply means that there is more content to be reflowed on the
7345 if (aReflowStatus
.IsIncomplete()) {
7346 aReflowStatus
.Reset();
7350 NS_ASSERTION(aReflowStatus
.IsFullyComplete() ||
7351 aFloatRI
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
,
7352 "The status can only be incomplete or overflow-incomplete if "
7353 "the available block-size is constrained!");
7355 if (aReflowStatus
.NextInFlowNeedsReflow()) {
7356 aState
.mReflowStatus
.SetNextInFlowNeedsReflow();
7359 const ReflowOutput
& metrics
= brc
.GetMetrics();
7361 // Set the rect, make sure the view is properly sized and positioned,
7362 // and tell the frame we're done reflowing it
7363 // XXXldb This seems like the wrong place to be doing this -- shouldn't
7364 // we be doing this in BlockReflowState::FlowAndPlaceFloat after
7365 // we've positioned the float, and shouldn't we be doing the equivalent
7366 // of |PlaceFrameView| here?
7367 WritingMode metricsWM
= metrics
.GetWritingMode();
7368 aFloat
->SetSize(metricsWM
, metrics
.Size(metricsWM
));
7369 if (aFloat
->HasView()) {
7370 nsContainerFrame::SyncFrameViewAfterReflow(
7371 aState
.mPresContext
, aFloat
, aFloat
->GetView(), metrics
.InkOverflow(),
7372 ReflowChildFlags::NoMoveView
);
7374 aFloat
->DidReflow(aState
.mPresContext
, &aFloatRI
);
7377 StyleClear
nsBlockFrame::FindTrailingClear() {
7378 for (nsBlockFrame
* b
= this; b
;
7379 b
= static_cast<nsBlockFrame
*>(b
->GetPrevInFlow())) {
7380 auto endLine
= b
->LinesRBegin();
7381 if (endLine
!= b
->LinesREnd()) {
7382 return endLine
->FloatClearTypeAfter();
7385 return StyleClear::None
;
7388 void nsBlockFrame::ReflowPushedFloats(BlockReflowState
& aState
,
7389 OverflowAreas
& aOverflowAreas
) {
7390 // Pushed floats live at the start of our float list; see comment
7391 // above nsBlockFrame::DrainPushedFloats.
7392 nsFrameList
* floats
= GetFloats();
7393 nsIFrame
* f
= floats
? floats
->FirstChild() : nullptr;
7394 nsIFrame
* prev
= nullptr;
7395 while (f
&& f
->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT
)) {
7396 MOZ_ASSERT(prev
== f
->GetPrevSibling());
7397 // When we push a first-continuation float in a non-initial reflow,
7398 // it's possible that we end up with two continuations with the same
7399 // parent. This happens if, on the previous reflow of the block or
7400 // a previous reflow of the line containing the block, the float was
7401 // split between continuations A and B of the parent, but on the
7402 // current reflow, none of the float can fit in A.
7404 // When this happens, we might even have the two continuations
7405 // out-of-order due to the management of the pushed floats. In
7406 // particular, if the float's placeholder was in a pushed line that
7407 // we reflowed before it was pushed, and we split the float during
7408 // that reflow, we might have the continuation of the float before
7409 // the float itself. (In the general case, however, it's correct
7410 // for floats in the pushed floats list to come before floats
7411 // anchored in pushed lines; however, in this case it's wrong. We
7412 // should probably find a way to fix it somehow, since it leads to
7413 // incorrect layout in some cases.)
7415 // When we have these out-of-order continuations, we might hit the
7416 // next-continuation before the previous-continuation. When that
7417 // happens, just push it. When we reflow the next continuation,
7418 // we'll either pull all of its content back and destroy it (by
7419 // calling DeleteNextInFlowChild), or nsBlockFrame::SplitFloat will
7420 // pull it out of its current position and push it again (and
7421 // potentially repeat this cycle for the next continuation, although
7422 // hopefully then they'll be in the right order).
7424 // We should also need this code for the in-order case if the first
7425 // continuation of a float gets moved across more than one
7426 // continuation of the containing block. In this case we'd manage
7427 // to push the second continuation without this check, but not the
7429 nsIFrame
* prevContinuation
= f
->GetPrevContinuation();
7430 if (prevContinuation
&& prevContinuation
->GetParent() == f
->GetParent()) {
7431 floats
->RemoveFrame(f
);
7432 if (floats
->IsEmpty()) {
7433 StealFloats()->Delete(PresShell());
7436 aState
.AppendPushedFloatChain(f
);
7438 // The floats list becomes empty after removing |f|. Bail out.
7442 // Even if we think |floats| is valid, AppendPushedFloatChain() can also
7443 // push |f|'s next-in-flows in our floats list to our pushed floats list.
7444 // If all the floats in the floats list are pushed, the floats list will
7445 // be deleted, and |floats| will be stale and poisoned. Therefore, we need
7446 // to get the floats list again to check its validity.
7447 floats
= GetFloats();
7452 f
= !prev
? floats
->FirstChild() : prev
->GetNextSibling();
7456 // Always call FlowAndPlaceFloat; we might need to place this float if it
7457 // didn't belong to this block the last time it was reflowed. Note that if
7458 // the float doesn't get placed, we don't consider its overflow areas.
7459 // (Not-getting-placed means it didn't fit and we pushed it instead of
7460 // placing it, and its position could be stale.)
7461 if (aState
.FlowAndPlaceFloat(f
) ==
7462 BlockReflowState::PlaceFloatResult::Placed
) {
7463 ConsiderChildOverflow(aOverflowAreas
, f
);
7466 // If f is the only child in the floats list, pushing it to the pushed
7467 // floats list in FlowAndPlaceFloat() can result in the floats list being
7468 // deleted. Get the floats list again.
7469 floats
= GetFloats();
7475 nsIFrame
* next
= !prev
? floats
->FirstChild() : prev
->GetNextSibling();
7477 // We didn't push |f| so its next-sibling is next.
7478 next
= f
->GetNextSibling();
7480 } // else: we did push |f| so |prev|'s new next-sibling is next.
7484 // If there are pushed or split floats, then we may need to continue BR
7486 if (auto [bCoord
, result
] = aState
.ClearFloats(0, StyleClear::Both
);
7487 result
!= ClearFloatsResult::BCoordNoChange
) {
7489 if (auto* prevBlock
= static_cast<nsBlockFrame
*>(GetPrevInFlow())) {
7490 aState
.mTrailingClearFromPIF
= prevBlock
->FindTrailingClear();
7495 void nsBlockFrame::RecoverFloats(nsFloatManager
& aFloatManager
, WritingMode aWM
,
7496 const nsSize
& aContainerSize
) {
7497 // Recover our own floats
7498 nsIFrame
* stop
= nullptr; // Stop before we reach pushed floats that
7499 // belong to our next-in-flow
7500 const nsFrameList
* floats
= GetFloats();
7501 for (nsIFrame
* f
= floats
? floats
->FirstChild() : nullptr; f
&& f
!= stop
;
7502 f
= f
->GetNextSibling()) {
7503 LogicalRect region
= nsFloatManager::GetRegionFor(aWM
, f
, aContainerSize
);
7504 aFloatManager
.AddFloat(f
, region
, aWM
, aContainerSize
);
7505 if (!stop
&& f
->GetNextInFlow()) {
7506 stop
= f
->GetNextInFlow();
7510 // Recurse into our overflow container children
7512 GetChildList(FrameChildListID::OverflowContainers
).FirstChild();
7513 oc
; oc
= oc
->GetNextSibling()) {
7514 RecoverFloatsFor(oc
, aFloatManager
, aWM
, aContainerSize
);
7517 // Recurse into our normal children
7518 for (const auto& line
: Lines()) {
7519 if (line
.IsBlock()) {
7520 RecoverFloatsFor(line
.mFirstChild
, aFloatManager
, aWM
, aContainerSize
);
7525 void nsBlockFrame::RecoverFloatsFor(nsIFrame
* aFrame
,
7526 nsFloatManager
& aFloatManager
,
7528 const nsSize
& aContainerSize
) {
7529 MOZ_ASSERT(aFrame
, "null frame");
7531 // Only blocks have floats
7532 nsBlockFrame
* block
= do_QueryFrame(aFrame
);
7533 // Don't recover any state inside a block that has its own float manager
7534 // (we don't currently have any blocks like this, though, thanks to our
7535 // use of extra frames for 'overflow')
7536 if (block
&& !nsBlockFrame::BlockNeedsFloatManager(block
)) {
7537 // If the element is relatively positioned, then adjust x and y
7538 // accordingly so that we consider relatively positioned frames
7539 // at their original position.
7541 const LogicalRect rect
= block
->GetLogicalNormalRect(aWM
, aContainerSize
);
7542 nscoord lineLeft
= rect
.LineLeft(aWM
, aContainerSize
);
7543 nscoord blockStart
= rect
.BStart(aWM
);
7544 aFloatManager
.Translate(lineLeft
, blockStart
);
7545 block
->RecoverFloats(aFloatManager
, aWM
, aContainerSize
);
7546 aFloatManager
.Translate(-lineLeft
, -blockStart
);
7550 bool nsBlockFrame::HasPushedFloatsFromPrevContinuation() const {
7551 if (const nsFrameList
* floats
= GetFloats()) {
7552 // If we have pushed floats, then they should be at the beginning of our
7554 if (floats
->FirstChild()->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT
)) {
7558 // Double-check the above assertion that pushed floats should be at the
7559 // beginning of our floats list.
7560 for (nsIFrame
* f
: *floats
) {
7561 NS_ASSERTION(!f
->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT
),
7562 "pushed floats must be at the beginning of the float list");
7567 // We may have a pending push of pushed floats, too.
7568 return HasPushedFloats();
7571 //////////////////////////////////////////////////////////////////////
7572 // Painting, event handling
7575 static void ComputeInkOverflowArea(nsLineList
& aLines
, nscoord aWidth
,
7576 nscoord aHeight
, nsRect
& aResult
) {
7577 nscoord xa
= 0, ya
= 0, xb
= aWidth
, yb
= aHeight
;
7578 for (nsLineList::iterator line
= aLines
.begin(), line_end
= aLines
.end();
7579 line
!= line_end
; ++line
) {
7580 // Compute min and max x/y values for the reflowed frame's
7582 nsRect
inkOverflow(line
->InkOverflowRect());
7583 nscoord x
= inkOverflow
.x
;
7584 nscoord y
= inkOverflow
.y
;
7585 nscoord xmost
= x
+ inkOverflow
.width
;
7586 nscoord ymost
= y
+ inkOverflow
.height
;
7603 aResult
.width
= xb
- xa
;
7604 aResult
.height
= yb
- ya
;
7609 static void DebugOutputDrawLine(int32_t aDepth
, nsLineBox
* aLine
, bool aDrawn
) {
7610 if (nsBlockFrame::gNoisyDamageRepair
) {
7611 nsIFrame::IndentBy(stdout
, aDepth
+ 1);
7612 nsRect lineArea
= aLine
->InkOverflowRect();
7613 printf("%s line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
7614 aDrawn
? "draw" : "skip", static_cast<void*>(aLine
), aLine
->IStart(),
7615 aLine
->BStart(), aLine
->ISize(), aLine
->BSize(), lineArea
.x
,
7616 lineArea
.y
, lineArea
.width
, lineArea
.height
);
7621 static void DisplayLine(nsDisplayListBuilder
* aBuilder
,
7622 nsBlockFrame::LineIterator
& aLine
,
7623 const bool aLineInLine
, const nsDisplayListSet
& aLists
,
7624 nsBlockFrame
* aFrame
, TextOverflow
* aTextOverflow
,
7625 uint32_t aLineNumberForTextOverflow
, int32_t aDepth
,
7626 int32_t& aDrawnLines
) {
7628 if (nsBlockFrame::gLamePaintMetrics
) {
7631 const bool intersect
=
7632 aLine
->InkOverflowRect().Intersects(aBuilder
->GetDirtyRect());
7633 DebugOutputDrawLine(aDepth
, aLine
.get(), intersect
);
7636 // Collect our line's display items in a temporary nsDisplayListCollection,
7637 // so that we can apply any "text-overflow" clipping to the entire collection
7638 // without affecting previous lines.
7639 nsDisplayListCollection
collection(aBuilder
);
7641 // Block-level child backgrounds go on the blockBorderBackgrounds list ...
7642 // Inline-level child backgrounds go on the regular child content list.
7643 nsDisplayListSet
childLists(
7645 aLineInLine
? collection
.Content() : collection
.BlockBorderBackgrounds());
7649 ? nsIFrame::DisplayChildFlags(nsIFrame::DisplayChildFlag::Inline
)
7650 : nsIFrame::DisplayChildFlags();
7652 nsIFrame
* kid
= aLine
->mFirstChild
;
7653 int32_t n
= aLine
->GetChildCount();
7655 aFrame
->BuildDisplayListForChild(aBuilder
, kid
, childLists
, flags
);
7656 kid
= kid
->GetNextSibling();
7659 if (aTextOverflow
&& aLineInLine
) {
7660 aTextOverflow
->ProcessLine(collection
, aLine
.get(),
7661 aLineNumberForTextOverflow
);
7664 collection
.MoveTo(aLists
);
7667 void nsBlockFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
7668 const nsDisplayListSet
& aLists
) {
7669 int32_t drawnLines
; // Will only be used if set (gLamePaintMetrics).
7672 if (gNoisyDamageRepair
) {
7673 nsRect dirty
= aBuilder
->GetDirtyRect();
7676 ::ComputeInkOverflowArea(mLines
, mRect
.width
, mRect
.height
, ca
);
7677 nsIFrame::IndentBy(stdout
, depth
);
7679 printf(": bounds=%d,%d,%d,%d dirty(absolute)=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
7680 mRect
.x
, mRect
.y
, mRect
.width
, mRect
.height
, dirty
.x
, dirty
.y
,
7681 dirty
.width
, dirty
.height
, ca
.x
, ca
.y
, ca
.width
, ca
.height
);
7683 PRTime start
= 0; // Initialize these variables to silence the compiler.
7684 if (gLamePaintMetrics
) {
7690 // TODO(heycam): Should we boost the load priority of any shape-outside
7691 // images using CATEGORY_DISPLAY, now that this block is being displayed?
7692 // We don't have a float manager here.
7694 DisplayBorderBackgroundOutline(aBuilder
, aLists
);
7696 if (GetPrevInFlow()) {
7697 DisplayOverflowContainers(aBuilder
, aLists
);
7698 for (nsIFrame
* f
: GetChildList(FrameChildListID::Float
)) {
7699 if (f
->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT
)) {
7700 BuildDisplayListForChild(aBuilder
, f
, aLists
);
7705 aBuilder
->MarkFramesForDisplayList(this,
7706 GetChildList(FrameChildListID::Float
));
7708 if (HasOutsideMarker()) {
7709 // Display outside ::marker manually.
7710 BuildDisplayListForChild(aBuilder
, GetOutsideMarker(), aLists
);
7713 // Prepare for text-overflow processing.
7714 Maybe
<TextOverflow
> textOverflow
=
7715 TextOverflow::WillProcessLines(aBuilder
, this);
7717 const bool hasDescendantPlaceHolders
=
7718 HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO
) ||
7719 ForceDescendIntoIfVisible() || aBuilder
->GetIncludeAllOutOfFlows();
7721 const auto ShouldDescendIntoLine
= [&](const nsRect
& aLineArea
) -> bool {
7722 // TODO(miko): Unfortunately |descendAlways| cannot be cached, because with
7723 // some frame trees, building display list for child lines can change it.
7725 const bool descendAlways
=
7726 HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO
) ||
7727 aBuilder
->GetIncludeAllOutOfFlows();
7729 return descendAlways
|| aLineArea
.Intersects(aBuilder
->GetDirtyRect()) ||
7730 (ForceDescendIntoIfVisible() &&
7731 aLineArea
.Intersects(aBuilder
->GetVisibleRect()));
7734 Maybe
<nscolor
> backplateColor
;
7736 // We'll try to draw an accessibility backplate behind text (to ensure it's
7737 // readable over any possible background-images), if all of the following
7739 // (A) we are not honoring the document colors
7740 // (B) the backplate feature is preffed on
7741 // (C) the force color adjust property is set to auto
7742 if (PresContext()->ForcingColors() &&
7743 StaticPrefs::browser_display_permit_backplate() &&
7744 StyleText()->mForcedColorAdjust
!= StyleForcedColorAdjust::None
) {
7745 backplateColor
.emplace(GetBackplateColor(this));
7748 // Don't use the line cursor if we might have a descendant placeholder ...
7749 // it might skip lines that contain placeholders but don't themselves
7750 // intersect with the dirty area.
7751 // In particular, we really want to check ShouldDescendIntoFrame()
7752 // on all our child frames, but that might be expensive. So we
7753 // approximate it by checking it on |this|; if it's true for any
7754 // frame in our child list, it's also true for |this|.
7755 // Also skip the cursor if we're creating text overflow markers,
7756 // since we need to know what line number we're up to in order
7757 // to generate unique display item keys.
7758 // Lastly, the cursor should be skipped if we're drawing
7759 // backplates behind text. When backplating we consider consecutive
7760 // runs of text as a whole, which requires we iterate through all lines
7761 // to find our backplate size.
7763 (hasDescendantPlaceHolders
|| textOverflow
.isSome() || backplateColor
)
7765 : GetFirstLineContaining(aBuilder
->GetDirtyRect().y
);
7766 LineIterator line_end
= LinesEnd();
7768 TextOverflow
* textOverflowPtr
= textOverflow
.ptrOr(nullptr);
7771 for (LineIterator line
= mLines
.begin(cursor
); line
!= line_end
; ++line
) {
7772 const nsRect lineArea
= line
->InkOverflowRect();
7773 if (!lineArea
.IsEmpty()) {
7774 // Because we have a cursor, the combinedArea.ys are non-decreasing.
7775 // Once we've passed aDirtyRect.YMost(), we can never see it again.
7776 if (lineArea
.y
>= aBuilder
->GetDirtyRect().YMost()) {
7779 MOZ_ASSERT(textOverflow
.isNothing());
7781 if (ShouldDescendIntoLine(lineArea
)) {
7782 DisplayLine(aBuilder
, line
, line
->IsInline(), aLists
, this, nullptr,
7783 0, depth
, drawnLines
);
7788 bool nonDecreasingYs
= true;
7789 uint32_t lineCount
= 0;
7790 nscoord lastY
= INT32_MIN
;
7791 nscoord lastYMost
= INT32_MIN
;
7793 // A frame's display list cannot contain more than one copy of a
7794 // given display item unless the items are uniquely identifiable.
7795 // Because backplate occasionally requires multiple
7796 // SolidColor items, we use an index (backplateIndex) to maintain
7797 // uniqueness among them. Note this is a mapping of index to
7798 // item, and the mapping is stable even if the dirty rect changes.
7799 uint16_t backplateIndex
= 0;
7800 nsRect curBackplateArea
;
7802 auto AddBackplate
= [&]() {
7803 aLists
.BorderBackground()->AppendNewToTopWithIndex
<nsDisplaySolidColor
>(
7804 aBuilder
, this, backplateIndex
, curBackplateArea
,
7805 backplateColor
.value());
7808 for (LineIterator line
= LinesBegin(); line
!= line_end
; ++line
) {
7809 const nsRect lineArea
= line
->InkOverflowRect();
7810 const bool lineInLine
= line
->IsInline();
7812 if ((lineInLine
&& textOverflowPtr
) || ShouldDescendIntoLine(lineArea
)) {
7813 DisplayLine(aBuilder
, line
, lineInLine
, aLists
, this, textOverflowPtr
,
7814 lineCount
, depth
, drawnLines
);
7817 if (!lineInLine
&& !curBackplateArea
.IsEmpty()) {
7818 // If we have encountered a non-inline line but were previously
7819 // forming a backplate, we should add the backplate to the display
7820 // list as-is and render future backplates disjointly.
7821 MOZ_ASSERT(backplateColor
,
7822 "if this master switch is off, curBackplateArea "
7823 "must be empty and we shouldn't get here");
7826 curBackplateArea
= nsRect();
7829 if (!lineArea
.IsEmpty()) {
7830 if (lineArea
.y
< lastY
|| lineArea
.YMost() < lastYMost
) {
7831 nonDecreasingYs
= false;
7834 lastYMost
= lineArea
.YMost();
7835 if (lineInLine
&& backplateColor
&& LineHasVisibleInlineText(line
)) {
7836 nsRect lineBackplate
= GetLineTextArea(line
, aBuilder
) +
7837 aBuilder
->ToReferenceFrame(this);
7838 if (curBackplateArea
.IsEmpty()) {
7839 curBackplateArea
= lineBackplate
;
7841 curBackplateArea
.OrWith(lineBackplate
);
7848 if (nonDecreasingYs
&& lineCount
>= MIN_LINES_NEEDING_CURSOR
) {
7849 SetupLineCursorForDisplay();
7852 if (!curBackplateArea
.IsEmpty()) {
7857 if (textOverflow
.isSome()) {
7858 // Put any text-overflow:ellipsis markers on top of the non-positioned
7859 // content of the block's lines. (If we ever start sorting the Content()
7860 // list this will end up in the wrong place.)
7861 aLists
.Content()->AppendToTop(&textOverflow
->GetMarkers());
7865 if (gLamePaintMetrics
) {
7866 PRTime end
= PR_Now();
7868 int32_t numLines
= mLines
.size();
7872 PRTime lines
, deltaPerLine
, delta
;
7873 lines
= int64_t(numLines
);
7874 delta
= end
- start
;
7875 deltaPerLine
= delta
/ lines
;
7880 ": %" PRId64
" elapsed (%" PRId64
7881 " per line) lines=%d drawn=%d skip=%d",
7882 delta
, deltaPerLine
, numLines
, drawnLines
,
7883 numLines
- drawnLines
);
7884 printf("%s\n", buf
);
7889 #ifdef ACCESSIBILITY
7890 a11y::AccType
nsBlockFrame::AccessibleType() {
7891 if (IsTableCaption()) {
7892 return GetRect().IsEmpty() ? a11y::eNoType
: a11y::eHTMLCaptionType
;
7895 // block frame may be for <hr>
7896 if (mContent
->IsHTMLElement(nsGkAtoms::hr
)) {
7897 return a11y::eHTMLHRType
;
7900 if (!HasMarker() || !PresContext()) {
7901 // XXXsmaug What if we're in the shadow dom?
7902 if (!mContent
->GetParent()) {
7903 // Don't create accessible objects for the root content node, they are
7904 // redundant with the nsDocAccessible object created with the document
7906 return a11y::eNoType
;
7909 if (mContent
== mContent
->OwnerDoc()->GetBody()) {
7910 // Don't create accessible objects for the body, they are redundant with
7911 // the nsDocAccessible object created with the document node
7912 return a11y::eNoType
;
7915 // Not a list item with a ::marker, treat as normal HTML container.
7916 return a11y::eHyperTextType
;
7919 // Create special list item accessible since we have a ::marker.
7920 return a11y::eHTMLLiType
;
7924 void nsBlockFrame::SetupLineCursorForDisplay() {
7925 if (mLines
.empty() || HasProperty(LineCursorPropertyDisplay())) {
7929 SetProperty(LineCursorPropertyDisplay(), mLines
.front());
7930 AddStateBits(NS_BLOCK_HAS_LINE_CURSOR
);
7933 void nsBlockFrame::SetupLineCursorForQuery() {
7934 if (mLines
.empty() || HasProperty(LineCursorPropertyQuery())) {
7938 SetProperty(LineCursorPropertyQuery(), mLines
.front());
7939 AddStateBits(NS_BLOCK_HAS_LINE_CURSOR
);
7942 nsLineBox
* nsBlockFrame::GetFirstLineContaining(nscoord y
) {
7943 // Although this looks like a "querying" method, it is used by the
7944 // display-list building code, so uses the Display cursor.
7945 nsLineBox
* property
= GetLineCursorForDisplay();
7949 LineIterator cursor
= mLines
.begin(property
);
7950 nsRect cursorArea
= cursor
->InkOverflowRect();
7952 while ((cursorArea
.IsEmpty() || cursorArea
.YMost() > y
) &&
7953 cursor
!= mLines
.front()) {
7954 cursor
= cursor
.prev();
7955 cursorArea
= cursor
->InkOverflowRect();
7957 while ((cursorArea
.IsEmpty() || cursorArea
.YMost() <= y
) &&
7958 cursor
!= mLines
.back()) {
7959 cursor
= cursor
.next();
7960 cursorArea
= cursor
->InkOverflowRect();
7963 if (cursor
.get() != property
) {
7964 SetProperty(LineCursorPropertyDisplay(), cursor
.get());
7967 return cursor
.get();
7971 void nsBlockFrame::ChildIsDirty(nsIFrame
* aChild
) {
7972 // See if the child is absolutely positioned
7973 if (aChild
->IsAbsolutelyPositioned()) {
7975 } else if (aChild
== GetOutsideMarker()) {
7976 // The ::marker lives in the first line, unless the first line has
7977 // height 0 and there is a second line, in which case it lives
7978 // in the second line.
7979 LineIterator markerLine
= LinesBegin();
7980 if (markerLine
!= LinesEnd() && markerLine
->BSize() == 0 &&
7981 markerLine
!= mLines
.back()) {
7982 markerLine
= markerLine
.next();
7985 if (markerLine
!= LinesEnd()) {
7986 MarkLineDirty(markerLine
, &mLines
);
7988 // otherwise we have an empty line list, and ReflowDirtyLines
7989 // will handle reflowing the ::marker.
7991 // Note that we should go through our children to mark lines dirty
7992 // before the next reflow. Doing it now could make things O(N^2)
7993 // since finding the right line is O(N).
7994 // We don't need to worry about marking lines on the overflow list
7995 // as dirty; we're guaranteed to reflow them if we take them off the
7997 // However, we might have gotten a float, in which case we need to
7998 // reflow the line containing its placeholder. So find the
7999 // ancestor-or-self of the placeholder that's a child of the block,
8000 // and mark it as NS_FRAME_HAS_DIRTY_CHILDREN too, so that we mark
8001 // its line dirty when we handle NS_BLOCK_LOOK_FOR_DIRTY_FRAMES.
8002 // We need to take some care to handle the case where a float is in
8003 // a different continuation than its placeholder, including marking
8004 // an extra block with NS_BLOCK_LOOK_FOR_DIRTY_FRAMES.
8005 if (!aChild
->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW
)) {
8006 AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES
);
8008 NS_ASSERTION(aChild
->IsFloating(), "should be a float");
8009 nsIFrame
* thisFC
= FirstContinuation();
8010 nsIFrame
* placeholderPath
= aChild
->GetPlaceholderFrame();
8011 // SVG code sometimes sends FrameNeedsReflow notifications during
8012 // frame destruction, leading to null placeholders, but we're safe
8014 if (placeholderPath
) {
8016 nsIFrame
* parent
= placeholderPath
->GetParent();
8017 if (parent
->GetContent() == mContent
&&
8018 parent
->FirstContinuation() == thisFC
) {
8019 parent
->AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES
);
8022 placeholderPath
= parent
;
8024 placeholderPath
->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN
);
8029 nsContainerFrame::ChildIsDirty(aChild
);
8032 void nsBlockFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
8033 nsIFrame
* aPrevInFlow
) {
8034 // These are all the block specific frame bits, they are copied from
8035 // the prev-in-flow to a newly created next-in-flow, except for the
8036 // NS_BLOCK_FLAGS_NON_INHERITED_MASK bits below.
8037 constexpr nsFrameState NS_BLOCK_FLAGS_MASK
=
8038 NS_BLOCK_BFC
| NS_BLOCK_HAS_FIRST_LETTER_STYLE
|
8039 NS_BLOCK_HAS_FIRST_LETTER_CHILD
| NS_BLOCK_HAS_OUTSIDE_MARKER
|
8040 NS_BLOCK_HAS_INSIDE_MARKER
;
8042 // This is the subset of NS_BLOCK_FLAGS_MASK that is NOT inherited
8043 // by default. They should only be set on the first-in-flow.
8044 constexpr nsFrameState NS_BLOCK_FLAGS_NON_INHERITED_MASK
=
8045 NS_BLOCK_HAS_FIRST_LETTER_CHILD
| NS_BLOCK_HAS_OUTSIDE_MARKER
|
8046 NS_BLOCK_HAS_INSIDE_MARKER
;
8049 // Copy over the inherited block frame bits from the prev-in-flow.
8050 RemoveStateBits(NS_BLOCK_FLAGS_MASK
);
8051 AddStateBits(aPrevInFlow
->GetStateBits() &
8052 (NS_BLOCK_FLAGS_MASK
& ~NS_BLOCK_FLAGS_NON_INHERITED_MASK
));
8055 nsContainerFrame::Init(aContent
, aParent
, aPrevInFlow
);
8058 aPrevInFlow
->HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION
)) {
8059 AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION
);
8062 if (EstablishesBFC(this)) {
8063 AddStateBits(NS_BLOCK_BFC
);
8066 if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER
) &&
8067 HasAnyStateBits(NS_BLOCK_BFC
)) {
8068 AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT
);
8072 void nsBlockFrame::SetInitialChildList(ChildListID aListID
,
8073 nsFrameList
&& aChildList
) {
8074 if (FrameChildListID::Float
== aListID
) {
8075 nsFrameList
* floats
= EnsureFloats();
8076 *floats
= std::move(aChildList
);
8077 } else if (FrameChildListID::Principal
== aListID
) {
8079 // The only times a block that is an anonymous box is allowed to have a
8080 // first-letter frame are when it's the block inside a non-anonymous cell,
8081 // the block inside a fieldset, button or column set, or a scrolled content
8082 // block, except for <select>. Note that this means that blocks which are
8083 // the anonymous block in {ib} splits do NOT get first-letter frames.
8084 // Note that NS_BLOCK_HAS_FIRST_LETTER_STYLE gets set on all continuations
8086 auto pseudo
= Style()->GetPseudoType();
8087 bool haveFirstLetterStyle
=
8088 (pseudo
== PseudoStyleType::NotPseudo
||
8089 (pseudo
== PseudoStyleType::cellContent
&&
8090 !GetParent()->Style()->IsPseudoOrAnonBox()) ||
8091 pseudo
== PseudoStyleType::fieldsetContent
||
8092 (pseudo
== PseudoStyleType::buttonContent
&&
8093 !GetParent()->IsComboboxControlFrame()) ||
8094 pseudo
== PseudoStyleType::columnContent
||
8095 (pseudo
== PseudoStyleType::scrolledContent
&&
8096 !GetParent()->IsListControlFrame()) ||
8097 pseudo
== PseudoStyleType::mozSVGText
) &&
8098 !IsMathMLFrame() && !IsColumnSetWrapperFrame() &&
8099 RefPtr
<ComputedStyle
>(GetFirstLetterStyle(PresContext())) != nullptr;
8100 NS_ASSERTION(haveFirstLetterStyle
==
8101 HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE
),
8102 "NS_BLOCK_HAS_FIRST_LETTER_STYLE state out of sync");
8105 AddFrames(std::move(aChildList
), nullptr, nullptr);
8107 nsContainerFrame::SetInitialChildList(aListID
, std::move(aChildList
));
8111 void nsBlockFrame::SetMarkerFrameForListItem(nsIFrame
* aMarkerFrame
) {
8112 MOZ_ASSERT(aMarkerFrame
);
8113 MOZ_ASSERT(!HasMarker(), "How can we have a ::marker frame already?");
8115 if (StyleList()->mListStylePosition
== StyleListStylePosition::Inside
) {
8116 SetProperty(InsideMarkerProperty(), aMarkerFrame
);
8117 AddStateBits(NS_BLOCK_HAS_INSIDE_MARKER
);
8119 SetProperty(OutsideMarkerProperty(),
8120 new (PresShell()) nsFrameList(aMarkerFrame
, aMarkerFrame
));
8121 AddStateBits(NS_BLOCK_HAS_OUTSIDE_MARKER
);
8125 bool nsBlockFrame::MarkerIsEmpty() const {
8126 NS_ASSERTION(mContent
->GetPrimaryFrame()->StyleDisplay()->IsListItem() &&
8128 "should only care when we have an outside ::marker");
8129 nsIFrame
* marker
= GetMarker();
8130 const nsStyleList
* list
= marker
->StyleList();
8131 return marker
->StyleContent()->mContent
.IsNone() ||
8132 (list
->mListStyleType
.IsNone() && list
->mListStyleImage
.IsNone() &&
8133 marker
->StyleContent()->NonAltContentItems().IsEmpty());
8136 void nsBlockFrame::ReflowOutsideMarker(nsIFrame
* aMarkerFrame
,
8137 BlockReflowState
& aState
,
8138 ReflowOutput
& aMetrics
,
8140 const ReflowInput
& ri
= aState
.mReflowInput
;
8142 WritingMode markerWM
= aMarkerFrame
->GetWritingMode();
8143 LogicalSize
availSize(markerWM
);
8144 // Make up an inline-size since it doesn't really matter (XXX).
8145 availSize
.ISize(markerWM
) = aState
.ContentISize();
8146 availSize
.BSize(markerWM
) = NS_UNCONSTRAINEDSIZE
;
8148 ReflowInput
reflowInput(aState
.mPresContext
, ri
, aMarkerFrame
, availSize
,
8149 Nothing(), {}, {}, {ComputeSizeFlag::ShrinkWrap
});
8150 nsReflowStatus status
;
8151 aMarkerFrame
->Reflow(aState
.mPresContext
, aMetrics
, reflowInput
, status
);
8153 // Get the float available space using our saved state from before we
8154 // started reflowing the block, so that we ignore any floats inside
8156 // FIXME: aLineTop isn't actually set correctly by some callers, since
8157 // they reposition the line.
8158 LogicalRect floatAvailSpace
=
8160 .GetFloatAvailableSpaceWithState(aLineTop
, ShapeType::ShapeOutside
,
8161 &aState
.mFloatManagerStateBefore
)
8163 // FIXME (bug 25888): need to check the entire region that the first
8164 // line overlaps, not just the top pixel.
8166 // Place the ::marker now. We want to place the ::marker relative to the
8167 // border-box of the associated block (using the right/left margin of
8168 // the ::marker frame as separation). However, if a line box would be
8169 // displaced by floats that are *outside* the associated block, we
8170 // want to displace it by the same amount. That is, we act as though
8171 // the edge of the floats is the content-edge of the block, and place
8172 // the ::marker at a position offset from there by the block's padding,
8173 // the block's border, and the ::marker frame's margin.
8175 // IStart from floatAvailSpace gives us the content/float start edge
8176 // in the current writing mode. Then we subtract out the start
8177 // border/padding and the ::marker's width and margin to offset the position.
8178 WritingMode wm
= ri
.GetWritingMode();
8179 // Get the ::marker's margin, converted to our writing mode so that we can
8180 // combine it with other logical values here.
8181 LogicalMargin markerMargin
= reflowInput
.ComputedLogicalMargin(wm
);
8182 nscoord iStart
= floatAvailSpace
.IStart(wm
) -
8183 ri
.ComputedLogicalBorderPadding(wm
).IStart(wm
) -
8184 markerMargin
.IEnd(wm
) - aMetrics
.ISize(wm
);
8186 // Approximate the ::marker's position; vertical alignment will provide
8187 // the final vertical location. We pass our writing-mode here, because
8188 // it may be different from the ::marker frame's mode.
8189 nscoord bStart
= floatAvailSpace
.BStart(wm
);
8190 aMarkerFrame
->SetRect(
8192 LogicalRect(wm
, iStart
, bStart
, aMetrics
.ISize(wm
), aMetrics
.BSize(wm
)),
8193 aState
.ContainerSize());
8194 aMarkerFrame
->DidReflow(aState
.mPresContext
, &aState
.mReflowInput
);
8197 // This is used to scan frames for any float placeholders, add their
8198 // floats to the list represented by aList, and remove the
8199 // floats from whatever list they might be in. We don't search descendants
8200 // that are float containing blocks. Floats that or not children of 'this'
8201 // are ignored (they are not added to aList).
8202 void nsBlockFrame::DoCollectFloats(nsIFrame
* aFrame
, nsFrameList
& aList
,
8203 bool aCollectSiblings
) {
8205 // Don't descend into float containing blocks.
8206 if (!aFrame
->IsFloatContainingBlock()) {
8207 nsIFrame
* outOfFlowFrame
=
8208 aFrame
->IsPlaceholderFrame()
8209 ? nsLayoutUtils::GetFloatFromPlaceholder(aFrame
)
8211 while (outOfFlowFrame
&& outOfFlowFrame
->GetParent() == this) {
8212 RemoveFloat(outOfFlowFrame
);
8213 // Remove the IS_PUSHED_FLOAT bit, in case |outOfFlowFrame| came from
8214 // the PushedFloats list.
8215 outOfFlowFrame
->RemoveStateBits(NS_FRAME_IS_PUSHED_FLOAT
);
8216 aList
.AppendFrame(nullptr, outOfFlowFrame
);
8217 outOfFlowFrame
= outOfFlowFrame
->GetNextInFlow();
8218 // FIXME: By not pulling floats whose parent is one of our
8219 // later siblings, are we risking the pushed floats getting
8221 // XXXmats nsInlineFrame's lazy reparenting depends on NOT doing that.
8224 DoCollectFloats(aFrame
->PrincipalChildList().FirstChild(), aList
, true);
8226 aFrame
->GetChildList(FrameChildListID::Overflow
).FirstChild(), aList
,
8229 if (!aCollectSiblings
) {
8232 aFrame
= aFrame
->GetNextSibling();
8236 void nsBlockFrame::CheckFloats(BlockReflowState
& aState
) {
8238 // If any line is still dirty, that must mean we're going to reflow this
8239 // block again soon (e.g. because we bailed out after noticing that
8240 // clearance was imposed), so don't worry if the floats are out of sync.
8241 bool anyLineDirty
= false;
8243 // Check that the float list is what we would have built
8244 AutoTArray
<nsIFrame
*, 8> lineFloats
;
8245 for (auto& line
: Lines()) {
8246 if (line
.HasFloats()) {
8247 lineFloats
.AppendElements(line
.Floats());
8249 if (line
.IsDirty()) {
8250 anyLineDirty
= true;
8254 AutoTArray
<nsIFrame
*, 8> storedFloats
;
8256 bool hasHiddenFloats
= false;
8258 for (nsIFrame
* f
: GetChildList(FrameChildListID::Float
)) {
8259 if (f
->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT
)) {
8262 // There are chances that the float children won't be added to lines,
8263 // because in nsBlockFrame::ReflowLine, it skips reflow line if the first
8264 // child of the line is IsHiddenByContentVisibilityOfInFlowParentForLayout.
8265 // There are also chances that the floats in line are out of date, for
8266 // instance, lines could reflow if
8267 // PresShell::IsForcingLayoutForHiddenContent, and after forcingLayout is
8268 // off, the reflow of lines could be skipped, but the floats are still in
8269 // there. Here we can't know whether the floats hidden by c-v are included
8270 // in the lines or not. So we use hasHiddenFloats to skip the float length
8272 if (!hasHiddenFloats
&&
8273 f
->IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
8274 hasHiddenFloats
= true;
8276 storedFloats
.AppendElement(f
);
8277 if (i
< lineFloats
.Length() && lineFloats
.ElementAt(i
) != f
) {
8283 if ((!equal
|| lineFloats
.Length() != storedFloats
.Length()) &&
8284 !anyLineDirty
&& !hasHiddenFloats
) {
8286 "nsBlockFrame::CheckFloats: Explicit float list is out of sync with "
8291 const nsFrameList
* oofs
= GetOverflowOutOfFlows();
8292 if (oofs
&& oofs
->NotEmpty()) {
8293 // Floats that were pushed should be removed from our float
8294 // manager. Otherwise the float manager's YMost or XMost might
8295 // be larger than necessary, causing this block to get an
8296 // incorrect desired height (or width). Some of these floats
8297 // may not actually have been added to the float manager because
8298 // they weren't reflowed before being pushed; that's OK,
8299 // RemoveRegions will ignore them. It is safe to do this here
8300 // because we know from here on the float manager will only be
8301 // used for its XMost and YMost, not to place new floats and
8303 aState
.FloatManager()->RemoveTrailingRegions(oofs
->FirstChild());
8307 void nsBlockFrame::IsMarginRoot(bool* aBStartMarginRoot
,
8308 bool* aBEndMarginRoot
) {
8309 nsIFrame
* parent
= GetParent();
8310 if (!HasAnyStateBits(NS_BLOCK_BFC
)) {
8311 if (!parent
|| parent
->IsFloatContainingBlock()) {
8312 *aBStartMarginRoot
= false;
8313 *aBEndMarginRoot
= false;
8318 if (parent
&& parent
->IsColumnSetFrame()) {
8319 // The first column is a start margin root and the last column is an end
8320 // margin root. (If the column-set is split by a column-span:all box then
8321 // the first and last column in each column-set fragment are margin roots.)
8322 *aBStartMarginRoot
= GetPrevInFlow() == nullptr;
8323 *aBEndMarginRoot
= GetNextInFlow() == nullptr;
8327 *aBStartMarginRoot
= true;
8328 *aBEndMarginRoot
= true;
8332 bool nsBlockFrame::BlockNeedsFloatManager(nsIFrame
* aBlock
) {
8333 MOZ_ASSERT(aBlock
, "Must have a frame");
8334 NS_ASSERTION(aBlock
->IsBlockFrameOrSubclass(), "aBlock must be a block");
8336 nsIFrame
* parent
= aBlock
->GetParent();
8337 return aBlock
->HasAnyStateBits(NS_BLOCK_BFC
) ||
8338 (parent
&& !parent
->IsFloatContainingBlock());
8342 bool nsBlockFrame::BlockCanIntersectFloats(nsIFrame
* aFrame
) {
8343 return aFrame
->IsBlockFrameOrSubclass() && !aFrame
->IsReplaced() &&
8344 !aFrame
->HasAnyStateBits(NS_BLOCK_BFC
);
8347 // Note that this width can vary based on the vertical position.
8348 // However, the cases where it varies are the cases where the width fits
8349 // in the available space given, which means that variation shouldn't
8352 nsBlockFrame::FloatAvoidingISizeToClear
nsBlockFrame::ISizeToClearPastFloats(
8353 const BlockReflowState
& aState
, const LogicalRect
& aFloatAvailableSpace
,
8354 nsIFrame
* aFloatAvoidingBlock
) {
8355 nscoord inlineStartOffset
, inlineEndOffset
;
8356 WritingMode wm
= aState
.mReflowInput
.GetWritingMode();
8358 FloatAvoidingISizeToClear result
;
8359 aState
.ComputeFloatAvoidingOffsets(aFloatAvoidingBlock
, aFloatAvailableSpace
,
8360 inlineStartOffset
, inlineEndOffset
);
8361 nscoord availISize
=
8362 aState
.mContentArea
.ISize(wm
) - inlineStartOffset
- inlineEndOffset
;
8364 // We actually don't want the min width here; see bug 427782; we only
8365 // want to displace if the width won't compute to a value small enough
8367 // All we really need here is the result of ComputeSize, and we
8368 // could *almost* get that from an SizeComputationInput, except for the
8370 WritingMode frWM
= aFloatAvoidingBlock
->GetWritingMode();
8371 LogicalSize availSpace
=
8372 LogicalSize(wm
, availISize
, NS_UNCONSTRAINEDSIZE
).ConvertTo(frWM
, wm
);
8373 ReflowInput
reflowInput(aState
.mPresContext
, aState
.mReflowInput
,
8374 aFloatAvoidingBlock
, availSpace
);
8375 result
.borderBoxISize
=
8376 reflowInput
.ComputedSizeWithBorderPadding(wm
).ISize(wm
);
8378 // Use the margins from sizingInput rather than reflowInput so that
8379 // they aren't reduced by ignoring margins in overconstrained cases.
8380 SizeComputationInput
sizingInput(aFloatAvoidingBlock
,
8381 aState
.mReflowInput
.mRenderingContext
, wm
,
8382 aState
.mContentArea
.ISize(wm
));
8383 const LogicalMargin computedMargin
= sizingInput
.ComputedLogicalMargin(wm
);
8385 nscoord marginISize
= computedMargin
.IStartEnd(wm
);
8386 const auto& iSize
= reflowInput
.mStylePosition
->ISize(wm
);
8387 if (marginISize
< 0 &&
8388 (iSize
.IsAuto() || iSize
.BehavesLikeStretchOnInlineAxis())) {
8389 // If we get here, floatAvoidingBlock has a negative amount of inline-axis
8390 // margin and an 'auto' (or ~equivalently, -moz-available) inline
8391 // size. Under these circumstances, we use the margin to establish a
8392 // (positive) minimum size for the border-box, in order to satisfy the
8393 // equation in CSS2 10.3.3. That equation essentially simplifies to the
8396 // iSize of margins + iSize of borderBox = iSize of containingBlock
8398 // ...where "iSize of borderBox" is the sum of floatAvoidingBlock's
8399 // inline-axis components of border, padding, and {width,height}.
8401 // Right now, in the above equation, "iSize of margins" is the only term
8402 // that we know for sure. (And we also know that it's negative, since we
8403 // got here.) The other terms are as-yet unresolved, since the frame has an
8404 // 'auto' iSize, and since we aren't yet sure if we'll clear this frame
8405 // beyond floats or place it alongside them.
8407 // However: we *do* know that the equation's "iSize of containingBlock"
8408 // term *must* be non-negative, since boxes' widths and heights generally
8409 // can't be negative in CSS. To satisfy that requirement, we can then
8410 // infer that the equation's "iSize of borderBox" term *must* be large
8411 // enough to cancel out the (known-to-be-negative) "iSize of margins"
8412 // term. Therefore, marginISize value (negated to make it positive)
8413 // establishes a lower-bound for how much inline-axis space our border-box
8414 // will really require in order to fit alongside any floats.
8416 // XXXdholbert This explanation is admittedly a bit hand-wavy and may not
8417 // precisely match what any particular spec requires. It's the best
8418 // reasoning I could come up with to explain engines' behavior. Also, our
8419 // behavior with -moz-available doesn't seem particularly correct here, per
8420 // bug 1767217, though that's probably due to a bug elsewhere in our float
8422 result
.borderBoxISize
= std::max(result
.borderBoxISize
, -marginISize
);
8425 result
.marginIStart
= computedMargin
.IStart(wm
);
8430 nsBlockFrame
* nsBlockFrame::GetNearestAncestorBlock(nsIFrame
* aCandidate
) {
8431 nsBlockFrame
* block
= nullptr;
8432 while (aCandidate
) {
8433 block
= do_QueryFrame(aCandidate
);
8435 // yay, candidate is a block!
8438 // Not a block. Check its parent next.
8439 aCandidate
= aCandidate
->GetParent();
8441 MOZ_ASSERT_UNREACHABLE("Fell off frame tree looking for ancestor block!");
8445 nscoord
nsBlockFrame::ComputeFinalBSize(BlockReflowState
& aState
,
8446 nscoord aBEndEdgeOfChildren
) {
8447 const WritingMode wm
= aState
.mReflowInput
.GetWritingMode();
8449 const nscoord effectiveContentBoxBSize
=
8450 GetEffectiveComputedBSize(aState
.mReflowInput
, aState
.mConsumedBSize
);
8451 const nscoord blockStartBP
= aState
.BorderPadding().BStart(wm
);
8452 const nscoord blockEndBP
= aState
.BorderPadding().BEnd(wm
);
8455 !IsTrueOverflowContainer() || (effectiveContentBoxBSize
== 0 &&
8456 blockStartBP
== 0 && blockEndBP
== 0),
8457 "An overflow container's effective content-box block-size, block-start "
8458 "BP, and block-end BP should all be zero!");
8460 const nscoord effectiveContentBoxBSizeWithBStartBP
=
8461 NSCoordSaturatingAdd(blockStartBP
, effectiveContentBoxBSize
);
8462 const nscoord effectiveBorderBoxBSize
=
8463 NSCoordSaturatingAdd(effectiveContentBoxBSizeWithBStartBP
, blockEndBP
);
8465 if (HasColumnSpanSiblings()) {
8466 MOZ_ASSERT(LastInFlow()->GetNextContinuation(),
8467 "Frame constructor should've created column-span siblings!");
8469 // If a block is split by any column-spans, we calculate the final
8470 // block-size by shrinkwrapping our children's block-size for all the
8471 // fragments except for those after the final column-span, but we should
8472 // take no more than our effective border-box block-size. If there's any
8473 // leftover block-size, our next continuations will take up rest.
8475 // We don't need to adjust aBri.mReflowStatus because our children's status
8476 // is the same as ours.
8477 return std::min(effectiveBorderBoxBSize
, aBEndEdgeOfChildren
);
8480 const nscoord availBSize
= aState
.mReflowInput
.AvailableBSize();
8481 if (availBSize
== NS_UNCONSTRAINEDSIZE
) {
8482 return effectiveBorderBoxBSize
;
8485 // Save our children's reflow status.
8486 const bool isChildStatusComplete
= aState
.mReflowStatus
.IsComplete();
8487 if (isChildStatusComplete
&& effectiveContentBoxBSize
> 0 &&
8488 effectiveBorderBoxBSize
> availBSize
&&
8489 ShouldAvoidBreakInside(aState
.mReflowInput
)) {
8490 aState
.mReflowStatus
.SetInlineLineBreakBeforeAndReset();
8491 return effectiveBorderBoxBSize
;
8494 const bool isBDBClone
=
8495 aState
.mReflowInput
.mStyleBorder
->mBoxDecorationBreak
==
8496 StyleBoxDecorationBreak::Clone
;
8498 // The maximum value our content-box block-size can take within the given
8499 // available block-size.
8500 const nscoord maxContentBoxBSize
= aState
.ContentBSize();
8502 // The block-end edge of our content-box (relative to this frame's origin) if
8503 // we consumed the maximum block-size available to us (maxContentBoxBSize).
8504 const nscoord maxContentBoxBEnd
= aState
.ContentBEnd();
8506 // These variables are uninitialized intentionally so that the compiler can
8507 // check they are assigned in every if-else branch below.
8508 nscoord finalContentBoxBSizeWithBStartBP
;
8509 bool isOurStatusComplete
;
8511 if (effectiveBorderBoxBSize
<= availBSize
) {
8512 // Our effective border-box block-size can fit in the available block-size,
8513 // so we are complete.
8514 finalContentBoxBSizeWithBStartBP
= effectiveContentBoxBSizeWithBStartBP
;
8515 isOurStatusComplete
= true;
8516 } else if (effectiveContentBoxBSizeWithBStartBP
<= maxContentBoxBEnd
) {
8517 // Note: The following assertion should generally hold because, for
8518 // box-decoration-break:clone, this "else if" branch is mathematically
8519 // equivalent to the initial "if".
8520 NS_ASSERTION(!isBDBClone
,
8521 "This else-if branch is handling a situation that's specific "
8522 "to box-decoration-break:slice, i.e. a case when we can skip "
8523 "our block-end border and padding!");
8525 // Our effective content-box block-size plus the block-start border and
8526 // padding can fit in the available block-size, but it cannot fit after
8527 // adding the block-end border and padding. Thus, we need a continuation
8528 // (unless we already weren't asking for any block-size, in which case we
8529 // stay complete to avoid looping forever).
8530 finalContentBoxBSizeWithBStartBP
= effectiveContentBoxBSizeWithBStartBP
;
8531 isOurStatusComplete
= effectiveContentBoxBSize
== 0;
8533 // We aren't going to be able to fit our content-box in the space available
8534 // to it, which means we'll probably call ourselves incomplete to request a
8535 // continuation. But before making that decision, we check for certain
8536 // conditions which would force us to overflow beyond the available space --
8537 // these might result in us actually being complete if we're forced to
8538 // overflow far enough.
8539 if (MOZ_UNLIKELY(aState
.mReflowInput
.mFlags
.mIsTopOfPage
&& isBDBClone
&&
8540 maxContentBoxBSize
<= 0 &&
8541 aBEndEdgeOfChildren
== blockStartBP
)) {
8542 // In this rare case, we are at the top of page/column, we have
8543 // box-decoration-break:clone and zero available block-size for our
8544 // content-box (e.g. our own block-start border and padding already exceed
8545 // the available block-size), and we didn't lay out any child to consume
8546 // our content-box block-size. To ensure we make progress (avoid looping
8547 // forever), use 1px as our content-box block-size regardless of our
8548 // effective content-box block-size, in the spirit of
8549 // https://drafts.csswg.org/css-break/#breaking-rules.
8550 finalContentBoxBSizeWithBStartBP
= blockStartBP
+ AppUnitsPerCSSPixel();
8551 isOurStatusComplete
= effectiveContentBoxBSize
<= AppUnitsPerCSSPixel();
8552 } else if (aBEndEdgeOfChildren
> maxContentBoxBEnd
) {
8553 // We have a unbreakable child whose block-end edge exceeds the available
8554 // block-size for children.
8555 if (aBEndEdgeOfChildren
>= effectiveContentBoxBSizeWithBStartBP
) {
8556 // The unbreakable child's block-end edge forces us to consume all of
8557 // our effective content-box block-size.
8558 finalContentBoxBSizeWithBStartBP
= effectiveContentBoxBSizeWithBStartBP
;
8560 // Even though we've consumed all of our effective content-box
8561 // block-size, we may still need to report an incomplete status in order
8562 // to get another continuation, which will be responsible for laying out
8563 // & drawing our block-end border & padding. But if we have no such
8564 // border & padding, or if we're forced to apply that border & padding
8565 // on this frame due to box-decoration-break:clone, then we don't need
8566 // to bother with that additional continuation.
8567 isOurStatusComplete
= (isBDBClone
|| blockEndBP
== 0);
8569 // The unbreakable child's block-end edge doesn't force us to consume
8570 // all of our effective content-box block-size.
8571 finalContentBoxBSizeWithBStartBP
= aBEndEdgeOfChildren
;
8572 isOurStatusComplete
= false;
8575 // The children's block-end edge can fit in the content-box space that we
8576 // have available for it. Consume all the space that is available so that
8577 // our inline-start/inline-end borders extend all the way to the block-end
8578 // edge of column/page.
8579 finalContentBoxBSizeWithBStartBP
= maxContentBoxBEnd
;
8580 isOurStatusComplete
= false;
8584 nscoord finalBorderBoxBSize
= finalContentBoxBSizeWithBStartBP
;
8585 if (isOurStatusComplete
) {
8586 finalBorderBoxBSize
= NSCoordSaturatingAdd(finalBorderBoxBSize
, blockEndBP
);
8587 if (isChildStatusComplete
) {
8588 // We want to use children's reflow status as ours, which can be overflow
8589 // incomplete. Suppress the urge to call aBri.mReflowStatus.Reset() here.
8591 aState
.mReflowStatus
.SetOverflowIncomplete();
8594 NS_ASSERTION(!IsTrueOverflowContainer(),
8595 "An overflow container should always be complete because of "
8596 "its zero border-box block-size!");
8598 finalBorderBoxBSize
=
8599 NSCoordSaturatingAdd(finalBorderBoxBSize
, blockEndBP
);
8601 aState
.mReflowStatus
.SetIncomplete();
8602 if (!GetNextInFlow()) {
8603 aState
.mReflowStatus
.SetNextInFlowNeedsReflow();
8607 return finalBorderBoxBSize
;
8610 nsresult
nsBlockFrame::ResolveBidi() {
8611 NS_ASSERTION(!GetPrevInFlow(),
8612 "ResolveBidi called on non-first continuation");
8613 MOZ_ASSERT(PresContext()->BidiEnabled());
8614 return nsBidiPresUtils::Resolve(this);
8617 void nsBlockFrame::UpdatePseudoElementStyles(ServoRestyleState
& aRestyleState
) {
8618 // first-letter needs to be updated before first-line, because first-line can
8619 // change the style of the first-letter.
8620 if (HasFirstLetterChild()) {
8621 UpdateFirstLetterStyle(aRestyleState
);
8624 if (nsIFrame
* firstLineFrame
= GetFirstLineFrame()) {
8625 nsIFrame
* styleParent
= CorrectStyleParentFrame(firstLineFrame
->GetParent(),
8626 PseudoStyleType::firstLine
);
8628 ComputedStyle
* parentStyle
= styleParent
->Style();
8629 RefPtr
<ComputedStyle
> firstLineStyle
=
8630 aRestyleState
.StyleSet().ResolvePseudoElementStyle(
8631 *mContent
->AsElement(), PseudoStyleType::firstLine
, nullptr,
8634 // FIXME(bz): Can we make first-line continuations be non-inheriting anon
8636 RefPtr
<ComputedStyle
> continuationStyle
=
8637 aRestyleState
.StyleSet().ResolveInheritingAnonymousBoxStyle(
8638 PseudoStyleType::mozLineFrame
, parentStyle
);
8640 UpdateStyleOfOwnedChildFrame(firstLineFrame
, firstLineStyle
, aRestyleState
,
8641 Some(continuationStyle
.get()));
8643 // We also want to update the styles of the first-line's descendants. We
8644 // don't need to compute a changehint for this, though, since any changes to
8645 // them are handled by the first-line anyway.
8646 RestyleManager
* manager
= PresContext()->RestyleManager();
8647 for (nsIFrame
* kid
: firstLineFrame
->PrincipalChildList()) {
8648 manager
->ReparentComputedStyleForFirstLine(kid
);
8653 nsIFrame
* nsBlockFrame::GetFirstLetter() const {
8654 if (!HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE
)) {
8655 // Certainly no first-letter frame.
8659 return GetProperty(FirstLetterProperty());
8662 nsIFrame
* nsBlockFrame::GetFirstLineFrame() const {
8663 nsIFrame
* maybeFirstLine
= PrincipalChildList().FirstChild();
8664 if (maybeFirstLine
&& maybeFirstLine
->IsLineFrame()) {
8665 return maybeFirstLine
;
8672 void nsBlockFrame::VerifyLines(bool aFinalCheckOK
) {
8673 if (!gVerifyLines
) {
8676 if (mLines
.empty()) {
8680 nsLineBox
* cursor
= GetLineCursorForQuery();
8682 // Add up the counts on each line. Also validate that IsFirstLine is
8685 for (const auto& line
: Lines()) {
8686 if (&line
== cursor
) {
8689 if (aFinalCheckOK
) {
8690 MOZ_ASSERT(line
.GetChildCount(), "empty line");
8691 if (line
.IsBlock()) {
8692 NS_ASSERTION(1 == line
.GetChildCount(), "bad first line");
8695 count
+= line
.GetChildCount();
8698 // Then count the frames
8699 int32_t frameCount
= 0;
8700 nsIFrame
* frame
= mLines
.front()->mFirstChild
;
8703 frame
= frame
->GetNextSibling();
8705 NS_ASSERTION(count
== frameCount
, "bad line list");
8707 // Next: test that each line has right number of frames on it
8708 for (LineIterator line
= LinesBegin(), line_end
= LinesEnd();
8709 line
!= line_end
;) {
8710 count
= line
->GetChildCount();
8711 frame
= line
->mFirstChild
;
8712 while (--count
>= 0) {
8713 frame
= frame
->GetNextSibling();
8716 if ((line
!= line_end
) && (0 != line
->GetChildCount())) {
8717 NS_ASSERTION(frame
== line
->mFirstChild
, "bad line list");
8722 FrameLines
* overflowLines
= GetOverflowLines();
8723 if (overflowLines
) {
8724 LineIterator line
= overflowLines
->mLines
.begin();
8725 LineIterator line_end
= overflowLines
->mLines
.end();
8726 for (; line
!= line_end
; ++line
) {
8727 if (line
== cursor
) {
8734 NS_ASSERTION(!cursor
, "stale LineCursorProperty");
8737 void nsBlockFrame::VerifyOverflowSituation() {
8738 // Overflow out-of-flows must not have a next-in-flow in floats list or
8740 nsFrameList
* oofs
= GetOverflowOutOfFlows();
8742 for (nsIFrame
* f
: *oofs
) {
8743 nsIFrame
* nif
= f
->GetNextInFlow();
8745 (!GetChildList(FrameChildListID::Float
).ContainsFrame(nif
) &&
8746 !mFrames
.ContainsFrame(nif
)));
8750 // Pushed floats must not have a next-in-flow in floats list or mFrames.
8751 oofs
= GetPushedFloats();
8753 for (nsIFrame
* f
: *oofs
) {
8754 nsIFrame
* nif
= f
->GetNextInFlow();
8756 (!GetChildList(FrameChildListID::Float
).ContainsFrame(nif
) &&
8757 !mFrames
.ContainsFrame(nif
)));
8761 // A child float next-in-flow's parent must be |this| or a next-in-flow of
8762 // |this|. Later next-in-flows must have the same or later parents.
8763 ChildListID childLists
[] = {FrameChildListID::Float
,
8764 FrameChildListID::PushedFloats
};
8765 for (size_t i
= 0; i
< ArrayLength(childLists
); ++i
) {
8766 const nsFrameList
& children
= GetChildList(childLists
[i
]);
8767 for (nsIFrame
* f
: children
) {
8768 nsIFrame
* parent
= this;
8769 nsIFrame
* nif
= f
->GetNextInFlow();
8770 for (; nif
; nif
= nif
->GetNextInFlow()) {
8772 for (nsIFrame
* p
= parent
; p
; p
= p
->GetNextInFlow()) {
8773 if (nif
->GetParent() == p
) {
8781 "next-in-flow is a child of parent earlier in the frame tree?");
8786 nsBlockFrame
* flow
= static_cast<nsBlockFrame
*>(FirstInFlow());
8788 FrameLines
* overflowLines
= flow
->GetOverflowLines();
8789 if (overflowLines
) {
8790 NS_ASSERTION(!overflowLines
->mLines
.empty(),
8791 "should not be empty if present");
8792 NS_ASSERTION(overflowLines
->mLines
.front()->mFirstChild
,
8793 "bad overflow lines");
8794 NS_ASSERTION(overflowLines
->mLines
.front()->mFirstChild
==
8795 overflowLines
->mFrames
.FirstChild(),
8796 "bad overflow frames / lines");
8798 auto checkCursor
= [&](nsLineBox
* cursor
) -> bool {
8802 LineIterator line
= flow
->LinesBegin();
8803 LineIterator line_end
= flow
->LinesEnd();
8804 for (; line
!= line_end
&& line
!= cursor
; ++line
);
8805 if (line
== line_end
&& overflowLines
) {
8806 line
= overflowLines
->mLines
.begin();
8807 line_end
= overflowLines
->mLines
.end();
8808 for (; line
!= line_end
&& line
!= cursor
; ++line
);
8810 return line
!= line_end
;
8812 MOZ_ASSERT(checkCursor(flow
->GetLineCursorForDisplay()),
8813 "stale LineCursorPropertyDisplay");
8814 MOZ_ASSERT(checkCursor(flow
->GetLineCursorForQuery()),
8815 "stale LineCursorPropertyQuery");
8816 flow
= static_cast<nsBlockFrame
*>(flow
->GetNextInFlow());
8820 int32_t nsBlockFrame::GetDepth() const {
8822 nsIFrame
* parent
= GetParent();
8824 parent
= parent
->GetParent();
8830 already_AddRefed
<ComputedStyle
> nsBlockFrame::GetFirstLetterStyle(
8831 nsPresContext
* aPresContext
) {
8832 return aPresContext
->StyleSet()->ProbePseudoElementStyle(
8833 *mContent
->AsElement(), PseudoStyleType::firstLetter
, nullptr, Style());