Bug 1842999 - Part 25: Support testing elements are present in resizable typed arrays...
[gecko.git] / layout / generic / nsBlockFrame.cpp
bloba24e37f961abdde74be19024301dbc73008088cb
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/StaticPrefs_browser.h"
23 #include "mozilla/StaticPrefs_layout.h"
24 #include "mozilla/ToString.h"
25 #include "mozilla/UniquePtr.h"
27 #include "nsCRT.h"
28 #include "nsCOMPtr.h"
29 #include "nsCSSRendering.h"
30 #include "nsAbsoluteContainingBlock.h"
31 #include "nsBlockReflowContext.h"
32 #include "BlockReflowState.h"
33 #include "nsFontMetrics.h"
34 #include "nsGenericHTMLElement.h"
35 #include "nsLineBox.h"
36 #include "nsLineLayout.h"
37 #include "nsPlaceholderFrame.h"
38 #include "nsStyleConsts.h"
39 #include "nsFrameManager.h"
40 #include "nsPresContext.h"
41 #include "nsPresContextInlines.h"
42 #include "nsHTMLParts.h"
43 #include "nsGkAtoms.h"
44 #include "nsAttrValueInlines.h"
45 #include "mozilla/Sprintf.h"
46 #include "nsFloatManager.h"
47 #include "prenv.h"
48 #include "nsError.h"
49 #include "nsIScrollableFrame.h"
50 #include <algorithm>
51 #include "nsLayoutUtils.h"
52 #include "nsDisplayList.h"
53 #include "nsCSSAnonBoxes.h"
54 #include "nsCSSFrameConstructor.h"
55 #include "TextOverflow.h"
56 #include "nsIFrameInlines.h"
57 #include "CounterStyleManager.h"
58 #include "mozilla/dom/HTMLDetailsElement.h"
59 #include "mozilla/dom/HTMLSummaryElement.h"
60 #include "mozilla/dom/Selection.h"
61 #include "mozilla/PresShell.h"
62 #include "mozilla/RestyleManager.h"
63 #include "mozilla/ServoStyleSet.h"
64 #include "mozilla/Telemetry.h"
65 #include "nsFlexContainerFrame.h"
66 #include "nsFileControlFrame.h"
67 #include "nsMathMLContainerFrame.h"
68 #include "nsSelectsAreaFrame.h"
70 #include "nsBidiPresUtils.h"
72 #include <inttypes.h>
74 static const int MIN_LINES_NEEDING_CURSOR = 20;
76 using namespace mozilla;
77 using namespace mozilla::css;
78 using namespace mozilla::dom;
79 using namespace mozilla::layout;
80 using AbsPosReflowFlags = nsAbsoluteContainingBlock::AbsPosReflowFlags;
81 using ClearFloatsResult = BlockReflowState::ClearFloatsResult;
82 using ShapeType = nsFloatManager::ShapeType;
84 static void MarkAllDescendantLinesDirty(nsBlockFrame* aBlock) {
85 for (auto& line : aBlock->Lines()) {
86 if (line.IsBlock()) {
87 nsBlockFrame* bf = do_QueryFrame(line.mFirstChild);
88 if (bf) {
89 MarkAllDescendantLinesDirty(bf);
92 line.MarkDirty();
96 static void MarkSameFloatManagerLinesDirty(nsBlockFrame* aBlock) {
97 nsBlockFrame* blockWithFloatMgr = aBlock;
98 while (!blockWithFloatMgr->HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS)) {
99 nsBlockFrame* bf = do_QueryFrame(blockWithFloatMgr->GetParent());
100 if (!bf) {
101 break;
103 blockWithFloatMgr = bf;
106 // Mark every line at and below the line where the float was
107 // dirty, and mark their lines dirty too. We could probably do
108 // something more efficient --- e.g., just dirty the lines that intersect
109 // the float vertically.
110 MarkAllDescendantLinesDirty(blockWithFloatMgr);
114 * Returns true if aFrame is a block that has one or more float children.
116 static bool BlockHasAnyFloats(nsIFrame* aFrame) {
117 nsBlockFrame* block = do_QueryFrame(aFrame);
118 if (!block) {
119 return false;
121 if (block->GetChildList(FrameChildListID::Float).FirstChild()) {
122 return true;
125 for (const auto& line : block->Lines()) {
126 if (line.IsBlock() && BlockHasAnyFloats(line.mFirstChild)) {
127 return true;
130 return false;
134 * Determines whether the given frame is visible or has
135 * visible children that participate in the same line. Frames
136 * that are not line participants do not have their
137 * children checked.
139 static bool FrameHasVisibleInlineContent(nsIFrame* aFrame) {
140 MOZ_ASSERT(aFrame, "Frame argument cannot be null");
142 if (aFrame->StyleVisibility()->IsVisible()) {
143 return true;
146 if (aFrame->IsLineParticipant()) {
147 for (nsIFrame* kid : aFrame->PrincipalChildList()) {
148 if (kid->StyleVisibility()->IsVisible() ||
149 FrameHasVisibleInlineContent(kid)) {
150 return true;
154 return false;
158 * Determines whether any of the frames descended from the
159 * given line have inline content with 'visibility: visible'.
160 * This function calls FrameHasVisibleInlineContent to process
161 * each frame in the line's child list.
163 static bool LineHasVisibleInlineContent(nsLineBox* aLine) {
164 nsIFrame* kid = aLine->mFirstChild;
165 int32_t n = aLine->GetChildCount();
166 while (n-- > 0) {
167 if (FrameHasVisibleInlineContent(kid)) {
168 return true;
171 kid = kid->GetNextSibling();
174 return false;
178 * Iterates through the frame's in-flow children and
179 * unions the ink overflow of all text frames which
180 * participate in the line aFrame belongs to.
181 * If a child of aFrame is not a text frame,
182 * we recurse with the child as the aFrame argument.
183 * If aFrame isn't a line participant, we skip it entirely
184 * and return an empty rect.
185 * The resulting nsRect is offset relative to the parent of aFrame.
187 static nsRect GetFrameTextArea(nsIFrame* aFrame,
188 nsDisplayListBuilder* aBuilder) {
189 nsRect textArea;
190 if (const nsTextFrame* textFrame = do_QueryFrame(aFrame)) {
191 if (!textFrame->IsEntirelyWhitespace()) {
192 textArea = aFrame->InkOverflowRect();
194 } else if (aFrame->IsLineParticipant()) {
195 for (nsIFrame* kid : aFrame->PrincipalChildList()) {
196 nsRect kidTextArea = GetFrameTextArea(kid, aBuilder);
197 textArea.OrWith(kidTextArea);
200 // add aFrame's position to keep textArea relative to aFrame's parent
201 return textArea + aFrame->GetPosition();
205 * Iterates through the line's children and
206 * unions the ink overflow of all text frames.
207 * GetFrameTextArea unions and returns the ink overflow
208 * from all line-participating text frames within the given child.
209 * The nsRect returned from GetLineTextArea is offset
210 * relative to the given line.
212 static nsRect GetLineTextArea(nsLineBox* aLine,
213 nsDisplayListBuilder* aBuilder) {
214 nsRect textArea;
215 nsIFrame* kid = aLine->mFirstChild;
216 int32_t n = aLine->GetChildCount();
217 while (n-- > 0) {
218 nsRect kidTextArea = GetFrameTextArea(kid, aBuilder);
219 textArea.OrWith(kidTextArea);
220 kid = kid->GetNextSibling();
223 return textArea;
227 * Starting with aFrame, iterates upward through parent frames and checks for
228 * non-transparent background colors. If one is found, we use that as our
229 * backplate color. Otheriwse, we use the default background color from
230 * our high contrast theme.
232 static nscolor GetBackplateColor(nsIFrame* aFrame) {
233 nsPresContext* pc = aFrame->PresContext();
234 nscolor currentBackgroundColor = NS_TRANSPARENT;
235 for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
236 // NOTE(emilio): We assume themed frames (frame->IsThemed()) have correct
237 // background-color information so as to compute the right backplate color.
239 // This holds because HTML widgets with author-specified backgrounds or
240 // borders disable theming. So as long as the UA-specified background colors
241 // match the actual theme (which they should because we always use system
242 // colors with the non-native theme, and native system colors should also
243 // match the native theme), then we're alright and we should compute an
244 // appropriate backplate color.
245 const auto* style = frame->Style();
246 if (style->StyleBackground()->IsTransparent(style)) {
247 continue;
249 bool drawImage = false, drawColor = false;
250 nscolor backgroundColor = nsCSSRendering::DetermineBackgroundColor(
251 pc, style, frame, drawImage, drawColor);
252 if (!drawColor && !drawImage) {
253 continue;
255 if (NS_GET_A(backgroundColor) == 0) {
256 // Even if there's a background image, if there's no background color we
257 // keep going up the frame tree, see bug 1723938.
258 continue;
260 if (NS_GET_A(currentBackgroundColor) == 0) {
261 // Try to avoid somewhat expensive math in the common case.
262 currentBackgroundColor = backgroundColor;
263 } else {
264 currentBackgroundColor =
265 NS_ComposeColors(backgroundColor, currentBackgroundColor);
267 if (NS_GET_A(currentBackgroundColor) == 0xff) {
268 // If fully opaque, we're done, otherwise keep going up blending with our
269 // background.
270 return currentBackgroundColor;
273 nscolor backgroundColor = aFrame->PresContext()->DefaultBackgroundColor();
274 if (NS_GET_A(currentBackgroundColor) == 0) {
275 return backgroundColor;
277 return NS_ComposeColors(backgroundColor, currentBackgroundColor);
280 #ifdef DEBUG
281 # include "nsBlockDebugFlags.h"
283 bool nsBlockFrame::gLamePaintMetrics;
284 bool nsBlockFrame::gLameReflowMetrics;
285 bool nsBlockFrame::gNoisy;
286 bool nsBlockFrame::gNoisyDamageRepair;
287 bool nsBlockFrame::gNoisyIntrinsic;
288 bool nsBlockFrame::gNoisyReflow;
289 bool nsBlockFrame::gReallyNoisyReflow;
290 bool nsBlockFrame::gNoisyFloatManager;
291 bool nsBlockFrame::gVerifyLines;
292 bool nsBlockFrame::gDisableResizeOpt;
294 int32_t nsBlockFrame::gNoiseIndent;
296 struct BlockDebugFlags {
297 const char* name;
298 bool* on;
301 static const BlockDebugFlags gFlags[] = {
302 {"reflow", &nsBlockFrame::gNoisyReflow},
303 {"really-noisy-reflow", &nsBlockFrame::gReallyNoisyReflow},
304 {"intrinsic", &nsBlockFrame::gNoisyIntrinsic},
305 {"float-manager", &nsBlockFrame::gNoisyFloatManager},
306 {"verify-lines", &nsBlockFrame::gVerifyLines},
307 {"damage-repair", &nsBlockFrame::gNoisyDamageRepair},
308 {"lame-paint-metrics", &nsBlockFrame::gLamePaintMetrics},
309 {"lame-reflow-metrics", &nsBlockFrame::gLameReflowMetrics},
310 {"disable-resize-opt", &nsBlockFrame::gDisableResizeOpt},
312 # define NUM_DEBUG_FLAGS (sizeof(gFlags) / sizeof(gFlags[0]))
314 static void ShowDebugFlags() {
315 printf("Here are the available GECKO_BLOCK_DEBUG_FLAGS:\n");
316 const BlockDebugFlags* bdf = gFlags;
317 const BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS;
318 for (; bdf < end; bdf++) {
319 printf(" %s\n", bdf->name);
321 printf("Note: GECKO_BLOCK_DEBUG_FLAGS is a comma separated list of flag\n");
322 printf("names (no whitespace)\n");
325 void nsBlockFrame::InitDebugFlags() {
326 static bool firstTime = true;
327 if (firstTime) {
328 firstTime = false;
329 char* flags = PR_GetEnv("GECKO_BLOCK_DEBUG_FLAGS");
330 if (flags) {
331 bool error = false;
332 for (;;) {
333 char* cm = strchr(flags, ',');
334 if (cm) {
335 *cm = '\0';
338 bool found = false;
339 const BlockDebugFlags* bdf = gFlags;
340 const BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS;
341 for (; bdf < end; bdf++) {
342 if (nsCRT::strcasecmp(bdf->name, flags) == 0) {
343 *(bdf->on) = true;
344 printf("nsBlockFrame: setting %s debug flag on\n", bdf->name);
345 gNoisy = true;
346 found = true;
347 break;
350 if (!found) {
351 error = true;
354 if (!cm) {
355 break;
357 *cm = ',';
358 flags = cm + 1;
360 if (error) {
361 ShowDebugFlags();
367 #endif
369 //----------------------------------------------------------------------
371 // Debugging support code
373 #ifdef DEBUG
374 const char* nsBlockFrame::kReflowCommandType[] = {
375 "ContentChanged", "StyleChanged", "ReflowDirty", "Timeout", "UserDefined",
378 const char* nsBlockFrame::LineReflowStatusToString(
379 LineReflowStatus aLineReflowStatus) const {
380 switch (aLineReflowStatus) {
381 case LineReflowStatus::OK:
382 return "LINE_REFLOW_OK";
383 case LineReflowStatus::Stop:
384 return "LINE_REFLOW_STOP";
385 case LineReflowStatus::RedoNoPull:
386 return "LINE_REFLOW_REDO_NO_PULL";
387 case LineReflowStatus::RedoMoreFloats:
388 return "LINE_REFLOW_REDO_MORE_FLOATS";
389 case LineReflowStatus::RedoNextBand:
390 return "LINE_REFLOW_REDO_NEXT_BAND";
391 case LineReflowStatus::Truncated:
392 return "LINE_REFLOW_TRUNCATED";
394 return "unknown";
397 #endif
399 #ifdef REFLOW_STATUS_COVERAGE
400 static void RecordReflowStatus(bool aChildIsBlock,
401 const nsReflowStatus& aFrameReflowStatus) {
402 static uint32_t record[2];
404 // 0: child-is-block
405 // 1: child-is-inline
406 int index = 0;
407 if (!aChildIsBlock) {
408 index |= 1;
411 // Compute new status
412 uint32_t newS = record[index];
413 if (aFrameReflowStatus.IsInlineBreak()) {
414 if (aFrameReflowStatus.IsInlineBreakBefore()) {
415 newS |= 1;
416 } else if (aFrameReflowStatus.IsIncomplete()) {
417 newS |= 2;
418 } else {
419 newS |= 4;
421 } else if (aFrameReflowStatus.IsIncomplete()) {
422 newS |= 8;
423 } else {
424 newS |= 16;
427 // Log updates to the status that yield different values
428 if (record[index] != newS) {
429 record[index] = newS;
430 printf("record(%d): %02x %02x\n", index, record[0], record[1]);
433 #endif
435 NS_DECLARE_FRAME_PROPERTY_WITH_DTOR_NEVER_CALLED(OverflowLinesProperty,
436 nsBlockFrame::FrameLines)
437 NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OverflowOutOfFlowsProperty)
438 NS_DECLARE_FRAME_PROPERTY_FRAMELIST(PushedFloatProperty)
439 NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OutsideMarkerProperty)
440 NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(InsideMarkerProperty, nsIFrame)
441 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(BlockEndEdgeOfChildrenProperty, nscoord)
443 //----------------------------------------------------------------------
445 nsBlockFrame* NS_NewBlockFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
446 return new (aPresShell) nsBlockFrame(aStyle, aPresShell->GetPresContext());
449 nsBlockFrame* NS_NewBlockFormattingContext(PresShell* aPresShell,
450 ComputedStyle* aComputedStyle) {
451 nsBlockFrame* blockFrame = NS_NewBlockFrame(aPresShell, aComputedStyle);
452 blockFrame->AddStateBits(NS_BLOCK_STATIC_BFC);
453 return blockFrame;
456 NS_IMPL_FRAMEARENA_HELPERS(nsBlockFrame)
458 nsBlockFrame::~nsBlockFrame() = default;
460 void nsBlockFrame::AddSizeOfExcludingThisForTree(
461 nsWindowSizes& aWindowSizes) const {
462 nsContainerFrame::AddSizeOfExcludingThisForTree(aWindowSizes);
464 // Add the size of any nsLineBox::mFrames hashtables we might have:
465 for (const auto& line : Lines()) {
466 line.AddSizeOfExcludingThis(aWindowSizes);
468 const FrameLines* overflowLines = GetOverflowLines();
469 if (overflowLines) {
470 ConstLineIterator line = overflowLines->mLines.begin(),
471 line_end = overflowLines->mLines.end();
472 for (; line != line_end; ++line) {
473 line->AddSizeOfExcludingThis(aWindowSizes);
478 void nsBlockFrame::Destroy(DestroyContext& aContext) {
479 ClearLineCursors();
480 DestroyAbsoluteFrames(aContext);
481 mFloats.DestroyFrames(aContext);
482 nsPresContext* presContext = PresContext();
483 mozilla::PresShell* presShell = presContext->PresShell();
484 nsLineBox::DeleteLineList(presContext, mLines, &mFrames, aContext);
486 if (HasPushedFloats()) {
487 SafelyDestroyFrameListProp(aContext, presShell, PushedFloatProperty());
488 RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
491 // destroy overflow lines now
492 FrameLines* overflowLines = RemoveOverflowLines();
493 if (overflowLines) {
494 nsLineBox::DeleteLineList(presContext, overflowLines->mLines,
495 &overflowLines->mFrames, aContext);
496 delete overflowLines;
499 if (HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
500 SafelyDestroyFrameListProp(aContext, presShell,
501 OverflowOutOfFlowsProperty());
502 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
505 if (HasOutsideMarker()) {
506 SafelyDestroyFrameListProp(aContext, presShell, OutsideMarkerProperty());
507 RemoveStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER);
510 nsContainerFrame::Destroy(aContext);
513 /* virtual */
514 nsILineIterator* nsBlockFrame::GetLineIterator() {
515 nsLineIterator* iter = GetProperty(LineIteratorProperty());
516 if (!iter) {
517 const nsStyleVisibility* visibility = StyleVisibility();
518 iter = new nsLineIterator(mLines,
519 visibility->mDirection == StyleDirection::Rtl);
520 SetProperty(LineIteratorProperty(), iter);
522 return iter;
525 NS_QUERYFRAME_HEAD(nsBlockFrame)
526 NS_QUERYFRAME_ENTRY(nsBlockFrame)
527 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
529 #ifdef DEBUG_FRAME_DUMP
530 void nsBlockFrame::List(FILE* out, const char* aPrefix,
531 ListFlags aFlags) const {
532 nsCString str;
533 ListGeneric(str, aPrefix, aFlags);
535 fprintf_stderr(out, "%s <\n", str.get());
537 nsCString pfx(aPrefix);
538 pfx += " ";
540 // Output the lines
541 if (!mLines.empty()) {
542 ConstLineIterator line = LinesBegin(), line_end = LinesEnd();
543 for (; line != line_end; ++line) {
544 line->List(out, pfx.get(), aFlags);
548 // Output the overflow lines.
549 const FrameLines* overflowLines = GetOverflowLines();
550 if (overflowLines && !overflowLines->mLines.empty()) {
551 fprintf_stderr(out, "%sOverflow-lines %p/%p <\n", pfx.get(), overflowLines,
552 &overflowLines->mFrames);
553 nsCString nestedPfx(pfx);
554 nestedPfx += " ";
555 ConstLineIterator line = overflowLines->mLines.begin(),
556 line_end = overflowLines->mLines.end();
557 for (; line != line_end; ++line) {
558 line->List(out, nestedPfx.get(), aFlags);
560 fprintf_stderr(out, "%s>\n", pfx.get());
563 // skip the principal list - we printed the lines above
564 // skip the overflow list - we printed the overflow lines above
565 ChildListIDs skip = {FrameChildListID::Principal, FrameChildListID::Overflow};
566 ListChildLists(out, pfx.get(), aFlags, skip);
568 fprintf_stderr(out, "%s>\n", aPrefix);
571 nsresult nsBlockFrame::GetFrameName(nsAString& aResult) const {
572 return MakeFrameName(u"Block"_ns, aResult);
574 #endif
576 void nsBlockFrame::InvalidateFrame(uint32_t aDisplayItemKey,
577 bool aRebuildDisplayItems) {
578 if (IsInSVGTextSubtree()) {
579 NS_ASSERTION(GetParent()->IsSVGTextFrame(),
580 "unexpected block frame in SVG text");
581 GetParent()->InvalidateFrame();
582 return;
584 nsContainerFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems);
587 void nsBlockFrame::InvalidateFrameWithRect(const nsRect& aRect,
588 uint32_t aDisplayItemKey,
589 bool aRebuildDisplayItems) {
590 if (IsInSVGTextSubtree()) {
591 NS_ASSERTION(GetParent()->IsSVGTextFrame(),
592 "unexpected block frame in SVG text");
593 GetParent()->InvalidateFrame();
594 return;
596 nsContainerFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey,
597 aRebuildDisplayItems);
600 nscoord nsBlockFrame::SynthesizeFallbackBaseline(
601 WritingMode aWM, BaselineSharingGroup aBaselineGroup) const {
602 return Baseline::SynthesizeBOffsetFromMarginBox(this, aWM, aBaselineGroup);
605 template <typename LineIteratorType>
606 Maybe<nscoord> nsBlockFrame::GetBaselineBOffset(
607 LineIteratorType aStart, LineIteratorType aEnd, WritingMode aWM,
608 BaselineSharingGroup aBaselineGroup,
609 BaselineExportContext aExportContext) const {
610 MOZ_ASSERT((std::is_same_v<LineIteratorType, ConstLineIterator> &&
611 aBaselineGroup == BaselineSharingGroup::First) ||
612 (std::is_same_v<LineIteratorType, ConstReverseLineIterator> &&
613 aBaselineGroup == BaselineSharingGroup::Last),
614 "Iterator direction must match baseline sharing group.");
615 for (auto line = aStart; line != aEnd; ++line) {
616 if (!line->IsBlock()) {
617 // XXX Is this the right test? We have some bogus empty lines
618 // floating around, but IsEmpty is perhaps too weak.
619 if (line->BSize() != 0 || !line->IsEmpty()) {
620 const auto ascent = line->BStart() + line->GetLogicalAscent();
621 if (aBaselineGroup == BaselineSharingGroup::Last) {
622 return Some(BSize(aWM) - ascent);
624 return Some(ascent);
626 continue;
628 nsIFrame* kid = line->mFirstChild;
629 if (aWM.IsOrthogonalTo(kid->GetWritingMode())) {
630 continue;
632 if (aExportContext == BaselineExportContext::LineLayout &&
633 kid->IsTableWrapperFrame()) {
634 // `<table>` in inline-block context does not export any baseline.
635 continue;
637 const auto kidBaselineGroup =
638 aExportContext == BaselineExportContext::LineLayout
639 ? kid->GetDefaultBaselineSharingGroup()
640 : aBaselineGroup;
641 const auto kidBaseline =
642 kid->GetNaturalBaselineBOffset(aWM, kidBaselineGroup, aExportContext);
643 if (!kidBaseline) {
644 continue;
646 auto result = *kidBaseline;
647 if (kidBaselineGroup == BaselineSharingGroup::Last) {
648 result = kid->BSize(aWM) - result;
650 // Ignore relative positioning for baseline calculations.
651 const nsSize& sz = line->mContainerSize;
652 result += kid->GetLogicalNormalPosition(aWM, sz).B(aWM);
653 if (aBaselineGroup == BaselineSharingGroup::Last) {
654 return Some(BSize(aWM) - result);
656 return Some(result);
658 return Nothing{};
661 Maybe<nscoord> nsBlockFrame::GetNaturalBaselineBOffset(
662 WritingMode aWM, BaselineSharingGroup aBaselineGroup,
663 BaselineExportContext aExportContext) const {
664 if (StyleDisplay()->IsContainLayout()) {
665 return Nothing{};
668 if (aBaselineGroup == BaselineSharingGroup::First) {
669 return GetBaselineBOffset(LinesBegin(), LinesEnd(), aWM, aBaselineGroup,
670 aExportContext);
673 return GetBaselineBOffset(LinesRBegin(), LinesREnd(), aWM, aBaselineGroup,
674 aExportContext);
677 nscoord nsBlockFrame::GetCaretBaseline() const {
678 nsRect contentRect = GetContentRect();
679 nsMargin bp = GetUsedBorderAndPadding();
681 if (!mLines.empty()) {
682 ConstLineIterator line = LinesBegin();
683 if (!line->IsEmpty()) {
684 if (line->IsBlock()) {
685 return bp.top + line->mFirstChild->GetCaretBaseline();
687 return line->BStart() + line->GetLogicalAscent();
691 float inflation = nsLayoutUtils::FontSizeInflationFor(this);
692 RefPtr<nsFontMetrics> fm =
693 nsLayoutUtils::GetFontMetricsForFrame(this, inflation);
694 nscoord lineHeight = ReflowInput::CalcLineHeight(
695 *Style(), PresContext(), GetContent(), contentRect.height, inflation);
696 const WritingMode wm = GetWritingMode();
697 return nsLayoutUtils::GetCenteredFontBaseline(fm, lineHeight,
698 wm.IsLineInverted()) +
699 bp.top;
702 /////////////////////////////////////////////////////////////////////////////
703 // Child frame enumeration
705 const nsFrameList& nsBlockFrame::GetChildList(ChildListID aListID) const {
706 switch (aListID) {
707 case FrameChildListID::Principal:
708 return mFrames;
709 case FrameChildListID::Overflow: {
710 FrameLines* overflowLines = GetOverflowLines();
711 return overflowLines ? overflowLines->mFrames : nsFrameList::EmptyList();
713 case FrameChildListID::Float:
714 return mFloats;
715 case FrameChildListID::OverflowOutOfFlow: {
716 const nsFrameList* list = GetOverflowOutOfFlows();
717 return list ? *list : nsFrameList::EmptyList();
719 case FrameChildListID::PushedFloats: {
720 const nsFrameList* list = GetPushedFloats();
721 return list ? *list : nsFrameList::EmptyList();
723 case FrameChildListID::Bullet: {
724 const nsFrameList* list = GetOutsideMarkerList();
725 return list ? *list : nsFrameList::EmptyList();
727 default:
728 return nsContainerFrame::GetChildList(aListID);
732 void nsBlockFrame::GetChildLists(nsTArray<ChildList>* aLists) const {
733 nsContainerFrame::GetChildLists(aLists);
734 FrameLines* overflowLines = GetOverflowLines();
735 if (overflowLines) {
736 overflowLines->mFrames.AppendIfNonempty(aLists, FrameChildListID::Overflow);
738 const nsFrameList* list = GetOverflowOutOfFlows();
739 if (list) {
740 list->AppendIfNonempty(aLists, FrameChildListID::OverflowOutOfFlow);
742 mFloats.AppendIfNonempty(aLists, FrameChildListID::Float);
743 list = GetOutsideMarkerList();
744 if (list) {
745 list->AppendIfNonempty(aLists, FrameChildListID::Bullet);
747 list = GetPushedFloats();
748 if (list) {
749 list->AppendIfNonempty(aLists, FrameChildListID::PushedFloats);
753 /* virtual */
754 bool nsBlockFrame::IsFloatContainingBlock() const { return true; }
757 * Remove the first line from aFromLines and adjust the associated frame list
758 * aFromFrames accordingly. The removed line is assigned to *aOutLine and
759 * a frame list with its frames is assigned to *aOutFrames, i.e. the frames
760 * that were extracted from the head of aFromFrames.
761 * aFromLines must contain at least one line, the line may be empty.
762 * @return true if aFromLines becomes empty
764 static bool RemoveFirstLine(nsLineList& aFromLines, nsFrameList& aFromFrames,
765 nsLineBox** aOutLine, nsFrameList* aOutFrames) {
766 nsLineList_iterator removedLine = aFromLines.begin();
767 *aOutLine = removedLine;
768 nsLineList_iterator next = aFromLines.erase(removedLine);
769 bool isLastLine = next == aFromLines.end();
770 nsIFrame* firstFrameInNextLine = isLastLine ? nullptr : next->mFirstChild;
771 *aOutFrames = aFromFrames.TakeFramesBefore(firstFrameInNextLine);
772 return isLastLine;
775 //////////////////////////////////////////////////////////////////////
776 // Reflow methods
778 /* virtual */
779 void nsBlockFrame::MarkIntrinsicISizesDirty() {
780 nsBlockFrame* dirtyBlock = static_cast<nsBlockFrame*>(FirstContinuation());
781 dirtyBlock->mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
782 dirtyBlock->mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN;
783 if (!HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION)) {
784 for (nsIFrame* frame = dirtyBlock; frame;
785 frame = frame->GetNextContinuation()) {
786 frame->AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
790 nsContainerFrame::MarkIntrinsicISizesDirty();
793 void nsBlockFrame::CheckIntrinsicCacheAgainstShrinkWrapState() {
794 nsPresContext* presContext = PresContext();
795 if (!nsLayoutUtils::FontSizeInflationEnabled(presContext)) {
796 return;
798 bool inflationEnabled = !presContext->mInflationDisabledForShrinkWrap;
799 if (inflationEnabled != HasAnyStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED)) {
800 mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
801 mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN;
802 if (inflationEnabled) {
803 AddStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED);
804 } else {
805 RemoveStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED);
810 // Whether this line is indented by the text-indent amount.
811 bool nsBlockFrame::TextIndentAppliesTo(const LineIterator& aLine) const {
812 const auto& textIndent = StyleText()->mTextIndent;
814 bool isFirstLineOrAfterHardBreak = [&] {
815 if (aLine != LinesBegin()) {
816 // If not the first line of the block, but 'each-line' is in effect,
817 // check if the previous line was not wrapped.
818 return textIndent.each_line && !aLine.prev()->IsLineWrapped();
820 if (nsBlockFrame* prevBlock = do_QueryFrame(GetPrevInFlow())) {
821 // There's a prev-in-flow, so this only counts as a first-line if
822 // 'each-line' and the prev-in-flow's last line was not wrapped.
823 return textIndent.each_line &&
824 (prevBlock->Lines().empty() ||
825 !prevBlock->LinesEnd().prev()->IsLineWrapped());
827 return true;
828 }();
830 // The 'hanging' option inverts which lines are/aren't indented.
831 return isFirstLineOrAfterHardBreak != textIndent.hanging;
834 /* virtual */
835 nscoord nsBlockFrame::GetMinISize(gfxContext* aRenderingContext) {
836 nsIFrame* firstInFlow = FirstContinuation();
837 if (firstInFlow != this) {
838 return firstInFlow->GetMinISize(aRenderingContext);
841 DISPLAY_MIN_INLINE_SIZE(this, mCachedMinISize);
843 CheckIntrinsicCacheAgainstShrinkWrapState();
845 if (mCachedMinISize != NS_INTRINSIC_ISIZE_UNKNOWN) {
846 return mCachedMinISize;
849 if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
850 mCachedMinISize = *containISize;
851 return mCachedMinISize;
854 #ifdef DEBUG
855 if (gNoisyIntrinsic) {
856 IndentBy(stdout, gNoiseIndent);
857 ListTag(stdout);
858 printf(": GetMinISize\n");
860 AutoNoisyIndenter indenter(gNoisyIntrinsic);
861 #endif
863 for (nsBlockFrame* curFrame = this; curFrame;
864 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
865 curFrame->LazyMarkLinesDirty();
868 if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
869 PresContext()->BidiEnabled()) {
870 ResolveBidi();
873 const bool whiteSpaceCanWrap = StyleText()->WhiteSpaceCanWrapStyle();
874 InlineMinISizeData data;
875 for (nsBlockFrame* curFrame = this; curFrame;
876 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
877 for (LineIterator line = curFrame->LinesBegin(),
878 line_end = curFrame->LinesEnd();
879 line != line_end; ++line) {
880 #ifdef DEBUG
881 if (gNoisyIntrinsic) {
882 IndentBy(stdout, gNoiseIndent);
883 printf("line (%s%s)\n", line->IsBlock() ? "block" : "inline",
884 line->IsEmpty() ? ", empty" : "");
886 AutoNoisyIndenter lineindent(gNoisyIntrinsic);
887 #endif
888 if (line->IsBlock()) {
889 data.ForceBreak();
890 data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(
891 aRenderingContext, line->mFirstChild, IntrinsicISizeType::MinISize);
892 data.ForceBreak();
893 } else {
894 if (!curFrame->GetPrevContinuation() && TextIndentAppliesTo(line)) {
895 data.mCurrentLine += StyleText()->mTextIndent.length.Resolve(0);
897 data.mLine = &line;
898 data.SetLineContainer(curFrame);
899 nsIFrame* kid = line->mFirstChild;
900 for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end;
901 ++i, kid = kid->GetNextSibling()) {
902 kid->AddInlineMinISize(aRenderingContext, &data);
903 if (whiteSpaceCanWrap && data.mTrailingWhitespace) {
904 data.OptionallyBreak();
908 #ifdef DEBUG
909 if (gNoisyIntrinsic) {
910 IndentBy(stdout, gNoiseIndent);
911 printf("min: [prevLines=%d currentLine=%d]\n", data.mPrevLines,
912 data.mCurrentLine);
914 #endif
917 data.ForceBreak();
919 mCachedMinISize = data.mPrevLines;
920 return mCachedMinISize;
923 /* virtual */
924 nscoord nsBlockFrame::GetPrefISize(gfxContext* aRenderingContext) {
925 nsIFrame* firstInFlow = FirstContinuation();
926 if (firstInFlow != this) {
927 return firstInFlow->GetPrefISize(aRenderingContext);
930 DISPLAY_PREF_INLINE_SIZE(this, mCachedPrefISize);
932 CheckIntrinsicCacheAgainstShrinkWrapState();
934 if (mCachedPrefISize != NS_INTRINSIC_ISIZE_UNKNOWN) {
935 return mCachedPrefISize;
938 if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
939 mCachedPrefISize = *containISize;
940 return mCachedPrefISize;
943 #ifdef DEBUG
944 if (gNoisyIntrinsic) {
945 IndentBy(stdout, gNoiseIndent);
946 ListTag(stdout);
947 printf(": GetPrefISize\n");
949 AutoNoisyIndenter indenter(gNoisyIntrinsic);
950 #endif
952 for (nsBlockFrame* curFrame = this; curFrame;
953 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
954 curFrame->LazyMarkLinesDirty();
957 if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
958 PresContext()->BidiEnabled()) {
959 ResolveBidi();
961 InlinePrefISizeData data;
962 for (nsBlockFrame* curFrame = this; curFrame;
963 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
964 for (LineIterator line = curFrame->LinesBegin(),
965 line_end = curFrame->LinesEnd();
966 line != line_end; ++line) {
967 #ifdef DEBUG
968 if (gNoisyIntrinsic) {
969 IndentBy(stdout, gNoiseIndent);
970 printf("line (%s%s)\n", line->IsBlock() ? "block" : "inline",
971 line->IsEmpty() ? ", empty" : "");
973 AutoNoisyIndenter lineindent(gNoisyIntrinsic);
974 #endif
975 if (line->IsBlock()) {
976 StyleClear clearType;
977 if (!data.mLineIsEmpty || BlockCanIntersectFloats(line->mFirstChild)) {
978 clearType = StyleClear::Both;
979 } else {
980 clearType = line->mFirstChild->StyleDisplay()->mClear;
982 data.ForceBreak(clearType);
983 data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(
984 aRenderingContext, line->mFirstChild,
985 IntrinsicISizeType::PrefISize);
986 data.ForceBreak();
987 } else {
988 if (!curFrame->GetPrevContinuation() && TextIndentAppliesTo(line)) {
989 nscoord indent = StyleText()->mTextIndent.length.Resolve(0);
990 data.mCurrentLine += indent;
991 // XXXmats should the test below be indent > 0?
992 if (indent != nscoord(0)) {
993 data.mLineIsEmpty = false;
996 data.mLine = &line;
997 data.SetLineContainer(curFrame);
998 nsIFrame* kid = line->mFirstChild;
999 for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end;
1000 ++i, kid = kid->GetNextSibling()) {
1001 kid->AddInlinePrefISize(aRenderingContext, &data);
1004 #ifdef DEBUG
1005 if (gNoisyIntrinsic) {
1006 IndentBy(stdout, gNoiseIndent);
1007 printf("pref: [prevLines=%d currentLine=%d]\n", data.mPrevLines,
1008 data.mCurrentLine);
1010 #endif
1013 data.ForceBreak();
1015 mCachedPrefISize = data.mPrevLines;
1016 return mCachedPrefISize;
1019 nsRect nsBlockFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const {
1020 // be conservative
1021 if (Style()->HasTextDecorationLines()) {
1022 return InkOverflowRect();
1024 return ComputeSimpleTightBounds(aDrawTarget);
1027 /* virtual */
1028 nsresult nsBlockFrame::GetPrefWidthTightBounds(gfxContext* aRenderingContext,
1029 nscoord* aX, nscoord* aXMost) {
1030 nsIFrame* firstInFlow = FirstContinuation();
1031 if (firstInFlow != this) {
1032 return firstInFlow->GetPrefWidthTightBounds(aRenderingContext, aX, aXMost);
1035 *aX = 0;
1036 *aXMost = 0;
1038 nsresult rv;
1039 InlinePrefISizeData data;
1040 for (nsBlockFrame* curFrame = this; curFrame;
1041 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
1042 for (LineIterator line = curFrame->LinesBegin(),
1043 line_end = curFrame->LinesEnd();
1044 line != line_end; ++line) {
1045 nscoord childX, childXMost;
1046 if (line->IsBlock()) {
1047 data.ForceBreak();
1048 rv = line->mFirstChild->GetPrefWidthTightBounds(aRenderingContext,
1049 &childX, &childXMost);
1050 NS_ENSURE_SUCCESS(rv, rv);
1051 *aX = std::min(*aX, childX);
1052 *aXMost = std::max(*aXMost, childXMost);
1053 } else {
1054 if (!curFrame->GetPrevContinuation() && TextIndentAppliesTo(line)) {
1055 data.mCurrentLine += StyleText()->mTextIndent.length.Resolve(0);
1057 data.mLine = &line;
1058 data.SetLineContainer(curFrame);
1059 nsIFrame* kid = line->mFirstChild;
1060 for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end;
1061 ++i, kid = kid->GetNextSibling()) {
1062 rv = kid->GetPrefWidthTightBounds(aRenderingContext, &childX,
1063 &childXMost);
1064 NS_ENSURE_SUCCESS(rv, rv);
1065 *aX = std::min(*aX, data.mCurrentLine + childX);
1066 *aXMost = std::max(*aXMost, data.mCurrentLine + childXMost);
1067 kid->AddInlinePrefISize(aRenderingContext, &data);
1072 data.ForceBreak();
1074 return NS_OK;
1078 * Return whether aNewAvailableSpace is smaller *on either side*
1079 * (inline-start or inline-end) than aOldAvailableSpace, so that we know
1080 * if we need to redo layout on an line, replaced block, or block
1081 * formatting context, because its height (which we used to compute
1082 * aNewAvailableSpace) caused it to intersect additional floats.
1084 static bool AvailableSpaceShrunk(WritingMode aWM,
1085 const LogicalRect& aOldAvailableSpace,
1086 const LogicalRect& aNewAvailableSpace,
1087 bool aCanGrow /* debug-only */) {
1088 if (aNewAvailableSpace.ISize(aWM) == 0) {
1089 // Positions are not significant if the inline size is zero.
1090 return aOldAvailableSpace.ISize(aWM) != 0;
1092 if (aCanGrow) {
1093 NS_ASSERTION(
1094 aNewAvailableSpace.IStart(aWM) <= aOldAvailableSpace.IStart(aWM) ||
1095 aNewAvailableSpace.IEnd(aWM) <= aOldAvailableSpace.IEnd(aWM),
1096 "available space should not shrink on the start side and "
1097 "grow on the end side");
1098 NS_ASSERTION(
1099 aNewAvailableSpace.IStart(aWM) >= aOldAvailableSpace.IStart(aWM) ||
1100 aNewAvailableSpace.IEnd(aWM) >= aOldAvailableSpace.IEnd(aWM),
1101 "available space should not grow on the start side and "
1102 "shrink on the end side");
1103 } else {
1104 NS_ASSERTION(
1105 aOldAvailableSpace.IStart(aWM) <= aNewAvailableSpace.IStart(aWM) &&
1106 aOldAvailableSpace.IEnd(aWM) >= aNewAvailableSpace.IEnd(aWM),
1107 "available space should never grow");
1109 // Have we shrunk on either side?
1110 return aNewAvailableSpace.IStart(aWM) > aOldAvailableSpace.IStart(aWM) ||
1111 aNewAvailableSpace.IEnd(aWM) < aOldAvailableSpace.IEnd(aWM);
1114 static LogicalSize CalculateContainingBlockSizeForAbsolutes(
1115 WritingMode aWM, const ReflowInput& aReflowInput, LogicalSize aFrameSize) {
1116 // The issue here is that for a 'height' of 'auto' the reflow input
1117 // code won't know how to calculate the containing block height
1118 // because it's calculated bottom up. So we use our own computed
1119 // size as the dimensions.
1120 nsIFrame* frame = aReflowInput.mFrame;
1122 LogicalSize cbSize(aFrameSize);
1123 // Containing block is relative to the padding edge
1124 const LogicalMargin border = aReflowInput.ComputedLogicalBorder(aWM);
1125 cbSize.ISize(aWM) -= border.IStartEnd(aWM);
1126 cbSize.BSize(aWM) -= border.BStartEnd(aWM);
1128 if (frame->GetParent()->GetContent() != frame->GetContent() ||
1129 frame->GetParent()->IsCanvasFrame()) {
1130 return cbSize;
1133 // We are a wrapped frame for the content (and the wrapper is not the
1134 // canvas frame, whose size is not meaningful here).
1135 // Use the container's dimensions, if they have been precomputed.
1136 // XXX This is a hack! We really should be waiting until the outermost
1137 // frame is fully reflowed and using the resulting dimensions, even
1138 // if they're intrinsic.
1139 // In fact we should be attaching absolute children to the outermost
1140 // frame and not always sticking them in block frames.
1142 // First, find the reflow input for the outermost frame for this content.
1143 const ReflowInput* lastRI = &aReflowInput;
1144 DebugOnly<const ReflowInput*> lastButOneRI = &aReflowInput;
1145 while (lastRI->mParentReflowInput &&
1146 lastRI->mParentReflowInput->mFrame->GetContent() ==
1147 frame->GetContent()) {
1148 lastButOneRI = lastRI;
1149 lastRI = lastRI->mParentReflowInput;
1152 if (lastRI == &aReflowInput) {
1153 return cbSize;
1156 // For scroll containers, we can just use cbSize (which is the padding-box
1157 // size of the scrolled-content frame).
1158 if (nsIScrollableFrame* scrollFrame = do_QueryFrame(lastRI->mFrame)) {
1159 // Assert that we're not missing any frames between the abspos containing
1160 // block and the scroll container.
1161 // the parent.
1162 Unused << scrollFrame;
1163 MOZ_ASSERT(lastButOneRI == &aReflowInput);
1164 return cbSize;
1167 // Same for fieldsets, where the inner anonymous frame has the correct padding
1168 // area with the legend taken into account.
1169 if (lastRI->mFrame->IsFieldSetFrame()) {
1170 return cbSize;
1173 // We found a reflow input for the outermost wrapping frame, so use
1174 // its computed metrics if available, converted to our writing mode
1175 const LogicalSize lastRISize = lastRI->ComputedSize(aWM);
1176 const LogicalMargin lastRIPadding = lastRI->ComputedLogicalPadding(aWM);
1177 if (lastRISize.ISize(aWM) != NS_UNCONSTRAINEDSIZE) {
1178 cbSize.ISize(aWM) =
1179 std::max(0, lastRISize.ISize(aWM) + lastRIPadding.IStartEnd(aWM));
1181 if (lastRISize.BSize(aWM) != NS_UNCONSTRAINEDSIZE) {
1182 cbSize.BSize(aWM) =
1183 std::max(0, lastRISize.BSize(aWM) + lastRIPadding.BStartEnd(aWM));
1186 return cbSize;
1190 * Returns aFrame if it is a non-BFC block frame, and null otherwise.
1192 * This is used to determine whether to recurse into aFrame when applying
1193 * -webkit-line-clamp.
1195 static const nsBlockFrame* GetAsLineClampDescendant(const nsIFrame* aFrame) {
1196 if (const nsBlockFrame* block = do_QueryFrame(aFrame)) {
1197 if (!block->HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS)) {
1198 return block;
1201 return nullptr;
1204 static nsBlockFrame* GetAsLineClampDescendant(nsIFrame* aFrame) {
1205 return const_cast<nsBlockFrame*>(
1206 GetAsLineClampDescendant(const_cast<const nsIFrame*>(aFrame)));
1209 static bool IsLineClampRoot(const nsBlockFrame* aFrame) {
1210 if (!aFrame->StyleDisplay()->mWebkitLineClamp) {
1211 return false;
1214 if (!aFrame->HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS)) {
1215 return false;
1218 if (StaticPrefs::layout_css_webkit_line_clamp_block_enabled()) {
1219 return true;
1222 // For now, -webkit-box is the only thing allowed to be a line-clamp root.
1223 // Ideally we'd just make this work everywhere, but for now we're carrying
1224 // this forward as a limitation on the legacy -webkit-line-clamp feature,
1225 // since relaxing this limitation might create webcompat trouble.
1226 auto origDisplay = [&] {
1227 if (aFrame->Style()->GetPseudoType() == PseudoStyleType::scrolledContent) {
1228 // If we're the anonymous block inside the scroll frame, we need to look
1229 // at the original display of our parent frame.
1230 MOZ_ASSERT(aFrame->GetParent());
1231 const auto& parentDisp = *aFrame->GetParent()->StyleDisplay();
1232 MOZ_ASSERT(parentDisp.mWebkitLineClamp ==
1233 aFrame->StyleDisplay()->mWebkitLineClamp,
1234 ":-moz-scrolled-content should inherit -webkit-line-clamp, "
1235 "via rule in UA stylesheet");
1236 return parentDisp.mOriginalDisplay;
1238 return aFrame->StyleDisplay()->mOriginalDisplay;
1239 }();
1240 return origDisplay.Inside() == StyleDisplayInside::WebkitBox;
1243 bool nsBlockFrame::IsInLineClampContext() const {
1244 if (IsLineClampRoot(this)) {
1245 return true;
1247 const nsBlockFrame* cur = this;
1248 while (GetAsLineClampDescendant(cur)) {
1249 cur = do_QueryFrame(cur->GetParent());
1250 if (!cur) {
1251 return false;
1253 if (IsLineClampRoot(cur)) {
1254 return true;
1257 return false;
1261 * Iterator over all descendant inline line boxes, except for those that are
1262 * under an independent formatting context.
1264 class MOZ_RAII LineClampLineIterator {
1265 public:
1266 explicit LineClampLineIterator(nsBlockFrame* aFrame)
1267 : mCur(aFrame->LinesBegin()),
1268 mEnd(aFrame->LinesEnd()),
1269 mCurrentFrame(mCur == mEnd ? nullptr : aFrame) {
1270 if (mCur != mEnd && !mCur->IsInline()) {
1271 Advance();
1275 nsLineBox* GetCurrentLine() { return mCurrentFrame ? mCur.get() : nullptr; }
1276 nsBlockFrame* GetCurrentFrame() { return mCurrentFrame; }
1278 // Advances the iterator to the next line line.
1280 // Next() shouldn't be called once the iterator is at the end, which can be
1281 // checked for by GetCurrentLine() or GetCurrentFrame() returning null.
1282 void Next() {
1283 MOZ_ASSERT(mCur != mEnd && mCurrentFrame,
1284 "Don't call Next() when the iterator is at the end");
1285 ++mCur;
1286 Advance();
1289 private:
1290 void Advance() {
1291 for (;;) {
1292 if (mCur == mEnd) {
1293 // Reached the end of the current block. Pop the parent off the
1294 // stack; if there isn't one, then we've reached the end.
1295 if (mStack.IsEmpty()) {
1296 mCurrentFrame = nullptr;
1297 break;
1299 auto entry = mStack.PopLastElement();
1300 mCurrentFrame = entry.first;
1301 mCur = entry.second;
1302 mEnd = mCurrentFrame->LinesEnd();
1303 } else if (mCur->IsBlock()) {
1304 if (nsBlockFrame* child = GetAsLineClampDescendant(mCur->mFirstChild)) {
1305 nsBlockFrame::LineIterator next = mCur;
1306 ++next;
1307 mStack.AppendElement(std::make_pair(mCurrentFrame, next));
1308 mCur = child->LinesBegin();
1309 mEnd = child->LinesEnd();
1310 mCurrentFrame = child;
1311 } else {
1312 // Some kind of frame we shouldn't descend into.
1313 ++mCur;
1315 } else {
1316 MOZ_ASSERT(mCur->IsInline());
1317 break;
1322 // The current line within the current block.
1324 // When this is equal to mEnd, the iterator is at its end, and mCurrentFrame
1325 // is set to null.
1326 nsBlockFrame::LineIterator mCur;
1328 // The iterator end for the current block.
1329 nsBlockFrame::LineIterator mEnd;
1331 // The current block.
1332 nsBlockFrame* mCurrentFrame;
1334 // Stack of mCurrentFrame and mEnd values that we push and pop as we enter and
1335 // exist blocks.
1336 AutoTArray<std::pair<nsBlockFrame*, nsBlockFrame::LineIterator>, 8> mStack;
1339 static bool ClearLineClampEllipsis(nsBlockFrame* aFrame) {
1340 if (!aFrame->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS)) {
1341 for (nsIFrame* f : aFrame->PrincipalChildList()) {
1342 if (nsBlockFrame* child = GetAsLineClampDescendant(f)) {
1343 if (ClearLineClampEllipsis(child)) {
1344 return true;
1348 return false;
1351 aFrame->RemoveStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS);
1353 for (auto& line : aFrame->Lines()) {
1354 if (line.HasLineClampEllipsis()) {
1355 line.ClearHasLineClampEllipsis();
1356 return true;
1360 // We didn't find a line with the ellipsis; it must have been deleted already.
1361 return true;
1364 void nsBlockFrame::ClearLineClampEllipsis() { ::ClearLineClampEllipsis(this); }
1366 void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
1367 const ReflowInput& aReflowInput,
1368 nsReflowStatus& aStatus) {
1369 if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
1370 FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
1371 return;
1374 MarkInReflow();
1375 DO_GLOBAL_REFLOW_COUNT("nsBlockFrame");
1376 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
1377 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
1379 #ifdef DEBUG
1380 if (gNoisyReflow) {
1381 IndentBy(stdout, gNoiseIndent);
1382 ListTag(stdout);
1383 printf(": begin reflow availSize=%d,%d computedSize=%d,%d\n",
1384 aReflowInput.AvailableISize(), aReflowInput.AvailableBSize(),
1385 aReflowInput.ComputedISize(), aReflowInput.ComputedBSize());
1387 AutoNoisyIndenter indent(gNoisy);
1388 PRTime start = 0; // Initialize these variablies to silence the compiler.
1389 int32_t ctc = 0; // We only use these if they are set (gLameReflowMetrics).
1390 if (gLameReflowMetrics) {
1391 start = PR_Now();
1392 ctc = nsLineBox::GetCtorCount();
1394 #endif
1396 // ColumnSetWrapper's children depend on ColumnSetWrapper's block-size or
1397 // max-block-size because both affect the children's available block-size.
1398 if (IsColumnSetWrapperFrame()) {
1399 AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
1402 Maybe<nscoord> restoreReflowInputAvailBSize;
1403 auto MaybeRestore = MakeScopeExit([&] {
1404 if (MOZ_UNLIKELY(restoreReflowInputAvailBSize)) {
1405 const_cast<ReflowInput&>(aReflowInput)
1406 .SetAvailableBSize(*restoreReflowInputAvailBSize);
1410 WritingMode wm = aReflowInput.GetWritingMode();
1411 const nscoord consumedBSize = CalcAndCacheConsumedBSize();
1412 const nscoord effectiveContentBoxBSize =
1413 GetEffectiveComputedBSize(aReflowInput, consumedBSize);
1414 // If we have non-auto block size, we're clipping our kids and we fit,
1415 // make sure our kids fit too.
1416 const PhysicalAxes physicalBlockAxis =
1417 wm.IsVertical() ? PhysicalAxes::Horizontal : PhysicalAxes::Vertical;
1418 if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
1419 aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE &&
1420 (ShouldApplyOverflowClipping(aReflowInput.mStyleDisplay) &
1421 physicalBlockAxis)) {
1422 LogicalMargin blockDirExtras =
1423 aReflowInput.ComputedLogicalBorderPadding(wm);
1424 if (GetLogicalSkipSides().BStart()) {
1425 blockDirExtras.BStart(wm) = 0;
1426 } else {
1427 // Block-end margin never causes us to create continuations, so we
1428 // don't need to worry about whether it fits in its entirety.
1429 blockDirExtras.BStart(wm) +=
1430 aReflowInput.ComputedLogicalMargin(wm).BStart(wm);
1433 if (effectiveContentBoxBSize + blockDirExtras.BStartEnd(wm) <=
1434 aReflowInput.AvailableBSize()) {
1435 restoreReflowInputAvailBSize.emplace(aReflowInput.AvailableBSize());
1436 const_cast<ReflowInput&>(aReflowInput)
1437 .SetAvailableBSize(NS_UNCONSTRAINEDSIZE);
1441 if (IsFrameTreeTooDeep(aReflowInput, aMetrics, aStatus)) {
1442 return;
1445 // OK, some lines may be reflowed. Blow away any saved line cursor
1446 // because we may invalidate the nondecreasing
1447 // overflowArea.InkOverflow().y/yMost invariant, and we may even
1448 // delete the line with the line cursor.
1449 ClearLineCursors();
1451 // See comment below about oldSize. Use *only* for the
1452 // abs-pos-containing-block-size-change optimization!
1453 nsSize oldSize = GetSize();
1455 // Should we create a float manager?
1456 nsAutoFloatManager autoFloatManager(const_cast<ReflowInput&>(aReflowInput));
1458 // XXXldb If we start storing the float manager in the frame rather
1459 // than keeping it around only during reflow then we should create it
1460 // only when there are actually floats to manage. Otherwise things
1461 // like tables will gain significant bloat.
1462 bool needFloatManager = nsBlockFrame::BlockNeedsFloatManager(this);
1463 if (needFloatManager) {
1464 autoFloatManager.CreateFloatManager(aPresContext);
1467 if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
1468 PresContext()->BidiEnabled()) {
1469 static_cast<nsBlockFrame*>(FirstContinuation())->ResolveBidi();
1472 // Whether to apply text-wrap: balance behavior.
1473 bool tryBalance =
1474 StyleText()->mTextWrapStyle == StyleTextWrapStyle::Balance &&
1475 !GetPrevContinuation();
1477 // Struct used to hold the "target" number of lines or clamp position to
1478 // maintain when doing text-wrap: balance.
1479 struct BalanceTarget {
1480 // If line-clamp is in effect, mContent and mOffset indicate the starting
1481 // position of the first line after the clamp limit. If line-clamp is not
1482 // in use, mContent is null and mOffset is the total number of lines that
1483 // the block must contain.
1484 nsIContent* mContent = nullptr;
1485 int32_t mOffset = -1;
1487 bool operator==(const BalanceTarget& aOther) const {
1488 return mContent == aOther.mContent && mOffset == aOther.mOffset;
1490 bool operator!=(const BalanceTarget& aOther) const {
1491 return !(*this == aOther);
1495 BalanceTarget balanceTarget;
1497 // Helpers for text-wrap: balance implementation:
1499 // Count the number of lines in the mLines list, but return -1 (to suppress
1500 // balancing) instead if the count is going to exceed aLimit, or if we
1501 // encounter a block.
1502 auto countLinesUpTo = [&](int32_t aLimit) -> int32_t {
1503 int32_t n = 0;
1504 for (auto iter = mLines.begin(); iter != mLines.end(); ++iter) {
1505 if (++n > aLimit || iter->IsBlock()) {
1506 return -1;
1509 return n;
1512 // Return a BalanceTarget record representing the position at which line-clamp
1513 // will take effect for the current line list. Only to be used when there are
1514 // enough lines that the clamp will apply.
1515 auto getClampPosition = [&](uint32_t aClampCount) -> BalanceTarget {
1516 MOZ_ASSERT(aClampCount < mLines.size());
1517 auto iter = mLines.begin();
1518 for (uint32_t i = 0; i < aClampCount; i++) {
1519 ++iter;
1521 nsIFrame* firstChild = iter->mFirstChild;
1522 if (!firstChild) {
1523 return BalanceTarget{};
1525 nsIContent* content = firstChild->GetContent();
1526 if (!content) {
1527 return BalanceTarget{};
1529 int32_t offset = 0;
1530 if (firstChild->IsTextFrame()) {
1531 auto* textFrame = static_cast<nsTextFrame*>(firstChild);
1532 offset = textFrame->GetContentOffset();
1534 return BalanceTarget{content, offset};
1537 // "balancing" is implemented by shortening the effective inline-size of the
1538 // lines, so that content will tend to be pushed down to fill later lines of
1539 // the block. `balanceInset` is the current amount of "inset" to apply, and
1540 // `balanceStep` is the increment to adjust it by for the next iteration.
1541 nscoord balanceStep = 0;
1543 // text-wrap: balance loop, executed only once if balancing is not required.
1544 nsReflowStatus reflowStatus;
1545 TrialReflowState trialState(consumedBSize, effectiveContentBoxBSize,
1546 needFloatManager);
1547 while (true) {
1548 // Save the initial floatManager state for repeated trial reflows.
1549 // We'll restore (and re-save) the initial state each time we repeat the
1550 // reflow.
1551 nsFloatManager::SavedState floatManagerState;
1552 aReflowInput.mFloatManager->PushState(&floatManagerState);
1554 aMetrics = ReflowOutput(aMetrics.GetWritingMode());
1555 reflowStatus =
1556 TrialReflow(aPresContext, aMetrics, aReflowInput, trialState);
1558 // Do we need to start a `text-wrap: balance` iteration?
1559 if (tryBalance) {
1560 tryBalance = false;
1561 // Don't try to balance an incomplete block, or if we had to use an
1562 // overflow-wrap break position in the initial reflow.
1563 if (!reflowStatus.IsFullyComplete() || trialState.mUsedOverflowWrap) {
1564 break;
1566 balanceTarget.mOffset =
1567 countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit());
1568 if (balanceTarget.mOffset < 2) {
1569 // If there are less than 2 lines, or the number exceeds the limit,
1570 // no balancing is needed; just break from the balance loop.
1571 break;
1573 // Initialize the amount of inset to try, and the iteration step size.
1574 balanceStep = aReflowInput.ComputedISize() / balanceTarget.mOffset;
1575 trialState.ResetForBalance(balanceStep);
1576 balanceStep /= 2;
1578 // If -webkit-line-clamp is in effect, then we need to maintain the
1579 // content location at which clamping occurs, rather than the total
1580 // number of lines in the block.
1581 if (StaticPrefs::layout_css_text_wrap_balance_after_clamp_enabled() &&
1582 IsLineClampRoot(this)) {
1583 uint32_t lineClampCount = aReflowInput.mStyleDisplay->mWebkitLineClamp;
1584 if (uint32_t(balanceTarget.mOffset) > lineClampCount) {
1585 auto t = getClampPosition(lineClampCount);
1586 if (t.mContent) {
1587 balanceTarget = t;
1592 // Restore initial floatManager state for a new trial with updated inset.
1593 aReflowInput.mFloatManager->PopState(&floatManagerState);
1594 continue;
1597 // Helper to determine whether the current trial succeeded (i.e. was able
1598 // to fit the content into the expected number of lines).
1599 auto trialSucceeded = [&]() -> bool {
1600 if (!reflowStatus.IsFullyComplete() || trialState.mUsedOverflowWrap) {
1601 return false;
1603 if (balanceTarget.mContent) {
1604 auto t = getClampPosition(aReflowInput.mStyleDisplay->mWebkitLineClamp);
1605 return t == balanceTarget;
1607 int32_t numLines =
1608 countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit());
1609 return numLines == balanceTarget.mOffset;
1612 // If we're in the process of a balance operation, check whether we've
1613 // inset by too much and either increase or reduce the inset for the next
1614 // iteration.
1615 if (balanceStep > 0) {
1616 if (trialSucceeded()) {
1617 trialState.ResetForBalance(balanceStep);
1618 } else {
1619 trialState.ResetForBalance(-balanceStep);
1621 balanceStep /= 2;
1623 aReflowInput.mFloatManager->PopState(&floatManagerState);
1624 continue;
1627 // If we were attempting to balance, check whether the final iteration was
1628 // successful, and if not, back up by one step.
1629 if (balanceTarget.mOffset >= 0) {
1630 if (!trialState.mInset || trialSucceeded()) {
1631 break;
1633 trialState.ResetForBalance(-1);
1635 aReflowInput.mFloatManager->PopState(&floatManagerState);
1636 continue;
1639 // If we reach here, no balancing was required, so just exit; we don't
1640 // reset (pop) the floatManager state because this is the reflow we're
1641 // going to keep. So the saved state is just dropped.
1642 break;
1643 } // End of text-wrap: balance retry loop
1645 // If the block direction is right-to-left, we need to update the bounds of
1646 // lines that were placed relative to mContainerSize during reflow, as
1647 // we typically do not know the true container size until we've reflowed all
1648 // its children. So we use a dummy mContainerSize during reflow (see
1649 // BlockReflowState's constructor) and then fix up the positions of the
1650 // lines here, once the final block size is known.
1652 // Note that writing-mode:vertical-rl is the only case where the block
1653 // logical direction progresses in a negative physical direction, and
1654 // therefore block-dir coordinate conversion depends on knowing the width
1655 // of the coordinate space in order to translate between the logical and
1656 // physical origins.
1657 if (aReflowInput.GetWritingMode().IsVerticalRL()) {
1658 nsSize containerSize = aMetrics.PhysicalSize();
1659 nscoord deltaX = containerSize.width - trialState.mContainerWidth;
1660 if (deltaX != 0) {
1661 // We compute our lines and markers' overflow areas later in
1662 // ComputeOverflowAreas(), so we don't need to adjust their overflow areas
1663 // here.
1664 const nsPoint physicalDelta(deltaX, 0);
1665 for (auto& line : Lines()) {
1666 UpdateLineContainerSize(&line, containerSize);
1668 trialState.mFcBounds.Clear();
1669 for (nsIFrame* f : mFloats) {
1670 f->MovePositionBy(physicalDelta);
1671 ConsiderChildOverflow(trialState.mFcBounds, f);
1673 nsFrameList* markerList = GetOutsideMarkerList();
1674 if (markerList) {
1675 for (nsIFrame* f : *markerList) {
1676 f->MovePositionBy(physicalDelta);
1679 if (nsFrameList* overflowContainers = GetOverflowContainers()) {
1680 trialState.mOcBounds.Clear();
1681 for (nsIFrame* f : *overflowContainers) {
1682 f->MovePositionBy(physicalDelta);
1683 ConsiderChildOverflow(trialState.mOcBounds, f);
1689 aMetrics.SetOverflowAreasToDesiredBounds();
1690 ComputeOverflowAreas(aMetrics.mOverflowAreas,
1691 trialState.mBlockEndEdgeOfChildren,
1692 aReflowInput.mStyleDisplay);
1693 // Factor overflow container child bounds into the overflow area
1694 aMetrics.mOverflowAreas.UnionWith(trialState.mOcBounds);
1695 // Factor pushed float child bounds into the overflow area
1696 aMetrics.mOverflowAreas.UnionWith(trialState.mFcBounds);
1698 // Let the absolutely positioned container reflow any absolutely positioned
1699 // child frames that need to be reflowed, e.g., elements with a percentage
1700 // based width/height
1701 // We want to do this under either of two conditions:
1702 // 1. If we didn't do the incremental reflow above.
1703 // 2. If our size changed.
1704 // Even though it's the padding edge that's the containing block, we
1705 // can use our rect (the border edge) since if the border style
1706 // changed, the reflow would have been targeted at us so we'd satisfy
1707 // condition 1.
1708 // XXX checking oldSize is bogus, there are various reasons we might have
1709 // reflowed but our size might not have been changed to what we
1710 // asked for (e.g., we ended up being pushed to a new page)
1711 // When WillReflowAgainForClearance is true, we will reflow again without
1712 // resetting the size. Because of this, we must not reflow our abs-pos
1713 // children in that situation --- what we think is our "new size" will not be
1714 // our real new size. This also happens to be more efficient.
1715 WritingMode parentWM = aMetrics.GetWritingMode();
1716 if (HasAbsolutelyPositionedChildren()) {
1717 nsAbsoluteContainingBlock* absoluteContainer = GetAbsoluteContainingBlock();
1718 bool haveInterrupt = aPresContext->HasPendingInterrupt();
1719 if (aReflowInput.WillReflowAgainForClearance() || haveInterrupt) {
1720 // Make sure that when we reflow again we'll actually reflow all the abs
1721 // pos frames that might conceivably depend on our size (or all of them,
1722 // if we're dirty right now and interrupted; in that case we also need
1723 // to mark them all with NS_FRAME_IS_DIRTY). Sadly, we can't do much
1724 // better than that, because we don't really know what our size will be,
1725 // and it might in fact not change on the followup reflow!
1726 if (haveInterrupt && HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
1727 absoluteContainer->MarkAllFramesDirty();
1728 } else {
1729 absoluteContainer->MarkSizeDependentFramesDirty();
1731 if (haveInterrupt) {
1732 // We're not going to reflow absolute frames; make sure to account for
1733 // their existing overflow areas, which is usually a side effect of this
1734 // reflow.
1736 // TODO(emilio): nsAbsoluteContainingBlock::Reflow already checks for
1737 // interrupt, can we just rely on it and unconditionally take the else
1738 // branch below? That's a bit more subtle / risky, since I don't see
1739 // what would reflow them in that case if they depended on our size.
1740 for (nsIFrame* kid = absoluteContainer->GetChildList().FirstChild();
1741 kid; kid = kid->GetNextSibling()) {
1742 ConsiderChildOverflow(aMetrics.mOverflowAreas, kid);
1745 } else {
1746 LogicalSize containingBlockSize =
1747 CalculateContainingBlockSizeForAbsolutes(parentWM, aReflowInput,
1748 aMetrics.Size(parentWM));
1750 // Mark frames that depend on changes we just made to this frame as dirty:
1751 // Now we can assume that the padding edge hasn't moved.
1752 // We need to reflow the absolutes if one of them depends on
1753 // its placeholder position, or the containing block size in a
1754 // direction in which the containing block size might have
1755 // changed.
1757 // XXX "width" and "height" in this block will become ISize and BSize
1758 // when nsAbsoluteContainingBlock is logicalized
1759 bool cbWidthChanged = aMetrics.Width() != oldSize.width;
1760 bool isRoot = !GetContent()->GetParent();
1761 // If isRoot and we have auto height, then we are the initial
1762 // containing block and the containing block height is the
1763 // viewport height, which can't change during incremental
1764 // reflow.
1765 bool cbHeightChanged =
1766 !(isRoot && NS_UNCONSTRAINEDSIZE == aReflowInput.ComputedHeight()) &&
1767 aMetrics.Height() != oldSize.height;
1769 nsRect containingBlock(nsPoint(0, 0),
1770 containingBlockSize.GetPhysicalSize(parentWM));
1771 AbsPosReflowFlags flags = AbsPosReflowFlags::ConstrainHeight;
1772 if (cbWidthChanged) {
1773 flags |= AbsPosReflowFlags::CBWidthChanged;
1775 if (cbHeightChanged) {
1776 flags |= AbsPosReflowFlags::CBHeightChanged;
1778 // Setup the line cursor here to optimize line searching for
1779 // calculating hypothetical position of absolutely-positioned
1780 // frames.
1781 SetupLineCursorForQuery();
1782 absoluteContainer->Reflow(this, aPresContext, aReflowInput, reflowStatus,
1783 containingBlock, flags,
1784 &aMetrics.mOverflowAreas);
1788 FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
1790 aStatus = reflowStatus;
1792 #ifdef DEBUG
1793 // Between when we drain pushed floats and when we complete reflow,
1794 // we're allowed to have multiple continuations of the same float on
1795 // our floats list, since a first-in-flow might get pushed to a later
1796 // continuation of its containing block. But it's not permitted
1797 // outside that time.
1798 nsLayoutUtils::AssertNoDuplicateContinuations(this, mFloats);
1800 if (gNoisyReflow) {
1801 IndentBy(stdout, gNoiseIndent);
1802 ListTag(stdout);
1803 printf(": status=%s metrics=%d,%d carriedMargin=%d",
1804 ToString(aStatus).c_str(), aMetrics.ISize(parentWM),
1805 aMetrics.BSize(parentWM), aMetrics.mCarriedOutBEndMargin.get());
1806 if (HasOverflowAreas()) {
1807 printf(" overflow-vis={%d,%d,%d,%d}", aMetrics.InkOverflow().x,
1808 aMetrics.InkOverflow().y, aMetrics.InkOverflow().width,
1809 aMetrics.InkOverflow().height);
1810 printf(" overflow-scr={%d,%d,%d,%d}", aMetrics.ScrollableOverflow().x,
1811 aMetrics.ScrollableOverflow().y,
1812 aMetrics.ScrollableOverflow().width,
1813 aMetrics.ScrollableOverflow().height);
1815 printf("\n");
1818 if (gLameReflowMetrics) {
1819 PRTime end = PR_Now();
1821 int32_t ectc = nsLineBox::GetCtorCount();
1822 int32_t numLines = mLines.size();
1823 if (!numLines) {
1824 numLines = 1;
1826 PRTime delta, perLineDelta, lines;
1827 lines = int64_t(numLines);
1828 delta = end - start;
1829 perLineDelta = delta / lines;
1831 ListTag(stdout);
1832 char buf[400];
1833 SprintfLiteral(buf,
1834 ": %" PRId64 " elapsed (%" PRId64
1835 " per line) (%d lines; %d new lines)",
1836 delta, perLineDelta, numLines, ectc - ctc);
1837 printf("%s\n", buf);
1839 #endif
1842 nsReflowStatus nsBlockFrame::TrialReflow(nsPresContext* aPresContext,
1843 ReflowOutput& aMetrics,
1844 const ReflowInput& aReflowInput,
1845 TrialReflowState& aTrialState) {
1846 #ifdef DEBUG
1847 // Between when we drain pushed floats and when we complete reflow,
1848 // we're allowed to have multiple continuations of the same float on
1849 // our floats list, since a first-in-flow might get pushed to a later
1850 // continuation of its containing block. But it's not permitted
1851 // outside that time.
1852 nsLayoutUtils::AssertNoDuplicateContinuations(this, mFloats);
1853 #endif
1855 // ALWAYS drain overflow. We never want to leave the previnflow's
1856 // overflow lines hanging around; block reflow depends on the
1857 // overflow line lists being cleared out between reflow passes.
1858 DrainOverflowLines();
1860 bool blockStartMarginRoot, blockEndMarginRoot;
1861 IsMarginRoot(&blockStartMarginRoot, &blockEndMarginRoot);
1863 BlockReflowState state(aReflowInput, aPresContext, this, blockStartMarginRoot,
1864 blockEndMarginRoot, aTrialState.mNeedFloatManager,
1865 aTrialState.mConsumedBSize,
1866 aTrialState.mEffectiveContentBoxBSize,
1867 aTrialState.mInset);
1869 // Handle paginated overflow (see nsContainerFrame.h)
1870 nsReflowStatus ocStatus;
1871 if (GetPrevInFlow()) {
1872 ReflowOverflowContainerChildren(
1873 aPresContext, aReflowInput, aTrialState.mOcBounds,
1874 ReflowChildFlags::Default, ocStatus, DefaultChildFrameMerge,
1875 Some(state.ContainerSize()));
1878 // Now that we're done cleaning up our overflow container lists, we can
1879 // give |state| its nsOverflowContinuationTracker.
1880 nsOverflowContinuationTracker tracker(this, false);
1881 state.mOverflowTracker = &tracker;
1883 // Drain & handle pushed floats
1884 DrainPushedFloats();
1885 ReflowPushedFloats(state, aTrialState.mFcBounds);
1887 // If we're not dirty (which means we'll mark everything dirty later)
1888 // and our inline-size has changed, mark the lines dirty that we need to
1889 // mark dirty for a resize reflow.
1890 if (!HasAnyStateBits(NS_FRAME_IS_DIRTY) && aReflowInput.IsIResize()) {
1891 PrepareResizeReflow(state);
1894 // The same for percentage text-indent, except conditioned on the
1895 // parent resizing.
1896 if (!HasAnyStateBits(NS_FRAME_IS_DIRTY) && aReflowInput.mCBReflowInput &&
1897 aReflowInput.mCBReflowInput->IsIResize() &&
1898 StyleText()->mTextIndent.length.HasPercent() && !mLines.empty()) {
1899 mLines.front()->MarkDirty();
1902 // For text-wrap:balance trials, we need to reflow all the lines even if
1903 // they're not all "dirty".
1904 if (aTrialState.mBalancing) {
1905 MarkAllDescendantLinesDirty(this);
1906 } else {
1907 LazyMarkLinesDirty();
1910 // Now reflow...
1911 aTrialState.mUsedOverflowWrap = ReflowDirtyLines(state);
1913 // If we have a next-in-flow, and that next-in-flow has pushed floats from
1914 // this frame from a previous iteration of reflow, then we should not return
1915 // a status with IsFullyComplete() equals to true, since we actually have
1916 // overflow, it's just already been handled.
1918 // NOTE: This really shouldn't happen, since we _should_ pull back our floats
1919 // and reflow them, but just in case it does, this is a safety precaution so
1920 // we don't end up with a placeholder pointing to frames that have already
1921 // been deleted as part of removing our next-in-flow.
1922 if (state.mReflowStatus.IsFullyComplete()) {
1923 nsBlockFrame* nif = static_cast<nsBlockFrame*>(GetNextInFlow());
1924 while (nif) {
1925 if (nif->HasPushedFloatsFromPrevContinuation()) {
1926 if (nif->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
1927 state.mReflowStatus.SetOverflowIncomplete();
1928 } else {
1929 state.mReflowStatus.SetIncomplete();
1931 break;
1934 nif = static_cast<nsBlockFrame*>(nif->GetNextInFlow());
1938 state.mReflowStatus.MergeCompletionStatusFrom(ocStatus);
1940 // If we end in a BR with clear and affected floats continue,
1941 // we need to continue, too.
1942 if (NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize() &&
1943 state.mReflowStatus.IsComplete() &&
1944 state.FloatManager()->ClearContinues(FindTrailingClear())) {
1945 state.mReflowStatus.SetIncomplete();
1948 if (!state.mReflowStatus.IsFullyComplete()) {
1949 if (HasOverflowLines() || HasPushedFloats()) {
1950 state.mReflowStatus.SetNextInFlowNeedsReflow();
1953 #ifdef DEBUG_kipp
1954 ListTag(stdout);
1955 printf(": block is not fully complete\n");
1956 #endif
1959 // Place the ::marker's frame if it is placed next to a block child.
1961 // According to the CSS2 spec, section 12.6.1, the ::marker's box
1962 // participates in the height calculation of the list-item box's
1963 // first line box.
1965 // There are exactly two places a ::marker can be placed: near the
1966 // first or second line. It's only placed on the second line in a
1967 // rare case: an empty first line followed by a second line that
1968 // contains a block (example: <LI>\n<P>... ). This is where
1969 // the second case can happen.
1970 if (HasOutsideMarker() && !mLines.empty() &&
1971 (mLines.front()->IsBlock() ||
1972 (0 == mLines.front()->BSize() && mLines.front() != mLines.back() &&
1973 mLines.begin().next()->IsBlock()))) {
1974 // Reflow the ::marker's frame.
1975 ReflowOutput reflowOutput(aReflowInput);
1976 // XXX Use the entire line when we fix bug 25888.
1977 nsLayoutUtils::LinePosition position;
1978 WritingMode wm = aReflowInput.GetWritingMode();
1979 bool havePosition =
1980 nsLayoutUtils::GetFirstLinePosition(wm, this, &position);
1981 nscoord lineBStart =
1982 havePosition ? position.mBStart
1983 : aReflowInput.ComputedLogicalBorderPadding(wm).BStart(wm);
1984 nsIFrame* marker = GetOutsideMarker();
1985 ReflowOutsideMarker(marker, state, reflowOutput, lineBStart);
1986 NS_ASSERTION(!MarkerIsEmpty() || reflowOutput.BSize(wm) == 0,
1987 "empty ::marker frame took up space");
1989 if (havePosition && !MarkerIsEmpty()) {
1990 // We have some lines to align the ::marker with.
1992 // Doing the alignment using the baseline will also cater for
1993 // ::markers that are placed next to a child block (bug 92896)
1995 // Tall ::markers won't look particularly nice here...
1996 LogicalRect bbox =
1997 marker->GetLogicalRect(wm, reflowOutput.PhysicalSize());
1998 const auto baselineGroup = BaselineSharingGroup::First;
1999 Maybe<nscoord> result;
2000 if (MOZ_LIKELY(!wm.IsOrthogonalTo(marker->GetWritingMode()))) {
2001 result = marker->GetNaturalBaselineBOffset(
2002 wm, baselineGroup, BaselineExportContext::LineLayout);
2004 const auto markerBaseline = result.valueOrFrom([bbox, wm, marker]() {
2005 return bbox.BSize(wm) + marker->GetLogicalUsedMargin(wm).BEnd(wm);
2007 bbox.BStart(wm) = position.mBaseline - markerBaseline;
2008 marker->SetRect(wm, bbox, reflowOutput.PhysicalSize());
2010 // Otherwise just leave the ::marker where it is, up against our
2011 // block-start padding.
2014 // Clear any existing -webkit-line-clamp ellipsis.
2015 if (aReflowInput.mStyleDisplay->mWebkitLineClamp) {
2016 ClearLineClampEllipsis();
2019 CheckFloats(state);
2021 // Compute our final size (for this trial layout)
2022 aTrialState.mBlockEndEdgeOfChildren =
2023 ComputeFinalSize(aReflowInput, state, aMetrics);
2024 aTrialState.mContainerWidth = state.ContainerSize().width;
2026 return state.mReflowStatus;
2029 bool nsBlockFrame::CheckForCollapsedBEndMarginFromClearanceLine() {
2030 for (auto& line : Reversed(Lines())) {
2031 if (0 != line.BSize() || !line.CachedIsEmpty()) {
2032 return false;
2034 if (line.HasClearance()) {
2035 return true;
2038 return false;
2041 static nsLineBox* FindLineClampTarget(nsBlockFrame*& aFrame,
2042 StyleLineClamp aLineNumber) {
2043 MOZ_ASSERT(aLineNumber > 0);
2044 MOZ_ASSERT(!aFrame->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS),
2045 "Should have been removed earlier in nsBlockReflow::Reflow");
2047 nsLineBox* target = nullptr;
2048 nsBlockFrame* targetFrame = nullptr;
2049 bool foundFollowingLine = false;
2051 LineClampLineIterator iter(aFrame);
2053 while (nsLineBox* line = iter.GetCurrentLine()) {
2054 MOZ_ASSERT(!line->HasLineClampEllipsis(),
2055 "Should have been removed earlier in nsBlockFrame::Reflow");
2056 MOZ_ASSERT(!iter.GetCurrentFrame()->HasAnyStateBits(
2057 NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS),
2058 "Should have been removed earlier in nsBlockReflow::Reflow");
2060 // Don't count a line that only has collapsible white space (as might exist
2061 // after calling e.g. getBoxQuads).
2062 if (line->IsEmpty()) {
2063 iter.Next();
2064 continue;
2067 if (aLineNumber == 0) {
2068 // We already previously found our target line, and now we have
2069 // confirmed that there is another line after it.
2070 foundFollowingLine = true;
2071 break;
2074 if (--aLineNumber == 0) {
2075 // This is our target line. Continue looping to confirm that we
2076 // have another line after us.
2077 target = line;
2078 targetFrame = iter.GetCurrentFrame();
2081 iter.Next();
2084 if (!foundFollowingLine) {
2085 aFrame = nullptr;
2086 return nullptr;
2089 MOZ_ASSERT(target);
2090 MOZ_ASSERT(targetFrame);
2092 aFrame = targetFrame;
2093 return target;
2096 static nscoord ApplyLineClamp(const ReflowInput& aReflowInput,
2097 nsBlockFrame* aFrame,
2098 nscoord aContentBlockEndEdge) {
2099 if (!IsLineClampRoot(aFrame)) {
2100 return aContentBlockEndEdge;
2102 auto lineClamp = aReflowInput.mStyleDisplay->mWebkitLineClamp;
2103 nsBlockFrame* frame = aFrame;
2104 nsLineBox* line = FindLineClampTarget(frame, lineClamp);
2105 if (!line) {
2106 // The number of lines did not exceed the -webkit-line-clamp value.
2107 return aContentBlockEndEdge;
2110 // Mark the line as having an ellipsis so that TextOverflow will render it.
2111 line->SetHasLineClampEllipsis();
2112 frame->AddStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS);
2114 // Translate the b-end edge of the line up to aFrame's space.
2115 nscoord edge = line->BEnd();
2116 for (nsIFrame* f = frame; f != aFrame; f = f->GetParent()) {
2117 edge +=
2118 f->GetLogicalPosition(f->GetParent()->GetSize()).B(f->GetWritingMode());
2121 return edge;
2124 nscoord nsBlockFrame::ComputeFinalSize(const ReflowInput& aReflowInput,
2125 BlockReflowState& aState,
2126 ReflowOutput& aMetrics) {
2127 WritingMode wm = aState.mReflowInput.GetWritingMode();
2128 const LogicalMargin& borderPadding = aState.BorderPadding();
2129 #ifdef NOISY_FINAL_SIZE
2130 ListTag(stdout);
2131 printf(": mBCoord=%d mIsBEndMarginRoot=%s mPrevBEndMargin=%d bp=%d,%d\n",
2132 aState.mBCoord, aState.mFlags.mIsBEndMarginRoot ? "yes" : "no",
2133 aState.mPrevBEndMargin.get(), borderPadding.BStart(wm),
2134 borderPadding.BEnd(wm));
2135 #endif
2137 // Compute final inline size
2138 LogicalSize finalSize(wm);
2139 finalSize.ISize(wm) =
2140 NSCoordSaturatingAdd(NSCoordSaturatingAdd(borderPadding.IStart(wm),
2141 aReflowInput.ComputedISize()),
2142 borderPadding.IEnd(wm));
2144 // Return block-end margin information
2145 // rbs says he hit this assertion occasionally (see bug 86947), so
2146 // just set the margin to zero and we'll figure out why later
2147 // NS_ASSERTION(aMetrics.mCarriedOutBEndMargin.IsZero(),
2148 // "someone else set the margin");
2149 nscoord nonCarriedOutBDirMargin = 0;
2150 if (!aState.mFlags.mIsBEndMarginRoot) {
2151 // Apply rule from CSS 2.1 section 8.3.1. If we have some empty
2152 // line with clearance and a non-zero block-start margin and all
2153 // subsequent lines are empty, then we do not allow our children's
2154 // carried out block-end margin to be carried out of us and collapse
2155 // with our own block-end margin.
2156 if (CheckForCollapsedBEndMarginFromClearanceLine()) {
2157 // Convert the children's carried out margin to something that
2158 // we will include in our height
2159 nonCarriedOutBDirMargin = aState.mPrevBEndMargin.get();
2160 aState.mPrevBEndMargin.Zero();
2162 aMetrics.mCarriedOutBEndMargin = aState.mPrevBEndMargin;
2163 } else {
2164 aMetrics.mCarriedOutBEndMargin.Zero();
2167 nscoord blockEndEdgeOfChildren = aState.mBCoord + nonCarriedOutBDirMargin;
2168 // Shrink wrap our height around our contents.
2169 if (aState.mFlags.mIsBEndMarginRoot ||
2170 NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) {
2171 // When we are a block-end-margin root make sure that our last
2172 // child's block-end margin is fully applied. We also do this when
2173 // we have a computed height, since in that case the carried out
2174 // margin is not going to be applied anywhere, so we should note it
2175 // here to be included in the overflow area.
2176 // Apply the margin only if there's space for it.
2177 if (blockEndEdgeOfChildren < aState.mReflowInput.AvailableBSize()) {
2178 // Truncate block-end margin if it doesn't fit to our available BSize.
2179 blockEndEdgeOfChildren =
2180 std::min(blockEndEdgeOfChildren + aState.mPrevBEndMargin.get(),
2181 aState.mReflowInput.AvailableBSize());
2184 if (aState.mFlags.mBlockNeedsFloatManager) {
2185 // Include the float manager's state to properly account for the
2186 // block-end margin of any floated elements; e.g., inside a table cell.
2188 // Note: The block coordinate returned by ClearFloats is always greater than
2189 // or equal to blockEndEdgeOfChildren.
2190 std::tie(blockEndEdgeOfChildren, std::ignore) =
2191 aState.ClearFloats(blockEndEdgeOfChildren, StyleClear::Both);
2194 if (NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) {
2195 // Note: We don't use blockEndEdgeOfChildren because it includes the
2196 // previous margin.
2197 const nscoord contentBSizeWithBStartBP =
2198 aState.mBCoord + nonCarriedOutBDirMargin;
2200 // We don't care about ApplyLineClamp's return value (the line-clamped
2201 // content BSize) in this explicit-BSize codepath, but we do still need to
2202 // call ApplyLineClamp for ellipsis markers to be placed as-needed.
2203 ApplyLineClamp(aState.mReflowInput, this, contentBSizeWithBStartBP);
2205 finalSize.BSize(wm) = ComputeFinalBSize(aState, contentBSizeWithBStartBP);
2207 // If the content block-size is larger than the effective computed
2208 // block-size, we extend the block-size to contain all the content.
2209 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
2210 if (aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis()) {
2211 // Note: finalSize.BSize(wm) is the border-box size, so we compare it with
2212 // the content's block-size plus our border and padding..
2213 finalSize.BSize(wm) =
2214 std::max(finalSize.BSize(wm),
2215 contentBSizeWithBStartBP + borderPadding.BEnd(wm));
2218 // Don't carry out a block-end margin when our BSize is fixed.
2220 // Note: this also includes the case that aReflowInput.ComputedBSize() is
2221 // calculated from aspect-ratio. i.e. Don't carry out block margin-end if it
2222 // is replaced by the block size from aspect-ratio and inline size.
2223 aMetrics.mCarriedOutBEndMargin.Zero();
2224 } else {
2225 Maybe<nscoord> containBSize = ContainIntrinsicBSize(
2226 IsComboboxControlFrame() ? NS_UNCONSTRAINEDSIZE : 0);
2227 if (containBSize && *containBSize != NS_UNCONSTRAINEDSIZE) {
2228 // If we're size-containing in block axis and we don't have a specified
2229 // block size, then our final size should actually be computed from only
2230 // our border, padding and contain-intrinsic-block-size, ignoring the
2231 // actual contents. Hence this case is a simplified version of the case
2232 // below.
2234 // NOTE: We exempt the nsComboboxControlFrame subclass from taking this
2235 // special case when it has 'contain-intrinsic-block-size: none', because
2236 // comboboxes implicitly honors the size-containment behavior on its
2237 // nsComboboxDisplayFrame child (which it shrinkwraps) rather than on the
2238 // nsComboboxControlFrame. (Moreover, the DisplayFrame child doesn't even
2239 // need any special content-size-ignoring behavior in its reflow method,
2240 // because that method just resolves "auto" BSize values to one
2241 // line-height rather than by measuring its contents' BSize.)
2242 nscoord contentBSize = *containBSize;
2243 nscoord autoBSize =
2244 aReflowInput.ApplyMinMaxBSize(contentBSize, aState.mConsumedBSize);
2245 aMetrics.mCarriedOutBEndMargin.Zero();
2246 autoBSize += borderPadding.BStartEnd(wm);
2247 finalSize.BSize(wm) = autoBSize;
2248 } else if (aState.mReflowStatus.IsInlineBreakBefore()) {
2249 // Our parent is expected to push this frame to the next page/column so
2250 // what size we set here doesn't really matter.
2251 finalSize.BSize(wm) = aReflowInput.AvailableBSize();
2252 } else if (aState.mReflowStatus.IsComplete()) {
2253 const nscoord lineClampedContentBlockEndEdge =
2254 ApplyLineClamp(aReflowInput, this, blockEndEdgeOfChildren);
2256 const nscoord bpBStart = borderPadding.BStart(wm);
2257 const nscoord contentBSize = blockEndEdgeOfChildren - bpBStart;
2258 const nscoord lineClampedContentBSize =
2259 lineClampedContentBlockEndEdge - bpBStart;
2261 const nscoord autoBSize = aReflowInput.ApplyMinMaxBSize(
2262 lineClampedContentBSize, aState.mConsumedBSize);
2263 if (autoBSize != contentBSize) {
2264 // Our min-block-size, max-block-size, or -webkit-line-clamp value made
2265 // our bsize change. Don't carry out our kids' block-end margins.
2266 aMetrics.mCarriedOutBEndMargin.Zero();
2268 nscoord bSize = autoBSize + borderPadding.BStartEnd(wm);
2269 if (MOZ_UNLIKELY(autoBSize > contentBSize &&
2270 bSize > aReflowInput.AvailableBSize() &&
2271 aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE)) {
2272 // Applying `min-size` made us overflow our available size.
2273 // Clamp it and report that we're Incomplete, or BreakBefore if we have
2274 // 'break-inside: avoid' that is applicable.
2275 bSize = aReflowInput.AvailableBSize();
2276 if (ShouldAvoidBreakInside(aReflowInput)) {
2277 aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
2278 } else {
2279 aState.mReflowStatus.SetIncomplete();
2282 finalSize.BSize(wm) = bSize;
2283 } else {
2284 NS_ASSERTION(
2285 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;
2317 if (IsTrueOverflowContainer()) {
2318 if (aState.mReflowStatus.IsIncomplete()) {
2319 // Overflow containers can only be overflow complete.
2320 // Note that auto height overflow containers have no normal children
2321 NS_ASSERTION(finalSize.BSize(wm) == 0,
2322 "overflow containers must be zero-block-size");
2323 aState.mReflowStatus.SetOverflowIncomplete();
2325 } else if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
2326 !aState.mReflowStatus.IsInlineBreakBefore() &&
2327 aState.mReflowStatus.IsComplete()) {
2328 // Currently only used for grid items, but could be used in other contexts.
2329 // The FragStretchBSizeProperty is our expected non-fragmented block-size
2330 // we should stretch to (for align-self:stretch etc). In some fragmentation
2331 // cases though, the last fragment (this frame since we're complete), needs
2332 // to have extra size applied because earlier fragments consumed too much of
2333 // our computed size due to overflowing their containing block. (E.g. this
2334 // ensures we fill the last row when a multi-row grid item is fragmented).
2335 bool found;
2336 nscoord bSize = GetProperty(FragStretchBSizeProperty(), &found);
2337 if (found) {
2338 finalSize.BSize(wm) = std::max(bSize, finalSize.BSize(wm));
2342 // Clamp the content size to fit within the margin-box clamp size, if any.
2343 if (MOZ_UNLIKELY(aReflowInput.mComputeSizeFlags.contains(
2344 ComputeSizeFlag::BClampMarginBoxMinSize)) &&
2345 aState.mReflowStatus.IsComplete()) {
2346 bool found;
2347 nscoord cbSize = GetProperty(BClampMarginBoxMinSizeProperty(), &found);
2348 if (found) {
2349 auto marginBoxBSize =
2350 finalSize.BSize(wm) +
2351 aReflowInput.ComputedLogicalMargin(wm).BStartEnd(wm);
2352 auto overflow = marginBoxBSize - cbSize;
2353 if (overflow > 0) {
2354 auto contentBSize = finalSize.BSize(wm) - borderPadding.BStartEnd(wm);
2355 auto newContentBSize = std::max(nscoord(0), contentBSize - overflow);
2356 // XXXmats deal with percentages better somehow?
2357 finalSize.BSize(wm) -= contentBSize - newContentBSize;
2362 // Screen out negative block sizes --- can happen due to integer overflows :-(
2363 finalSize.BSize(wm) = std::max(0, finalSize.BSize(wm));
2365 if (blockEndEdgeOfChildren != finalSize.BSize(wm) - borderPadding.BEnd(wm)) {
2366 SetProperty(BlockEndEdgeOfChildrenProperty(), blockEndEdgeOfChildren);
2367 } else {
2368 RemoveProperty(BlockEndEdgeOfChildrenProperty());
2371 aMetrics.SetSize(wm, finalSize);
2373 #ifdef DEBUG_blocks
2374 if ((ABSURD_SIZE(aMetrics.Width()) || ABSURD_SIZE(aMetrics.Height())) &&
2375 !GetParent()->IsAbsurdSizeAssertSuppressed()) {
2376 ListTag(stdout);
2377 printf(": WARNING: desired:%d,%d\n", aMetrics.Width(), aMetrics.Height());
2379 #endif
2381 return blockEndEdgeOfChildren;
2384 void nsBlockFrame::ConsiderBlockEndEdgeOfChildren(
2385 OverflowAreas& aOverflowAreas, nscoord aBEndEdgeOfChildren,
2386 const nsStyleDisplay* aDisplay) const {
2387 const auto wm = GetWritingMode();
2389 // Factor in the block-end edge of the children. Child frames will be added
2390 // to the overflow area as we iterate through the lines, but their margins
2391 // won't, so we need to account for block-end margins here.
2392 // REVIEW: For now, we do this for both visual and scrollable area,
2393 // although when we make scrollable overflow area not be a subset of
2394 // visual, we can change this.
2396 if (Style()->GetPseudoType() == PseudoStyleType::scrolledContent) {
2397 // If we are a scrolled inner frame, add our block-end padding to our
2398 // children's block-end edge.
2400 // Note: aBEndEdgeOfChildren already includes our own block-start padding
2401 // because it is relative to our block-start edge of our border-box, which
2402 // is the same as our padding-box here.
2403 MOZ_ASSERT(GetLogicalUsedBorderAndPadding(wm) == GetLogicalUsedPadding(wm),
2404 "A scrolled inner frame shouldn't have any border!");
2405 aBEndEdgeOfChildren += GetLogicalUsedPadding(wm).BEnd(wm);
2408 // XXX Currently, overflow areas are stored as physical rects, so we have
2409 // to handle writing modes explicitly here. If we change overflow rects
2410 // to be stored logically, this can be simplified again.
2411 if (wm.IsVertical()) {
2412 if (wm.IsVerticalLR()) {
2413 for (const auto otype : AllOverflowTypes()) {
2414 if (!(aDisplay->IsContainLayout() &&
2415 otype == OverflowType::Scrollable)) {
2416 // Layout containment should force all overflow to be ink (visual)
2417 // overflow, so if we're layout-contained, we only add our children's
2418 // block-end edge to the ink (visual) overflow -- not to the
2419 // scrollable overflow.
2420 nsRect& o = aOverflowAreas.Overflow(otype);
2421 o.width = std::max(o.XMost(), aBEndEdgeOfChildren) - o.x;
2424 } else {
2425 for (const auto otype : AllOverflowTypes()) {
2426 if (!(aDisplay->IsContainLayout() &&
2427 otype == OverflowType::Scrollable)) {
2428 nsRect& o = aOverflowAreas.Overflow(otype);
2429 nscoord xmost = o.XMost();
2430 o.x = std::min(o.x, xmost - aBEndEdgeOfChildren);
2431 o.width = xmost - o.x;
2435 } else {
2436 for (const auto otype : AllOverflowTypes()) {
2437 if (!(aDisplay->IsContainLayout() && otype == OverflowType::Scrollable)) {
2438 nsRect& o = aOverflowAreas.Overflow(otype);
2439 o.height = std::max(o.YMost(), aBEndEdgeOfChildren) - o.y;
2445 void nsBlockFrame::ComputeOverflowAreas(OverflowAreas& aOverflowAreas,
2446 nscoord aBEndEdgeOfChildren,
2447 const nsStyleDisplay* aDisplay) const {
2448 // XXX_perf: This can be done incrementally. It is currently one of
2449 // the things that makes incremental reflow O(N^2).
2450 auto overflowClipAxes = ShouldApplyOverflowClipping(aDisplay);
2451 auto overflowClipMargin = OverflowClipMargin(overflowClipAxes);
2452 if (overflowClipAxes == PhysicalAxes::Both &&
2453 overflowClipMargin == nsSize()) {
2454 return;
2457 // We rely here on our caller having called SetOverflowAreasToDesiredBounds().
2458 nsRect frameBounds = aOverflowAreas.ScrollableOverflow();
2460 for (const auto& line : Lines()) {
2461 if (aDisplay->IsContainLayout()) {
2462 // If we have layout containment, we should only consider our child's
2463 // ink overflow, leaving the scrollable regions of the parent
2464 // unaffected.
2465 // Note: scrollable overflow is a subset of ink overflow,
2466 // so this has the same affect as unioning the child's visual and
2467 // scrollable overflow with its parent's ink overflow.
2468 nsRect childVisualRect = line.InkOverflowRect();
2469 OverflowAreas childVisualArea = OverflowAreas(childVisualRect, nsRect());
2470 aOverflowAreas.UnionWith(childVisualArea);
2471 } else {
2472 aOverflowAreas.UnionWith(line.GetOverflowAreas());
2476 // Factor an outside ::marker in; normally the ::marker will be factored
2477 // into the line-box's overflow areas. However, if the line is a block
2478 // line then it won't; if there are no lines, it won't. So just
2479 // factor it in anyway (it can't hurt if it was already done).
2480 // XXXldb Can we just fix GetOverflowArea instead?
2481 if (nsIFrame* outsideMarker = GetOutsideMarker()) {
2482 aOverflowAreas.UnionAllWith(outsideMarker->GetRect());
2485 ConsiderBlockEndEdgeOfChildren(aOverflowAreas, aBEndEdgeOfChildren, aDisplay);
2487 if (overflowClipAxes != PhysicalAxes::None) {
2488 aOverflowAreas.ApplyClipping(frameBounds, overflowClipAxes,
2489 overflowClipMargin);
2492 #ifdef NOISY_OVERFLOW_AREAS
2493 printf("%s: InkOverflowArea=%s, ScrollableOverflowArea=%s\n", ListTag().get(),
2494 ToString(aOverflowAreas.InkOverflow()).c_str(),
2495 ToString(aOverflowAreas.ScrollableOverflow()).c_str());
2496 #endif
2499 void nsBlockFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas) {
2500 // We need to update the overflow areas of lines manually, as they
2501 // get cached and re-used otherwise. Lines aren't exposed as normal
2502 // frame children, so calling UnionChildOverflow alone will end up
2503 // using the old cached values.
2504 for (auto& line : Lines()) {
2505 nsRect bounds = line.GetPhysicalBounds();
2506 OverflowAreas lineAreas(bounds, bounds);
2508 int32_t n = line.GetChildCount();
2509 for (nsIFrame* lineFrame = line.mFirstChild; n > 0;
2510 lineFrame = lineFrame->GetNextSibling(), --n) {
2511 ConsiderChildOverflow(lineAreas, lineFrame);
2514 // Consider the overflow areas of the floats attached to the line as well
2515 if (line.HasFloats()) {
2516 for (nsIFrame* f : line.Floats()) {
2517 ConsiderChildOverflow(lineAreas, f);
2521 line.SetOverflowAreas(lineAreas);
2522 aOverflowAreas.UnionWith(lineAreas);
2525 // Union with child frames, skipping the principal and float lists
2526 // since we already handled those using the line boxes.
2527 nsLayoutUtils::UnionChildOverflow(
2528 this, aOverflowAreas,
2529 {FrameChildListID::Principal, FrameChildListID::Float});
2532 bool nsBlockFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) {
2533 bool found;
2534 nscoord blockEndEdgeOfChildren =
2535 GetProperty(BlockEndEdgeOfChildrenProperty(), &found);
2536 if (found) {
2537 ConsiderBlockEndEdgeOfChildren(aOverflowAreas, blockEndEdgeOfChildren,
2538 StyleDisplay());
2541 // Line cursor invariants depend on the overflow areas of the lines, so
2542 // we must clear the line cursor since those areas may have changed.
2543 ClearLineCursors();
2544 return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
2547 void nsBlockFrame::LazyMarkLinesDirty() {
2548 if (HasAnyStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES)) {
2549 for (LineIterator line = LinesBegin(), line_end = LinesEnd();
2550 line != line_end; ++line) {
2551 int32_t n = line->GetChildCount();
2552 for (nsIFrame* lineFrame = line->mFirstChild; n > 0;
2553 lineFrame = lineFrame->GetNextSibling(), --n) {
2554 if (lineFrame->IsSubtreeDirty()) {
2555 // NOTE: MarkLineDirty does more than just marking the line dirty.
2556 MarkLineDirty(line, &mLines);
2557 break;
2561 RemoveStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
2565 void nsBlockFrame::MarkLineDirty(LineIterator aLine,
2566 const nsLineList* aLineList) {
2567 // Mark aLine dirty
2568 aLine->MarkDirty();
2569 aLine->SetInvalidateTextRuns(true);
2570 #ifdef DEBUG
2571 if (gNoisyReflow) {
2572 IndentBy(stdout, gNoiseIndent);
2573 ListTag(stdout);
2574 printf(": mark line %p dirty\n", static_cast<void*>(aLine.get()));
2576 #endif
2578 // Mark previous line dirty if it's an inline line so that it can
2579 // maybe pullup something from the line just affected.
2580 // XXX We don't need to do this if aPrevLine ends in a break-after...
2581 if (aLine != aLineList->front() && aLine->IsInline() &&
2582 aLine.prev()->IsInline()) {
2583 aLine.prev()->MarkDirty();
2584 aLine.prev()->SetInvalidateTextRuns(true);
2585 #ifdef DEBUG
2586 if (gNoisyReflow) {
2587 IndentBy(stdout, gNoiseIndent);
2588 ListTag(stdout);
2589 printf(": mark prev-line %p dirty\n",
2590 static_cast<void*>(aLine.prev().get()));
2592 #endif
2597 * Test whether lines are certain to be aligned left so that we can make
2598 * resizing optimizations
2600 static inline bool IsAlignedLeft(StyleTextAlign aAlignment,
2601 StyleDirection aDirection,
2602 StyleUnicodeBidi aUnicodeBidi,
2603 nsIFrame* aFrame) {
2604 return aFrame->IsInSVGTextSubtree() || StyleTextAlign::Left == aAlignment ||
2605 (((StyleTextAlign::Start == aAlignment &&
2606 StyleDirection::Ltr == aDirection) ||
2607 (StyleTextAlign::End == aAlignment &&
2608 StyleDirection::Rtl == aDirection)) &&
2609 aUnicodeBidi != StyleUnicodeBidi::Plaintext);
2612 void nsBlockFrame::PrepareResizeReflow(BlockReflowState& aState) {
2613 // See if we can try and avoid marking all the lines as dirty
2614 // FIXME(emilio): This should be writing-mode aware, I guess.
2615 bool tryAndSkipLines =
2616 // The left content-edge must be a constant distance from the left
2617 // border-edge.
2618 !StylePadding()->mPadding.Get(eSideLeft).HasPercent();
2620 #ifdef DEBUG
2621 if (gDisableResizeOpt) {
2622 tryAndSkipLines = false;
2624 if (gNoisyReflow) {
2625 if (!tryAndSkipLines) {
2626 IndentBy(stdout, gNoiseIndent);
2627 ListTag(stdout);
2628 printf(": marking all lines dirty: availISize=%d\n",
2629 aState.mReflowInput.AvailableISize());
2632 #endif
2634 if (tryAndSkipLines) {
2635 WritingMode wm = aState.mReflowInput.GetWritingMode();
2636 nscoord newAvailISize =
2637 aState.mReflowInput.ComputedLogicalBorderPadding(wm).IStart(wm) +
2638 aState.mReflowInput.ComputedISize();
2640 #ifdef DEBUG
2641 if (gNoisyReflow) {
2642 IndentBy(stdout, gNoiseIndent);
2643 ListTag(stdout);
2644 printf(": trying to avoid marking all lines dirty\n");
2646 #endif
2648 for (LineIterator line = LinesBegin(), line_end = LinesEnd();
2649 line != line_end; ++line) {
2650 // We let child blocks make their own decisions the same
2651 // way we are here.
2652 bool isLastLine = line == mLines.back() && !GetNextInFlow();
2653 if (line->IsBlock() || line->HasFloats() ||
2654 (!isLastLine && !line->HasForcedLineBreakAfter()) ||
2655 ((isLastLine || !line->IsLineWrapped())) ||
2656 line->ResizeReflowOptimizationDisabled() ||
2657 line->IsImpactedByFloat() || (line->IEnd() > newAvailISize)) {
2658 line->MarkDirty();
2661 #ifdef REALLY_NOISY_REFLOW
2662 if (!line->IsBlock()) {
2663 printf("PrepareResizeReflow thinks line %p is %simpacted by floats\n",
2664 line.get(), line->IsImpactedByFloat() ? "" : "not ");
2666 #endif
2667 #ifdef DEBUG
2668 if (gNoisyReflow && !line->IsDirty()) {
2669 IndentBy(stdout, gNoiseIndent + 1);
2670 printf(
2671 "skipped: line=%p next=%p %s %s%s%s clearTypeBefore/After=%s/%s "
2672 "xmost=%d\n",
2673 static_cast<void*>(line.get()),
2674 static_cast<void*>(
2675 (line.next() != LinesEnd() ? line.next().get() : nullptr)),
2676 line->IsBlock() ? "block" : "inline",
2677 line->HasForcedLineBreakAfter() ? "has-break-after " : "",
2678 line->HasFloats() ? "has-floats " : "",
2679 line->IsImpactedByFloat() ? "impacted " : "",
2680 line->StyleClearToString(line->FloatClearTypeBefore()),
2681 line->StyleClearToString(line->FloatClearTypeAfter()),
2682 line->IEnd());
2684 #endif
2686 } else {
2687 // Mark everything dirty
2688 for (auto& line : Lines()) {
2689 line.MarkDirty();
2694 //----------------------------------------
2697 * Propagate reflow "damage" from from earlier lines to the current
2698 * line. The reflow damage comes from the following sources:
2699 * 1. The regions of float damage remembered during reflow.
2700 * 2. The combination of nonzero |aDeltaBCoord| and any impact by a
2701 * float, either the previous reflow or now.
2703 * When entering this function, |aLine| is still at its old position and
2704 * |aDeltaBCoord| indicates how much it will later be slid (assuming it
2705 * doesn't get marked dirty and reflowed entirely).
2707 void nsBlockFrame::PropagateFloatDamage(BlockReflowState& aState,
2708 nsLineBox* aLine,
2709 nscoord aDeltaBCoord) {
2710 nsFloatManager* floatManager = aState.FloatManager();
2711 NS_ASSERTION(
2712 (aState.mReflowInput.mParentReflowInput &&
2713 aState.mReflowInput.mParentReflowInput->mFloatManager == floatManager) ||
2714 aState.mReflowInput.mBlockDelta == 0,
2715 "Bad block delta passed in");
2717 // Check to see if there are any floats; if there aren't, there can't
2718 // be any float damage
2719 if (!floatManager->HasAnyFloats()) {
2720 return;
2723 // Check the damage region recorded in the float damage.
2724 if (floatManager->HasFloatDamage()) {
2725 // Need to check mBounds *and* mCombinedArea to find intersections
2726 // with aLine's floats
2727 nscoord lineBCoordBefore = aLine->BStart() + aDeltaBCoord;
2728 nscoord lineBCoordAfter = lineBCoordBefore + aLine->BSize();
2729 // Scrollable overflow should be sufficient for things that affect
2730 // layout.
2731 WritingMode wm = aState.mReflowInput.GetWritingMode();
2732 nsSize containerSize = aState.ContainerSize();
2733 LogicalRect overflow =
2734 aLine->GetOverflowArea(OverflowType::Scrollable, wm, containerSize);
2735 nscoord lineBCoordCombinedBefore = overflow.BStart(wm) + aDeltaBCoord;
2736 nscoord lineBCoordCombinedAfter =
2737 lineBCoordCombinedBefore + overflow.BSize(wm);
2739 bool isDirty =
2740 floatManager->IntersectsDamage(lineBCoordBefore, lineBCoordAfter) ||
2741 floatManager->IntersectsDamage(lineBCoordCombinedBefore,
2742 lineBCoordCombinedAfter);
2743 if (isDirty) {
2744 aLine->MarkDirty();
2745 return;
2749 // Check if the line is moving relative to the float manager
2750 if (aDeltaBCoord + aState.mReflowInput.mBlockDelta != 0) {
2751 if (aLine->IsBlock()) {
2752 // Unconditionally reflow sliding blocks; we only really need to reflow
2753 // if there's a float impacting this block, but the current float manager
2754 // makes it difficult to check that. Therefore, we let the child block
2755 // decide what it needs to reflow.
2756 aLine->MarkDirty();
2757 } else {
2758 bool wasImpactedByFloat = aLine->IsImpactedByFloat();
2759 nsFlowAreaRect floatAvailableSpace =
2760 aState.GetFloatAvailableSpaceForBSize(aLine->BStart() + aDeltaBCoord,
2761 aLine->BSize(), nullptr);
2763 #ifdef REALLY_NOISY_REFLOW
2764 printf("nsBlockFrame::PropagateFloatDamage %p was = %d, is=%d\n", this,
2765 wasImpactedByFloat, floatAvailableSpace.HasFloats());
2766 #endif
2768 // Mark the line dirty if it was or is affected by a float
2769 // We actually only really need to reflow if the amount of impact
2770 // changes, but that's not straightforward to check
2771 if (wasImpactedByFloat || floatAvailableSpace.HasFloats()) {
2772 aLine->MarkDirty();
2778 static bool LineHasClear(nsLineBox* aLine) {
2779 return aLine->IsBlock()
2780 ? (aLine->HasForcedLineBreakBefore() ||
2781 aLine->mFirstChild->HasAnyStateBits(
2782 NS_BLOCK_HAS_CLEAR_CHILDREN) ||
2783 !nsBlockFrame::BlockCanIntersectFloats(aLine->mFirstChild))
2784 : aLine->HasFloatClearTypeAfter();
2788 * Reparent a whole list of floats from aOldParent to this block. The
2789 * floats might be taken from aOldParent's overflow list. They will be
2790 * removed from the list. They end up appended to our mFloats list.
2792 void nsBlockFrame::ReparentFloats(nsIFrame* aFirstFrame,
2793 nsBlockFrame* aOldParent,
2794 bool aReparentSiblings) {
2795 nsFrameList list;
2796 aOldParent->CollectFloats(aFirstFrame, list, aReparentSiblings);
2797 if (list.NotEmpty()) {
2798 for (nsIFrame* f : list) {
2799 MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
2800 "CollectFloats should've removed that bit");
2801 ReparentFrame(f, aOldParent, this);
2803 mFloats.AppendFrames(nullptr, std::move(list));
2807 static void DumpLine(const BlockReflowState& aState, nsLineBox* aLine,
2808 nscoord aDeltaBCoord, int32_t aDeltaIndent) {
2809 #ifdef DEBUG
2810 if (nsBlockFrame::gNoisyReflow) {
2811 nsRect ovis(aLine->InkOverflowRect());
2812 nsRect oscr(aLine->ScrollableOverflowRect());
2813 nsBlockFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent + aDeltaIndent);
2814 printf(
2815 "line=%p mBCoord=%d dirty=%s oldBounds={%d,%d,%d,%d} "
2816 "oldoverflow-vis={%d,%d,%d,%d} oldoverflow-scr={%d,%d,%d,%d} "
2817 "deltaBCoord=%d mPrevBEndMargin=%d childCount=%d\n",
2818 static_cast<void*>(aLine), aState.mBCoord,
2819 aLine->IsDirty() ? "yes" : "no", aLine->IStart(), aLine->BStart(),
2820 aLine->ISize(), aLine->BSize(), ovis.x, ovis.y, ovis.width, ovis.height,
2821 oscr.x, oscr.y, oscr.width, oscr.height, aDeltaBCoord,
2822 aState.mPrevBEndMargin.get(), aLine->GetChildCount());
2824 #endif
2827 static bool LinesAreEmpty(const nsLineList& aList) {
2828 for (const auto& line : aList) {
2829 if (!line.IsEmpty()) {
2830 return false;
2833 return true;
2836 bool nsBlockFrame::ReflowDirtyLines(BlockReflowState& aState) {
2837 bool keepGoing = true;
2838 bool repositionViews = false; // should we really need this?
2839 bool foundAnyClears = aState.mTrailingClearFromPIF != StyleClear::None;
2840 bool willReflowAgain = false;
2841 bool usedOverflowWrap = false;
2843 #ifdef DEBUG
2844 if (gNoisyReflow) {
2845 IndentBy(stdout, gNoiseIndent);
2846 ListTag(stdout);
2847 printf(": reflowing dirty lines");
2848 printf(" computedISize=%d\n", aState.mReflowInput.ComputedISize());
2850 AutoNoisyIndenter indent(gNoisyReflow);
2851 #endif
2853 bool selfDirty = HasAnyStateBits(NS_FRAME_IS_DIRTY) ||
2854 (aState.mReflowInput.IsBResize() &&
2855 HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE));
2857 // Reflow our last line if our availableBSize has increased
2858 // so that we (and our last child) pull up content as necessary
2859 if (aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
2860 GetNextInFlow() &&
2861 aState.mReflowInput.AvailableBSize() >
2862 GetLogicalSize().BSize(aState.mReflowInput.GetWritingMode())) {
2863 LineIterator lastLine = LinesEnd();
2864 if (lastLine != LinesBegin()) {
2865 --lastLine;
2866 lastLine->MarkDirty();
2869 // the amount by which we will slide the current line if it is not
2870 // dirty
2871 nscoord deltaBCoord = 0;
2873 // whether we did NOT reflow the previous line and thus we need to
2874 // recompute the carried out margin before the line if we want to
2875 // reflow it or if its previous margin is dirty
2876 bool needToRecoverState = false;
2877 // Float continuations were reflowed in ReflowPushedFloats
2878 bool reflowedFloat =
2879 mFloats.NotEmpty() &&
2880 mFloats.FirstChild()->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT);
2881 bool lastLineMovedUp = false;
2882 // We save up information about BR-clearance here
2883 StyleClear inlineFloatClearType = aState.mTrailingClearFromPIF;
2885 LineIterator line = LinesBegin(), line_end = LinesEnd();
2887 // Determine if children of this frame could have breaks between them for
2888 // page names.
2890 // We need to check for paginated layout, the named-page pref, and if the
2891 // available block-size is constrained.
2893 // Note that we need to check for paginated layout as named-pages are only
2894 // used during paginated reflow. We need to additionally check for
2895 // unconstrained block-size to avoid introducing fragmentation breaks during
2896 // "measuring" reflows within an overall paginated reflow, and to avoid
2897 // fragmentation in monolithic containers like 'inline-block'.
2899 // Because we can only break for named pages using Class A breakpoints, we
2900 // also need to check that the block flow direction of the containing frame
2901 // of these items (which is this block) is parallel to that of this page.
2902 // See: https://www.w3.org/TR/css-break-3/#btw-blocks
2903 const nsPresContext* const presCtx = aState.mPresContext;
2904 const bool canBreakForPageNames =
2905 aState.mReflowInput.mFlags.mCanHaveClassABreakpoints &&
2906 aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
2907 presCtx->GetPresShell()->GetRootFrame()->GetWritingMode().IsVertical() ==
2908 GetWritingMode().IsVertical();
2910 // ReflowInput.mFlags.mCanHaveClassABreakpoints should respect the named
2911 // pages pref and presCtx->IsPaginated, so we did not explicitly check these
2912 // above when setting canBreakForPageNames.
2913 if (canBreakForPageNames) {
2914 MOZ_ASSERT(presCtx->IsPaginated(),
2915 "canBreakForPageNames should not be set during non-paginated "
2916 "reflow");
2919 // Reflow the lines that are already ours
2920 for (; line != line_end; ++line, aState.AdvanceToNextLine()) {
2921 DumpLine(aState, line, deltaBCoord, 0);
2922 #ifdef DEBUG
2923 AutoNoisyIndenter indent2(gNoisyReflow);
2924 #endif
2926 if (selfDirty) {
2927 line->MarkDirty();
2930 // This really sucks, but we have to look inside any blocks that have clear
2931 // elements inside them.
2932 // XXX what can we do smarter here?
2933 if (!line->IsDirty() && line->IsBlock() &&
2934 line->mFirstChild->HasAnyStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN)) {
2935 line->MarkDirty();
2938 nsIFrame* floatAvoidingBlock = nullptr;
2939 if (line->IsBlock() &&
2940 !nsBlockFrame::BlockCanIntersectFloats(line->mFirstChild)) {
2941 floatAvoidingBlock = line->mFirstChild;
2944 // We have to reflow the line if it's a block whose clearance
2945 // might have changed, so detect that.
2946 if (!line->IsDirty() &&
2947 (line->HasForcedLineBreakBefore() || floatAvoidingBlock)) {
2948 nscoord curBCoord = aState.mBCoord;
2949 // See where we would be after applying any clearance due to
2950 // BRs.
2951 if (inlineFloatClearType != StyleClear::None) {
2952 std::tie(curBCoord, std::ignore) =
2953 aState.ClearFloats(curBCoord, inlineFloatClearType);
2956 auto [newBCoord, result] = aState.ClearFloats(
2957 curBCoord, line->FloatClearTypeBefore(), floatAvoidingBlock);
2959 if (line->HasClearance()) {
2960 // Reflow the line if it might not have clearance anymore.
2961 if (result == ClearFloatsResult::BCoordNoChange
2962 // aState.mBCoord is the clearance point which should be the
2963 // block-start border-edge of the block frame. If sliding the
2964 // block by deltaBCoord isn't going to put it in the predicted
2965 // position, then we'd better reflow the line.
2966 || newBCoord != line->BStart() + deltaBCoord) {
2967 line->MarkDirty();
2969 } else {
2970 // Reflow the line if the line might have clearance now.
2971 if (result != ClearFloatsResult::BCoordNoChange) {
2972 line->MarkDirty();
2977 // We might have to reflow a line that is after a clearing BR.
2978 if (inlineFloatClearType != StyleClear::None) {
2979 std::tie(aState.mBCoord, std::ignore) =
2980 aState.ClearFloats(aState.mBCoord, inlineFloatClearType);
2981 if (aState.mBCoord != line->BStart() + deltaBCoord) {
2982 // SlideLine is not going to put the line where the clearance
2983 // put it. Reflow the line to be sure.
2984 line->MarkDirty();
2986 inlineFloatClearType = StyleClear::None;
2989 bool previousMarginWasDirty = line->IsPreviousMarginDirty();
2990 if (previousMarginWasDirty) {
2991 // If the previous margin is dirty, reflow the current line
2992 line->MarkDirty();
2993 line->ClearPreviousMarginDirty();
2994 } else if (aState.ContentBSize() != NS_UNCONSTRAINEDSIZE) {
2995 const nscoord scrollableOverflowBEnd =
2996 LogicalRect(line->mWritingMode, line->ScrollableOverflowRect(),
2997 line->mContainerSize)
2998 .BEnd(line->mWritingMode);
2999 if (scrollableOverflowBEnd + deltaBCoord > aState.ContentBEnd()) {
3000 // Lines that aren't dirty but get slid past our available block-size
3001 // constraint must be reflowed.
3002 line->MarkDirty();
3006 if (!line->IsDirty()) {
3007 const bool isPaginated =
3008 // Last column can be reflowed unconstrained during column balancing.
3009 // Hence the additional NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR bit check
3010 // as a fail-safe fallback.
3011 aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE ||
3012 HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) ||
3013 // Table can also be reflowed unconstrained during printing.
3014 aState.mPresContext->IsPaginated();
3015 if (isPaginated) {
3016 // We are in a paginated context, i.e. in columns or pages.
3017 const bool mayContainFloats =
3018 line->IsBlock() || line->HasFloats() || line->HadFloatPushed();
3019 if (mayContainFloats) {
3020 // The following if-else conditions check whether this line -- which
3021 // might have floats in its subtree, or has floats as direct children,
3022 // or had floats pushed -- needs to be reflowed.
3023 if (deltaBCoord != 0 || aState.mReflowInput.IsBResize()) {
3024 // The distance to the block-end edge might have changed. Reflow the
3025 // line both because the breakpoints within its floats may have
3026 // changed and because we might have to push/pull the floats in
3027 // their entirety.
3028 line->MarkDirty();
3029 } else if (HasPushedFloats()) {
3030 // We had pushed floats which haven't been drained by our
3031 // next-in-flow, which means our parent is currently reflowing us
3032 // again due to clearance without creating a next-in-flow for us.
3033 // Reflow the line to redo the floats split logic to correctly set
3034 // our reflow status.
3035 line->MarkDirty();
3036 } else if (aState.mReflowInput.mFlags.mMustReflowPlaceholders) {
3037 // Reflow the line (that may containing a float's placeholder frame)
3038 // if our parent tells us to do so.
3039 line->MarkDirty();
3040 } else if (aState.mReflowInput.mFlags.mMovedBlockFragments) {
3041 // Our parent's line containing us moved to a different fragment.
3042 // Reflow the line because the decision about whether the float fits
3043 // may be different in a different fragment.
3044 line->MarkDirty();
3050 if (!line->IsDirty()) {
3051 // See if there's any reflow damage that requires that we mark the
3052 // line dirty.
3053 PropagateFloatDamage(aState, line, deltaBCoord);
3056 // If the container size has changed, reset mContainerSize. If the
3057 // line's writing mode is not ltr, or if the line is not left-aligned, also
3058 // mark the line dirty.
3059 if (aState.ContainerSize() != line->mContainerSize) {
3060 line->mContainerSize = aState.ContainerSize();
3062 const bool isLastLine = line == mLines.back() && !GetNextInFlow();
3063 const auto align = isLastLine ? StyleText()->TextAlignForLastLine()
3064 : StyleText()->mTextAlign;
3065 if (line->mWritingMode.IsVertical() || line->mWritingMode.IsBidiRTL() ||
3066 !IsAlignedLeft(align, StyleVisibility()->mDirection,
3067 StyleTextReset()->mUnicodeBidi, this)) {
3068 line->MarkDirty();
3072 // Check for a page break caused by CSS named pages.
3074 // We should break for named pages when two frames meet at a class A
3075 // breakpoint, where the first frame has a different end page value to the
3076 // second frame's start page value. canBreakForPageNames is true iff
3077 // children of this frame can form class A breakpoints, and that we are not
3078 // in a measurement reflow or in a monolithic container such as
3079 // 'inline-block'.
3081 // We specifically do not want to cause a page-break for named pages when
3082 // we are at the top of a page. This would otherwise happen when the
3083 // previous sibling is an nsPageBreakFrame, or all previous siblings on the
3084 // current page are zero-height. The latter may not be per-spec, but is
3085 // compatible with Chrome's implementation of named pages.
3086 const nsAtom* nextPageName = nullptr;
3087 bool shouldBreakForPageName = false;
3088 if (canBreakForPageNames && (!aState.mReflowInput.mFlags.mIsTopOfPage ||
3089 !aState.IsAdjacentWithBStart())) {
3090 const nsIFrame* const frame = line->mFirstChild;
3091 if (!frame->IsPlaceholderFrame() && !frame->IsPageBreakFrame()) {
3092 nextPageName = frame->GetStartPageValue();
3093 // Walk back to the last frame that isn't a placeholder.
3094 const nsIFrame* prevFrame = frame->GetPrevSibling();
3095 while (prevFrame && prevFrame->IsPlaceholderFrame()) {
3096 prevFrame = prevFrame->GetPrevSibling();
3098 if (prevFrame && prevFrame->GetEndPageValue() != nextPageName) {
3099 shouldBreakForPageName = true;
3100 line->MarkDirty();
3105 if (needToRecoverState && line->IsDirty()) {
3106 // We need to reconstruct the block-end margin only if we didn't
3107 // reflow the previous line and we do need to reflow (or repair
3108 // the block-start position of) the next line.
3109 aState.ReconstructMarginBefore(line);
3112 bool reflowedPrevLine = !needToRecoverState;
3113 if (needToRecoverState) {
3114 needToRecoverState = false;
3116 // Update aState.mPrevChild as if we had reflowed all of the frames in
3117 // this line.
3118 if (line->IsDirty()) {
3119 NS_ASSERTION(
3120 line->mFirstChild->GetPrevSibling() == line.prev()->LastChild(),
3121 "unexpected line frames");
3122 aState.mPrevChild = line->mFirstChild->GetPrevSibling();
3126 // Now repair the line and update |aState.mBCoord| by calling
3127 // |ReflowLine| or |SlideLine|.
3128 // If we're going to reflow everything again, then no need to reflow
3129 // the dirty line ... unless the line has floats, in which case we'd
3130 // better reflow it now to refresh its float cache, which may contain
3131 // dangling frame pointers! Ugh! This reflow of the line may be
3132 // incorrect because we skipped reflowing previous lines (e.g., floats
3133 // may be placed incorrectly), but that's OK because we'll mark the
3134 // line dirty below under "if (aState.mReflowInput.mDiscoveredClearance..."
3135 if (line->IsDirty() && (line->HasFloats() || !willReflowAgain)) {
3136 lastLineMovedUp = true;
3138 bool maybeReflowingForFirstTime =
3139 line->IStart() == 0 && line->BStart() == 0 && line->ISize() == 0 &&
3140 line->BSize() == 0;
3142 // Compute the dirty lines "before" BEnd, after factoring in
3143 // the running deltaBCoord value - the running value is implicit in
3144 // aState.mBCoord.
3145 nscoord oldB = line->BStart();
3146 nscoord oldBMost = line->BEnd();
3148 NS_ASSERTION(!willReflowAgain || !line->IsBlock(),
3149 "Don't reflow blocks while willReflowAgain is true, reflow "
3150 "of block abs-pos children depends on this");
3152 if (shouldBreakForPageName) {
3153 // Immediately fragment for page-name. It is possible we could break
3154 // out of the loop right here, but this should make it more similar to
3155 // what happens when reflow causes fragmentation.
3156 // Set the page name, so that PushTruncatedLine does not need to
3157 // recalculate the new page name.
3158 PresShell()->FrameConstructor()->SetNextPageContentFramePageName(
3159 nextPageName ? nextPageName : GetAutoPageValue());
3160 PushTruncatedLine(aState, line, &keepGoing,
3161 ComputeNewPageNameIfNeeded::No);
3162 } else {
3163 // Reflow the dirty line. If it's an incremental reflow, then force
3164 // it to invalidate the dirty area if necessary
3165 usedOverflowWrap |= ReflowLine(aState, line, &keepGoing);
3168 if (aState.mReflowInput.WillReflowAgainForClearance()) {
3169 line->MarkDirty();
3170 willReflowAgain = true;
3171 // Note that once we've entered this state, every line that gets here
3172 // (e.g. because it has floats) gets marked dirty and reflowed again.
3173 // in the next pass. This is important, see above.
3176 if (line->HasFloats()) {
3177 reflowedFloat = true;
3180 if (!keepGoing) {
3181 DumpLine(aState, line, deltaBCoord, -1);
3182 if (0 == line->GetChildCount()) {
3183 DeleteLine(aState, line, line_end);
3185 break;
3188 // Test to see whether the margin that should be carried out
3189 // to the next line (NL) might have changed. In ReflowBlockFrame
3190 // we call nextLine->MarkPreviousMarginDirty if the block's
3191 // actual carried-out block-end margin changed. So here we only
3192 // need to worry about the following effects:
3193 // 1) the line was just created, and it might now be blocking
3194 // a carried-out block-end margin from previous lines that
3195 // used to reach NL from reaching NL
3196 // 2) the line used to be empty, and is now not empty,
3197 // thus blocking a carried-out block-end margin from previous lines
3198 // that used to reach NL from reaching NL
3199 // 3) the line wasn't empty, but now is, so a carried-out
3200 // block-end margin from previous lines that didn't used to reach NL
3201 // now does
3202 // 4) the line might have changed in a way that affects NL's
3203 // ShouldApplyBStartMargin decision. The three things that matter
3204 // are the line's emptiness, its adjacency to the block-start edge of the
3205 // block, and whether it has clearance (the latter only matters if the
3206 // block was and is adjacent to the block-start and empty).
3208 // If the line is empty now, we can't reliably tell if the line was empty
3209 // before, so we just assume it was and do
3210 // nextLine->MarkPreviousMarginDirty. This means the checks in 4) are
3211 // redundant; if the line is empty now we don't need to check 4), but if
3212 // the line is not empty now and we're sure it wasn't empty before, any
3213 // adjacency and clearance changes are irrelevant to the result of
3214 // nextLine->ShouldApplyBStartMargin.
3215 if (line.next() != LinesEnd()) {
3216 bool maybeWasEmpty = oldB == line.next()->BStart();
3217 bool isEmpty = line->CachedIsEmpty();
3218 if (maybeReflowingForFirstTime /*1*/ ||
3219 (isEmpty || maybeWasEmpty) /*2/3/4*/) {
3220 line.next()->MarkPreviousMarginDirty();
3221 // since it's marked dirty, nobody will care about |deltaBCoord|
3225 // If the line was just reflowed for the first time, then its
3226 // old mBounds cannot be trusted so this deltaBCoord computation is
3227 // bogus. But that's OK because we just did
3228 // MarkPreviousMarginDirty on the next line which will force it
3229 // to be reflowed, so this computation of deltaBCoord will not be
3230 // used.
3231 deltaBCoord = line->BEnd() - oldBMost;
3233 // Now do an interrupt check. We want to do this only in the case when we
3234 // actually reflow the line, so that if we get back in here we'll get
3235 // further on the reflow before interrupting.
3236 aState.mPresContext->CheckForInterrupt(this);
3237 } else {
3238 aState.mOverflowTracker->Skip(line->mFirstChild, aState.mReflowStatus);
3239 // Nop except for blocks (we don't create overflow container
3240 // continuations for any inlines atm), so only checking mFirstChild
3241 // is enough
3243 lastLineMovedUp = deltaBCoord < 0;
3245 if (deltaBCoord != 0) {
3246 SlideLine(aState, line, deltaBCoord);
3247 } else {
3248 repositionViews = true;
3251 NS_ASSERTION(!line->IsDirty() || !line->HasFloats(),
3252 "Possibly stale float cache here!");
3253 if (willReflowAgain && line->IsBlock()) {
3254 // If we're going to reflow everything again, and this line is a block,
3255 // then there is no need to recover float state. The line may contain
3256 // other lines with floats, but in that case RecoverStateFrom would only
3257 // add floats to the float manager. We don't need to do that because
3258 // everything's going to get reflowed again "for real". Calling
3259 // RecoverStateFrom in this situation could be lethal because the
3260 // block's descendant lines may have float caches containing dangling
3261 // frame pointers. Ugh!
3262 // If this line is inline, then we need to recover its state now
3263 // to make sure that we don't forget to move its floats by deltaBCoord.
3264 } else {
3265 // XXX EVIL O(N^2) EVIL
3266 aState.RecoverStateFrom(line, deltaBCoord);
3269 // Keep mBCoord up to date in case we're propagating reflow damage
3270 // and also because our final height may depend on it. If the
3271 // line is inlines, then only update mBCoord if the line is not
3272 // empty, because that's what PlaceLine does. (Empty blocks may
3273 // want to update mBCoord, e.g. if they have clearance.)
3274 if (line->IsBlock() || !line->CachedIsEmpty()) {
3275 aState.mBCoord = line->BEnd();
3278 needToRecoverState = true;
3280 if (reflowedPrevLine && !line->IsBlock() &&
3281 aState.mPresContext->HasPendingInterrupt()) {
3282 // Need to make sure to pull overflows from any prev-in-flows
3283 for (nsIFrame* inlineKid = line->mFirstChild; inlineKid;
3284 inlineKid = inlineKid->PrincipalChildList().FirstChild()) {
3285 inlineKid->PullOverflowsFromPrevInFlow();
3290 // Record if we need to clear floats before reflowing the next
3291 // line. Note that inlineFloatClearType will be handled and
3292 // cleared before the next line is processed, so there is no
3293 // need to combine break types here.
3294 if (line->HasFloatClearTypeAfter()) {
3295 inlineFloatClearType = line->FloatClearTypeAfter();
3298 if (LineHasClear(line.get())) {
3299 foundAnyClears = true;
3302 DumpLine(aState, line, deltaBCoord, -1);
3304 if (aState.mPresContext->HasPendingInterrupt()) {
3305 willReflowAgain = true;
3306 // Another option here might be to leave |line| clean if
3307 // !HasPendingInterrupt() before the CheckForInterrupt() call, since in
3308 // that case the line really did reflow as it should have. Not sure
3309 // whether that would be safe, so doing this for now instead. Also not
3310 // sure whether we really want to mark all lines dirty after an
3311 // interrupt, but until we get better at propagating float damage we
3312 // really do need to do it this way; see comments inside MarkLineDirty.
3313 MarkLineDirtyForInterrupt(line);
3317 // Handle BR-clearance from the last line of the block
3318 if (inlineFloatClearType != StyleClear::None) {
3319 std::tie(aState.mBCoord, std::ignore) =
3320 aState.ClearFloats(aState.mBCoord, inlineFloatClearType);
3323 if (needToRecoverState) {
3324 // Is this expensive?
3325 aState.ReconstructMarginBefore(line);
3327 // Update aState.mPrevChild as if we had reflowed all of the frames in
3328 // the last line.
3329 NS_ASSERTION(line == line_end || line->mFirstChild->GetPrevSibling() ==
3330 line.prev()->LastChild(),
3331 "unexpected line frames");
3332 aState.mPrevChild = line == line_end ? mFrames.LastChild()
3333 : line->mFirstChild->GetPrevSibling();
3336 // Should we really have to do this?
3337 if (repositionViews) {
3338 nsContainerFrame::PlaceFrameView(this);
3341 // We can skip trying to pull up the next line if our height is constrained
3342 // (so we can report being incomplete) and there is no next in flow or we
3343 // were told not to or we know it will be futile, i.e.,
3344 // -- the next in flow is not changing
3345 // -- and we cannot have added more space for its first line to be
3346 // pulled up into,
3347 // -- it's an incremental reflow of a descendant
3348 // -- and we didn't reflow any floats (so the available space
3349 // didn't change)
3350 // -- my chain of next-in-flows either has no first line, or its first
3351 // line isn't dirty.
3352 bool heightConstrained =
3353 aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE;
3354 bool skipPull = willReflowAgain && heightConstrained;
3355 if (!skipPull && heightConstrained && aState.mNextInFlow &&
3356 (aState.mReflowInput.mFlags.mNextInFlowUntouched && !lastLineMovedUp &&
3357 !HasAnyStateBits(NS_FRAME_IS_DIRTY) && !reflowedFloat)) {
3358 // We'll place lineIter at the last line of this block, so that
3359 // nsBlockInFlowLineIterator::Next() will take us to the first
3360 // line of my next-in-flow-chain. (But first, check that I
3361 // have any lines -- if I don't, just bail out of this
3362 // optimization.)
3363 LineIterator lineIter = this->LinesEnd();
3364 if (lineIter != this->LinesBegin()) {
3365 lineIter--; // I have lines; step back from dummy iterator to last line.
3366 nsBlockInFlowLineIterator bifLineIter(this, lineIter);
3368 // Check for next-in-flow-chain's first line.
3369 // (First, see if there is such a line, and second, see if it's clean)
3370 if (!bifLineIter.Next() || !bifLineIter.GetLine()->IsDirty()) {
3371 skipPull = true;
3376 if (skipPull && aState.mNextInFlow) {
3377 NS_ASSERTION(heightConstrained, "Height should be constrained here\n");
3378 if (aState.mNextInFlow->IsTrueOverflowContainer()) {
3379 aState.mReflowStatus.SetOverflowIncomplete();
3380 } else {
3381 aState.mReflowStatus.SetIncomplete();
3385 if (!skipPull && aState.mNextInFlow) {
3386 // Pull data from a next-in-flow if there's still room for more
3387 // content here.
3388 while (keepGoing && aState.mNextInFlow) {
3389 // Grab first line from our next-in-flow
3390 nsBlockFrame* nextInFlow = aState.mNextInFlow;
3391 nsLineBox* pulledLine;
3392 nsFrameList pulledFrames;
3393 if (!nextInFlow->mLines.empty()) {
3394 RemoveFirstLine(nextInFlow->mLines, nextInFlow->mFrames, &pulledLine,
3395 &pulledFrames);
3396 } else {
3397 // Grab an overflow line if there are any
3398 FrameLines* overflowLines = nextInFlow->GetOverflowLines();
3399 if (!overflowLines) {
3400 aState.mNextInFlow =
3401 static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow());
3402 continue;
3404 bool last =
3405 RemoveFirstLine(overflowLines->mLines, overflowLines->mFrames,
3406 &pulledLine, &pulledFrames);
3407 if (last) {
3408 nextInFlow->DestroyOverflowLines();
3412 if (pulledFrames.IsEmpty()) {
3413 // The line is empty. Try the next one.
3414 NS_ASSERTION(
3415 pulledLine->GetChildCount() == 0 && !pulledLine->mFirstChild,
3416 "bad empty line");
3417 nextInFlow->FreeLineBox(pulledLine);
3418 continue;
3421 if (nextInFlow->MaybeHasLineCursor()) {
3422 if (pulledLine == nextInFlow->GetLineCursorForDisplay()) {
3423 nextInFlow->ClearLineCursorForDisplay();
3425 if (pulledLine == nextInFlow->GetLineCursorForQuery()) {
3426 nextInFlow->ClearLineCursorForQuery();
3429 ReparentFrames(pulledFrames, nextInFlow, this);
3430 pulledLine->SetMovedFragments();
3432 NS_ASSERTION(pulledFrames.LastChild() == pulledLine->LastChild(),
3433 "Unexpected last frame");
3434 NS_ASSERTION(aState.mPrevChild || mLines.empty(),
3435 "should have a prevchild here");
3436 NS_ASSERTION(aState.mPrevChild == mFrames.LastChild(),
3437 "Incorrect aState.mPrevChild before inserting line at end");
3439 // Shift pulledLine's frames into our mFrames list.
3440 mFrames.AppendFrames(nullptr, std::move(pulledFrames));
3442 // Add line to our line list, and set its last child as our new prev-child
3443 line = mLines.before_insert(LinesEnd(), pulledLine);
3444 aState.mPrevChild = mFrames.LastChild();
3446 // Reparent floats whose placeholders are in the line.
3447 ReparentFloats(pulledLine->mFirstChild, nextInFlow, true);
3449 DumpLine(aState, pulledLine, deltaBCoord, 0);
3450 #ifdef DEBUG
3451 AutoNoisyIndenter indent2(gNoisyReflow);
3452 #endif
3454 if (aState.mPresContext->HasPendingInterrupt()) {
3455 MarkLineDirtyForInterrupt(line);
3456 } else {
3457 // Now reflow it and any lines that it makes during it's reflow
3458 // (we have to loop here because reflowing the line may cause a new
3459 // line to be created; see SplitLine's callers for examples of
3460 // when this happens).
3461 while (line != LinesEnd()) {
3462 usedOverflowWrap |= ReflowLine(aState, line, &keepGoing);
3464 if (aState.mReflowInput.WillReflowAgainForClearance()) {
3465 line->MarkDirty();
3466 keepGoing = false;
3467 aState.mReflowStatus.SetIncomplete();
3468 break;
3471 DumpLine(aState, line, deltaBCoord, -1);
3472 if (!keepGoing) {
3473 if (0 == line->GetChildCount()) {
3474 DeleteLine(aState, line, line_end);
3476 break;
3479 if (LineHasClear(line.get())) {
3480 foundAnyClears = true;
3483 if (aState.mPresContext->CheckForInterrupt(this)) {
3484 MarkLineDirtyForInterrupt(line);
3485 break;
3488 // If this is an inline frame then its time to stop
3489 ++line;
3490 aState.AdvanceToNextLine();
3495 if (aState.mReflowStatus.IsIncomplete()) {
3496 aState.mReflowStatus.SetNextInFlowNeedsReflow();
3497 } // XXXfr shouldn't set this flag when nextinflow has no lines
3500 // Handle an odd-ball case: a list-item with no lines
3501 if (mLines.empty() && HasOutsideMarker()) {
3502 ReflowOutput metrics(aState.mReflowInput);
3503 nsIFrame* marker = GetOutsideMarker();
3504 WritingMode wm = aState.mReflowInput.GetWritingMode();
3505 ReflowOutsideMarker(
3506 marker, aState, metrics,
3507 aState.mReflowInput.ComputedPhysicalBorderPadding().top);
3508 NS_ASSERTION(!MarkerIsEmpty() || metrics.BSize(wm) == 0,
3509 "empty ::marker frame took up space");
3511 if (!MarkerIsEmpty()) {
3512 // There are no lines so we have to fake up some y motion so that
3513 // we end up with *some* height.
3514 // (Note: if we're layout-contained, we have to be sure to leave our
3515 // ReflowOutput's BlockStartAscent() (i.e. the baseline) untouched,
3516 // because layout-contained frames have no baseline.)
3517 if (!aState.mReflowInput.mStyleDisplay->IsContainLayout() &&
3518 metrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
3519 nscoord ascent;
3520 WritingMode wm = aState.mReflowInput.GetWritingMode();
3521 if (nsLayoutUtils::GetFirstLineBaseline(wm, marker, &ascent)) {
3522 metrics.SetBlockStartAscent(ascent);
3523 } else {
3524 metrics.SetBlockStartAscent(metrics.BSize(wm));
3528 RefPtr<nsFontMetrics> fm =
3529 nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
3531 nscoord minAscent = nsLayoutUtils::GetCenteredFontBaseline(
3532 fm, aState.mMinLineHeight, wm.IsLineInverted());
3533 nscoord minDescent = aState.mMinLineHeight - minAscent;
3535 aState.mBCoord +=
3536 std::max(minAscent, metrics.BlockStartAscent()) +
3537 std::max(minDescent, metrics.BSize(wm) - metrics.BlockStartAscent());
3539 nscoord offset = minAscent - metrics.BlockStartAscent();
3540 if (offset > 0) {
3541 marker->SetRect(marker->GetRect() + nsPoint(0, offset));
3546 if (LinesAreEmpty(mLines) && ShouldHaveLineIfEmpty()) {
3547 aState.mBCoord += aState.mMinLineHeight;
3550 if (foundAnyClears) {
3551 AddStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN);
3552 } else {
3553 RemoveStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN);
3556 #ifdef DEBUG
3557 VerifyLines(true);
3558 VerifyOverflowSituation();
3559 if (gNoisyReflow) {
3560 IndentBy(stdout, gNoiseIndent - 1);
3561 ListTag(stdout);
3562 printf(": done reflowing dirty lines (status=%s)\n",
3563 ToString(aState.mReflowStatus).c_str());
3565 #endif
3567 return usedOverflowWrap;
3570 void nsBlockFrame::MarkLineDirtyForInterrupt(nsLineBox* aLine) {
3571 aLine->MarkDirty();
3573 // Just checking NS_FRAME_IS_DIRTY is ok, because we've already
3574 // marked the lines that need to be marked dirty based on our
3575 // vertical resize stuff. So we'll definitely reflow all those kids;
3576 // the only question is how they should behave.
3577 if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
3578 // Mark all our child frames dirty so we make sure to reflow them
3579 // later.
3580 int32_t n = aLine->GetChildCount();
3581 for (nsIFrame* f = aLine->mFirstChild; n > 0;
3582 f = f->GetNextSibling(), --n) {
3583 f->MarkSubtreeDirty();
3585 // And mark all the floats whose reflows we might be skipping dirty too.
3586 if (aLine->HasFloats()) {
3587 for (nsIFrame* f : aLine->Floats()) {
3588 f->MarkSubtreeDirty();
3591 } else {
3592 // Dirty all the descendant lines of block kids to handle float damage,
3593 // since our nsFloatManager will go away by the next time we're reflowing.
3594 // XXXbz Can we do something more like what PropagateFloatDamage does?
3595 // Would need to sort out the exact business with mBlockDelta for that....
3596 // This marks way too much dirty. If we ever make this better, revisit
3597 // which lines we mark dirty in the interrupt case in ReflowDirtyLines.
3598 nsBlockFrame* bf = do_QueryFrame(aLine->mFirstChild);
3599 if (bf) {
3600 MarkAllDescendantLinesDirty(bf);
3605 void nsBlockFrame::DeleteLine(BlockReflowState& aState,
3606 nsLineList::iterator aLine,
3607 nsLineList::iterator aLineEnd) {
3608 MOZ_ASSERT(0 == aLine->GetChildCount(), "can't delete !empty line");
3609 if (0 == aLine->GetChildCount()) {
3610 NS_ASSERTION(aState.mCurrentLine == aLine,
3611 "using function more generally than designed, "
3612 "but perhaps OK now");
3613 nsLineBox* line = aLine;
3614 aLine = mLines.erase(aLine);
3615 FreeLineBox(line);
3616 // Mark the previous margin of the next line dirty since we need to
3617 // recompute its top position.
3618 if (aLine != aLineEnd) {
3619 aLine->MarkPreviousMarginDirty();
3625 * Reflow a line. The line will either contain a single block frame
3626 * or contain 1 or more inline frames. aKeepReflowGoing indicates
3627 * whether or not the caller should continue to reflow more lines.
3628 * Returns true if the reflow used an overflow-wrap breakpoint.
3630 bool nsBlockFrame::ReflowLine(BlockReflowState& aState, LineIterator aLine,
3631 bool* aKeepReflowGoing) {
3632 MOZ_ASSERT(aLine->GetChildCount(), "reflowing empty line");
3634 // Setup the line-layout for the new line
3635 aState.mCurrentLine = aLine;
3636 aLine->ClearDirty();
3637 aLine->InvalidateCachedIsEmpty();
3638 aLine->ClearHadFloatPushed();
3640 // If this line contains a single block that is hidden by `content-visibility`
3641 // don't reflow the line. If this line contains inlines and the first one is
3642 // hidden by `content-visibility`, all of them are, so avoid reflow in that
3643 // case as well.
3644 // For frames that own anonymous children, even the first child is hidden by
3645 // `content-visibility`, there could be some anonymous children need reflow,
3646 // so we don't skip reflow this line.
3647 nsIFrame* firstChild = aLine->mFirstChild;
3648 if (firstChild->IsHiddenByContentVisibilityOfInFlowParentForLayout() &&
3649 !HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES)) {
3650 return false;
3653 // Now that we know what kind of line we have, reflow it
3654 bool usedOverflowWrap = false;
3655 if (aLine->IsBlock()) {
3656 ReflowBlockFrame(aState, aLine, aKeepReflowGoing);
3657 } else {
3658 aLine->SetLineWrapped(false);
3659 usedOverflowWrap = ReflowInlineFrames(aState, aLine, aKeepReflowGoing);
3661 // Store the line's float edges for overflow marker analysis if needed.
3662 aLine->ClearFloatEdges();
3663 if (aState.mFlags.mCanHaveOverflowMarkers) {
3664 WritingMode wm = aLine->mWritingMode;
3665 nsFlowAreaRect r = aState.GetFloatAvailableSpaceForBSize(
3666 aLine->BStart(), aLine->BSize(), nullptr);
3667 if (r.HasFloats()) {
3668 LogicalRect so = aLine->GetOverflowArea(OverflowType::Scrollable, wm,
3669 aLine->mContainerSize);
3670 nscoord s = r.mRect.IStart(wm);
3671 nscoord e = r.mRect.IEnd(wm);
3672 if (so.IEnd(wm) > e || so.IStart(wm) < s) {
3673 // This line is overlapping a float - store the edges marking the area
3674 // between the floats for text-overflow analysis.
3675 aLine->SetFloatEdges(s, e);
3681 aLine->ClearMovedFragments();
3683 return usedOverflowWrap;
3686 nsIFrame* nsBlockFrame::PullFrame(BlockReflowState& aState,
3687 LineIterator aLine) {
3688 // First check our remaining lines.
3689 if (LinesEnd() != aLine.next()) {
3690 return PullFrameFrom(aLine, this, aLine.next());
3693 NS_ASSERTION(
3694 !GetOverflowLines(),
3695 "Our overflow lines should have been removed at the start of reflow");
3697 // Try each next-in-flow.
3698 nsBlockFrame* nextInFlow = aState.mNextInFlow;
3699 while (nextInFlow) {
3700 if (nextInFlow->mLines.empty()) {
3701 nextInFlow->DrainSelfOverflowList();
3703 if (!nextInFlow->mLines.empty()) {
3704 return PullFrameFrom(aLine, nextInFlow, nextInFlow->mLines.begin());
3706 nextInFlow = static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow());
3707 aState.mNextInFlow = nextInFlow;
3710 return nullptr;
3713 nsIFrame* nsBlockFrame::PullFrameFrom(nsLineBox* aLine,
3714 nsBlockFrame* aFromContainer,
3715 nsLineList::iterator aFromLine) {
3716 nsLineBox* fromLine = aFromLine;
3717 MOZ_ASSERT(fromLine, "bad line to pull from");
3718 MOZ_ASSERT(fromLine->GetChildCount(), "empty line");
3719 MOZ_ASSERT(aLine->GetChildCount(), "empty line");
3720 MOZ_ASSERT(!HasProperty(LineIteratorProperty()),
3721 "Shouldn't have line iterators mid-reflow");
3723 NS_ASSERTION(fromLine->IsBlock() == fromLine->mFirstChild->IsBlockOutside(),
3724 "Disagreement about whether it's a block or not");
3726 if (fromLine->IsBlock()) {
3727 // If our line is not empty and the child in aFromLine is a block
3728 // then we cannot pull up the frame into this line. In this case
3729 // we stop pulling.
3730 return nullptr;
3732 // Take frame from fromLine
3733 nsIFrame* frame = fromLine->mFirstChild;
3734 nsIFrame* newFirstChild = frame->GetNextSibling();
3736 if (aFromContainer != this) {
3737 // The frame is being pulled from a next-in-flow; therefore we need to add
3738 // it to our sibling list.
3739 MOZ_ASSERT(aLine == mLines.back());
3740 MOZ_ASSERT(aFromLine == aFromContainer->mLines.begin(),
3741 "should only pull from first line");
3742 aFromContainer->mFrames.RemoveFrame(frame);
3744 // When pushing and pulling frames we need to check for whether any
3745 // views need to be reparented.
3746 ReparentFrame(frame, aFromContainer, this);
3747 mFrames.AppendFrame(nullptr, frame);
3749 // The frame might have (or contain) floats that need to be brought
3750 // over too. (pass 'false' since there are no siblings to check)
3751 ReparentFloats(frame, aFromContainer, false);
3752 } else {
3753 MOZ_ASSERT(aLine == aFromLine.prev());
3756 aLine->NoteFrameAdded(frame);
3757 fromLine->NoteFrameRemoved(frame);
3759 if (fromLine->GetChildCount() > 0) {
3760 // Mark line dirty now that we pulled a child
3761 fromLine->MarkDirty();
3762 fromLine->mFirstChild = newFirstChild;
3763 } else {
3764 // Free up the fromLine now that it's empty.
3765 // Its bounds might need to be redrawn, though.
3766 if (aFromLine.next() != aFromContainer->mLines.end()) {
3767 aFromLine.next()->MarkPreviousMarginDirty();
3769 aFromContainer->mLines.erase(aFromLine);
3770 // aFromLine is now invalid
3771 aFromContainer->FreeLineBox(fromLine);
3774 #ifdef DEBUG
3775 VerifyLines(true);
3776 VerifyOverflowSituation();
3777 #endif
3779 return frame;
3782 void nsBlockFrame::SlideLine(BlockReflowState& aState, nsLineBox* aLine,
3783 nscoord aDeltaBCoord) {
3784 MOZ_ASSERT(aDeltaBCoord != 0, "why slide a line nowhere?");
3786 // Adjust line state
3787 aLine->SlideBy(aDeltaBCoord, aState.ContainerSize());
3789 // Adjust the frames in the line
3790 MoveChildFramesOfLine(aLine, aDeltaBCoord);
3793 void nsBlockFrame::UpdateLineContainerSize(nsLineBox* aLine,
3794 const nsSize& aNewContainerSize) {
3795 if (aNewContainerSize == aLine->mContainerSize) {
3796 return;
3799 // Adjust line state
3800 nsSize sizeDelta = aLine->UpdateContainerSize(aNewContainerSize);
3802 // Changing container width only matters if writing mode is vertical-rl
3803 if (GetWritingMode().IsVerticalRL()) {
3804 MoveChildFramesOfLine(aLine, sizeDelta.width);
3808 void nsBlockFrame::MoveChildFramesOfLine(nsLineBox* aLine,
3809 nscoord aDeltaBCoord) {
3810 // Adjust the frames in the line
3811 nsIFrame* kid = aLine->mFirstChild;
3812 if (!kid) {
3813 return;
3816 WritingMode wm = GetWritingMode();
3817 LogicalPoint translation(wm, 0, aDeltaBCoord);
3819 if (aLine->IsBlock()) {
3820 if (aDeltaBCoord) {
3821 kid->MovePositionBy(wm, translation);
3824 // Make sure the frame's view and any child views are updated
3825 nsContainerFrame::PlaceFrameView(kid);
3826 } else {
3827 // Adjust the block-dir coordinate of the frames in the line.
3828 // Note: we need to re-position views even if aDeltaBCoord is 0, because
3829 // one of our parent frames may have moved and so the view's position
3830 // relative to its parent may have changed.
3831 int32_t n = aLine->GetChildCount();
3832 while (--n >= 0) {
3833 if (aDeltaBCoord) {
3834 kid->MovePositionBy(wm, translation);
3836 // Make sure the frame's view and any child views are updated
3837 nsContainerFrame::PlaceFrameView(kid);
3838 kid = kid->GetNextSibling();
3843 static inline bool IsNonAutoNonZeroBSize(const StyleSize& aCoord) {
3844 // The "extremum length" values (see ExtremumLength) were originally aimed at
3845 // inline-size (or width, as it was before logicalization). For now, let them
3846 // return false here, so we treat them like 'auto' pending a real
3847 // implementation. (See bug 1126420.)
3849 // FIXME (bug 567039, bug 527285) This isn't correct for the 'fill' value,
3850 // which should more likely (but not necessarily, depending on the available
3851 // space) be returning true.
3852 if (aCoord.BehavesLikeInitialValueOnBlockAxis()) {
3853 return false;
3855 MOZ_ASSERT(aCoord.IsLengthPercentage());
3856 // If we evaluate the length/percent/calc at a percentage basis of
3857 // both nscoord_MAX and 0, and it's zero both ways, then it's a zero
3858 // length, percent, or combination thereof. Test > 0 so we clamp
3859 // negative calc() results to 0.
3860 return aCoord.AsLengthPercentage().Resolve(nscoord_MAX) > 0 ||
3861 aCoord.AsLengthPercentage().Resolve(0) > 0;
3864 /* virtual */
3865 bool nsBlockFrame::IsSelfEmpty() {
3866 if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
3867 return true;
3870 // Blocks which are margin-roots (including inline-blocks) cannot be treated
3871 // as empty for margin-collapsing and other purposes. They're more like
3872 // replaced elements.
3873 if (HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS)) {
3874 return false;
3877 WritingMode wm = GetWritingMode();
3878 const nsStylePosition* position = StylePosition();
3880 if (IsNonAutoNonZeroBSize(position->MinBSize(wm)) ||
3881 IsNonAutoNonZeroBSize(position->BSize(wm))) {
3882 return false;
3885 // FIXME: Bug 1646100 - Take intrinsic size into account.
3886 // FIXME: Handle the case that both inline and block sizes are auto.
3887 // https://github.com/w3c/csswg-drafts/issues/5060.
3888 // Note: block-size could be zero or auto/intrinsic keywords here.
3889 if (position->BSize(wm).BehavesLikeInitialValueOnBlockAxis() &&
3890 position->mAspectRatio.HasFiniteRatio()) {
3891 return false;
3894 const nsStyleBorder* border = StyleBorder();
3895 const nsStylePadding* padding = StylePadding();
3897 if (border->GetComputedBorderWidth(wm.PhysicalSide(eLogicalSideBStart)) !=
3898 0 ||
3899 border->GetComputedBorderWidth(wm.PhysicalSide(eLogicalSideBEnd)) != 0 ||
3900 !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBStart(wm)) ||
3901 !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBEnd(wm))) {
3902 return false;
3905 if (HasOutsideMarker() && !MarkerIsEmpty()) {
3906 return false;
3909 return true;
3912 bool nsBlockFrame::CachedIsEmpty() {
3913 if (!IsSelfEmpty()) {
3914 return false;
3916 for (auto& line : mLines) {
3917 if (!line.CachedIsEmpty()) {
3918 return false;
3921 return true;
3924 bool nsBlockFrame::IsEmpty() {
3925 if (!IsSelfEmpty()) {
3926 return false;
3929 return LinesAreEmpty(mLines);
3932 bool nsBlockFrame::ShouldApplyBStartMargin(BlockReflowState& aState,
3933 nsLineBox* aLine) {
3934 if (aLine->mFirstChild->IsPageBreakFrame()) {
3935 // A page break frame consumes margins adjacent to it.
3936 // https://drafts.csswg.org/css-break/#break-margins
3937 return false;
3940 if (aState.mFlags.mShouldApplyBStartMargin) {
3941 // Apply short-circuit check to avoid searching the line list
3942 return true;
3945 if (!aState.IsAdjacentWithBStart()) {
3946 // If we aren't at the start block-coordinate then something of non-zero
3947 // height must have been placed. Therefore the childs block-start margin
3948 // applies.
3949 aState.mFlags.mShouldApplyBStartMargin = true;
3950 return true;
3953 // Determine if this line is "essentially" the first line
3954 LineIterator line = LinesBegin();
3955 if (aState.mFlags.mHasLineAdjacentToTop) {
3956 line = aState.mLineAdjacentToTop;
3958 while (line != aLine) {
3959 if (!line->CachedIsEmpty() || line->HasClearance()) {
3960 // A line which precedes aLine is non-empty, or has clearance,
3961 // so therefore the block-start margin applies.
3962 aState.mFlags.mShouldApplyBStartMargin = true;
3963 return true;
3965 // No need to apply the block-start margin if the line has floats. We
3966 // should collapse anyway (bug 44419)
3967 ++line;
3968 aState.mFlags.mHasLineAdjacentToTop = true;
3969 aState.mLineAdjacentToTop = line;
3972 // The line being reflowed is "essentially" the first line in the
3973 // block. Therefore its block-start margin will be collapsed by the
3974 // generational collapsing logic with its parent (us).
3975 return false;
3978 void nsBlockFrame::ReflowBlockFrame(BlockReflowState& aState,
3979 LineIterator aLine,
3980 bool* aKeepReflowGoing) {
3981 MOZ_ASSERT(*aKeepReflowGoing, "bad caller");
3983 nsIFrame* frame = aLine->mFirstChild;
3984 if (!frame) {
3985 NS_ASSERTION(false, "program error - unexpected empty line");
3986 return;
3989 // If the previous frame was a page-break-frame, then preemptively push this
3990 // frame to the next page.
3991 // This is primarily important for the placeholders for abspos frames, which
3992 // measure as zero height and then would be placed on this page.
3993 if (aState.ContentBSize() != NS_UNCONSTRAINEDSIZE) {
3994 const nsIFrame* const prev = frame->GetPrevSibling();
3995 if (prev && prev->IsPageBreakFrame()) {
3996 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
3997 return;
4001 // Prepare the block reflow engine
4002 nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput);
4004 StyleClear clearType = frame->StyleDisplay()->mClear;
4005 if (aState.mTrailingClearFromPIF != StyleClear::None) {
4006 clearType = nsLayoutUtils::CombineClearType(clearType,
4007 aState.mTrailingClearFromPIF);
4008 aState.mTrailingClearFromPIF = StyleClear::None;
4011 // Clear past floats before the block if the clear style is not none
4012 aLine->ClearForcedLineBreak();
4013 if (clearType != StyleClear::None) {
4014 aLine->SetForcedLineBreakBefore(clearType);
4017 // See if we should apply the block-start margin. If the block frame being
4018 // reflowed is a continuation, then we don't apply its block-start margin
4019 // because it's not significant. Otherwise, dig deeper.
4020 bool applyBStartMargin =
4021 !frame->GetPrevContinuation() && ShouldApplyBStartMargin(aState, aLine);
4022 if (applyBStartMargin) {
4023 // The HasClearance setting is only valid if ShouldApplyBStartMargin
4024 // returned false (in which case the block-start margin-root set our
4025 // clearance flag). Otherwise clear it now. We'll set it later on
4026 // ourselves if necessary.
4027 aLine->ClearHasClearance();
4029 bool treatWithClearance = aLine->HasClearance();
4031 bool mightClearFloats = clearType != StyleClear::None;
4032 nsIFrame* floatAvoidingBlock = nullptr;
4033 if (!nsBlockFrame::BlockCanIntersectFloats(frame)) {
4034 mightClearFloats = true;
4035 floatAvoidingBlock = frame;
4038 // If our block-start margin was counted as part of some parent's block-start
4039 // margin collapse, and we are being speculatively reflowed assuming this
4040 // frame DID NOT need clearance, then we need to check that
4041 // assumption.
4042 if (!treatWithClearance && !applyBStartMargin && mightClearFloats &&
4043 aState.mReflowInput.mDiscoveredClearance) {
4044 nscoord curBCoord = aState.mBCoord + aState.mPrevBEndMargin.get();
4045 if (auto [clearBCoord, result] =
4046 aState.ClearFloats(curBCoord, clearType, floatAvoidingBlock);
4047 result != ClearFloatsResult::BCoordNoChange) {
4048 Unused << clearBCoord;
4050 // Only record the first frame that requires clearance
4051 if (!*aState.mReflowInput.mDiscoveredClearance) {
4052 *aState.mReflowInput.mDiscoveredClearance = frame;
4054 aState.mPrevChild = frame;
4055 // Exactly what we do now is flexible since we'll definitely be
4056 // reflowed.
4057 return;
4060 if (treatWithClearance) {
4061 applyBStartMargin = true;
4064 nsIFrame* clearanceFrame = nullptr;
4065 const nscoord startingBCoord = aState.mBCoord;
4066 const nsCollapsingMargin incomingMargin = aState.mPrevBEndMargin;
4067 nscoord clearance;
4068 // Save the original position of the frame so that we can reposition
4069 // its view as needed.
4070 nsPoint originalPosition = frame->GetPosition();
4071 while (true) {
4072 clearance = 0;
4073 nscoord bStartMargin = 0;
4074 bool mayNeedRetry = false;
4075 bool clearedFloats = false;
4076 bool clearedPushedOrSplitFloat = false;
4077 if (applyBStartMargin) {
4078 // Precompute the blocks block-start margin value so that we can get the
4079 // correct available space (there might be a float that's
4080 // already been placed below the aState.mPrevBEndMargin
4082 // Setup a reflowInput to get the style computed block-start margin
4083 // value. We'll use a reason of `resize' so that we don't fudge
4084 // any incremental reflow input.
4086 // The availSpace here is irrelevant to our needs - all we want
4087 // out if this setup is the block-start margin value which doesn't depend
4088 // on the childs available space.
4089 // XXX building a complete ReflowInput just to get the block-start
4090 // margin seems like a waste. And we do this for almost every block!
4091 WritingMode wm = frame->GetWritingMode();
4092 LogicalSize availSpace = aState.ContentSize(wm);
4093 ReflowInput reflowInput(aState.mPresContext, aState.mReflowInput, frame,
4094 availSpace);
4096 if (treatWithClearance) {
4097 aState.mBCoord += aState.mPrevBEndMargin.get();
4098 aState.mPrevBEndMargin.Zero();
4101 // Now compute the collapsed margin-block-start value into
4102 // aState.mPrevBEndMargin, assuming that all child margins
4103 // collapse down to clearanceFrame.
4104 brc.ComputeCollapsedBStartMargin(reflowInput, &aState.mPrevBEndMargin,
4105 clearanceFrame, &mayNeedRetry);
4107 // XXX optimization; we could check the collapsing children to see if they
4108 // are sure to require clearance, and so avoid retrying them
4110 if (clearanceFrame) {
4111 // Don't allow retries on the second pass. The clearance decisions for
4112 // the blocks whose block-start margins collapse with ours are now
4113 // fixed.
4114 mayNeedRetry = false;
4117 if (!treatWithClearance && !clearanceFrame && mightClearFloats) {
4118 // We don't know if we need clearance and this is the first,
4119 // optimistic pass. So determine whether *this block* needs
4120 // clearance. Note that we do not allow the decision for whether
4121 // this block has clearance to change on the second pass; that
4122 // decision is only allowed to be made under the optimistic
4123 // first pass.
4124 nscoord curBCoord = aState.mBCoord + aState.mPrevBEndMargin.get();
4125 if (auto [clearBCoord, result] =
4126 aState.ClearFloats(curBCoord, clearType, floatAvoidingBlock);
4127 result != ClearFloatsResult::BCoordNoChange) {
4128 Unused << clearBCoord;
4130 // Looks like we need clearance and we didn't know about it already.
4131 // So recompute collapsed margin
4132 treatWithClearance = true;
4133 // Remember this decision, needed for incremental reflow
4134 aLine->SetHasClearance();
4136 // Apply incoming margins
4137 aState.mBCoord += aState.mPrevBEndMargin.get();
4138 aState.mPrevBEndMargin.Zero();
4140 // Compute the collapsed margin again, ignoring the incoming margin
4141 // this time
4142 mayNeedRetry = false;
4143 brc.ComputeCollapsedBStartMargin(reflowInput, &aState.mPrevBEndMargin,
4144 clearanceFrame, &mayNeedRetry);
4148 // Temporarily advance the running block-direction value so that the
4149 // GetFloatAvailableSpace method will return the right available space.
4150 // This undone as soon as the horizontal margins are computed.
4151 bStartMargin = aState.mPrevBEndMargin.get();
4153 if (treatWithClearance) {
4154 nscoord currentBCoord = aState.mBCoord;
4155 // advance mBCoord to the clear position.
4156 auto [clearBCoord, result] =
4157 aState.ClearFloats(aState.mBCoord, clearType, floatAvoidingBlock);
4158 aState.mBCoord = clearBCoord;
4160 clearedFloats = result != ClearFloatsResult::BCoordNoChange;
4161 clearedPushedOrSplitFloat =
4162 result == ClearFloatsResult::FloatsPushedOrSplit;
4164 // Compute clearance. It's the amount we need to add to the block-start
4165 // border-edge of the frame, after applying collapsed margins
4166 // from the frame and its children, to get it to line up with
4167 // the block-end of the floats. The former is
4168 // currentBCoord + bStartMargin, the latter is the current
4169 // aState.mBCoord.
4170 // Note that negative clearance is possible
4171 clearance = aState.mBCoord - (currentBCoord + bStartMargin);
4173 // Add clearance to our block-start margin while we compute available
4174 // space for the frame
4175 bStartMargin += clearance;
4177 // Note that aState.mBCoord should stay where it is: at the block-start
4178 // border-edge of the frame
4179 } else {
4180 // Advance aState.mBCoord to the block-start border-edge of the frame.
4181 aState.mBCoord += bStartMargin;
4185 aLine->SetLineIsImpactedByFloat(false);
4187 // Here aState.mBCoord is the block-start border-edge of the block.
4188 // Compute the available space for the block
4189 nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace();
4190 WritingMode wm = aState.mReflowInput.GetWritingMode();
4191 LogicalRect availSpace = aState.ComputeBlockAvailSpace(
4192 frame, floatAvailableSpace, (floatAvoidingBlock));
4194 // The check for
4195 // (!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats)
4196 // is to some degree out of paranoia: if we reliably eat up block-start
4197 // margins at the top of the page as we ought to, it wouldn't be
4198 // needed.
4199 if ((!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats) &&
4200 (availSpace.BSize(wm) < 0 || clearedPushedOrSplitFloat)) {
4201 // We know already that this child block won't fit on this
4202 // page/column due to the block-start margin or the clearance. So we
4203 // need to get out of here now. (If we don't, most blocks will handle
4204 // things fine, and report break-before, but zero-height blocks
4205 // won't, and will thus make their parent overly-large and force
4206 // *it* to be pushed in its entirety.)
4207 aState.mBCoord = startingBCoord;
4208 aState.mPrevBEndMargin = incomingMargin;
4209 if (ShouldAvoidBreakInside(aState.mReflowInput)) {
4210 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
4211 } else {
4212 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
4214 return;
4217 // Now put the block-dir coordinate back to the start of the
4218 // block-start-margin + clearance.
4219 aState.mBCoord -= bStartMargin;
4220 availSpace.BStart(wm) -= bStartMargin;
4221 if (NS_UNCONSTRAINEDSIZE != availSpace.BSize(wm)) {
4222 availSpace.BSize(wm) += bStartMargin;
4225 // Construct the reflow input for the block.
4226 Maybe<ReflowInput> childReflowInput;
4227 Maybe<LogicalSize> cbSize;
4228 LogicalSize availSize = availSpace.Size(wm);
4229 bool columnSetWrapperHasNoBSizeLeft = false;
4230 if (Style()->GetPseudoType() == PseudoStyleType::columnContent) {
4231 // Calculate the multicol containing block's block size so that the
4232 // children with percentage block size get correct percentage basis.
4233 const ReflowInput* cbReflowInput =
4234 aState.mReflowInput.mParentReflowInput->mCBReflowInput;
4235 MOZ_ASSERT(cbReflowInput->mFrame->StyleColumn()->IsColumnContainerStyle(),
4236 "Get unexpected reflow input of multicol containing block!");
4238 // Use column-width as the containing block's inline-size, i.e. the column
4239 // content's computed inline-size.
4240 cbSize.emplace(LogicalSize(wm, aState.mReflowInput.ComputedISize(),
4241 cbReflowInput->ComputedBSize())
4242 .ConvertTo(frame->GetWritingMode(), wm));
4244 // If a ColumnSetWrapper is in a balancing column content, it may be
4245 // pushed or pulled back and forth between column contents. Always add
4246 // NS_FRAME_HAS_DIRTY_CHILDREN bit to it so that its ColumnSet children
4247 // can have a chance to reflow under current block size constraint.
4248 if (aState.mReflowInput.mFlags.mIsColumnBalancing &&
4249 frame->IsColumnSetWrapperFrame()) {
4250 frame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
4252 } else if (IsColumnSetWrapperFrame()) {
4253 // If we are reflowing our ColumnSet children, we want to apply our block
4254 // size constraint to the available block size when constructing reflow
4255 // input for ColumnSet so that ColumnSet can use it to compute its max
4256 // column block size.
4257 if (frame->IsColumnSetFrame()) {
4258 nscoord contentBSize = aState.mReflowInput.ComputedBSize();
4259 if (aState.mReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) {
4260 contentBSize =
4261 std::min(contentBSize, aState.mReflowInput.ComputedMaxBSize());
4263 if (contentBSize != NS_UNCONSTRAINEDSIZE) {
4264 // To get the remaining content block-size, subtract the content
4265 // block-size consumed by our previous continuations.
4266 contentBSize -= aState.mConsumedBSize;
4268 // ColumnSet is not the outermost frame in the column container, so it
4269 // cannot have any margin. We don't need to consider any margin that
4270 // can be generated by "box-decoration-break: clone" as we do in
4271 // BlockReflowState::ComputeBlockAvailSpace().
4272 const nscoord availContentBSize = std::max(
4273 0, contentBSize - (aState.mBCoord - aState.ContentBStart()));
4274 if (availSize.BSize(wm) >= availContentBSize) {
4275 availSize.BSize(wm) = availContentBSize;
4276 columnSetWrapperHasNoBSizeLeft = true;
4282 childReflowInput.emplace(aState.mPresContext, aState.mReflowInput, frame,
4283 availSize.ConvertTo(frame->GetWritingMode(), wm),
4284 cbSize);
4286 childReflowInput->mFlags.mColumnSetWrapperHasNoBSizeLeft =
4287 columnSetWrapperHasNoBSizeLeft;
4289 if (aLine->MovedFragments()) {
4290 // We only need to set this the first reflow, since if we reflow
4291 // again (and replace childReflowInput) we'll be reflowing it
4292 // again in the same fragment as the previous time.
4293 childReflowInput->mFlags.mMovedBlockFragments = true;
4296 nsFloatManager::SavedState floatManagerState;
4297 nsReflowStatus frameReflowStatus;
4298 do {
4299 if (floatAvailableSpace.HasFloats()) {
4300 // Set if floatAvailableSpace.HasFloats() is true for any
4301 // iteration of the loop.
4302 aLine->SetLineIsImpactedByFloat(true);
4305 // We might need to store into mDiscoveredClearance later if it's
4306 // currently null; we want to overwrite any writes that
4307 // brc.ReflowBlock() below does, so we need to remember now
4308 // whether it's empty.
4309 const bool shouldStoreClearance =
4310 aState.mReflowInput.mDiscoveredClearance &&
4311 !*aState.mReflowInput.mDiscoveredClearance;
4313 // Reflow the block into the available space
4314 if (mayNeedRetry || floatAvoidingBlock) {
4315 aState.FloatManager()->PushState(&floatManagerState);
4318 if (mayNeedRetry) {
4319 childReflowInput->mDiscoveredClearance = &clearanceFrame;
4320 } else if (!applyBStartMargin) {
4321 childReflowInput->mDiscoveredClearance =
4322 aState.mReflowInput.mDiscoveredClearance;
4325 frameReflowStatus.Reset();
4326 brc.ReflowBlock(availSpace, applyBStartMargin, aState.mPrevBEndMargin,
4327 clearance, aLine.get(), *childReflowInput,
4328 frameReflowStatus, aState);
4330 if (frameReflowStatus.IsInlineBreakBefore()) {
4331 // No need to retry this loop if there is a break opportunity before the
4332 // child block.
4333 break;
4336 // Now the block has a height. Using that height, get the
4337 // available space again and call ComputeBlockAvailSpace again.
4338 // If ComputeBlockAvailSpace gives a different result, we need to
4339 // reflow again.
4340 if (!floatAvoidingBlock) {
4341 break;
4344 LogicalRect oldFloatAvailableSpaceRect(floatAvailableSpace.mRect);
4345 floatAvailableSpace = aState.GetFloatAvailableSpaceForBSize(
4346 aState.mBCoord + bStartMargin, brc.GetMetrics().BSize(wm),
4347 &floatManagerState);
4348 NS_ASSERTION(floatAvailableSpace.mRect.BStart(wm) ==
4349 oldFloatAvailableSpaceRect.BStart(wm),
4350 "yikes");
4351 // Restore the height to the position of the next band.
4352 floatAvailableSpace.mRect.BSize(wm) =
4353 oldFloatAvailableSpaceRect.BSize(wm);
4354 // Determine whether the available space shrunk on either side,
4355 // because (the first time round) we now know the block's height,
4356 // and it may intersect additional floats, or (on later
4357 // iterations) because narrowing the width relative to the
4358 // previous time may cause the block to become taller. Note that
4359 // since we're reflowing the block, narrowing the width might also
4360 // make it shorter, so we must pass aCanGrow as true.
4361 if (!AvailableSpaceShrunk(wm, oldFloatAvailableSpaceRect,
4362 floatAvailableSpace.mRect, true)) {
4363 // The size and position we chose before are fine (i.e., they
4364 // don't cause intersecting with floats that requires a change
4365 // in size or position), so we're done.
4366 break;
4369 bool advanced = false;
4370 if (!aState.FloatAvoidingBlockFitsInAvailSpace(floatAvoidingBlock,
4371 floatAvailableSpace)) {
4372 // Advance to the next band.
4373 nscoord newBCoord = aState.mBCoord;
4374 if (aState.AdvanceToNextBand(floatAvailableSpace.mRect, &newBCoord)) {
4375 advanced = true;
4377 // ClearFloats might be able to advance us further once we're there.
4378 std::tie(aState.mBCoord, std::ignore) =
4379 aState.ClearFloats(newBCoord, StyleClear::None, floatAvoidingBlock);
4381 // Start over with a new available space rect at the new height.
4382 floatAvailableSpace = aState.GetFloatAvailableSpaceWithState(
4383 aState.mBCoord, ShapeType::ShapeOutside, &floatManagerState);
4386 const LogicalRect oldAvailSpace = availSpace;
4387 availSpace = aState.ComputeBlockAvailSpace(frame, floatAvailableSpace,
4388 (floatAvoidingBlock));
4390 if (!advanced && availSpace.IsEqualEdges(oldAvailSpace)) {
4391 break;
4394 // We need another reflow.
4395 aState.FloatManager()->PopState(&floatManagerState);
4397 if (!treatWithClearance && !applyBStartMargin &&
4398 aState.mReflowInput.mDiscoveredClearance) {
4399 // We set shouldStoreClearance above to record only the first
4400 // frame that requires clearance.
4401 if (shouldStoreClearance) {
4402 *aState.mReflowInput.mDiscoveredClearance = frame;
4404 aState.mPrevChild = frame;
4405 // Exactly what we do now is flexible since we'll definitely be
4406 // reflowed.
4407 return;
4410 if (advanced) {
4411 // We're pushing down the border-box, so we don't apply margin anymore.
4412 // This should never cause us to move up since the call to
4413 // GetFloatAvailableSpaceForBSize above included the margin.
4414 applyBStartMargin = false;
4415 bStartMargin = 0;
4416 treatWithClearance = true; // avoid hitting test above
4417 clearance = 0;
4420 childReflowInput.reset();
4421 childReflowInput.emplace(
4422 aState.mPresContext, aState.mReflowInput, frame,
4423 availSpace.Size(wm).ConvertTo(frame->GetWritingMode(), wm));
4424 } while (true);
4426 if (mayNeedRetry && clearanceFrame) {
4427 // Found a clearance frame, so we need to reflow |frame| a second time.
4428 // Restore the states and start over again.
4429 aState.FloatManager()->PopState(&floatManagerState);
4430 aState.mBCoord = startingBCoord;
4431 aState.mPrevBEndMargin = incomingMargin;
4432 continue;
4435 aState.mPrevChild = frame;
4437 if (childReflowInput->WillReflowAgainForClearance()) {
4438 // If an ancestor of ours is going to reflow for clearance, we
4439 // need to avoid calling PlaceBlock, because it unsets dirty bits
4440 // on the child block (both itself, and through its call to
4441 // nsIFrame::DidReflow), and those dirty bits imply dirtiness for
4442 // all of the child block, including the lines it didn't reflow.
4443 NS_ASSERTION(originalPosition == frame->GetPosition(),
4444 "we need to call PositionChildViews");
4445 return;
4448 #if defined(REFLOW_STATUS_COVERAGE)
4449 RecordReflowStatus(true, frameReflowStatus);
4450 #endif
4452 if (frameReflowStatus.IsInlineBreakBefore()) {
4453 // None of the child block fits.
4454 if (ShouldAvoidBreakInside(aState.mReflowInput)) {
4455 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
4456 } else {
4457 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
4459 } else {
4460 // Note: line-break-after a block is a nop
4462 // Try to place the child block.
4463 // Don't force the block to fit if we have positive clearance, because
4464 // pushing it to the next page would give it more room.
4465 // Don't force the block to fit if it's impacted by a float. If it is,
4466 // then pushing it to the next page would give it more room. Note that
4467 // isImpacted doesn't include impact from the block's own floats.
4468 bool forceFit = aState.IsAdjacentWithBStart() && clearance <= 0 &&
4469 !floatAvailableSpace.HasFloats();
4470 nsCollapsingMargin collapsedBEndMargin;
4471 OverflowAreas overflowAreas;
4472 *aKeepReflowGoing =
4473 brc.PlaceBlock(*childReflowInput, forceFit, aLine.get(),
4474 collapsedBEndMargin, overflowAreas, frameReflowStatus);
4475 if (!frameReflowStatus.IsFullyComplete() &&
4476 ShouldAvoidBreakInside(aState.mReflowInput)) {
4477 *aKeepReflowGoing = false;
4478 aLine->MarkDirty();
4481 if (aLine->SetCarriedOutBEndMargin(collapsedBEndMargin)) {
4482 LineIterator nextLine = aLine;
4483 ++nextLine;
4484 if (nextLine != LinesEnd()) {
4485 nextLine->MarkPreviousMarginDirty();
4489 aLine->SetOverflowAreas(overflowAreas);
4490 if (*aKeepReflowGoing) {
4491 // Some of the child block fit
4493 // Advance to new Y position
4494 nscoord newBCoord = aLine->BEnd();
4495 aState.mBCoord = newBCoord;
4497 // Continue the block frame now if it didn't completely fit in
4498 // the available space.
4499 if (!frameReflowStatus.IsFullyComplete()) {
4500 bool madeContinuation = CreateContinuationFor(aState, nullptr, frame);
4502 nsIFrame* nextFrame = frame->GetNextInFlow();
4503 NS_ASSERTION(nextFrame,
4504 "We're supposed to have a next-in-flow by now");
4506 if (frameReflowStatus.IsIncomplete()) {
4507 // If nextFrame used to be an overflow container, make it a normal
4508 // block
4509 if (!madeContinuation &&
4510 nextFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
4511 nsOverflowContinuationTracker::AutoFinish fini(
4512 aState.mOverflowTracker, frame);
4513 nsContainerFrame* parent = nextFrame->GetParent();
4514 parent->StealFrame(nextFrame);
4515 if (parent != this) {
4516 ReparentFrame(nextFrame, parent, this);
4518 mFrames.InsertFrame(nullptr, frame, nextFrame);
4519 madeContinuation = true; // needs to be added to mLines
4520 nextFrame->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
4521 frameReflowStatus.SetNextInFlowNeedsReflow();
4524 // Push continuation to a new line, but only if we actually made
4525 // one.
4526 if (madeContinuation) {
4527 nsLineBox* line = NewLineBox(nextFrame, true);
4528 mLines.after_insert(aLine, line);
4531 PushTruncatedLine(aState, aLine.next(), aKeepReflowGoing);
4533 // If we need to reflow the continuation of the block child,
4534 // then we'd better reflow our continuation
4535 if (frameReflowStatus.NextInFlowNeedsReflow()) {
4536 aState.mReflowStatus.SetNextInFlowNeedsReflow();
4537 // We also need to make that continuation's line dirty so
4538 // it gets reflowed when we reflow our next in flow. The
4539 // nif's line must always be either a line of the nif's
4540 // parent block (only if we didn't make a continuation) or
4541 // else one of our own overflow lines. In the latter case
4542 // the line is already marked dirty, so just handle the
4543 // first case.
4544 if (!madeContinuation) {
4545 nsBlockFrame* nifBlock = do_QueryFrame(nextFrame->GetParent());
4546 NS_ASSERTION(
4547 nifBlock,
4548 "A block's child's next in flow's parent must be a block!");
4549 for (auto& line : nifBlock->Lines()) {
4550 if (line.Contains(nextFrame)) {
4551 line.MarkDirty();
4552 break;
4558 // The block-end margin for a block is only applied on the last
4559 // flow block. Since we just continued the child block frame,
4560 // we know that line->mFirstChild is not the last flow block
4561 // therefore zero out the running margin value.
4562 #ifdef NOISY_BLOCK_DIR_MARGINS
4563 ListTag(stdout);
4564 printf(": reflow incomplete, frame=");
4565 frame->ListTag(stdout);
4566 printf(" prevBEndMargin=%d, setting to zero\n",
4567 aState.mPrevBEndMargin.get());
4568 #endif
4569 aState.mPrevBEndMargin.Zero();
4570 } else { // frame is complete but its overflow is not complete
4571 // Disconnect the next-in-flow and put it in our overflow tracker
4572 if (!madeContinuation &&
4573 !nextFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
4574 // It already exists, but as a normal next-in-flow, so we need
4575 // to dig it out of the child lists.
4576 nextFrame->GetParent()->StealFrame(nextFrame);
4577 } else if (madeContinuation) {
4578 mFrames.RemoveFrame(nextFrame);
4581 // Put it in our overflow list
4582 aState.mOverflowTracker->Insert(nextFrame, frameReflowStatus);
4583 aState.mReflowStatus.MergeCompletionStatusFrom(frameReflowStatus);
4585 #ifdef NOISY_BLOCK_DIR_MARGINS
4586 ListTag(stdout);
4587 printf(": reflow complete but overflow incomplete for ");
4588 frame->ListTag(stdout);
4589 printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n",
4590 aState.mPrevBEndMargin.get(), collapsedBEndMargin.get());
4591 #endif
4592 aState.mPrevBEndMargin = collapsedBEndMargin;
4594 } else { // frame is fully complete
4595 #ifdef NOISY_BLOCK_DIR_MARGINS
4596 ListTag(stdout);
4597 printf(": reflow complete for ");
4598 frame->ListTag(stdout);
4599 printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n",
4600 aState.mPrevBEndMargin.get(), collapsedBEndMargin.get());
4601 #endif
4602 aState.mPrevBEndMargin = collapsedBEndMargin;
4604 #ifdef NOISY_BLOCK_DIR_MARGINS
4605 ListTag(stdout);
4606 printf(": frame=");
4607 frame->ListTag(stdout);
4608 printf(" carriedOutBEndMargin=%d collapsedBEndMargin=%d => %d\n",
4609 brc.GetCarriedOutBEndMargin().get(), collapsedBEndMargin.get(),
4610 aState.mPrevBEndMargin.get());
4611 #endif
4612 } else {
4613 if (!frameReflowStatus.IsFullyComplete()) {
4614 // The frame reported an incomplete status, but then it also didn't
4615 // fit. This means we need to reflow it again so that it can
4616 // (again) report the incomplete status.
4617 frame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
4620 if ((aLine == mLines.front() && !GetPrevInFlow()) ||
4621 ShouldAvoidBreakInside(aState.mReflowInput)) {
4622 // If it's our very first line *or* we're not at the top of the page
4623 // and we have page-break-inside:avoid, then we need to be pushed to
4624 // our parent's next-in-flow.
4625 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
4626 } else {
4627 // Push the line that didn't fit and any lines that follow it
4628 // to our next-in-flow.
4629 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
4633 break; // out of the reflow retry loop
4636 // Now that we've got its final position all figured out, position any child
4637 // views it may have. Note that the case when frame has a view got handled
4638 // by FinishReflowChild, but that function didn't have the coordinates needed
4639 // to correctly decide whether to reposition child views.
4640 if (originalPosition != frame->GetPosition() && !frame->HasView()) {
4641 nsContainerFrame::PositionChildViews(frame);
4644 #ifdef DEBUG
4645 VerifyLines(true);
4646 #endif
4649 // Returns true if an overflow-wrap break was used.
4650 bool nsBlockFrame::ReflowInlineFrames(BlockReflowState& aState,
4651 LineIterator aLine,
4652 bool* aKeepReflowGoing) {
4653 *aKeepReflowGoing = true;
4654 bool usedOverflowWrap = false;
4656 aLine->SetLineIsImpactedByFloat(false);
4658 // Setup initial coordinate system for reflowing the inline frames
4659 // into. Apply a previous block frame's block-end margin first.
4660 if (ShouldApplyBStartMargin(aState, aLine)) {
4661 aState.mBCoord += aState.mPrevBEndMargin.get();
4663 nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace();
4665 LineReflowStatus lineReflowStatus;
4666 do {
4667 nscoord availableSpaceBSize = 0;
4668 aState.mLineBSize.reset();
4669 do {
4670 bool allowPullUp = true;
4671 nsIFrame* forceBreakInFrame = nullptr;
4672 int32_t forceBreakOffset = -1;
4673 gfxBreakPriority forceBreakPriority = gfxBreakPriority::eNoBreak;
4674 do {
4675 nsFloatManager::SavedState floatManagerState;
4676 aState.FloatManager()->PushState(&floatManagerState);
4678 // Once upon a time we allocated the first 30 nsLineLayout objects
4679 // on the stack, and then we switched to the heap. At that time
4680 // these objects were large (1100 bytes on a 32 bit system).
4681 // Then the nsLineLayout object was shrunk to 156 bytes by
4682 // removing some internal buffers. Given that it is so much
4683 // smaller, the complexity of 2 different ways of allocating
4684 // no longer makes sense. Now we always allocate on the stack.
4685 nsLineLayout lineLayout(aState.mPresContext, aState.FloatManager(),
4686 aState.mReflowInput, &aLine, nullptr);
4687 lineLayout.Init(&aState, aState.mMinLineHeight, aState.mLineNumber);
4688 if (forceBreakInFrame) {
4689 lineLayout.ForceBreakAtPosition(forceBreakInFrame, forceBreakOffset);
4691 DoReflowInlineFrames(aState, lineLayout, aLine, floatAvailableSpace,
4692 availableSpaceBSize, &floatManagerState,
4693 aKeepReflowGoing, &lineReflowStatus, allowPullUp);
4694 usedOverflowWrap = lineLayout.EndLineReflow();
4696 if (LineReflowStatus::RedoNoPull == lineReflowStatus ||
4697 LineReflowStatus::RedoMoreFloats == lineReflowStatus ||
4698 LineReflowStatus::RedoNextBand == lineReflowStatus) {
4699 if (lineLayout.NeedsBackup()) {
4700 NS_ASSERTION(!forceBreakInFrame,
4701 "Backing up twice; this should never be necessary");
4702 // If there is no saved break position, then this will set
4703 // set forceBreakInFrame to null and we won't back up, which is
4704 // correct.
4705 forceBreakInFrame = lineLayout.GetLastOptionalBreakPosition(
4706 &forceBreakOffset, &forceBreakPriority);
4707 } else {
4708 forceBreakInFrame = nullptr;
4710 // restore the float manager state
4711 aState.FloatManager()->PopState(&floatManagerState);
4712 // Clear out float lists
4713 aState.mCurrentLineFloats.Clear();
4714 aState.mBelowCurrentLineFloats.Clear();
4715 aState.mNoWrapFloats.Clear();
4718 // Don't allow pullup on a subsequent LineReflowStatus::RedoNoPull pass
4719 allowPullUp = false;
4720 } while (LineReflowStatus::RedoNoPull == lineReflowStatus);
4721 } while (LineReflowStatus::RedoMoreFloats == lineReflowStatus);
4722 } while (LineReflowStatus::RedoNextBand == lineReflowStatus);
4724 return usedOverflowWrap;
4727 void nsBlockFrame::SetBreakBeforeStatusBeforeLine(BlockReflowState& aState,
4728 LineIterator aLine,
4729 bool* aKeepReflowGoing) {
4730 aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
4731 // Reflow the line again when we reflow at our new position.
4732 aLine->MarkDirty();
4733 *aKeepReflowGoing = false;
4736 void nsBlockFrame::PushTruncatedLine(
4737 BlockReflowState& aState, LineIterator aLine, bool* aKeepReflowGoing,
4738 ComputeNewPageNameIfNeeded aComputeNewPageName) {
4739 PushLines(aState, aLine.prev());
4740 *aKeepReflowGoing = false;
4742 if (aComputeNewPageName == ComputeNewPageNameIfNeeded::Yes) {
4743 // mCanHaveClassABreakpoints can only be true during paginated reflow, and
4744 // we expect this function to only be called when the available bsize is
4745 // constrained.
4746 const WritingMode wm = GetWritingMode();
4747 const bool canBreakForPageNames =
4748 aState.mReflowInput.mFlags.mCanHaveClassABreakpoints &&
4749 !PresShell()->GetRootFrame()->GetWritingMode().IsOrthogonalTo(wm);
4750 if (canBreakForPageNames) {
4751 PresShell()->FrameConstructor()->MaybeSetNextPageContentFramePageName(
4752 aLine->mFirstChild);
4755 aState.mReflowStatus.SetIncomplete();
4758 void nsBlockFrame::DoReflowInlineFrames(
4759 BlockReflowState& aState, nsLineLayout& aLineLayout, LineIterator aLine,
4760 nsFlowAreaRect& aFloatAvailableSpace, nscoord& aAvailableSpaceBSize,
4761 nsFloatManager::SavedState* aFloatStateBeforeLine, bool* aKeepReflowGoing,
4762 LineReflowStatus* aLineReflowStatus, bool aAllowPullUp) {
4763 // Forget all of the floats on the line
4764 aLine->ClearFloats();
4765 aState.mFloatOverflowAreas.Clear();
4767 // We need to set this flag on the line if any of our reflow passes
4768 // are impacted by floats.
4769 if (aFloatAvailableSpace.HasFloats()) {
4770 aLine->SetLineIsImpactedByFloat(true);
4772 #ifdef REALLY_NOISY_REFLOW
4773 printf("nsBlockFrame::DoReflowInlineFrames %p impacted = %d\n", this,
4774 aFloatAvailableSpace.HasFloats());
4775 #endif
4777 WritingMode outerWM = aState.mReflowInput.GetWritingMode();
4778 WritingMode lineWM = WritingModeForLine(outerWM, aLine->mFirstChild);
4779 LogicalRect lineRect = aFloatAvailableSpace.mRect.ConvertTo(
4780 lineWM, outerWM, aState.ContainerSize());
4782 nscoord iStart = lineRect.IStart(lineWM);
4783 nscoord availISize = lineRect.ISize(lineWM);
4784 nscoord availBSize;
4785 if (aState.mReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
4786 availBSize = NS_UNCONSTRAINEDSIZE;
4787 } else {
4788 /* XXX get the height right! */
4789 availBSize = lineRect.BSize(lineWM);
4792 // Make sure to enable resize optimization before we call BeginLineReflow
4793 // because it might get disabled there
4794 aLine->EnableResizeReflowOptimization();
4796 aLineLayout.BeginLineReflow(
4797 iStart, aState.mBCoord, availISize, availBSize,
4798 aFloatAvailableSpace.HasFloats(), false, /*XXX isTopOfPage*/
4799 lineWM, aState.mContainerSize, aState.mInsetForBalance);
4801 aState.mFlags.mIsLineLayoutEmpty = false;
4803 // XXX Unfortunately we need to know this before reflowing the first
4804 // inline frame in the line. FIX ME.
4805 if (0 == aLineLayout.GetLineNumber() &&
4806 HasAllStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD |
4807 NS_BLOCK_HAS_FIRST_LETTER_STYLE)) {
4808 aLineLayout.SetFirstLetterStyleOK(true);
4810 NS_ASSERTION(!(HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD) &&
4811 GetPrevContinuation()),
4812 "first letter child bit should only be on first continuation");
4814 // Reflow the frames that are already on the line first
4815 LineReflowStatus lineReflowStatus = LineReflowStatus::OK;
4816 int32_t i;
4817 nsIFrame* frame = aLine->mFirstChild;
4819 if (aFloatAvailableSpace.HasFloats()) {
4820 // There is a soft break opportunity at the start of the line, because
4821 // we can always move this line down below float(s).
4822 if (aLineLayout.NotifyOptionalBreakPosition(
4823 frame, 0, true, gfxBreakPriority::eNormalBreak)) {
4824 lineReflowStatus = LineReflowStatus::RedoNextBand;
4828 // need to repeatedly call GetChildCount here, because the child
4829 // count can change during the loop!
4830 for (i = 0;
4831 LineReflowStatus::OK == lineReflowStatus && i < aLine->GetChildCount();
4832 i++, frame = frame->GetNextSibling()) {
4833 ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus);
4834 if (LineReflowStatus::OK != lineReflowStatus) {
4835 // It is possible that one or more of next lines are empty
4836 // (because of DeleteNextInFlowChild). If so, delete them now
4837 // in case we are finished.
4838 ++aLine;
4839 while ((aLine != LinesEnd()) && (0 == aLine->GetChildCount())) {
4840 // XXX Is this still necessary now that DeleteNextInFlowChild
4841 // uses DoRemoveFrame?
4842 nsLineBox* toremove = aLine;
4843 aLine = mLines.erase(aLine);
4844 NS_ASSERTION(nullptr == toremove->mFirstChild, "bad empty line");
4845 FreeLineBox(toremove);
4847 --aLine;
4849 NS_ASSERTION(lineReflowStatus != LineReflowStatus::Truncated,
4850 "ReflowInlineFrame should never determine that a line "
4851 "needs to go to the next page/column");
4855 // Don't pull up new frames into lines with continuation placeholders
4856 if (aAllowPullUp) {
4857 // Pull frames and reflow them until we can't
4858 while (LineReflowStatus::OK == lineReflowStatus) {
4859 frame = PullFrame(aState, aLine);
4860 if (!frame) {
4861 break;
4864 while (LineReflowStatus::OK == lineReflowStatus) {
4865 int32_t oldCount = aLine->GetChildCount();
4866 ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus);
4867 if (aLine->GetChildCount() != oldCount) {
4868 // We just created a continuation for aFrame AND its going
4869 // to end up on this line (e.g. :first-letter
4870 // situation). Therefore we have to loop here before trying
4871 // to pull another frame.
4872 frame = frame->GetNextSibling();
4873 } else {
4874 break;
4880 aState.mFlags.mIsLineLayoutEmpty = aLineLayout.LineIsEmpty();
4882 // We only need to backup if the line isn't going to be reflowed again anyway
4883 bool needsBackup = aLineLayout.NeedsBackup() &&
4884 (lineReflowStatus == LineReflowStatus::Stop ||
4885 lineReflowStatus == LineReflowStatus::OK);
4886 if (needsBackup && aLineLayout.HaveForcedBreakPosition()) {
4887 NS_WARNING(
4888 "We shouldn't be backing up more than once! "
4889 "Someone must have set a break opportunity beyond the available width, "
4890 "even though there were better break opportunities before it");
4891 needsBackup = false;
4893 if (needsBackup) {
4894 // We need to try backing up to before a text run
4895 // XXX It's possible, in fact not unusual, for the break opportunity to
4896 // already be the end of the line. We should detect that and optimize to not
4897 // re-do the line.
4898 if (aLineLayout.HasOptionalBreakPosition()) {
4899 // We can back up!
4900 lineReflowStatus = LineReflowStatus::RedoNoPull;
4902 } else {
4903 // In case we reflow this line again, remember that we don't
4904 // need to force any breaking
4905 aLineLayout.ClearOptionalBreakPosition();
4908 if (LineReflowStatus::RedoNextBand == lineReflowStatus) {
4909 // This happens only when we have a line that is impacted by
4910 // floats and the first element in the line doesn't fit with
4911 // the floats.
4913 // If there's block space available, we either try to reflow the line
4914 // past the current band (if it's non-zero and the band definitely won't
4915 // widen around a shape-outside), otherwise we try one pixel down. If
4916 // there's no block space available, we push the line to the next
4917 // page/column.
4918 NS_ASSERTION(
4919 NS_UNCONSTRAINEDSIZE != aFloatAvailableSpace.mRect.BSize(outerWM),
4920 "unconstrained block size on totally empty line");
4922 // See the analogous code for blocks in BlockReflowState::ClearFloats.
4923 nscoord bandBSize = aFloatAvailableSpace.mRect.BSize(outerWM);
4924 if (bandBSize > 0 ||
4925 NS_UNCONSTRAINEDSIZE == aState.mReflowInput.AvailableBSize()) {
4926 NS_ASSERTION(bandBSize == 0 || aFloatAvailableSpace.HasFloats(),
4927 "redo line on totally empty line with non-empty band...");
4928 // We should never hit this case if we've placed floats on the
4929 // line; if we have, then the GetFloatAvailableSpace call is wrong
4930 // and needs to happen after the caller pops the float manager
4931 // state.
4932 aState.FloatManager()->AssertStateMatches(aFloatStateBeforeLine);
4934 if (!aFloatAvailableSpace.MayWiden() && bandBSize > 0) {
4935 // Move it down far enough to clear the current band.
4936 aState.mBCoord += bandBSize;
4937 } else {
4938 // Move it down by one dev pixel.
4939 aState.mBCoord += aState.mPresContext->DevPixelsToAppUnits(1);
4942 aFloatAvailableSpace = aState.GetFloatAvailableSpace();
4943 } else {
4944 // There's nowhere to retry placing the line, so we want to push
4945 // it to the next page/column where its contents can fit not
4946 // next to a float.
4947 lineReflowStatus = LineReflowStatus::Truncated;
4948 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
4951 // XXX: a small optimization can be done here when paginating:
4952 // if the new Y coordinate is past the end of the block then
4953 // push the line and return now instead of later on after we are
4954 // past the float.
4955 } else if (LineReflowStatus::Truncated != lineReflowStatus &&
4956 LineReflowStatus::RedoNoPull != lineReflowStatus) {
4957 // If we are propagating out a break-before status then there is
4958 // no point in placing the line.
4959 if (!aState.mReflowStatus.IsInlineBreakBefore()) {
4960 if (!PlaceLine(aState, aLineLayout, aLine, aFloatStateBeforeLine,
4961 aFloatAvailableSpace, aAvailableSpaceBSize,
4962 aKeepReflowGoing)) {
4963 lineReflowStatus = LineReflowStatus::RedoMoreFloats;
4964 // PlaceLine already called GetFloatAvailableSpaceForBSize or its
4965 // variant for us.
4969 #ifdef DEBUG
4970 if (gNoisyReflow) {
4971 printf("Line reflow status = %s\n",
4972 LineReflowStatusToString(lineReflowStatus));
4974 #endif
4976 if (aLineLayout.GetDirtyNextLine()) {
4977 // aLine may have been pushed to the overflow lines.
4978 FrameLines* overflowLines = GetOverflowLines();
4979 // We can't just compare iterators front() to aLine here, since they may be
4980 // in different lists.
4981 bool pushedToOverflowLines =
4982 overflowLines && overflowLines->mLines.front() == aLine.get();
4983 if (pushedToOverflowLines) {
4984 // aLine is stale, it's associated with the main line list but it should
4985 // be associated with the overflow line list now
4986 aLine = overflowLines->mLines.begin();
4988 nsBlockInFlowLineIterator iter(this, aLine, pushedToOverflowLines);
4989 if (iter.Next() && iter.GetLine()->IsInline()) {
4990 iter.GetLine()->MarkDirty();
4991 if (iter.GetContainer() != this) {
4992 aState.mReflowStatus.SetNextInFlowNeedsReflow();
4997 *aLineReflowStatus = lineReflowStatus;
5001 * Reflow an inline frame. The reflow status is mapped from the frames
5002 * reflow status to the lines reflow status (not to our reflow status).
5003 * The line reflow status is simple: true means keep placing frames
5004 * on the line; false means don't (the line is done). If the line
5005 * has some sort of breaking affect then aLine's break-type will be set
5006 * to something other than StyleClear::None.
5008 void nsBlockFrame::ReflowInlineFrame(BlockReflowState& aState,
5009 nsLineLayout& aLineLayout,
5010 LineIterator aLine, nsIFrame* aFrame,
5011 LineReflowStatus* aLineReflowStatus) {
5012 MOZ_ASSERT(aFrame);
5013 *aLineReflowStatus = LineReflowStatus::OK;
5015 #ifdef NOISY_FIRST_LETTER
5016 ListTag(stdout);
5017 printf(": reflowing ");
5018 aFrame->ListTag(stdout);
5019 printf(" reflowingFirstLetter=%s\n",
5020 aLineLayout.GetFirstLetterStyleOK() ? "on" : "off");
5021 #endif
5023 if (aFrame->IsPlaceholderFrame()) {
5024 auto ph = static_cast<nsPlaceholderFrame*>(aFrame);
5025 ph->ForgetLineIsEmptySoFar();
5028 // Reflow the inline frame
5029 nsReflowStatus frameReflowStatus;
5030 bool pushedFrame;
5031 aLineLayout.ReflowFrame(aFrame, frameReflowStatus, nullptr, pushedFrame);
5033 if (frameReflowStatus.NextInFlowNeedsReflow()) {
5034 aLineLayout.SetDirtyNextLine();
5037 #ifdef REALLY_NOISY_REFLOW
5038 aFrame->ListTag(stdout);
5039 printf(": status=%s\n", ToString(frameReflowStatus).c_str());
5040 #endif
5042 #if defined(REFLOW_STATUS_COVERAGE)
5043 RecordReflowStatus(false, frameReflowStatus);
5044 #endif
5046 // Send post-reflow notification
5047 aState.mPrevChild = aFrame;
5049 /* XXX
5050 This is where we need to add logic to handle some odd behavior.
5051 For one thing, we should usually place at least one thing next
5052 to a left float, even when that float takes up all the width on a line.
5053 see bug 22496
5056 // Process the child frames reflow status. There are 5 cases:
5057 // complete, not-complete, break-before, break-after-complete,
5058 // break-after-not-complete. There are two situations: we are a
5059 // block or we are an inline. This makes a total of 10 cases
5060 // (fortunately, there is some overlap).
5061 aLine->ClearForcedLineBreak();
5062 if (frameReflowStatus.IsInlineBreak() ||
5063 aState.mTrailingClearFromPIF != StyleClear::None) {
5064 // Always abort the line reflow (because a line break is the
5065 // minimal amount of break we do).
5066 *aLineReflowStatus = LineReflowStatus::Stop;
5068 // XXX what should aLine's break-type be set to in all these cases?
5069 if (frameReflowStatus.IsInlineBreakBefore()) {
5070 // Break-before cases.
5071 if (aFrame == aLine->mFirstChild) {
5072 // If we break before the first frame on the line then we must
5073 // be trying to place content where there's no room (e.g. on a
5074 // line with wide floats). Inform the caller to reflow the
5075 // line after skipping past a float.
5076 *aLineReflowStatus = LineReflowStatus::RedoNextBand;
5077 } else {
5078 // It's not the first child on this line so go ahead and split
5079 // the line. We will see the frame again on the next-line.
5080 SplitLine(aState, aLineLayout, aLine, aFrame, aLineReflowStatus);
5082 // If we're splitting the line because the frame didn't fit and it
5083 // was pushed, then mark the line as having word wrapped. We need to
5084 // know that if we're shrink wrapping our width
5085 if (pushedFrame) {
5086 aLine->SetLineWrapped(true);
5089 } else {
5090 MOZ_ASSERT(frameReflowStatus.IsInlineBreakAfter() ||
5091 aState.mTrailingClearFromPIF != StyleClear::None,
5092 "We should've handled inline break-before in the if-branch!");
5094 // If a float split and its prev-in-flow was followed by a <BR>, then
5095 // combine the <BR>'s float clear type with the inline's float clear type
5096 // (the inline will be the very next frame after the split float).
5097 StyleClear clearType = frameReflowStatus.FloatClearType();
5098 if (aState.mTrailingClearFromPIF != StyleClear::None) {
5099 clearType = nsLayoutUtils::CombineClearType(
5100 clearType, aState.mTrailingClearFromPIF);
5101 aState.mTrailingClearFromPIF = StyleClear::None;
5103 // Break-after cases
5104 if (clearType != StyleClear::None || aLineLayout.GetLineEndsInBR()) {
5105 aLine->SetForcedLineBreakAfter(clearType);
5107 if (frameReflowStatus.IsComplete()) {
5108 // Split line, but after the frame just reflowed
5109 SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(),
5110 aLineReflowStatus);
5112 if (frameReflowStatus.IsInlineBreakAfter() &&
5113 !aLineLayout.GetLineEndsInBR()) {
5114 aLineLayout.SetDirtyNextLine();
5120 if (!frameReflowStatus.IsFullyComplete()) {
5121 // Create a continuation for the incomplete frame. Note that the
5122 // frame may already have a continuation.
5123 CreateContinuationFor(aState, aLine, aFrame);
5125 // Remember that the line has wrapped
5126 if (!aLineLayout.GetLineEndsInBR()) {
5127 aLine->SetLineWrapped(true);
5130 // If we just ended a first-letter frame or reflowed a placeholder then
5131 // don't split the line and don't stop the line reflow...
5132 // But if we are going to stop anyways we'd better split the line.
5133 if ((!frameReflowStatus.FirstLetterComplete() &&
5134 !aFrame->IsPlaceholderFrame()) ||
5135 *aLineReflowStatus == LineReflowStatus::Stop) {
5136 // Split line after the current frame
5137 *aLineReflowStatus = LineReflowStatus::Stop;
5138 SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(),
5139 aLineReflowStatus);
5144 bool nsBlockFrame::CreateContinuationFor(BlockReflowState& aState,
5145 nsLineBox* aLine, nsIFrame* aFrame) {
5146 nsIFrame* newFrame = nullptr;
5148 if (!aFrame->GetNextInFlow()) {
5149 newFrame =
5150 PresShell()->FrameConstructor()->CreateContinuingFrame(aFrame, this);
5152 mFrames.InsertFrame(nullptr, aFrame, newFrame);
5154 if (aLine) {
5155 aLine->NoteFrameAdded(newFrame);
5158 #ifdef DEBUG
5159 VerifyLines(false);
5160 #endif
5161 return !!newFrame;
5164 void nsBlockFrame::SplitFloat(BlockReflowState& aState, nsIFrame* aFloat,
5165 const nsReflowStatus& aFloatStatus) {
5166 MOZ_ASSERT(!aFloatStatus.IsFullyComplete(),
5167 "why split the frame if it's fully complete?");
5168 MOZ_ASSERT(aState.mBlock == this);
5170 nsIFrame* nextInFlow = aFloat->GetNextInFlow();
5171 if (nextInFlow) {
5172 nsContainerFrame* oldParent = nextInFlow->GetParent();
5173 oldParent->StealFrame(nextInFlow);
5174 if (oldParent != this) {
5175 ReparentFrame(nextInFlow, oldParent, this);
5177 if (!aFloatStatus.IsOverflowIncomplete()) {
5178 nextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
5180 } else {
5181 nextInFlow =
5182 PresShell()->FrameConstructor()->CreateContinuingFrame(aFloat, this);
5184 if (aFloatStatus.IsOverflowIncomplete()) {
5185 nextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
5188 StyleFloat floatStyle = aFloat->StyleDisplay()->mFloat;
5189 if (floatStyle == StyleFloat::Left) {
5190 aState.FloatManager()->SetSplitLeftFloatAcrossBreak();
5191 } else {
5192 MOZ_ASSERT(floatStyle == StyleFloat::Right, "Unexpected float side!");
5193 aState.FloatManager()->SetSplitRightFloatAcrossBreak();
5196 aState.AppendPushedFloatChain(nextInFlow);
5197 if (MOZ_LIKELY(!HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS)) ||
5198 MOZ_UNLIKELY(IsTrueOverflowContainer())) {
5199 aState.mReflowStatus.SetOverflowIncomplete();
5200 } else {
5201 aState.mReflowStatus.SetIncomplete();
5205 static bool CheckPlaceholderInLine(nsIFrame* aBlock, nsLineBox* aLine,
5206 nsIFrame* aFloat) {
5207 if (!aFloat) {
5208 return true;
5210 NS_ASSERTION(!aFloat->GetPrevContinuation(),
5211 "float in a line should never be a continuation");
5212 NS_ASSERTION(!aFloat->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
5213 "float in a line should never be a pushed float");
5214 nsIFrame* ph = aFloat->FirstInFlow()->GetPlaceholderFrame();
5215 for (nsIFrame* f = ph; f; f = f->GetParent()) {
5216 if (f->GetParent() == aBlock) {
5217 return aLine->Contains(f);
5220 NS_ASSERTION(false, "aBlock is not an ancestor of aFrame!");
5221 return true;
5224 void nsBlockFrame::SplitLine(BlockReflowState& aState,
5225 nsLineLayout& aLineLayout, LineIterator aLine,
5226 nsIFrame* aFrame,
5227 LineReflowStatus* aLineReflowStatus) {
5228 MOZ_ASSERT(aLine->IsInline(), "illegal SplitLine on block line");
5230 int32_t pushCount =
5231 aLine->GetChildCount() - aLineLayout.GetCurrentSpanCount();
5232 MOZ_ASSERT(pushCount >= 0, "bad push count");
5234 #ifdef DEBUG
5235 if (gNoisyReflow) {
5236 nsIFrame::IndentBy(stdout, gNoiseIndent);
5237 printf("split line: from line=%p pushCount=%d aFrame=",
5238 static_cast<void*>(aLine.get()), pushCount);
5239 if (aFrame) {
5240 aFrame->ListTag(stdout);
5241 } else {
5242 printf("(null)");
5244 printf("\n");
5245 if (gReallyNoisyReflow) {
5246 aLine->List(stdout, gNoiseIndent + 1);
5249 #endif
5251 if (0 != pushCount) {
5252 MOZ_ASSERT(aLine->GetChildCount() > pushCount, "bad push");
5253 MOZ_ASSERT(nullptr != aFrame, "whoops");
5254 #ifdef DEBUG
5256 nsIFrame* f = aFrame;
5257 int32_t count = pushCount;
5258 while (f && count > 0) {
5259 f = f->GetNextSibling();
5260 --count;
5262 NS_ASSERTION(count == 0, "Not enough frames to push");
5264 #endif
5266 // Put frames being split out into their own line
5267 nsLineBox* newLine = NewLineBox(aLine, aFrame, pushCount);
5268 mLines.after_insert(aLine, newLine);
5269 #ifdef DEBUG
5270 if (gReallyNoisyReflow) {
5271 newLine->List(stdout, gNoiseIndent + 1);
5273 #endif
5275 // Let line layout know that some frames are no longer part of its
5276 // state.
5277 aLineLayout.SplitLineTo(aLine->GetChildCount());
5279 // If floats have been placed whose placeholders have been pushed to the new
5280 // line, we need to reflow the old line again. We don't want to look at the
5281 // frames in the new line, because as a large paragraph is laid out the
5282 // we'd get O(N^2) performance. So instead we just check that the last
5283 // float and the last below-current-line float are still in aLine.
5284 if (!CheckPlaceholderInLine(
5285 this, aLine,
5286 aLine->HasFloats() ? aLine->Floats().LastElement() : nullptr) ||
5287 !CheckPlaceholderInLine(
5288 this, aLine,
5289 aState.mBelowCurrentLineFloats.SafeLastElement(nullptr))) {
5290 *aLineReflowStatus = LineReflowStatus::RedoNoPull;
5293 #ifdef DEBUG
5294 VerifyLines(true);
5295 #endif
5299 bool nsBlockFrame::IsLastLine(BlockReflowState& aState, LineIterator aLine) {
5300 while (++aLine != LinesEnd()) {
5301 // There is another line
5302 if (0 != aLine->GetChildCount()) {
5303 // If the next line is a block line then this line is the last in a
5304 // group of inline lines.
5305 return aLine->IsBlock();
5307 // The next line is empty, try the next one
5310 // Try our next-in-flows lines to answer the question
5311 nsBlockFrame* nextInFlow = (nsBlockFrame*)GetNextInFlow();
5312 while (nullptr != nextInFlow) {
5313 for (const auto& line : nextInFlow->Lines()) {
5314 if (0 != line.GetChildCount()) {
5315 return line.IsBlock();
5318 nextInFlow = (nsBlockFrame*)nextInFlow->GetNextInFlow();
5321 // This is the last line - so don't allow justification
5322 return true;
5325 bool nsBlockFrame::PlaceLine(BlockReflowState& aState,
5326 nsLineLayout& aLineLayout, LineIterator aLine,
5327 nsFloatManager::SavedState* aFloatStateBeforeLine,
5328 nsFlowAreaRect& aFlowArea,
5329 nscoord& aAvailableSpaceBSize,
5330 bool* aKeepReflowGoing) {
5331 // Try to position the floats in a nowrap context.
5332 aLineLayout.FlushNoWrapFloats();
5334 // Trim extra white-space from the line before placing the frames
5335 aLineLayout.TrimTrailingWhiteSpace();
5337 // Vertically align the frames on this line.
5339 // According to the CSS2 spec, section 12.6.1, the "marker" box
5340 // participates in the height calculation of the list-item box's
5341 // first line box.
5343 // There are exactly two places a ::marker can be placed: near the
5344 // first or second line. It's only placed on the second line in a
5345 // rare case: when the first line is empty.
5346 WritingMode wm = aState.mReflowInput.GetWritingMode();
5347 bool addedMarker = false;
5348 if (HasOutsideMarker() &&
5349 ((aLine == mLines.front() &&
5350 (!aLineLayout.IsZeroBSize() || (aLine == mLines.back()))) ||
5351 (mLines.front() != mLines.back() && 0 == mLines.front()->BSize() &&
5352 aLine == mLines.begin().next()))) {
5353 ReflowOutput metrics(aState.mReflowInput);
5354 nsIFrame* marker = GetOutsideMarker();
5355 ReflowOutsideMarker(marker, aState, metrics, aState.mBCoord);
5356 NS_ASSERTION(!MarkerIsEmpty() || metrics.BSize(wm) == 0,
5357 "empty ::marker frame took up space");
5358 aLineLayout.AddMarkerFrame(marker, metrics);
5359 addedMarker = true;
5361 aLineLayout.VerticalAlignLine();
5363 // We want to consider the floats in the current line when determining
5364 // whether the float available space is shrunk. If mLineBSize doesn't
5365 // exist, we are in the first pass trying to place the line. Calling
5366 // GetFloatAvailableSpace() like we did in BlockReflowState::AddFloat()
5367 // for UpdateBand().
5369 // floatAvailableSpaceWithOldLineBSize is the float available space with
5370 // the old BSize, but including the floats that were added in this line.
5371 LogicalRect floatAvailableSpaceWithOldLineBSize =
5372 aState.mLineBSize.isNothing()
5373 ? aState.GetFloatAvailableSpace(aLine->BStart()).mRect
5374 : aState
5375 .GetFloatAvailableSpaceForBSize(
5376 aLine->BStart(), aState.mLineBSize.value(), nullptr)
5377 .mRect;
5379 // As we redo for floats, we can't reduce the amount of BSize we're
5380 // checking.
5381 aAvailableSpaceBSize = std::max(aAvailableSpaceBSize, aLine->BSize());
5382 LogicalRect floatAvailableSpaceWithLineBSize =
5383 aState
5384 .GetFloatAvailableSpaceForBSize(aLine->BStart(), aAvailableSpaceBSize,
5385 nullptr)
5386 .mRect;
5388 // If the available space between the floats is smaller now that we
5389 // know the BSize, return false (and cause another pass with
5390 // LineReflowStatus::RedoMoreFloats). We ensure aAvailableSpaceBSize
5391 // never decreases, which means that we can't reduce the set of floats
5392 // we intersect, which means that the available space cannot grow.
5393 if (AvailableSpaceShrunk(wm, floatAvailableSpaceWithOldLineBSize,
5394 floatAvailableSpaceWithLineBSize, false)) {
5395 // Prepare data for redoing the line.
5396 aState.mLineBSize = Some(aLine->BSize());
5398 // Since we want to redo the line, we update aFlowArea by using the
5399 // aFloatStateBeforeLine, which is the float manager's state before the
5400 // line is placed.
5401 LogicalRect oldFloatAvailableSpace(aFlowArea.mRect);
5402 aFlowArea = aState.GetFloatAvailableSpaceForBSize(
5403 aLine->BStart(), aAvailableSpaceBSize, aFloatStateBeforeLine);
5405 NS_ASSERTION(
5406 aFlowArea.mRect.BStart(wm) == oldFloatAvailableSpace.BStart(wm),
5407 "yikes");
5408 // Restore the BSize to the position of the next band.
5409 aFlowArea.mRect.BSize(wm) = oldFloatAvailableSpace.BSize(wm);
5411 // Enforce both IStart() and IEnd() never move outwards to prevent
5412 // infinite grow-shrink loops.
5413 const nscoord iStartDiff =
5414 aFlowArea.mRect.IStart(wm) - oldFloatAvailableSpace.IStart(wm);
5415 const nscoord iEndDiff =
5416 aFlowArea.mRect.IEnd(wm) - oldFloatAvailableSpace.IEnd(wm);
5417 if (iStartDiff < 0) {
5418 aFlowArea.mRect.IStart(wm) -= iStartDiff;
5419 aFlowArea.mRect.ISize(wm) += iStartDiff;
5421 if (iEndDiff > 0) {
5422 aFlowArea.mRect.ISize(wm) -= iEndDiff;
5425 return false;
5428 #ifdef DEBUG
5429 if (!GetParent()->IsAbsurdSizeAssertSuppressed()) {
5430 static nscoord lastHeight = 0;
5431 if (ABSURD_SIZE(aLine->BStart())) {
5432 lastHeight = aLine->BStart();
5433 if (abs(aLine->BStart() - lastHeight) > ABSURD_COORD / 10) {
5434 nsIFrame::ListTag(stdout);
5435 printf(": line=%p y=%d line.bounds.height=%d\n",
5436 static_cast<void*>(aLine.get()), aLine->BStart(),
5437 aLine->BSize());
5439 } else {
5440 lastHeight = 0;
5443 #endif
5445 // Only block frames horizontally align their children because
5446 // inline frames "shrink-wrap" around their children (therefore
5447 // there is no extra horizontal space).
5448 const nsStyleText* styleText = StyleText();
5451 * We don't care checking for IsLastLine properly if we don't care (if it
5452 * can't change the used text-align value for the line).
5454 * In other words, isLastLine really means isLastLineAndWeCare.
5456 const bool isLastLine =
5457 !IsInSVGTextSubtree() &&
5458 styleText->TextAlignForLastLine() != styleText->mTextAlign &&
5459 (aLineLayout.GetLineEndsInBR() || IsLastLine(aState, aLine));
5461 aLineLayout.TextAlignLine(aLine, isLastLine);
5463 // From here on, pfd->mBounds rectangles are incorrect because bidi
5464 // might have moved frames around!
5465 OverflowAreas overflowAreas;
5466 aLineLayout.RelativePositionFrames(overflowAreas);
5467 aLine->SetOverflowAreas(overflowAreas);
5468 if (addedMarker) {
5469 aLineLayout.RemoveMarkerFrame(GetOutsideMarker());
5472 // Inline lines do not have margins themselves; however they are
5473 // impacted by prior block margins. If this line ends up having some
5474 // height then we zero out the previous block-end margin value that was
5475 // already applied to the line's starting Y coordinate. Otherwise we
5476 // leave it be so that the previous blocks block-end margin can be
5477 // collapsed with a block that follows.
5478 nscoord newBCoord;
5480 if (!aLine->CachedIsEmpty()) {
5481 // This line has some height. Therefore the application of the
5482 // previous-bottom-margin should stick.
5483 aState.mPrevBEndMargin.Zero();
5484 newBCoord = aLine->BEnd();
5485 } else {
5486 // Don't let the previous-bottom-margin value affect the newBCoord
5487 // coordinate (it was applied in ReflowInlineFrames speculatively)
5488 // since the line is empty.
5489 // We already called |ShouldApplyBStartMargin|, and if we applied it
5490 // then mShouldApplyBStartMargin is set.
5491 nscoord dy = aState.mFlags.mShouldApplyBStartMargin
5492 ? -aState.mPrevBEndMargin.get()
5493 : 0;
5494 newBCoord = aState.mBCoord + dy;
5497 if (!aState.mReflowStatus.IsFullyComplete() &&
5498 ShouldAvoidBreakInside(aState.mReflowInput)) {
5499 aLine->AppendFloats(std::move(aState.mCurrentLineFloats));
5500 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
5501 return true;
5504 // See if the line fit (our first line always does).
5505 if (mLines.front() != aLine &&
5506 aState.ContentBSize() != NS_UNCONSTRAINEDSIZE &&
5507 newBCoord > aState.ContentBEnd()) {
5508 NS_ASSERTION(aState.mCurrentLine == aLine, "oops");
5509 if (ShouldAvoidBreakInside(aState.mReflowInput)) {
5510 // All our content doesn't fit, start on the next page.
5511 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
5512 } else {
5513 // Push aLine and all of its children and anything else that
5514 // follows to our next-in-flow.
5515 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
5517 return true;
5520 // Note that any early return before this update of aState.mBCoord
5521 // must either (a) return false or (b) set aKeepReflowGoing to false.
5522 // Otherwise we'll keep reflowing later lines at an incorrect
5523 // position, and we might not come back and clean up the damage later.
5524 aState.mBCoord = newBCoord;
5526 // Add the already placed current-line floats to the line
5527 aLine->AppendFloats(std::move(aState.mCurrentLineFloats));
5529 // Any below current line floats to place?
5530 if (!aState.mBelowCurrentLineFloats.IsEmpty()) {
5531 // Reflow the below-current-line floats, which places on the line's
5532 // float list.
5533 aState.PlaceBelowCurrentLineFloats(aLine);
5536 // When a line has floats, factor them into the overflow areas computations.
5537 if (aLine->HasFloats()) {
5538 // Union the float overflow areas (stored in aState) and the value computed
5539 // by the line layout code.
5540 OverflowAreas lineOverflowAreas = aState.mFloatOverflowAreas;
5541 lineOverflowAreas.UnionWith(aLine->GetOverflowAreas());
5542 aLine->SetOverflowAreas(lineOverflowAreas);
5544 #ifdef NOISY_OVERFLOW_AREAS
5545 printf("%s: Line %p, InkOverflowRect=%s, ScrollableOverflowRect=%s\n",
5546 ListTag().get(), aLine.get(),
5547 ToString(aLine->InkOverflowRect()).c_str(),
5548 ToString(aLine->ScrollableOverflowRect()).c_str());
5549 #endif
5552 // Apply break-after clearing if necessary
5553 // This must stay in sync with |ReflowDirtyLines|.
5554 if (aLine->HasFloatClearTypeAfter()) {
5555 std::tie(aState.mBCoord, std::ignore) =
5556 aState.ClearFloats(aState.mBCoord, aLine->FloatClearTypeAfter());
5558 return true;
5561 void nsBlockFrame::PushLines(BlockReflowState& aState,
5562 nsLineList::iterator aLineBefore) {
5563 // NOTE: aLineBefore is always a normal line, not an overflow line.
5564 // The following expression will assert otherwise.
5565 DebugOnly<bool> check = aLineBefore == mLines.begin();
5567 nsLineList::iterator overBegin(aLineBefore.next());
5569 // PushTruncatedPlaceholderLine sometimes pushes the first line. Ugh.
5570 bool firstLine = overBegin == LinesBegin();
5572 if (overBegin != LinesEnd()) {
5573 // Remove floats in the lines from mFloats
5574 nsFrameList floats;
5575 CollectFloats(overBegin->mFirstChild, floats, true);
5577 if (floats.NotEmpty()) {
5578 #ifdef DEBUG
5579 for (nsIFrame* f : floats) {
5580 MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
5581 "CollectFloats should've removed that bit");
5583 #endif
5584 // Push the floats onto the front of the overflow out-of-flows list
5585 nsAutoOOFFrameList oofs(this);
5586 oofs.mList.InsertFrames(nullptr, nullptr, std::move(floats));
5589 // overflow lines can already exist in some cases, in particular,
5590 // when shrinkwrapping and we discover that the shrinkwap causes
5591 // the height of some child block to grow which creates additional
5592 // overflowing content. In such cases we must prepend the new
5593 // overflow to the existing overflow.
5594 FrameLines* overflowLines = RemoveOverflowLines();
5595 if (!overflowLines) {
5596 // XXXldb use presshell arena!
5597 overflowLines = new FrameLines();
5599 if (overflowLines) {
5600 nsIFrame* lineBeforeLastFrame;
5601 if (firstLine) {
5602 lineBeforeLastFrame = nullptr; // removes all frames
5603 } else {
5604 nsIFrame* f = overBegin->mFirstChild;
5605 lineBeforeLastFrame = f ? f->GetPrevSibling() : mFrames.LastChild();
5606 NS_ASSERTION(!f || lineBeforeLastFrame == aLineBefore->LastChild(),
5607 "unexpected line frames");
5609 nsFrameList pushedFrames = mFrames.TakeFramesAfter(lineBeforeLastFrame);
5610 overflowLines->mFrames.InsertFrames(nullptr, nullptr,
5611 std::move(pushedFrames));
5613 overflowLines->mLines.splice(overflowLines->mLines.begin(), mLines,
5614 overBegin, LinesEnd());
5615 NS_ASSERTION(!overflowLines->mLines.empty(), "should not be empty");
5616 // this takes ownership but it won't delete it immediately so we
5617 // can keep using it.
5618 SetOverflowLines(overflowLines);
5620 // Mark all the overflow lines dirty so that they get reflowed when
5621 // they are pulled up by our next-in-flow.
5623 // XXXldb Can this get called O(N) times making the whole thing O(N^2)?
5624 for (LineIterator line = overflowLines->mLines.begin(),
5625 line_end = overflowLines->mLines.end();
5626 line != line_end; ++line) {
5627 line->MarkDirty();
5628 line->MarkPreviousMarginDirty();
5629 line->SetMovedFragments();
5630 line->SetBoundsEmpty();
5631 if (line->HasFloats()) {
5632 line->ClearFloats();
5638 #ifdef DEBUG
5639 VerifyOverflowSituation();
5640 #endif
5643 // The overflowLines property is stored as a pointer to a line list,
5644 // which must be deleted. However, the following functions all maintain
5645 // the invariant that the property is never set if the list is empty.
5647 bool nsBlockFrame::DrainOverflowLines() {
5648 #ifdef DEBUG
5649 VerifyOverflowSituation();
5650 #endif
5652 // Steal the prev-in-flow's overflow lines and prepend them.
5653 bool didFindOverflow = false;
5654 nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow());
5655 if (prevBlock) {
5656 prevBlock->ClearLineCursors();
5657 FrameLines* overflowLines = prevBlock->RemoveOverflowLines();
5658 if (overflowLines) {
5659 // Make all the frames on the overflow line list mine.
5660 ReparentFrames(overflowLines->mFrames, prevBlock, this);
5662 // Collect overflow containers from our OverflowContainers list that are
5663 // continuations from the frames we picked up from our prev-in-flow, then
5664 // prepend those to ExcessOverflowContainers to ensure the continuations
5665 // are ordered.
5666 if (GetOverflowContainers()) {
5667 nsFrameList ocContinuations;
5668 for (auto* f : overflowLines->mFrames) {
5669 auto* cont = f;
5670 bool done = false;
5671 while (!done && (cont = cont->GetNextContinuation()) &&
5672 cont->GetParent() == this) {
5673 bool onlyChild = !cont->GetPrevSibling() && !cont->GetNextSibling();
5674 if (cont->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER) &&
5675 TryRemoveFrame(OverflowContainersProperty(), cont)) {
5676 ocContinuations.AppendFrame(nullptr, cont);
5677 done = onlyChild;
5678 continue;
5680 break;
5682 if (done) {
5683 break;
5686 if (!ocContinuations.IsEmpty()) {
5687 if (nsFrameList* eoc = GetExcessOverflowContainers()) {
5688 eoc->InsertFrames(nullptr, nullptr, std::move(ocContinuations));
5689 } else {
5690 SetExcessOverflowContainers(std::move(ocContinuations));
5695 // Make the overflow out-of-flow frames mine too.
5696 nsAutoOOFFrameList oofs(prevBlock);
5697 if (oofs.mList.NotEmpty()) {
5698 // In case we own any next-in-flows of any of the drained frames, then
5699 // move those to the PushedFloat list.
5700 nsFrameList pushedFloats;
5701 for (nsIFrame* f : oofs.mList) {
5702 nsIFrame* nif = f->GetNextInFlow();
5703 for (; nif && nif->GetParent() == this; nif = nif->GetNextInFlow()) {
5704 MOZ_ASSERT(nif->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT));
5705 RemoveFloat(nif);
5706 pushedFloats.AppendFrame(nullptr, nif);
5709 ReparentFrames(oofs.mList, prevBlock, this);
5710 mFloats.InsertFrames(nullptr, nullptr, std::move(oofs.mList));
5711 if (!pushedFloats.IsEmpty()) {
5712 nsFrameList* pf = EnsurePushedFloats();
5713 pf->InsertFrames(nullptr, nullptr, std::move(pushedFloats));
5717 if (!mLines.empty()) {
5718 // Remember to recompute the margins on the first line. This will
5719 // also recompute the correct deltaBCoord if necessary.
5720 mLines.front()->MarkPreviousMarginDirty();
5722 // The overflow lines have already been marked dirty and their previous
5723 // margins marked dirty also.
5725 // Prepend the overflow frames/lines to our principal list.
5726 mFrames.InsertFrames(nullptr, nullptr, std::move(overflowLines->mFrames));
5727 mLines.splice(mLines.begin(), overflowLines->mLines);
5728 NS_ASSERTION(overflowLines->mLines.empty(), "splice should empty list");
5729 delete overflowLines;
5730 didFindOverflow = true;
5734 // Now append our own overflow lines.
5735 return DrainSelfOverflowList() || didFindOverflow;
5738 bool nsBlockFrame::DrainSelfOverflowList() {
5739 UniquePtr<FrameLines> ourOverflowLines(RemoveOverflowLines());
5740 if (!ourOverflowLines) {
5741 return false;
5744 // No need to reparent frames in our own overflow lines/oofs, because they're
5745 // already ours. But we should put overflow floats back in mFloats.
5746 // (explicit scope to remove the OOF list before VerifyOverflowSituation)
5748 nsAutoOOFFrameList oofs(this);
5749 if (oofs.mList.NotEmpty()) {
5750 #ifdef DEBUG
5751 for (nsIFrame* f : oofs.mList) {
5752 MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
5753 "CollectFloats should've removed that bit");
5755 #endif
5756 // The overflow floats go after our regular floats.
5757 mFloats.AppendFrames(nullptr, std::move(oofs).mList);
5760 if (!ourOverflowLines->mLines.empty()) {
5761 mFrames.AppendFrames(nullptr, std::move(ourOverflowLines->mFrames));
5762 mLines.splice(mLines.end(), ourOverflowLines->mLines);
5765 #ifdef DEBUG
5766 VerifyOverflowSituation();
5767 #endif
5768 return true;
5772 * Pushed floats are floats whose placeholders are in a previous
5773 * continuation. They might themselves be next-continuations of a float
5774 * that partially fit in an earlier continuation, or they might be the
5775 * first continuation of a float that couldn't be placed at all.
5777 * Pushed floats live permanently at the beginning of a block's float
5778 * list, where they must live *before* any floats whose placeholders are
5779 * in that block.
5781 * Temporarily, during reflow, they also live on the pushed floats list,
5782 * which only holds them between (a) when one continuation pushes them to
5783 * its pushed floats list because they don't fit and (b) when the next
5784 * continuation pulls them onto the beginning of its float list.
5786 * DrainPushedFloats sets up pushed floats the way we need them at the
5787 * start of reflow; they are then reflowed by ReflowPushedFloats (which
5788 * might push some of them on). Floats with placeholders in this block
5789 * are reflowed by (BlockReflowState/nsLineLayout)::AddFloat, which
5790 * also maintains these invariants.
5792 * DrainSelfPushedFloats moves any pushed floats from this block's own
5793 * PushedFloats list back into mFloats. DrainPushedFloats additionally
5794 * moves frames from its prev-in-flow's PushedFloats list into mFloats.
5796 void nsBlockFrame::DrainSelfPushedFloats() {
5797 // If we're getting reflowed multiple times without our
5798 // next-continuation being reflowed, we might need to pull back floats
5799 // that we just put in the list to be pushed to our next-in-flow.
5800 // We don't want to pull back any next-in-flows of floats on our own
5801 // float list, and we only need to pull back first-in-flows whose
5802 // placeholders were in earlier blocks (since first-in-flows whose
5803 // placeholders are in this block will get pulled appropriately by
5804 // AddFloat, and will then be more likely to be in the correct order).
5805 mozilla::PresShell* presShell = PresShell();
5806 nsFrameList* ourPushedFloats = GetPushedFloats();
5807 if (ourPushedFloats) {
5808 // When we pull back floats, we want to put them with the pushed
5809 // floats, which must live at the start of our float list, but we
5810 // want them at the end of those pushed floats.
5811 // FIXME: This isn't quite right! What if they're all pushed floats?
5812 nsIFrame* insertionPrevSibling = nullptr; /* beginning of list */
5813 for (nsIFrame* f = mFloats.FirstChild();
5814 f && f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT);
5815 f = f->GetNextSibling()) {
5816 insertionPrevSibling = f;
5819 nsIFrame* f = ourPushedFloats->LastChild();
5820 while (f) {
5821 nsIFrame* prevSibling = f->GetPrevSibling();
5823 nsPlaceholderFrame* placeholder = f->GetPlaceholderFrame();
5824 nsIFrame* floatOriginalParent =
5825 presShell->FrameConstructor()->GetFloatContainingBlock(placeholder);
5826 if (floatOriginalParent != this) {
5827 // This is a first continuation that was pushed from one of our
5828 // previous continuations. Take it out of the pushed floats
5829 // list and put it in our floats list, before any of our
5830 // floats, but after other pushed floats.
5831 ourPushedFloats->RemoveFrame(f);
5832 mFloats.InsertFrame(nullptr, insertionPrevSibling, f);
5835 f = prevSibling;
5838 if (ourPushedFloats->IsEmpty()) {
5839 RemovePushedFloats()->Delete(presShell);
5844 void nsBlockFrame::DrainPushedFloats() {
5845 DrainSelfPushedFloats();
5847 // After our prev-in-flow has completed reflow, it may have a pushed
5848 // floats list, containing floats that we need to own. Take these.
5849 nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow());
5850 if (prevBlock) {
5851 AutoFrameListPtr list(PresContext(), prevBlock->RemovePushedFloats());
5852 if (list && list->NotEmpty()) {
5853 mFloats.InsertFrames(this, nullptr, std::move(*list));
5858 nsBlockFrame::FrameLines* nsBlockFrame::GetOverflowLines() const {
5859 if (!HasOverflowLines()) {
5860 return nullptr;
5862 FrameLines* prop = GetProperty(OverflowLinesProperty());
5863 NS_ASSERTION(
5864 prop && !prop->mLines.empty() &&
5865 prop->mLines.front()->GetChildCount() == 0
5866 ? prop->mFrames.IsEmpty()
5867 : prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(),
5868 "value should always be stored and non-empty when state set");
5869 return prop;
5872 nsBlockFrame::FrameLines* nsBlockFrame::RemoveOverflowLines() {
5873 if (!HasOverflowLines()) {
5874 return nullptr;
5876 FrameLines* prop = TakeProperty(OverflowLinesProperty());
5877 NS_ASSERTION(
5878 prop && !prop->mLines.empty() &&
5879 prop->mLines.front()->GetChildCount() == 0
5880 ? prop->mFrames.IsEmpty()
5881 : prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(),
5882 "value should always be stored and non-empty when state set");
5883 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
5884 return prop;
5887 void nsBlockFrame::DestroyOverflowLines() {
5888 NS_ASSERTION(HasOverflowLines(), "huh?");
5889 FrameLines* prop = TakeProperty(OverflowLinesProperty());
5890 NS_ASSERTION(prop && prop->mLines.empty(),
5891 "value should always be stored but empty when destroying");
5892 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
5893 delete prop;
5896 // This takes ownership of aOverflowLines.
5897 // XXX We should allocate overflowLines from presShell arena!
5898 void nsBlockFrame::SetOverflowLines(FrameLines* aOverflowLines) {
5899 NS_ASSERTION(aOverflowLines, "null lines");
5900 NS_ASSERTION(!aOverflowLines->mLines.empty(), "empty lines");
5901 NS_ASSERTION(aOverflowLines->mLines.front()->mFirstChild ==
5902 aOverflowLines->mFrames.FirstChild(),
5903 "invalid overflow lines / frames");
5904 NS_ASSERTION(!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_LINES),
5905 "Overwriting existing overflow lines");
5907 // Verify that we won't overwrite an existing overflow list
5908 NS_ASSERTION(!GetProperty(OverflowLinesProperty()), "existing overflow list");
5909 SetProperty(OverflowLinesProperty(), aOverflowLines);
5910 AddStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
5913 nsFrameList* nsBlockFrame::GetOverflowOutOfFlows() const {
5914 if (!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
5915 return nullptr;
5917 nsFrameList* result = GetProperty(OverflowOutOfFlowsProperty());
5918 NS_ASSERTION(result, "value should always be non-empty when state set");
5919 return result;
5922 void nsBlockFrame::SetOverflowOutOfFlows(nsFrameList&& aList,
5923 nsFrameList* aPropValue) {
5924 MOZ_ASSERT(
5925 HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS) == !!aPropValue,
5926 "state does not match value");
5928 if (aList.IsEmpty()) {
5929 if (!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
5930 return;
5932 nsFrameList* list = TakeProperty(OverflowOutOfFlowsProperty());
5933 NS_ASSERTION(aPropValue == list, "prop value mismatch");
5934 list->Clear();
5935 list->Delete(PresShell());
5936 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
5937 } else if (HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
5938 NS_ASSERTION(aPropValue == GetProperty(OverflowOutOfFlowsProperty()),
5939 "prop value mismatch");
5940 *aPropValue = std::move(aList);
5941 } else {
5942 SetProperty(OverflowOutOfFlowsProperty(),
5943 new (PresShell()) nsFrameList(std::move(aList)));
5944 AddStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
5948 nsIFrame* nsBlockFrame::GetInsideMarker() const {
5949 if (!HasInsideMarker()) {
5950 return nullptr;
5952 NS_ASSERTION(!HasOutsideMarker(), "invalid marker state");
5953 nsIFrame* frame = GetProperty(InsideMarkerProperty());
5954 NS_ASSERTION(frame, "bogus inside ::marker frame");
5955 return frame;
5958 nsIFrame* nsBlockFrame::GetOutsideMarker() const {
5959 nsFrameList* list = GetOutsideMarkerList();
5960 return list ? list->FirstChild() : nullptr;
5963 nsFrameList* nsBlockFrame::GetOutsideMarkerList() const {
5964 if (!HasOutsideMarker()) {
5965 return nullptr;
5967 NS_ASSERTION(!HasInsideMarker(), "invalid marker state");
5968 nsFrameList* list = GetProperty(OutsideMarkerProperty());
5969 NS_ASSERTION(list && list->GetLength() == 1, "bogus outside ::marker list");
5970 return list;
5973 nsFrameList* nsBlockFrame::GetPushedFloats() const {
5974 if (!HasPushedFloats()) {
5975 return nullptr;
5977 nsFrameList* result = GetProperty(PushedFloatProperty());
5978 NS_ASSERTION(result, "value should always be non-empty when state set");
5979 return result;
5982 nsFrameList* nsBlockFrame::EnsurePushedFloats() {
5983 nsFrameList* result = GetPushedFloats();
5984 if (result) {
5985 return result;
5988 result = new (PresShell()) nsFrameList;
5989 SetProperty(PushedFloatProperty(), result);
5990 AddStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
5992 return result;
5995 nsFrameList* nsBlockFrame::RemovePushedFloats() {
5996 if (!HasPushedFloats()) {
5997 return nullptr;
5999 nsFrameList* result = TakeProperty(PushedFloatProperty());
6000 RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
6001 NS_ASSERTION(result, "value should always be non-empty when state set");
6002 return result;
6005 //////////////////////////////////////////////////////////////////////
6006 // Frame list manipulation routines
6008 void nsBlockFrame::AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) {
6009 if (aFrameList.IsEmpty()) {
6010 return;
6012 if (aListID != FrameChildListID::Principal) {
6013 if (FrameChildListID::Float == aListID) {
6014 DrainSelfPushedFloats(); // ensure the last frame is in mFloats
6015 mFloats.AppendFrames(nullptr, std::move(aFrameList));
6016 return;
6018 MOZ_ASSERT(FrameChildListID::NoReflowPrincipal == aListID,
6019 "unexpected child list");
6022 // Find the proper last-child for where the append should go
6023 nsIFrame* lastKid = mFrames.LastChild();
6024 NS_ASSERTION(
6025 (mLines.empty() ? nullptr : mLines.back()->LastChild()) == lastKid,
6026 "out-of-sync mLines / mFrames");
6028 #ifdef NOISY_REFLOW_REASON
6029 ListTag(stdout);
6030 printf(": append ");
6031 for (nsIFrame* frame : aFrameList) {
6032 frame->ListTag(stdout);
6034 if (lastKid) {
6035 printf(" after ");
6036 lastKid->ListTag(stdout);
6038 printf("\n");
6039 #endif
6041 if (IsInSVGTextSubtree()) {
6042 MOZ_ASSERT(GetParent()->IsSVGTextFrame(),
6043 "unexpected block frame in SVG text");
6044 // Workaround for bug 1399425 in case this bit has been removed from the
6045 // SVGTextFrame just before the parser adds more descendant nodes.
6046 GetParent()->AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY);
6049 AddFrames(std::move(aFrameList), lastKid, nullptr);
6050 if (aListID != FrameChildListID::NoReflowPrincipal) {
6051 PresShell()->FrameNeedsReflow(
6052 this, IntrinsicDirty::FrameAndAncestors,
6053 NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
6057 void nsBlockFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
6058 const nsLineList::iterator* aPrevFrameLine,
6059 nsFrameList&& aFrameList) {
6060 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
6061 "inserting after sibling frame with different parent");
6063 if (aListID != FrameChildListID::Principal) {
6064 if (FrameChildListID::Float == aListID) {
6065 DrainSelfPushedFloats(); // ensure aPrevFrame is in mFloats
6066 mFloats.InsertFrames(this, aPrevFrame, std::move(aFrameList));
6067 return;
6069 MOZ_ASSERT(FrameChildListID::NoReflowPrincipal == aListID,
6070 "unexpected child list");
6073 #ifdef NOISY_REFLOW_REASON
6074 ListTag(stdout);
6075 printf(": insert ");
6076 for (nsIFrame* frame : aFrameList) {
6077 frame->ListTag(stdout);
6079 if (aPrevFrame) {
6080 printf(" after ");
6081 aPrevFrame->ListTag(stdout);
6083 printf("\n");
6084 #endif
6086 AddFrames(std::move(aFrameList), aPrevFrame, aPrevFrameLine);
6087 if (aListID != FrameChildListID::NoReflowPrincipal) {
6088 PresShell()->FrameNeedsReflow(
6089 this, IntrinsicDirty::FrameAndAncestors,
6090 NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
6094 void nsBlockFrame::RemoveFrame(DestroyContext& aContext, ChildListID aListID,
6095 nsIFrame* aOldFrame) {
6096 #ifdef NOISY_REFLOW_REASON
6097 ListTag(stdout);
6098 printf(": remove ");
6099 aOldFrame->ListTag(stdout);
6100 printf("\n");
6101 #endif
6103 if (aListID == FrameChildListID::Principal) {
6104 bool hasFloats = BlockHasAnyFloats(aOldFrame);
6105 DoRemoveFrame(aContext, aOldFrame, REMOVE_FIXED_CONTINUATIONS);
6106 if (hasFloats) {
6107 MarkSameFloatManagerLinesDirty(this);
6109 } else if (FrameChildListID::Float == aListID) {
6110 // Make sure to mark affected lines dirty for the float frame
6111 // we are removing; this way is a bit messy, but so is the rest of the code.
6112 // See bug 390762.
6113 NS_ASSERTION(!aOldFrame->GetPrevContinuation(),
6114 "RemoveFrame should not be called on pushed floats.");
6115 for (nsIFrame* f = aOldFrame;
6116 f && !f->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
6117 f = f->GetNextContinuation()) {
6118 MarkSameFloatManagerLinesDirty(
6119 static_cast<nsBlockFrame*>(f->GetParent()));
6121 DoRemoveOutOfFlowFrame(aContext, aOldFrame);
6122 } else if (FrameChildListID::NoReflowPrincipal == aListID) {
6123 // Skip the call to |FrameNeedsReflow| below by returning now.
6124 DoRemoveFrame(aContext, aOldFrame, REMOVE_FIXED_CONTINUATIONS);
6125 return;
6126 } else {
6127 MOZ_CRASH("unexpected child list");
6130 PresShell()->FrameNeedsReflow(
6131 this, IntrinsicDirty::FrameAndAncestors,
6132 NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
6135 static bool ShouldPutNextSiblingOnNewLine(nsIFrame* aLastFrame) {
6136 LayoutFrameType type = aLastFrame->Type();
6137 if (type == LayoutFrameType::Br) {
6138 return true;
6140 // XXX the TEXT_OFFSETS_NEED_FIXING check is a wallpaper for bug 822910.
6141 if (type == LayoutFrameType::Text &&
6142 !aLastFrame->HasAnyStateBits(TEXT_OFFSETS_NEED_FIXING)) {
6143 return aLastFrame->HasSignificantTerminalNewline();
6145 return false;
6148 void nsBlockFrame::AddFrames(nsFrameList&& aFrameList, nsIFrame* aPrevSibling,
6149 const nsLineList::iterator* aPrevSiblingLine) {
6150 // Clear our line cursor, since our lines may change.
6151 ClearLineCursors();
6153 if (aFrameList.IsEmpty()) {
6154 return;
6157 // Attempt to find the line that contains the previous sibling
6158 nsLineList* lineList = &mLines;
6159 nsFrameList* frames = &mFrames;
6160 nsLineList::iterator prevSibLine;
6161 int32_t prevSiblingIndex;
6162 if (aPrevSiblingLine) {
6163 MOZ_ASSERT(aPrevSibling);
6164 prevSibLine = *aPrevSiblingLine;
6165 FrameLines* overflowLines = GetOverflowLines();
6166 MOZ_ASSERT(prevSibLine.IsInSameList(mLines.begin()) ||
6167 (overflowLines &&
6168 prevSibLine.IsInSameList(overflowLines->mLines.begin())),
6169 "must be one of our line lists");
6170 if (overflowLines) {
6171 // We need to find out which list it's actually in. Assume that
6172 // *if* we have overflow lines, that our primary lines aren't
6173 // huge, but our overflow lines might be.
6174 nsLineList::iterator line = mLines.begin(), lineEnd = mLines.end();
6175 while (line != lineEnd) {
6176 if (line == prevSibLine) {
6177 break;
6179 ++line;
6181 if (line == lineEnd) {
6182 // By elimination, the line must be in our overflow lines.
6183 lineList = &overflowLines->mLines;
6184 frames = &overflowLines->mFrames;
6188 nsLineList::iterator nextLine = prevSibLine.next();
6189 nsIFrame* lastFrameInLine = nextLine == lineList->end()
6190 ? frames->LastChild()
6191 : nextLine->mFirstChild->GetPrevSibling();
6192 prevSiblingIndex = prevSibLine->RIndexOf(aPrevSibling, lastFrameInLine);
6193 MOZ_ASSERT(prevSiblingIndex >= 0,
6194 "aPrevSibling must be in aPrevSiblingLine");
6195 } else {
6196 prevSibLine = lineList->end();
6197 prevSiblingIndex = -1;
6198 if (aPrevSibling) {
6199 // XXX_perf This is technically O(N^2) in some cases, but by using
6200 // RFind instead of Find, we make it O(N) in the most common case,
6201 // which is appending content.
6203 // Find the line that contains the previous sibling
6204 if (!nsLineBox::RFindLineContaining(aPrevSibling, lineList->begin(),
6205 prevSibLine, mFrames.LastChild(),
6206 &prevSiblingIndex)) {
6207 // Not in mLines - try overflow lines.
6208 FrameLines* overflowLines = GetOverflowLines();
6209 bool found = false;
6210 if (overflowLines) {
6211 prevSibLine = overflowLines->mLines.end();
6212 prevSiblingIndex = -1;
6213 found = nsLineBox::RFindLineContaining(
6214 aPrevSibling, overflowLines->mLines.begin(), prevSibLine,
6215 overflowLines->mFrames.LastChild(), &prevSiblingIndex);
6217 if (MOZ_LIKELY(found)) {
6218 lineList = &overflowLines->mLines;
6219 frames = &overflowLines->mFrames;
6220 } else {
6221 // Note: defensive code! RFindLineContaining must not return
6222 // false in this case, so if it does...
6223 MOZ_ASSERT_UNREACHABLE("prev sibling not in line list");
6224 aPrevSibling = nullptr;
6225 prevSibLine = lineList->end();
6231 // Find the frame following aPrevSibling so that we can join up the
6232 // two lists of frames.
6233 if (aPrevSibling) {
6234 // Split line containing aPrevSibling in two if the insertion
6235 // point is somewhere in the middle of the line.
6236 int32_t rem = prevSibLine->GetChildCount() - prevSiblingIndex - 1;
6237 if (rem) {
6238 // Split the line in two where the frame(s) are being inserted.
6239 nsLineBox* line =
6240 NewLineBox(prevSibLine, aPrevSibling->GetNextSibling(), rem);
6241 lineList->after_insert(prevSibLine, line);
6242 // Mark prevSibLine dirty and as needing textrun invalidation, since
6243 // we may be breaking up text in the line. Its previous line may also
6244 // need to be invalidated because it may be able to pull some text up.
6245 MarkLineDirty(prevSibLine, lineList);
6246 // The new line will also need its textruns recomputed because of the
6247 // frame changes.
6248 line->MarkDirty();
6249 line->SetInvalidateTextRuns(true);
6251 } else if (!lineList->empty()) {
6252 lineList->front()->MarkDirty();
6253 lineList->front()->SetInvalidateTextRuns(true);
6255 const nsFrameList::Slice& newFrames =
6256 frames->InsertFrames(nullptr, aPrevSibling, std::move(aFrameList));
6258 // Walk through the new frames being added and update the line data
6259 // structures to fit.
6260 for (nsIFrame* newFrame : newFrames) {
6261 NS_ASSERTION(!aPrevSibling || aPrevSibling->GetNextSibling() == newFrame,
6262 "Unexpected aPrevSibling");
6263 NS_ASSERTION(
6264 !newFrame->IsPlaceholderFrame() ||
6265 (!newFrame->IsAbsolutelyPositioned() && !newFrame->IsFloating()),
6266 "Placeholders should not float or be positioned");
6268 bool isBlock = newFrame->IsBlockOutside();
6270 // If the frame is a block frame, or if there is no previous line or if the
6271 // previous line is a block line we need to make a new line. We also make
6272 // a new line, as an optimization, in the two cases we know we'll need it:
6273 // if the previous line ended with a <br>, or if it has significant
6274 // whitespace and ended in a newline.
6275 if (isBlock || prevSibLine == lineList->end() || prevSibLine->IsBlock() ||
6276 (aPrevSibling && ShouldPutNextSiblingOnNewLine(aPrevSibling))) {
6277 // Create a new line for the frame and add its line to the line
6278 // list.
6279 nsLineBox* line = NewLineBox(newFrame, isBlock);
6280 if (prevSibLine != lineList->end()) {
6281 // Append new line after prevSibLine
6282 lineList->after_insert(prevSibLine, line);
6283 ++prevSibLine;
6284 } else {
6285 // New line is going before the other lines
6286 lineList->push_front(line);
6287 prevSibLine = lineList->begin();
6289 } else {
6290 prevSibLine->NoteFrameAdded(newFrame);
6291 // We're adding inline content to prevSibLine, so we need to mark it
6292 // dirty, ensure its textruns are recomputed, and possibly do the same
6293 // to its previous line since that line may be able to pull content up.
6294 MarkLineDirty(prevSibLine, lineList);
6297 aPrevSibling = newFrame;
6300 #ifdef DEBUG
6301 MOZ_ASSERT(aFrameList.IsEmpty());
6302 VerifyLines(true);
6303 #endif
6306 nsContainerFrame* nsBlockFrame::GetRubyContentPseudoFrame() {
6307 auto* firstChild = PrincipalChildList().FirstChild();
6308 if (firstChild && firstChild->IsRubyFrame() &&
6309 firstChild->Style()->GetPseudoType() ==
6310 mozilla::PseudoStyleType::blockRubyContent) {
6311 return static_cast<nsContainerFrame*>(firstChild);
6313 return nullptr;
6316 nsContainerFrame* nsBlockFrame::GetContentInsertionFrame() {
6317 // 'display:block ruby' use the inner (Ruby) frame for insertions.
6318 if (auto* rubyContentPseudoFrame = GetRubyContentPseudoFrame()) {
6319 return rubyContentPseudoFrame;
6321 return this;
6324 void nsBlockFrame::AppendDirectlyOwnedAnonBoxes(
6325 nsTArray<OwnedAnonBox>& aResult) {
6326 if (auto* rubyContentPseudoFrame = GetRubyContentPseudoFrame()) {
6327 aResult.AppendElement(OwnedAnonBox(rubyContentPseudoFrame));
6331 void nsBlockFrame::RemoveFloatFromFloatCache(nsIFrame* aFloat) {
6332 // Find which line contains the float, so we can update
6333 // the float cache.
6334 for (auto& line : Lines()) {
6335 if (line.IsInline() && line.RemoveFloat(aFloat)) {
6336 break;
6341 void nsBlockFrame::RemoveFloat(nsIFrame* aFloat) {
6342 #ifdef DEBUG
6343 // Floats live in mFloats, or in the PushedFloat or OverflowOutOfFlows
6344 // frame list properties.
6345 if (!mFloats.ContainsFrame(aFloat)) {
6346 MOZ_ASSERT(
6347 (GetOverflowOutOfFlows() &&
6348 GetOverflowOutOfFlows()->ContainsFrame(aFloat)) ||
6349 (GetPushedFloats() && GetPushedFloats()->ContainsFrame(aFloat)),
6350 "aFloat is not our child or on an unexpected frame list");
6352 #endif
6354 if (mFloats.StartRemoveFrame(aFloat)) {
6355 return;
6358 nsFrameList* list = GetPushedFloats();
6359 if (list && list->ContinueRemoveFrame(aFloat)) {
6360 #if 0
6361 // XXXmats not yet - need to investigate BlockReflowState::mPushedFloats
6362 // first so we don't leave it pointing to a deleted list.
6363 if (list->IsEmpty()) {
6364 delete RemovePushedFloats();
6366 #endif
6367 return;
6371 nsAutoOOFFrameList oofs(this);
6372 if (oofs.mList.ContinueRemoveFrame(aFloat)) {
6373 return;
6378 void nsBlockFrame::DoRemoveOutOfFlowFrame(DestroyContext& aContext,
6379 nsIFrame* aFrame) {
6380 // The containing block is always the parent of aFrame.
6381 nsBlockFrame* block = (nsBlockFrame*)aFrame->GetParent();
6383 // Remove aFrame from the appropriate list.
6384 if (aFrame->IsAbsolutelyPositioned()) {
6385 // This also deletes the next-in-flows
6386 block->GetAbsoluteContainingBlock()->RemoveFrame(
6387 aContext, FrameChildListID::Absolute, aFrame);
6388 } else {
6389 // First remove aFrame's next-in-flows.
6390 if (nsIFrame* nif = aFrame->GetNextInFlow()) {
6391 nif->GetParent()->DeleteNextInFlowChild(aContext, nif, false);
6393 // Now remove aFrame from its child list and Destroy it.
6394 block->RemoveFloatFromFloatCache(aFrame);
6395 block->RemoveFloat(aFrame);
6396 aFrame->Destroy(aContext);
6401 * This helps us iterate over the list of all normal + overflow lines
6403 void nsBlockFrame::TryAllLines(nsLineList::iterator* aIterator,
6404 nsLineList::iterator* aStartIterator,
6405 nsLineList::iterator* aEndIterator,
6406 bool* aInOverflowLines,
6407 FrameLines** aOverflowLines) {
6408 if (*aIterator == *aEndIterator) {
6409 if (!*aInOverflowLines) {
6410 // Try the overflow lines
6411 *aInOverflowLines = true;
6412 FrameLines* lines = GetOverflowLines();
6413 if (lines) {
6414 *aStartIterator = lines->mLines.begin();
6415 *aIterator = *aStartIterator;
6416 *aEndIterator = lines->mLines.end();
6417 *aOverflowLines = lines;
6423 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
6424 LineIterator aLine)
6425 : mFrame(aFrame), mLine(aLine), mLineList(&aFrame->mLines) {
6426 // This will assert if aLine isn't in mLines of aFrame:
6427 DebugOnly<bool> check = aLine == mFrame->LinesBegin();
6430 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
6431 LineIterator aLine,
6432 bool aInOverflow)
6433 : mFrame(aFrame),
6434 mLine(aLine),
6435 mLineList(aInOverflow ? &aFrame->GetOverflowLines()->mLines
6436 : &aFrame->mLines) {}
6438 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
6439 bool* aFoundValidLine)
6440 : mFrame(aFrame), mLineList(&aFrame->mLines) {
6441 mLine = aFrame->LinesBegin();
6442 *aFoundValidLine = FindValidLine();
6445 static bool StyleEstablishesBFC(const ComputedStyle* aStyle) {
6446 // paint/layout containment boxes and multi-column containers establish an
6447 // independent formatting context.
6448 // https://drafts.csswg.org/css-contain/#containment-paint
6449 // https://drafts.csswg.org/css-contain/#containment-layout
6450 // https://drafts.csswg.org/css-multicol/#columns
6451 return aStyle->StyleDisplay()->IsContainPaint() ||
6452 aStyle->StyleDisplay()->IsContainLayout() ||
6453 aStyle->GetPseudoType() == PseudoStyleType::columnContent;
6456 void nsBlockFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
6457 nsContainerFrame::DidSetComputedStyle(aOldStyle);
6458 if (!aOldStyle) {
6459 return;
6462 // If NS_BLOCK_STATIC_BFC flag was set when the frame was initialized, it
6463 // remains set during the lifetime of the frame and always forces it to be
6464 // treated as a BFC, independently of the value of NS_BLOCK_DYNAMIC_BFC.
6465 // Consequently, we don't bother invalidating or updating that latter flag.
6466 if (HasAnyStateBits(NS_BLOCK_STATIC_BFC)) {
6467 return;
6470 bool isBFC = StyleEstablishesBFC(Style());
6471 if (StyleEstablishesBFC(aOldStyle) != isBFC) {
6472 if (MaybeHasFloats()) {
6473 // If the frame contains floats, this update may change their float
6474 // manager. Be safe by dirtying all descendant lines of the nearest
6475 // ancestor's float manager.
6476 RemoveStateBits(NS_BLOCK_DYNAMIC_BFC);
6477 MarkSameFloatManagerLinesDirty(this);
6479 AddOrRemoveStateBits(NS_BLOCK_DYNAMIC_BFC, isBFC);
6483 void nsBlockFrame::UpdateFirstLetterStyle(ServoRestyleState& aRestyleState) {
6484 nsIFrame* letterFrame = GetFirstLetter();
6485 if (!letterFrame) {
6486 return;
6489 // Figure out what the right style parent is. This needs to match
6490 // nsCSSFrameConstructor::CreateLetterFrame.
6491 nsIFrame* inFlowFrame = letterFrame;
6492 if (inFlowFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
6493 inFlowFrame = inFlowFrame->GetPlaceholderFrame();
6495 nsIFrame* styleParent = CorrectStyleParentFrame(inFlowFrame->GetParent(),
6496 PseudoStyleType::firstLetter);
6497 ComputedStyle* parentStyle = styleParent->Style();
6498 RefPtr<ComputedStyle> firstLetterStyle =
6499 aRestyleState.StyleSet().ResolvePseudoElementStyle(
6500 *mContent->AsElement(), PseudoStyleType::firstLetter, nullptr,
6501 parentStyle);
6502 // Note that we don't need to worry about changehints for the continuation
6503 // styles: those will be handled by the styleParent already.
6504 RefPtr<ComputedStyle> continuationStyle =
6505 aRestyleState.StyleSet().ResolveStyleForFirstLetterContinuation(
6506 parentStyle);
6507 UpdateStyleOfOwnedChildFrame(letterFrame, firstLetterStyle, aRestyleState,
6508 Some(continuationStyle.get()));
6510 // We also want to update the style on the textframe inside the first-letter.
6511 // We don't need to compute a changehint for this, though, since any changes
6512 // to it are handled by the first-letter anyway.
6513 nsIFrame* textFrame = letterFrame->PrincipalChildList().FirstChild();
6514 RefPtr<ComputedStyle> firstTextStyle =
6515 aRestyleState.StyleSet().ResolveStyleForText(textFrame->GetContent(),
6516 firstLetterStyle);
6517 textFrame->SetComputedStyle(firstTextStyle);
6519 // We don't need to update style for textFrame's continuations: it's already
6520 // set up to inherit from parentStyle, which is what we want.
6523 static nsIFrame* FindChildContaining(nsBlockFrame* aFrame,
6524 nsIFrame* aFindFrame) {
6525 NS_ASSERTION(aFrame, "must have frame");
6526 nsIFrame* child;
6527 while (true) {
6528 nsIFrame* block = aFrame;
6529 do {
6530 child = nsLayoutUtils::FindChildContainingDescendant(block, aFindFrame);
6531 if (child) {
6532 break;
6534 block = block->GetNextContinuation();
6535 } while (block);
6536 if (!child) {
6537 return nullptr;
6539 if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
6540 break;
6542 aFindFrame = child->GetPlaceholderFrame();
6545 return child;
6548 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
6549 nsIFrame* aFindFrame,
6550 bool* aFoundValidLine)
6551 : mFrame(aFrame), mLineList(&aFrame->mLines) {
6552 *aFoundValidLine = false;
6554 nsIFrame* child = FindChildContaining(aFrame, aFindFrame);
6555 if (!child) {
6556 return;
6559 LineIterator line_end = aFrame->LinesEnd();
6560 mLine = aFrame->LinesBegin();
6561 if (mLine != line_end && mLine.next() == line_end &&
6562 !aFrame->HasOverflowLines()) {
6563 // The block has a single line - that must be it!
6564 *aFoundValidLine = true;
6565 return;
6568 // Try to use the cursor if it exists, otherwise fall back to the first line
6569 if (nsLineBox* const cursor = aFrame->GetLineCursorForQuery()) {
6570 mLine = line_end;
6571 // Perform a simultaneous forward and reverse search starting from the
6572 // line cursor.
6573 nsBlockFrame::LineIterator line = aFrame->LinesBeginFrom(cursor);
6574 nsBlockFrame::ReverseLineIterator rline = aFrame->LinesRBeginFrom(cursor);
6575 nsBlockFrame::ReverseLineIterator rline_end = aFrame->LinesREnd();
6576 // rline is positioned on the line containing 'cursor', so it's not
6577 // rline_end. So we can safely increment it (i.e. move it to one line
6578 // earlier) to start searching there.
6579 ++rline;
6580 while (line != line_end || rline != rline_end) {
6581 if (line != line_end) {
6582 if (line->Contains(child)) {
6583 mLine = line;
6584 break;
6586 ++line;
6588 if (rline != rline_end) {
6589 if (rline->Contains(child)) {
6590 mLine = rline;
6591 break;
6593 ++rline;
6596 if (mLine != line_end) {
6597 *aFoundValidLine = true;
6598 if (mLine != cursor) {
6599 aFrame->SetProperty(nsBlockFrame::LineCursorPropertyQuery(), mLine);
6601 return;
6603 } else {
6604 for (mLine = aFrame->LinesBegin(); mLine != line_end; ++mLine) {
6605 if (mLine->Contains(child)) {
6606 *aFoundValidLine = true;
6607 return;
6611 // Didn't find the line
6612 MOZ_ASSERT(mLine == line_end, "mLine should be line_end at this point");
6614 // If we reach here, it means that we have not been able to find the
6615 // desired frame in our in-flow lines. So we should start looking at
6616 // our overflow lines. In order to do that, we set mLine to the end
6617 // iterator so that FindValidLine starts to look at overflow lines,
6618 // if any.
6620 if (!FindValidLine()) {
6621 return;
6624 do {
6625 if (mLine->Contains(child)) {
6626 *aFoundValidLine = true;
6627 return;
6629 } while (Next());
6632 nsBlockFrame::LineIterator nsBlockInFlowLineIterator::End() {
6633 return mLineList->end();
6636 bool nsBlockInFlowLineIterator::IsLastLineInList() {
6637 LineIterator end = End();
6638 return mLine != end && mLine.next() == end;
6641 bool nsBlockInFlowLineIterator::Next() {
6642 ++mLine;
6643 return FindValidLine();
6646 bool nsBlockInFlowLineIterator::Prev() {
6647 LineIterator begin = mLineList->begin();
6648 if (mLine != begin) {
6649 --mLine;
6650 return true;
6652 bool currentlyInOverflowLines = GetInOverflow();
6653 while (true) {
6654 if (currentlyInOverflowLines) {
6655 mLineList = &mFrame->mLines;
6656 mLine = mLineList->end();
6657 if (mLine != mLineList->begin()) {
6658 --mLine;
6659 return true;
6661 } else {
6662 mFrame = static_cast<nsBlockFrame*>(mFrame->GetPrevInFlow());
6663 if (!mFrame) {
6664 return false;
6666 nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines();
6667 if (overflowLines) {
6668 mLineList = &overflowLines->mLines;
6669 mLine = mLineList->end();
6670 NS_ASSERTION(mLine != mLineList->begin(), "empty overflow line list?");
6671 --mLine;
6672 return true;
6675 currentlyInOverflowLines = !currentlyInOverflowLines;
6679 bool nsBlockInFlowLineIterator::FindValidLine() {
6680 LineIterator end = mLineList->end();
6681 if (mLine != end) {
6682 return true;
6684 bool currentlyInOverflowLines = GetInOverflow();
6685 while (true) {
6686 if (currentlyInOverflowLines) {
6687 mFrame = static_cast<nsBlockFrame*>(mFrame->GetNextInFlow());
6688 if (!mFrame) {
6689 return false;
6691 mLineList = &mFrame->mLines;
6692 mLine = mLineList->begin();
6693 if (mLine != mLineList->end()) {
6694 return true;
6696 } else {
6697 nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines();
6698 if (overflowLines) {
6699 mLineList = &overflowLines->mLines;
6700 mLine = mLineList->begin();
6701 NS_ASSERTION(mLine != mLineList->end(), "empty overflow line list?");
6702 return true;
6705 currentlyInOverflowLines = !currentlyInOverflowLines;
6709 // This function removes aDeletedFrame and all its continuations. It
6710 // is optimized for deleting a whole series of frames. The easy
6711 // implementation would invoke itself recursively on
6712 // aDeletedFrame->GetNextContinuation, then locate the line containing
6713 // aDeletedFrame and remove aDeletedFrame from that line. But here we
6714 // start by locating aDeletedFrame and then scanning from that point
6715 // on looking for continuations.
6716 void nsBlockFrame::DoRemoveFrame(DestroyContext& aContext,
6717 nsIFrame* aDeletedFrame, uint32_t aFlags) {
6718 // Clear our line cursor, since our lines may change.
6719 ClearLineCursors();
6721 if (aDeletedFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW |
6722 NS_FRAME_IS_OVERFLOW_CONTAINER)) {
6723 if (!aDeletedFrame->GetPrevInFlow()) {
6724 NS_ASSERTION(aDeletedFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
6725 "Expected out-of-flow frame");
6726 DoRemoveOutOfFlowFrame(aContext, aDeletedFrame);
6727 } else {
6728 // FIXME(emilio): aContext is lost here, maybe it's not a big deal?
6729 nsContainerFrame::DeleteNextInFlowChild(aContext, aDeletedFrame,
6730 (aFlags & FRAMES_ARE_EMPTY) != 0);
6732 return;
6735 // Find the line that contains deletedFrame
6736 nsLineList::iterator line_start = mLines.begin(), line_end = mLines.end();
6737 nsLineList::iterator line = line_start;
6738 FrameLines* overflowLines = nullptr;
6739 bool searchingOverflowList = false;
6740 // Make sure we look in the overflow lines even if the normal line
6741 // list is empty
6742 TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
6743 &overflowLines);
6744 while (line != line_end) {
6745 if (line->Contains(aDeletedFrame)) {
6746 break;
6748 ++line;
6749 TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
6750 &overflowLines);
6753 if (line == line_end) {
6754 NS_ERROR("can't find deleted frame in lines");
6755 return;
6758 if (!(aFlags & FRAMES_ARE_EMPTY)) {
6759 if (line != line_start) {
6760 line.prev()->MarkDirty();
6761 line.prev()->SetInvalidateTextRuns(true);
6762 } else if (searchingOverflowList && !mLines.empty()) {
6763 mLines.back()->MarkDirty();
6764 mLines.back()->SetInvalidateTextRuns(true);
6768 while (line != line_end && aDeletedFrame) {
6769 MOZ_ASSERT(this == aDeletedFrame->GetParent(), "messed up delete code");
6770 MOZ_ASSERT(line->Contains(aDeletedFrame), "frame not in line");
6772 if (!(aFlags & FRAMES_ARE_EMPTY)) {
6773 line->MarkDirty();
6774 line->SetInvalidateTextRuns(true);
6777 // If the frame being deleted is the last one on the line then
6778 // optimize away the line->Contains(next-in-flow) call below.
6779 bool isLastFrameOnLine = 1 == line->GetChildCount();
6780 if (!isLastFrameOnLine) {
6781 LineIterator next = line.next();
6782 nsIFrame* lastFrame =
6783 next != line_end
6784 ? next->mFirstChild->GetPrevSibling()
6785 : (searchingOverflowList ? overflowLines->mFrames.LastChild()
6786 : mFrames.LastChild());
6787 NS_ASSERTION(next == line_end || lastFrame == line->LastChild(),
6788 "unexpected line frames");
6789 isLastFrameOnLine = lastFrame == aDeletedFrame;
6792 // Remove aDeletedFrame from the line
6793 if (line->mFirstChild == aDeletedFrame) {
6794 // We should be setting this to null if aDeletedFrame
6795 // is the only frame on the line. HOWEVER in that case
6796 // we will be removing the line anyway, see below.
6797 line->mFirstChild = aDeletedFrame->GetNextSibling();
6800 // Hmm, this won't do anything if we're removing a frame in the first
6801 // overflow line... Hopefully doesn't matter
6802 --line;
6803 if (line != line_end && !line->IsBlock()) {
6804 // Since we just removed a frame that follows some inline
6805 // frames, we need to reflow the previous line.
6806 line->MarkDirty();
6808 ++line;
6810 // Take aDeletedFrame out of the sibling list. Note that
6811 // prevSibling will only be nullptr when we are deleting the very
6812 // first frame in the main or overflow list.
6813 if (searchingOverflowList) {
6814 overflowLines->mFrames.RemoveFrame(aDeletedFrame);
6815 } else {
6816 mFrames.RemoveFrame(aDeletedFrame);
6819 // Update the child count of the line to be accurate
6820 line->NoteFrameRemoved(aDeletedFrame);
6822 // Destroy frame; capture its next continuation first in case we need
6823 // to destroy that too.
6824 nsIFrame* deletedNextContinuation =
6825 (aFlags & REMOVE_FIXED_CONTINUATIONS)
6826 ? aDeletedFrame->GetNextContinuation()
6827 : aDeletedFrame->GetNextInFlow();
6828 #ifdef NOISY_REMOVE_FRAME
6829 printf("DoRemoveFrame: %s line=%p frame=",
6830 searchingOverflowList ? "overflow" : "normal", line.get());
6831 aDeletedFrame->ListTag(stdout);
6832 printf(" prevSibling=%p deletedNextContinuation=%p\n",
6833 aDeletedFrame->GetPrevSibling(), deletedNextContinuation);
6834 #endif
6836 // If next-in-flow is an overflow container, must remove it first.
6837 // FIXME: Can we do this unconditionally?
6838 if (deletedNextContinuation && deletedNextContinuation->HasAnyStateBits(
6839 NS_FRAME_IS_OVERFLOW_CONTAINER)) {
6840 deletedNextContinuation->GetParent()->DeleteNextInFlowChild(
6841 aContext, deletedNextContinuation, false);
6842 deletedNextContinuation = nullptr;
6845 aDeletedFrame->Destroy(aContext);
6846 aDeletedFrame = deletedNextContinuation;
6848 bool haveAdvancedToNextLine = false;
6849 // If line is empty, remove it now.
6850 if (0 == line->GetChildCount()) {
6851 #ifdef NOISY_REMOVE_FRAME
6852 printf("DoRemoveFrame: %s line=%p became empty so it will be removed\n",
6853 searchingOverflowList ? "overflow" : "normal", line.get());
6854 #endif
6855 nsLineBox* cur = line;
6856 if (!searchingOverflowList) {
6857 line = mLines.erase(line);
6858 // Invalidate the space taken up by the line.
6859 // XXX We need to do this if we're removing a frame as a result of
6860 // a call to RemoveFrame(), but we may not need to do this in all
6861 // cases...
6862 #ifdef NOISY_BLOCK_INVALIDATE
6863 nsRect inkOverflow(cur->InkOverflowRect());
6864 printf("%p invalidate 10 (%d, %d, %d, %d)\n", this, inkOverflow.x,
6865 inkOverflow.y, inkOverflow.width, inkOverflow.height);
6866 #endif
6867 } else {
6868 line = overflowLines->mLines.erase(line);
6869 if (overflowLines->mLines.empty()) {
6870 DestroyOverflowLines();
6871 overflowLines = nullptr;
6872 // We just invalidated our iterators. Since we were in
6873 // the overflow lines list, which is now empty, set them
6874 // so we're at the end of the regular line list.
6875 line_start = mLines.begin();
6876 line_end = mLines.end();
6877 line = line_end;
6880 FreeLineBox(cur);
6882 // If we're removing a line, ReflowDirtyLines isn't going to
6883 // know that it needs to slide lines unless something is marked
6884 // dirty. So mark the previous margin of the next line dirty if
6885 // there is one.
6886 if (line != line_end) {
6887 line->MarkPreviousMarginDirty();
6889 haveAdvancedToNextLine = true;
6890 } else {
6891 // Make the line that just lost a frame dirty, and advance to
6892 // the next line.
6893 if (!deletedNextContinuation || isLastFrameOnLine ||
6894 !line->Contains(deletedNextContinuation)) {
6895 line->MarkDirty();
6896 ++line;
6897 haveAdvancedToNextLine = true;
6901 if (deletedNextContinuation) {
6902 // See if we should keep looking in the current flow's line list.
6903 if (deletedNextContinuation->GetParent() != this) {
6904 // The deceased frames continuation is not a child of the
6905 // current block. So break out of the loop so that we advance
6906 // to the next parent.
6908 // If we have a continuation in a different block then all bets are
6909 // off regarding whether we are deleting frames without actual content,
6910 // so don't propagate FRAMES_ARE_EMPTY any further.
6911 aFlags &= ~FRAMES_ARE_EMPTY;
6912 break;
6915 // If we advanced to the next line then check if we should switch to the
6916 // overflow line list.
6917 if (haveAdvancedToNextLine) {
6918 if (line != line_end && !searchingOverflowList &&
6919 !line->Contains(deletedNextContinuation)) {
6920 // We have advanced to the next *normal* line but the next-in-flow
6921 // is not there - force a switch to the overflow line list.
6922 line = line_end;
6925 TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
6926 &overflowLines);
6927 #ifdef NOISY_REMOVE_FRAME
6928 printf("DoRemoveFrame: now on %s line=%p\n",
6929 searchingOverflowList ? "overflow" : "normal", line.get());
6930 #endif
6935 if (!(aFlags & FRAMES_ARE_EMPTY) && line.next() != line_end) {
6936 line.next()->MarkDirty();
6937 line.next()->SetInvalidateTextRuns(true);
6940 #ifdef DEBUG
6941 VerifyLines(true);
6942 VerifyOverflowSituation();
6943 #endif
6945 // Advance to next flow block if the frame has more continuations.
6946 if (!aDeletedFrame) {
6947 return;
6949 nsBlockFrame* nextBlock = do_QueryFrame(aDeletedFrame->GetParent());
6950 NS_ASSERTION(nextBlock, "Our child's continuation's parent is not a block?");
6951 uint32_t flags = (aFlags & REMOVE_FIXED_CONTINUATIONS);
6952 nextBlock->DoRemoveFrame(aContext, aDeletedFrame, flags);
6955 static bool FindBlockLineFor(nsIFrame* aChild, nsLineList::iterator aBegin,
6956 nsLineList::iterator aEnd,
6957 nsLineList::iterator* aResult) {
6958 MOZ_ASSERT(aChild->IsBlockOutside());
6959 for (nsLineList::iterator line = aBegin; line != aEnd; ++line) {
6960 MOZ_ASSERT(line->GetChildCount() > 0);
6961 if (line->IsBlock() && line->mFirstChild == aChild) {
6962 MOZ_ASSERT(line->GetChildCount() == 1);
6963 *aResult = line;
6964 return true;
6967 return false;
6970 static bool FindInlineLineFor(nsIFrame* aChild, const nsFrameList& aFrameList,
6971 nsLineList::iterator aBegin,
6972 nsLineList::iterator aEnd,
6973 nsLineList::iterator* aResult) {
6974 MOZ_ASSERT(!aChild->IsBlockOutside());
6975 for (nsLineList::iterator line = aBegin; line != aEnd; ++line) {
6976 MOZ_ASSERT(line->GetChildCount() > 0);
6977 if (!line->IsBlock()) {
6978 // Optimize by comparing the line's last child first.
6979 nsLineList::iterator next = line.next();
6980 if (aChild == (next == aEnd ? aFrameList.LastChild()
6981 : next->mFirstChild->GetPrevSibling()) ||
6982 line->Contains(aChild)) {
6983 *aResult = line;
6984 return true;
6988 return false;
6991 static bool FindLineFor(nsIFrame* aChild, const nsFrameList& aFrameList,
6992 nsLineList::iterator aBegin, nsLineList::iterator aEnd,
6993 nsLineList::iterator* aResult) {
6994 return aChild->IsBlockOutside()
6995 ? FindBlockLineFor(aChild, aBegin, aEnd, aResult)
6996 : FindInlineLineFor(aChild, aFrameList, aBegin, aEnd, aResult);
6999 void nsBlockFrame::StealFrame(nsIFrame* aChild) {
7000 MOZ_ASSERT(aChild->GetParent() == this);
7002 if (aChild->IsFloating()) {
7003 RemoveFloat(aChild);
7004 return;
7007 if (MaybeStealOverflowContainerFrame(aChild)) {
7008 return;
7011 MOZ_ASSERT(!aChild->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
7013 nsLineList::iterator line;
7014 if (FindLineFor(aChild, mFrames, mLines.begin(), mLines.end(), &line)) {
7015 RemoveFrameFromLine(aChild, line, mFrames, mLines);
7016 } else {
7017 FrameLines* overflowLines = GetOverflowLines();
7018 DebugOnly<bool> found;
7019 found = FindLineFor(aChild, overflowLines->mFrames,
7020 overflowLines->mLines.begin(),
7021 overflowLines->mLines.end(), &line);
7022 MOZ_ASSERT(found, "Why can't we find aChild in our overflow lines?");
7023 RemoveFrameFromLine(aChild, line, overflowLines->mFrames,
7024 overflowLines->mLines);
7025 if (overflowLines->mLines.empty()) {
7026 DestroyOverflowLines();
7031 void nsBlockFrame::RemoveFrameFromLine(nsIFrame* aChild,
7032 nsLineList::iterator aLine,
7033 nsFrameList& aFrameList,
7034 nsLineList& aLineList) {
7035 aFrameList.RemoveFrame(aChild);
7036 if (aChild == aLine->mFirstChild) {
7037 aLine->mFirstChild = aChild->GetNextSibling();
7039 aLine->NoteFrameRemoved(aChild);
7040 if (aLine->GetChildCount() > 0) {
7041 aLine->MarkDirty();
7042 } else {
7043 // The line became empty - destroy it.
7044 nsLineBox* lineBox = aLine;
7045 aLine = aLineList.erase(aLine);
7046 if (aLine != aLineList.end()) {
7047 aLine->MarkPreviousMarginDirty();
7049 FreeLineBox(lineBox);
7053 void nsBlockFrame::DeleteNextInFlowChild(DestroyContext& aContext,
7054 nsIFrame* aNextInFlow,
7055 bool aDeletingEmptyFrames) {
7056 MOZ_ASSERT(aNextInFlow->GetPrevInFlow(), "bad next-in-flow");
7058 if (aNextInFlow->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW |
7059 NS_FRAME_IS_OVERFLOW_CONTAINER)) {
7060 nsContainerFrame::DeleteNextInFlowChild(aContext, aNextInFlow,
7061 aDeletingEmptyFrames);
7062 } else {
7063 #ifdef DEBUG
7064 if (aDeletingEmptyFrames) {
7065 nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(aNextInFlow);
7067 #endif
7068 DoRemoveFrame(aContext, aNextInFlow,
7069 aDeletingEmptyFrames ? FRAMES_ARE_EMPTY : 0);
7073 const nsStyleText* nsBlockFrame::StyleTextForLineLayout() {
7074 // Return the pointer to an unmodified style text
7075 return StyleText();
7078 void nsBlockFrame::ReflowFloat(BlockReflowState& aState, ReflowInput& aFloatRI,
7079 nsIFrame* aFloat,
7080 nsReflowStatus& aReflowStatus) {
7081 MOZ_ASSERT(aReflowStatus.IsEmpty(),
7082 "Caller should pass a fresh reflow status!");
7083 MOZ_ASSERT(aFloat->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
7084 "aFloat must be an out-of-flow frame");
7086 WritingMode wm = aState.mReflowInput.GetWritingMode();
7088 // Setup a block reflow context to reflow the float.
7089 nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput);
7091 nsIFrame* clearanceFrame = nullptr;
7092 do {
7093 nsCollapsingMargin margin;
7094 bool mayNeedRetry = false;
7095 aFloatRI.mDiscoveredClearance = nullptr;
7096 // Only first in flow gets a block-start margin.
7097 if (!aFloat->GetPrevInFlow()) {
7098 brc.ComputeCollapsedBStartMargin(aFloatRI, &margin, clearanceFrame,
7099 &mayNeedRetry);
7101 if (mayNeedRetry && !clearanceFrame) {
7102 aFloatRI.mDiscoveredClearance = &clearanceFrame;
7103 // We don't need to push the float manager state because the the block
7104 // has its own float manager that will be destroyed and recreated
7108 // When reflowing a float, aSpace argument doesn't matter because we pass
7109 // nullptr to aLine and we don't call nsBlockReflowContext::PlaceBlock()
7110 // later.
7111 brc.ReflowBlock(LogicalRect(wm), true, margin, 0, nullptr, aFloatRI,
7112 aReflowStatus, aState);
7113 } while (clearanceFrame);
7115 if (aFloat->IsLetterFrame()) {
7116 // We never split floating first letters; an incomplete status for such
7117 // frames simply means that there is more content to be reflowed on the
7118 // line.
7119 if (aReflowStatus.IsIncomplete()) {
7120 aReflowStatus.Reset();
7124 NS_ASSERTION(aReflowStatus.IsFullyComplete() ||
7125 aFloatRI.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
7126 "The status can only be incomplete or overflow-incomplete if "
7127 "the available block-size is constrained!");
7129 if (aReflowStatus.NextInFlowNeedsReflow()) {
7130 aState.mReflowStatus.SetNextInFlowNeedsReflow();
7133 const ReflowOutput& metrics = brc.GetMetrics();
7135 // Set the rect, make sure the view is properly sized and positioned,
7136 // and tell the frame we're done reflowing it
7137 // XXXldb This seems like the wrong place to be doing this -- shouldn't
7138 // we be doing this in BlockReflowState::FlowAndPlaceFloat after
7139 // we've positioned the float, and shouldn't we be doing the equivalent
7140 // of |PlaceFrameView| here?
7141 WritingMode metricsWM = metrics.GetWritingMode();
7142 aFloat->SetSize(metricsWM, metrics.Size(metricsWM));
7143 if (aFloat->HasView()) {
7144 nsContainerFrame::SyncFrameViewAfterReflow(
7145 aState.mPresContext, aFloat, aFloat->GetView(), metrics.InkOverflow(),
7146 ReflowChildFlags::NoMoveView);
7148 aFloat->DidReflow(aState.mPresContext, &aFloatRI);
7151 StyleClear nsBlockFrame::FindTrailingClear() {
7152 for (nsBlockFrame* b = this; b;
7153 b = static_cast<nsBlockFrame*>(b->GetPrevInFlow())) {
7154 auto endLine = b->LinesRBegin();
7155 if (endLine != b->LinesREnd()) {
7156 return endLine->FloatClearTypeAfter();
7159 return StyleClear::None;
7162 void nsBlockFrame::ReflowPushedFloats(BlockReflowState& aState,
7163 OverflowAreas& aOverflowAreas) {
7164 // Pushed floats live at the start of our float list; see comment
7165 // above nsBlockFrame::DrainPushedFloats.
7166 nsIFrame* f = mFloats.FirstChild();
7167 nsIFrame* prev = nullptr;
7168 while (f && f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
7169 MOZ_ASSERT(prev == f->GetPrevSibling());
7170 // When we push a first-continuation float in a non-initial reflow,
7171 // it's possible that we end up with two continuations with the same
7172 // parent. This happens if, on the previous reflow of the block or
7173 // a previous reflow of the line containing the block, the float was
7174 // split between continuations A and B of the parent, but on the
7175 // current reflow, none of the float can fit in A.
7177 // When this happens, we might even have the two continuations
7178 // out-of-order due to the management of the pushed floats. In
7179 // particular, if the float's placeholder was in a pushed line that
7180 // we reflowed before it was pushed, and we split the float during
7181 // that reflow, we might have the continuation of the float before
7182 // the float itself. (In the general case, however, it's correct
7183 // for floats in the pushed floats list to come before floats
7184 // anchored in pushed lines; however, in this case it's wrong. We
7185 // should probably find a way to fix it somehow, since it leads to
7186 // incorrect layout in some cases.)
7188 // When we have these out-of-order continuations, we might hit the
7189 // next-continuation before the previous-continuation. When that
7190 // happens, just push it. When we reflow the next continuation,
7191 // we'll either pull all of its content back and destroy it (by
7192 // calling DeleteNextInFlowChild), or nsBlockFrame::SplitFloat will
7193 // pull it out of its current position and push it again (and
7194 // potentially repeat this cycle for the next continuation, although
7195 // hopefully then they'll be in the right order).
7197 // We should also need this code for the in-order case if the first
7198 // continuation of a float gets moved across more than one
7199 // continuation of the containing block. In this case we'd manage
7200 // to push the second continuation without this check, but not the
7201 // third and later.
7202 nsIFrame* prevContinuation = f->GetPrevContinuation();
7203 if (prevContinuation && prevContinuation->GetParent() == f->GetParent()) {
7204 mFloats.RemoveFrame(f);
7205 aState.AppendPushedFloatChain(f);
7206 f = !prev ? mFloats.FirstChild() : prev->GetNextSibling();
7207 continue;
7210 // Always call FlowAndPlaceFloat; we might need to place this float if it
7211 // didn't belong to this block the last time it was reflowed. Note that if
7212 // the float doesn't get placed, we don't consider its overflow areas.
7213 // (Not-getting-placed means it didn't fit and we pushed it instead of
7214 // placing it, and its position could be stale.)
7215 if (aState.FlowAndPlaceFloat(f) ==
7216 BlockReflowState::PlaceFloatResult::Placed) {
7217 ConsiderChildOverflow(aOverflowAreas, f);
7220 nsIFrame* next = !prev ? mFloats.FirstChild() : prev->GetNextSibling();
7221 if (next == f) {
7222 // We didn't push |f| so its next-sibling is next.
7223 next = f->GetNextSibling();
7224 prev = f;
7225 } // else: we did push |f| so |prev|'s new next-sibling is next.
7226 f = next;
7229 // If there are pushed or split floats, then we may need to continue BR
7230 // clearance
7231 if (auto [bCoord, result] = aState.ClearFloats(0, StyleClear::Both);
7232 result != ClearFloatsResult::BCoordNoChange) {
7233 Unused << bCoord;
7234 if (auto* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow())) {
7235 aState.mTrailingClearFromPIF = prevBlock->FindTrailingClear();
7240 void nsBlockFrame::RecoverFloats(nsFloatManager& aFloatManager, WritingMode aWM,
7241 const nsSize& aContainerSize) {
7242 // Recover our own floats
7243 nsIFrame* stop = nullptr; // Stop before we reach pushed floats that
7244 // belong to our next-in-flow
7245 for (nsIFrame* f = mFloats.FirstChild(); f && f != stop;
7246 f = f->GetNextSibling()) {
7247 LogicalRect region = nsFloatManager::GetRegionFor(aWM, f, aContainerSize);
7248 aFloatManager.AddFloat(f, region, aWM, aContainerSize);
7249 if (!stop && f->GetNextInFlow()) {
7250 stop = f->GetNextInFlow();
7254 // Recurse into our overflow container children
7255 for (nsIFrame* oc =
7256 GetChildList(FrameChildListID::OverflowContainers).FirstChild();
7257 oc; oc = oc->GetNextSibling()) {
7258 RecoverFloatsFor(oc, aFloatManager, aWM, aContainerSize);
7261 // Recurse into our normal children
7262 for (const auto& line : Lines()) {
7263 if (line.IsBlock()) {
7264 RecoverFloatsFor(line.mFirstChild, aFloatManager, aWM, aContainerSize);
7269 void nsBlockFrame::RecoverFloatsFor(nsIFrame* aFrame,
7270 nsFloatManager& aFloatManager,
7271 WritingMode aWM,
7272 const nsSize& aContainerSize) {
7273 MOZ_ASSERT(aFrame, "null frame");
7275 // Only blocks have floats
7276 nsBlockFrame* block = do_QueryFrame(aFrame);
7277 // Don't recover any state inside a block that has its own float manager
7278 // (we don't currently have any blocks like this, though, thanks to our
7279 // use of extra frames for 'overflow')
7280 if (block && !nsBlockFrame::BlockNeedsFloatManager(block)) {
7281 // If the element is relatively positioned, then adjust x and y
7282 // accordingly so that we consider relatively positioned frames
7283 // at their original position.
7285 const LogicalRect rect = block->GetLogicalNormalRect(aWM, aContainerSize);
7286 nscoord lineLeft = rect.LineLeft(aWM, aContainerSize);
7287 nscoord blockStart = rect.BStart(aWM);
7288 aFloatManager.Translate(lineLeft, blockStart);
7289 block->RecoverFloats(aFloatManager, aWM, aContainerSize);
7290 aFloatManager.Translate(-lineLeft, -blockStart);
7294 bool nsBlockFrame::HasPushedFloatsFromPrevContinuation() const {
7295 if (!mFloats.IsEmpty()) {
7296 // If we have pushed floats, then they should be at the beginning of our
7297 // float list.
7298 if (mFloats.FirstChild()->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
7299 return true;
7303 #ifdef DEBUG
7304 // Double-check the above assertion that pushed floats should be at the
7305 // beginning of our floats list.
7306 for (nsIFrame* f : mFloats) {
7307 NS_ASSERTION(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
7308 "pushed floats must be at the beginning of the float list");
7310 #endif
7312 // We may have a pending push of pushed floats too:
7313 if (HasPushedFloats()) {
7314 // XXX we can return 'true' here once we make HasPushedFloats
7315 // not lie. (see nsBlockFrame::RemoveFloat)
7316 auto* pushedFloats = GetPushedFloats();
7317 return pushedFloats && !pushedFloats->IsEmpty();
7319 return false;
7322 //////////////////////////////////////////////////////////////////////
7323 // Painting, event handling
7325 #ifdef DEBUG
7326 static void ComputeInkOverflowArea(nsLineList& aLines, nscoord aWidth,
7327 nscoord aHeight, nsRect& aResult) {
7328 nscoord xa = 0, ya = 0, xb = aWidth, yb = aHeight;
7329 for (nsLineList::iterator line = aLines.begin(), line_end = aLines.end();
7330 line != line_end; ++line) {
7331 // Compute min and max x/y values for the reflowed frame's
7332 // combined areas
7333 nsRect inkOverflow(line->InkOverflowRect());
7334 nscoord x = inkOverflow.x;
7335 nscoord y = inkOverflow.y;
7336 nscoord xmost = x + inkOverflow.width;
7337 nscoord ymost = y + inkOverflow.height;
7338 if (x < xa) {
7339 xa = x;
7341 if (xmost > xb) {
7342 xb = xmost;
7344 if (y < ya) {
7345 ya = y;
7347 if (ymost > yb) {
7348 yb = ymost;
7352 aResult.x = xa;
7353 aResult.y = ya;
7354 aResult.width = xb - xa;
7355 aResult.height = yb - ya;
7357 #endif
7359 #ifdef DEBUG
7360 static void DebugOutputDrawLine(int32_t aDepth, nsLineBox* aLine, bool aDrawn) {
7361 if (nsBlockFrame::gNoisyDamageRepair) {
7362 nsIFrame::IndentBy(stdout, aDepth + 1);
7363 nsRect lineArea = aLine->InkOverflowRect();
7364 printf("%s line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
7365 aDrawn ? "draw" : "skip", static_cast<void*>(aLine), aLine->IStart(),
7366 aLine->BStart(), aLine->ISize(), aLine->BSize(), lineArea.x,
7367 lineArea.y, lineArea.width, lineArea.height);
7370 #endif
7372 static void DisplayLine(nsDisplayListBuilder* aBuilder,
7373 nsBlockFrame::LineIterator& aLine,
7374 const bool aLineInLine, const nsDisplayListSet& aLists,
7375 nsBlockFrame* aFrame, TextOverflow* aTextOverflow,
7376 uint32_t aLineNumberForTextOverflow, int32_t aDepth,
7377 int32_t& aDrawnLines) {
7378 #ifdef DEBUG
7379 if (nsBlockFrame::gLamePaintMetrics) {
7380 aDrawnLines++;
7382 const bool intersect =
7383 aLine->InkOverflowRect().Intersects(aBuilder->GetDirtyRect());
7384 DebugOutputDrawLine(aDepth, aLine.get(), intersect);
7385 #endif
7387 // Collect our line's display items in a temporary nsDisplayListCollection,
7388 // so that we can apply any "text-overflow" clipping to the entire collection
7389 // without affecting previous lines.
7390 nsDisplayListCollection collection(aBuilder);
7392 // Block-level child backgrounds go on the blockBorderBackgrounds list ...
7393 // Inline-level child backgrounds go on the regular child content list.
7394 nsDisplayListSet childLists(
7395 collection,
7396 aLineInLine ? collection.Content() : collection.BlockBorderBackgrounds());
7398 auto flags =
7399 aLineInLine
7400 ? nsIFrame::DisplayChildFlags(nsIFrame::DisplayChildFlag::Inline)
7401 : nsIFrame::DisplayChildFlags();
7403 nsIFrame* kid = aLine->mFirstChild;
7404 int32_t n = aLine->GetChildCount();
7405 while (--n >= 0) {
7406 aFrame->BuildDisplayListForChild(aBuilder, kid, childLists, flags);
7407 kid = kid->GetNextSibling();
7410 if (aTextOverflow && aLineInLine) {
7411 aTextOverflow->ProcessLine(collection, aLine.get(),
7412 aLineNumberForTextOverflow);
7415 collection.MoveTo(aLists);
7418 void nsBlockFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
7419 const nsDisplayListSet& aLists) {
7420 int32_t drawnLines; // Will only be used if set (gLamePaintMetrics).
7421 int32_t depth = 0;
7422 #ifdef DEBUG
7423 if (gNoisyDamageRepair) {
7424 nsRect dirty = aBuilder->GetDirtyRect();
7425 depth = GetDepth();
7426 nsRect ca;
7427 ::ComputeInkOverflowArea(mLines, mRect.width, mRect.height, ca);
7428 nsIFrame::IndentBy(stdout, depth);
7429 ListTag(stdout);
7430 printf(": bounds=%d,%d,%d,%d dirty(absolute)=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
7431 mRect.x, mRect.y, mRect.width, mRect.height, dirty.x, dirty.y,
7432 dirty.width, dirty.height, ca.x, ca.y, ca.width, ca.height);
7434 PRTime start = 0; // Initialize these variables to silence the compiler.
7435 if (gLamePaintMetrics) {
7436 start = PR_Now();
7437 drawnLines = 0;
7439 #endif
7441 // TODO(heycam): Should we boost the load priority of any shape-outside
7442 // images using CATEGORY_DISPLAY, now that this block is being displayed?
7443 // We don't have a float manager here.
7445 DisplayBorderBackgroundOutline(aBuilder, aLists);
7447 if (GetPrevInFlow()) {
7448 DisplayOverflowContainers(aBuilder, aLists);
7449 for (nsIFrame* f : mFloats) {
7450 if (f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
7451 BuildDisplayListForChild(aBuilder, f, aLists);
7456 aBuilder->MarkFramesForDisplayList(this, mFloats);
7458 if (HasOutsideMarker()) {
7459 // Display outside ::marker manually.
7460 BuildDisplayListForChild(aBuilder, GetOutsideMarker(), aLists);
7463 // Prepare for text-overflow processing.
7464 Maybe<TextOverflow> textOverflow =
7465 TextOverflow::WillProcessLines(aBuilder, this);
7467 const bool hasDescendantPlaceHolders =
7468 HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) ||
7469 ForceDescendIntoIfVisible() || aBuilder->GetIncludeAllOutOfFlows();
7471 const auto ShouldDescendIntoLine = [&](const nsRect& aLineArea) -> bool {
7472 // TODO(miko): Unfortunately |descendAlways| cannot be cached, because with
7473 // some frame trees, building display list for child lines can change it.
7474 // See bug 1552789.
7475 const bool descendAlways =
7476 HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) ||
7477 aBuilder->GetIncludeAllOutOfFlows();
7479 return descendAlways || aLineArea.Intersects(aBuilder->GetDirtyRect()) ||
7480 (ForceDescendIntoIfVisible() &&
7481 aLineArea.Intersects(aBuilder->GetVisibleRect()));
7484 Maybe<nscolor> backplateColor;
7486 // We'll try to draw an accessibility backplate behind text (to ensure it's
7487 // readable over any possible background-images), if all of the following
7488 // hold:
7489 // (A) the backplate feature is preffed on
7490 // (B) we are not honoring the document colors
7491 // (C) the force color adjust property is set to auto
7492 if (StaticPrefs::browser_display_permit_backplate() &&
7493 PresContext()->ForcingColors() && !IsComboboxControlFrame() &&
7494 StyleText()->mForcedColorAdjust != StyleForcedColorAdjust::None) {
7495 backplateColor.emplace(GetBackplateColor(this));
7498 // Don't use the line cursor if we might have a descendant placeholder ...
7499 // it might skip lines that contain placeholders but don't themselves
7500 // intersect with the dirty area.
7501 // In particular, we really want to check ShouldDescendIntoFrame()
7502 // on all our child frames, but that might be expensive. So we
7503 // approximate it by checking it on |this|; if it's true for any
7504 // frame in our child list, it's also true for |this|.
7505 // Also skip the cursor if we're creating text overflow markers,
7506 // since we need to know what line number we're up to in order
7507 // to generate unique display item keys.
7508 // Lastly, the cursor should be skipped if we're drawing
7509 // backplates behind text. When backplating we consider consecutive
7510 // runs of text as a whole, which requires we iterate through all lines
7511 // to find our backplate size.
7512 nsLineBox* cursor =
7513 (hasDescendantPlaceHolders || textOverflow.isSome() || backplateColor)
7514 ? nullptr
7515 : GetFirstLineContaining(aBuilder->GetDirtyRect().y);
7516 LineIterator line_end = LinesEnd();
7518 TextOverflow* textOverflowPtr = textOverflow.ptrOr(nullptr);
7520 if (cursor) {
7521 for (LineIterator line = mLines.begin(cursor); line != line_end; ++line) {
7522 const nsRect lineArea = line->InkOverflowRect();
7523 if (!lineArea.IsEmpty()) {
7524 // Because we have a cursor, the combinedArea.ys are non-decreasing.
7525 // Once we've passed aDirtyRect.YMost(), we can never see it again.
7526 if (lineArea.y >= aBuilder->GetDirtyRect().YMost()) {
7527 break;
7529 MOZ_ASSERT(textOverflow.isNothing());
7531 if (ShouldDescendIntoLine(lineArea)) {
7532 DisplayLine(aBuilder, line, line->IsInline(), aLists, this, nullptr,
7533 0, depth, drawnLines);
7537 } else {
7538 bool nonDecreasingYs = true;
7539 uint32_t lineCount = 0;
7540 nscoord lastY = INT32_MIN;
7541 nscoord lastYMost = INT32_MIN;
7543 // A frame's display list cannot contain more than one copy of a
7544 // given display item unless the items are uniquely identifiable.
7545 // Because backplate occasionally requires multiple
7546 // SolidColor items, we use an index (backplateIndex) to maintain
7547 // uniqueness among them. Note this is a mapping of index to
7548 // item, and the mapping is stable even if the dirty rect changes.
7549 uint16_t backplateIndex = 0;
7550 nsRect curBackplateArea;
7552 auto AddBackplate = [&]() {
7553 aLists.BorderBackground()->AppendNewToTopWithIndex<nsDisplaySolidColor>(
7554 aBuilder, this, backplateIndex, curBackplateArea,
7555 backplateColor.value());
7558 for (LineIterator line = LinesBegin(); line != line_end; ++line) {
7559 const nsRect lineArea = line->InkOverflowRect();
7560 const bool lineInLine = line->IsInline();
7562 if ((lineInLine && textOverflowPtr) || ShouldDescendIntoLine(lineArea)) {
7563 DisplayLine(aBuilder, line, lineInLine, aLists, this, textOverflowPtr,
7564 lineCount, depth, drawnLines);
7567 if (!lineInLine && !curBackplateArea.IsEmpty()) {
7568 // If we have encountered a non-inline line but were previously
7569 // forming a backplate, we should add the backplate to the display
7570 // list as-is and render future backplates disjointly.
7571 MOZ_ASSERT(backplateColor,
7572 "if this master switch is off, curBackplateArea "
7573 "must be empty and we shouldn't get here");
7574 AddBackplate();
7575 backplateIndex++;
7576 curBackplateArea = nsRect();
7579 if (!lineArea.IsEmpty()) {
7580 if (lineArea.y < lastY || lineArea.YMost() < lastYMost) {
7581 nonDecreasingYs = false;
7583 lastY = lineArea.y;
7584 lastYMost = lineArea.YMost();
7585 if (lineInLine && backplateColor && LineHasVisibleInlineContent(line)) {
7586 nsRect lineBackplate = GetLineTextArea(line, aBuilder) +
7587 aBuilder->ToReferenceFrame(this);
7588 if (curBackplateArea.IsEmpty()) {
7589 curBackplateArea = lineBackplate;
7590 } else {
7591 curBackplateArea.OrWith(lineBackplate);
7595 lineCount++;
7598 if (nonDecreasingYs && lineCount >= MIN_LINES_NEEDING_CURSOR) {
7599 SetupLineCursorForDisplay();
7602 if (!curBackplateArea.IsEmpty()) {
7603 AddBackplate();
7607 if (textOverflow.isSome()) {
7608 // Put any text-overflow:ellipsis markers on top of the non-positioned
7609 // content of the block's lines. (If we ever start sorting the Content()
7610 // list this will end up in the wrong place.)
7611 aLists.Content()->AppendToTop(&textOverflow->GetMarkers());
7614 #ifdef DEBUG
7615 if (gLamePaintMetrics) {
7616 PRTime end = PR_Now();
7618 int32_t numLines = mLines.size();
7619 if (!numLines) {
7620 numLines = 1;
7622 PRTime lines, deltaPerLine, delta;
7623 lines = int64_t(numLines);
7624 delta = end - start;
7625 deltaPerLine = delta / lines;
7627 ListTag(stdout);
7628 char buf[400];
7629 SprintfLiteral(buf,
7630 ": %" PRId64 " elapsed (%" PRId64
7631 " per line) lines=%d drawn=%d skip=%d",
7632 delta, deltaPerLine, numLines, drawnLines,
7633 numLines - drawnLines);
7634 printf("%s\n", buf);
7636 #endif
7639 #ifdef ACCESSIBILITY
7640 a11y::AccType nsBlockFrame::AccessibleType() {
7641 if (IsTableCaption()) {
7642 return GetRect().IsEmpty() ? a11y::eNoType : a11y::eHTMLCaptionType;
7645 // block frame may be for <hr>
7646 if (mContent->IsHTMLElement(nsGkAtoms::hr)) {
7647 return a11y::eHTMLHRType;
7650 if (!HasMarker() || !PresContext()) {
7651 // XXXsmaug What if we're in the shadow dom?
7652 if (!mContent->GetParent()) {
7653 // Don't create accessible objects for the root content node, they are
7654 // redundant with the nsDocAccessible object created with the document
7655 // node
7656 return a11y::eNoType;
7659 if (mContent == mContent->OwnerDoc()->GetBody()) {
7660 // Don't create accessible objects for the body, they are redundant with
7661 // the nsDocAccessible object created with the document node
7662 return a11y::eNoType;
7665 // Not a list item with a ::marker, treat as normal HTML container.
7666 return a11y::eHyperTextType;
7669 // Create special list item accessible since we have a ::marker.
7670 return a11y::eHTMLLiType;
7672 #endif
7674 void nsBlockFrame::SetupLineCursorForDisplay() {
7675 if (mLines.empty() || HasProperty(LineCursorPropertyDisplay())) {
7676 return;
7679 SetProperty(LineCursorPropertyDisplay(), mLines.front());
7680 AddStateBits(NS_BLOCK_HAS_LINE_CURSOR);
7683 void nsBlockFrame::SetupLineCursorForQuery() {
7684 if (mLines.empty() || HasProperty(LineCursorPropertyQuery())) {
7685 return;
7688 SetProperty(LineCursorPropertyQuery(), mLines.front());
7689 AddStateBits(NS_BLOCK_HAS_LINE_CURSOR);
7692 nsLineBox* nsBlockFrame::GetFirstLineContaining(nscoord y) {
7693 // Although this looks like a "querying" method, it is used by the
7694 // display-list building code, so uses the Display cursor.
7695 nsLineBox* property = GetLineCursorForDisplay();
7696 if (!property) {
7697 return nullptr;
7699 LineIterator cursor = mLines.begin(property);
7700 nsRect cursorArea = cursor->InkOverflowRect();
7702 while ((cursorArea.IsEmpty() || cursorArea.YMost() > y) &&
7703 cursor != mLines.front()) {
7704 cursor = cursor.prev();
7705 cursorArea = cursor->InkOverflowRect();
7707 while ((cursorArea.IsEmpty() || cursorArea.YMost() <= y) &&
7708 cursor != mLines.back()) {
7709 cursor = cursor.next();
7710 cursorArea = cursor->InkOverflowRect();
7713 if (cursor.get() != property) {
7714 SetProperty(LineCursorPropertyDisplay(), cursor.get());
7717 return cursor.get();
7720 /* virtual */
7721 void nsBlockFrame::ChildIsDirty(nsIFrame* aChild) {
7722 // See if the child is absolutely positioned
7723 if (aChild->IsAbsolutelyPositioned()) {
7724 // do nothing
7725 } else if (aChild == GetOutsideMarker()) {
7726 // The ::marker lives in the first line, unless the first line has
7727 // height 0 and there is a second line, in which case it lives
7728 // in the second line.
7729 LineIterator markerLine = LinesBegin();
7730 if (markerLine != LinesEnd() && markerLine->BSize() == 0 &&
7731 markerLine != mLines.back()) {
7732 markerLine = markerLine.next();
7735 if (markerLine != LinesEnd()) {
7736 MarkLineDirty(markerLine, &mLines);
7738 // otherwise we have an empty line list, and ReflowDirtyLines
7739 // will handle reflowing the ::marker.
7740 } else {
7741 // Note that we should go through our children to mark lines dirty
7742 // before the next reflow. Doing it now could make things O(N^2)
7743 // since finding the right line is O(N).
7744 // We don't need to worry about marking lines on the overflow list
7745 // as dirty; we're guaranteed to reflow them if we take them off the
7746 // overflow list.
7747 // However, we might have gotten a float, in which case we need to
7748 // reflow the line containing its placeholder. So find the
7749 // ancestor-or-self of the placeholder that's a child of the block,
7750 // and mark it as NS_FRAME_HAS_DIRTY_CHILDREN too, so that we mark
7751 // its line dirty when we handle NS_BLOCK_LOOK_FOR_DIRTY_FRAMES.
7752 // We need to take some care to handle the case where a float is in
7753 // a different continuation than its placeholder, including marking
7754 // an extra block with NS_BLOCK_LOOK_FOR_DIRTY_FRAMES.
7755 if (!aChild->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
7756 AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
7757 } else {
7758 NS_ASSERTION(aChild->IsFloating(), "should be a float");
7759 nsIFrame* thisFC = FirstContinuation();
7760 nsIFrame* placeholderPath = aChild->GetPlaceholderFrame();
7761 // SVG code sometimes sends FrameNeedsReflow notifications during
7762 // frame destruction, leading to null placeholders, but we're safe
7763 // ignoring those.
7764 if (placeholderPath) {
7765 for (;;) {
7766 nsIFrame* parent = placeholderPath->GetParent();
7767 if (parent->GetContent() == mContent &&
7768 parent->FirstContinuation() == thisFC) {
7769 parent->AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
7770 break;
7772 placeholderPath = parent;
7774 placeholderPath->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
7779 nsContainerFrame::ChildIsDirty(aChild);
7782 static bool AlwaysEstablishesBFC(const nsBlockFrame* aFrame) {
7783 switch (aFrame->Type()) {
7784 case LayoutFrameType::ColumnSetWrapper:
7785 // CSS Multi-column level 1 section 2: A multi-column container
7786 // establishes a new block formatting context, as per CSS 2.1 section
7787 // 9.4.1.
7788 case LayoutFrameType::ComboboxControl:
7789 return true;
7790 case LayoutFrameType::Block:
7791 return static_cast<const nsFileControlFrame*>(do_QueryFrame(aFrame)) ||
7792 // Ensure that the options inside the select aren't expanded by
7793 // right floats outside the select.
7794 static_cast<const nsSelectsAreaFrame*>(do_QueryFrame(aFrame)) ||
7795 // See bug 1373767 and bug 353894.
7796 static_cast<const nsMathMLmathBlockFrame*>(do_QueryFrame(aFrame));
7797 default:
7798 return false;
7802 void nsBlockFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
7803 nsIFrame* aPrevInFlow) {
7804 // These are all the block specific frame bits, they are copied from
7805 // the prev-in-flow to a newly created next-in-flow, except for the
7806 // NS_BLOCK_FLAGS_NON_INHERITED_MASK bits below.
7807 constexpr nsFrameState NS_BLOCK_FLAGS_MASK =
7808 NS_BLOCK_BFC_STATE_BITS | NS_BLOCK_CLIP_PAGINATED_OVERFLOW |
7809 NS_BLOCK_HAS_FIRST_LETTER_STYLE | NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER |
7810 NS_BLOCK_HAS_FIRST_LETTER_CHILD | NS_BLOCK_FRAME_HAS_INSIDE_MARKER;
7812 // This is the subset of NS_BLOCK_FLAGS_MASK that is NOT inherited
7813 // by default. They should only be set on the first-in-flow.
7814 constexpr nsFrameState NS_BLOCK_FLAGS_NON_INHERITED_MASK =
7815 NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER | NS_BLOCK_HAS_FIRST_LETTER_CHILD |
7816 NS_BLOCK_FRAME_HAS_INSIDE_MARKER;
7818 if (aPrevInFlow) {
7819 // Copy over the inherited block frame bits from the prev-in-flow.
7820 RemoveStateBits(NS_BLOCK_FLAGS_MASK);
7821 AddStateBits(aPrevInFlow->GetStateBits() &
7822 (NS_BLOCK_FLAGS_MASK & ~NS_BLOCK_FLAGS_NON_INHERITED_MASK));
7825 nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
7827 if (!aPrevInFlow ||
7828 aPrevInFlow->HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION)) {
7829 AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
7832 // A display:flow-root box establishes a block formatting context.
7834 // If a box has a different writing-mode value than its containing block:
7835 // ...
7836 // If the box is a block container, then it establishes a new block
7837 // formatting context.
7838 // (https://drafts.csswg.org/css-writing-modes/#block-flow)
7840 // If the box has contain: paint or contain:layout (or contain:strict),
7841 // then it should also establish a formatting context.
7843 // Per spec, a column-span always establishes a new block formatting context.
7845 // Other more specific frame types also always establish a BFC.
7847 if (StyleDisplay()->mDisplay == mozilla::StyleDisplay::FlowRoot ||
7848 (GetParent() &&
7849 (GetWritingMode().GetBlockDir() !=
7850 GetParent()->GetWritingMode().GetBlockDir() ||
7851 GetWritingMode().IsVerticalSideways() !=
7852 GetParent()->GetWritingMode().IsVerticalSideways())) ||
7853 IsColumnSpan() || AlwaysEstablishesBFC(this)) {
7854 AddStateBits(NS_BLOCK_STATIC_BFC);
7857 if (StyleEstablishesBFC(Style())) {
7858 AddStateBits(NS_BLOCK_DYNAMIC_BFC);
7861 if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER) &&
7862 HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS)) {
7863 AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
7867 void nsBlockFrame::SetInitialChildList(ChildListID aListID,
7868 nsFrameList&& aChildList) {
7869 if (FrameChildListID::Float == aListID) {
7870 mFloats = std::move(aChildList);
7871 } else if (FrameChildListID::Principal == aListID) {
7872 #ifdef DEBUG
7873 // The only times a block that is an anonymous box is allowed to have a
7874 // first-letter frame are when it's the block inside a non-anonymous cell,
7875 // the block inside a fieldset, button or column set, or a scrolled content
7876 // block, except for <select>. Note that this means that blocks which are
7877 // the anonymous block in {ib} splits do NOT get first-letter frames.
7878 // Note that NS_BLOCK_HAS_FIRST_LETTER_STYLE gets set on all continuations
7879 // of the block.
7880 auto pseudo = Style()->GetPseudoType();
7881 bool haveFirstLetterStyle =
7882 (pseudo == PseudoStyleType::NotPseudo ||
7883 (pseudo == PseudoStyleType::cellContent &&
7884 !GetParent()->Style()->IsPseudoOrAnonBox()) ||
7885 pseudo == PseudoStyleType::fieldsetContent ||
7886 pseudo == PseudoStyleType::buttonContent ||
7887 pseudo == PseudoStyleType::columnContent ||
7888 (pseudo == PseudoStyleType::scrolledContent &&
7889 !GetParent()->IsListControlFrame()) ||
7890 pseudo == PseudoStyleType::mozSVGText) &&
7891 !IsComboboxControlFrame() && !IsMathMLFrame() &&
7892 !IsColumnSetWrapperFrame() &&
7893 RefPtr<ComputedStyle>(GetFirstLetterStyle(PresContext())) != nullptr;
7894 NS_ASSERTION(haveFirstLetterStyle ==
7895 HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE),
7896 "NS_BLOCK_HAS_FIRST_LETTER_STYLE state out of sync");
7897 #endif
7899 AddFrames(std::move(aChildList), nullptr, nullptr);
7900 } else {
7901 nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
7905 void nsBlockFrame::SetMarkerFrameForListItem(nsIFrame* aMarkerFrame) {
7906 MOZ_ASSERT(aMarkerFrame);
7907 MOZ_ASSERT(!HasAnyStateBits(NS_BLOCK_FRAME_HAS_INSIDE_MARKER |
7908 NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER),
7909 "How can we have a ::marker frame already?");
7911 if (StyleList()->mListStylePosition == StyleListStylePosition::Inside) {
7912 SetProperty(InsideMarkerProperty(), aMarkerFrame);
7913 AddStateBits(NS_BLOCK_FRAME_HAS_INSIDE_MARKER);
7914 } else {
7915 if (nsBlockFrame* marker = do_QueryFrame(aMarkerFrame)) {
7916 // An outside ::marker needs to be an independent formatting context
7917 // to avoid being influenced by the float manager etc.
7918 marker->AddStateBits(NS_BLOCK_STATIC_BFC);
7920 SetProperty(OutsideMarkerProperty(),
7921 new (PresShell()) nsFrameList(aMarkerFrame, aMarkerFrame));
7922 AddStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER);
7926 bool nsBlockFrame::MarkerIsEmpty() const {
7927 NS_ASSERTION(mContent->GetPrimaryFrame()->StyleDisplay()->IsListItem() &&
7928 HasOutsideMarker(),
7929 "should only care when we have an outside ::marker");
7930 nsIFrame* marker = GetMarker();
7931 const nsStyleList* list = marker->StyleList();
7932 return marker->StyleContent()->mContent.IsNone() ||
7933 (list->mCounterStyle.IsNone() && list->mListStyleImage.IsNone() &&
7934 marker->StyleContent()->ContentCount() == 0);
7937 void nsBlockFrame::ReflowOutsideMarker(nsIFrame* aMarkerFrame,
7938 BlockReflowState& aState,
7939 ReflowOutput& aMetrics,
7940 nscoord aLineTop) {
7941 const ReflowInput& ri = aState.mReflowInput;
7943 WritingMode markerWM = aMarkerFrame->GetWritingMode();
7944 LogicalSize availSize(markerWM);
7945 // Make up an inline-size since it doesn't really matter (XXX).
7946 availSize.ISize(markerWM) = aState.ContentISize();
7947 availSize.BSize(markerWM) = NS_UNCONSTRAINEDSIZE;
7949 ReflowInput reflowInput(aState.mPresContext, ri, aMarkerFrame, availSize,
7950 Nothing(), {}, {}, {ComputeSizeFlag::ShrinkWrap});
7951 nsReflowStatus status;
7952 aMarkerFrame->Reflow(aState.mPresContext, aMetrics, reflowInput, status);
7954 // Get the float available space using our saved state from before we
7955 // started reflowing the block, so that we ignore any floats inside
7956 // the block.
7957 // FIXME: aLineTop isn't actually set correctly by some callers, since
7958 // they reposition the line.
7959 LogicalRect floatAvailSpace =
7960 aState
7961 .GetFloatAvailableSpaceWithState(aLineTop, ShapeType::ShapeOutside,
7962 &aState.mFloatManagerStateBefore)
7963 .mRect;
7964 // FIXME (bug 25888): need to check the entire region that the first
7965 // line overlaps, not just the top pixel.
7967 // Place the ::marker now. We want to place the ::marker relative to the
7968 // border-box of the associated block (using the right/left margin of
7969 // the ::marker frame as separation). However, if a line box would be
7970 // displaced by floats that are *outside* the associated block, we
7971 // want to displace it by the same amount. That is, we act as though
7972 // the edge of the floats is the content-edge of the block, and place
7973 // the ::marker at a position offset from there by the block's padding,
7974 // the block's border, and the ::marker frame's margin.
7976 // IStart from floatAvailSpace gives us the content/float start edge
7977 // in the current writing mode. Then we subtract out the start
7978 // border/padding and the ::marker's width and margin to offset the position.
7979 WritingMode wm = ri.GetWritingMode();
7980 // Get the ::marker's margin, converted to our writing mode so that we can
7981 // combine it with other logical values here.
7982 LogicalMargin markerMargin = reflowInput.ComputedLogicalMargin(wm);
7983 nscoord iStart = floatAvailSpace.IStart(wm) -
7984 ri.ComputedLogicalBorderPadding(wm).IStart(wm) -
7985 markerMargin.IEnd(wm) - aMetrics.ISize(wm);
7987 // Approximate the ::marker's position; vertical alignment will provide
7988 // the final vertical location. We pass our writing-mode here, because
7989 // it may be different from the ::marker frame's mode.
7990 nscoord bStart = floatAvailSpace.BStart(wm);
7991 aMarkerFrame->SetRect(
7993 LogicalRect(wm, iStart, bStart, aMetrics.ISize(wm), aMetrics.BSize(wm)),
7994 aState.ContainerSize());
7995 aMarkerFrame->DidReflow(aState.mPresContext, &aState.mReflowInput);
7998 // This is used to scan frames for any float placeholders, add their
7999 // floats to the list represented by aList, and remove the
8000 // floats from whatever list they might be in. We don't search descendants
8001 // that are float containing blocks. Floats that or not children of 'this'
8002 // are ignored (they are not added to aList).
8003 void nsBlockFrame::DoCollectFloats(nsIFrame* aFrame, nsFrameList& aList,
8004 bool aCollectSiblings) {
8005 while (aFrame) {
8006 // Don't descend into float containing blocks.
8007 if (!aFrame->IsFloatContainingBlock()) {
8008 nsIFrame* outOfFlowFrame =
8009 aFrame->IsPlaceholderFrame()
8010 ? nsLayoutUtils::GetFloatFromPlaceholder(aFrame)
8011 : nullptr;
8012 while (outOfFlowFrame && outOfFlowFrame->GetParent() == this) {
8013 RemoveFloat(outOfFlowFrame);
8014 // Remove the IS_PUSHED_FLOAT bit, in case |outOfFlowFrame| came from
8015 // the PushedFloats list.
8016 outOfFlowFrame->RemoveStateBits(NS_FRAME_IS_PUSHED_FLOAT);
8017 aList.AppendFrame(nullptr, outOfFlowFrame);
8018 outOfFlowFrame = outOfFlowFrame->GetNextInFlow();
8019 // FIXME: By not pulling floats whose parent is one of our
8020 // later siblings, are we risking the pushed floats getting
8021 // out-of-order?
8022 // XXXmats nsInlineFrame's lazy reparenting depends on NOT doing that.
8025 DoCollectFloats(aFrame->PrincipalChildList().FirstChild(), aList, true);
8026 DoCollectFloats(
8027 aFrame->GetChildList(FrameChildListID::Overflow).FirstChild(), aList,
8028 true);
8030 if (!aCollectSiblings) {
8031 break;
8033 aFrame = aFrame->GetNextSibling();
8037 void nsBlockFrame::CheckFloats(BlockReflowState& aState) {
8038 #ifdef DEBUG
8039 // If any line is still dirty, that must mean we're going to reflow this
8040 // block again soon (e.g. because we bailed out after noticing that
8041 // clearance was imposed), so don't worry if the floats are out of sync.
8042 bool anyLineDirty = false;
8044 // Check that the float list is what we would have built
8045 AutoTArray<nsIFrame*, 8> lineFloats;
8046 for (auto& line : Lines()) {
8047 if (line.HasFloats()) {
8048 lineFloats.AppendElements(line.Floats());
8050 if (line.IsDirty()) {
8051 anyLineDirty = true;
8055 AutoTArray<nsIFrame*, 8> storedFloats;
8056 bool equal = true;
8057 bool hasHiddenFloats = false;
8058 uint32_t i = 0;
8059 for (nsIFrame* f : mFloats) {
8060 if (f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
8061 continue;
8063 // There are chances that the float children won't be added to lines,
8064 // because in nsBlockFrame::ReflowLine, it skips reflow line if the first
8065 // child of the line is IsHiddenByContentVisibilityOfInFlowParentForLayout.
8066 // There are also chances that the floats in line are out of date, for
8067 // instance, lines could reflow if
8068 // PresShell::IsForcingLayoutForHiddenContent, and after forcingLayout is
8069 // off, the reflow of lines could be skipped, but the floats are still in
8070 // there. Here we can't know whether the floats hidden by c-v are included
8071 // in the lines or not. So we use hasHiddenFloats to skip the float length
8072 // checking.
8073 if (!hasHiddenFloats &&
8074 f->IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
8075 hasHiddenFloats = true;
8077 storedFloats.AppendElement(f);
8078 if (i < lineFloats.Length() && lineFloats.ElementAt(i) != f) {
8079 equal = false;
8081 ++i;
8084 if ((!equal || lineFloats.Length() != storedFloats.Length()) &&
8085 !anyLineDirty && !hasHiddenFloats) {
8086 NS_ERROR(
8087 "nsBlockFrame::CheckFloats: Explicit float list is out of sync with "
8088 "float cache");
8089 # if defined(DEBUG_roc)
8090 nsIFrame::RootFrameList(PresContext(), stdout, 0);
8091 for (i = 0; i < lineFloats.Length(); ++i) {
8092 printf("Line float: %p\n", lineFloats.ElementAt(i));
8094 for (i = 0; i < storedFloats.Length(); ++i) {
8095 printf("Stored float: %p\n", storedFloats.ElementAt(i));
8097 # endif
8099 #endif
8101 const nsFrameList* oofs = GetOverflowOutOfFlows();
8102 if (oofs && oofs->NotEmpty()) {
8103 // Floats that were pushed should be removed from our float
8104 // manager. Otherwise the float manager's YMost or XMost might
8105 // be larger than necessary, causing this block to get an
8106 // incorrect desired height (or width). Some of these floats
8107 // may not actually have been added to the float manager because
8108 // they weren't reflowed before being pushed; that's OK,
8109 // RemoveRegions will ignore them. It is safe to do this here
8110 // because we know from here on the float manager will only be
8111 // used for its XMost and YMost, not to place new floats and
8112 // lines.
8113 aState.FloatManager()->RemoveTrailingRegions(oofs->FirstChild());
8117 void nsBlockFrame::IsMarginRoot(bool* aBStartMarginRoot,
8118 bool* aBEndMarginRoot) {
8119 nsIFrame* parent = GetParent();
8120 if (!HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS)) {
8121 if (!parent || parent->IsFloatContainingBlock()) {
8122 *aBStartMarginRoot = false;
8123 *aBEndMarginRoot = false;
8124 return;
8128 if (parent && parent->IsColumnSetFrame()) {
8129 // The first column is a start margin root and the last column is an end
8130 // margin root. (If the column-set is split by a column-span:all box then
8131 // the first and last column in each column-set fragment are margin roots.)
8132 *aBStartMarginRoot = GetPrevInFlow() == nullptr;
8133 *aBEndMarginRoot = GetNextInFlow() == nullptr;
8134 return;
8137 *aBStartMarginRoot = true;
8138 *aBEndMarginRoot = true;
8141 /* static */
8142 bool nsBlockFrame::BlockNeedsFloatManager(nsIFrame* aBlock) {
8143 MOZ_ASSERT(aBlock, "Must have a frame");
8144 NS_ASSERTION(aBlock->IsBlockFrameOrSubclass(), "aBlock must be a block");
8146 nsIFrame* parent = aBlock->GetParent();
8147 return aBlock->HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS) ||
8148 (parent && !parent->IsFloatContainingBlock());
8151 /* static */
8152 bool nsBlockFrame::BlockCanIntersectFloats(nsIFrame* aFrame) {
8153 return aFrame->IsBlockFrameOrSubclass() && !aFrame->IsReplaced() &&
8154 !aFrame->HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS);
8157 // Note that this width can vary based on the vertical position.
8158 // However, the cases where it varies are the cases where the width fits
8159 // in the available space given, which means that variation shouldn't
8160 // matter.
8161 /* static */
8162 nsBlockFrame::FloatAvoidingISizeToClear nsBlockFrame::ISizeToClearPastFloats(
8163 const BlockReflowState& aState, const LogicalRect& aFloatAvailableSpace,
8164 nsIFrame* aFloatAvoidingBlock) {
8165 nscoord inlineStartOffset, inlineEndOffset;
8166 WritingMode wm = aState.mReflowInput.GetWritingMode();
8168 FloatAvoidingISizeToClear result;
8169 aState.ComputeFloatAvoidingOffsets(aFloatAvoidingBlock, aFloatAvailableSpace,
8170 inlineStartOffset, inlineEndOffset);
8171 nscoord availISize =
8172 aState.mContentArea.ISize(wm) - inlineStartOffset - inlineEndOffset;
8174 // We actually don't want the min width here; see bug 427782; we only
8175 // want to displace if the width won't compute to a value small enough
8176 // to fit.
8177 // All we really need here is the result of ComputeSize, and we
8178 // could *almost* get that from an SizeComputationInput, except for the
8179 // last argument.
8180 WritingMode frWM = aFloatAvoidingBlock->GetWritingMode();
8181 LogicalSize availSpace =
8182 LogicalSize(wm, availISize, NS_UNCONSTRAINEDSIZE).ConvertTo(frWM, wm);
8183 ReflowInput reflowInput(aState.mPresContext, aState.mReflowInput,
8184 aFloatAvoidingBlock, availSpace);
8185 result.borderBoxISize =
8186 reflowInput.ComputedSizeWithBorderPadding(wm).ISize(wm);
8188 // Use the margins from sizingInput rather than reflowInput so that
8189 // they aren't reduced by ignoring margins in overconstrained cases.
8190 SizeComputationInput sizingInput(aFloatAvoidingBlock,
8191 aState.mReflowInput.mRenderingContext, wm,
8192 aState.mContentArea.ISize(wm));
8193 const LogicalMargin computedMargin = sizingInput.ComputedLogicalMargin(wm);
8195 nscoord marginISize = computedMargin.IStartEnd(wm);
8196 const auto& iSize = reflowInput.mStylePosition->ISize(wm);
8197 if (marginISize < 0 && (iSize.IsAuto() || iSize.IsMozAvailable())) {
8198 // If we get here, floatAvoidingBlock has a negative amount of inline-axis
8199 // margin and an 'auto' (or ~equivalently, -moz-available) inline
8200 // size. Under these circumstances, we use the margin to establish a
8201 // (positive) minimum size for the border-box, in order to satisfy the
8202 // equation in CSS2 10.3.3. That equation essentially simplifies to the
8203 // following:
8205 // iSize of margins + iSize of borderBox = iSize of containingBlock
8207 // ...where "iSize of borderBox" is the sum of floatAvoidingBlock's
8208 // inline-axis components of border, padding, and {width,height}.
8210 // Right now, in the above equation, "iSize of margins" is the only term
8211 // that we know for sure. (And we also know that it's negative, since we
8212 // got here.) The other terms are as-yet unresolved, since the frame has an
8213 // 'auto' iSize, and since we aren't yet sure if we'll clear this frame
8214 // beyond floats or place it alongside them.
8216 // However: we *do* know that the equation's "iSize of containingBlock"
8217 // term *must* be non-negative, since boxes' widths and heights generally
8218 // can't be negative in CSS. To satisfy that requirement, we can then
8219 // infer that the equation's "iSize of borderBox" term *must* be large
8220 // enough to cancel out the (known-to-be-negative) "iSize of margins"
8221 // term. Therefore, marginISize value (negated to make it positive)
8222 // establishes a lower-bound for how much inline-axis space our border-box
8223 // will really require in order to fit alongside any floats.
8225 // XXXdholbert This explanation is admittedly a bit hand-wavy and may not
8226 // precisely match what any particular spec requires. It's the best
8227 // reasoning I could come up with to explain engines' behavior. Also, our
8228 // behavior with -moz-available doesn't seem particularly correct here, per
8229 // bug 1767217, though that's probably due to a bug elsewhere in our float
8230 // handling code...
8231 result.borderBoxISize = std::max(result.borderBoxISize, -marginISize);
8234 result.marginIStart = computedMargin.IStart(wm);
8235 return result;
8238 /* static */
8239 nsBlockFrame* nsBlockFrame::GetNearestAncestorBlock(nsIFrame* aCandidate) {
8240 nsBlockFrame* block = nullptr;
8241 while (aCandidate) {
8242 block = do_QueryFrame(aCandidate);
8243 if (block) {
8244 // yay, candidate is a block!
8245 return block;
8247 // Not a block. Check its parent next.
8248 aCandidate = aCandidate->GetParent();
8250 MOZ_ASSERT_UNREACHABLE("Fell off frame tree looking for ancestor block!");
8251 return nullptr;
8254 nscoord nsBlockFrame::ComputeFinalBSize(BlockReflowState& aState,
8255 nscoord aBEndEdgeOfChildren) {
8256 const WritingMode wm = aState.mReflowInput.GetWritingMode();
8258 const nscoord effectiveContentBoxBSize =
8259 GetEffectiveComputedBSize(aState.mReflowInput, aState.mConsumedBSize);
8260 const nscoord blockStartBP = aState.BorderPadding().BStart(wm);
8261 const nscoord blockEndBP = aState.BorderPadding().BEnd(wm);
8263 NS_ASSERTION(
8264 !IsTrueOverflowContainer() || (effectiveContentBoxBSize == 0 &&
8265 blockStartBP == 0 && blockEndBP == 0),
8266 "An overflow container's effective content-box block-size, block-start "
8267 "BP, and block-end BP should all be zero!");
8269 const nscoord effectiveContentBoxBSizeWithBStartBP =
8270 NSCoordSaturatingAdd(blockStartBP, effectiveContentBoxBSize);
8271 const nscoord effectiveBorderBoxBSize =
8272 NSCoordSaturatingAdd(effectiveContentBoxBSizeWithBStartBP, blockEndBP);
8274 if (HasColumnSpanSiblings()) {
8275 MOZ_ASSERT(LastInFlow()->GetNextContinuation(),
8276 "Frame constructor should've created column-span siblings!");
8278 // If a block is split by any column-spans, we calculate the final
8279 // block-size by shrinkwrapping our children's block-size for all the
8280 // fragments except for those after the final column-span, but we should
8281 // take no more than our effective border-box block-size. If there's any
8282 // leftover block-size, our next continuations will take up rest.
8284 // We don't need to adjust aBri.mReflowStatus because our children's status
8285 // is the same as ours.
8286 return std::min(effectiveBorderBoxBSize, aBEndEdgeOfChildren);
8289 const nscoord availBSize = aState.mReflowInput.AvailableBSize();
8290 if (availBSize == NS_UNCONSTRAINEDSIZE) {
8291 return effectiveBorderBoxBSize;
8294 // Save our children's reflow status.
8295 const bool isChildStatusComplete = aState.mReflowStatus.IsComplete();
8296 if (isChildStatusComplete && effectiveContentBoxBSize > 0 &&
8297 effectiveBorderBoxBSize > availBSize &&
8298 ShouldAvoidBreakInside(aState.mReflowInput)) {
8299 aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
8300 return effectiveBorderBoxBSize;
8303 const bool isBDBClone =
8304 aState.mReflowInput.mStyleBorder->mBoxDecorationBreak ==
8305 StyleBoxDecorationBreak::Clone;
8307 // The maximum value our content-box block-size can take within the given
8308 // available block-size.
8309 const nscoord maxContentBoxBSize = aState.ContentBSize();
8311 // The block-end edge of our content-box (relative to this frame's origin) if
8312 // we consumed the maximum block-size available to us (maxContentBoxBSize).
8313 const nscoord maxContentBoxBEnd = aState.ContentBEnd();
8315 // These variables are uninitialized intentionally so that the compiler can
8316 // check they are assigned in every if-else branch below.
8317 nscoord finalContentBoxBSizeWithBStartBP;
8318 bool isOurStatusComplete;
8320 if (effectiveBorderBoxBSize <= availBSize) {
8321 // Our effective border-box block-size can fit in the available block-size,
8322 // so we are complete.
8323 finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP;
8324 isOurStatusComplete = true;
8325 } else if (effectiveContentBoxBSizeWithBStartBP <= maxContentBoxBEnd) {
8326 // Note: The following assertion should generally hold because, for
8327 // box-decoration-break:clone, this "else if" branch is mathematically
8328 // equivalent to the initial "if".
8329 NS_ASSERTION(!isBDBClone,
8330 "This else-if branch is handling a situation that's specific "
8331 "to box-decoration-break:slice, i.e. a case when we can skip "
8332 "our block-end border and padding!");
8334 // Our effective content-box block-size plus the block-start border and
8335 // padding can fit in the available block-size, but it cannot fit after
8336 // adding the block-end border and padding. Thus, we need a continuation
8337 // (unless we already weren't asking for any block-size, in which case we
8338 // stay complete to avoid looping forever).
8339 finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP;
8340 isOurStatusComplete = effectiveContentBoxBSize == 0;
8341 } else {
8342 // We aren't going to be able to fit our content-box in the space available
8343 // to it, which means we'll probably call ourselves incomplete to request a
8344 // continuation. But before making that decision, we check for certain
8345 // conditions which would force us to overflow beyond the available space --
8346 // these might result in us actually being complete if we're forced to
8347 // overflow far enough.
8348 if (MOZ_UNLIKELY(aState.mReflowInput.mFlags.mIsTopOfPage && isBDBClone &&
8349 maxContentBoxBSize <= 0 &&
8350 aBEndEdgeOfChildren == blockStartBP)) {
8351 // In this rare case, we are at the top of page/column, we have
8352 // box-decoration-break:clone and zero available block-size for our
8353 // content-box (e.g. our own block-start border and padding already exceed
8354 // the available block-size), and we didn't lay out any child to consume
8355 // our content-box block-size. To ensure we make progress (avoid looping
8356 // forever), use 1px as our content-box block-size regardless of our
8357 // effective content-box block-size, in the spirit of
8358 // https://drafts.csswg.org/css-break/#breaking-rules.
8359 finalContentBoxBSizeWithBStartBP = blockStartBP + AppUnitsPerCSSPixel();
8360 isOurStatusComplete = effectiveContentBoxBSize <= AppUnitsPerCSSPixel();
8361 } else if (aBEndEdgeOfChildren > maxContentBoxBEnd) {
8362 // We have a unbreakable child whose block-end edge exceeds the available
8363 // block-size for children.
8364 if (aBEndEdgeOfChildren >= effectiveContentBoxBSizeWithBStartBP) {
8365 // The unbreakable child's block-end edge forces us to consume all of
8366 // our effective content-box block-size.
8367 finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP;
8369 // Even though we've consumed all of our effective content-box
8370 // block-size, we may still need to report an incomplete status in order
8371 // to get another continuation, which will be responsible for laying out
8372 // & drawing our block-end border & padding. But if we have no such
8373 // border & padding, or if we're forced to apply that border & padding
8374 // on this frame due to box-decoration-break:clone, then we don't need
8375 // to bother with that additional continuation.
8376 isOurStatusComplete = (isBDBClone || blockEndBP == 0);
8377 } else {
8378 // The unbreakable child's block-end edge doesn't force us to consume
8379 // all of our effective content-box block-size.
8380 finalContentBoxBSizeWithBStartBP = aBEndEdgeOfChildren;
8381 isOurStatusComplete = false;
8383 } else {
8384 // The children's block-end edge can fit in the content-box space that we
8385 // have available for it. Consume all the space that is available so that
8386 // our inline-start/inline-end borders extend all the way to the block-end
8387 // edge of column/page.
8388 finalContentBoxBSizeWithBStartBP = maxContentBoxBEnd;
8389 isOurStatusComplete = false;
8393 nscoord finalBorderBoxBSize = finalContentBoxBSizeWithBStartBP;
8394 if (isOurStatusComplete) {
8395 finalBorderBoxBSize = NSCoordSaturatingAdd(finalBorderBoxBSize, blockEndBP);
8396 if (isChildStatusComplete) {
8397 // We want to use children's reflow status as ours, which can be overflow
8398 // incomplete. Suppress the urge to call aBri.mReflowStatus.Reset() here.
8399 } else {
8400 aState.mReflowStatus.SetOverflowIncomplete();
8402 } else {
8403 NS_ASSERTION(!IsTrueOverflowContainer(),
8404 "An overflow container should always be complete because of "
8405 "its zero border-box block-size!");
8406 if (isBDBClone) {
8407 finalBorderBoxBSize =
8408 NSCoordSaturatingAdd(finalBorderBoxBSize, blockEndBP);
8410 aState.mReflowStatus.SetIncomplete();
8411 if (!GetNextInFlow()) {
8412 aState.mReflowStatus.SetNextInFlowNeedsReflow();
8416 return finalBorderBoxBSize;
8419 nsresult nsBlockFrame::ResolveBidi() {
8420 NS_ASSERTION(!GetPrevInFlow(),
8421 "ResolveBidi called on non-first continuation");
8422 MOZ_ASSERT(PresContext()->BidiEnabled());
8423 return nsBidiPresUtils::Resolve(this);
8426 void nsBlockFrame::UpdatePseudoElementStyles(ServoRestyleState& aRestyleState) {
8427 // first-letter needs to be updated before first-line, because first-line can
8428 // change the style of the first-letter.
8429 if (HasFirstLetterChild()) {
8430 UpdateFirstLetterStyle(aRestyleState);
8433 if (nsIFrame* firstLineFrame = GetFirstLineFrame()) {
8434 nsIFrame* styleParent = CorrectStyleParentFrame(firstLineFrame->GetParent(),
8435 PseudoStyleType::firstLine);
8437 ComputedStyle* parentStyle = styleParent->Style();
8438 RefPtr<ComputedStyle> firstLineStyle =
8439 aRestyleState.StyleSet().ResolvePseudoElementStyle(
8440 *mContent->AsElement(), PseudoStyleType::firstLine, nullptr,
8441 parentStyle);
8443 // FIXME(bz): Can we make first-line continuations be non-inheriting anon
8444 // boxes?
8445 RefPtr<ComputedStyle> continuationStyle =
8446 aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle(
8447 PseudoStyleType::mozLineFrame, parentStyle);
8449 UpdateStyleOfOwnedChildFrame(firstLineFrame, firstLineStyle, aRestyleState,
8450 Some(continuationStyle.get()));
8452 // We also want to update the styles of the first-line's descendants. We
8453 // don't need to compute a changehint for this, though, since any changes to
8454 // them are handled by the first-line anyway.
8455 RestyleManager* manager = PresContext()->RestyleManager();
8456 for (nsIFrame* kid : firstLineFrame->PrincipalChildList()) {
8457 manager->ReparentComputedStyleForFirstLine(kid);
8462 nsIFrame* nsBlockFrame::GetFirstLetter() const {
8463 if (!HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE)) {
8464 // Certainly no first-letter frame.
8465 return nullptr;
8468 return GetProperty(FirstLetterProperty());
8471 nsIFrame* nsBlockFrame::GetFirstLineFrame() const {
8472 nsIFrame* maybeFirstLine = PrincipalChildList().FirstChild();
8473 if (maybeFirstLine && maybeFirstLine->IsLineFrame()) {
8474 return maybeFirstLine;
8477 return nullptr;
8480 #ifdef DEBUG
8481 void nsBlockFrame::VerifyLines(bool aFinalCheckOK) {
8482 if (!gVerifyLines) {
8483 return;
8485 if (mLines.empty()) {
8486 return;
8489 nsLineBox* cursor = GetLineCursorForQuery();
8491 // Add up the counts on each line. Also validate that IsFirstLine is
8492 // set properly.
8493 int32_t count = 0;
8494 for (const auto& line : Lines()) {
8495 if (&line == cursor) {
8496 cursor = nullptr;
8498 if (aFinalCheckOK) {
8499 MOZ_ASSERT(line.GetChildCount(), "empty line");
8500 if (line.IsBlock()) {
8501 NS_ASSERTION(1 == line.GetChildCount(), "bad first line");
8504 count += line.GetChildCount();
8507 // Then count the frames
8508 int32_t frameCount = 0;
8509 nsIFrame* frame = mLines.front()->mFirstChild;
8510 while (frame) {
8511 frameCount++;
8512 frame = frame->GetNextSibling();
8514 NS_ASSERTION(count == frameCount, "bad line list");
8516 // Next: test that each line has right number of frames on it
8517 for (LineIterator line = LinesBegin(), line_end = LinesEnd();
8518 line != line_end;) {
8519 count = line->GetChildCount();
8520 frame = line->mFirstChild;
8521 while (--count >= 0) {
8522 frame = frame->GetNextSibling();
8524 ++line;
8525 if ((line != line_end) && (0 != line->GetChildCount())) {
8526 NS_ASSERTION(frame == line->mFirstChild, "bad line list");
8530 if (cursor) {
8531 FrameLines* overflowLines = GetOverflowLines();
8532 if (overflowLines) {
8533 LineIterator line = overflowLines->mLines.begin();
8534 LineIterator line_end = overflowLines->mLines.end();
8535 for (; line != line_end; ++line) {
8536 if (line == cursor) {
8537 cursor = nullptr;
8538 break;
8543 NS_ASSERTION(!cursor, "stale LineCursorProperty");
8546 void nsBlockFrame::VerifyOverflowSituation() {
8547 // Overflow out-of-flows must not have a next-in-flow in mFloats or mFrames.
8548 nsFrameList* oofs = GetOverflowOutOfFlows();
8549 if (oofs) {
8550 for (nsIFrame* f : *oofs) {
8551 nsIFrame* nif = f->GetNextInFlow();
8552 MOZ_ASSERT(!nif ||
8553 (!mFloats.ContainsFrame(nif) && !mFrames.ContainsFrame(nif)));
8557 // Pushed floats must not have a next-in-flow in mFloats or mFrames.
8558 oofs = GetPushedFloats();
8559 if (oofs) {
8560 for (nsIFrame* f : *oofs) {
8561 nsIFrame* nif = f->GetNextInFlow();
8562 MOZ_ASSERT(!nif ||
8563 (!mFloats.ContainsFrame(nif) && !mFrames.ContainsFrame(nif)));
8567 // A child float next-in-flow's parent must be |this| or a next-in-flow of
8568 // |this|. Later next-in-flows must have the same or later parents.
8569 ChildListID childLists[] = {FrameChildListID::Float,
8570 FrameChildListID::PushedFloats};
8571 for (size_t i = 0; i < ArrayLength(childLists); ++i) {
8572 const nsFrameList& children = GetChildList(childLists[i]);
8573 for (nsIFrame* f : children) {
8574 nsIFrame* parent = this;
8575 nsIFrame* nif = f->GetNextInFlow();
8576 for (; nif; nif = nif->GetNextInFlow()) {
8577 bool found = false;
8578 for (nsIFrame* p = parent; p; p = p->GetNextInFlow()) {
8579 if (nif->GetParent() == p) {
8580 parent = p;
8581 found = true;
8582 break;
8585 MOZ_ASSERT(
8586 found,
8587 "next-in-flow is a child of parent earlier in the frame tree?");
8592 nsBlockFrame* flow = static_cast<nsBlockFrame*>(FirstInFlow());
8593 while (flow) {
8594 FrameLines* overflowLines = flow->GetOverflowLines();
8595 if (overflowLines) {
8596 NS_ASSERTION(!overflowLines->mLines.empty(),
8597 "should not be empty if present");
8598 NS_ASSERTION(overflowLines->mLines.front()->mFirstChild,
8599 "bad overflow lines");
8600 NS_ASSERTION(overflowLines->mLines.front()->mFirstChild ==
8601 overflowLines->mFrames.FirstChild(),
8602 "bad overflow frames / lines");
8604 auto checkCursor = [&](nsLineBox* cursor) -> bool {
8605 if (!cursor) {
8606 return true;
8608 LineIterator line = flow->LinesBegin();
8609 LineIterator line_end = flow->LinesEnd();
8610 for (; line != line_end && line != cursor; ++line)
8612 if (line == line_end && overflowLines) {
8613 line = overflowLines->mLines.begin();
8614 line_end = overflowLines->mLines.end();
8615 for (; line != line_end && line != cursor; ++line)
8618 return line != line_end;
8620 MOZ_ASSERT(checkCursor(flow->GetLineCursorForDisplay()),
8621 "stale LineCursorPropertyDisplay");
8622 MOZ_ASSERT(checkCursor(flow->GetLineCursorForQuery()),
8623 "stale LineCursorPropertyQuery");
8624 flow = static_cast<nsBlockFrame*>(flow->GetNextInFlow());
8628 int32_t nsBlockFrame::GetDepth() const {
8629 int32_t depth = 0;
8630 nsIFrame* parent = GetParent();
8631 while (parent) {
8632 parent = parent->GetParent();
8633 depth++;
8635 return depth;
8638 already_AddRefed<ComputedStyle> nsBlockFrame::GetFirstLetterStyle(
8639 nsPresContext* aPresContext) {
8640 return aPresContext->StyleSet()->ProbePseudoElementStyle(
8641 *mContent->AsElement(), PseudoStyleType::firstLetter, nullptr, Style());
8643 #endif