Bug 1921551 - React to sync sign in flow correctly r=android-reviewers,matt-tighe
[gecko.git] / layout / generic / nsBlockFrame.cpp
blob0cf30859e3ed2a1f63af043f73a805e182e34aa5
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /*
8 * rendering object for CSS display:block, inline-block, and list-item
9 * boxes, also used for various anonymous boxes
12 #include "nsBlockFrame.h"
14 #include "gfxContext.h"
16 #include "mozilla/AppUnits.h"
17 #include "mozilla/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"
28 #include "nsCRT.h"
29 #include "nsCOMPtr.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"
47 #include "prenv.h"
48 #include "nsError.h"
49 #include <algorithm>
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"
64 #include <inttypes.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()) {
78 if (line.IsBlock()) {
79 nsBlockFrame* bf = do_QueryFrame(line.mFirstChild);
80 if (bf) {
81 MarkAllDescendantLinesDirty(bf);
84 line.MarkDirty();
88 static void MarkSameFloatManagerLinesDirty(nsBlockFrame* aBlock) {
89 nsBlockFrame* blockWithFloatMgr = aBlock;
90 while (!blockWithFloatMgr->HasAnyStateBits(NS_BLOCK_BFC)) {
91 nsBlockFrame* bf = do_QueryFrame(blockWithFloatMgr->GetParent());
92 if (!bf) {
93 break;
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);
110 if (!block) {
111 return false;
113 if (block->GetChildList(FrameChildListID::Float).FirstChild()) {
114 return true;
117 for (const auto& line : block->Lines()) {
118 if (line.IsBlock() && BlockHasAnyFloats(line.mFirstChild)) {
119 return true;
122 return false;
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()) {
131 return false;
133 if (aFrame->IsTextFrame()) {
134 return aFrame->StyleVisibility()->IsVisible() &&
135 NS_GET_A(aFrame->StyleText()->mWebkitTextFillColor.CalcColor(
136 aFrame)) != 0;
138 for (nsIFrame* kid : aFrame->PrincipalChildList()) {
139 if (FrameHasVisibleInlineText(kid)) {
140 return true;
143 return false;
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();
150 while (n-- > 0) {
151 if (FrameHasVisibleInlineText(kid)) {
152 return true;
154 kid = kid->GetNextSibling();
156 return false;
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) {
171 nsRect textArea;
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) {
196 nsRect textArea;
197 nsIFrame* kid = aLine->mFirstChild;
198 int32_t n = aLine->GetChildCount();
199 while (n-- > 0) {
200 nsRect kidTextArea = GetFrameTextArea(kid, aBuilder);
201 textArea.OrWith(kidTextArea);
202 kid = kid->GetNextSibling();
205 return textArea;
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)) {
229 continue;
231 bool drawImage = false, drawColor = false;
232 nscolor backgroundColor = nsCSSRendering::DetermineBackgroundColor(
233 pc, style, frame, drawImage, drawColor);
234 if (!drawColor && !drawImage) {
235 continue;
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.
240 continue;
242 if (NS_GET_A(currentBackgroundColor) == 0) {
243 // Try to avoid somewhat expensive math in the common case.
244 currentBackgroundColor = backgroundColor;
245 } else {
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
251 // background.
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);
262 #ifdef DEBUG
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 {
279 const char* name;
280 bool* on;
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;
309 if (firstTime) {
310 firstTime = false;
311 char* flags = PR_GetEnv("GECKO_BLOCK_DEBUG_FLAGS");
312 if (flags) {
313 bool error = false;
314 for (;;) {
315 char* cm = strchr(flags, ',');
316 if (cm) {
317 *cm = '\0';
320 bool found = false;
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) {
325 *(bdf->on) = true;
326 printf("nsBlockFrame: setting %s debug flag on\n", bdf->name);
327 gNoisy = true;
328 found = true;
329 break;
332 if (!found) {
333 error = true;
336 if (!cm) {
337 break;
339 *cm = ',';
340 flags = cm + 1;
342 if (error) {
343 ShowDebugFlags();
349 #endif
351 //----------------------------------------------------------------------
353 // Debugging support code
355 #ifdef DEBUG
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";
376 return "unknown";
379 #endif
381 #ifdef REFLOW_STATUS_COVERAGE
382 static void RecordReflowStatus(bool aChildIsBlock,
383 const nsReflowStatus& aFrameReflowStatus) {
384 static uint32_t record[2];
386 // 0: child-is-block
387 // 1: child-is-inline
388 int index = 0;
389 if (!aChildIsBlock) {
390 index |= 1;
393 // Compute new status
394 uint32_t newS = record[index];
395 if (aFrameReflowStatus.IsInlineBreak()) {
396 if (aFrameReflowStatus.IsInlineBreakBefore()) {
397 newS |= 1;
398 } else if (aFrameReflowStatus.IsIncomplete()) {
399 newS |= 2;
400 } else {
401 newS |= 4;
403 } else if (aFrameReflowStatus.IsIncomplete()) {
404 newS |= 8;
405 } else {
406 newS |= 16;
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]);
415 #endif
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();
445 if (overflowLines) {
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) {
455 ClearLineCursors();
456 DestroyAbsoluteFrames(aContext);
457 nsPresContext* presContext = PresContext();
458 mozilla::PresShell* presShell = presContext->PresShell();
459 if (HasFloats()) {
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();
472 if (overflowLines) {
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);
492 /* virtual */
493 nsILineIterator* nsBlockFrame::GetLineIterator() {
494 nsLineIterator* iter = GetProperty(LineIteratorProperty());
495 if (!iter) {
496 const nsStyleVisibility* visibility = StyleVisibility();
497 iter = new nsLineIterator(mLines,
498 visibility->mDirection == StyleDirection::Rtl);
499 SetProperty(LineIteratorProperty(), iter);
501 return 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 {
511 nsCString str;
512 ListGeneric(str, aPrefix, aFlags);
514 fprintf_stderr(out, "%s <\n", str.get());
516 nsCString pfx(aPrefix);
517 pfx += " ";
519 // Output the lines
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);
533 nestedPfx += " ";
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);
553 #endif
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();
561 return;
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();
573 return;
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);
603 return Some(ascent);
605 continue;
607 nsIFrame* kid = line->mFirstChild;
608 if (aWM.IsOrthogonalTo(kid->GetWritingMode())) {
609 continue;
611 if (aExportContext == BaselineExportContext::LineLayout &&
612 kid->IsTableWrapperFrame()) {
613 // `<table>` in inline-block context does not export any baseline.
614 continue;
616 const auto kidBaselineGroup =
617 aExportContext == BaselineExportContext::LineLayout
618 ? kid->GetDefaultBaselineSharingGroup()
619 : aBaselineGroup;
620 const auto kidBaseline =
621 kid->GetNaturalBaselineBOffset(aWM, kidBaselineGroup, aExportContext);
622 if (!kidBaseline) {
623 continue;
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);
635 return Some(result);
637 return Nothing{};
640 Maybe<nscoord> nsBlockFrame::GetNaturalBaselineBOffset(
641 WritingMode aWM, BaselineSharingGroup aBaselineGroup,
642 BaselineExportContext aExportContext) const {
643 if (StyleDisplay()->IsContainLayout()) {
644 return Nothing{};
647 if (aBaselineGroup == BaselineSharingGroup::First) {
648 return GetBaselineBOffset(LinesBegin(), LinesEnd(), aWM, aBaselineGroup,
649 aExportContext);
652 return GetBaselineBOffset(LinesRBegin(), LinesREnd(), aWM, aBaselineGroup,
653 aExportContext);
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()) +
678 bp.top;
681 /////////////////////////////////////////////////////////////////////////////
682 // Child frame enumeration
684 const nsFrameList& nsBlockFrame::GetChildList(ChildListID aListID) const {
685 switch (aListID) {
686 case FrameChildListID::Principal:
687 return mFrames;
688 case FrameChildListID::Overflow: {
689 FrameLines* overflowLines = GetOverflowLines();
690 return overflowLines ? overflowLines->mFrames : nsFrameList::EmptyList();
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();
708 default:
709 return nsContainerFrame::GetChildList(aListID);
713 void nsBlockFrame::GetChildLists(nsTArray<ChildList>* aLists) const {
714 nsContainerFrame::GetChildLists(aLists);
715 FrameLines* overflowLines = GetOverflowLines();
716 if (overflowLines) {
717 overflowLines->mFrames.AppendIfNonempty(aLists, FrameChildListID::Overflow);
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);
733 /* virtual */
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);
752 return isLastLine;
755 //////////////////////////////////////////////////////////////////////
756 // Reflow methods
758 /* virtual */
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)) {
776 return;
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());
803 return true;
804 }();
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;
832 /* virtual */
833 nscoord nsBlockFrame::MinISize(const IntrinsicSizeInput& aInput) {
834 if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
835 return *containISize;
838 #ifdef DEBUG
839 if (gNoisyIntrinsic) {
840 IndentBy(stdout, gNoiseIndent);
841 ListTag(stdout);
842 printf(": MinISize\n");
844 AutoNoisyIndenter indenter(gNoisyIntrinsic);
845 #endif
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()) {
854 ResolveBidi();
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) {
864 #ifdef DEBUG
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);
871 #endif
872 if (line->IsBlock()) {
873 data.ForceBreak();
874 nsIFrame* kid = line->mFirstChild;
875 const IntrinsicSizeInput kidInput(aInput, kid->GetWritingMode(),
876 GetWritingMode());
877 data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(
878 kidInput.mContext, kid, IntrinsicISizeType::MinISize,
879 kidInput.mPercentageBasisForChildren);
880 data.ForceBreak();
881 } else {
882 if (!curFrame->GetPrevContinuation() && TextIndentAppliesTo(line)) {
883 data.mCurrentLine += StyleText()->mTextIndent.length.Resolve(0);
885 data.mLine = &line;
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(),
891 GetWritingMode());
892 kid->AddInlineMinISize(kidInput, &data);
893 if (whiteSpaceCanWrap && data.mTrailingWhitespace) {
894 data.OptionallyBreak();
898 #ifdef DEBUG
899 if (gNoisyIntrinsic) {
900 IndentBy(stdout, gNoiseIndent);
901 printf("min: [prevLines=%d currentLine=%d]\n", data.mPrevLines,
902 data.mCurrentLine);
904 #endif
907 data.ForceBreak();
908 return data.mPrevLines;
911 /* virtual */
912 nscoord nsBlockFrame::PrefISize(const IntrinsicSizeInput& aInput) {
913 if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
914 return *containISize;
917 #ifdef DEBUG
918 if (gNoisyIntrinsic) {
919 IndentBy(stdout, gNoiseIndent);
920 ListTag(stdout);
921 printf(": PrefISize\n");
923 AutoNoisyIndenter indenter(gNoisyIntrinsic);
924 #endif
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()) {
933 ResolveBidi();
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) {
941 #ifdef DEBUG
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);
948 #endif
949 if (line->IsBlock()) {
950 nsIFrame* kid = line->mFirstChild;
951 StyleClear clearType;
952 if (!data.mLineIsEmpty || BlockCanIntersectFloats(kid)) {
953 clearType = StyleClear::Both;
954 } else {
955 clearType = kid->StyleDisplay()->mClear;
957 data.ForceBreak(clearType);
958 const IntrinsicSizeInput kidInput(aInput, kid->GetWritingMode(),
959 GetWritingMode());
960 data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(
961 kidInput.mContext, kid, IntrinsicISizeType::PrefISize,
962 kidInput.mPercentageBasisForChildren);
963 data.ForceBreak();
964 } else {
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;
973 data.mLine = &line;
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(),
979 GetWritingMode());
980 kid->AddInlinePrefISize(kidInput, &data);
983 #ifdef DEBUG
984 if (gNoisyIntrinsic) {
985 IndentBy(stdout, gNoiseIndent);
986 printf("pref: [prevLines=%d currentLine=%d]\n", data.mPrevLines,
987 data.mCurrentLine);
989 #endif
992 data.ForceBreak();
993 return data.mPrevLines;
996 nsRect nsBlockFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const {
997 // be conservative
998 if (Style()->HasTextDecorationLines()) {
999 return InkOverflowRect();
1001 return ComputeSimpleTightBounds(aDrawTarget);
1004 /* virtual */
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);
1012 *aX = 0;
1013 *aXMost = 0;
1015 nsresult rv;
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()) {
1024 data.ForceBreak();
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);
1030 } else {
1031 if (!curFrame->GetPrevContinuation() && TextIndentAppliesTo(line)) {
1032 data.mCurrentLine += StyleText()->mTextIndent.length.Resolve(0);
1034 data.mLine = &line;
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(),
1043 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,
1047 &childXMost);
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);
1056 data.ForceBreak();
1058 return NS_OK;
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;
1076 if (aCanGrow) {
1077 NS_ASSERTION(
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");
1082 NS_ASSERTION(
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");
1087 } else {
1088 NS_ASSERTION(
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()) {
1114 return cbSize;
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) {
1137 return cbSize;
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.
1145 // the parent.
1146 MOZ_ASSERT(lastButOneRI == &aReflowInput);
1147 return cbSize;
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()) {
1153 return cbSize;
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) {
1161 cbSize.ISize(aWM) =
1162 std::max(0, lastRISize.ISize(aWM) + lastRIPadding.IStartEnd(aWM));
1164 if (lastRISize.BSize(aWM) != NS_UNCONSTRAINEDSIZE) {
1165 cbSize.BSize(aWM) =
1166 std::max(0, lastRISize.BSize(aWM) + lastRIPadding.BStartEnd(aWM));
1169 return cbSize;
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)) {
1181 return block;
1184 return nullptr;
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) {
1194 return false;
1197 if (!aFrame->HasAnyStateBits(NS_BLOCK_BFC)) {
1198 return false;
1201 if (StaticPrefs::layout_css_webkit_line_clamp_block_enabled() ||
1202 aFrame->PresContext()->Document()->ChromeRulesEnabled()) {
1203 return true;
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;
1223 }();
1224 return origDisplay.Inside() == StyleDisplayInside::WebkitBox;
1227 bool nsBlockFrame::IsInLineClampContext() const {
1228 if (IsLineClampRoot(this)) {
1229 return true;
1231 const nsBlockFrame* cur = this;
1232 while (GetAsLineClampDescendant(cur)) {
1233 cur = do_QueryFrame(cur->GetParent());
1234 if (!cur) {
1235 return false;
1237 if (IsLineClampRoot(cur)) {
1238 return true;
1241 return false;
1244 bool nsBlockFrame::MaybeHasFloats() const {
1245 if (HasFloats()) {
1246 return true;
1248 if (HasPushedFloats()) {
1249 return true;
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 {
1261 public:
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()) {
1267 Advance();
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.
1278 void Next() {
1279 MOZ_ASSERT(mCur != mEnd && mCurrentFrame,
1280 "Don't call Next() when the iterator is at the end");
1281 ++mCur;
1282 Advance();
1285 private:
1286 void Advance() {
1287 for (;;) {
1288 if (mCur == mEnd) {
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;
1293 break;
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;
1302 ++next;
1303 mStack.AppendElement(std::make_pair(mCurrentFrame, next));
1304 mCur = child->LinesBegin();
1305 mEnd = child->LinesEnd();
1306 mCurrentFrame = child;
1307 } else {
1308 // Some kind of frame we shouldn't descend into.
1309 ++mCur;
1311 } else {
1312 MOZ_ASSERT(mCur->IsInline());
1313 break;
1318 // The current line within the current block.
1320 // When this is equal to mEnd, the iterator is at its end, and mCurrentFrame
1321 // is set to null.
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
1331 // exist blocks.
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)) {
1340 return true;
1344 return false;
1347 aFrame->RemoveStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS);
1349 for (auto& line : aFrame->Lines()) {
1350 if (line.HasLineClampEllipsis()) {
1351 line.ClearHasLineClampEllipsis();
1352 return true;
1356 // We didn't find a line with the ellipsis; it must have been deleted already.
1357 return true;
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);
1367 return;
1370 MarkInReflow();
1371 DO_GLOBAL_REFLOW_COUNT("nsBlockFrame");
1372 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
1374 #ifdef DEBUG
1375 if (gNoisyReflow) {
1376 IndentBy(stdout, gNoiseIndent);
1377 ListTag(stdout);
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) {
1386 start = PR_Now();
1387 ctc = nsLineBox::GetCtorCount();
1389 #endif
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;
1419 } else {
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)) {
1435 return;
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.
1442 ClearLineCursors();
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.
1466 bool tryBalance =
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 {
1500 int32_t n = 0;
1501 for (auto iter = mLines.begin(); iter != mLines.end(); ++iter) {
1502 if (++n > aLimit || iter->IsBlock()) {
1503 return -1;
1506 return n;
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++) {
1516 ++iter;
1518 nsIFrame* firstChild = iter->mFirstChild;
1519 if (!firstChild) {
1520 return BalanceTarget{};
1522 nsIContent* content = firstChild->GetContent();
1523 if (!content) {
1524 return BalanceTarget{};
1526 int32_t offset = 0;
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,
1543 needFloatManager);
1544 while (true) {
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
1547 // reflow.
1548 nsFloatManager::SavedState floatManagerState;
1549 aReflowInput.mFloatManager->PushState(&floatManagerState);
1551 aMetrics = ReflowOutput(aMetrics.GetWritingMode());
1552 reflowStatus =
1553 TrialReflow(aPresContext, aMetrics, aReflowInput, trialState);
1555 // Do we need to start a `text-wrap: balance` iteration?
1556 if (tryBalance) {
1557 tryBalance = false;
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) {
1561 break;
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.
1568 break;
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);
1574 balanceStep /= 2;
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);
1584 if (t.mContent) {
1585 balanceTarget = t;
1590 // Restore initial floatManager state for a new trial with updated inset.
1591 aReflowInput.mFloatManager->PopState(&floatManagerState);
1592 continue;
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) {
1599 return false;
1601 if (balanceTarget.mContent) {
1602 auto t = getClampPosition(aReflowInput.mStyleDisplay->mWebkitLineClamp);
1603 return t == balanceTarget;
1605 int32_t numLines =
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
1613 // iteration.
1614 if (balanceStep > 0) {
1615 if (trialSucceeded()) {
1616 trialState.ResetForBalance(balanceStep);
1617 } else {
1618 trialState.ResetForBalance(-balanceStep);
1620 balanceStep /= 2;
1622 aReflowInput.mFloatManager->PopState(&floatManagerState);
1623 continue;
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()) {
1630 break;
1632 trialState.ResetForBalance(-1);
1634 aReflowInput.mFloatManager->PopState(&floatManagerState);
1635 continue;
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.
1641 break;
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;
1659 if (deltaX != 0) {
1660 // We compute our lines and markers' overflow areas later in
1661 // ComputeOverflowAreas(), so we don't need to adjust their overflow areas
1662 // here.
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();
1675 if (markerList) {
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
1708 // condition 1.
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();
1729 } else {
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
1735 // reflow.
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);
1746 } else {
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
1756 // changed.
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
1765 // reflow.
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
1781 // frames.
1782 SetupLineCursorForQuery();
1783 absoluteContainer->Reflow(this, aPresContext, aReflowInput, reflowStatus,
1784 containingBlock, flags,
1785 &aMetrics.mOverflowAreas);
1789 FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
1791 aStatus = reflowStatus;
1793 #ifdef DEBUG
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));
1802 if (gNoisyReflow) {
1803 IndentBy(stdout, gNoiseIndent);
1804 ListTag(stdout);
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);
1817 printf("\n");
1820 if (gLameReflowMetrics) {
1821 PRTime end = PR_Now();
1823 int32_t ectc = nsLineBox::GetCtorCount();
1824 int32_t numLines = mLines.size();
1825 if (!numLines) {
1826 numLines = 1;
1828 PRTime delta, perLineDelta, lines;
1829 lines = int64_t(numLines);
1830 delta = end - start;
1831 perLineDelta = delta / lines;
1833 ListTag(stdout);
1834 char buf[400];
1835 SprintfLiteral(buf,
1836 ": %" PRId64 " elapsed (%" PRId64
1837 " per line) (%d lines; %d new lines)",
1838 delta, perLineDelta, numLines, ectc - ctc);
1839 printf("%s\n", buf);
1841 #endif
1844 nsReflowStatus nsBlockFrame::TrialReflow(nsPresContext* aPresContext,
1845 ReflowOutput& aMetrics,
1846 const ReflowInput& aReflowInput,
1847 TrialReflowState& aTrialState) {
1848 #ifdef DEBUG
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));
1856 #endif
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
1898 // parent resizing.
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);
1909 } else {
1910 LazyMarkLinesDirty();
1913 // Now reflow...
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());
1927 while (nif) {
1928 if (nif->HasPushedFloatsFromPrevContinuation()) {
1929 if (nif->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
1930 state.mReflowStatus.SetOverflowIncomplete();
1931 } else {
1932 state.mReflowStatus.SetIncomplete();
1934 break;
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
1961 // first line box.
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();
1977 bool havePosition =
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...
1994 LogicalRect bbox =
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();
2017 CheckFloats(state);
2019 // Compute our final size (for this trial layout)
2020 aTrialState.mBlockEndEdgeOfChildren =
2021 ComputeFinalSize(aReflowInput, state, aMetrics);
2022 aTrialState.mContainerWidth = state.ContainerSize().width;
2024 // Align content
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()) {
2033 return false;
2035 if (line.HasClearance()) {
2036 return true;
2039 return false;
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()) {
2064 iter.Next();
2065 continue;
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;
2072 break;
2075 if (--aLineNumber == 0) {
2076 // This is our target line. Continue looping to confirm that we
2077 // have another line after us.
2078 target = line;
2079 targetFrame = iter.GetCurrentFrame();
2082 iter.Next();
2085 if (!foundFollowingLine) {
2086 aFrame = nullptr;
2087 return nullptr;
2090 MOZ_ASSERT(target);
2091 MOZ_ASSERT(targetFrame);
2093 aFrame = targetFrame;
2094 return target;
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);
2106 if (!line) {
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()) {
2118 edge +=
2119 f->GetLogicalPosition(f->GetParent()->GetSize()).B(f->GetWritingMode());
2122 return edge;
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
2131 ListTag(stdout);
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));
2136 #endif
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;
2164 } else {
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
2202 // previous margin.
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
2242 // below.
2243 nscoord contentBSize = *containBSize;
2244 nscoord autoBSize =
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();
2279 } else {
2280 aState.mReflowStatus.SetIncomplete();
2283 finalSize.BSize(wm) = bSize;
2284 } else {
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();
2307 } else {
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).
2334 bool found;
2335 nscoord bSize = GetProperty(FragStretchBSizeProperty(), &found);
2336 if (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()) {
2345 bool found;
2346 nscoord cbSize = GetProperty(BClampMarginBoxMinSizeProperty(), &found);
2347 if (found) {
2348 auto marginBoxBSize =
2349 finalSize.BSize(wm) +
2350 aReflowInput.ComputedLogicalMargin(wm).BStartEnd(wm);
2351 auto overflow = marginBoxBSize - cbSize;
2352 if (overflow > 0) {
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);
2366 } else {
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()) {
2379 return;
2382 StyleAlignFlags alignment = StylePosition()->mAlignContent.primary;
2383 alignment &= ~StyleAlignFlags::FLAG_BITS;
2385 // Short circuit
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
2394 return;
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.
2402 // Calculate shift
2403 nscoord shift = 0;
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);
2415 if (isCentered) {
2416 shift = shift / 2;
2419 // else: zero shift if start-aligned or if fragmented
2421 nscoord delta = shift - aState.mAlignContentShift;
2422 if (delta) {
2423 // Shift children
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);
2438 if (shift) {
2439 // Cache shift
2440 SetProperty(AlignContentShift(), shift);
2441 } else {
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;
2486 } else {
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;
2497 } else {
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()) {
2515 return;
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
2525 // unaffected.
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);
2532 } else {
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());
2557 #endif
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) {
2595 bool found;
2596 nscoord blockEndEdgeOfChildren =
2597 GetProperty(BlockEndEdgeOfChildrenProperty(), &found);
2598 if (found) {
2599 ConsiderBlockEndEdgeOfChildren(aOverflowAreas, blockEndEdgeOfChildren,
2600 StyleDisplay());
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.
2605 ClearLineCursors();
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);
2619 break;
2623 RemoveStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
2627 void nsBlockFrame::MarkLineDirty(LineIterator aLine,
2628 const nsLineList* aLineList) {
2629 // Mark aLine dirty
2630 aLine->MarkDirty();
2631 aLine->SetInvalidateTextRuns(true);
2632 #ifdef DEBUG
2633 if (gNoisyReflow) {
2634 IndentBy(stdout, gNoiseIndent);
2635 ListTag(stdout);
2636 printf(": mark line %p dirty\n", static_cast<void*>(aLine.get()));
2638 #endif
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);
2647 #ifdef DEBUG
2648 if (gNoisyReflow) {
2649 IndentBy(stdout, gNoiseIndent);
2650 ListTag(stdout);
2651 printf(": mark prev-line %p dirty\n",
2652 static_cast<void*>(aLine.prev().get()));
2654 #endif
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,
2665 nsIFrame* aFrame) {
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
2679 // border-edge.
2680 !StylePadding()->mPadding.Get(eSideLeft).HasPercent();
2682 #ifdef DEBUG
2683 if (gDisableResizeOpt) {
2684 tryAndSkipLines = false;
2686 if (gNoisyReflow) {
2687 if (!tryAndSkipLines) {
2688 IndentBy(stdout, gNoiseIndent);
2689 ListTag(stdout);
2690 printf(": marking all lines dirty: availISize=%d\n",
2691 aState.mReflowInput.AvailableISize());
2694 #endif
2696 if (tryAndSkipLines) {
2697 WritingMode wm = aState.mReflowInput.GetWritingMode();
2698 nscoord newAvailISize =
2699 aState.mReflowInput.ComputedLogicalBorderPadding(wm).IStart(wm) +
2700 aState.mReflowInput.ComputedISize();
2702 #ifdef DEBUG
2703 if (gNoisyReflow) {
2704 IndentBy(stdout, gNoiseIndent);
2705 ListTag(stdout);
2706 printf(": trying to avoid marking all lines dirty\n");
2708 #endif
2710 for (LineIterator line = LinesBegin(), line_end = LinesEnd();
2711 line != line_end; ++line) {
2712 // We let child blocks make their own decisions the same
2713 // way we are here.
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)) {
2720 line->MarkDirty();
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 ");
2728 #endif
2729 #ifdef DEBUG
2730 if (gNoisyReflow && !line->IsDirty()) {
2731 IndentBy(stdout, gNoiseIndent + 1);
2732 printf(
2733 "skipped: line=%p next=%p %s %s%s%s clearTypeBefore/After=%s/%s "
2734 "xmost=%d\n",
2735 static_cast<void*>(line.get()),
2736 static_cast<void*>(
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()),
2744 line->IEnd());
2746 #endif
2748 } else {
2749 // Mark everything dirty
2750 for (auto& line : Lines()) {
2751 line.MarkDirty();
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,
2770 nsLineBox* aLine,
2771 nscoord aDeltaBCoord) {
2772 nsFloatManager* floatManager = aState.FloatManager();
2773 NS_ASSERTION(
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()) {
2782 return;
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
2792 // layout.
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);
2801 bool isDirty =
2802 floatManager->IntersectsDamage(lineBCoordBefore, lineBCoordAfter) ||
2803 floatManager->IntersectsDamage(lineBCoordCombinedBefore,
2804 lineBCoordCombinedAfter);
2805 if (isDirty) {
2806 aLine->MarkDirty();
2807 return;
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.
2818 aLine->MarkDirty();
2819 } else {
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());
2828 #endif
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()) {
2834 aLine->MarkDirty();
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) {
2857 nsFrameList list;
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) {
2871 #ifdef DEBUG
2872 if (nsBlockFrame::gNoisyReflow) {
2873 nsRect ovis(aLine->InkOverflowRect());
2874 nsRect oscr(aLine->ScrollableOverflowRect());
2875 nsBlockFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent + aDeltaIndent);
2876 printf(
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());
2886 #endif
2889 static bool LinesAreEmpty(const nsLineList& aList) {
2890 for (const auto& line : aList) {
2891 if (!line.IsEmpty()) {
2892 return false;
2895 return true;
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;
2905 #ifdef DEBUG
2906 if (gNoisyReflow) {
2907 IndentBy(stdout, gNoiseIndent);
2908 ListTag(stdout);
2909 printf(": reflowing dirty lines");
2910 printf(" computedISize=%d\n", aState.mReflowInput.ComputedISize());
2912 AutoNoisyIndenter indent(gNoisyReflow);
2913 #endif
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 &&
2922 GetNextInFlow() &&
2923 aState.mReflowInput.AvailableBSize() >
2924 GetLogicalSize().BSize(aState.mReflowInput.GetWritingMode())) {
2925 LineIterator lastLine = LinesEnd();
2926 if (lastLine != LinesBegin()) {
2927 --lastLine;
2928 lastLine->MarkDirty();
2931 // the amount by which we will slide the current line if it is not
2932 // dirty
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 =
2941 HasFloats() &&
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
2950 // page names.
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 "
2978 "reflow");
2981 // Reflow the lines that are already ours
2982 for (; line != line_end; ++line, aState.AdvanceToNextLine()) {
2983 DumpLine(aState, line, deltaBCoord, 0);
2984 #ifdef DEBUG
2985 AutoNoisyIndenter indent2(gNoisyReflow);
2986 #endif
2988 if (selfDirty) {
2989 line->MarkDirty();
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()) {
2998 line->MarkDirty();
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
3013 // BRs.
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) {
3030 line->MarkDirty();
3032 } else {
3033 // Reflow the line if the line might have clearance now.
3034 if (result != ClearFloatsResult::BCoordNoChange) {
3035 line->MarkDirty();
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.
3047 line->MarkDirty();
3049 inlineFloatClearType = StyleClear::None;
3052 bool previousMarginWasDirty = line->IsPreviousMarginDirty();
3053 if (previousMarginWasDirty) {
3054 // If the previous margin is dirty, reflow the current line
3055 line->MarkDirty();
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.
3065 line->MarkDirty();
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();
3078 if (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
3090 // their entirety.
3091 line->MarkDirty();
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.
3098 line->MarkDirty();
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.
3102 line->MarkDirty();
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.
3107 line->MarkDirty();
3113 if (!line->IsDirty()) {
3114 // See if there's any reflow damage that requires that we mark the
3115 // line dirty.
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)) {
3131 line->MarkDirty();
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
3142 // 'inline-block'.
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;
3163 line->MarkDirty();
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
3180 // this line.
3181 if (line->IsDirty()) {
3182 NS_ASSERTION(
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 &&
3203 line->BSize() == 0;
3205 // Compute the dirty lines "before" BEnd, after factoring in
3206 // the running deltaBCoord value - the running value is implicit in
3207 // aState.mBCoord.
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);
3225 } else {
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()) {
3232 line->MarkDirty();
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;
3243 if (!keepGoing) {
3244 DumpLine(aState, line, deltaBCoord, -1);
3245 if (0 == line->GetChildCount()) {
3246 DeleteLine(aState, line, line_end);
3248 break;
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
3264 // now does
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
3293 // used.
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);
3300 } else {
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
3304 // is enough
3306 lastLineMovedUp = deltaBCoord < 0;
3308 if (deltaBCoord != 0) {
3309 SlideLine(aState, line, deltaBCoord);
3310 } else {
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.
3327 } else {
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
3391 // the last line.
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
3409 // pulled up into,
3410 // -- it's an incremental reflow of a descendant
3411 // -- and we didn't reflow any floats (so the available space
3412 // didn't change)
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
3425 // optimization.)
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()) {
3434 skipPull = true;
3439 if (skipPull && aState.mNextInFlow) {
3440 NS_ASSERTION(heightConstrained, "Height should be constrained here\n");
3441 if (aState.mNextInFlow->IsTrueOverflowContainer()) {
3442 aState.mReflowStatus.SetOverflowIncomplete();
3443 } else {
3444 aState.mReflowStatus.SetIncomplete();
3448 if (!skipPull && aState.mNextInFlow) {
3449 // Pull data from a next-in-flow if there's still room for more
3450 // content here.
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,
3458 &pulledFrames);
3459 ClearLineCursors();
3460 } else {
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());
3466 continue;
3468 bool last =
3469 RemoveFirstLine(overflowLines->mLines, overflowLines->mFrames,
3470 &pulledLine, &pulledFrames);
3471 if (last) {
3472 nextInFlow->DestroyOverflowLines();
3476 if (pulledFrames.IsEmpty()) {
3477 // The line is empty. Try the next one.
3478 NS_ASSERTION(
3479 pulledLine->GetChildCount() == 0 && !pulledLine->mFirstChild,
3480 "bad empty line");
3481 nextInFlow->FreeLineBox(pulledLine);
3482 continue;
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);
3514 #ifdef DEBUG
3515 AutoNoisyIndenter indent2(gNoisyReflow);
3516 #endif
3518 if (aState.mPresContext->HasPendingInterrupt()) {
3519 MarkLineDirtyForInterrupt(line);
3520 } else {
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()) {
3529 line->MarkDirty();
3530 keepGoing = false;
3531 aState.mReflowStatus.SetIncomplete();
3532 break;
3535 DumpLine(aState, line, deltaBCoord, -1);
3536 if (!keepGoing) {
3537 if (0 == line->GetChildCount()) {
3538 DeleteLine(aState, line, line_end);
3540 break;
3543 if (LineHasClear(line.get())) {
3544 foundAnyClears = true;
3547 if (aState.mPresContext->CheckForInterrupt(this)) {
3548 MarkLineDirtyForInterrupt(line);
3549 break;
3552 // If this is an inline frame then its time to stop
3553 ++line;
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) {
3583 nscoord ascent;
3584 WritingMode wm = aState.mReflowInput.GetWritingMode();
3585 if (nsLayoutUtils::GetFirstLineBaseline(wm, marker, &ascent)) {
3586 metrics.SetBlockStartAscent(ascent);
3587 } else {
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;
3599 aState.mBCoord +=
3600 std::max(minAscent, metrics.BlockStartAscent()) +
3601 std::max(minDescent, metrics.BSize(wm) - metrics.BlockStartAscent());
3603 nscoord offset = minAscent - metrics.BlockStartAscent();
3604 if (offset > 0) {
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);
3616 } else {
3617 RemoveStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN);
3620 #ifdef DEBUG
3621 VerifyLines(true);
3622 VerifyOverflowSituation();
3623 if (gNoisyReflow) {
3624 IndentBy(stdout, gNoiseIndent - 1);
3625 ListTag(stdout);
3626 printf(": done reflowing dirty lines (status=%s)\n",
3627 ToString(aState.mReflowStatus).c_str());
3629 #endif
3631 return usedOverflowWrap;
3634 void nsBlockFrame::MarkLineDirtyForInterrupt(nsLineBox* aLine) {
3635 aLine->MarkDirty();
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
3643 // later.
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();
3655 } else {
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);
3663 if (bf) {
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);
3679 FreeLineBox(line);
3680 ClearLineCursors();
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
3708 // case as well.
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)) {
3715 return false;
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);
3722 } else {
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());
3758 NS_ASSERTION(
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;
3775 return nullptr;
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
3794 // we stop pulling.
3795 return nullptr;
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);
3817 } else {
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;
3828 } else {
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);
3839 #ifdef DEBUG
3840 VerifyLines(true);
3841 VerifyOverflowSituation();
3842 #endif
3844 return frame;
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) {
3861 return;
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;
3877 if (!kid) {
3878 return;
3881 WritingMode wm = GetWritingMode();
3882 LogicalPoint translation(wm, 0, aDeltaBCoord);
3884 if (aLine->IsBlock()) {
3885 if (aDeltaBCoord) {
3886 kid->MovePositionBy(wm, translation);
3889 // Make sure the frame's view and any child views are updated
3890 nsContainerFrame::PlaceFrameView(kid);
3891 } else {
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();
3897 while (--n >= 0) {
3898 if (aDeltaBCoord) {
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()) {
3918 return false;
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;
3929 /* virtual */
3930 bool nsBlockFrame::IsSelfEmpty() {
3931 if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
3932 return true;
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)) {
3939 return false;
3942 WritingMode wm = GetWritingMode();
3943 const nsStylePosition* position = StylePosition();
3945 if (IsNonAutoNonZeroBSize(position->MinBSize(wm)) ||
3946 IsNonAutoNonZeroBSize(position->BSize(wm))) {
3947 return false;
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()) {
3956 return false;
3959 const nsStyleBorder* border = StyleBorder();
3960 const nsStylePadding* padding = StylePadding();
3962 if (border->GetComputedBorderWidth(wm.PhysicalSide(LogicalSide::BStart)) !=
3963 0 ||
3964 border->GetComputedBorderWidth(wm.PhysicalSide(LogicalSide::BEnd)) != 0 ||
3965 !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBStart(wm)) ||
3966 !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBEnd(wm))) {
3967 return false;
3970 if (HasOutsideMarker() && !MarkerIsEmpty()) {
3971 return false;
3974 return true;
3977 bool nsBlockFrame::CachedIsEmpty() {
3978 if (!IsSelfEmpty()) {
3979 return false;
3981 for (auto& line : mLines) {
3982 if (!line.CachedIsEmpty()) {
3983 return false;
3986 return true;
3989 bool nsBlockFrame::IsEmpty() {
3990 if (!IsSelfEmpty()) {
3991 return false;
3994 return LinesAreEmpty(mLines);
3997 bool nsBlockFrame::ShouldApplyBStartMargin(BlockReflowState& aState,
3998 nsLineBox* aLine) {
3999 if (aLine->mFirstChild->IsPageBreakFrame()) {
4000 // A page break frame consumes margins adjacent to it.
4001 // https://drafts.csswg.org/css-break/#break-margins
4002 return false;
4005 if (aState.mFlags.mShouldApplyBStartMargin) {
4006 // Apply short-circuit check to avoid searching the line list
4007 return true;
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
4013 // applies.
4014 aState.mFlags.mShouldApplyBStartMargin = true;
4015 return 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;
4028 return true;
4030 // No need to apply the block-start margin if the line has floats. We
4031 // should collapse anyway (bug 44419)
4032 ++line;
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).
4040 return false;
4043 void nsBlockFrame::ReflowBlockFrame(BlockReflowState& aState,
4044 LineIterator aLine,
4045 bool* aKeepReflowGoing) {
4046 MOZ_ASSERT(*aKeepReflowGoing, "bad caller");
4048 nsIFrame* frame = aLine->mFirstChild;
4049 if (!frame) {
4050 NS_ASSERTION(false, "program error - unexpected empty line");
4051 return;
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);
4062 return;
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
4106 // assumption.
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
4121 // reflowed.
4122 return;
4125 if (treatWithClearance) {
4126 applyBStartMargin = true;
4129 nsIFrame* clearanceFrame = nullptr;
4130 const nscoord startingBCoord = aState.mBCoord;
4131 const CollapsingMargin incomingMargin = aState.mPrevBEndMargin;
4132 nscoord clearance;
4133 // Save the original position of the frame so that we can reposition
4134 // its view as needed.
4135 nsPoint originalPosition = frame->GetPosition();
4136 while (true) {
4137 clearance = 0;
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,
4159 availSpace);
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
4178 // fixed.
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
4188 // first pass.
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
4206 // this time
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
4234 // aState.mBCoord.
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
4244 } else {
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));
4259 // The check for
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
4263 // needed.
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);
4276 } else {
4277 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
4279 return;
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) {
4325 contentBSize =
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),
4349 cbSize);
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;
4363 do {
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);
4383 if (mayNeedRetry) {
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
4397 // child block.
4398 break;
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
4404 // reflow again.
4405 if (!floatAvoidingBlock) {
4406 break;
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),
4415 "yikes");
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.
4431 break;
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)) {
4440 advanced = true;
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)) {
4456 break;
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
4471 // reflowed.
4472 return;
4475 if (advanced) {
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;
4480 bStartMargin = 0;
4481 treatWithClearance = true; // avoid hitting test above
4482 clearance = 0;
4485 childReflowInput.reset();
4486 childReflowInput.emplace(
4487 aState.mPresContext, aState.mReflowInput, frame,
4488 availSpace.Size(wm).ConvertTo(frame->GetWritingMode(), wm));
4489 } while (true);
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;
4497 continue;
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");
4510 return;
4513 #if defined(REFLOW_STATUS_COVERAGE)
4514 RecordReflowStatus(true, frameReflowStatus);
4515 #endif
4517 if (frameReflowStatus.IsInlineBreakBefore()) {
4518 // None of the child block fits.
4519 if (ShouldAvoidBreakInside(aState.mReflowInput)) {
4520 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
4521 } else {
4522 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
4524 } else {
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;
4537 *aKeepReflowGoing =
4538 brc.PlaceBlock(*childReflowInput, forceFit, aLine.get(),
4539 collapsedBEndMargin, overflowAreas, frameReflowStatus);
4540 if (!frameReflowStatus.IsFullyComplete() &&
4541 ShouldAvoidBreakInside(aState.mReflowInput)) {
4542 *aKeepReflowGoing = false;
4543 aLine->MarkDirty();
4546 if (aLine->SetCarriedOutBEndMargin(collapsedBEndMargin)) {
4547 LineIterator nextLine = aLine;
4548 ++nextLine;
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
4573 // block
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
4590 // one.
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
4608 // first case.
4609 if (!madeContinuation) {
4610 nsBlockFrame* nifBlock = do_QueryFrame(nextFrame->GetParent());
4611 NS_ASSERTION(
4612 nifBlock,
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)) {
4616 line.MarkDirty();
4617 break;
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
4628 ListTag(stdout);
4629 printf(": reflow incomplete, frame=");
4630 frame->ListTag(stdout);
4631 printf(" prevBEndMargin=%d, setting to zero\n",
4632 aState.mPrevBEndMargin.get());
4633 #endif
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
4651 ListTag(stdout);
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());
4656 #endif
4657 aState.mPrevBEndMargin = collapsedBEndMargin;
4659 } else { // frame is fully complete
4660 #ifdef NOISY_BLOCK_DIR_MARGINS
4661 ListTag(stdout);
4662 printf(": reflow complete for ");
4663 frame->ListTag(stdout);
4664 printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n",
4665 aState.mPrevBEndMargin.get(), collapsedBEndMargin.get());
4666 #endif
4667 aState.mPrevBEndMargin = collapsedBEndMargin;
4669 #ifdef NOISY_BLOCK_DIR_MARGINS
4670 ListTag(stdout);
4671 printf(": frame=");
4672 frame->ListTag(stdout);
4673 printf(" carriedOutBEndMargin=%d collapsedBEndMargin=%d => %d\n",
4674 brc.GetCarriedOutBEndMargin().get(), collapsedBEndMargin.get(),
4675 aState.mPrevBEndMargin.get());
4676 #endif
4677 } else {
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);
4691 } else {
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);
4709 #ifdef DEBUG
4710 VerifyLines(true);
4711 #endif
4714 // Returns true if an overflow-wrap break was used.
4715 bool nsBlockFrame::ReflowInlineFrames(BlockReflowState& aState,
4716 LineIterator aLine,
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;
4731 do {
4732 nscoord availableSpaceBSize = 0;
4733 aState.mLineBSize.reset();
4734 do {
4735 bool allowPullUp = true;
4736 nsIFrame* forceBreakInFrame = nullptr;
4737 int32_t forceBreakOffset = -1;
4738 gfxBreakPriority forceBreakPriority = gfxBreakPriority::eNoBreak;
4739 do {
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
4769 // correct.
4770 forceBreakInFrame = lineLayout.GetLastOptionalBreakPosition(
4771 &forceBreakOffset, &forceBreakPriority);
4772 } else {
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,
4793 LineIterator aLine,
4794 bool* aKeepReflowGoing) {
4795 aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
4796 // Reflow the line again when we reflow at our new position.
4797 aLine->MarkDirty();
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
4810 // constrained.
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());
4840 #endif
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);
4849 nscoord availBSize;
4850 if (aState.mReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
4851 availBSize = NS_UNCONSTRAINEDSIZE;
4852 } else {
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;
4881 int32_t i;
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!
4895 for (i = 0;
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.
4904 ++aLine;
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);
4912 ClearLineCursors();
4914 --aLine;
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
4923 if (aAllowPullUp) {
4924 // Pull frames and reflow them until we can't
4925 while (LineReflowStatus::OK == lineReflowStatus) {
4926 frame = PullFrame(aState, aLine);
4927 if (!frame) {
4928 break;
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();
4941 } else {
4942 break;
4947 ClearLineCursors();
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()) {
4956 NS_WARNING(
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;
4962 if (needsBackup) {
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
4966 // re-do the line.
4967 if (aLineLayout.HasOptionalBreakPosition()) {
4968 // We can back up!
4969 lineReflowStatus = LineReflowStatus::RedoNoPull;
4971 } else {
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
4980 // the floats.
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
4986 // page/column.
4987 NS_ASSERTION(
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
5000 // state.
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;
5006 } else {
5007 // Move it down by one dev pixel.
5008 aState.mBCoord += aState.mPresContext->DevPixelsToAppUnits(1);
5011 aFloatAvailableSpace = aState.GetFloatAvailableSpace();
5012 } else {
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
5015 // next to a float.
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
5023 // past the float.
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
5034 // variant for us.
5038 #ifdef DEBUG
5039 if (gNoisyReflow) {
5040 printf("Line reflow status = %s\n",
5041 LineReflowStatusToString(lineReflowStatus));
5043 #endif
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) {
5081 MOZ_ASSERT(aFrame);
5082 *aLineReflowStatus = LineReflowStatus::OK;
5084 #ifdef NOISY_FIRST_LETTER
5085 ListTag(stdout);
5086 printf(": reflowing ");
5087 aFrame->ListTag(stdout);
5088 printf(" reflowingFirstLetter=%s\n",
5089 aLineLayout.GetFirstLetterStyleOK() ? "on" : "off");
5090 #endif
5092 if (aFrame->IsPlaceholderFrame()) {
5093 auto ph = static_cast<nsPlaceholderFrame*>(aFrame);
5094 ph->ForgetLineIsEmptySoFar();
5097 // Reflow the inline frame
5098 nsReflowStatus frameReflowStatus;
5099 bool pushedFrame;
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());
5109 #endif
5111 #if defined(REFLOW_STATUS_COVERAGE)
5112 RecordReflowStatus(false, frameReflowStatus);
5113 #endif
5115 // Send post-reflow notification
5116 aState.mPrevChild = aFrame;
5118 /* XXX
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.
5122 see bug 22496
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;
5146 } else {
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
5154 if (pushedFrame) {
5155 aLine->SetLineWrapped(true);
5158 } else {
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(),
5179 aLineReflowStatus);
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(),
5208 aLineReflowStatus);
5213 bool nsBlockFrame::CreateContinuationFor(BlockReflowState& aState,
5214 nsLineBox* aLine, nsIFrame* aFrame) {
5215 nsIFrame* newFrame = nullptr;
5217 if (!aFrame->GetNextInFlow()) {
5218 newFrame =
5219 PresShell()->FrameConstructor()->CreateContinuingFrame(aFrame, this);
5221 mFrames.InsertFrame(nullptr, aFrame, newFrame);
5223 if (aLine) {
5224 aLine->NoteFrameAdded(newFrame);
5227 #ifdef DEBUG
5228 VerifyLines(false);
5229 #endif
5230 return !!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();
5240 if (nextInFlow) {
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);
5249 } else {
5250 nextInFlow =
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();
5260 } else {
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();
5269 } else {
5270 aState.mReflowStatus.SetIncomplete();
5274 static bool CheckPlaceholderInLine(nsIFrame* aBlock, nsLineBox* aLine,
5275 nsIFrame* aFloat) {
5276 if (!aFloat) {
5277 return true;
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!");
5290 return true;
5293 void nsBlockFrame::SplitLine(BlockReflowState& aState,
5294 nsLineLayout& aLineLayout, LineIterator aLine,
5295 nsIFrame* aFrame,
5296 LineReflowStatus* aLineReflowStatus) {
5297 MOZ_ASSERT(aLine->IsInline(), "illegal SplitLine on block line");
5299 int32_t pushCount =
5300 aLine->GetChildCount() - aLineLayout.GetCurrentSpanCount();
5301 MOZ_ASSERT(pushCount >= 0, "bad push count");
5303 #ifdef DEBUG
5304 if (gNoisyReflow) {
5305 nsIFrame::IndentBy(stdout, gNoiseIndent);
5306 printf("split line: from line=%p pushCount=%d aFrame=",
5307 static_cast<void*>(aLine.get()), pushCount);
5308 if (aFrame) {
5309 aFrame->ListTag(stdout);
5310 } else {
5311 printf("(null)");
5313 printf("\n");
5314 if (gReallyNoisyReflow) {
5315 aLine->List(stdout, gNoiseIndent + 1);
5318 #endif
5320 if (0 != pushCount) {
5321 MOZ_ASSERT(aLine->GetChildCount() > pushCount, "bad push");
5322 MOZ_ASSERT(nullptr != aFrame, "whoops");
5323 #ifdef DEBUG
5325 nsIFrame* f = aFrame;
5326 int32_t count = pushCount;
5327 while (f && count > 0) {
5328 f = f->GetNextSibling();
5329 --count;
5331 NS_ASSERTION(count == 0, "Not enough frames to push");
5333 #endif
5335 // Put frames being split out into their own line
5336 nsLineBox* newLine = NewLineBox(aLine, aFrame, pushCount);
5337 mLines.after_insert(aLine, newLine);
5338 #ifdef DEBUG
5339 if (gReallyNoisyReflow) {
5340 newLine->List(stdout, gNoiseIndent + 1);
5342 #endif
5344 // Let line layout know that some frames are no longer part of its
5345 // state.
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(
5354 this, aLine,
5355 aLine->HasFloats() ? aLine->Floats().LastElement() : nullptr) ||
5356 !CheckPlaceholderInLine(
5357 this, aLine,
5358 aState.mBelowCurrentLineFloats.SafeLastElement(nullptr))) {
5359 *aLineReflowStatus = LineReflowStatus::RedoNoPull;
5362 #ifdef DEBUG
5363 VerifyLines(true);
5364 #endif
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
5391 return true;
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
5410 // first line box.
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);
5428 addedMarker = true;
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
5443 : aState
5444 .GetFloatAvailableSpaceForBSize(
5445 aLine->BStart(), aState.mLineBSize.value(), nullptr)
5446 .mRect;
5448 // As we redo for floats, we can't reduce the amount of BSize we're
5449 // checking.
5450 aAvailableSpaceBSize = std::max(aAvailableSpaceBSize, aLine->BSize());
5451 LogicalRect floatAvailableSpaceWithLineBSize =
5452 aState
5453 .GetFloatAvailableSpaceForBSize(aLine->BStart(), aAvailableSpaceBSize,
5454 nullptr)
5455 .mRect;
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
5469 // line is placed.
5470 LogicalRect oldFloatAvailableSpace(aFlowArea.mRect);
5471 aFlowArea = aState.GetFloatAvailableSpaceForBSize(
5472 aLine->BStart(), aAvailableSpaceBSize, aFloatStateBeforeLine);
5474 NS_ASSERTION(
5475 aFlowArea.mRect.BStart(wm) == oldFloatAvailableSpace.BStart(wm),
5476 "yikes");
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;
5490 if (iEndDiff > 0) {
5491 aFlowArea.mRect.ISize(wm) -= iEndDiff;
5494 return false;
5497 #ifdef DEBUG
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(),
5506 aLine->BSize());
5508 } else {
5509 lastHeight = 0;
5512 #endif
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);
5537 if (addedMarker) {
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.
5547 nscoord newBCoord;
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();
5554 } else {
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()
5562 : 0;
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);
5570 return true;
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);
5581 } else {
5582 // Push aLine and all of its children and anything else that
5583 // follows to our next-in-flow.
5584 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
5586 return true;
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
5601 // float list.
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());
5618 #endif
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());
5627 return true;
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.
5643 nsFrameList floats;
5644 CollectFloats(overBegin->mFirstChild, floats, true);
5646 if (floats.NotEmpty()) {
5647 #ifdef DEBUG
5648 for (nsIFrame* f : floats) {
5649 MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
5650 "CollectFloats should've removed that bit");
5652 #endif
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;
5670 if (firstLine) {
5671 lineBeforeLastFrame = nullptr; // removes all frames
5672 } else {
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) {
5699 ClearLineCursors();
5701 line->MarkDirty();
5702 line->MarkPreviousMarginDirty();
5703 line->SetMovedFragments();
5704 line->SetBoundsEmpty();
5705 if (line->HasFloats()) {
5706 line->ClearFloats();
5712 #ifdef DEBUG
5713 VerifyOverflowSituation();
5714 #endif
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() {
5722 #ifdef DEBUG
5723 VerifyOverflowSituation();
5724 #endif
5726 // Steal the prev-in-flow's overflow lines and prepend them.
5727 bool didFindOverflow = false;
5728 nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow());
5729 if (prevBlock) {
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
5739 // are ordered.
5740 if (GetOverflowContainers()) {
5741 nsFrameList ocContinuations;
5742 for (auto* f : overflowLines->mFrames) {
5743 auto* cont = f;
5744 bool done = false;
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);
5751 done = onlyChild;
5752 continue;
5754 break;
5756 if (done) {
5757 break;
5760 if (!ocContinuations.IsEmpty()) {
5761 if (nsFrameList* eoc = GetExcessOverflowContainers()) {
5762 eoc->InsertFrames(nullptr, nullptr, std::move(ocContinuations));
5763 } else {
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));
5779 RemoveFloat(nif);
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) {
5815 return false;
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()) {
5824 #ifdef DEBUG
5825 for (nsIFrame* f : oofs.mList) {
5826 MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
5827 "CollectFloats should've removed that bit");
5829 #endif
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);
5839 #ifdef DEBUG
5840 VerifyOverflowSituation();
5841 #endif
5842 return true;
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
5853 * in that block.
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();
5896 while (f) {
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);
5908 if (!floats) {
5909 floats = EnsureFloats();
5911 floats->InsertFrame(nullptr, insertionPrevSibling, f);
5914 f = prevSibling;
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());
5929 if (prevBlock) {
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()) {
5939 return nullptr;
5941 FrameLines* prop = GetProperty(OverflowLinesProperty());
5942 NS_ASSERTION(
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");
5948 return prop;
5951 nsBlockFrame::FrameLines* nsBlockFrame::RemoveOverflowLines() {
5952 if (!HasOverflowLines()) {
5953 return nullptr;
5955 FrameLines* prop = TakeProperty(OverflowLinesProperty());
5956 NS_ASSERTION(
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);
5963 return prop;
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);
5972 delete prop;
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)) {
5994 return nullptr;
5996 nsFrameList* result = GetProperty(OverflowOutOfFlowsProperty());
5997 NS_ASSERTION(result, "value should always be non-empty when state set");
5998 return result;
6001 void nsBlockFrame::SetOverflowOutOfFlows(nsFrameList&& aList,
6002 nsFrameList* aPropValue) {
6003 MOZ_ASSERT(
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)) {
6009 return;
6011 nsFrameList* list = TakeProperty(OverflowOutOfFlowsProperty());
6012 NS_ASSERTION(aPropValue == list, "prop value mismatch");
6013 list->Clear();
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);
6020 } else {
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()) {
6029 return nullptr;
6031 NS_ASSERTION(!HasOutsideMarker(), "invalid marker state");
6032 nsIFrame* frame = GetProperty(InsideMarkerProperty());
6033 NS_ASSERTION(frame, "bogus inside ::marker frame");
6034 return frame;
6037 nsIFrame* nsBlockFrame::GetOutsideMarker() const {
6038 nsFrameList* list = GetOutsideMarkerList();
6039 return list ? list->FirstChild() : nullptr;
6042 nsFrameList* nsBlockFrame::GetOutsideMarkerList() const {
6043 if (!HasOutsideMarker()) {
6044 return nullptr;
6046 NS_ASSERTION(!HasInsideMarker(), "invalid marker state");
6047 nsFrameList* list = GetProperty(OutsideMarkerProperty());
6048 NS_ASSERTION(list && list->GetLength() == 1, "bogus outside ::marker list");
6049 return list;
6052 bool nsBlockFrame::HasFloats() const {
6053 const bool isStateBitSet = HasAnyStateBits(NS_BLOCK_HAS_FLOATS);
6054 MOZ_ASSERT(
6055 isStateBitSet == HasProperty(FloatsProperty()),
6056 "State bit should accurately reflect presence/absence of the property!");
6057 return isStateBitSet;
6060 nsFrameList* nsBlockFrame::GetFloats() const {
6061 if (!HasFloats()) {
6062 return nullptr;
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!");
6068 return list;
6071 nsFrameList* nsBlockFrame::EnsureFloats() {
6072 nsFrameList* list = GetFloats();
6073 if (list) {
6074 return list;
6076 list = new (PresShell()) nsFrameList;
6077 SetProperty(FloatsProperty(), list);
6078 AddStateBits(NS_BLOCK_HAS_FLOATS);
6079 return list;
6082 nsFrameList* nsBlockFrame::StealFloats() {
6083 if (!HasFloats()) {
6084 return nullptr;
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!");
6089 return list;
6092 bool nsBlockFrame::HasPushedFloats() const {
6093 const bool isStateBitSet = HasAnyStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
6094 MOZ_ASSERT(
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()) {
6102 return nullptr;
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!");
6108 return list;
6111 nsFrameList* nsBlockFrame::EnsurePushedFloats() {
6112 nsFrameList* result = GetPushedFloats();
6113 if (result) {
6114 return result;
6117 result = new (PresShell()) nsFrameList;
6118 SetProperty(PushedFloatsProperty(), result);
6119 AddStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
6121 return result;
6124 nsFrameList* nsBlockFrame::StealPushedFloats() {
6125 if (!HasPushedFloats()) {
6126 return nullptr;
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!");
6131 return list;
6134 //////////////////////////////////////////////////////////////////////
6135 // Frame list manipulation routines
6137 void nsBlockFrame::AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) {
6138 if (aFrameList.IsEmpty()) {
6139 return;
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));
6145 return;
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();
6153 NS_ASSERTION(
6154 (mLines.empty() ? nullptr : mLines.back()->LastChild()) == lastKid,
6155 "out-of-sync mLines / mFrames");
6157 #ifdef NOISY_REFLOW_REASON
6158 ListTag(stdout);
6159 printf(": append ");
6160 for (nsIFrame* frame : aFrameList) {
6161 frame->ListTag(stdout);
6163 if (lastKid) {
6164 printf(" after ");
6165 lastKid->ListTag(stdout);
6167 printf("\n");
6168 #endif
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));
6196 return;
6198 MOZ_ASSERT(FrameChildListID::NoReflowPrincipal == aListID,
6199 "unexpected child list");
6202 #ifdef NOISY_REFLOW_REASON
6203 ListTag(stdout);
6204 printf(": insert ");
6205 for (nsIFrame* frame : aFrameList) {
6206 frame->ListTag(stdout);
6208 if (aPrevFrame) {
6209 printf(" after ");
6210 aPrevFrame->ListTag(stdout);
6212 printf("\n");
6213 #endif
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
6226 ListTag(stdout);
6227 printf(": remove ");
6228 aOldFrame->ListTag(stdout);
6229 printf("\n");
6230 #endif
6232 if (aListID == FrameChildListID::Principal) {
6233 bool hasFloats = BlockHasAnyFloats(aOldFrame);
6234 DoRemoveFrame(aContext, aOldFrame, REMOVE_FIXED_CONTINUATIONS);
6235 if (hasFloats) {
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.
6241 // See bug 390762.
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);
6254 return;
6255 } else {
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) {
6267 return true;
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();
6274 return false;
6277 void nsBlockFrame::AddFrames(nsFrameList&& aFrameList, nsIFrame* aPrevSibling,
6278 const nsLineList::iterator* aPrevSiblingLine) {
6279 // Clear our line cursor, since our lines may change.
6280 ClearLineCursors();
6282 if (aFrameList.IsEmpty()) {
6283 return;
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()) ||
6296 (overflowLines &&
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) {
6306 break;
6308 ++line;
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");
6324 } else {
6325 prevSibLine = lineList->end();
6326 prevSiblingIndex = -1;
6327 if (aPrevSibling) {
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();
6338 bool found = false;
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;
6349 } else {
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.
6362 if (aPrevSibling) {
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;
6366 if (rem) {
6367 // Split the line in two where the frame(s) are being inserted.
6368 nsLineBox* line =
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
6376 // frame changes.
6377 line->MarkDirty();
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");
6392 NS_ASSERTION(
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
6407 // list.
6408 nsLineBox* line = NewLineBox(newFrame, isBlock);
6409 if (prevSibLine != lineList->end()) {
6410 // Append new line after prevSibLine
6411 lineList->after_insert(prevSibLine, line);
6412 ++prevSibLine;
6413 } else {
6414 // New line is going before the other lines
6415 lineList->push_front(line);
6416 prevSibLine = lineList->begin();
6418 } else {
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;
6429 #ifdef DEBUG
6430 MOZ_ASSERT(aFrameList.IsEmpty());
6431 VerifyLines(true);
6432 #endif
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);
6442 return nullptr;
6445 nsContainerFrame* nsBlockFrame::GetContentInsertionFrame() {
6446 // 'display:block ruby' use the inner (Ruby) frame for insertions.
6447 if (auto* rubyContentPseudoFrame = GetRubyContentPseudoFrame()) {
6448 return rubyContentPseudoFrame;
6450 return this;
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
6462 // the float cache.
6463 for (auto& line : Lines()) {
6464 if (line.IsInline() && line.RemoveFloat(aFloat)) {
6465 break;
6470 void nsBlockFrame::RemoveFloat(nsIFrame* aFloat) {
6471 MOZ_ASSERT(aFloat);
6473 // Floats live in floats list, pushed floats list, or overflow out-of-flow
6474 // list.
6475 MOZ_ASSERT(
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());
6489 return;
6493 if (nsFrameList* pushedFloats = GetPushedFloats()) {
6494 bool found;
6495 if (didStartRemovingFloat) {
6496 found = pushedFloats->ContinueRemoveFrame(aFloat);
6497 } else {
6498 didStartRemovingFloat = true;
6499 found = pushedFloats->StartRemoveFrame(aFloat);
6501 if (found) {
6502 if (pushedFloats->IsEmpty()) {
6503 StealPushedFloats()->Delete(PresShell());
6505 return;
6510 nsAutoOOFFrameList oofs(this);
6511 if (didStartRemovingFloat ? oofs.mList.ContinueRemoveFrame(aFloat)
6512 : oofs.mList.StartRemoveFrame(aFloat)) {
6513 return;
6518 void nsBlockFrame::DoRemoveOutOfFlowFrame(DestroyContext& aContext,
6519 nsIFrame* aFrame) {
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);
6528 } else {
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();
6553 if (lines) {
6554 *aStartIterator = lines->mLines.begin();
6555 *aIterator = *aStartIterator;
6556 *aEndIterator = lines->mLines.end();
6557 *aOverflowLines = lines;
6563 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
6564 LineIterator aLine)
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,
6571 LineIterator aLine,
6572 bool aInOverflow)
6573 : mFrame(aFrame),
6574 mLine(aLine),
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:
6593 return true;
6594 default:
6595 return false;
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)) {
6617 return true;
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
6625 return true;
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
6636 return true;
6640 if (aFrame->IsColumnSpan()) {
6641 return true;
6644 if (aFrame->IsSuppressedScrollableBlockForPrint()) {
6645 return true;
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.
6655 return true;
6658 return StyleEstablishesBFC(style);
6661 void nsBlockFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
6662 nsContainerFrame::DidSetComputedStyle(aOldStyle);
6663 if (!aOldStyle) {
6664 return;
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();
6682 if (!letterFrame) {
6683 return;
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,
6698 parentStyle);
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(
6703 parentStyle);
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(),
6713 firstLetterStyle);
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");
6723 nsIFrame* child;
6724 while (true) {
6725 nsIFrame* block = aFrame;
6726 do {
6727 child = nsLayoutUtils::FindChildContainingDescendant(block, aFindFrame);
6728 if (child) {
6729 break;
6731 block = block->GetNextContinuation();
6732 } while (block);
6733 if (!child) {
6734 return nullptr;
6736 if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
6737 break;
6739 aFindFrame = child->GetPlaceholderFrame();
6742 return child;
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);
6752 if (!child) {
6753 return;
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;
6762 return;
6765 // Try to use the cursor if it exists, otherwise fall back to the first line
6766 if (nsLineBox* const cursor = aFrame->GetLineCursorForQuery()) {
6767 mLine = line_end;
6768 // Perform a simultaneous forward and reverse search starting from the
6769 // line cursor.
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.
6776 ++rline;
6777 while (line != line_end || rline != rline_end) {
6778 if (line != line_end) {
6779 if (line->Contains(child)) {
6780 mLine = line;
6781 break;
6783 ++line;
6785 if (rline != rline_end) {
6786 if (rline->Contains(child)) {
6787 mLine = rline;
6788 break;
6790 ++rline;
6793 if (mLine != line_end) {
6794 *aFoundValidLine = true;
6795 if (mLine != cursor) {
6796 aFrame->SetProperty(nsBlockFrame::LineCursorPropertyQuery(), mLine);
6798 return;
6800 } else {
6801 for (mLine = aFrame->LinesBegin(); mLine != line_end; ++mLine) {
6802 if (mLine->Contains(child)) {
6803 *aFoundValidLine = true;
6804 return;
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,
6815 // if any.
6817 if (!FindValidLine()) {
6818 return;
6821 do {
6822 if (mLine->Contains(child)) {
6823 *aFoundValidLine = true;
6824 return;
6826 } while (Next());
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() {
6839 ++mLine;
6840 return FindValidLine();
6843 bool nsBlockInFlowLineIterator::Prev() {
6844 LineIterator begin = mLineList->begin();
6845 if (mLine != begin) {
6846 --mLine;
6847 return true;
6849 bool currentlyInOverflowLines = GetInOverflow();
6850 while (true) {
6851 if (currentlyInOverflowLines) {
6852 mLineList = &mFrame->mLines;
6853 mLine = mLineList->end();
6854 if (mLine != mLineList->begin()) {
6855 --mLine;
6856 return true;
6858 } else {
6859 mFrame = static_cast<nsBlockFrame*>(mFrame->GetPrevInFlow());
6860 if (!mFrame) {
6861 return false;
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?");
6868 --mLine;
6869 return true;
6872 currentlyInOverflowLines = !currentlyInOverflowLines;
6876 bool nsBlockInFlowLineIterator::FindValidLine() {
6877 LineIterator end = mLineList->end();
6878 if (mLine != end) {
6879 return true;
6881 bool currentlyInOverflowLines = GetInOverflow();
6882 while (true) {
6883 if (currentlyInOverflowLines) {
6884 mFrame = static_cast<nsBlockFrame*>(mFrame->GetNextInFlow());
6885 if (!mFrame) {
6886 return false;
6888 mLineList = &mFrame->mLines;
6889 mLine = mLineList->begin();
6890 if (mLine != mLineList->end()) {
6891 return true;
6893 } else {
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?");
6899 return true;
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);
6924 } else {
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);
6929 return;
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;
6938 bool found = false;
6939 if (nsLineBox* cursor = GetLineCursorForDisplay()) {
6940 for (line.SetPosition(cursor); line != line_end; ++line) {
6941 if (line->Contains(aDeletedFrame)) {
6942 found = true;
6943 break;
6946 if (!found) {
6947 // Setup for a shorter TryAllLines normal line search to avoid searching
6948 // the [cursor .. line_end] range again.
6949 line = line_start;
6950 line_end.SetPosition(cursor);
6954 FrameLines* overflowLines = nullptr;
6955 bool searchingOverflowList = false;
6956 if (!found) {
6957 // Make sure we look in the overflow lines even if the normal line
6958 // list is empty.
6959 TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
6960 &overflowLines);
6961 while (line != line_end) {
6962 if (line->Contains(aDeletedFrame)) {
6963 break;
6965 ++line;
6966 TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
6967 &overflowLines);
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.
6973 ClearLineCursors();
6977 if (line == line_end) {
6978 NS_ERROR("can't find deleted frame in lines");
6979 return;
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)) {
6997 line->MarkDirty();
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 =
7007 next != line_end
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
7026 --line;
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.
7030 line->MarkDirty();
7032 ++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);
7039 } else {
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);
7058 #endif
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());
7078 #endif
7079 nsLineBox* cur = line;
7080 if (!searchingOverflowList) {
7081 line = mLines.erase(line);
7082 ClearLineCursors();
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
7086 // cases...
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);
7091 #endif
7092 } else {
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();
7102 line = line_end;
7105 FreeLineBox(cur);
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
7110 // there is one.
7111 if (line != line_end) {
7112 line->MarkPreviousMarginDirty();
7114 haveAdvancedToNextLine = true;
7115 } else {
7116 // Make the line that just lost a frame dirty, and advance to
7117 // the next line.
7118 if (!deletedNextContinuation || isLastFrameOnLine ||
7119 !line->Contains(deletedNextContinuation)) {
7120 line->MarkDirty();
7121 ++line;
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;
7137 break;
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.
7147 line = line_end;
7150 TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
7151 &overflowLines);
7152 #ifdef NOISY_REMOVE_FRAME
7153 printf("DoRemoveFrame: now on %s line=%p\n",
7154 searchingOverflowList ? "overflow" : "normal", line.get());
7155 #endif
7160 if (!(aFlags & FRAMES_ARE_EMPTY) && line.next() != line_end) {
7161 line.next()->MarkDirty();
7162 line.next()->SetInvalidateTextRuns(true);
7165 #ifdef DEBUG
7166 VerifyLines(true);
7167 VerifyOverflowSituation();
7168 #endif
7170 // Advance to next flow block if the frame has more continuations.
7171 if (!aDeletedFrame) {
7172 return;
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);
7188 *aResult = line;
7189 return true;
7192 return false;
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)) {
7208 *aResult = line;
7209 return true;
7213 return false;
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);
7229 return;
7232 if (MaybeStealOverflowContainerFrame(aChild)) {
7233 return;
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);
7241 } else {
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) {
7266 aLine->MarkDirty();
7267 } else {
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);
7275 ClearLineCursors();
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);
7288 } else {
7289 #ifdef DEBUG
7290 if (aDeletingEmptyFrames) {
7291 nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(aNextInFlow);
7293 #endif
7294 DoRemoveFrame(aContext, aNextInFlow,
7295 aDeletingEmptyFrames ? FRAMES_ARE_EMPTY : 0);
7299 const nsStyleText* nsBlockFrame::StyleTextForLineLayout() {
7300 // Return the pointer to an unmodified style text
7301 return StyleText();
7304 void nsBlockFrame::ReflowFloat(BlockReflowState& aState, ReflowInput& aFloatRI,
7305 nsIFrame* aFloat,
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;
7318 do {
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,
7325 &mayNeedRetry);
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()
7336 // later.
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
7344 // line.
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
7428 // third and later.
7429 nsIFrame* prevContinuation = f->GetPrevContinuation();
7430 if (prevContinuation && prevContinuation->GetParent() == f->GetParent()) {
7431 floats->RemoveFrame(f);
7432 if (floats->IsEmpty()) {
7433 StealFloats()->Delete(PresShell());
7434 floats = nullptr;
7436 aState.AppendPushedFloatChain(f);
7437 if (!floats) {
7438 // The floats list becomes empty after removing |f|. Bail out.
7439 f = prev = nullptr;
7440 break;
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();
7448 if (!floats) {
7449 f = prev = nullptr;
7450 break;
7452 f = !prev ? floats->FirstChild() : prev->GetNextSibling();
7453 continue;
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();
7470 if (!floats) {
7471 f = prev = nullptr;
7472 break;
7475 nsIFrame* next = !prev ? floats->FirstChild() : prev->GetNextSibling();
7476 if (next == f) {
7477 // We didn't push |f| so its next-sibling is next.
7478 next = f->GetNextSibling();
7479 prev = f;
7480 } // else: we did push |f| so |prev|'s new next-sibling is next.
7481 f = next;
7484 // If there are pushed or split floats, then we may need to continue BR
7485 // clearance
7486 if (auto [bCoord, result] = aState.ClearFloats(0, StyleClear::Both);
7487 result != ClearFloatsResult::BCoordNoChange) {
7488 Unused << bCoord;
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
7511 for (nsIFrame* oc =
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,
7527 WritingMode aWM,
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
7553 // float list.
7554 if (floats->FirstChild()->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
7555 return true;
7557 #ifdef DEBUG
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");
7564 #endif
7567 // We may have a pending push of pushed floats, too.
7568 return HasPushedFloats();
7571 //////////////////////////////////////////////////////////////////////
7572 // Painting, event handling
7574 #ifdef DEBUG
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
7581 // combined areas
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;
7587 if (x < xa) {
7588 xa = x;
7590 if (xmost > xb) {
7591 xb = xmost;
7593 if (y < ya) {
7594 ya = y;
7596 if (ymost > yb) {
7597 yb = ymost;
7601 aResult.x = xa;
7602 aResult.y = ya;
7603 aResult.width = xb - xa;
7604 aResult.height = yb - ya;
7606 #endif
7608 #ifdef DEBUG
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);
7619 #endif
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) {
7627 #ifdef DEBUG
7628 if (nsBlockFrame::gLamePaintMetrics) {
7629 aDrawnLines++;
7631 const bool intersect =
7632 aLine->InkOverflowRect().Intersects(aBuilder->GetDirtyRect());
7633 DebugOutputDrawLine(aDepth, aLine.get(), intersect);
7634 #endif
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(
7644 collection,
7645 aLineInLine ? collection.Content() : collection.BlockBorderBackgrounds());
7647 auto flags =
7648 aLineInLine
7649 ? nsIFrame::DisplayChildFlags(nsIFrame::DisplayChildFlag::Inline)
7650 : nsIFrame::DisplayChildFlags();
7652 nsIFrame* kid = aLine->mFirstChild;
7653 int32_t n = aLine->GetChildCount();
7654 while (--n >= 0) {
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).
7670 int32_t depth = 0;
7671 #ifdef DEBUG
7672 if (gNoisyDamageRepair) {
7673 nsRect dirty = aBuilder->GetDirtyRect();
7674 depth = GetDepth();
7675 nsRect ca;
7676 ::ComputeInkOverflowArea(mLines, mRect.width, mRect.height, ca);
7677 nsIFrame::IndentBy(stdout, depth);
7678 ListTag(stdout);
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) {
7685 start = PR_Now();
7686 drawnLines = 0;
7688 #endif
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.
7724 // See bug 1552789.
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
7738 // hold:
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.
7762 nsLineBox* cursor =
7763 (hasDescendantPlaceHolders || textOverflow.isSome() || backplateColor)
7764 ? nullptr
7765 : GetFirstLineContaining(aBuilder->GetDirtyRect().y);
7766 LineIterator line_end = LinesEnd();
7768 TextOverflow* textOverflowPtr = textOverflow.ptrOr(nullptr);
7770 if (cursor) {
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()) {
7777 break;
7779 MOZ_ASSERT(textOverflow.isNothing());
7781 if (ShouldDescendIntoLine(lineArea)) {
7782 DisplayLine(aBuilder, line, line->IsInline(), aLists, this, nullptr,
7783 0, depth, drawnLines);
7787 } else {
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");
7824 AddBackplate();
7825 backplateIndex++;
7826 curBackplateArea = nsRect();
7829 if (!lineArea.IsEmpty()) {
7830 if (lineArea.y < lastY || lineArea.YMost() < lastYMost) {
7831 nonDecreasingYs = false;
7833 lastY = lineArea.y;
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;
7840 } else {
7841 curBackplateArea.OrWith(lineBackplate);
7845 lineCount++;
7848 if (nonDecreasingYs && lineCount >= MIN_LINES_NEEDING_CURSOR) {
7849 SetupLineCursorForDisplay();
7852 if (!curBackplateArea.IsEmpty()) {
7853 AddBackplate();
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());
7864 #ifdef DEBUG
7865 if (gLamePaintMetrics) {
7866 PRTime end = PR_Now();
7868 int32_t numLines = mLines.size();
7869 if (!numLines) {
7870 numLines = 1;
7872 PRTime lines, deltaPerLine, delta;
7873 lines = int64_t(numLines);
7874 delta = end - start;
7875 deltaPerLine = delta / lines;
7877 ListTag(stdout);
7878 char buf[400];
7879 SprintfLiteral(buf,
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);
7886 #endif
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
7905 // node
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;
7922 #endif
7924 void nsBlockFrame::SetupLineCursorForDisplay() {
7925 if (mLines.empty() || HasProperty(LineCursorPropertyDisplay())) {
7926 return;
7929 SetProperty(LineCursorPropertyDisplay(), mLines.front());
7930 AddStateBits(NS_BLOCK_HAS_LINE_CURSOR);
7933 void nsBlockFrame::SetupLineCursorForQuery() {
7934 if (mLines.empty() || HasProperty(LineCursorPropertyQuery())) {
7935 return;
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();
7946 if (!property) {
7947 return nullptr;
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();
7970 /* virtual */
7971 void nsBlockFrame::ChildIsDirty(nsIFrame* aChild) {
7972 // See if the child is absolutely positioned
7973 if (aChild->IsAbsolutelyPositioned()) {
7974 // do nothing
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.
7990 } else {
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
7996 // overflow list.
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);
8007 } else {
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
8013 // ignoring those.
8014 if (placeholderPath) {
8015 for (;;) {
8016 nsIFrame* parent = placeholderPath->GetParent();
8017 if (parent->GetContent() == mContent &&
8018 parent->FirstContinuation() == thisFC) {
8019 parent->AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
8020 break;
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;
8048 if (aPrevInFlow) {
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);
8057 if (!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) {
8078 #ifdef DEBUG
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
8085 // of the block.
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");
8103 #endif
8105 AddFrames(std::move(aChildList), nullptr, nullptr);
8106 } else {
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);
8118 } else {
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() &&
8127 HasOutsideMarker(),
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,
8139 nscoord aLineTop) {
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
8155 // the block.
8156 // FIXME: aLineTop isn't actually set correctly by some callers, since
8157 // they reposition the line.
8158 LogicalRect floatAvailSpace =
8159 aState
8160 .GetFloatAvailableSpaceWithState(aLineTop, ShapeType::ShapeOutside,
8161 &aState.mFloatManagerStateBefore)
8162 .mRect;
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) {
8204 while (aFrame) {
8205 // Don't descend into float containing blocks.
8206 if (!aFrame->IsFloatContainingBlock()) {
8207 nsIFrame* outOfFlowFrame =
8208 aFrame->IsPlaceholderFrame()
8209 ? nsLayoutUtils::GetFloatFromPlaceholder(aFrame)
8210 : nullptr;
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
8220 // out-of-order?
8221 // XXXmats nsInlineFrame's lazy reparenting depends on NOT doing that.
8224 DoCollectFloats(aFrame->PrincipalChildList().FirstChild(), aList, true);
8225 DoCollectFloats(
8226 aFrame->GetChildList(FrameChildListID::Overflow).FirstChild(), aList,
8227 true);
8229 if (!aCollectSiblings) {
8230 break;
8232 aFrame = aFrame->GetNextSibling();
8236 void nsBlockFrame::CheckFloats(BlockReflowState& aState) {
8237 #ifdef DEBUG
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;
8255 bool equal = true;
8256 bool hasHiddenFloats = false;
8257 uint32_t i = 0;
8258 for (nsIFrame* f : GetChildList(FrameChildListID::Float)) {
8259 if (f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
8260 continue;
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
8271 // checking.
8272 if (!hasHiddenFloats &&
8273 f->IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
8274 hasHiddenFloats = true;
8276 storedFloats.AppendElement(f);
8277 if (i < lineFloats.Length() && lineFloats.ElementAt(i) != f) {
8278 equal = false;
8280 ++i;
8283 if ((!equal || lineFloats.Length() != storedFloats.Length()) &&
8284 !anyLineDirty && !hasHiddenFloats) {
8285 NS_ERROR(
8286 "nsBlockFrame::CheckFloats: Explicit float list is out of sync with "
8287 "float cache");
8289 #endif
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
8302 // lines.
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;
8314 return;
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;
8324 return;
8327 *aBStartMarginRoot = true;
8328 *aBEndMarginRoot = true;
8331 /* static */
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());
8341 /* static */
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
8350 // matter.
8351 /* static */
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
8366 // to fit.
8367 // All we really need here is the result of ComputeSize, and we
8368 // could *almost* get that from an SizeComputationInput, except for the
8369 // last argument.
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
8394 // following:
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
8421 // handling code...
8422 result.borderBoxISize = std::max(result.borderBoxISize, -marginISize);
8425 result.marginIStart = computedMargin.IStart(wm);
8426 return result;
8429 /* static */
8430 nsBlockFrame* nsBlockFrame::GetNearestAncestorBlock(nsIFrame* aCandidate) {
8431 nsBlockFrame* block = nullptr;
8432 while (aCandidate) {
8433 block = do_QueryFrame(aCandidate);
8434 if (block) {
8435 // yay, candidate is a block!
8436 return 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!");
8442 return nullptr;
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);
8454 NS_ASSERTION(
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;
8532 } else {
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);
8568 } else {
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;
8574 } else {
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.
8590 } else {
8591 aState.mReflowStatus.SetOverflowIncomplete();
8593 } else {
8594 NS_ASSERTION(!IsTrueOverflowContainer(),
8595 "An overflow container should always be complete because of "
8596 "its zero border-box block-size!");
8597 if (isBDBClone) {
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,
8632 parentStyle);
8634 // FIXME(bz): Can we make first-line continuations be non-inheriting anon
8635 // boxes?
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.
8656 return nullptr;
8659 return GetProperty(FirstLetterProperty());
8662 nsIFrame* nsBlockFrame::GetFirstLineFrame() const {
8663 nsIFrame* maybeFirstLine = PrincipalChildList().FirstChild();
8664 if (maybeFirstLine && maybeFirstLine->IsLineFrame()) {
8665 return maybeFirstLine;
8668 return nullptr;
8671 #ifdef DEBUG
8672 void nsBlockFrame::VerifyLines(bool aFinalCheckOK) {
8673 if (!gVerifyLines) {
8674 return;
8676 if (mLines.empty()) {
8677 return;
8680 nsLineBox* cursor = GetLineCursorForQuery();
8682 // Add up the counts on each line. Also validate that IsFirstLine is
8683 // set properly.
8684 int32_t count = 0;
8685 for (const auto& line : Lines()) {
8686 if (&line == cursor) {
8687 cursor = nullptr;
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;
8701 while (frame) {
8702 frameCount++;
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();
8715 ++line;
8716 if ((line != line_end) && (0 != line->GetChildCount())) {
8717 NS_ASSERTION(frame == line->mFirstChild, "bad line list");
8721 if (cursor) {
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) {
8728 cursor = nullptr;
8729 break;
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
8739 // mFrames.
8740 nsFrameList* oofs = GetOverflowOutOfFlows();
8741 if (oofs) {
8742 for (nsIFrame* f : *oofs) {
8743 nsIFrame* nif = f->GetNextInFlow();
8744 MOZ_ASSERT(!nif ||
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();
8752 if (oofs) {
8753 for (nsIFrame* f : *oofs) {
8754 nsIFrame* nif = f->GetNextInFlow();
8755 MOZ_ASSERT(!nif ||
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()) {
8771 bool found = false;
8772 for (nsIFrame* p = parent; p; p = p->GetNextInFlow()) {
8773 if (nif->GetParent() == p) {
8774 parent = p;
8775 found = true;
8776 break;
8779 MOZ_ASSERT(
8780 found,
8781 "next-in-flow is a child of parent earlier in the frame tree?");
8786 nsBlockFrame* flow = static_cast<nsBlockFrame*>(FirstInFlow());
8787 while (flow) {
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 {
8799 if (!cursor) {
8800 return true;
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 {
8821 int32_t depth = 0;
8822 nsIFrame* parent = GetParent();
8823 while (parent) {
8824 parent = parent->GetParent();
8825 depth++;
8827 return depth;
8830 already_AddRefed<ComputedStyle> nsBlockFrame::GetFirstLetterStyle(
8831 nsPresContext* aPresContext) {
8832 return aPresContext->StyleSet()->ProbePseudoElementStyle(
8833 *mContent->AsElement(), PseudoStyleType::firstLetter, nullptr, Style());
8835 #endif