Bug 1839315: part 4) Link from `SheetLoadData::mWasAlternate` to spec. r=emilio DONTBUILD
[gecko.git] / layout / generic / nsBlockFrame.cpp
blob5ad0f713e2ba72f8f0ac64cad4d70a45e0b5c8f3
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"
67 #include "nsBidiPresUtils.h"
69 #include <inttypes.h>
71 static const int MIN_LINES_NEEDING_CURSOR = 20;
73 using namespace mozilla;
74 using namespace mozilla::css;
75 using namespace mozilla::dom;
76 using namespace mozilla::layout;
77 using AbsPosReflowFlags = nsAbsoluteContainingBlock::AbsPosReflowFlags;
78 using ClearFloatsResult = BlockReflowState::ClearFloatsResult;
79 using ShapeType = nsFloatManager::ShapeType;
81 static void MarkAllDescendantLinesDirty(nsBlockFrame* aBlock) {
82 for (auto& line : aBlock->Lines()) {
83 if (line.IsBlock()) {
84 nsBlockFrame* bf = do_QueryFrame(line.mFirstChild);
85 if (bf) {
86 MarkAllDescendantLinesDirty(bf);
89 line.MarkDirty();
93 static void MarkSameFloatManagerLinesDirty(nsBlockFrame* aBlock) {
94 nsBlockFrame* blockWithFloatMgr = aBlock;
95 while (!blockWithFloatMgr->HasAnyStateBits(NS_BLOCK_FLOAT_MGR)) {
96 nsBlockFrame* bf = do_QueryFrame(blockWithFloatMgr->GetParent());
97 if (!bf) {
98 break;
100 blockWithFloatMgr = bf;
103 // Mark every line at and below the line where the float was
104 // dirty, and mark their lines dirty too. We could probably do
105 // something more efficient --- e.g., just dirty the lines that intersect
106 // the float vertically.
107 MarkAllDescendantLinesDirty(blockWithFloatMgr);
111 * Returns true if aFrame is a block that has one or more float children.
113 static bool BlockHasAnyFloats(nsIFrame* aFrame) {
114 nsBlockFrame* block = do_QueryFrame(aFrame);
115 if (!block) {
116 return false;
118 if (block->GetChildList(FrameChildListID::Float).FirstChild()) {
119 return true;
122 for (const auto& line : block->Lines()) {
123 if (line.IsBlock() && BlockHasAnyFloats(line.mFirstChild)) {
124 return true;
127 return false;
131 * Determines whether the given frame is visible or has
132 * visible children that participate in the same line. Frames
133 * that are not line participants do not have their
134 * children checked.
136 static bool FrameHasVisibleInlineContent(nsIFrame* aFrame) {
137 MOZ_ASSERT(aFrame, "Frame argument cannot be null");
139 if (aFrame->StyleVisibility()->IsVisible()) {
140 return true;
143 if (aFrame->IsFrameOfType(nsIFrame::eLineParticipant)) {
144 for (nsIFrame* kid : aFrame->PrincipalChildList()) {
145 if (kid->StyleVisibility()->IsVisible() ||
146 FrameHasVisibleInlineContent(kid)) {
147 return true;
151 return false;
155 * Determines whether any of the frames descended from the
156 * given line have inline content with 'visibility: visible'.
157 * This function calls FrameHasVisibleInlineContent to process
158 * each frame in the line's child list.
160 static bool LineHasVisibleInlineContent(nsLineBox* aLine) {
161 nsIFrame* kid = aLine->mFirstChild;
162 int32_t n = aLine->GetChildCount();
163 while (n-- > 0) {
164 if (FrameHasVisibleInlineContent(kid)) {
165 return true;
168 kid = kid->GetNextSibling();
171 return false;
175 * Iterates through the frame's in-flow children and
176 * unions the ink overflow of all text frames which
177 * participate in the line aFrame belongs to.
178 * If a child of aFrame is not a text frame,
179 * we recurse with the child as the aFrame argument.
180 * If aFrame isn't a line participant, we skip it entirely
181 * and return an empty rect.
182 * The resulting nsRect is offset relative to the parent of aFrame.
184 static nsRect GetFrameTextArea(nsIFrame* aFrame,
185 nsDisplayListBuilder* aBuilder) {
186 nsRect textArea;
187 if (const nsTextFrame* textFrame = do_QueryFrame(aFrame)) {
188 if (!textFrame->IsEntirelyWhitespace()) {
189 textArea = aFrame->InkOverflowRect();
191 } else if (aFrame->IsFrameOfType(nsIFrame::eLineParticipant)) {
192 for (nsIFrame* kid : aFrame->PrincipalChildList()) {
193 nsRect kidTextArea = GetFrameTextArea(kid, aBuilder);
194 textArea.OrWith(kidTextArea);
197 // add aFrame's position to keep textArea relative to aFrame's parent
198 return textArea + aFrame->GetPosition();
202 * Iterates through the line's children and
203 * unions the ink overflow of all text frames.
204 * GetFrameTextArea unions and returns the ink overflow
205 * from all line-participating text frames within the given child.
206 * The nsRect returned from GetLineTextArea is offset
207 * relative to the given line.
209 static nsRect GetLineTextArea(nsLineBox* aLine,
210 nsDisplayListBuilder* aBuilder) {
211 nsRect textArea;
212 nsIFrame* kid = aLine->mFirstChild;
213 int32_t n = aLine->GetChildCount();
214 while (n-- > 0) {
215 nsRect kidTextArea = GetFrameTextArea(kid, aBuilder);
216 textArea.OrWith(kidTextArea);
217 kid = kid->GetNextSibling();
220 return textArea;
224 * Starting with aFrame, iterates upward through parent frames and checks for
225 * non-transparent background colors. If one is found, we use that as our
226 * backplate color. Otheriwse, we use the default background color from
227 * our high contrast theme.
229 static nscolor GetBackplateColor(nsIFrame* aFrame) {
230 nsPresContext* pc = aFrame->PresContext();
231 nscolor currentBackgroundColor = NS_TRANSPARENT;
232 for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
233 // NOTE(emilio): We assume themed frames (frame->IsThemed()) have correct
234 // background-color information so as to compute the right backplate color.
236 // This holds because HTML widgets with author-specified backgrounds or
237 // borders disable theming. So as long as the UA-specified background colors
238 // match the actual theme (which they should because we always use system
239 // colors with the non-native theme, and native system colors should also
240 // match the native theme), then we're alright and we should compute an
241 // appropriate backplate color.
242 const auto* style = frame->Style();
243 if (style->StyleBackground()->IsTransparent(style)) {
244 continue;
246 bool drawImage = false, drawColor = false;
247 nscolor backgroundColor = nsCSSRendering::DetermineBackgroundColor(
248 pc, style, frame, drawImage, drawColor);
249 if (!drawColor && !drawImage) {
250 continue;
252 if (NS_GET_A(backgroundColor) == 0) {
253 // Even if there's a background image, if there's no background color we
254 // keep going up the frame tree, see bug 1723938.
255 continue;
257 if (NS_GET_A(currentBackgroundColor) == 0) {
258 // Try to avoid somewhat expensive math in the common case.
259 currentBackgroundColor = backgroundColor;
260 } else {
261 currentBackgroundColor =
262 NS_ComposeColors(backgroundColor, currentBackgroundColor);
264 if (NS_GET_A(currentBackgroundColor) == 0xff) {
265 // If fully opaque, we're done, otherwise keep going up blending with our
266 // background.
267 return currentBackgroundColor;
270 nscolor backgroundColor = aFrame->PresContext()->DefaultBackgroundColor();
271 if (NS_GET_A(currentBackgroundColor) == 0) {
272 return backgroundColor;
274 return NS_ComposeColors(backgroundColor, currentBackgroundColor);
277 #ifdef DEBUG
278 # include "nsBlockDebugFlags.h"
280 bool nsBlockFrame::gLamePaintMetrics;
281 bool nsBlockFrame::gLameReflowMetrics;
282 bool nsBlockFrame::gNoisy;
283 bool nsBlockFrame::gNoisyDamageRepair;
284 bool nsBlockFrame::gNoisyIntrinsic;
285 bool nsBlockFrame::gNoisyReflow;
286 bool nsBlockFrame::gReallyNoisyReflow;
287 bool nsBlockFrame::gNoisyFloatManager;
288 bool nsBlockFrame::gVerifyLines;
289 bool nsBlockFrame::gDisableResizeOpt;
291 int32_t nsBlockFrame::gNoiseIndent;
293 struct BlockDebugFlags {
294 const char* name;
295 bool* on;
298 static const BlockDebugFlags gFlags[] = {
299 {"reflow", &nsBlockFrame::gNoisyReflow},
300 {"really-noisy-reflow", &nsBlockFrame::gReallyNoisyReflow},
301 {"intrinsic", &nsBlockFrame::gNoisyIntrinsic},
302 {"float-manager", &nsBlockFrame::gNoisyFloatManager},
303 {"verify-lines", &nsBlockFrame::gVerifyLines},
304 {"damage-repair", &nsBlockFrame::gNoisyDamageRepair},
305 {"lame-paint-metrics", &nsBlockFrame::gLamePaintMetrics},
306 {"lame-reflow-metrics", &nsBlockFrame::gLameReflowMetrics},
307 {"disable-resize-opt", &nsBlockFrame::gDisableResizeOpt},
309 # define NUM_DEBUG_FLAGS (sizeof(gFlags) / sizeof(gFlags[0]))
311 static void ShowDebugFlags() {
312 printf("Here are the available GECKO_BLOCK_DEBUG_FLAGS:\n");
313 const BlockDebugFlags* bdf = gFlags;
314 const BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS;
315 for (; bdf < end; bdf++) {
316 printf(" %s\n", bdf->name);
318 printf("Note: GECKO_BLOCK_DEBUG_FLAGS is a comma separated list of flag\n");
319 printf("names (no whitespace)\n");
322 void nsBlockFrame::InitDebugFlags() {
323 static bool firstTime = true;
324 if (firstTime) {
325 firstTime = false;
326 char* flags = PR_GetEnv("GECKO_BLOCK_DEBUG_FLAGS");
327 if (flags) {
328 bool error = false;
329 for (;;) {
330 char* cm = strchr(flags, ',');
331 if (cm) {
332 *cm = '\0';
335 bool found = false;
336 const BlockDebugFlags* bdf = gFlags;
337 const BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS;
338 for (; bdf < end; bdf++) {
339 if (nsCRT::strcasecmp(bdf->name, flags) == 0) {
340 *(bdf->on) = true;
341 printf("nsBlockFrame: setting %s debug flag on\n", bdf->name);
342 gNoisy = true;
343 found = true;
344 break;
347 if (!found) {
348 error = true;
351 if (!cm) {
352 break;
354 *cm = ',';
355 flags = cm + 1;
357 if (error) {
358 ShowDebugFlags();
364 #endif
366 //----------------------------------------------------------------------
368 // Debugging support code
370 #ifdef DEBUG
371 const char* nsBlockFrame::kReflowCommandType[] = {
372 "ContentChanged", "StyleChanged", "ReflowDirty", "Timeout", "UserDefined",
375 const char* nsBlockFrame::LineReflowStatusToString(
376 LineReflowStatus aLineReflowStatus) const {
377 switch (aLineReflowStatus) {
378 case LineReflowStatus::OK:
379 return "LINE_REFLOW_OK";
380 case LineReflowStatus::Stop:
381 return "LINE_REFLOW_STOP";
382 case LineReflowStatus::RedoNoPull:
383 return "LINE_REFLOW_REDO_NO_PULL";
384 case LineReflowStatus::RedoMoreFloats:
385 return "LINE_REFLOW_REDO_MORE_FLOATS";
386 case LineReflowStatus::RedoNextBand:
387 return "LINE_REFLOW_REDO_NEXT_BAND";
388 case LineReflowStatus::Truncated:
389 return "LINE_REFLOW_TRUNCATED";
391 return "unknown";
394 #endif
396 #ifdef REFLOW_STATUS_COVERAGE
397 static void RecordReflowStatus(bool aChildIsBlock,
398 const nsReflowStatus& aFrameReflowStatus) {
399 static uint32_t record[2];
401 // 0: child-is-block
402 // 1: child-is-inline
403 int index = 0;
404 if (!aChildIsBlock) {
405 index |= 1;
408 // Compute new status
409 uint32_t newS = record[index];
410 if (aFrameReflowStatus.IsInlineBreak()) {
411 if (aFrameReflowStatus.IsInlineBreakBefore()) {
412 newS |= 1;
413 } else if (aFrameReflowStatus.IsIncomplete()) {
414 newS |= 2;
415 } else {
416 newS |= 4;
418 } else if (aFrameReflowStatus.IsIncomplete()) {
419 newS |= 8;
420 } else {
421 newS |= 16;
424 // Log updates to the status that yield different values
425 if (record[index] != newS) {
426 record[index] = newS;
427 printf("record(%d): %02x %02x\n", index, record[0], record[1]);
430 #endif
432 NS_DECLARE_FRAME_PROPERTY_WITH_DTOR_NEVER_CALLED(OverflowLinesProperty,
433 nsBlockFrame::FrameLines)
434 NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OverflowOutOfFlowsProperty)
435 NS_DECLARE_FRAME_PROPERTY_FRAMELIST(PushedFloatProperty)
436 NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OutsideMarkerProperty)
437 NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(InsideMarkerProperty, nsIFrame)
438 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(BlockEndEdgeOfChildrenProperty, nscoord)
440 //----------------------------------------------------------------------
442 nsBlockFrame* NS_NewBlockFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
443 return new (aPresShell) nsBlockFrame(aStyle, aPresShell->GetPresContext());
446 nsBlockFrame* NS_NewBlockFormattingContext(PresShell* aPresShell,
447 ComputedStyle* aComputedStyle) {
448 nsBlockFrame* blockFrame = NS_NewBlockFrame(aPresShell, aComputedStyle);
449 blockFrame->AddStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS);
450 return blockFrame;
453 NS_IMPL_FRAMEARENA_HELPERS(nsBlockFrame)
455 nsBlockFrame::~nsBlockFrame() = default;
457 void nsBlockFrame::AddSizeOfExcludingThisForTree(
458 nsWindowSizes& aWindowSizes) const {
459 nsContainerFrame::AddSizeOfExcludingThisForTree(aWindowSizes);
461 // Add the size of any nsLineBox::mFrames hashtables we might have:
462 for (const auto& line : Lines()) {
463 line.AddSizeOfExcludingThis(aWindowSizes);
465 const FrameLines* overflowLines = GetOverflowLines();
466 if (overflowLines) {
467 ConstLineIterator line = overflowLines->mLines.begin(),
468 line_end = overflowLines->mLines.end();
469 for (; line != line_end; ++line) {
470 line->AddSizeOfExcludingThis(aWindowSizes);
475 void nsBlockFrame::Destroy(DestroyContext& aContext) {
476 ClearLineCursors();
477 DestroyAbsoluteFrames(aContext);
478 mFloats.DestroyFrames(aContext);
479 nsPresContext* presContext = PresContext();
480 mozilla::PresShell* presShell = presContext->PresShell();
481 nsLineBox::DeleteLineList(presContext, mLines, &mFrames, aContext);
483 if (HasPushedFloats()) {
484 SafelyDestroyFrameListProp(aContext, presShell, PushedFloatProperty());
485 RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
488 // destroy overflow lines now
489 FrameLines* overflowLines = RemoveOverflowLines();
490 if (overflowLines) {
491 nsLineBox::DeleteLineList(presContext, overflowLines->mLines,
492 &overflowLines->mFrames, aContext);
493 delete overflowLines;
496 if (HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
497 SafelyDestroyFrameListProp(aContext, presShell,
498 OverflowOutOfFlowsProperty());
499 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
502 if (HasOutsideMarker()) {
503 SafelyDestroyFrameListProp(aContext, presShell, OutsideMarkerProperty());
504 RemoveStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER);
507 nsContainerFrame::Destroy(aContext);
510 /* virtual */
511 nsILineIterator* nsBlockFrame::GetLineIterator() {
512 nsLineIterator* iter = GetProperty(LineIteratorProperty());
513 if (!iter) {
514 const nsStyleVisibility* visibility = StyleVisibility();
515 iter = new nsLineIterator(mLines,
516 visibility->mDirection == StyleDirection::Rtl);
517 SetProperty(LineIteratorProperty(), iter);
519 return iter;
522 NS_QUERYFRAME_HEAD(nsBlockFrame)
523 NS_QUERYFRAME_ENTRY(nsBlockFrame)
524 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
526 #ifdef DEBUG_FRAME_DUMP
527 void nsBlockFrame::List(FILE* out, const char* aPrefix,
528 ListFlags aFlags) const {
529 nsCString str;
530 ListGeneric(str, aPrefix, aFlags);
532 fprintf_stderr(out, "%s <\n", str.get());
534 nsCString pfx(aPrefix);
535 pfx += " ";
537 // Output the lines
538 if (!mLines.empty()) {
539 ConstLineIterator line = LinesBegin(), line_end = LinesEnd();
540 for (; line != line_end; ++line) {
541 line->List(out, pfx.get(), aFlags);
545 // Output the overflow lines.
546 const FrameLines* overflowLines = GetOverflowLines();
547 if (overflowLines && !overflowLines->mLines.empty()) {
548 fprintf_stderr(out, "%sOverflow-lines %p/%p <\n", pfx.get(), overflowLines,
549 &overflowLines->mFrames);
550 nsCString nestedPfx(pfx);
551 nestedPfx += " ";
552 ConstLineIterator line = overflowLines->mLines.begin(),
553 line_end = overflowLines->mLines.end();
554 for (; line != line_end; ++line) {
555 line->List(out, nestedPfx.get(), aFlags);
557 fprintf_stderr(out, "%s>\n", pfx.get());
560 // skip the principal list - we printed the lines above
561 // skip the overflow list - we printed the overflow lines above
562 ChildListIDs skip = {FrameChildListID::Principal, FrameChildListID::Overflow};
563 ListChildLists(out, pfx.get(), aFlags, skip);
565 fprintf_stderr(out, "%s>\n", aPrefix);
568 nsresult nsBlockFrame::GetFrameName(nsAString& aResult) const {
569 return MakeFrameName(u"Block"_ns, aResult);
571 #endif
573 void nsBlockFrame::InvalidateFrame(uint32_t aDisplayItemKey,
574 bool aRebuildDisplayItems) {
575 if (IsInSVGTextSubtree()) {
576 NS_ASSERTION(GetParent()->IsSVGTextFrame(),
577 "unexpected block frame in SVG text");
578 GetParent()->InvalidateFrame();
579 return;
581 nsContainerFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems);
584 void nsBlockFrame::InvalidateFrameWithRect(const nsRect& aRect,
585 uint32_t aDisplayItemKey,
586 bool aRebuildDisplayItems) {
587 if (IsInSVGTextSubtree()) {
588 NS_ASSERTION(GetParent()->IsSVGTextFrame(),
589 "unexpected block frame in SVG text");
590 GetParent()->InvalidateFrame();
591 return;
593 nsContainerFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey,
594 aRebuildDisplayItems);
597 nscoord nsBlockFrame::SynthesizeFallbackBaseline(
598 WritingMode aWM, BaselineSharingGroup aBaselineGroup) const {
599 return Baseline::SynthesizeBOffsetFromMarginBox(this, aWM, aBaselineGroup);
602 template <typename LineIteratorType>
603 Maybe<nscoord> nsBlockFrame::GetBaselineBOffset(
604 LineIteratorType aStart, LineIteratorType aEnd, WritingMode aWM,
605 BaselineSharingGroup aBaselineGroup,
606 BaselineExportContext aExportContext) const {
607 MOZ_ASSERT((std::is_same_v<LineIteratorType, ConstLineIterator> &&
608 aBaselineGroup == BaselineSharingGroup::First) ||
609 (std::is_same_v<LineIteratorType, ConstReverseLineIterator> &&
610 aBaselineGroup == BaselineSharingGroup::Last),
611 "Iterator direction must match baseline sharing group.");
612 for (auto line = aStart; line != aEnd; ++line) {
613 if (!line->IsBlock()) {
614 // XXX Is this the right test? We have some bogus empty lines
615 // floating around, but IsEmpty is perhaps too weak.
616 if (line->BSize() != 0 || !line->IsEmpty()) {
617 const auto ascent = line->BStart() + line->GetLogicalAscent();
618 if (aBaselineGroup == BaselineSharingGroup::Last) {
619 return Some(BSize(aWM) - ascent);
621 return Some(ascent);
623 continue;
625 nsIFrame* kid = line->mFirstChild;
626 if (aWM.IsOrthogonalTo(kid->GetWritingMode())) {
627 continue;
629 if (aExportContext == BaselineExportContext::LineLayout &&
630 kid->IsTableWrapperFrame()) {
631 // `<table>` in inline-block context does not export any baseline.
632 continue;
634 const auto kidBaselineGroup =
635 aExportContext == BaselineExportContext::LineLayout
636 ? kid->GetDefaultBaselineSharingGroup()
637 : aBaselineGroup;
638 const auto kidBaseline =
639 kid->GetNaturalBaselineBOffset(aWM, kidBaselineGroup, aExportContext);
640 if (!kidBaseline) {
641 continue;
643 auto result = *kidBaseline;
644 if (kidBaselineGroup == BaselineSharingGroup::Last) {
645 result = kid->BSize(aWM) - result;
647 // Ignore relative positioning for baseline calculations.
648 const nsSize& sz = line->mContainerSize;
649 result += kid->GetLogicalNormalPosition(aWM, sz).B(aWM);
650 if (aBaselineGroup == BaselineSharingGroup::Last) {
651 return Some(BSize(aWM) - result);
653 return Some(result);
655 return Nothing{};
658 Maybe<nscoord> nsBlockFrame::GetNaturalBaselineBOffset(
659 WritingMode aWM, BaselineSharingGroup aBaselineGroup,
660 BaselineExportContext aExportContext) const {
661 if (StyleDisplay()->IsContainLayout()) {
662 return Nothing{};
665 if (aBaselineGroup == BaselineSharingGroup::First) {
666 return GetBaselineBOffset(LinesBegin(), LinesEnd(), aWM, aBaselineGroup,
667 aExportContext);
670 return GetBaselineBOffset(LinesRBegin(), LinesREnd(), aWM, aBaselineGroup,
671 aExportContext);
674 nscoord nsBlockFrame::GetCaretBaseline() const {
675 nsRect contentRect = GetContentRect();
676 nsMargin bp = GetUsedBorderAndPadding();
678 if (!mLines.empty()) {
679 ConstLineIterator line = LinesBegin();
680 if (!line->IsEmpty()) {
681 if (line->IsBlock()) {
682 return bp.top + line->mFirstChild->GetCaretBaseline();
684 return line->BStart() + line->GetLogicalAscent();
688 float inflation = nsLayoutUtils::FontSizeInflationFor(this);
689 RefPtr<nsFontMetrics> fm =
690 nsLayoutUtils::GetFontMetricsForFrame(this, inflation);
691 nscoord lineHeight = ReflowInput::CalcLineHeight(
692 *Style(), PresContext(), GetContent(), contentRect.height, inflation);
693 const WritingMode wm = GetWritingMode();
694 return nsLayoutUtils::GetCenteredFontBaseline(fm, lineHeight,
695 wm.IsLineInverted()) +
696 bp.top;
699 /////////////////////////////////////////////////////////////////////////////
700 // Child frame enumeration
702 const nsFrameList& nsBlockFrame::GetChildList(ChildListID aListID) const {
703 switch (aListID) {
704 case FrameChildListID::Principal:
705 return mFrames;
706 case FrameChildListID::Overflow: {
707 FrameLines* overflowLines = GetOverflowLines();
708 return overflowLines ? overflowLines->mFrames : nsFrameList::EmptyList();
710 case FrameChildListID::Float:
711 return mFloats;
712 case FrameChildListID::OverflowOutOfFlow: {
713 const nsFrameList* list = GetOverflowOutOfFlows();
714 return list ? *list : nsFrameList::EmptyList();
716 case FrameChildListID::PushedFloats: {
717 const nsFrameList* list = GetPushedFloats();
718 return list ? *list : nsFrameList::EmptyList();
720 case FrameChildListID::Bullet: {
721 const nsFrameList* list = GetOutsideMarkerList();
722 return list ? *list : nsFrameList::EmptyList();
724 default:
725 return nsContainerFrame::GetChildList(aListID);
729 void nsBlockFrame::GetChildLists(nsTArray<ChildList>* aLists) const {
730 nsContainerFrame::GetChildLists(aLists);
731 FrameLines* overflowLines = GetOverflowLines();
732 if (overflowLines) {
733 overflowLines->mFrames.AppendIfNonempty(aLists, FrameChildListID::Overflow);
735 const nsFrameList* list = GetOverflowOutOfFlows();
736 if (list) {
737 list->AppendIfNonempty(aLists, FrameChildListID::OverflowOutOfFlow);
739 mFloats.AppendIfNonempty(aLists, FrameChildListID::Float);
740 list = GetOutsideMarkerList();
741 if (list) {
742 list->AppendIfNonempty(aLists, FrameChildListID::Bullet);
744 list = GetPushedFloats();
745 if (list) {
746 list->AppendIfNonempty(aLists, FrameChildListID::PushedFloats);
750 /* virtual */
751 bool nsBlockFrame::IsFloatContainingBlock() const { return true; }
754 * Remove the first line from aFromLines and adjust the associated frame list
755 * aFromFrames accordingly. The removed line is assigned to *aOutLine and
756 * a frame list with its frames is assigned to *aOutFrames, i.e. the frames
757 * that were extracted from the head of aFromFrames.
758 * aFromLines must contain at least one line, the line may be empty.
759 * @return true if aFromLines becomes empty
761 static bool RemoveFirstLine(nsLineList& aFromLines, nsFrameList& aFromFrames,
762 nsLineBox** aOutLine, nsFrameList* aOutFrames) {
763 nsLineList_iterator removedLine = aFromLines.begin();
764 *aOutLine = removedLine;
765 nsLineList_iterator next = aFromLines.erase(removedLine);
766 bool isLastLine = next == aFromLines.end();
767 nsIFrame* firstFrameInNextLine = isLastLine ? nullptr : next->mFirstChild;
768 *aOutFrames = aFromFrames.TakeFramesBefore(firstFrameInNextLine);
769 return isLastLine;
772 //////////////////////////////////////////////////////////////////////
773 // Reflow methods
775 /* virtual */
776 void nsBlockFrame::MarkIntrinsicISizesDirty() {
777 nsBlockFrame* dirtyBlock = static_cast<nsBlockFrame*>(FirstContinuation());
778 dirtyBlock->mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
779 dirtyBlock->mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN;
780 if (!HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION)) {
781 for (nsIFrame* frame = dirtyBlock; frame;
782 frame = frame->GetNextContinuation()) {
783 frame->AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
787 nsContainerFrame::MarkIntrinsicISizesDirty();
790 void nsBlockFrame::CheckIntrinsicCacheAgainstShrinkWrapState() {
791 nsPresContext* presContext = PresContext();
792 if (!nsLayoutUtils::FontSizeInflationEnabled(presContext)) {
793 return;
795 bool inflationEnabled = !presContext->mInflationDisabledForShrinkWrap;
796 if (inflationEnabled != HasAnyStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED)) {
797 mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
798 mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN;
799 if (inflationEnabled) {
800 AddStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED);
801 } else {
802 RemoveStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED);
807 /* virtual */
808 nscoord nsBlockFrame::GetMinISize(gfxContext* aRenderingContext) {
809 nsIFrame* firstInFlow = FirstContinuation();
810 if (firstInFlow != this) {
811 return firstInFlow->GetMinISize(aRenderingContext);
814 DISPLAY_MIN_INLINE_SIZE(this, mCachedMinISize);
816 CheckIntrinsicCacheAgainstShrinkWrapState();
818 if (mCachedMinISize != NS_INTRINSIC_ISIZE_UNKNOWN) {
819 return mCachedMinISize;
822 if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
823 mCachedMinISize = *containISize;
824 return mCachedMinISize;
827 #ifdef DEBUG
828 if (gNoisyIntrinsic) {
829 IndentBy(stdout, gNoiseIndent);
830 ListTag(stdout);
831 printf(": GetMinISize\n");
833 AutoNoisyIndenter indenter(gNoisyIntrinsic);
834 #endif
836 for (nsBlockFrame* curFrame = this; curFrame;
837 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
838 curFrame->LazyMarkLinesDirty();
841 if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
842 PresContext()->BidiEnabled()) {
843 ResolveBidi();
846 const bool whiteSpaceCanWrap = StyleText()->WhiteSpaceCanWrapStyle();
847 InlineMinISizeData data;
848 for (nsBlockFrame* curFrame = this; curFrame;
849 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
850 for (LineIterator line = curFrame->LinesBegin(),
851 line_end = curFrame->LinesEnd();
852 line != line_end; ++line) {
853 #ifdef DEBUG
854 if (gNoisyIntrinsic) {
855 IndentBy(stdout, gNoiseIndent);
856 printf("line (%s%s)\n", line->IsBlock() ? "block" : "inline",
857 line->IsEmpty() ? ", empty" : "");
859 AutoNoisyIndenter lineindent(gNoisyIntrinsic);
860 #endif
861 if (line->IsBlock()) {
862 data.ForceBreak();
863 data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(
864 aRenderingContext, line->mFirstChild, IntrinsicISizeType::MinISize);
865 data.ForceBreak();
866 } else {
867 if (!curFrame->GetPrevContinuation() &&
868 line == curFrame->LinesBegin()) {
869 data.mCurrentLine += StyleText()->mTextIndent.Resolve(0);
871 data.mLine = &line;
872 data.SetLineContainer(curFrame);
873 nsIFrame* kid = line->mFirstChild;
874 for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end;
875 ++i, kid = kid->GetNextSibling()) {
876 kid->AddInlineMinISize(aRenderingContext, &data);
877 if (whiteSpaceCanWrap && data.mTrailingWhitespace) {
878 data.OptionallyBreak();
882 #ifdef DEBUG
883 if (gNoisyIntrinsic) {
884 IndentBy(stdout, gNoiseIndent);
885 printf("min: [prevLines=%d currentLine=%d]\n", data.mPrevLines,
886 data.mCurrentLine);
888 #endif
891 data.ForceBreak();
893 mCachedMinISize = data.mPrevLines;
894 return mCachedMinISize;
897 /* virtual */
898 nscoord nsBlockFrame::GetPrefISize(gfxContext* aRenderingContext) {
899 nsIFrame* firstInFlow = FirstContinuation();
900 if (firstInFlow != this) {
901 return firstInFlow->GetPrefISize(aRenderingContext);
904 DISPLAY_PREF_INLINE_SIZE(this, mCachedPrefISize);
906 CheckIntrinsicCacheAgainstShrinkWrapState();
908 if (mCachedPrefISize != NS_INTRINSIC_ISIZE_UNKNOWN) {
909 return mCachedPrefISize;
912 if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
913 mCachedPrefISize = *containISize;
914 return mCachedPrefISize;
917 #ifdef DEBUG
918 if (gNoisyIntrinsic) {
919 IndentBy(stdout, gNoiseIndent);
920 ListTag(stdout);
921 printf(": GetPrefISize\n");
923 AutoNoisyIndenter indenter(gNoisyIntrinsic);
924 #endif
926 for (nsBlockFrame* curFrame = this; curFrame;
927 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
928 curFrame->LazyMarkLinesDirty();
931 if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
932 PresContext()->BidiEnabled()) {
933 ResolveBidi();
935 InlinePrefISizeData data;
936 for (nsBlockFrame* curFrame = this; curFrame;
937 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
938 for (LineIterator line = curFrame->LinesBegin(),
939 line_end = curFrame->LinesEnd();
940 line != line_end; ++line) {
941 #ifdef DEBUG
942 if (gNoisyIntrinsic) {
943 IndentBy(stdout, gNoiseIndent);
944 printf("line (%s%s)\n", line->IsBlock() ? "block" : "inline",
945 line->IsEmpty() ? ", empty" : "");
947 AutoNoisyIndenter lineindent(gNoisyIntrinsic);
948 #endif
949 if (line->IsBlock()) {
950 StyleClear clearType;
951 if (!data.mLineIsEmpty || BlockCanIntersectFloats(line->mFirstChild)) {
952 clearType = StyleClear::Both;
953 } else {
954 clearType = line->mFirstChild->StyleDisplay()->mClear;
956 data.ForceBreak(clearType);
957 data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(
958 aRenderingContext, line->mFirstChild,
959 IntrinsicISizeType::PrefISize);
960 data.ForceBreak();
961 } else {
962 if (!curFrame->GetPrevContinuation() &&
963 line == curFrame->LinesBegin()) {
964 nscoord indent = StyleText()->mTextIndent.Resolve(0);
965 data.mCurrentLine += indent;
966 // XXXmats should the test below be indent > 0?
967 if (indent != nscoord(0)) {
968 data.mLineIsEmpty = false;
971 data.mLine = &line;
972 data.SetLineContainer(curFrame);
973 nsIFrame* kid = line->mFirstChild;
974 for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end;
975 ++i, kid = kid->GetNextSibling()) {
976 kid->AddInlinePrefISize(aRenderingContext, &data);
979 #ifdef DEBUG
980 if (gNoisyIntrinsic) {
981 IndentBy(stdout, gNoiseIndent);
982 printf("pref: [prevLines=%d currentLine=%d]\n", data.mPrevLines,
983 data.mCurrentLine);
985 #endif
988 data.ForceBreak();
990 mCachedPrefISize = data.mPrevLines;
991 return mCachedPrefISize;
994 nsRect nsBlockFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const {
995 // be conservative
996 if (Style()->HasTextDecorationLines()) {
997 return InkOverflowRect();
999 return ComputeSimpleTightBounds(aDrawTarget);
1002 /* virtual */
1003 nsresult nsBlockFrame::GetPrefWidthTightBounds(gfxContext* aRenderingContext,
1004 nscoord* aX, nscoord* aXMost) {
1005 nsIFrame* firstInFlow = FirstContinuation();
1006 if (firstInFlow != this) {
1007 return firstInFlow->GetPrefWidthTightBounds(aRenderingContext, aX, aXMost);
1010 *aX = 0;
1011 *aXMost = 0;
1013 nsresult rv;
1014 InlinePrefISizeData data;
1015 for (nsBlockFrame* curFrame = this; curFrame;
1016 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
1017 for (LineIterator line = curFrame->LinesBegin(),
1018 line_end = curFrame->LinesEnd();
1019 line != line_end; ++line) {
1020 nscoord childX, childXMost;
1021 if (line->IsBlock()) {
1022 data.ForceBreak();
1023 rv = line->mFirstChild->GetPrefWidthTightBounds(aRenderingContext,
1024 &childX, &childXMost);
1025 NS_ENSURE_SUCCESS(rv, rv);
1026 *aX = std::min(*aX, childX);
1027 *aXMost = std::max(*aXMost, childXMost);
1028 } else {
1029 if (!curFrame->GetPrevContinuation() &&
1030 line == curFrame->LinesBegin()) {
1031 data.mCurrentLine += StyleText()->mTextIndent.Resolve(0);
1033 data.mLine = &line;
1034 data.SetLineContainer(curFrame);
1035 nsIFrame* kid = line->mFirstChild;
1036 for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end;
1037 ++i, kid = kid->GetNextSibling()) {
1038 rv = kid->GetPrefWidthTightBounds(aRenderingContext, &childX,
1039 &childXMost);
1040 NS_ENSURE_SUCCESS(rv, rv);
1041 *aX = std::min(*aX, data.mCurrentLine + childX);
1042 *aXMost = std::max(*aXMost, data.mCurrentLine + childXMost);
1043 kid->AddInlinePrefISize(aRenderingContext, &data);
1048 data.ForceBreak();
1050 return NS_OK;
1054 * Return whether aNewAvailableSpace is smaller *on either side*
1055 * (inline-start or inline-end) than aOldAvailableSpace, so that we know
1056 * if we need to redo layout on an line, replaced block, or block
1057 * formatting context, because its height (which we used to compute
1058 * aNewAvailableSpace) caused it to intersect additional floats.
1060 static bool AvailableSpaceShrunk(WritingMode aWM,
1061 const LogicalRect& aOldAvailableSpace,
1062 const LogicalRect& aNewAvailableSpace,
1063 bool aCanGrow /* debug-only */) {
1064 if (aNewAvailableSpace.ISize(aWM) == 0) {
1065 // Positions are not significant if the inline size is zero.
1066 return aOldAvailableSpace.ISize(aWM) != 0;
1068 if (aCanGrow) {
1069 NS_ASSERTION(
1070 aNewAvailableSpace.IStart(aWM) <= aOldAvailableSpace.IStart(aWM) ||
1071 aNewAvailableSpace.IEnd(aWM) <= aOldAvailableSpace.IEnd(aWM),
1072 "available space should not shrink on the start side and "
1073 "grow on the end side");
1074 NS_ASSERTION(
1075 aNewAvailableSpace.IStart(aWM) >= aOldAvailableSpace.IStart(aWM) ||
1076 aNewAvailableSpace.IEnd(aWM) >= aOldAvailableSpace.IEnd(aWM),
1077 "available space should not grow on the start side and "
1078 "shrink on the end side");
1079 } else {
1080 NS_ASSERTION(
1081 aOldAvailableSpace.IStart(aWM) <= aNewAvailableSpace.IStart(aWM) &&
1082 aOldAvailableSpace.IEnd(aWM) >= aNewAvailableSpace.IEnd(aWM),
1083 "available space should never grow");
1085 // Have we shrunk on either side?
1086 return aNewAvailableSpace.IStart(aWM) > aOldAvailableSpace.IStart(aWM) ||
1087 aNewAvailableSpace.IEnd(aWM) < aOldAvailableSpace.IEnd(aWM);
1090 static LogicalSize CalculateContainingBlockSizeForAbsolutes(
1091 WritingMode aWM, const ReflowInput& aReflowInput, LogicalSize aFrameSize) {
1092 // The issue here is that for a 'height' of 'auto' the reflow input
1093 // code won't know how to calculate the containing block height
1094 // because it's calculated bottom up. So we use our own computed
1095 // size as the dimensions.
1096 nsIFrame* frame = aReflowInput.mFrame;
1098 LogicalSize cbSize(aFrameSize);
1099 // Containing block is relative to the padding edge
1100 const LogicalMargin border = aReflowInput.ComputedLogicalBorder(aWM);
1101 cbSize.ISize(aWM) -= border.IStartEnd(aWM);
1102 cbSize.BSize(aWM) -= border.BStartEnd(aWM);
1104 if (frame->GetParent()->GetContent() != frame->GetContent() ||
1105 frame->GetParent()->IsCanvasFrame()) {
1106 return cbSize;
1109 // We are a wrapped frame for the content (and the wrapper is not the
1110 // canvas frame, whose size is not meaningful here).
1111 // Use the container's dimensions, if they have been precomputed.
1112 // XXX This is a hack! We really should be waiting until the outermost
1113 // frame is fully reflowed and using the resulting dimensions, even
1114 // if they're intrinsic.
1115 // In fact we should be attaching absolute children to the outermost
1116 // frame and not always sticking them in block frames.
1118 // First, find the reflow input for the outermost frame for this content.
1119 const ReflowInput* lastRI = &aReflowInput;
1120 DebugOnly<const ReflowInput*> lastButOneRI = &aReflowInput;
1121 while (lastRI->mParentReflowInput &&
1122 lastRI->mParentReflowInput->mFrame->GetContent() ==
1123 frame->GetContent()) {
1124 lastButOneRI = lastRI;
1125 lastRI = lastRI->mParentReflowInput;
1128 if (lastRI == &aReflowInput) {
1129 return cbSize;
1132 // For scroll containers, we can just use cbSize (which is the padding-box
1133 // size of the scrolled-content frame).
1134 if (nsIScrollableFrame* scrollFrame = do_QueryFrame(lastRI->mFrame)) {
1135 // Assert that we're not missing any frames between the abspos containing
1136 // block and the scroll container.
1137 // the parent.
1138 Unused << scrollFrame;
1139 MOZ_ASSERT(lastButOneRI == &aReflowInput);
1140 return cbSize;
1143 // Same for fieldsets, where the inner anonymous frame has the correct padding
1144 // area with the legend taken into account.
1145 if (lastRI->mFrame->IsFieldSetFrame()) {
1146 return cbSize;
1149 // We found a reflow input for the outermost wrapping frame, so use
1150 // its computed metrics if available, converted to our writing mode
1151 const LogicalSize lastRISize = lastRI->ComputedSize(aWM);
1152 const LogicalMargin lastRIPadding = lastRI->ComputedLogicalPadding(aWM);
1153 if (lastRISize.ISize(aWM) != NS_UNCONSTRAINEDSIZE) {
1154 cbSize.ISize(aWM) =
1155 std::max(0, lastRISize.ISize(aWM) + lastRIPadding.IStartEnd(aWM));
1157 if (lastRISize.BSize(aWM) != NS_UNCONSTRAINEDSIZE) {
1158 cbSize.BSize(aWM) =
1159 std::max(0, lastRISize.BSize(aWM) + lastRIPadding.BStartEnd(aWM));
1162 return cbSize;
1166 * Returns aFrame if it is a non-BFC block frame, and null otherwise.
1168 * This is used to determine whether to recurse into aFrame when applying
1169 * -webkit-line-clamp.
1171 static const nsBlockFrame* GetAsLineClampDescendant(const nsIFrame* aFrame) {
1172 if (const nsBlockFrame* block = do_QueryFrame(aFrame)) {
1173 if (!block->HasAllStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS)) {
1174 return block;
1177 return nullptr;
1180 static nsBlockFrame* GetAsLineClampDescendant(nsIFrame* aFrame) {
1181 return const_cast<nsBlockFrame*>(
1182 GetAsLineClampDescendant(const_cast<const nsIFrame*>(aFrame)));
1185 static bool IsLineClampRoot(const nsBlockFrame* aFrame) {
1186 if (!aFrame->StyleDisplay()->mWebkitLineClamp) {
1187 return false;
1190 if (!aFrame->HasAllStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS)) {
1191 return false;
1194 if (StaticPrefs::layout_css_webkit_line_clamp_block_enabled()) {
1195 return true;
1198 // For now, -webkit-box is the only thing allowed to be a line-clamp root.
1199 // Ideally we'd just make this work everywhere, but for now we're carrying
1200 // this forward as a limitation on the legacy -webkit-line-clamp feature,
1201 // since relaxing this limitation might create webcompat trouble.
1202 auto origDisplay = [&] {
1203 if (aFrame->Style()->GetPseudoType() == PseudoStyleType::scrolledContent) {
1204 // If we're the anonymous block inside the scroll frame, we need to look
1205 // at the original display of our parent frame.
1206 MOZ_ASSERT(aFrame->GetParent());
1207 const auto& parentDisp = *aFrame->GetParent()->StyleDisplay();
1208 MOZ_ASSERT(parentDisp.mWebkitLineClamp ==
1209 aFrame->StyleDisplay()->mWebkitLineClamp,
1210 ":-moz-scrolled-content should inherit -webkit-line-clamp, "
1211 "via rule in UA stylesheet");
1212 return parentDisp.mOriginalDisplay;
1214 return aFrame->StyleDisplay()->mOriginalDisplay;
1215 }();
1216 return origDisplay.Inside() == StyleDisplayInside::WebkitBox;
1219 bool nsBlockFrame::IsInLineClampContext() const {
1220 if (IsLineClampRoot(this)) {
1221 return true;
1223 const nsBlockFrame* cur = this;
1224 while (GetAsLineClampDescendant(cur)) {
1225 cur = do_QueryFrame(cur->GetParent());
1226 if (!cur) {
1227 return false;
1229 if (IsLineClampRoot(cur)) {
1230 return true;
1233 return false;
1237 * Iterator over all descendant inline line boxes, except for those that are
1238 * under an independent formatting context.
1240 class MOZ_RAII LineClampLineIterator {
1241 public:
1242 explicit LineClampLineIterator(nsBlockFrame* aFrame)
1243 : mCur(aFrame->LinesBegin()),
1244 mEnd(aFrame->LinesEnd()),
1245 mCurrentFrame(mCur == mEnd ? nullptr : aFrame) {
1246 if (mCur != mEnd && !mCur->IsInline()) {
1247 Advance();
1251 nsLineBox* GetCurrentLine() { return mCurrentFrame ? mCur.get() : nullptr; }
1252 nsBlockFrame* GetCurrentFrame() { return mCurrentFrame; }
1254 // Advances the iterator to the next line line.
1256 // Next() shouldn't be called once the iterator is at the end, which can be
1257 // checked for by GetCurrentLine() or GetCurrentFrame() returning null.
1258 void Next() {
1259 MOZ_ASSERT(mCur != mEnd && mCurrentFrame,
1260 "Don't call Next() when the iterator is at the end");
1261 ++mCur;
1262 Advance();
1265 private:
1266 void Advance() {
1267 for (;;) {
1268 if (mCur == mEnd) {
1269 // Reached the end of the current block. Pop the parent off the
1270 // stack; if there isn't one, then we've reached the end.
1271 if (mStack.IsEmpty()) {
1272 mCurrentFrame = nullptr;
1273 break;
1275 auto entry = mStack.PopLastElement();
1276 mCurrentFrame = entry.first;
1277 mCur = entry.second;
1278 mEnd = mCurrentFrame->LinesEnd();
1279 } else if (mCur->IsBlock()) {
1280 if (nsBlockFrame* child = GetAsLineClampDescendant(mCur->mFirstChild)) {
1281 nsBlockFrame::LineIterator next = mCur;
1282 ++next;
1283 mStack.AppendElement(std::make_pair(mCurrentFrame, next));
1284 mCur = child->LinesBegin();
1285 mEnd = child->LinesEnd();
1286 mCurrentFrame = child;
1287 } else {
1288 // Some kind of frame we shouldn't descend into.
1289 ++mCur;
1291 } else {
1292 MOZ_ASSERT(mCur->IsInline());
1293 break;
1298 // The current line within the current block.
1300 // When this is equal to mEnd, the iterator is at its end, and mCurrentFrame
1301 // is set to null.
1302 nsBlockFrame::LineIterator mCur;
1304 // The iterator end for the current block.
1305 nsBlockFrame::LineIterator mEnd;
1307 // The current block.
1308 nsBlockFrame* mCurrentFrame;
1310 // Stack of mCurrentFrame and mEnd values that we push and pop as we enter and
1311 // exist blocks.
1312 AutoTArray<std::pair<nsBlockFrame*, nsBlockFrame::LineIterator>, 8> mStack;
1315 static bool ClearLineClampEllipsis(nsBlockFrame* aFrame) {
1316 if (!aFrame->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS)) {
1317 for (nsIFrame* f : aFrame->PrincipalChildList()) {
1318 if (nsBlockFrame* child = GetAsLineClampDescendant(f)) {
1319 if (ClearLineClampEllipsis(child)) {
1320 return true;
1324 return false;
1327 aFrame->RemoveStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS);
1329 for (auto& line : aFrame->Lines()) {
1330 if (line.HasLineClampEllipsis()) {
1331 line.ClearHasLineClampEllipsis();
1332 return true;
1336 // We didn't find a line with the ellipsis; it must have been deleted already.
1337 return true;
1340 void nsBlockFrame::ClearLineClampEllipsis() { ::ClearLineClampEllipsis(this); }
1342 void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
1343 const ReflowInput& aReflowInput,
1344 nsReflowStatus& aStatus) {
1345 if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
1346 FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
1347 return;
1350 MarkInReflow();
1351 DO_GLOBAL_REFLOW_COUNT("nsBlockFrame");
1352 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
1353 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
1355 #ifdef DEBUG
1356 if (gNoisyReflow) {
1357 IndentBy(stdout, gNoiseIndent);
1358 ListTag(stdout);
1359 printf(": begin reflow availSize=%d,%d computedSize=%d,%d\n",
1360 aReflowInput.AvailableISize(), aReflowInput.AvailableBSize(),
1361 aReflowInput.ComputedISize(), aReflowInput.ComputedBSize());
1363 AutoNoisyIndenter indent(gNoisy);
1364 PRTime start = 0; // Initialize these variablies to silence the compiler.
1365 int32_t ctc = 0; // We only use these if they are set (gLameReflowMetrics).
1366 if (gLameReflowMetrics) {
1367 start = PR_Now();
1368 ctc = nsLineBox::GetCtorCount();
1370 #endif
1372 // ColumnSetWrapper's children depend on ColumnSetWrapper's block-size or
1373 // max-block-size because both affect the children's available block-size.
1374 if (IsColumnSetWrapperFrame()) {
1375 AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
1378 Maybe<nscoord> restoreReflowInputAvailBSize;
1379 auto MaybeRestore = MakeScopeExit([&] {
1380 if (MOZ_UNLIKELY(restoreReflowInputAvailBSize)) {
1381 const_cast<ReflowInput&>(aReflowInput)
1382 .SetAvailableBSize(*restoreReflowInputAvailBSize);
1386 WritingMode wm = aReflowInput.GetWritingMode();
1387 const nscoord consumedBSize = CalcAndCacheConsumedBSize();
1388 const nscoord effectiveContentBoxBSize =
1389 GetEffectiveComputedBSize(aReflowInput, consumedBSize);
1390 // If we have non-auto block size, we're clipping our kids and we fit,
1391 // make sure our kids fit too.
1392 const PhysicalAxes physicalBlockAxis =
1393 wm.IsVertical() ? PhysicalAxes::Horizontal : PhysicalAxes::Vertical;
1394 if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
1395 aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE &&
1396 (ShouldApplyOverflowClipping(aReflowInput.mStyleDisplay) &
1397 physicalBlockAxis)) {
1398 LogicalMargin blockDirExtras =
1399 aReflowInput.ComputedLogicalBorderPadding(wm);
1400 if (GetLogicalSkipSides().BStart()) {
1401 blockDirExtras.BStart(wm) = 0;
1402 } else {
1403 // Block-end margin never causes us to create continuations, so we
1404 // don't need to worry about whether it fits in its entirety.
1405 blockDirExtras.BStart(wm) +=
1406 aReflowInput.ComputedLogicalMargin(wm).BStart(wm);
1409 if (effectiveContentBoxBSize + blockDirExtras.BStartEnd(wm) <=
1410 aReflowInput.AvailableBSize()) {
1411 restoreReflowInputAvailBSize.emplace(aReflowInput.AvailableBSize());
1412 const_cast<ReflowInput&>(aReflowInput)
1413 .SetAvailableBSize(NS_UNCONSTRAINEDSIZE);
1417 if (IsFrameTreeTooDeep(aReflowInput, aMetrics, aStatus)) {
1418 return;
1421 // OK, some lines may be reflowed. Blow away any saved line cursor
1422 // because we may invalidate the nondecreasing
1423 // overflowArea.InkOverflow().y/yMost invariant, and we may even
1424 // delete the line with the line cursor.
1425 ClearLineCursors();
1427 // See comment below about oldSize. Use *only* for the
1428 // abs-pos-containing-block-size-change optimization!
1429 nsSize oldSize = GetSize();
1431 // Should we create a float manager?
1432 nsAutoFloatManager autoFloatManager(const_cast<ReflowInput&>(aReflowInput));
1434 // XXXldb If we start storing the float manager in the frame rather
1435 // than keeping it around only during reflow then we should create it
1436 // only when there are actually floats to manage. Otherwise things
1437 // like tables will gain significant bloat.
1438 bool needFloatManager = nsBlockFrame::BlockNeedsFloatManager(this);
1439 if (needFloatManager) {
1440 autoFloatManager.CreateFloatManager(aPresContext);
1443 if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
1444 PresContext()->BidiEnabled()) {
1445 static_cast<nsBlockFrame*>(FirstContinuation())->ResolveBidi();
1448 // Whether to apply text-wrap: balance behavior.
1449 bool tryBalance = StyleText()->mTextWrap == StyleTextWrap::Balance &&
1450 !GetPrevContinuation();
1452 // Struct used to hold the "target" number of lines or clamp position to
1453 // maintain when doing text-wrap: balance.
1454 struct BalanceTarget {
1455 // If line-clamp is in effect, mContent and mOffset indicate the starting
1456 // position of the first line after the clamp limit. If line-clamp is not
1457 // in use, mContent is null and mOffset is the total number of lines that
1458 // the block must contain.
1459 nsIContent* mContent = nullptr;
1460 int32_t mOffset = -1;
1462 bool operator==(const BalanceTarget& aOther) const {
1463 return mContent == aOther.mContent && mOffset == aOther.mOffset;
1465 bool operator!=(const BalanceTarget& aOther) const {
1466 return !(*this == aOther);
1470 BalanceTarget balanceTarget;
1472 // Helpers for text-wrap: balance implementation:
1474 // Count the number of lines in the mLines list, but return -1 instead if the
1475 // count is going to exceed aLimit.
1476 auto countLinesUpTo = [&](int32_t aLimit) -> int32_t {
1477 int32_t n = 0;
1478 for (auto iter = mLines.begin(); iter != mLines.end(); ++iter) {
1479 if (++n > aLimit) {
1480 return -1;
1483 return n;
1486 // Return a BalanceTarget record representing the position at which line-clamp
1487 // will take effect for the current line list. Only to be used when there are
1488 // enough lines that the clamp will apply.
1489 auto getClampPosition = [&](uint32_t aClampCount) -> BalanceTarget {
1490 MOZ_ASSERT(aClampCount < mLines.size());
1491 auto iter = mLines.begin();
1492 for (uint32_t i = 0; i < aClampCount; i++) {
1493 ++iter;
1495 nsIFrame* firstChild = iter->mFirstChild;
1496 if (!firstChild) {
1497 return BalanceTarget{};
1499 nsIContent* content = firstChild->GetContent();
1500 if (!content) {
1501 return BalanceTarget{};
1503 int32_t offset = 0;
1504 if (firstChild->IsTextFrame()) {
1505 auto* textFrame = static_cast<nsTextFrame*>(firstChild);
1506 offset = textFrame->GetContentOffset();
1508 return BalanceTarget{content, offset};
1511 // "balancing" is implemented by shortening the effective inline-size of the
1512 // lines, so that content will tend to be pushed down to fill later lines of
1513 // the block. `balanceInset` is the current amount of "inset" to apply, and
1514 // `balanceStep` is the increment to adjust it by for the next iteration.
1515 nscoord balanceStep = 0;
1517 // text-wrap: balance loop, executed only once if balancing is not required.
1518 nsReflowStatus reflowStatus;
1519 TrialReflowState trialState(consumedBSize, effectiveContentBoxBSize,
1520 needFloatManager);
1521 while (true) {
1522 // Save the initial floatManager state for repeated trial reflows.
1523 // We'll restore (and re-save) the initial state each time we repeat the
1524 // reflow.
1525 nsFloatManager::SavedState floatManagerState;
1526 aReflowInput.mFloatManager->PushState(&floatManagerState);
1528 aMetrics = ReflowOutput(aMetrics.GetWritingMode());
1529 reflowStatus =
1530 TrialReflow(aPresContext, aMetrics, aReflowInput, trialState);
1532 // Do we need to start a `text-wrap: balance` iteration?
1533 if (tryBalance) {
1534 tryBalance = false;
1535 // Don't try to balance an incomplete block.
1536 if (!reflowStatus.IsFullyComplete()) {
1537 break;
1539 balanceTarget.mOffset =
1540 countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit());
1541 if (balanceTarget.mOffset < 2) {
1542 // If there are less than 2 lines, or the number exceeds the limit,
1543 // no balancing is needed; just break from the balance loop.
1544 break;
1546 // Initialize the amount of inset to try, and the iteration step size.
1547 balanceStep = aReflowInput.ComputedISize() / balanceTarget.mOffset;
1548 trialState.ResetForBalance(balanceStep);
1549 balanceStep /= 2;
1551 // If -webkit-line-clamp is in effect, then we need to maintain the
1552 // content location at which clamping occurs, rather than the total
1553 // number of lines in the block.
1554 if (StaticPrefs::layout_css_text_wrap_balance_after_clamp_enabled() &&
1555 IsLineClampRoot(this)) {
1556 uint32_t lineClampCount = aReflowInput.mStyleDisplay->mWebkitLineClamp;
1557 if (uint32_t(balanceTarget.mOffset) > lineClampCount) {
1558 auto t = getClampPosition(lineClampCount);
1559 if (t.mContent) {
1560 balanceTarget = t;
1565 // Restore initial floatManager state for a new trial with updated inset.
1566 aReflowInput.mFloatManager->PopState(&floatManagerState);
1567 continue;
1570 // Helper to determine whether the current trial succeeded (i.e. was able
1571 // to fit the content into the expected number of lines).
1572 auto trialSucceeded = [&]() -> bool {
1573 if (!reflowStatus.IsFullyComplete()) {
1574 return false;
1576 if (balanceTarget.mContent) {
1577 auto t = getClampPosition(aReflowInput.mStyleDisplay->mWebkitLineClamp);
1578 return t == balanceTarget;
1580 int32_t numLines =
1581 countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit());
1582 return numLines == balanceTarget.mOffset;
1585 // If we're in the process of a balance operation, check whether we've
1586 // inset by too much and either increase or reduce the inset for the next
1587 // iteration.
1588 if (balanceStep > 0) {
1589 if (trialSucceeded()) {
1590 trialState.ResetForBalance(balanceStep);
1591 } else {
1592 trialState.ResetForBalance(-balanceStep);
1594 balanceStep /= 2;
1596 aReflowInput.mFloatManager->PopState(&floatManagerState);
1597 continue;
1600 // If we were attempting to balance, check whether the final iteration was
1601 // successful, and if not, back up by one step.
1602 if (balanceTarget.mOffset >= 0) {
1603 if (trialSucceeded()) {
1604 break;
1606 trialState.ResetForBalance(-1);
1608 aReflowInput.mFloatManager->PopState(&floatManagerState);
1609 continue;
1612 // If we reach here, no balancing was required, so just exit; we don't
1613 // reset (pop) the floatManager state because this is the reflow we're
1614 // going to keep. So the saved state is just dropped.
1615 break;
1616 } // End of text-wrap: balance retry loop
1618 // If the block direction is right-to-left, we need to update the bounds of
1619 // lines that were placed relative to mContainerSize during reflow, as
1620 // we typically do not know the true container size until we've reflowed all
1621 // its children. So we use a dummy mContainerSize during reflow (see
1622 // BlockReflowState's constructor) and then fix up the positions of the
1623 // lines here, once the final block size is known.
1625 // Note that writing-mode:vertical-rl is the only case where the block
1626 // logical direction progresses in a negative physical direction, and
1627 // therefore block-dir coordinate conversion depends on knowing the width
1628 // of the coordinate space in order to translate between the logical and
1629 // physical origins.
1630 if (aReflowInput.GetWritingMode().IsVerticalRL()) {
1631 nsSize containerSize = aMetrics.PhysicalSize();
1632 nscoord deltaX = containerSize.width - trialState.mContainerWidth;
1633 if (deltaX != 0) {
1634 // We compute our lines and markers' overflow areas later in
1635 // ComputeOverflowAreas(), so we don't need to adjust their overflow areas
1636 // here.
1637 const nsPoint physicalDelta(deltaX, 0);
1638 for (auto& line : Lines()) {
1639 UpdateLineContainerSize(&line, containerSize);
1641 trialState.mFcBounds.Clear();
1642 for (nsIFrame* f : mFloats) {
1643 f->MovePositionBy(physicalDelta);
1644 ConsiderChildOverflow(trialState.mFcBounds, f);
1646 nsFrameList* markerList = GetOutsideMarkerList();
1647 if (markerList) {
1648 for (nsIFrame* f : *markerList) {
1649 f->MovePositionBy(physicalDelta);
1652 if (nsFrameList* overflowContainers = GetOverflowContainers()) {
1653 trialState.mOcBounds.Clear();
1654 for (nsIFrame* f : *overflowContainers) {
1655 f->MovePositionBy(physicalDelta);
1656 ConsiderChildOverflow(trialState.mOcBounds, f);
1662 aMetrics.SetOverflowAreasToDesiredBounds();
1663 ComputeOverflowAreas(aMetrics.mOverflowAreas,
1664 trialState.mBlockEndEdgeOfChildren,
1665 aReflowInput.mStyleDisplay);
1666 // Factor overflow container child bounds into the overflow area
1667 aMetrics.mOverflowAreas.UnionWith(trialState.mOcBounds);
1668 // Factor pushed float child bounds into the overflow area
1669 aMetrics.mOverflowAreas.UnionWith(trialState.mFcBounds);
1671 // Let the absolutely positioned container reflow any absolutely positioned
1672 // child frames that need to be reflowed, e.g., elements with a percentage
1673 // based width/height
1674 // We want to do this under either of two conditions:
1675 // 1. If we didn't do the incremental reflow above.
1676 // 2. If our size changed.
1677 // Even though it's the padding edge that's the containing block, we
1678 // can use our rect (the border edge) since if the border style
1679 // changed, the reflow would have been targeted at us so we'd satisfy
1680 // condition 1.
1681 // XXX checking oldSize is bogus, there are various reasons we might have
1682 // reflowed but our size might not have been changed to what we
1683 // asked for (e.g., we ended up being pushed to a new page)
1684 // When WillReflowAgainForClearance is true, we will reflow again without
1685 // resetting the size. Because of this, we must not reflow our abs-pos
1686 // children in that situation --- what we think is our "new size" will not be
1687 // our real new size. This also happens to be more efficient.
1688 WritingMode parentWM = aMetrics.GetWritingMode();
1689 if (HasAbsolutelyPositionedChildren()) {
1690 nsAbsoluteContainingBlock* absoluteContainer = GetAbsoluteContainingBlock();
1691 bool haveInterrupt = aPresContext->HasPendingInterrupt();
1692 if (aReflowInput.WillReflowAgainForClearance() || haveInterrupt) {
1693 // Make sure that when we reflow again we'll actually reflow all the abs
1694 // pos frames that might conceivably depend on our size (or all of them,
1695 // if we're dirty right now and interrupted; in that case we also need
1696 // to mark them all with NS_FRAME_IS_DIRTY). Sadly, we can't do much
1697 // better than that, because we don't really know what our size will be,
1698 // and it might in fact not change on the followup reflow!
1699 if (haveInterrupt && HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
1700 absoluteContainer->MarkAllFramesDirty();
1701 } else {
1702 absoluteContainer->MarkSizeDependentFramesDirty();
1704 if (haveInterrupt) {
1705 // We're not going to reflow absolute frames; make sure to account for
1706 // their existing overflow areas, which is usually a side effect of this
1707 // reflow.
1709 // TODO(emilio): nsAbsoluteContainingBlock::Reflow already checks for
1710 // interrupt, can we just rely on it and unconditionally take the else
1711 // branch below? That's a bit more subtle / risky, since I don't see
1712 // what would reflow them in that case if they depended on our size.
1713 for (nsIFrame* kid = absoluteContainer->GetChildList().FirstChild();
1714 kid; kid = kid->GetNextSibling()) {
1715 ConsiderChildOverflow(aMetrics.mOverflowAreas, kid);
1718 } else {
1719 LogicalSize containingBlockSize =
1720 CalculateContainingBlockSizeForAbsolutes(parentWM, aReflowInput,
1721 aMetrics.Size(parentWM));
1723 // Mark frames that depend on changes we just made to this frame as dirty:
1724 // Now we can assume that the padding edge hasn't moved.
1725 // We need to reflow the absolutes if one of them depends on
1726 // its placeholder position, or the containing block size in a
1727 // direction in which the containing block size might have
1728 // changed.
1730 // XXX "width" and "height" in this block will become ISize and BSize
1731 // when nsAbsoluteContainingBlock is logicalized
1732 bool cbWidthChanged = aMetrics.Width() != oldSize.width;
1733 bool isRoot = !GetContent()->GetParent();
1734 // If isRoot and we have auto height, then we are the initial
1735 // containing block and the containing block height is the
1736 // viewport height, which can't change during incremental
1737 // reflow.
1738 bool cbHeightChanged =
1739 !(isRoot && NS_UNCONSTRAINEDSIZE == aReflowInput.ComputedHeight()) &&
1740 aMetrics.Height() != oldSize.height;
1742 nsRect containingBlock(nsPoint(0, 0),
1743 containingBlockSize.GetPhysicalSize(parentWM));
1744 AbsPosReflowFlags flags = AbsPosReflowFlags::ConstrainHeight;
1745 if (cbWidthChanged) {
1746 flags |= AbsPosReflowFlags::CBWidthChanged;
1748 if (cbHeightChanged) {
1749 flags |= AbsPosReflowFlags::CBHeightChanged;
1751 // Setup the line cursor here to optimize line searching for
1752 // calculating hypothetical position of absolutely-positioned
1753 // frames.
1754 SetupLineCursorForQuery();
1755 absoluteContainer->Reflow(this, aPresContext, aReflowInput, reflowStatus,
1756 containingBlock, flags,
1757 &aMetrics.mOverflowAreas);
1761 FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
1763 aStatus = reflowStatus;
1765 #ifdef DEBUG
1766 // Between when we drain pushed floats and when we complete reflow,
1767 // we're allowed to have multiple continuations of the same float on
1768 // our floats list, since a first-in-flow might get pushed to a later
1769 // continuation of its containing block. But it's not permitted
1770 // outside that time.
1771 nsLayoutUtils::AssertNoDuplicateContinuations(this, mFloats);
1773 if (gNoisyReflow) {
1774 IndentBy(stdout, gNoiseIndent);
1775 ListTag(stdout);
1776 printf(": status=%s metrics=%d,%d carriedMargin=%d",
1777 ToString(aStatus).c_str(), aMetrics.ISize(parentWM),
1778 aMetrics.BSize(parentWM), aMetrics.mCarriedOutBEndMargin.get());
1779 if (HasOverflowAreas()) {
1780 printf(" overflow-vis={%d,%d,%d,%d}", aMetrics.InkOverflow().x,
1781 aMetrics.InkOverflow().y, aMetrics.InkOverflow().width,
1782 aMetrics.InkOverflow().height);
1783 printf(" overflow-scr={%d,%d,%d,%d}", aMetrics.ScrollableOverflow().x,
1784 aMetrics.ScrollableOverflow().y,
1785 aMetrics.ScrollableOverflow().width,
1786 aMetrics.ScrollableOverflow().height);
1788 printf("\n");
1791 if (gLameReflowMetrics) {
1792 PRTime end = PR_Now();
1794 int32_t ectc = nsLineBox::GetCtorCount();
1795 int32_t numLines = mLines.size();
1796 if (!numLines) {
1797 numLines = 1;
1799 PRTime delta, perLineDelta, lines;
1800 lines = int64_t(numLines);
1801 delta = end - start;
1802 perLineDelta = delta / lines;
1804 ListTag(stdout);
1805 char buf[400];
1806 SprintfLiteral(buf,
1807 ": %" PRId64 " elapsed (%" PRId64
1808 " per line) (%d lines; %d new lines)",
1809 delta, perLineDelta, numLines, ectc - ctc);
1810 printf("%s\n", buf);
1812 #endif
1815 nsReflowStatus nsBlockFrame::TrialReflow(nsPresContext* aPresContext,
1816 ReflowOutput& aMetrics,
1817 const ReflowInput& aReflowInput,
1818 TrialReflowState& aTrialState) {
1819 #ifdef DEBUG
1820 // Between when we drain pushed floats and when we complete reflow,
1821 // we're allowed to have multiple continuations of the same float on
1822 // our floats list, since a first-in-flow might get pushed to a later
1823 // continuation of its containing block. But it's not permitted
1824 // outside that time.
1825 nsLayoutUtils::AssertNoDuplicateContinuations(this, mFloats);
1826 #endif
1828 // ALWAYS drain overflow. We never want to leave the previnflow's
1829 // overflow lines hanging around; block reflow depends on the
1830 // overflow line lists being cleared out between reflow passes.
1831 DrainOverflowLines();
1833 bool blockStartMarginRoot, blockEndMarginRoot;
1834 IsMarginRoot(&blockStartMarginRoot, &blockEndMarginRoot);
1836 BlockReflowState state(aReflowInput, aPresContext, this, blockStartMarginRoot,
1837 blockEndMarginRoot, aTrialState.mNeedFloatManager,
1838 aTrialState.mConsumedBSize,
1839 aTrialState.mEffectiveContentBoxBSize,
1840 aTrialState.mInset);
1842 // Handle paginated overflow (see nsContainerFrame.h)
1843 nsReflowStatus ocStatus;
1844 if (GetPrevInFlow()) {
1845 ReflowOverflowContainerChildren(
1846 aPresContext, aReflowInput, aTrialState.mOcBounds,
1847 ReflowChildFlags::Default, ocStatus, DefaultChildFrameMerge,
1848 Some(state.ContainerSize()));
1851 // Now that we're done cleaning up our overflow container lists, we can
1852 // give |state| its nsOverflowContinuationTracker.
1853 nsOverflowContinuationTracker tracker(this, false);
1854 state.mOverflowTracker = &tracker;
1856 // Drain & handle pushed floats
1857 DrainPushedFloats();
1858 ReflowPushedFloats(state, aTrialState.mFcBounds);
1860 // If we're not dirty (which means we'll mark everything dirty later)
1861 // and our inline-size has changed, mark the lines dirty that we need to
1862 // mark dirty for a resize reflow.
1863 if (!HasAnyStateBits(NS_FRAME_IS_DIRTY) && aReflowInput.IsIResize()) {
1864 PrepareResizeReflow(state);
1867 // The same for percentage text-indent, except conditioned on the
1868 // parent resizing.
1869 if (!HasAnyStateBits(NS_FRAME_IS_DIRTY) && aReflowInput.mCBReflowInput &&
1870 aReflowInput.mCBReflowInput->IsIResize() &&
1871 StyleText()->mTextIndent.HasPercent() && !mLines.empty()) {
1872 mLines.front()->MarkDirty();
1875 // For text-wrap:balance trials, we need to reflow all the lines even if
1876 // they're not all "dirty".
1877 if (aTrialState.mBalancing) {
1878 MarkAllDescendantLinesDirty(this);
1879 } else {
1880 LazyMarkLinesDirty();
1883 // Now reflow...
1884 ReflowDirtyLines(state);
1886 // If we have a next-in-flow, and that next-in-flow has pushed floats from
1887 // this frame from a previous iteration of reflow, then we should not return
1888 // a status with IsFullyComplete() equals to true, since we actually have
1889 // overflow, it's just already been handled.
1891 // NOTE: This really shouldn't happen, since we _should_ pull back our floats
1892 // and reflow them, but just in case it does, this is a safety precaution so
1893 // we don't end up with a placeholder pointing to frames that have already
1894 // been deleted as part of removing our next-in-flow.
1895 if (state.mReflowStatus.IsFullyComplete()) {
1896 nsBlockFrame* nif = static_cast<nsBlockFrame*>(GetNextInFlow());
1897 while (nif) {
1898 if (nif->HasPushedFloatsFromPrevContinuation()) {
1899 if (nif->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
1900 state.mReflowStatus.SetOverflowIncomplete();
1901 } else {
1902 state.mReflowStatus.SetIncomplete();
1904 break;
1907 nif = static_cast<nsBlockFrame*>(nif->GetNextInFlow());
1911 state.mReflowStatus.MergeCompletionStatusFrom(ocStatus);
1913 // If we end in a BR with clear and affected floats continue,
1914 // we need to continue, too.
1915 if (NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize() &&
1916 state.mReflowStatus.IsComplete() &&
1917 state.FloatManager()->ClearContinues(FindTrailingClear())) {
1918 state.mReflowStatus.SetIncomplete();
1921 if (!state.mReflowStatus.IsFullyComplete()) {
1922 if (HasOverflowLines() || HasPushedFloats()) {
1923 state.mReflowStatus.SetNextInFlowNeedsReflow();
1926 #ifdef DEBUG_kipp
1927 ListTag(stdout);
1928 printf(": block is not fully complete\n");
1929 #endif
1932 // Place the ::marker's frame if it is placed next to a block child.
1934 // According to the CSS2 spec, section 12.6.1, the ::marker's box
1935 // participates in the height calculation of the list-item box's
1936 // first line box.
1938 // There are exactly two places a ::marker can be placed: near the
1939 // first or second line. It's only placed on the second line in a
1940 // rare case: an empty first line followed by a second line that
1941 // contains a block (example: <LI>\n<P>... ). This is where
1942 // the second case can happen.
1943 if (HasOutsideMarker() && !mLines.empty() &&
1944 (mLines.front()->IsBlock() ||
1945 (0 == mLines.front()->BSize() && mLines.front() != mLines.back() &&
1946 mLines.begin().next()->IsBlock()))) {
1947 // Reflow the ::marker's frame.
1948 ReflowOutput reflowOutput(aReflowInput);
1949 // XXX Use the entire line when we fix bug 25888.
1950 nsLayoutUtils::LinePosition position;
1951 WritingMode wm = aReflowInput.GetWritingMode();
1952 bool havePosition =
1953 nsLayoutUtils::GetFirstLinePosition(wm, this, &position);
1954 nscoord lineBStart =
1955 havePosition ? position.mBStart
1956 : aReflowInput.ComputedLogicalBorderPadding(wm).BStart(wm);
1957 nsIFrame* marker = GetOutsideMarker();
1958 ReflowOutsideMarker(marker, state, reflowOutput, lineBStart);
1959 NS_ASSERTION(!MarkerIsEmpty() || reflowOutput.BSize(wm) == 0,
1960 "empty ::marker frame took up space");
1962 if (havePosition && !MarkerIsEmpty()) {
1963 // We have some lines to align the ::marker with.
1965 // Doing the alignment using the baseline will also cater for
1966 // ::markers that are placed next to a child block (bug 92896)
1968 // Tall ::markers won't look particularly nice here...
1969 LogicalRect bbox =
1970 marker->GetLogicalRect(wm, reflowOutput.PhysicalSize());
1971 const auto baselineGroup = BaselineSharingGroup::First;
1972 Maybe<nscoord> result;
1973 if (MOZ_LIKELY(!wm.IsOrthogonalTo(marker->GetWritingMode()))) {
1974 result = marker->GetNaturalBaselineBOffset(
1975 wm, baselineGroup, BaselineExportContext::LineLayout);
1977 const auto markerBaseline = result.valueOrFrom([bbox, wm, marker]() {
1978 return bbox.BSize(wm) + marker->GetLogicalUsedMargin(wm).BEnd(wm);
1980 bbox.BStart(wm) = position.mBaseline - markerBaseline;
1981 marker->SetRect(wm, bbox, reflowOutput.PhysicalSize());
1983 // Otherwise just leave the ::marker where it is, up against our
1984 // block-start padding.
1987 // Clear any existing -webkit-line-clamp ellipsis.
1988 if (aReflowInput.mStyleDisplay->mWebkitLineClamp) {
1989 ClearLineClampEllipsis();
1992 CheckFloats(state);
1994 // Compute our final size (for this trial layout)
1995 aTrialState.mBlockEndEdgeOfChildren =
1996 ComputeFinalSize(aReflowInput, state, aMetrics);
1997 aTrialState.mContainerWidth = state.ContainerSize().width;
1999 return state.mReflowStatus;
2002 bool nsBlockFrame::CheckForCollapsedBEndMarginFromClearanceLine() {
2003 for (auto& line : Reversed(Lines())) {
2004 if (0 != line.BSize() || !line.CachedIsEmpty()) {
2005 return false;
2007 if (line.HasClearance()) {
2008 return true;
2011 return false;
2014 static nsLineBox* FindLineClampTarget(nsBlockFrame*& aFrame,
2015 StyleLineClamp aLineNumber) {
2016 MOZ_ASSERT(aLineNumber > 0);
2017 MOZ_ASSERT(!aFrame->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS),
2018 "Should have been removed earlier in nsBlockReflow::Reflow");
2020 nsLineBox* target = nullptr;
2021 nsBlockFrame* targetFrame = nullptr;
2022 bool foundFollowingLine = false;
2024 LineClampLineIterator iter(aFrame);
2026 while (nsLineBox* line = iter.GetCurrentLine()) {
2027 MOZ_ASSERT(!line->HasLineClampEllipsis(),
2028 "Should have been removed earlier in nsBlockFrame::Reflow");
2029 MOZ_ASSERT(!iter.GetCurrentFrame()->HasAnyStateBits(
2030 NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS),
2031 "Should have been removed earlier in nsBlockReflow::Reflow");
2033 // Don't count a line that only has collapsible white space (as might exist
2034 // after calling e.g. getBoxQuads).
2035 if (line->IsEmpty()) {
2036 iter.Next();
2037 continue;
2040 if (aLineNumber == 0) {
2041 // We already previously found our target line, and now we have
2042 // confirmed that there is another line after it.
2043 foundFollowingLine = true;
2044 break;
2047 if (--aLineNumber == 0) {
2048 // This is our target line. Continue looping to confirm that we
2049 // have another line after us.
2050 target = line;
2051 targetFrame = iter.GetCurrentFrame();
2054 iter.Next();
2057 if (!foundFollowingLine) {
2058 aFrame = nullptr;
2059 return nullptr;
2062 MOZ_ASSERT(target);
2063 MOZ_ASSERT(targetFrame);
2065 aFrame = targetFrame;
2066 return target;
2069 static nscoord ApplyLineClamp(const ReflowInput& aReflowInput,
2070 nsBlockFrame* aFrame,
2071 nscoord aContentBlockEndEdge) {
2072 if (!IsLineClampRoot(aFrame)) {
2073 return aContentBlockEndEdge;
2075 auto lineClamp = aReflowInput.mStyleDisplay->mWebkitLineClamp;
2076 nsBlockFrame* frame = aFrame;
2077 nsLineBox* line = FindLineClampTarget(frame, lineClamp);
2078 if (!line) {
2079 // The number of lines did not exceed the -webkit-line-clamp value.
2080 return aContentBlockEndEdge;
2083 // Mark the line as having an ellipsis so that TextOverflow will render it.
2084 line->SetHasLineClampEllipsis();
2085 frame->AddStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS);
2087 // Translate the b-end edge of the line up to aFrame's space.
2088 nscoord edge = line->BEnd();
2089 for (nsIFrame* f = frame; f != aFrame; f = f->GetParent()) {
2090 edge +=
2091 f->GetLogicalPosition(f->GetParent()->GetSize()).B(f->GetWritingMode());
2094 return edge;
2097 nscoord nsBlockFrame::ComputeFinalSize(const ReflowInput& aReflowInput,
2098 BlockReflowState& aState,
2099 ReflowOutput& aMetrics) {
2100 WritingMode wm = aState.mReflowInput.GetWritingMode();
2101 const LogicalMargin& borderPadding = aState.BorderPadding();
2102 #ifdef NOISY_FINAL_SIZE
2103 ListTag(stdout);
2104 printf(": mBCoord=%d mIsBEndMarginRoot=%s mPrevBEndMargin=%d bp=%d,%d\n",
2105 aState.mBCoord, aState.mFlags.mIsBEndMarginRoot ? "yes" : "no",
2106 aState.mPrevBEndMargin.get(), borderPadding.BStart(wm),
2107 borderPadding.BEnd(wm));
2108 #endif
2110 // Compute final inline size
2111 LogicalSize finalSize(wm);
2112 finalSize.ISize(wm) =
2113 NSCoordSaturatingAdd(NSCoordSaturatingAdd(borderPadding.IStart(wm),
2114 aReflowInput.ComputedISize()),
2115 borderPadding.IEnd(wm));
2117 // Return block-end margin information
2118 // rbs says he hit this assertion occasionally (see bug 86947), so
2119 // just set the margin to zero and we'll figure out why later
2120 // NS_ASSERTION(aMetrics.mCarriedOutBEndMargin.IsZero(),
2121 // "someone else set the margin");
2122 nscoord nonCarriedOutBDirMargin = 0;
2123 if (!aState.mFlags.mIsBEndMarginRoot) {
2124 // Apply rule from CSS 2.1 section 8.3.1. If we have some empty
2125 // line with clearance and a non-zero block-start margin and all
2126 // subsequent lines are empty, then we do not allow our children's
2127 // carried out block-end margin to be carried out of us and collapse
2128 // with our own block-end margin.
2129 if (CheckForCollapsedBEndMarginFromClearanceLine()) {
2130 // Convert the children's carried out margin to something that
2131 // we will include in our height
2132 nonCarriedOutBDirMargin = aState.mPrevBEndMargin.get();
2133 aState.mPrevBEndMargin.Zero();
2135 aMetrics.mCarriedOutBEndMargin = aState.mPrevBEndMargin;
2136 } else {
2137 aMetrics.mCarriedOutBEndMargin.Zero();
2140 nscoord blockEndEdgeOfChildren = aState.mBCoord + nonCarriedOutBDirMargin;
2141 // Shrink wrap our height around our contents.
2142 if (aState.mFlags.mIsBEndMarginRoot ||
2143 NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) {
2144 // When we are a block-end-margin root make sure that our last
2145 // child's block-end margin is fully applied. We also do this when
2146 // we have a computed height, since in that case the carried out
2147 // margin is not going to be applied anywhere, so we should note it
2148 // here to be included in the overflow area.
2149 // Apply the margin only if there's space for it.
2150 if (blockEndEdgeOfChildren < aState.mReflowInput.AvailableBSize()) {
2151 // Truncate block-end margin if it doesn't fit to our available BSize.
2152 blockEndEdgeOfChildren =
2153 std::min(blockEndEdgeOfChildren + aState.mPrevBEndMargin.get(),
2154 aState.mReflowInput.AvailableBSize());
2157 if (aState.mFlags.mBlockNeedsFloatManager) {
2158 // Include the float manager's state to properly account for the
2159 // block-end margin of any floated elements; e.g., inside a table cell.
2161 // Note: The block coordinate returned by ClearFloats is always greater than
2162 // or equal to blockEndEdgeOfChildren.
2163 std::tie(blockEndEdgeOfChildren, std::ignore) =
2164 aState.ClearFloats(blockEndEdgeOfChildren, StyleClear::Both);
2167 if (NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) {
2168 // Note: We don't use blockEndEdgeOfChildren because it includes the
2169 // previous margin.
2170 const nscoord contentBSizeWithBStartBP =
2171 aState.mBCoord + nonCarriedOutBDirMargin;
2173 // We don't care about ApplyLineClamp's return value (the line-clamped
2174 // content BSize) in this explicit-BSize codepath, but we do still need to
2175 // call ApplyLineClamp for ellipsis markers to be placed as-needed.
2176 ApplyLineClamp(aState.mReflowInput, this, contentBSizeWithBStartBP);
2178 finalSize.BSize(wm) = ComputeFinalBSize(aState, contentBSizeWithBStartBP);
2180 // If the content block-size is larger than the effective computed
2181 // block-size, we extend the block-size to contain all the content.
2182 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
2183 if (aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis()) {
2184 // Note: finalSize.BSize(wm) is the border-box size, so we compare it with
2185 // the content's block-size plus our border and padding..
2186 finalSize.BSize(wm) =
2187 std::max(finalSize.BSize(wm),
2188 contentBSizeWithBStartBP + borderPadding.BEnd(wm));
2191 // Don't carry out a block-end margin when our BSize is fixed.
2193 // Note: this also includes the case that aReflowInput.ComputedBSize() is
2194 // calculated from aspect-ratio. i.e. Don't carry out block margin-end if it
2195 // is replaced by the block size from aspect-ratio and inline size.
2196 aMetrics.mCarriedOutBEndMargin.Zero();
2197 } else {
2198 Maybe<nscoord> containBSize = ContainIntrinsicBSize(
2199 IsComboboxControlFrame() ? NS_UNCONSTRAINEDSIZE : 0);
2200 if (containBSize && *containBSize != NS_UNCONSTRAINEDSIZE) {
2201 // If we're size-containing in block axis and we don't have a specified
2202 // block size, then our final size should actually be computed from only
2203 // our border, padding and contain-intrinsic-block-size, ignoring the
2204 // actual contents. Hence this case is a simplified version of the case
2205 // below.
2207 // NOTE: We exempt the nsComboboxControlFrame subclass from taking this
2208 // special case when it has 'contain-intrinsic-block-size: none', because
2209 // comboboxes implicitly honors the size-containment behavior on its
2210 // nsComboboxDisplayFrame child (which it shrinkwraps) rather than on the
2211 // nsComboboxControlFrame. (Moreover, the DisplayFrame child doesn't even
2212 // need any special content-size-ignoring behavior in its reflow method,
2213 // because that method just resolves "auto" BSize values to one
2214 // line-height rather than by measuring its contents' BSize.)
2215 nscoord contentBSize = *containBSize;
2216 nscoord autoBSize =
2217 aReflowInput.ApplyMinMaxBSize(contentBSize, aState.mConsumedBSize);
2218 aMetrics.mCarriedOutBEndMargin.Zero();
2219 autoBSize += borderPadding.BStartEnd(wm);
2220 finalSize.BSize(wm) = autoBSize;
2221 } else if (aState.mReflowStatus.IsInlineBreakBefore()) {
2222 // Our parent is expected to push this frame to the next page/column so
2223 // what size we set here doesn't really matter.
2224 finalSize.BSize(wm) = aReflowInput.AvailableBSize();
2225 } else if (aState.mReflowStatus.IsComplete()) {
2226 const nscoord lineClampedContentBlockEndEdge =
2227 ApplyLineClamp(aReflowInput, this, blockEndEdgeOfChildren);
2229 const nscoord bpBStart = borderPadding.BStart(wm);
2230 const nscoord contentBSize = blockEndEdgeOfChildren - bpBStart;
2231 const nscoord lineClampedContentBSize =
2232 lineClampedContentBlockEndEdge - bpBStart;
2234 const nscoord autoBSize = aReflowInput.ApplyMinMaxBSize(
2235 lineClampedContentBSize, aState.mConsumedBSize);
2236 if (autoBSize != contentBSize) {
2237 // Our min-block-size, max-block-size, or -webkit-line-clamp value made
2238 // our bsize change. Don't carry out our kids' block-end margins.
2239 aMetrics.mCarriedOutBEndMargin.Zero();
2241 nscoord bSize = autoBSize + borderPadding.BStartEnd(wm);
2242 if (MOZ_UNLIKELY(autoBSize > contentBSize &&
2243 bSize > aReflowInput.AvailableBSize() &&
2244 aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE)) {
2245 // Applying `min-size` made us overflow our available size.
2246 // Clamp it and report that we're Incomplete, or BreakBefore if we have
2247 // 'break-inside: avoid' that is applicable.
2248 bSize = aReflowInput.AvailableBSize();
2249 if (ShouldAvoidBreakInside(aReflowInput)) {
2250 aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
2251 } else {
2252 aState.mReflowStatus.SetIncomplete();
2255 finalSize.BSize(wm) = bSize;
2256 } else {
2257 NS_ASSERTION(
2258 aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
2259 "Shouldn't be incomplete if availableBSize is UNCONSTRAINED.");
2260 nscoord bSize = std::max(aState.mBCoord, aReflowInput.AvailableBSize());
2261 if (aReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
2262 // This should never happen, but it does. See bug 414255
2263 bSize = aState.mBCoord;
2265 const nscoord maxBSize = aReflowInput.ComputedMaxBSize();
2266 if (maxBSize != NS_UNCONSTRAINEDSIZE &&
2267 aState.mConsumedBSize + bSize - borderPadding.BStart(wm) > maxBSize) {
2268 // Compute this fragment's block-size, with the max-block-size
2269 // constraint taken into consideration.
2270 const nscoord clampedBSizeWithoutEndBP =
2271 std::max(0, maxBSize - aState.mConsumedBSize) +
2272 borderPadding.BStart(wm);
2273 const nscoord clampedBSize =
2274 clampedBSizeWithoutEndBP + borderPadding.BEnd(wm);
2275 if (clampedBSize <= aReflowInput.AvailableBSize()) {
2276 // We actually fit after applying `max-size` so we should be
2277 // Overflow-Incomplete instead.
2278 bSize = clampedBSize;
2279 aState.mReflowStatus.SetOverflowIncomplete();
2280 } else {
2281 // We cannot fit after applying `max-size` with our block-end BP, so
2282 // we should draw it in our next continuation.
2283 bSize = clampedBSizeWithoutEndBP;
2286 finalSize.BSize(wm) = bSize;
2290 if (IsTrueOverflowContainer()) {
2291 if (aState.mReflowStatus.IsIncomplete()) {
2292 // Overflow containers can only be overflow complete.
2293 // Note that auto height overflow containers have no normal children
2294 NS_ASSERTION(finalSize.BSize(wm) == 0,
2295 "overflow containers must be zero-block-size");
2296 aState.mReflowStatus.SetOverflowIncomplete();
2298 } else if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
2299 !aState.mReflowStatus.IsInlineBreakBefore() &&
2300 aState.mReflowStatus.IsComplete()) {
2301 // Currently only used for grid items, but could be used in other contexts.
2302 // The FragStretchBSizeProperty is our expected non-fragmented block-size
2303 // we should stretch to (for align-self:stretch etc). In some fragmentation
2304 // cases though, the last fragment (this frame since we're complete), needs
2305 // to have extra size applied because earlier fragments consumed too much of
2306 // our computed size due to overflowing their containing block. (E.g. this
2307 // ensures we fill the last row when a multi-row grid item is fragmented).
2308 bool found;
2309 nscoord bSize = GetProperty(FragStretchBSizeProperty(), &found);
2310 if (found) {
2311 finalSize.BSize(wm) = std::max(bSize, finalSize.BSize(wm));
2315 // Clamp the content size to fit within the margin-box clamp size, if any.
2316 if (MOZ_UNLIKELY(aReflowInput.mComputeSizeFlags.contains(
2317 ComputeSizeFlag::BClampMarginBoxMinSize)) &&
2318 aState.mReflowStatus.IsComplete()) {
2319 bool found;
2320 nscoord cbSize = GetProperty(BClampMarginBoxMinSizeProperty(), &found);
2321 if (found) {
2322 auto marginBoxBSize =
2323 finalSize.BSize(wm) +
2324 aReflowInput.ComputedLogicalMargin(wm).BStartEnd(wm);
2325 auto overflow = marginBoxBSize - cbSize;
2326 if (overflow > 0) {
2327 auto contentBSize = finalSize.BSize(wm) - borderPadding.BStartEnd(wm);
2328 auto newContentBSize = std::max(nscoord(0), contentBSize - overflow);
2329 // XXXmats deal with percentages better somehow?
2330 finalSize.BSize(wm) -= contentBSize - newContentBSize;
2335 // Screen out negative block sizes --- can happen due to integer overflows :-(
2336 finalSize.BSize(wm) = std::max(0, finalSize.BSize(wm));
2338 if (blockEndEdgeOfChildren != finalSize.BSize(wm) - borderPadding.BEnd(wm)) {
2339 SetProperty(BlockEndEdgeOfChildrenProperty(), blockEndEdgeOfChildren);
2340 } else {
2341 RemoveProperty(BlockEndEdgeOfChildrenProperty());
2344 aMetrics.SetSize(wm, finalSize);
2346 #ifdef DEBUG_blocks
2347 if ((ABSURD_SIZE(aMetrics.Width()) || ABSURD_SIZE(aMetrics.Height())) &&
2348 !GetParent()->IsAbsurdSizeAssertSuppressed()) {
2349 ListTag(stdout);
2350 printf(": WARNING: desired:%d,%d\n", aMetrics.Width(), aMetrics.Height());
2352 #endif
2354 return blockEndEdgeOfChildren;
2357 void nsBlockFrame::ConsiderBlockEndEdgeOfChildren(
2358 OverflowAreas& aOverflowAreas, nscoord aBEndEdgeOfChildren,
2359 const nsStyleDisplay* aDisplay) const {
2360 const auto wm = GetWritingMode();
2362 // Factor in the block-end edge of the children. Child frames will be added
2363 // to the overflow area as we iterate through the lines, but their margins
2364 // won't, so we need to account for block-end margins here.
2365 // REVIEW: For now, we do this for both visual and scrollable area,
2366 // although when we make scrollable overflow area not be a subset of
2367 // visual, we can change this.
2369 if (Style()->GetPseudoType() == PseudoStyleType::scrolledContent) {
2370 // If we are a scrolled inner frame, add our block-end padding to our
2371 // children's block-end edge.
2373 // Note: aBEndEdgeOfChildren already includes our own block-start padding
2374 // because it is relative to our block-start edge of our border-box, which
2375 // is the same as our padding-box here.
2376 MOZ_ASSERT(GetLogicalUsedBorderAndPadding(wm) == GetLogicalUsedPadding(wm),
2377 "A scrolled inner frame shouldn't have any border!");
2378 aBEndEdgeOfChildren += GetLogicalUsedPadding(wm).BEnd(wm);
2381 // XXX Currently, overflow areas are stored as physical rects, so we have
2382 // to handle writing modes explicitly here. If we change overflow rects
2383 // to be stored logically, this can be simplified again.
2384 if (wm.IsVertical()) {
2385 if (wm.IsVerticalLR()) {
2386 for (const auto otype : AllOverflowTypes()) {
2387 if (!(aDisplay->IsContainLayout() &&
2388 otype == OverflowType::Scrollable)) {
2389 // Layout containment should force all overflow to be ink (visual)
2390 // overflow, so if we're layout-contained, we only add our children's
2391 // block-end edge to the ink (visual) overflow -- not to the
2392 // scrollable overflow.
2393 nsRect& o = aOverflowAreas.Overflow(otype);
2394 o.width = std::max(o.XMost(), aBEndEdgeOfChildren) - o.x;
2397 } else {
2398 for (const auto otype : AllOverflowTypes()) {
2399 if (!(aDisplay->IsContainLayout() &&
2400 otype == OverflowType::Scrollable)) {
2401 nsRect& o = aOverflowAreas.Overflow(otype);
2402 nscoord xmost = o.XMost();
2403 o.x = std::min(o.x, xmost - aBEndEdgeOfChildren);
2404 o.width = xmost - o.x;
2408 } else {
2409 for (const auto otype : AllOverflowTypes()) {
2410 if (!(aDisplay->IsContainLayout() && otype == OverflowType::Scrollable)) {
2411 nsRect& o = aOverflowAreas.Overflow(otype);
2412 o.height = std::max(o.YMost(), aBEndEdgeOfChildren) - o.y;
2418 void nsBlockFrame::ComputeOverflowAreas(OverflowAreas& aOverflowAreas,
2419 nscoord aBEndEdgeOfChildren,
2420 const nsStyleDisplay* aDisplay) const {
2421 // XXX_perf: This can be done incrementally. It is currently one of
2422 // the things that makes incremental reflow O(N^2).
2423 auto overflowClipAxes = ShouldApplyOverflowClipping(aDisplay);
2424 auto overflowClipMargin = OverflowClipMargin(overflowClipAxes);
2425 if (overflowClipAxes == PhysicalAxes::Both &&
2426 overflowClipMargin == nsSize()) {
2427 return;
2430 // We rely here on our caller having called SetOverflowAreasToDesiredBounds().
2431 nsRect frameBounds = aOverflowAreas.ScrollableOverflow();
2433 for (const auto& line : Lines()) {
2434 if (aDisplay->IsContainLayout()) {
2435 // If we have layout containment, we should only consider our child's
2436 // ink overflow, leaving the scrollable regions of the parent
2437 // unaffected.
2438 // Note: scrollable overflow is a subset of ink overflow,
2439 // so this has the same affect as unioning the child's visual and
2440 // scrollable overflow with its parent's ink overflow.
2441 nsRect childVisualRect = line.InkOverflowRect();
2442 OverflowAreas childVisualArea = OverflowAreas(childVisualRect, nsRect());
2443 aOverflowAreas.UnionWith(childVisualArea);
2444 } else {
2445 aOverflowAreas.UnionWith(line.GetOverflowAreas());
2449 // Factor an outside ::marker in; normally the ::marker will be factored
2450 // into the line-box's overflow areas. However, if the line is a block
2451 // line then it won't; if there are no lines, it won't. So just
2452 // factor it in anyway (it can't hurt if it was already done).
2453 // XXXldb Can we just fix GetOverflowArea instead?
2454 if (nsIFrame* outsideMarker = GetOutsideMarker()) {
2455 aOverflowAreas.UnionAllWith(outsideMarker->GetRect());
2458 ConsiderBlockEndEdgeOfChildren(aOverflowAreas, aBEndEdgeOfChildren, aDisplay);
2460 if (overflowClipAxes != PhysicalAxes::None) {
2461 aOverflowAreas.ApplyClipping(frameBounds, overflowClipAxes,
2462 overflowClipMargin);
2465 #ifdef NOISY_OVERFLOW_AREAS
2466 printf("%s: InkOverflowArea=%s, ScrollableOverflowArea=%s\n", ListTag().get(),
2467 ToString(aOverflowAreas.InkOverflow()).c_str(),
2468 ToString(aOverflowAreas.ScrollableOverflow()).c_str());
2469 #endif
2472 void nsBlockFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas) {
2473 // We need to update the overflow areas of lines manually, as they
2474 // get cached and re-used otherwise. Lines aren't exposed as normal
2475 // frame children, so calling UnionChildOverflow alone will end up
2476 // using the old cached values.
2477 for (auto& line : Lines()) {
2478 nsRect bounds = line.GetPhysicalBounds();
2479 OverflowAreas lineAreas(bounds, bounds);
2481 int32_t n = line.GetChildCount();
2482 for (nsIFrame* lineFrame = line.mFirstChild; n > 0;
2483 lineFrame = lineFrame->GetNextSibling(), --n) {
2484 ConsiderChildOverflow(lineAreas, lineFrame);
2487 // Consider the overflow areas of the floats attached to the line as well
2488 if (line.HasFloats()) {
2489 for (nsIFrame* f : line.Floats()) {
2490 ConsiderChildOverflow(lineAreas, f);
2494 line.SetOverflowAreas(lineAreas);
2495 aOverflowAreas.UnionWith(lineAreas);
2498 // Union with child frames, skipping the principal and float lists
2499 // since we already handled those using the line boxes.
2500 nsLayoutUtils::UnionChildOverflow(
2501 this, aOverflowAreas,
2502 {FrameChildListID::Principal, FrameChildListID::Float});
2505 bool nsBlockFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) {
2506 bool found;
2507 nscoord blockEndEdgeOfChildren =
2508 GetProperty(BlockEndEdgeOfChildrenProperty(), &found);
2509 if (found) {
2510 ConsiderBlockEndEdgeOfChildren(aOverflowAreas, blockEndEdgeOfChildren,
2511 StyleDisplay());
2514 // Line cursor invariants depend on the overflow areas of the lines, so
2515 // we must clear the line cursor since those areas may have changed.
2516 ClearLineCursors();
2517 return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
2520 void nsBlockFrame::LazyMarkLinesDirty() {
2521 if (HasAnyStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES)) {
2522 for (LineIterator line = LinesBegin(), line_end = LinesEnd();
2523 line != line_end; ++line) {
2524 int32_t n = line->GetChildCount();
2525 for (nsIFrame* lineFrame = line->mFirstChild; n > 0;
2526 lineFrame = lineFrame->GetNextSibling(), --n) {
2527 if (lineFrame->IsSubtreeDirty()) {
2528 // NOTE: MarkLineDirty does more than just marking the line dirty.
2529 MarkLineDirty(line, &mLines);
2530 break;
2534 RemoveStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
2538 void nsBlockFrame::MarkLineDirty(LineIterator aLine,
2539 const nsLineList* aLineList) {
2540 // Mark aLine dirty
2541 aLine->MarkDirty();
2542 aLine->SetInvalidateTextRuns(true);
2543 #ifdef DEBUG
2544 if (gNoisyReflow) {
2545 IndentBy(stdout, gNoiseIndent);
2546 ListTag(stdout);
2547 printf(": mark line %p dirty\n", static_cast<void*>(aLine.get()));
2549 #endif
2551 // Mark previous line dirty if it's an inline line so that it can
2552 // maybe pullup something from the line just affected.
2553 // XXX We don't need to do this if aPrevLine ends in a break-after...
2554 if (aLine != aLineList->front() && aLine->IsInline() &&
2555 aLine.prev()->IsInline()) {
2556 aLine.prev()->MarkDirty();
2557 aLine.prev()->SetInvalidateTextRuns(true);
2558 #ifdef DEBUG
2559 if (gNoisyReflow) {
2560 IndentBy(stdout, gNoiseIndent);
2561 ListTag(stdout);
2562 printf(": mark prev-line %p dirty\n",
2563 static_cast<void*>(aLine.prev().get()));
2565 #endif
2570 * Test whether lines are certain to be aligned left so that we can make
2571 * resizing optimizations
2573 static inline bool IsAlignedLeft(StyleTextAlign aAlignment,
2574 StyleDirection aDirection,
2575 StyleUnicodeBidi aUnicodeBidi,
2576 nsIFrame* aFrame) {
2577 return aFrame->IsInSVGTextSubtree() || StyleTextAlign::Left == aAlignment ||
2578 (((StyleTextAlign::Start == aAlignment &&
2579 StyleDirection::Ltr == aDirection) ||
2580 (StyleTextAlign::End == aAlignment &&
2581 StyleDirection::Rtl == aDirection)) &&
2582 aUnicodeBidi != StyleUnicodeBidi::Plaintext);
2585 void nsBlockFrame::PrepareResizeReflow(BlockReflowState& aState) {
2586 // See if we can try and avoid marking all the lines as dirty
2587 // FIXME(emilio): This should be writing-mode aware, I guess.
2588 bool tryAndSkipLines =
2589 // The left content-edge must be a constant distance from the left
2590 // border-edge.
2591 !StylePadding()->mPadding.Get(eSideLeft).HasPercent();
2593 #ifdef DEBUG
2594 if (gDisableResizeOpt) {
2595 tryAndSkipLines = false;
2597 if (gNoisyReflow) {
2598 if (!tryAndSkipLines) {
2599 IndentBy(stdout, gNoiseIndent);
2600 ListTag(stdout);
2601 printf(": marking all lines dirty: availISize=%d\n",
2602 aState.mReflowInput.AvailableISize());
2605 #endif
2607 if (tryAndSkipLines) {
2608 WritingMode wm = aState.mReflowInput.GetWritingMode();
2609 nscoord newAvailISize =
2610 aState.mReflowInput.ComputedLogicalBorderPadding(wm).IStart(wm) +
2611 aState.mReflowInput.ComputedISize();
2613 #ifdef DEBUG
2614 if (gNoisyReflow) {
2615 IndentBy(stdout, gNoiseIndent);
2616 ListTag(stdout);
2617 printf(": trying to avoid marking all lines dirty\n");
2619 #endif
2621 for (LineIterator line = LinesBegin(), line_end = LinesEnd();
2622 line != line_end; ++line) {
2623 // We let child blocks make their own decisions the same
2624 // way we are here.
2625 bool isLastLine = line == mLines.back() && !GetNextInFlow();
2626 if (line->IsBlock() || line->HasFloats() ||
2627 (!isLastLine && !line->HasForcedLineBreakAfter()) ||
2628 ((isLastLine || !line->IsLineWrapped())) ||
2629 line->ResizeReflowOptimizationDisabled() ||
2630 line->IsImpactedByFloat() || (line->IEnd() > newAvailISize)) {
2631 line->MarkDirty();
2634 #ifdef REALLY_NOISY_REFLOW
2635 if (!line->IsBlock()) {
2636 printf("PrepareResizeReflow thinks line %p is %simpacted by floats\n",
2637 line.get(), line->IsImpactedByFloat() ? "" : "not ");
2639 #endif
2640 #ifdef DEBUG
2641 if (gNoisyReflow && !line->IsDirty()) {
2642 IndentBy(stdout, gNoiseIndent + 1);
2643 printf(
2644 "skipped: line=%p next=%p %s %s%s%s clearTypeBefore/After=%s/%s "
2645 "xmost=%d\n",
2646 static_cast<void*>(line.get()),
2647 static_cast<void*>(
2648 (line.next() != LinesEnd() ? line.next().get() : nullptr)),
2649 line->IsBlock() ? "block" : "inline",
2650 line->HasForcedLineBreakAfter() ? "has-break-after " : "",
2651 line->HasFloats() ? "has-floats " : "",
2652 line->IsImpactedByFloat() ? "impacted " : "",
2653 line->StyleClearToString(line->FloatClearTypeBefore()),
2654 line->StyleClearToString(line->FloatClearTypeAfter()),
2655 line->IEnd());
2657 #endif
2659 } else {
2660 // Mark everything dirty
2661 for (auto& line : Lines()) {
2662 line.MarkDirty();
2667 //----------------------------------------
2670 * Propagate reflow "damage" from from earlier lines to the current
2671 * line. The reflow damage comes from the following sources:
2672 * 1. The regions of float damage remembered during reflow.
2673 * 2. The combination of nonzero |aDeltaBCoord| and any impact by a
2674 * float, either the previous reflow or now.
2676 * When entering this function, |aLine| is still at its old position and
2677 * |aDeltaBCoord| indicates how much it will later be slid (assuming it
2678 * doesn't get marked dirty and reflowed entirely).
2680 void nsBlockFrame::PropagateFloatDamage(BlockReflowState& aState,
2681 nsLineBox* aLine,
2682 nscoord aDeltaBCoord) {
2683 nsFloatManager* floatManager = aState.FloatManager();
2684 NS_ASSERTION(
2685 (aState.mReflowInput.mParentReflowInput &&
2686 aState.mReflowInput.mParentReflowInput->mFloatManager == floatManager) ||
2687 aState.mReflowInput.mBlockDelta == 0,
2688 "Bad block delta passed in");
2690 // Check to see if there are any floats; if there aren't, there can't
2691 // be any float damage
2692 if (!floatManager->HasAnyFloats()) {
2693 return;
2696 // Check the damage region recorded in the float damage.
2697 if (floatManager->HasFloatDamage()) {
2698 // Need to check mBounds *and* mCombinedArea to find intersections
2699 // with aLine's floats
2700 nscoord lineBCoordBefore = aLine->BStart() + aDeltaBCoord;
2701 nscoord lineBCoordAfter = lineBCoordBefore + aLine->BSize();
2702 // Scrollable overflow should be sufficient for things that affect
2703 // layout.
2704 WritingMode wm = aState.mReflowInput.GetWritingMode();
2705 nsSize containerSize = aState.ContainerSize();
2706 LogicalRect overflow =
2707 aLine->GetOverflowArea(OverflowType::Scrollable, wm, containerSize);
2708 nscoord lineBCoordCombinedBefore = overflow.BStart(wm) + aDeltaBCoord;
2709 nscoord lineBCoordCombinedAfter =
2710 lineBCoordCombinedBefore + overflow.BSize(wm);
2712 bool isDirty =
2713 floatManager->IntersectsDamage(lineBCoordBefore, lineBCoordAfter) ||
2714 floatManager->IntersectsDamage(lineBCoordCombinedBefore,
2715 lineBCoordCombinedAfter);
2716 if (isDirty) {
2717 aLine->MarkDirty();
2718 return;
2722 // Check if the line is moving relative to the float manager
2723 if (aDeltaBCoord + aState.mReflowInput.mBlockDelta != 0) {
2724 if (aLine->IsBlock()) {
2725 // Unconditionally reflow sliding blocks; we only really need to reflow
2726 // if there's a float impacting this block, but the current float manager
2727 // makes it difficult to check that. Therefore, we let the child block
2728 // decide what it needs to reflow.
2729 aLine->MarkDirty();
2730 } else {
2731 bool wasImpactedByFloat = aLine->IsImpactedByFloat();
2732 nsFlowAreaRect floatAvailableSpace =
2733 aState.GetFloatAvailableSpaceForBSize(aLine->BStart() + aDeltaBCoord,
2734 aLine->BSize(), nullptr);
2736 #ifdef REALLY_NOISY_REFLOW
2737 printf("nsBlockFrame::PropagateFloatDamage %p was = %d, is=%d\n", this,
2738 wasImpactedByFloat, floatAvailableSpace.HasFloats());
2739 #endif
2741 // Mark the line dirty if it was or is affected by a float
2742 // We actually only really need to reflow if the amount of impact
2743 // changes, but that's not straightforward to check
2744 if (wasImpactedByFloat || floatAvailableSpace.HasFloats()) {
2745 aLine->MarkDirty();
2751 static bool LineHasClear(nsLineBox* aLine) {
2752 return aLine->IsBlock()
2753 ? (aLine->HasForcedLineBreakBefore() ||
2754 aLine->mFirstChild->HasAnyStateBits(
2755 NS_BLOCK_HAS_CLEAR_CHILDREN) ||
2756 !nsBlockFrame::BlockCanIntersectFloats(aLine->mFirstChild))
2757 : aLine->HasFloatClearTypeAfter();
2761 * Reparent a whole list of floats from aOldParent to this block. The
2762 * floats might be taken from aOldParent's overflow list. They will be
2763 * removed from the list. They end up appended to our mFloats list.
2765 void nsBlockFrame::ReparentFloats(nsIFrame* aFirstFrame,
2766 nsBlockFrame* aOldParent,
2767 bool aReparentSiblings) {
2768 nsFrameList list;
2769 aOldParent->CollectFloats(aFirstFrame, list, aReparentSiblings);
2770 if (list.NotEmpty()) {
2771 for (nsIFrame* f : list) {
2772 MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
2773 "CollectFloats should've removed that bit");
2774 ReparentFrame(f, aOldParent, this);
2776 mFloats.AppendFrames(nullptr, std::move(list));
2780 static void DumpLine(const BlockReflowState& aState, nsLineBox* aLine,
2781 nscoord aDeltaBCoord, int32_t aDeltaIndent) {
2782 #ifdef DEBUG
2783 if (nsBlockFrame::gNoisyReflow) {
2784 nsRect ovis(aLine->InkOverflowRect());
2785 nsRect oscr(aLine->ScrollableOverflowRect());
2786 nsBlockFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent + aDeltaIndent);
2787 printf(
2788 "line=%p mBCoord=%d dirty=%s oldBounds={%d,%d,%d,%d} "
2789 "oldoverflow-vis={%d,%d,%d,%d} oldoverflow-scr={%d,%d,%d,%d} "
2790 "deltaBCoord=%d mPrevBEndMargin=%d childCount=%d\n",
2791 static_cast<void*>(aLine), aState.mBCoord,
2792 aLine->IsDirty() ? "yes" : "no", aLine->IStart(), aLine->BStart(),
2793 aLine->ISize(), aLine->BSize(), ovis.x, ovis.y, ovis.width, ovis.height,
2794 oscr.x, oscr.y, oscr.width, oscr.height, aDeltaBCoord,
2795 aState.mPrevBEndMargin.get(), aLine->GetChildCount());
2797 #endif
2800 static bool LinesAreEmpty(const nsLineList& aList) {
2801 for (const auto& line : aList) {
2802 if (!line.IsEmpty()) {
2803 return false;
2806 return true;
2809 void nsBlockFrame::ReflowDirtyLines(BlockReflowState& aState) {
2810 bool keepGoing = true;
2811 bool repositionViews = false; // should we really need this?
2812 bool foundAnyClears = aState.mTrailingClearFromPIF != StyleClear::None;
2813 bool willReflowAgain = false;
2815 #ifdef DEBUG
2816 if (gNoisyReflow) {
2817 IndentBy(stdout, gNoiseIndent);
2818 ListTag(stdout);
2819 printf(": reflowing dirty lines");
2820 printf(" computedISize=%d\n", aState.mReflowInput.ComputedISize());
2822 AutoNoisyIndenter indent(gNoisyReflow);
2823 #endif
2825 bool selfDirty = HasAnyStateBits(NS_FRAME_IS_DIRTY) ||
2826 (aState.mReflowInput.IsBResize() &&
2827 HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE));
2829 // Reflow our last line if our availableBSize has increased
2830 // so that we (and our last child) pull up content as necessary
2831 if (aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
2832 GetNextInFlow() &&
2833 aState.mReflowInput.AvailableBSize() >
2834 GetLogicalSize().BSize(aState.mReflowInput.GetWritingMode())) {
2835 LineIterator lastLine = LinesEnd();
2836 if (lastLine != LinesBegin()) {
2837 --lastLine;
2838 lastLine->MarkDirty();
2841 // the amount by which we will slide the current line if it is not
2842 // dirty
2843 nscoord deltaBCoord = 0;
2845 // whether we did NOT reflow the previous line and thus we need to
2846 // recompute the carried out margin before the line if we want to
2847 // reflow it or if its previous margin is dirty
2848 bool needToRecoverState = false;
2849 // Float continuations were reflowed in ReflowPushedFloats
2850 bool reflowedFloat =
2851 mFloats.NotEmpty() &&
2852 mFloats.FirstChild()->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT);
2853 bool lastLineMovedUp = false;
2854 // We save up information about BR-clearance here
2855 StyleClear inlineFloatClearType = aState.mTrailingClearFromPIF;
2857 LineIterator line = LinesBegin(), line_end = LinesEnd();
2859 // Determine if children of this frame could have breaks between them for
2860 // page names.
2862 // We need to check for paginated layout, the named-page pref, and if the
2863 // available block-size is constrained.
2865 // Note that we need to check for paginated layout as named-pages are only
2866 // used during paginated reflow. We need to additionally check for
2867 // unconstrained block-size to avoid introducing fragmentation breaks during
2868 // "measuring" reflows within an overall paginated reflow, and to avoid
2869 // fragmentation in monolithic containers like 'inline-block'.
2871 // Because we can only break for named pages using Class A breakpoints, we
2872 // also need to check that the block flow direction of the containing frame
2873 // of these items (which is this block) is parallel to that of this page.
2874 // See: https://www.w3.org/TR/css-break-3/#btw-blocks
2875 const nsPresContext* const presCtx = aState.mPresContext;
2876 const bool canBreakForPageNames =
2877 aState.mReflowInput.mFlags.mCanHaveClassABreakpoints &&
2878 aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
2879 presCtx->GetPresShell()->GetRootFrame()->GetWritingMode().IsVertical() ==
2880 GetWritingMode().IsVertical();
2882 // ReflowInput.mFlags.mCanHaveClassABreakpoints should respect the named
2883 // pages pref and presCtx->IsPaginated, so we did not explicitly check these
2884 // above when setting canBreakForPageNames.
2885 if (canBreakForPageNames) {
2886 MOZ_ASSERT(presCtx->IsPaginated(),
2887 "canBreakForPageNames should not be set during non-paginated "
2888 "reflow");
2891 // Reflow the lines that are already ours
2892 for (; line != line_end; ++line, aState.AdvanceToNextLine()) {
2893 DumpLine(aState, line, deltaBCoord, 0);
2894 #ifdef DEBUG
2895 AutoNoisyIndenter indent2(gNoisyReflow);
2896 #endif
2898 if (selfDirty) {
2899 line->MarkDirty();
2902 // This really sucks, but we have to look inside any blocks that have clear
2903 // elements inside them.
2904 // XXX what can we do smarter here?
2905 if (!line->IsDirty() && line->IsBlock() &&
2906 line->mFirstChild->HasAnyStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN)) {
2907 line->MarkDirty();
2910 nsIFrame* floatAvoidingBlock = nullptr;
2911 if (line->IsBlock() &&
2912 !nsBlockFrame::BlockCanIntersectFloats(line->mFirstChild)) {
2913 floatAvoidingBlock = line->mFirstChild;
2916 // We have to reflow the line if it's a block whose clearance
2917 // might have changed, so detect that.
2918 if (!line->IsDirty() &&
2919 (line->HasForcedLineBreakBefore() || floatAvoidingBlock)) {
2920 nscoord curBCoord = aState.mBCoord;
2921 // See where we would be after applying any clearance due to
2922 // BRs.
2923 if (inlineFloatClearType != StyleClear::None) {
2924 std::tie(curBCoord, std::ignore) =
2925 aState.ClearFloats(curBCoord, inlineFloatClearType);
2928 auto [newBCoord, result] = aState.ClearFloats(
2929 curBCoord, line->FloatClearTypeBefore(), floatAvoidingBlock);
2931 if (line->HasClearance()) {
2932 // Reflow the line if it might not have clearance anymore.
2933 if (result == ClearFloatsResult::BCoordNoChange
2934 // aState.mBCoord is the clearance point which should be the
2935 // block-start border-edge of the block frame. If sliding the
2936 // block by deltaBCoord isn't going to put it in the predicted
2937 // position, then we'd better reflow the line.
2938 || newBCoord != line->BStart() + deltaBCoord) {
2939 line->MarkDirty();
2941 } else {
2942 // Reflow the line if the line might have clearance now.
2943 if (result != ClearFloatsResult::BCoordNoChange) {
2944 line->MarkDirty();
2949 // We might have to reflow a line that is after a clearing BR.
2950 if (inlineFloatClearType != StyleClear::None) {
2951 std::tie(aState.mBCoord, std::ignore) =
2952 aState.ClearFloats(aState.mBCoord, inlineFloatClearType);
2953 if (aState.mBCoord != line->BStart() + deltaBCoord) {
2954 // SlideLine is not going to put the line where the clearance
2955 // put it. Reflow the line to be sure.
2956 line->MarkDirty();
2958 inlineFloatClearType = StyleClear::None;
2961 bool previousMarginWasDirty = line->IsPreviousMarginDirty();
2962 if (previousMarginWasDirty) {
2963 // If the previous margin is dirty, reflow the current line
2964 line->MarkDirty();
2965 line->ClearPreviousMarginDirty();
2966 } else if (aState.ContentBSize() != NS_UNCONSTRAINEDSIZE) {
2967 const nscoord scrollableOverflowBEnd =
2968 LogicalRect(line->mWritingMode, line->ScrollableOverflowRect(),
2969 line->mContainerSize)
2970 .BEnd(line->mWritingMode);
2971 if (scrollableOverflowBEnd + deltaBCoord > aState.ContentBEnd()) {
2972 // Lines that aren't dirty but get slid past our available block-size
2973 // constraint must be reflowed.
2974 line->MarkDirty();
2978 if (!line->IsDirty()) {
2979 const bool isPaginated =
2980 // Last column can be reflowed unconstrained during column balancing.
2981 // Hence the additional NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR bit check
2982 // as a fail-safe fallback.
2983 aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE ||
2984 HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) ||
2985 // Table can also be reflowed unconstrained during printing.
2986 aState.mPresContext->IsPaginated();
2987 if (isPaginated) {
2988 // We are in a paginated context, i.e. in columns or pages.
2989 const bool mayContainFloats =
2990 line->IsBlock() || line->HasFloats() || line->HadFloatPushed();
2991 if (mayContainFloats) {
2992 // The following if-else conditions check whether this line -- which
2993 // might have floats in its subtree, or has floats as direct children,
2994 // or had floats pushed -- needs to be reflowed.
2995 if (deltaBCoord != 0 || aState.mReflowInput.IsBResize()) {
2996 // The distance to the block-end edge might have changed. Reflow the
2997 // line both because the breakpoints within its floats may have
2998 // changed and because we might have to push/pull the floats in
2999 // their entirety.
3000 line->MarkDirty();
3001 } else if (HasPushedFloats()) {
3002 // We had pushed floats which haven't been drained by our
3003 // next-in-flow, which means our parent is currently reflowing us
3004 // again due to clearance without creating a next-in-flow for us.
3005 // Reflow the line to redo the floats split logic to correctly set
3006 // our reflow status.
3007 line->MarkDirty();
3008 } else if (aState.mReflowInput.mFlags.mMustReflowPlaceholders) {
3009 // Reflow the line (that may containing a float's placeholder frame)
3010 // if our parent tells us to do so.
3011 line->MarkDirty();
3012 } else if (aState.mReflowInput.mFlags.mMovedBlockFragments) {
3013 // Our parent's line containing us moved to a different fragment.
3014 // Reflow the line because the decision about whether the float fits
3015 // may be different in a different fragment.
3016 line->MarkDirty();
3022 if (!line->IsDirty()) {
3023 // See if there's any reflow damage that requires that we mark the
3024 // line dirty.
3025 PropagateFloatDamage(aState, line, deltaBCoord);
3028 // If the container size has changed, reset mContainerSize. If the
3029 // line's writing mode is not ltr, or if the line is not left-aligned, also
3030 // mark the line dirty.
3031 if (aState.ContainerSize() != line->mContainerSize) {
3032 line->mContainerSize = aState.ContainerSize();
3034 const bool isLastLine = line == mLines.back() && !GetNextInFlow();
3035 const auto align = isLastLine ? StyleText()->TextAlignForLastLine()
3036 : StyleText()->mTextAlign;
3037 if (line->mWritingMode.IsVertical() || line->mWritingMode.IsBidiRTL() ||
3038 !IsAlignedLeft(align, StyleVisibility()->mDirection,
3039 StyleTextReset()->mUnicodeBidi, this)) {
3040 line->MarkDirty();
3044 // Check for a page break caused by CSS named pages.
3046 // We should break for named pages when two frames meet at a class A
3047 // breakpoint, where the first frame has a different end page value to the
3048 // second frame's start page value. canBreakForPageNames is true iff
3049 // children of this frame can form class A breakpoints, and that we are not
3050 // in a measurement reflow or in a monolithic container such as
3051 // 'inline-block'.
3053 // We specifically do not want to cause a page-break for named pages when
3054 // we are at the top of a page. This would otherwise happen when the
3055 // previous sibling is an nsPageBreakFrame, or all previous siblings on the
3056 // current page are zero-height. The latter may not be per-spec, but is
3057 // compatible with Chrome's implementation of named pages.
3058 const nsAtom* nextPageName = nullptr;
3059 bool shouldBreakForPageName = false;
3060 if (canBreakForPageNames && (!aState.mReflowInput.mFlags.mIsTopOfPage ||
3061 !aState.IsAdjacentWithBStart())) {
3062 const nsIFrame* const frame = line->mFirstChild;
3063 if (!frame->IsPlaceholderFrame()) {
3064 nextPageName = frame->GetStartPageValue();
3065 // Walk back to the last frame that isn't a placeholder.
3066 const nsIFrame* prevFrame = frame->GetPrevSibling();
3067 while (prevFrame && prevFrame->IsPlaceholderFrame()) {
3068 prevFrame = prevFrame->GetPrevSibling();
3070 if (prevFrame && prevFrame->GetEndPageValue() != nextPageName) {
3071 shouldBreakForPageName = true;
3072 line->MarkDirty();
3077 if (needToRecoverState && line->IsDirty()) {
3078 // We need to reconstruct the block-end margin only if we didn't
3079 // reflow the previous line and we do need to reflow (or repair
3080 // the block-start position of) the next line.
3081 aState.ReconstructMarginBefore(line);
3084 bool reflowedPrevLine = !needToRecoverState;
3085 if (needToRecoverState) {
3086 needToRecoverState = false;
3088 // Update aState.mPrevChild as if we had reflowed all of the frames in
3089 // this line.
3090 if (line->IsDirty()) {
3091 NS_ASSERTION(
3092 line->mFirstChild->GetPrevSibling() == line.prev()->LastChild(),
3093 "unexpected line frames");
3094 aState.mPrevChild = line->mFirstChild->GetPrevSibling();
3098 // Now repair the line and update |aState.mBCoord| by calling
3099 // |ReflowLine| or |SlideLine|.
3100 // If we're going to reflow everything again, then no need to reflow
3101 // the dirty line ... unless the line has floats, in which case we'd
3102 // better reflow it now to refresh its float cache, which may contain
3103 // dangling frame pointers! Ugh! This reflow of the line may be
3104 // incorrect because we skipped reflowing previous lines (e.g., floats
3105 // may be placed incorrectly), but that's OK because we'll mark the
3106 // line dirty below under "if (aState.mReflowInput.mDiscoveredClearance..."
3107 if (line->IsDirty() && (line->HasFloats() || !willReflowAgain)) {
3108 lastLineMovedUp = true;
3110 bool maybeReflowingForFirstTime =
3111 line->IStart() == 0 && line->BStart() == 0 && line->ISize() == 0 &&
3112 line->BSize() == 0;
3114 // Compute the dirty lines "before" BEnd, after factoring in
3115 // the running deltaBCoord value - the running value is implicit in
3116 // aState.mBCoord.
3117 nscoord oldB = line->BStart();
3118 nscoord oldBMost = line->BEnd();
3120 NS_ASSERTION(!willReflowAgain || !line->IsBlock(),
3121 "Don't reflow blocks while willReflowAgain is true, reflow "
3122 "of block abs-pos children depends on this");
3124 if (shouldBreakForPageName) {
3125 // Immediately fragment for page-name. It is possible we could break
3126 // out of the loop right here, but this should make it more similar to
3127 // what happens when reflow causes fragmentation.
3128 PushTruncatedLine(aState, line, &keepGoing);
3129 PresShell()->FrameConstructor()->SetNextPageContentFramePageName(
3130 nextPageName ? nextPageName : GetAutoPageValue());
3131 } else {
3132 // Reflow the dirty line. If it's an incremental reflow, then force
3133 // it to invalidate the dirty area if necessary
3134 ReflowLine(aState, line, &keepGoing);
3137 if (aState.mReflowInput.WillReflowAgainForClearance()) {
3138 line->MarkDirty();
3139 willReflowAgain = true;
3140 // Note that once we've entered this state, every line that gets here
3141 // (e.g. because it has floats) gets marked dirty and reflowed again.
3142 // in the next pass. This is important, see above.
3145 if (line->HasFloats()) {
3146 reflowedFloat = true;
3149 if (!keepGoing) {
3150 DumpLine(aState, line, deltaBCoord, -1);
3151 if (0 == line->GetChildCount()) {
3152 DeleteLine(aState, line, line_end);
3154 break;
3157 // Test to see whether the margin that should be carried out
3158 // to the next line (NL) might have changed. In ReflowBlockFrame
3159 // we call nextLine->MarkPreviousMarginDirty if the block's
3160 // actual carried-out block-end margin changed. So here we only
3161 // need to worry about the following effects:
3162 // 1) the line was just created, and it might now be blocking
3163 // a carried-out block-end margin from previous lines that
3164 // used to reach NL from reaching NL
3165 // 2) the line used to be empty, and is now not empty,
3166 // thus blocking a carried-out block-end margin from previous lines
3167 // that used to reach NL from reaching NL
3168 // 3) the line wasn't empty, but now is, so a carried-out
3169 // block-end margin from previous lines that didn't used to reach NL
3170 // now does
3171 // 4) the line might have changed in a way that affects NL's
3172 // ShouldApplyBStartMargin decision. The three things that matter
3173 // are the line's emptiness, its adjacency to the block-start edge of the
3174 // block, and whether it has clearance (the latter only matters if the
3175 // block was and is adjacent to the block-start and empty).
3177 // If the line is empty now, we can't reliably tell if the line was empty
3178 // before, so we just assume it was and do
3179 // nextLine->MarkPreviousMarginDirty. This means the checks in 4) are
3180 // redundant; if the line is empty now we don't need to check 4), but if
3181 // the line is not empty now and we're sure it wasn't empty before, any
3182 // adjacency and clearance changes are irrelevant to the result of
3183 // nextLine->ShouldApplyBStartMargin.
3184 if (line.next() != LinesEnd()) {
3185 bool maybeWasEmpty = oldB == line.next()->BStart();
3186 bool isEmpty = line->CachedIsEmpty();
3187 if (maybeReflowingForFirstTime /*1*/ ||
3188 (isEmpty || maybeWasEmpty) /*2/3/4*/) {
3189 line.next()->MarkPreviousMarginDirty();
3190 // since it's marked dirty, nobody will care about |deltaBCoord|
3194 // If the line was just reflowed for the first time, then its
3195 // old mBounds cannot be trusted so this deltaBCoord computation is
3196 // bogus. But that's OK because we just did
3197 // MarkPreviousMarginDirty on the next line which will force it
3198 // to be reflowed, so this computation of deltaBCoord will not be
3199 // used.
3200 deltaBCoord = line->BEnd() - oldBMost;
3202 // Now do an interrupt check. We want to do this only in the case when we
3203 // actually reflow the line, so that if we get back in here we'll get
3204 // further on the reflow before interrupting.
3205 aState.mPresContext->CheckForInterrupt(this);
3206 } else {
3207 aState.mOverflowTracker->Skip(line->mFirstChild, aState.mReflowStatus);
3208 // Nop except for blocks (we don't create overflow container
3209 // continuations for any inlines atm), so only checking mFirstChild
3210 // is enough
3212 lastLineMovedUp = deltaBCoord < 0;
3214 if (deltaBCoord != 0) {
3215 SlideLine(aState, line, deltaBCoord);
3216 } else {
3217 repositionViews = true;
3220 NS_ASSERTION(!line->IsDirty() || !line->HasFloats(),
3221 "Possibly stale float cache here!");
3222 if (willReflowAgain && line->IsBlock()) {
3223 // If we're going to reflow everything again, and this line is a block,
3224 // then there is no need to recover float state. The line may contain
3225 // other lines with floats, but in that case RecoverStateFrom would only
3226 // add floats to the float manager. We don't need to do that because
3227 // everything's going to get reflowed again "for real". Calling
3228 // RecoverStateFrom in this situation could be lethal because the
3229 // block's descendant lines may have float caches containing dangling
3230 // frame pointers. Ugh!
3231 // If this line is inline, then we need to recover its state now
3232 // to make sure that we don't forget to move its floats by deltaBCoord.
3233 } else {
3234 // XXX EVIL O(N^2) EVIL
3235 aState.RecoverStateFrom(line, deltaBCoord);
3238 // Keep mBCoord up to date in case we're propagating reflow damage
3239 // and also because our final height may depend on it. If the
3240 // line is inlines, then only update mBCoord if the line is not
3241 // empty, because that's what PlaceLine does. (Empty blocks may
3242 // want to update mBCoord, e.g. if they have clearance.)
3243 if (line->IsBlock() || !line->CachedIsEmpty()) {
3244 aState.mBCoord = line->BEnd();
3247 needToRecoverState = true;
3249 if (reflowedPrevLine && !line->IsBlock() &&
3250 aState.mPresContext->HasPendingInterrupt()) {
3251 // Need to make sure to pull overflows from any prev-in-flows
3252 for (nsIFrame* inlineKid = line->mFirstChild; inlineKid;
3253 inlineKid = inlineKid->PrincipalChildList().FirstChild()) {
3254 inlineKid->PullOverflowsFromPrevInFlow();
3259 // Record if we need to clear floats before reflowing the next
3260 // line. Note that inlineFloatClearType will be handled and
3261 // cleared before the next line is processed, so there is no
3262 // need to combine break types here.
3263 if (line->HasFloatClearTypeAfter()) {
3264 inlineFloatClearType = line->FloatClearTypeAfter();
3267 if (LineHasClear(line.get())) {
3268 foundAnyClears = true;
3271 DumpLine(aState, line, deltaBCoord, -1);
3273 if (aState.mPresContext->HasPendingInterrupt()) {
3274 willReflowAgain = true;
3275 // Another option here might be to leave |line| clean if
3276 // !HasPendingInterrupt() before the CheckForInterrupt() call, since in
3277 // that case the line really did reflow as it should have. Not sure
3278 // whether that would be safe, so doing this for now instead. Also not
3279 // sure whether we really want to mark all lines dirty after an
3280 // interrupt, but until we get better at propagating float damage we
3281 // really do need to do it this way; see comments inside MarkLineDirty.
3282 MarkLineDirtyForInterrupt(line);
3286 // Handle BR-clearance from the last line of the block
3287 if (inlineFloatClearType != StyleClear::None) {
3288 std::tie(aState.mBCoord, std::ignore) =
3289 aState.ClearFloats(aState.mBCoord, inlineFloatClearType);
3292 if (needToRecoverState) {
3293 // Is this expensive?
3294 aState.ReconstructMarginBefore(line);
3296 // Update aState.mPrevChild as if we had reflowed all of the frames in
3297 // the last line.
3298 NS_ASSERTION(line == line_end || line->mFirstChild->GetPrevSibling() ==
3299 line.prev()->LastChild(),
3300 "unexpected line frames");
3301 aState.mPrevChild = line == line_end ? mFrames.LastChild()
3302 : line->mFirstChild->GetPrevSibling();
3305 // Should we really have to do this?
3306 if (repositionViews) {
3307 nsContainerFrame::PlaceFrameView(this);
3310 // We can skip trying to pull up the next line if our height is constrained
3311 // (so we can report being incomplete) and there is no next in flow or we
3312 // were told not to or we know it will be futile, i.e.,
3313 // -- the next in flow is not changing
3314 // -- and we cannot have added more space for its first line to be
3315 // pulled up into,
3316 // -- it's an incremental reflow of a descendant
3317 // -- and we didn't reflow any floats (so the available space
3318 // didn't change)
3319 // -- my chain of next-in-flows either has no first line, or its first
3320 // line isn't dirty.
3321 bool heightConstrained =
3322 aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE;
3323 bool skipPull = willReflowAgain && heightConstrained;
3324 if (!skipPull && heightConstrained && aState.mNextInFlow &&
3325 (aState.mReflowInput.mFlags.mNextInFlowUntouched && !lastLineMovedUp &&
3326 !HasAnyStateBits(NS_FRAME_IS_DIRTY) && !reflowedFloat)) {
3327 // We'll place lineIter at the last line of this block, so that
3328 // nsBlockInFlowLineIterator::Next() will take us to the first
3329 // line of my next-in-flow-chain. (But first, check that I
3330 // have any lines -- if I don't, just bail out of this
3331 // optimization.)
3332 LineIterator lineIter = this->LinesEnd();
3333 if (lineIter != this->LinesBegin()) {
3334 lineIter--; // I have lines; step back from dummy iterator to last line.
3335 nsBlockInFlowLineIterator bifLineIter(this, lineIter);
3337 // Check for next-in-flow-chain's first line.
3338 // (First, see if there is such a line, and second, see if it's clean)
3339 if (!bifLineIter.Next() || !bifLineIter.GetLine()->IsDirty()) {
3340 skipPull = true;
3345 if (skipPull && aState.mNextInFlow) {
3346 NS_ASSERTION(heightConstrained, "Height should be constrained here\n");
3347 if (aState.mNextInFlow->IsTrueOverflowContainer()) {
3348 aState.mReflowStatus.SetOverflowIncomplete();
3349 } else {
3350 aState.mReflowStatus.SetIncomplete();
3354 if (!skipPull && aState.mNextInFlow) {
3355 // Pull data from a next-in-flow if there's still room for more
3356 // content here.
3357 while (keepGoing && aState.mNextInFlow) {
3358 // Grab first line from our next-in-flow
3359 nsBlockFrame* nextInFlow = aState.mNextInFlow;
3360 nsLineBox* pulledLine;
3361 nsFrameList pulledFrames;
3362 if (!nextInFlow->mLines.empty()) {
3363 RemoveFirstLine(nextInFlow->mLines, nextInFlow->mFrames, &pulledLine,
3364 &pulledFrames);
3365 } else {
3366 // Grab an overflow line if there are any
3367 FrameLines* overflowLines = nextInFlow->GetOverflowLines();
3368 if (!overflowLines) {
3369 aState.mNextInFlow =
3370 static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow());
3371 continue;
3373 bool last =
3374 RemoveFirstLine(overflowLines->mLines, overflowLines->mFrames,
3375 &pulledLine, &pulledFrames);
3376 if (last) {
3377 nextInFlow->DestroyOverflowLines();
3381 if (pulledFrames.IsEmpty()) {
3382 // The line is empty. Try the next one.
3383 NS_ASSERTION(
3384 pulledLine->GetChildCount() == 0 && !pulledLine->mFirstChild,
3385 "bad empty line");
3386 nextInFlow->FreeLineBox(pulledLine);
3387 continue;
3390 if (nextInFlow->MaybeHasLineCursor()) {
3391 if (pulledLine == nextInFlow->GetLineCursorForDisplay()) {
3392 nextInFlow->ClearLineCursorForDisplay();
3394 if (pulledLine == nextInFlow->GetLineCursorForQuery()) {
3395 nextInFlow->ClearLineCursorForQuery();
3398 ReparentFrames(pulledFrames, nextInFlow, this);
3399 pulledLine->SetMovedFragments();
3401 NS_ASSERTION(pulledFrames.LastChild() == pulledLine->LastChild(),
3402 "Unexpected last frame");
3403 NS_ASSERTION(aState.mPrevChild || mLines.empty(),
3404 "should have a prevchild here");
3405 NS_ASSERTION(aState.mPrevChild == mFrames.LastChild(),
3406 "Incorrect aState.mPrevChild before inserting line at end");
3408 // Shift pulledLine's frames into our mFrames list.
3409 mFrames.AppendFrames(nullptr, std::move(pulledFrames));
3411 // Add line to our line list, and set its last child as our new prev-child
3412 line = mLines.before_insert(LinesEnd(), pulledLine);
3413 aState.mPrevChild = mFrames.LastChild();
3415 // Reparent floats whose placeholders are in the line.
3416 ReparentFloats(pulledLine->mFirstChild, nextInFlow, true);
3418 DumpLine(aState, pulledLine, deltaBCoord, 0);
3419 #ifdef DEBUG
3420 AutoNoisyIndenter indent2(gNoisyReflow);
3421 #endif
3423 if (aState.mPresContext->HasPendingInterrupt()) {
3424 MarkLineDirtyForInterrupt(line);
3425 } else {
3426 // Now reflow it and any lines that it makes during it's reflow
3427 // (we have to loop here because reflowing the line may cause a new
3428 // line to be created; see SplitLine's callers for examples of
3429 // when this happens).
3430 while (line != LinesEnd()) {
3431 ReflowLine(aState, line, &keepGoing);
3433 if (aState.mReflowInput.WillReflowAgainForClearance()) {
3434 line->MarkDirty();
3435 keepGoing = false;
3436 aState.mReflowStatus.SetIncomplete();
3437 break;
3440 DumpLine(aState, line, deltaBCoord, -1);
3441 if (!keepGoing) {
3442 if (0 == line->GetChildCount()) {
3443 DeleteLine(aState, line, line_end);
3445 break;
3448 if (LineHasClear(line.get())) {
3449 foundAnyClears = true;
3452 if (aState.mPresContext->CheckForInterrupt(this)) {
3453 MarkLineDirtyForInterrupt(line);
3454 break;
3457 // If this is an inline frame then its time to stop
3458 ++line;
3459 aState.AdvanceToNextLine();
3464 if (aState.mReflowStatus.IsIncomplete()) {
3465 aState.mReflowStatus.SetNextInFlowNeedsReflow();
3466 } // XXXfr shouldn't set this flag when nextinflow has no lines
3469 // Handle an odd-ball case: a list-item with no lines
3470 if (mLines.empty() && HasOutsideMarker()) {
3471 ReflowOutput metrics(aState.mReflowInput);
3472 nsIFrame* marker = GetOutsideMarker();
3473 WritingMode wm = aState.mReflowInput.GetWritingMode();
3474 ReflowOutsideMarker(
3475 marker, aState, metrics,
3476 aState.mReflowInput.ComputedPhysicalBorderPadding().top);
3477 NS_ASSERTION(!MarkerIsEmpty() || metrics.BSize(wm) == 0,
3478 "empty ::marker frame took up space");
3480 if (!MarkerIsEmpty()) {
3481 // There are no lines so we have to fake up some y motion so that
3482 // we end up with *some* height.
3483 // (Note: if we're layout-contained, we have to be sure to leave our
3484 // ReflowOutput's BlockStartAscent() (i.e. the baseline) untouched,
3485 // because layout-contained frames have no baseline.)
3486 if (!aState.mReflowInput.mStyleDisplay->IsContainLayout() &&
3487 metrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
3488 nscoord ascent;
3489 WritingMode wm = aState.mReflowInput.GetWritingMode();
3490 if (nsLayoutUtils::GetFirstLineBaseline(wm, marker, &ascent)) {
3491 metrics.SetBlockStartAscent(ascent);
3492 } else {
3493 metrics.SetBlockStartAscent(metrics.BSize(wm));
3497 RefPtr<nsFontMetrics> fm =
3498 nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
3500 nscoord minAscent = nsLayoutUtils::GetCenteredFontBaseline(
3501 fm, aState.mMinLineHeight, wm.IsLineInverted());
3502 nscoord minDescent = aState.mMinLineHeight - minAscent;
3504 aState.mBCoord +=
3505 std::max(minAscent, metrics.BlockStartAscent()) +
3506 std::max(minDescent, metrics.BSize(wm) - metrics.BlockStartAscent());
3508 nscoord offset = minAscent - metrics.BlockStartAscent();
3509 if (offset > 0) {
3510 marker->SetRect(marker->GetRect() + nsPoint(0, offset));
3515 if (LinesAreEmpty(mLines) && ShouldHaveLineIfEmpty()) {
3516 aState.mBCoord += aState.mMinLineHeight;
3519 if (foundAnyClears) {
3520 AddStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN);
3521 } else {
3522 RemoveStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN);
3525 #ifdef DEBUG
3526 VerifyLines(true);
3527 VerifyOverflowSituation();
3528 if (gNoisyReflow) {
3529 IndentBy(stdout, gNoiseIndent - 1);
3530 ListTag(stdout);
3531 printf(": done reflowing dirty lines (status=%s)\n",
3532 ToString(aState.mReflowStatus).c_str());
3534 #endif
3537 void nsBlockFrame::MarkLineDirtyForInterrupt(nsLineBox* aLine) {
3538 aLine->MarkDirty();
3540 // Just checking NS_FRAME_IS_DIRTY is ok, because we've already
3541 // marked the lines that need to be marked dirty based on our
3542 // vertical resize stuff. So we'll definitely reflow all those kids;
3543 // the only question is how they should behave.
3544 if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
3545 // Mark all our child frames dirty so we make sure to reflow them
3546 // later.
3547 int32_t n = aLine->GetChildCount();
3548 for (nsIFrame* f = aLine->mFirstChild; n > 0;
3549 f = f->GetNextSibling(), --n) {
3550 f->MarkSubtreeDirty();
3552 // And mark all the floats whose reflows we might be skipping dirty too.
3553 if (aLine->HasFloats()) {
3554 for (nsIFrame* f : aLine->Floats()) {
3555 f->MarkSubtreeDirty();
3558 } else {
3559 // Dirty all the descendant lines of block kids to handle float damage,
3560 // since our nsFloatManager will go away by the next time we're reflowing.
3561 // XXXbz Can we do something more like what PropagateFloatDamage does?
3562 // Would need to sort out the exact business with mBlockDelta for that....
3563 // This marks way too much dirty. If we ever make this better, revisit
3564 // which lines we mark dirty in the interrupt case in ReflowDirtyLines.
3565 nsBlockFrame* bf = do_QueryFrame(aLine->mFirstChild);
3566 if (bf) {
3567 MarkAllDescendantLinesDirty(bf);
3572 void nsBlockFrame::DeleteLine(BlockReflowState& aState,
3573 nsLineList::iterator aLine,
3574 nsLineList::iterator aLineEnd) {
3575 MOZ_ASSERT(0 == aLine->GetChildCount(), "can't delete !empty line");
3576 if (0 == aLine->GetChildCount()) {
3577 NS_ASSERTION(aState.mCurrentLine == aLine,
3578 "using function more generally than designed, "
3579 "but perhaps OK now");
3580 nsLineBox* line = aLine;
3581 aLine = mLines.erase(aLine);
3582 FreeLineBox(line);
3583 // Mark the previous margin of the next line dirty since we need to
3584 // recompute its top position.
3585 if (aLine != aLineEnd) {
3586 aLine->MarkPreviousMarginDirty();
3592 * Reflow a line. The line will either contain a single block frame
3593 * or contain 1 or more inline frames. aKeepReflowGoing indicates
3594 * whether or not the caller should continue to reflow more lines.
3596 void nsBlockFrame::ReflowLine(BlockReflowState& aState, LineIterator aLine,
3597 bool* aKeepReflowGoing) {
3598 MOZ_ASSERT(aLine->GetChildCount(), "reflowing empty line");
3600 // Setup the line-layout for the new line
3601 aState.mCurrentLine = aLine;
3602 aLine->ClearDirty();
3603 aLine->InvalidateCachedIsEmpty();
3604 aLine->ClearHadFloatPushed();
3606 // If this line contains a single block that is hidden by `content-visibility`
3607 // don't reflow the line. If this line contains inlines and the first one is
3608 // hidden by `content-visibility`, all of them are, so avoid reflow in that
3609 // case as well.
3610 // For frames that own anonymous children, even the first child is hidden by
3611 // `content-visibility`, there could be some anonymous children need reflow,
3612 // so we don't skip reflow this line.
3613 nsIFrame* firstChild = aLine->mFirstChild;
3614 if (firstChild->IsHiddenByContentVisibilityOfInFlowParentForLayout() &&
3615 !HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES)) {
3616 return;
3619 // Now that we know what kind of line we have, reflow it
3620 if (aLine->IsBlock()) {
3621 ReflowBlockFrame(aState, aLine, aKeepReflowGoing);
3622 } else {
3623 aLine->SetLineWrapped(false);
3624 ReflowInlineFrames(aState, aLine, aKeepReflowGoing);
3626 // Store the line's float edges for overflow marker analysis if needed.
3627 aLine->ClearFloatEdges();
3628 if (aState.mFlags.mCanHaveOverflowMarkers) {
3629 WritingMode wm = aLine->mWritingMode;
3630 nsFlowAreaRect r = aState.GetFloatAvailableSpaceForBSize(
3631 aLine->BStart(), aLine->BSize(), nullptr);
3632 if (r.HasFloats()) {
3633 LogicalRect so = aLine->GetOverflowArea(OverflowType::Scrollable, wm,
3634 aLine->mContainerSize);
3635 nscoord s = r.mRect.IStart(wm);
3636 nscoord e = r.mRect.IEnd(wm);
3637 if (so.IEnd(wm) > e || so.IStart(wm) < s) {
3638 // This line is overlapping a float - store the edges marking the area
3639 // between the floats for text-overflow analysis.
3640 aLine->SetFloatEdges(s, e);
3646 aLine->ClearMovedFragments();
3649 nsIFrame* nsBlockFrame::PullFrame(BlockReflowState& aState,
3650 LineIterator aLine) {
3651 // First check our remaining lines.
3652 if (LinesEnd() != aLine.next()) {
3653 return PullFrameFrom(aLine, this, aLine.next());
3656 NS_ASSERTION(
3657 !GetOverflowLines(),
3658 "Our overflow lines should have been removed at the start of reflow");
3660 // Try each next-in-flow.
3661 nsBlockFrame* nextInFlow = aState.mNextInFlow;
3662 while (nextInFlow) {
3663 if (nextInFlow->mLines.empty()) {
3664 nextInFlow->DrainSelfOverflowList();
3666 if (!nextInFlow->mLines.empty()) {
3667 return PullFrameFrom(aLine, nextInFlow, nextInFlow->mLines.begin());
3669 nextInFlow = static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow());
3670 aState.mNextInFlow = nextInFlow;
3673 return nullptr;
3676 nsIFrame* nsBlockFrame::PullFrameFrom(nsLineBox* aLine,
3677 nsBlockFrame* aFromContainer,
3678 nsLineList::iterator aFromLine) {
3679 nsLineBox* fromLine = aFromLine;
3680 MOZ_ASSERT(fromLine, "bad line to pull from");
3681 MOZ_ASSERT(fromLine->GetChildCount(), "empty line");
3682 MOZ_ASSERT(aLine->GetChildCount(), "empty line");
3683 MOZ_ASSERT(!HasProperty(LineIteratorProperty()),
3684 "Shouldn't have line iterators mid-reflow");
3686 NS_ASSERTION(fromLine->IsBlock() == fromLine->mFirstChild->IsBlockOutside(),
3687 "Disagreement about whether it's a block or not");
3689 if (fromLine->IsBlock()) {
3690 // If our line is not empty and the child in aFromLine is a block
3691 // then we cannot pull up the frame into this line. In this case
3692 // we stop pulling.
3693 return nullptr;
3695 // Take frame from fromLine
3696 nsIFrame* frame = fromLine->mFirstChild;
3697 nsIFrame* newFirstChild = frame->GetNextSibling();
3699 if (aFromContainer != this) {
3700 // The frame is being pulled from a next-in-flow; therefore we need to add
3701 // it to our sibling list.
3702 MOZ_ASSERT(aLine == mLines.back());
3703 MOZ_ASSERT(aFromLine == aFromContainer->mLines.begin(),
3704 "should only pull from first line");
3705 aFromContainer->mFrames.RemoveFrame(frame);
3707 // When pushing and pulling frames we need to check for whether any
3708 // views need to be reparented.
3709 ReparentFrame(frame, aFromContainer, this);
3710 mFrames.AppendFrame(nullptr, frame);
3712 // The frame might have (or contain) floats that need to be brought
3713 // over too. (pass 'false' since there are no siblings to check)
3714 ReparentFloats(frame, aFromContainer, false);
3715 } else {
3716 MOZ_ASSERT(aLine == aFromLine.prev());
3719 aLine->NoteFrameAdded(frame);
3720 fromLine->NoteFrameRemoved(frame);
3722 if (fromLine->GetChildCount() > 0) {
3723 // Mark line dirty now that we pulled a child
3724 fromLine->MarkDirty();
3725 fromLine->mFirstChild = newFirstChild;
3726 } else {
3727 // Free up the fromLine now that it's empty.
3728 // Its bounds might need to be redrawn, though.
3729 if (aFromLine.next() != aFromContainer->mLines.end()) {
3730 aFromLine.next()->MarkPreviousMarginDirty();
3732 aFromContainer->mLines.erase(aFromLine);
3733 // aFromLine is now invalid
3734 aFromContainer->FreeLineBox(fromLine);
3737 #ifdef DEBUG
3738 VerifyLines(true);
3739 VerifyOverflowSituation();
3740 #endif
3742 return frame;
3745 void nsBlockFrame::SlideLine(BlockReflowState& aState, nsLineBox* aLine,
3746 nscoord aDeltaBCoord) {
3747 MOZ_ASSERT(aDeltaBCoord != 0, "why slide a line nowhere?");
3749 // Adjust line state
3750 aLine->SlideBy(aDeltaBCoord, aState.ContainerSize());
3752 // Adjust the frames in the line
3753 MoveChildFramesOfLine(aLine, aDeltaBCoord);
3756 void nsBlockFrame::UpdateLineContainerSize(nsLineBox* aLine,
3757 const nsSize& aNewContainerSize) {
3758 if (aNewContainerSize == aLine->mContainerSize) {
3759 return;
3762 // Adjust line state
3763 nsSize sizeDelta = aLine->UpdateContainerSize(aNewContainerSize);
3765 // Changing container width only matters if writing mode is vertical-rl
3766 if (GetWritingMode().IsVerticalRL()) {
3767 MoveChildFramesOfLine(aLine, sizeDelta.width);
3771 void nsBlockFrame::MoveChildFramesOfLine(nsLineBox* aLine,
3772 nscoord aDeltaBCoord) {
3773 // Adjust the frames in the line
3774 nsIFrame* kid = aLine->mFirstChild;
3775 if (!kid) {
3776 return;
3779 WritingMode wm = GetWritingMode();
3780 LogicalPoint translation(wm, 0, aDeltaBCoord);
3782 if (aLine->IsBlock()) {
3783 if (aDeltaBCoord) {
3784 kid->MovePositionBy(wm, translation);
3787 // Make sure the frame's view and any child views are updated
3788 nsContainerFrame::PlaceFrameView(kid);
3789 } else {
3790 // Adjust the block-dir coordinate of the frames in the line.
3791 // Note: we need to re-position views even if aDeltaBCoord is 0, because
3792 // one of our parent frames may have moved and so the view's position
3793 // relative to its parent may have changed.
3794 int32_t n = aLine->GetChildCount();
3795 while (--n >= 0) {
3796 if (aDeltaBCoord) {
3797 kid->MovePositionBy(wm, translation);
3799 // Make sure the frame's view and any child views are updated
3800 nsContainerFrame::PlaceFrameView(kid);
3801 kid = kid->GetNextSibling();
3806 static inline bool IsNonAutoNonZeroBSize(const StyleSize& aCoord) {
3807 // The "extremum length" values (see ExtremumLength) were originally aimed at
3808 // inline-size (or width, as it was before logicalization). For now, let them
3809 // return false here, so we treat them like 'auto' pending a real
3810 // implementation. (See bug 1126420.)
3812 // FIXME (bug 567039, bug 527285) This isn't correct for the 'fill' value,
3813 // which should more likely (but not necessarily, depending on the available
3814 // space) be returning true.
3815 if (aCoord.BehavesLikeInitialValueOnBlockAxis()) {
3816 return false;
3818 MOZ_ASSERT(aCoord.IsLengthPercentage());
3819 // If we evaluate the length/percent/calc at a percentage basis of
3820 // both nscoord_MAX and 0, and it's zero both ways, then it's a zero
3821 // length, percent, or combination thereof. Test > 0 so we clamp
3822 // negative calc() results to 0.
3823 return aCoord.AsLengthPercentage().Resolve(nscoord_MAX) > 0 ||
3824 aCoord.AsLengthPercentage().Resolve(0) > 0;
3827 /* virtual */
3828 bool nsBlockFrame::IsSelfEmpty() {
3829 if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
3830 return true;
3833 // Blocks which are margin-roots (including inline-blocks) cannot be treated
3834 // as empty for margin-collapsing and other purposes. They're more like
3835 // replaced elements.
3836 if (HasAnyStateBits(NS_BLOCK_MARGIN_ROOT)) {
3837 return false;
3840 WritingMode wm = GetWritingMode();
3841 const nsStylePosition* position = StylePosition();
3843 if (IsNonAutoNonZeroBSize(position->MinBSize(wm)) ||
3844 IsNonAutoNonZeroBSize(position->BSize(wm))) {
3845 return false;
3848 // FIXME: Bug 1646100 - Take intrinsic size into account.
3849 // FIXME: Handle the case that both inline and block sizes are auto.
3850 // https://github.com/w3c/csswg-drafts/issues/5060.
3851 // Note: block-size could be zero or auto/intrinsic keywords here.
3852 if (position->BSize(wm).BehavesLikeInitialValueOnBlockAxis() &&
3853 position->mAspectRatio.HasFiniteRatio()) {
3854 return false;
3857 const nsStyleBorder* border = StyleBorder();
3858 const nsStylePadding* padding = StylePadding();
3860 if (border->GetComputedBorderWidth(wm.PhysicalSide(eLogicalSideBStart)) !=
3861 0 ||
3862 border->GetComputedBorderWidth(wm.PhysicalSide(eLogicalSideBEnd)) != 0 ||
3863 !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBStart(wm)) ||
3864 !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBEnd(wm))) {
3865 return false;
3868 if (HasOutsideMarker() && !MarkerIsEmpty()) {
3869 return false;
3872 return true;
3875 bool nsBlockFrame::CachedIsEmpty() {
3876 if (!IsSelfEmpty()) {
3877 return false;
3879 for (auto& line : mLines) {
3880 if (!line.CachedIsEmpty()) {
3881 return false;
3884 return true;
3887 bool nsBlockFrame::IsEmpty() {
3888 if (!IsSelfEmpty()) {
3889 return false;
3892 return LinesAreEmpty(mLines);
3895 bool nsBlockFrame::ShouldApplyBStartMargin(BlockReflowState& aState,
3896 nsLineBox* aLine) {
3897 if (aLine->mFirstChild->IsPageBreakFrame()) {
3898 // A page break frame consumes margins adjacent to it.
3899 // https://drafts.csswg.org/css-break/#break-margins
3900 return false;
3903 if (aState.mFlags.mShouldApplyBStartMargin) {
3904 // Apply short-circuit check to avoid searching the line list
3905 return true;
3908 if (!aState.IsAdjacentWithBStart()) {
3909 // If we aren't at the start block-coordinate then something of non-zero
3910 // height must have been placed. Therefore the childs block-start margin
3911 // applies.
3912 aState.mFlags.mShouldApplyBStartMargin = true;
3913 return true;
3916 // Determine if this line is "essentially" the first line
3917 LineIterator line = LinesBegin();
3918 if (aState.mFlags.mHasLineAdjacentToTop) {
3919 line = aState.mLineAdjacentToTop;
3921 while (line != aLine) {
3922 if (!line->CachedIsEmpty() || line->HasClearance()) {
3923 // A line which precedes aLine is non-empty, or has clearance,
3924 // so therefore the block-start margin applies.
3925 aState.mFlags.mShouldApplyBStartMargin = true;
3926 return true;
3928 // No need to apply the block-start margin if the line has floats. We
3929 // should collapse anyway (bug 44419)
3930 ++line;
3931 aState.mFlags.mHasLineAdjacentToTop = true;
3932 aState.mLineAdjacentToTop = line;
3935 // The line being reflowed is "essentially" the first line in the
3936 // block. Therefore its block-start margin will be collapsed by the
3937 // generational collapsing logic with its parent (us).
3938 return false;
3941 void nsBlockFrame::ReflowBlockFrame(BlockReflowState& aState,
3942 LineIterator aLine,
3943 bool* aKeepReflowGoing) {
3944 MOZ_ASSERT(*aKeepReflowGoing, "bad caller");
3946 nsIFrame* frame = aLine->mFirstChild;
3947 if (!frame) {
3948 NS_ASSERTION(false, "program error - unexpected empty line");
3949 return;
3952 // If the previous frame was a page-break-frame, then preemptively push this
3953 // frame to the next page.
3954 // This is primarily important for the placeholders for abspos frames, which
3955 // measure as zero height and then would be placed on this page.
3956 if (aState.ContentBSize() != NS_UNCONSTRAINEDSIZE) {
3957 const nsIFrame* const prev = frame->GetPrevSibling();
3958 if (prev && prev->IsPageBreakFrame()) {
3959 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
3960 return;
3964 // Prepare the block reflow engine
3965 nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput);
3967 StyleClear clearType = frame->StyleDisplay()->mClear;
3968 if (aState.mTrailingClearFromPIF != StyleClear::None) {
3969 clearType = nsLayoutUtils::CombineClearType(clearType,
3970 aState.mTrailingClearFromPIF);
3971 aState.mTrailingClearFromPIF = StyleClear::None;
3974 // Clear past floats before the block if the clear style is not none
3975 aLine->ClearForcedLineBreak();
3976 if (clearType != StyleClear::None) {
3977 aLine->SetForcedLineBreakBefore(clearType);
3980 // See if we should apply the block-start margin. If the block frame being
3981 // reflowed is a continuation, then we don't apply its block-start margin
3982 // because it's not significant. Otherwise, dig deeper.
3983 bool applyBStartMargin =
3984 !frame->GetPrevContinuation() && ShouldApplyBStartMargin(aState, aLine);
3985 if (applyBStartMargin) {
3986 // The HasClearance setting is only valid if ShouldApplyBStartMargin
3987 // returned false (in which case the block-start margin-root set our
3988 // clearance flag). Otherwise clear it now. We'll set it later on
3989 // ourselves if necessary.
3990 aLine->ClearHasClearance();
3992 bool treatWithClearance = aLine->HasClearance();
3994 bool mightClearFloats = clearType != StyleClear::None;
3995 nsIFrame* floatAvoidingBlock = nullptr;
3996 if (!nsBlockFrame::BlockCanIntersectFloats(frame)) {
3997 mightClearFloats = true;
3998 floatAvoidingBlock = frame;
4001 // If our block-start margin was counted as part of some parent's block-start
4002 // margin collapse, and we are being speculatively reflowed assuming this
4003 // frame DID NOT need clearance, then we need to check that
4004 // assumption.
4005 if (!treatWithClearance && !applyBStartMargin && mightClearFloats &&
4006 aState.mReflowInput.mDiscoveredClearance) {
4007 nscoord curBCoord = aState.mBCoord + aState.mPrevBEndMargin.get();
4008 if (auto [clearBCoord, result] =
4009 aState.ClearFloats(curBCoord, clearType, floatAvoidingBlock);
4010 result != ClearFloatsResult::BCoordNoChange) {
4011 Unused << clearBCoord;
4013 // Only record the first frame that requires clearance
4014 if (!*aState.mReflowInput.mDiscoveredClearance) {
4015 *aState.mReflowInput.mDiscoveredClearance = frame;
4017 aState.mPrevChild = frame;
4018 // Exactly what we do now is flexible since we'll definitely be
4019 // reflowed.
4020 return;
4023 if (treatWithClearance) {
4024 applyBStartMargin = true;
4027 nsIFrame* clearanceFrame = nullptr;
4028 const nscoord startingBCoord = aState.mBCoord;
4029 const nsCollapsingMargin incomingMargin = aState.mPrevBEndMargin;
4030 nscoord clearance;
4031 // Save the original position of the frame so that we can reposition
4032 // its view as needed.
4033 nsPoint originalPosition = frame->GetPosition();
4034 while (true) {
4035 clearance = 0;
4036 nscoord bStartMargin = 0;
4037 bool mayNeedRetry = false;
4038 bool clearedFloats = false;
4039 bool clearedPushedOrSplitFloat = false;
4040 if (applyBStartMargin) {
4041 // Precompute the blocks block-start margin value so that we can get the
4042 // correct available space (there might be a float that's
4043 // already been placed below the aState.mPrevBEndMargin
4045 // Setup a reflowInput to get the style computed block-start margin
4046 // value. We'll use a reason of `resize' so that we don't fudge
4047 // any incremental reflow input.
4049 // The availSpace here is irrelevant to our needs - all we want
4050 // out if this setup is the block-start margin value which doesn't depend
4051 // on the childs available space.
4052 // XXX building a complete ReflowInput just to get the block-start
4053 // margin seems like a waste. And we do this for almost every block!
4054 WritingMode wm = frame->GetWritingMode();
4055 LogicalSize availSpace = aState.ContentSize(wm);
4056 ReflowInput reflowInput(aState.mPresContext, aState.mReflowInput, frame,
4057 availSpace);
4059 if (treatWithClearance) {
4060 aState.mBCoord += aState.mPrevBEndMargin.get();
4061 aState.mPrevBEndMargin.Zero();
4064 // Now compute the collapsed margin-block-start value into
4065 // aState.mPrevBEndMargin, assuming that all child margins
4066 // collapse down to clearanceFrame.
4067 brc.ComputeCollapsedBStartMargin(reflowInput, &aState.mPrevBEndMargin,
4068 clearanceFrame, &mayNeedRetry);
4070 // XXX optimization; we could check the collapsing children to see if they
4071 // are sure to require clearance, and so avoid retrying them
4073 if (clearanceFrame) {
4074 // Don't allow retries on the second pass. The clearance decisions for
4075 // the blocks whose block-start margins collapse with ours are now
4076 // fixed.
4077 mayNeedRetry = false;
4080 if (!treatWithClearance && !clearanceFrame && mightClearFloats) {
4081 // We don't know if we need clearance and this is the first,
4082 // optimistic pass. So determine whether *this block* needs
4083 // clearance. Note that we do not allow the decision for whether
4084 // this block has clearance to change on the second pass; that
4085 // decision is only allowed to be made under the optimistic
4086 // first pass.
4087 nscoord curBCoord = aState.mBCoord + aState.mPrevBEndMargin.get();
4088 if (auto [clearBCoord, result] =
4089 aState.ClearFloats(curBCoord, clearType, floatAvoidingBlock);
4090 result != ClearFloatsResult::BCoordNoChange) {
4091 Unused << clearBCoord;
4093 // Looks like we need clearance and we didn't know about it already.
4094 // So recompute collapsed margin
4095 treatWithClearance = true;
4096 // Remember this decision, needed for incremental reflow
4097 aLine->SetHasClearance();
4099 // Apply incoming margins
4100 aState.mBCoord += aState.mPrevBEndMargin.get();
4101 aState.mPrevBEndMargin.Zero();
4103 // Compute the collapsed margin again, ignoring the incoming margin
4104 // this time
4105 mayNeedRetry = false;
4106 brc.ComputeCollapsedBStartMargin(reflowInput, &aState.mPrevBEndMargin,
4107 clearanceFrame, &mayNeedRetry);
4111 // Temporarily advance the running block-direction value so that the
4112 // GetFloatAvailableSpace method will return the right available space.
4113 // This undone as soon as the horizontal margins are computed.
4114 bStartMargin = aState.mPrevBEndMargin.get();
4116 if (treatWithClearance) {
4117 nscoord currentBCoord = aState.mBCoord;
4118 // advance mBCoord to the clear position.
4119 auto [clearBCoord, result] =
4120 aState.ClearFloats(aState.mBCoord, clearType, floatAvoidingBlock);
4121 aState.mBCoord = clearBCoord;
4123 clearedFloats = result != ClearFloatsResult::BCoordNoChange;
4124 clearedPushedOrSplitFloat =
4125 result == ClearFloatsResult::FloatsPushedOrSplit;
4127 // Compute clearance. It's the amount we need to add to the block-start
4128 // border-edge of the frame, after applying collapsed margins
4129 // from the frame and its children, to get it to line up with
4130 // the block-end of the floats. The former is
4131 // currentBCoord + bStartMargin, the latter is the current
4132 // aState.mBCoord.
4133 // Note that negative clearance is possible
4134 clearance = aState.mBCoord - (currentBCoord + bStartMargin);
4136 // Add clearance to our block-start margin while we compute available
4137 // space for the frame
4138 bStartMargin += clearance;
4140 // Note that aState.mBCoord should stay where it is: at the block-start
4141 // border-edge of the frame
4142 } else {
4143 // Advance aState.mBCoord to the block-start border-edge of the frame.
4144 aState.mBCoord += bStartMargin;
4148 aLine->SetLineIsImpactedByFloat(false);
4150 // Here aState.mBCoord is the block-start border-edge of the block.
4151 // Compute the available space for the block
4152 nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace();
4153 WritingMode wm = aState.mReflowInput.GetWritingMode();
4154 LogicalRect availSpace = aState.ComputeBlockAvailSpace(
4155 frame, floatAvailableSpace, (floatAvoidingBlock));
4157 // The check for
4158 // (!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats)
4159 // is to some degree out of paranoia: if we reliably eat up block-start
4160 // margins at the top of the page as we ought to, it wouldn't be
4161 // needed.
4162 if ((!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats) &&
4163 (availSpace.BSize(wm) < 0 || clearedPushedOrSplitFloat)) {
4164 // We know already that this child block won't fit on this
4165 // page/column due to the block-start margin or the clearance. So we
4166 // need to get out of here now. (If we don't, most blocks will handle
4167 // things fine, and report break-before, but zero-height blocks
4168 // won't, and will thus make their parent overly-large and force
4169 // *it* to be pushed in its entirety.)
4170 aState.mBCoord = startingBCoord;
4171 aState.mPrevBEndMargin = incomingMargin;
4172 if (ShouldAvoidBreakInside(aState.mReflowInput)) {
4173 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
4174 } else {
4175 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
4177 return;
4180 // Now put the block-dir coordinate back to the start of the
4181 // block-start-margin + clearance.
4182 aState.mBCoord -= bStartMargin;
4183 availSpace.BStart(wm) -= bStartMargin;
4184 if (NS_UNCONSTRAINEDSIZE != availSpace.BSize(wm)) {
4185 availSpace.BSize(wm) += bStartMargin;
4188 // Construct the reflow input for the block.
4189 Maybe<ReflowInput> childReflowInput;
4190 Maybe<LogicalSize> cbSize;
4191 LogicalSize availSize = availSpace.Size(wm);
4192 bool columnSetWrapperHasNoBSizeLeft = false;
4193 if (Style()->GetPseudoType() == PseudoStyleType::columnContent) {
4194 // Calculate the multicol containing block's block size so that the
4195 // children with percentage block size get correct percentage basis.
4196 const ReflowInput* cbReflowInput =
4197 aState.mReflowInput.mParentReflowInput->mCBReflowInput;
4198 MOZ_ASSERT(cbReflowInput->mFrame->StyleColumn()->IsColumnContainerStyle(),
4199 "Get unexpected reflow input of multicol containing block!");
4201 // Use column-width as the containing block's inline-size, i.e. the column
4202 // content's computed inline-size.
4203 cbSize.emplace(LogicalSize(wm, aState.mReflowInput.ComputedISize(),
4204 cbReflowInput->ComputedBSize())
4205 .ConvertTo(frame->GetWritingMode(), wm));
4207 // If a ColumnSetWrapper is in a balancing column content, it may be
4208 // pushed or pulled back and forth between column contents. Always add
4209 // NS_FRAME_HAS_DIRTY_CHILDREN bit to it so that its ColumnSet children
4210 // can have a chance to reflow under current block size constraint.
4211 if (aState.mReflowInput.mFlags.mIsColumnBalancing &&
4212 frame->IsColumnSetWrapperFrame()) {
4213 frame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
4215 } else if (IsColumnSetWrapperFrame()) {
4216 // If we are reflowing our ColumnSet children, we want to apply our block
4217 // size constraint to the available block size when constructing reflow
4218 // input for ColumnSet so that ColumnSet can use it to compute its max
4219 // column block size.
4220 if (frame->IsColumnSetFrame()) {
4221 nscoord contentBSize = aState.mReflowInput.ComputedBSize();
4222 if (aState.mReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) {
4223 contentBSize =
4224 std::min(contentBSize, aState.mReflowInput.ComputedMaxBSize());
4226 if (contentBSize != NS_UNCONSTRAINEDSIZE) {
4227 // To get the remaining content block-size, subtract the content
4228 // block-size consumed by our previous continuations.
4229 contentBSize -= aState.mConsumedBSize;
4231 // ColumnSet is not the outermost frame in the column container, so it
4232 // cannot have any margin. We don't need to consider any margin that
4233 // can be generated by "box-decoration-break: clone" as we do in
4234 // BlockReflowState::ComputeBlockAvailSpace().
4235 const nscoord availContentBSize = std::max(
4236 0, contentBSize - (aState.mBCoord - aState.ContentBStart()));
4237 if (availSize.BSize(wm) >= availContentBSize) {
4238 availSize.BSize(wm) = availContentBSize;
4239 columnSetWrapperHasNoBSizeLeft = true;
4245 childReflowInput.emplace(aState.mPresContext, aState.mReflowInput, frame,
4246 availSize.ConvertTo(frame->GetWritingMode(), wm),
4247 cbSize);
4249 childReflowInput->mFlags.mColumnSetWrapperHasNoBSizeLeft =
4250 columnSetWrapperHasNoBSizeLeft;
4252 if (aLine->MovedFragments()) {
4253 // We only need to set this the first reflow, since if we reflow
4254 // again (and replace childReflowInput) we'll be reflowing it
4255 // again in the same fragment as the previous time.
4256 childReflowInput->mFlags.mMovedBlockFragments = true;
4259 nsFloatManager::SavedState floatManagerState;
4260 nsReflowStatus frameReflowStatus;
4261 do {
4262 if (floatAvailableSpace.HasFloats()) {
4263 // Set if floatAvailableSpace.HasFloats() is true for any
4264 // iteration of the loop.
4265 aLine->SetLineIsImpactedByFloat(true);
4268 // We might need to store into mDiscoveredClearance later if it's
4269 // currently null; we want to overwrite any writes that
4270 // brc.ReflowBlock() below does, so we need to remember now
4271 // whether it's empty.
4272 const bool shouldStoreClearance =
4273 aState.mReflowInput.mDiscoveredClearance &&
4274 !*aState.mReflowInput.mDiscoveredClearance;
4276 // Reflow the block into the available space
4277 if (mayNeedRetry || floatAvoidingBlock) {
4278 aState.FloatManager()->PushState(&floatManagerState);
4281 if (mayNeedRetry) {
4282 childReflowInput->mDiscoveredClearance = &clearanceFrame;
4283 } else if (!applyBStartMargin) {
4284 childReflowInput->mDiscoveredClearance =
4285 aState.mReflowInput.mDiscoveredClearance;
4288 frameReflowStatus.Reset();
4289 brc.ReflowBlock(availSpace, applyBStartMargin, aState.mPrevBEndMargin,
4290 clearance, aLine.get(), *childReflowInput,
4291 frameReflowStatus, aState);
4293 if (frameReflowStatus.IsInlineBreakBefore()) {
4294 // No need to retry this loop if there is a break opportunity before the
4295 // child block.
4296 break;
4299 // Now the block has a height. Using that height, get the
4300 // available space again and call ComputeBlockAvailSpace again.
4301 // If ComputeBlockAvailSpace gives a different result, we need to
4302 // reflow again.
4303 if (!floatAvoidingBlock) {
4304 break;
4307 LogicalRect oldFloatAvailableSpaceRect(floatAvailableSpace.mRect);
4308 floatAvailableSpace = aState.GetFloatAvailableSpaceForBSize(
4309 aState.mBCoord + bStartMargin, brc.GetMetrics().BSize(wm),
4310 &floatManagerState);
4311 NS_ASSERTION(floatAvailableSpace.mRect.BStart(wm) ==
4312 oldFloatAvailableSpaceRect.BStart(wm),
4313 "yikes");
4314 // Restore the height to the position of the next band.
4315 floatAvailableSpace.mRect.BSize(wm) =
4316 oldFloatAvailableSpaceRect.BSize(wm);
4317 // Determine whether the available space shrunk on either side,
4318 // because (the first time round) we now know the block's height,
4319 // and it may intersect additional floats, or (on later
4320 // iterations) because narrowing the width relative to the
4321 // previous time may cause the block to become taller. Note that
4322 // since we're reflowing the block, narrowing the width might also
4323 // make it shorter, so we must pass aCanGrow as true.
4324 if (!AvailableSpaceShrunk(wm, oldFloatAvailableSpaceRect,
4325 floatAvailableSpace.mRect, true)) {
4326 // The size and position we chose before are fine (i.e., they
4327 // don't cause intersecting with floats that requires a change
4328 // in size or position), so we're done.
4329 break;
4332 bool advanced = false;
4333 if (!aState.FloatAvoidingBlockFitsInAvailSpace(floatAvoidingBlock,
4334 floatAvailableSpace)) {
4335 // Advance to the next band.
4336 nscoord newBCoord = aState.mBCoord;
4337 if (aState.AdvanceToNextBand(floatAvailableSpace.mRect, &newBCoord)) {
4338 advanced = true;
4340 // ClearFloats might be able to advance us further once we're there.
4341 std::tie(aState.mBCoord, std::ignore) =
4342 aState.ClearFloats(newBCoord, StyleClear::None, floatAvoidingBlock);
4344 // Start over with a new available space rect at the new height.
4345 floatAvailableSpace = aState.GetFloatAvailableSpaceWithState(
4346 aState.mBCoord, ShapeType::ShapeOutside, &floatManagerState);
4349 const LogicalRect oldAvailSpace = availSpace;
4350 availSpace = aState.ComputeBlockAvailSpace(frame, floatAvailableSpace,
4351 (floatAvoidingBlock));
4353 if (!advanced && availSpace.IsEqualEdges(oldAvailSpace)) {
4354 break;
4357 // We need another reflow.
4358 aState.FloatManager()->PopState(&floatManagerState);
4360 if (!treatWithClearance && !applyBStartMargin &&
4361 aState.mReflowInput.mDiscoveredClearance) {
4362 // We set shouldStoreClearance above to record only the first
4363 // frame that requires clearance.
4364 if (shouldStoreClearance) {
4365 *aState.mReflowInput.mDiscoveredClearance = frame;
4367 aState.mPrevChild = frame;
4368 // Exactly what we do now is flexible since we'll definitely be
4369 // reflowed.
4370 return;
4373 if (advanced) {
4374 // We're pushing down the border-box, so we don't apply margin anymore.
4375 // This should never cause us to move up since the call to
4376 // GetFloatAvailableSpaceForBSize above included the margin.
4377 applyBStartMargin = false;
4378 bStartMargin = 0;
4379 treatWithClearance = true; // avoid hitting test above
4380 clearance = 0;
4383 childReflowInput.reset();
4384 childReflowInput.emplace(
4385 aState.mPresContext, aState.mReflowInput, frame,
4386 availSpace.Size(wm).ConvertTo(frame->GetWritingMode(), wm));
4387 } while (true);
4389 if (mayNeedRetry && clearanceFrame) {
4390 // Found a clearance frame, so we need to reflow |frame| a second time.
4391 // Restore the states and start over again.
4392 aState.FloatManager()->PopState(&floatManagerState);
4393 aState.mBCoord = startingBCoord;
4394 aState.mPrevBEndMargin = incomingMargin;
4395 continue;
4398 aState.mPrevChild = frame;
4400 if (childReflowInput->WillReflowAgainForClearance()) {
4401 // If an ancestor of ours is going to reflow for clearance, we
4402 // need to avoid calling PlaceBlock, because it unsets dirty bits
4403 // on the child block (both itself, and through its call to
4404 // nsIFrame::DidReflow), and those dirty bits imply dirtiness for
4405 // all of the child block, including the lines it didn't reflow.
4406 NS_ASSERTION(originalPosition == frame->GetPosition(),
4407 "we need to call PositionChildViews");
4408 return;
4411 #if defined(REFLOW_STATUS_COVERAGE)
4412 RecordReflowStatus(true, frameReflowStatus);
4413 #endif
4415 if (frameReflowStatus.IsInlineBreakBefore()) {
4416 // None of the child block fits.
4417 if (ShouldAvoidBreakInside(aState.mReflowInput)) {
4418 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
4419 } else {
4420 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
4422 } else {
4423 // Note: line-break-after a block is a nop
4425 // Try to place the child block.
4426 // Don't force the block to fit if we have positive clearance, because
4427 // pushing it to the next page would give it more room.
4428 // Don't force the block to fit if it's impacted by a float. If it is,
4429 // then pushing it to the next page would give it more room. Note that
4430 // isImpacted doesn't include impact from the block's own floats.
4431 bool forceFit = aState.IsAdjacentWithBStart() && clearance <= 0 &&
4432 !floatAvailableSpace.HasFloats();
4433 nsCollapsingMargin collapsedBEndMargin;
4434 OverflowAreas overflowAreas;
4435 *aKeepReflowGoing =
4436 brc.PlaceBlock(*childReflowInput, forceFit, aLine.get(),
4437 collapsedBEndMargin, overflowAreas, frameReflowStatus);
4438 if (!frameReflowStatus.IsFullyComplete() &&
4439 ShouldAvoidBreakInside(aState.mReflowInput)) {
4440 *aKeepReflowGoing = false;
4441 aLine->MarkDirty();
4444 if (aLine->SetCarriedOutBEndMargin(collapsedBEndMargin)) {
4445 LineIterator nextLine = aLine;
4446 ++nextLine;
4447 if (nextLine != LinesEnd()) {
4448 nextLine->MarkPreviousMarginDirty();
4452 aLine->SetOverflowAreas(overflowAreas);
4453 if (*aKeepReflowGoing) {
4454 // Some of the child block fit
4456 // Advance to new Y position
4457 nscoord newBCoord = aLine->BEnd();
4458 aState.mBCoord = newBCoord;
4460 // Continue the block frame now if it didn't completely fit in
4461 // the available space.
4462 if (!frameReflowStatus.IsFullyComplete()) {
4463 bool madeContinuation = CreateContinuationFor(aState, nullptr, frame);
4465 nsIFrame* nextFrame = frame->GetNextInFlow();
4466 NS_ASSERTION(nextFrame,
4467 "We're supposed to have a next-in-flow by now");
4469 if (frameReflowStatus.IsIncomplete()) {
4470 // If nextFrame used to be an overflow container, make it a normal
4471 // block
4472 if (!madeContinuation &&
4473 nextFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
4474 nsOverflowContinuationTracker::AutoFinish fini(
4475 aState.mOverflowTracker, frame);
4476 nsContainerFrame* parent = nextFrame->GetParent();
4477 parent->StealFrame(nextFrame);
4478 if (parent != this) {
4479 ReparentFrame(nextFrame, parent, this);
4481 mFrames.InsertFrame(nullptr, frame, nextFrame);
4482 madeContinuation = true; // needs to be added to mLines
4483 nextFrame->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
4484 frameReflowStatus.SetNextInFlowNeedsReflow();
4487 // Push continuation to a new line, but only if we actually made
4488 // one.
4489 if (madeContinuation) {
4490 nsLineBox* line = NewLineBox(nextFrame, true);
4491 mLines.after_insert(aLine, line);
4494 PushTruncatedLine(aState, aLine.next(), aKeepReflowGoing);
4496 // If we need to reflow the continuation of the block child,
4497 // then we'd better reflow our continuation
4498 if (frameReflowStatus.NextInFlowNeedsReflow()) {
4499 aState.mReflowStatus.SetNextInFlowNeedsReflow();
4500 // We also need to make that continuation's line dirty so
4501 // it gets reflowed when we reflow our next in flow. The
4502 // nif's line must always be either a line of the nif's
4503 // parent block (only if we didn't make a continuation) or
4504 // else one of our own overflow lines. In the latter case
4505 // the line is already marked dirty, so just handle the
4506 // first case.
4507 if (!madeContinuation) {
4508 nsBlockFrame* nifBlock = do_QueryFrame(nextFrame->GetParent());
4509 NS_ASSERTION(
4510 nifBlock,
4511 "A block's child's next in flow's parent must be a block!");
4512 for (auto& line : nifBlock->Lines()) {
4513 if (line.Contains(nextFrame)) {
4514 line.MarkDirty();
4515 break;
4521 // The block-end margin for a block is only applied on the last
4522 // flow block. Since we just continued the child block frame,
4523 // we know that line->mFirstChild is not the last flow block
4524 // therefore zero out the running margin value.
4525 #ifdef NOISY_BLOCK_DIR_MARGINS
4526 ListTag(stdout);
4527 printf(": reflow incomplete, frame=");
4528 frame->ListTag(stdout);
4529 printf(" prevBEndMargin=%d, setting to zero\n",
4530 aState.mPrevBEndMargin.get());
4531 #endif
4532 aState.mPrevBEndMargin.Zero();
4533 } else { // frame is complete but its overflow is not complete
4534 // Disconnect the next-in-flow and put it in our overflow tracker
4535 if (!madeContinuation &&
4536 !nextFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
4537 // It already exists, but as a normal next-in-flow, so we need
4538 // to dig it out of the child lists.
4539 nextFrame->GetParent()->StealFrame(nextFrame);
4540 } else if (madeContinuation) {
4541 mFrames.RemoveFrame(nextFrame);
4544 // Put it in our overflow list
4545 aState.mOverflowTracker->Insert(nextFrame, frameReflowStatus);
4546 aState.mReflowStatus.MergeCompletionStatusFrom(frameReflowStatus);
4548 #ifdef NOISY_BLOCK_DIR_MARGINS
4549 ListTag(stdout);
4550 printf(": reflow complete but overflow incomplete for ");
4551 frame->ListTag(stdout);
4552 printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n",
4553 aState.mPrevBEndMargin.get(), collapsedBEndMargin.get());
4554 #endif
4555 aState.mPrevBEndMargin = collapsedBEndMargin;
4557 } else { // frame is fully complete
4558 #ifdef NOISY_BLOCK_DIR_MARGINS
4559 ListTag(stdout);
4560 printf(": reflow complete for ");
4561 frame->ListTag(stdout);
4562 printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n",
4563 aState.mPrevBEndMargin.get(), collapsedBEndMargin.get());
4564 #endif
4565 aState.mPrevBEndMargin = collapsedBEndMargin;
4567 #ifdef NOISY_BLOCK_DIR_MARGINS
4568 ListTag(stdout);
4569 printf(": frame=");
4570 frame->ListTag(stdout);
4571 printf(" carriedOutBEndMargin=%d collapsedBEndMargin=%d => %d\n",
4572 brc.GetCarriedOutBEndMargin().get(), collapsedBEndMargin.get(),
4573 aState.mPrevBEndMargin.get());
4574 #endif
4575 } else {
4576 if (!frameReflowStatus.IsFullyComplete()) {
4577 // The frame reported an incomplete status, but then it also didn't
4578 // fit. This means we need to reflow it again so that it can
4579 // (again) report the incomplete status.
4580 frame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
4583 if ((aLine == mLines.front() && !GetPrevInFlow()) ||
4584 ShouldAvoidBreakInside(aState.mReflowInput)) {
4585 // If it's our very first line *or* we're not at the top of the page
4586 // and we have page-break-inside:avoid, then we need to be pushed to
4587 // our parent's next-in-flow.
4588 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
4589 } else {
4590 // Push the line that didn't fit and any lines that follow it
4591 // to our next-in-flow.
4592 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
4596 break; // out of the reflow retry loop
4599 // Now that we've got its final position all figured out, position any child
4600 // views it may have. Note that the case when frame has a view got handled
4601 // by FinishReflowChild, but that function didn't have the coordinates needed
4602 // to correctly decide whether to reposition child views.
4603 if (originalPosition != frame->GetPosition() && !frame->HasView()) {
4604 nsContainerFrame::PositionChildViews(frame);
4607 #ifdef DEBUG
4608 VerifyLines(true);
4609 #endif
4612 void nsBlockFrame::ReflowInlineFrames(BlockReflowState& aState,
4613 LineIterator aLine,
4614 bool* aKeepReflowGoing) {
4615 *aKeepReflowGoing = true;
4617 aLine->SetLineIsImpactedByFloat(false);
4619 // Setup initial coordinate system for reflowing the inline frames
4620 // into. Apply a previous block frame's block-end margin first.
4621 if (ShouldApplyBStartMargin(aState, aLine)) {
4622 aState.mBCoord += aState.mPrevBEndMargin.get();
4624 nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace();
4626 LineReflowStatus lineReflowStatus;
4627 do {
4628 nscoord availableSpaceBSize = 0;
4629 aState.mLineBSize.reset();
4630 do {
4631 bool allowPullUp = true;
4632 nsIFrame* forceBreakInFrame = nullptr;
4633 int32_t forceBreakOffset = -1;
4634 gfxBreakPriority forceBreakPriority = gfxBreakPriority::eNoBreak;
4635 do {
4636 nsFloatManager::SavedState floatManagerState;
4637 aState.FloatManager()->PushState(&floatManagerState);
4639 // Once upon a time we allocated the first 30 nsLineLayout objects
4640 // on the stack, and then we switched to the heap. At that time
4641 // these objects were large (1100 bytes on a 32 bit system).
4642 // Then the nsLineLayout object was shrunk to 156 bytes by
4643 // removing some internal buffers. Given that it is so much
4644 // smaller, the complexity of 2 different ways of allocating
4645 // no longer makes sense. Now we always allocate on the stack.
4646 nsLineLayout lineLayout(aState.mPresContext, aState.FloatManager(),
4647 aState.mReflowInput, &aLine, nullptr);
4648 lineLayout.Init(&aState, aState.mMinLineHeight, aState.mLineNumber);
4649 if (forceBreakInFrame) {
4650 lineLayout.ForceBreakAtPosition(forceBreakInFrame, forceBreakOffset);
4652 DoReflowInlineFrames(aState, lineLayout, aLine, floatAvailableSpace,
4653 availableSpaceBSize, &floatManagerState,
4654 aKeepReflowGoing, &lineReflowStatus, allowPullUp);
4655 lineLayout.EndLineReflow();
4657 if (LineReflowStatus::RedoNoPull == lineReflowStatus ||
4658 LineReflowStatus::RedoMoreFloats == lineReflowStatus ||
4659 LineReflowStatus::RedoNextBand == lineReflowStatus) {
4660 if (lineLayout.NeedsBackup()) {
4661 NS_ASSERTION(!forceBreakInFrame,
4662 "Backing up twice; this should never be necessary");
4663 // If there is no saved break position, then this will set
4664 // set forceBreakInFrame to null and we won't back up, which is
4665 // correct.
4666 forceBreakInFrame = lineLayout.GetLastOptionalBreakPosition(
4667 &forceBreakOffset, &forceBreakPriority);
4668 } else {
4669 forceBreakInFrame = nullptr;
4671 // restore the float manager state
4672 aState.FloatManager()->PopState(&floatManagerState);
4673 // Clear out float lists
4674 aState.mCurrentLineFloats.Clear();
4675 aState.mBelowCurrentLineFloats.Clear();
4676 aState.mNoWrapFloats.Clear();
4679 // Don't allow pullup on a subsequent LineReflowStatus::RedoNoPull pass
4680 allowPullUp = false;
4681 } while (LineReflowStatus::RedoNoPull == lineReflowStatus);
4682 } while (LineReflowStatus::RedoMoreFloats == lineReflowStatus);
4683 } while (LineReflowStatus::RedoNextBand == lineReflowStatus);
4686 void nsBlockFrame::SetBreakBeforeStatusBeforeLine(BlockReflowState& aState,
4687 LineIterator aLine,
4688 bool* aKeepReflowGoing) {
4689 aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
4690 // Reflow the line again when we reflow at our new position.
4691 aLine->MarkDirty();
4692 *aKeepReflowGoing = false;
4695 void nsBlockFrame::PushTruncatedLine(BlockReflowState& aState,
4696 LineIterator aLine,
4697 bool* aKeepReflowGoing) {
4698 PushLines(aState, aLine.prev());
4699 *aKeepReflowGoing = false;
4700 aState.mReflowStatus.SetIncomplete();
4703 void nsBlockFrame::DoReflowInlineFrames(
4704 BlockReflowState& aState, nsLineLayout& aLineLayout, LineIterator aLine,
4705 nsFlowAreaRect& aFloatAvailableSpace, nscoord& aAvailableSpaceBSize,
4706 nsFloatManager::SavedState* aFloatStateBeforeLine, bool* aKeepReflowGoing,
4707 LineReflowStatus* aLineReflowStatus, bool aAllowPullUp) {
4708 // Forget all of the floats on the line
4709 aLine->ClearFloats();
4710 aState.mFloatOverflowAreas.Clear();
4712 // We need to set this flag on the line if any of our reflow passes
4713 // are impacted by floats.
4714 if (aFloatAvailableSpace.HasFloats()) {
4715 aLine->SetLineIsImpactedByFloat(true);
4717 #ifdef REALLY_NOISY_REFLOW
4718 printf("nsBlockFrame::DoReflowInlineFrames %p impacted = %d\n", this,
4719 aFloatAvailableSpace.HasFloats());
4720 #endif
4722 WritingMode outerWM = aState.mReflowInput.GetWritingMode();
4723 WritingMode lineWM = WritingModeForLine(outerWM, aLine->mFirstChild);
4724 LogicalRect lineRect = aFloatAvailableSpace.mRect.ConvertTo(
4725 lineWM, outerWM, aState.ContainerSize());
4727 nscoord iStart = lineRect.IStart(lineWM);
4728 nscoord availISize = lineRect.ISize(lineWM);
4729 nscoord availBSize;
4730 if (aState.mReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
4731 availBSize = NS_UNCONSTRAINEDSIZE;
4732 } else {
4733 /* XXX get the height right! */
4734 availBSize = lineRect.BSize(lineWM);
4737 // Make sure to enable resize optimization before we call BeginLineReflow
4738 // because it might get disabled there
4739 aLine->EnableResizeReflowOptimization();
4741 aLineLayout.BeginLineReflow(
4742 iStart, aState.mBCoord, availISize, availBSize,
4743 aFloatAvailableSpace.HasFloats(), false, /*XXX isTopOfPage*/
4744 lineWM, aState.mContainerSize, aState.mInsetForBalance);
4746 aState.mFlags.mIsLineLayoutEmpty = false;
4748 // XXX Unfortunately we need to know this before reflowing the first
4749 // inline frame in the line. FIX ME.
4750 if (0 == aLineLayout.GetLineNumber() &&
4751 HasAllStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD |
4752 NS_BLOCK_HAS_FIRST_LETTER_STYLE)) {
4753 aLineLayout.SetFirstLetterStyleOK(true);
4755 NS_ASSERTION(!(HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD) &&
4756 GetPrevContinuation()),
4757 "first letter child bit should only be on first continuation");
4759 // Reflow the frames that are already on the line first
4760 LineReflowStatus lineReflowStatus = LineReflowStatus::OK;
4761 int32_t i;
4762 nsIFrame* frame = aLine->mFirstChild;
4764 if (aFloatAvailableSpace.HasFloats()) {
4765 // There is a soft break opportunity at the start of the line, because
4766 // we can always move this line down below float(s).
4767 if (aLineLayout.NotifyOptionalBreakPosition(
4768 frame, 0, true, gfxBreakPriority::eNormalBreak)) {
4769 lineReflowStatus = LineReflowStatus::RedoNextBand;
4773 // need to repeatedly call GetChildCount here, because the child
4774 // count can change during the loop!
4775 for (i = 0;
4776 LineReflowStatus::OK == lineReflowStatus && i < aLine->GetChildCount();
4777 i++, frame = frame->GetNextSibling()) {
4778 ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus);
4779 if (LineReflowStatus::OK != lineReflowStatus) {
4780 // It is possible that one or more of next lines are empty
4781 // (because of DeleteNextInFlowChild). If so, delete them now
4782 // in case we are finished.
4783 ++aLine;
4784 while ((aLine != LinesEnd()) && (0 == aLine->GetChildCount())) {
4785 // XXX Is this still necessary now that DeleteNextInFlowChild
4786 // uses DoRemoveFrame?
4787 nsLineBox* toremove = aLine;
4788 aLine = mLines.erase(aLine);
4789 NS_ASSERTION(nullptr == toremove->mFirstChild, "bad empty line");
4790 FreeLineBox(toremove);
4792 --aLine;
4794 NS_ASSERTION(lineReflowStatus != LineReflowStatus::Truncated,
4795 "ReflowInlineFrame should never determine that a line "
4796 "needs to go to the next page/column");
4800 // Don't pull up new frames into lines with continuation placeholders
4801 if (aAllowPullUp) {
4802 // Pull frames and reflow them until we can't
4803 while (LineReflowStatus::OK == lineReflowStatus) {
4804 frame = PullFrame(aState, aLine);
4805 if (!frame) {
4806 break;
4809 while (LineReflowStatus::OK == lineReflowStatus) {
4810 int32_t oldCount = aLine->GetChildCount();
4811 ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus);
4812 if (aLine->GetChildCount() != oldCount) {
4813 // We just created a continuation for aFrame AND its going
4814 // to end up on this line (e.g. :first-letter
4815 // situation). Therefore we have to loop here before trying
4816 // to pull another frame.
4817 frame = frame->GetNextSibling();
4818 } else {
4819 break;
4825 aState.mFlags.mIsLineLayoutEmpty = aLineLayout.LineIsEmpty();
4827 // We only need to backup if the line isn't going to be reflowed again anyway
4828 bool needsBackup = aLineLayout.NeedsBackup() &&
4829 (lineReflowStatus == LineReflowStatus::Stop ||
4830 lineReflowStatus == LineReflowStatus::OK);
4831 if (needsBackup && aLineLayout.HaveForcedBreakPosition()) {
4832 NS_WARNING(
4833 "We shouldn't be backing up more than once! "
4834 "Someone must have set a break opportunity beyond the available width, "
4835 "even though there were better break opportunities before it");
4836 needsBackup = false;
4838 if (needsBackup) {
4839 // We need to try backing up to before a text run
4840 // XXX It's possible, in fact not unusual, for the break opportunity to
4841 // already be the end of the line. We should detect that and optimize to not
4842 // re-do the line.
4843 if (aLineLayout.HasOptionalBreakPosition()) {
4844 // We can back up!
4845 lineReflowStatus = LineReflowStatus::RedoNoPull;
4847 } else {
4848 // In case we reflow this line again, remember that we don't
4849 // need to force any breaking
4850 aLineLayout.ClearOptionalBreakPosition();
4853 if (LineReflowStatus::RedoNextBand == lineReflowStatus) {
4854 // This happens only when we have a line that is impacted by
4855 // floats and the first element in the line doesn't fit with
4856 // the floats.
4858 // If there's block space available, we either try to reflow the line
4859 // past the current band (if it's non-zero and the band definitely won't
4860 // widen around a shape-outside), otherwise we try one pixel down. If
4861 // there's no block space available, we push the line to the next
4862 // page/column.
4863 NS_ASSERTION(
4864 NS_UNCONSTRAINEDSIZE != aFloatAvailableSpace.mRect.BSize(outerWM),
4865 "unconstrained block size on totally empty line");
4867 // See the analogous code for blocks in BlockReflowState::ClearFloats.
4868 nscoord bandBSize = aFloatAvailableSpace.mRect.BSize(outerWM);
4869 if (bandBSize > 0 ||
4870 NS_UNCONSTRAINEDSIZE == aState.mReflowInput.AvailableBSize()) {
4871 NS_ASSERTION(bandBSize == 0 || aFloatAvailableSpace.HasFloats(),
4872 "redo line on totally empty line with non-empty band...");
4873 // We should never hit this case if we've placed floats on the
4874 // line; if we have, then the GetFloatAvailableSpace call is wrong
4875 // and needs to happen after the caller pops the float manager
4876 // state.
4877 aState.FloatManager()->AssertStateMatches(aFloatStateBeforeLine);
4879 if (!aFloatAvailableSpace.MayWiden() && bandBSize > 0) {
4880 // Move it down far enough to clear the current band.
4881 aState.mBCoord += bandBSize;
4882 } else {
4883 // Move it down by one dev pixel.
4884 aState.mBCoord += aState.mPresContext->DevPixelsToAppUnits(1);
4887 aFloatAvailableSpace = aState.GetFloatAvailableSpace();
4888 } else {
4889 // There's nowhere to retry placing the line, so we want to push
4890 // it to the next page/column where its contents can fit not
4891 // next to a float.
4892 lineReflowStatus = LineReflowStatus::Truncated;
4893 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
4896 // XXX: a small optimization can be done here when paginating:
4897 // if the new Y coordinate is past the end of the block then
4898 // push the line and return now instead of later on after we are
4899 // past the float.
4900 } else if (LineReflowStatus::Truncated != lineReflowStatus &&
4901 LineReflowStatus::RedoNoPull != lineReflowStatus) {
4902 // If we are propagating out a break-before status then there is
4903 // no point in placing the line.
4904 if (!aState.mReflowStatus.IsInlineBreakBefore()) {
4905 if (!PlaceLine(aState, aLineLayout, aLine, aFloatStateBeforeLine,
4906 aFloatAvailableSpace, aAvailableSpaceBSize,
4907 aKeepReflowGoing)) {
4908 lineReflowStatus = LineReflowStatus::RedoMoreFloats;
4909 // PlaceLine already called GetFloatAvailableSpaceForBSize or its
4910 // variant for us.
4914 #ifdef DEBUG
4915 if (gNoisyReflow) {
4916 printf("Line reflow status = %s\n",
4917 LineReflowStatusToString(lineReflowStatus));
4919 #endif
4921 if (aLineLayout.GetDirtyNextLine()) {
4922 // aLine may have been pushed to the overflow lines.
4923 FrameLines* overflowLines = GetOverflowLines();
4924 // We can't just compare iterators front() to aLine here, since they may be
4925 // in different lists.
4926 bool pushedToOverflowLines =
4927 overflowLines && overflowLines->mLines.front() == aLine.get();
4928 if (pushedToOverflowLines) {
4929 // aLine is stale, it's associated with the main line list but it should
4930 // be associated with the overflow line list now
4931 aLine = overflowLines->mLines.begin();
4933 nsBlockInFlowLineIterator iter(this, aLine, pushedToOverflowLines);
4934 if (iter.Next() && iter.GetLine()->IsInline()) {
4935 iter.GetLine()->MarkDirty();
4936 if (iter.GetContainer() != this) {
4937 aState.mReflowStatus.SetNextInFlowNeedsReflow();
4942 *aLineReflowStatus = lineReflowStatus;
4946 * Reflow an inline frame. The reflow status is mapped from the frames
4947 * reflow status to the lines reflow status (not to our reflow status).
4948 * The line reflow status is simple: true means keep placing frames
4949 * on the line; false means don't (the line is done). If the line
4950 * has some sort of breaking affect then aLine's break-type will be set
4951 * to something other than StyleClear::None.
4953 void nsBlockFrame::ReflowInlineFrame(BlockReflowState& aState,
4954 nsLineLayout& aLineLayout,
4955 LineIterator aLine, nsIFrame* aFrame,
4956 LineReflowStatus* aLineReflowStatus) {
4957 MOZ_ASSERT(aFrame);
4958 *aLineReflowStatus = LineReflowStatus::OK;
4960 #ifdef NOISY_FIRST_LETTER
4961 ListTag(stdout);
4962 printf(": reflowing ");
4963 aFrame->ListTag(stdout);
4964 printf(" reflowingFirstLetter=%s\n",
4965 aLineLayout.GetFirstLetterStyleOK() ? "on" : "off");
4966 #endif
4968 if (aFrame->IsPlaceholderFrame()) {
4969 auto ph = static_cast<nsPlaceholderFrame*>(aFrame);
4970 ph->ForgetLineIsEmptySoFar();
4973 // Reflow the inline frame
4974 nsReflowStatus frameReflowStatus;
4975 bool pushedFrame;
4976 aLineLayout.ReflowFrame(aFrame, frameReflowStatus, nullptr, pushedFrame);
4978 if (frameReflowStatus.NextInFlowNeedsReflow()) {
4979 aLineLayout.SetDirtyNextLine();
4982 #ifdef REALLY_NOISY_REFLOW
4983 aFrame->ListTag(stdout);
4984 printf(": status=%s\n", ToString(frameReflowStatus).c_str());
4985 #endif
4987 #if defined(REFLOW_STATUS_COVERAGE)
4988 RecordReflowStatus(false, frameReflowStatus);
4989 #endif
4991 // Send post-reflow notification
4992 aState.mPrevChild = aFrame;
4994 /* XXX
4995 This is where we need to add logic to handle some odd behavior.
4996 For one thing, we should usually place at least one thing next
4997 to a left float, even when that float takes up all the width on a line.
4998 see bug 22496
5001 // Process the child frames reflow status. There are 5 cases:
5002 // complete, not-complete, break-before, break-after-complete,
5003 // break-after-not-complete. There are two situations: we are a
5004 // block or we are an inline. This makes a total of 10 cases
5005 // (fortunately, there is some overlap).
5006 aLine->ClearForcedLineBreak();
5007 if (frameReflowStatus.IsInlineBreak() ||
5008 aState.mTrailingClearFromPIF != StyleClear::None) {
5009 // Always abort the line reflow (because a line break is the
5010 // minimal amount of break we do).
5011 *aLineReflowStatus = LineReflowStatus::Stop;
5013 // XXX what should aLine's break-type be set to in all these cases?
5014 if (frameReflowStatus.IsInlineBreakBefore()) {
5015 // Break-before cases.
5016 if (aFrame == aLine->mFirstChild) {
5017 // If we break before the first frame on the line then we must
5018 // be trying to place content where there's no room (e.g. on a
5019 // line with wide floats). Inform the caller to reflow the
5020 // line after skipping past a float.
5021 *aLineReflowStatus = LineReflowStatus::RedoNextBand;
5022 } else {
5023 // It's not the first child on this line so go ahead and split
5024 // the line. We will see the frame again on the next-line.
5025 SplitLine(aState, aLineLayout, aLine, aFrame, aLineReflowStatus);
5027 // If we're splitting the line because the frame didn't fit and it
5028 // was pushed, then mark the line as having word wrapped. We need to
5029 // know that if we're shrink wrapping our width
5030 if (pushedFrame) {
5031 aLine->SetLineWrapped(true);
5034 } else {
5035 MOZ_ASSERT(frameReflowStatus.IsInlineBreakAfter() ||
5036 aState.mTrailingClearFromPIF != StyleClear::None,
5037 "We should've handled inline break-before in the if-branch!");
5039 // If a float split and its prev-in-flow was followed by a <BR>, then
5040 // combine the <BR>'s float clear type with the inline's float clear type
5041 // (the inline will be the very next frame after the split float).
5042 StyleClear clearType = frameReflowStatus.FloatClearType();
5043 if (aState.mTrailingClearFromPIF != StyleClear::None) {
5044 clearType = nsLayoutUtils::CombineClearType(
5045 clearType, aState.mTrailingClearFromPIF);
5046 aState.mTrailingClearFromPIF = StyleClear::None;
5048 // Break-after cases
5049 if (clearType != StyleClear::None || aLineLayout.GetLineEndsInBR()) {
5050 aLine->SetForcedLineBreakAfter(clearType);
5052 if (frameReflowStatus.IsComplete()) {
5053 // Split line, but after the frame just reflowed
5054 SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(),
5055 aLineReflowStatus);
5057 if (frameReflowStatus.IsInlineBreakAfter() &&
5058 !aLineLayout.GetLineEndsInBR()) {
5059 aLineLayout.SetDirtyNextLine();
5065 if (!frameReflowStatus.IsFullyComplete()) {
5066 // Create a continuation for the incomplete frame. Note that the
5067 // frame may already have a continuation.
5068 CreateContinuationFor(aState, aLine, aFrame);
5070 // Remember that the line has wrapped
5071 if (!aLineLayout.GetLineEndsInBR()) {
5072 aLine->SetLineWrapped(true);
5075 // If we just ended a first-letter frame or reflowed a placeholder then
5076 // don't split the line and don't stop the line reflow...
5077 // But if we are going to stop anyways we'd better split the line.
5078 if ((!frameReflowStatus.FirstLetterComplete() &&
5079 !aFrame->IsPlaceholderFrame()) ||
5080 *aLineReflowStatus == LineReflowStatus::Stop) {
5081 // Split line after the current frame
5082 *aLineReflowStatus = LineReflowStatus::Stop;
5083 SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(),
5084 aLineReflowStatus);
5089 bool nsBlockFrame::CreateContinuationFor(BlockReflowState& aState,
5090 nsLineBox* aLine, nsIFrame* aFrame) {
5091 nsIFrame* newFrame = nullptr;
5093 if (!aFrame->GetNextInFlow()) {
5094 newFrame =
5095 PresShell()->FrameConstructor()->CreateContinuingFrame(aFrame, this);
5097 mFrames.InsertFrame(nullptr, aFrame, newFrame);
5099 if (aLine) {
5100 aLine->NoteFrameAdded(newFrame);
5103 #ifdef DEBUG
5104 VerifyLines(false);
5105 #endif
5106 return !!newFrame;
5109 void nsBlockFrame::SplitFloat(BlockReflowState& aState, nsIFrame* aFloat,
5110 const nsReflowStatus& aFloatStatus) {
5111 MOZ_ASSERT(!aFloatStatus.IsFullyComplete(),
5112 "why split the frame if it's fully complete?");
5113 MOZ_ASSERT(aState.mBlock == this);
5115 nsIFrame* nextInFlow = aFloat->GetNextInFlow();
5116 if (nextInFlow) {
5117 nsContainerFrame* oldParent = nextInFlow->GetParent();
5118 oldParent->StealFrame(nextInFlow);
5119 if (oldParent != this) {
5120 ReparentFrame(nextInFlow, oldParent, this);
5122 if (!aFloatStatus.IsOverflowIncomplete()) {
5123 nextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
5125 } else {
5126 nextInFlow =
5127 PresShell()->FrameConstructor()->CreateContinuingFrame(aFloat, this);
5129 if (aFloatStatus.IsOverflowIncomplete()) {
5130 nextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
5133 StyleFloat floatStyle = aFloat->StyleDisplay()->mFloat;
5134 if (floatStyle == StyleFloat::Left) {
5135 aState.FloatManager()->SetSplitLeftFloatAcrossBreak();
5136 } else {
5137 MOZ_ASSERT(floatStyle == StyleFloat::Right, "Unexpected float side!");
5138 aState.FloatManager()->SetSplitRightFloatAcrossBreak();
5141 aState.AppendPushedFloatChain(nextInFlow);
5142 if (MOZ_LIKELY(!HasAnyStateBits(NS_BLOCK_FLOAT_MGR)) ||
5143 MOZ_UNLIKELY(IsTrueOverflowContainer())) {
5144 aState.mReflowStatus.SetOverflowIncomplete();
5145 } else {
5146 aState.mReflowStatus.SetIncomplete();
5150 static bool CheckPlaceholderInLine(nsIFrame* aBlock, nsLineBox* aLine,
5151 nsIFrame* aFloat) {
5152 if (!aFloat) {
5153 return true;
5155 NS_ASSERTION(!aFloat->GetPrevContinuation(),
5156 "float in a line should never be a continuation");
5157 NS_ASSERTION(!aFloat->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
5158 "float in a line should never be a pushed float");
5159 nsIFrame* ph = aFloat->FirstInFlow()->GetPlaceholderFrame();
5160 for (nsIFrame* f = ph; f; f = f->GetParent()) {
5161 if (f->GetParent() == aBlock) {
5162 return aLine->Contains(f);
5165 NS_ASSERTION(false, "aBlock is not an ancestor of aFrame!");
5166 return true;
5169 void nsBlockFrame::SplitLine(BlockReflowState& aState,
5170 nsLineLayout& aLineLayout, LineIterator aLine,
5171 nsIFrame* aFrame,
5172 LineReflowStatus* aLineReflowStatus) {
5173 MOZ_ASSERT(aLine->IsInline(), "illegal SplitLine on block line");
5175 int32_t pushCount =
5176 aLine->GetChildCount() - aLineLayout.GetCurrentSpanCount();
5177 MOZ_ASSERT(pushCount >= 0, "bad push count");
5179 #ifdef DEBUG
5180 if (gNoisyReflow) {
5181 nsIFrame::IndentBy(stdout, gNoiseIndent);
5182 printf("split line: from line=%p pushCount=%d aFrame=",
5183 static_cast<void*>(aLine.get()), pushCount);
5184 if (aFrame) {
5185 aFrame->ListTag(stdout);
5186 } else {
5187 printf("(null)");
5189 printf("\n");
5190 if (gReallyNoisyReflow) {
5191 aLine->List(stdout, gNoiseIndent + 1);
5194 #endif
5196 if (0 != pushCount) {
5197 MOZ_ASSERT(aLine->GetChildCount() > pushCount, "bad push");
5198 MOZ_ASSERT(nullptr != aFrame, "whoops");
5199 #ifdef DEBUG
5201 nsIFrame* f = aFrame;
5202 int32_t count = pushCount;
5203 while (f && count > 0) {
5204 f = f->GetNextSibling();
5205 --count;
5207 NS_ASSERTION(count == 0, "Not enough frames to push");
5209 #endif
5211 // Put frames being split out into their own line
5212 nsLineBox* newLine = NewLineBox(aLine, aFrame, pushCount);
5213 mLines.after_insert(aLine, newLine);
5214 #ifdef DEBUG
5215 if (gReallyNoisyReflow) {
5216 newLine->List(stdout, gNoiseIndent + 1);
5218 #endif
5220 // Let line layout know that some frames are no longer part of its
5221 // state.
5222 aLineLayout.SplitLineTo(aLine->GetChildCount());
5224 // If floats have been placed whose placeholders have been pushed to the new
5225 // line, we need to reflow the old line again. We don't want to look at the
5226 // frames in the new line, because as a large paragraph is laid out the
5227 // we'd get O(N^2) performance. So instead we just check that the last
5228 // float and the last below-current-line float are still in aLine.
5229 if (!CheckPlaceholderInLine(
5230 this, aLine,
5231 aLine->HasFloats() ? aLine->Floats().LastElement() : nullptr) ||
5232 !CheckPlaceholderInLine(
5233 this, aLine,
5234 aState.mBelowCurrentLineFloats.SafeLastElement(nullptr))) {
5235 *aLineReflowStatus = LineReflowStatus::RedoNoPull;
5238 #ifdef DEBUG
5239 VerifyLines(true);
5240 #endif
5244 bool nsBlockFrame::IsLastLine(BlockReflowState& aState, LineIterator aLine) {
5245 while (++aLine != LinesEnd()) {
5246 // There is another line
5247 if (0 != aLine->GetChildCount()) {
5248 // If the next line is a block line then this line is the last in a
5249 // group of inline lines.
5250 return aLine->IsBlock();
5252 // The next line is empty, try the next one
5255 // Try our next-in-flows lines to answer the question
5256 nsBlockFrame* nextInFlow = (nsBlockFrame*)GetNextInFlow();
5257 while (nullptr != nextInFlow) {
5258 for (const auto& line : nextInFlow->Lines()) {
5259 if (0 != line.GetChildCount()) {
5260 return line.IsBlock();
5263 nextInFlow = (nsBlockFrame*)nextInFlow->GetNextInFlow();
5266 // This is the last line - so don't allow justification
5267 return true;
5270 bool nsBlockFrame::PlaceLine(BlockReflowState& aState,
5271 nsLineLayout& aLineLayout, LineIterator aLine,
5272 nsFloatManager::SavedState* aFloatStateBeforeLine,
5273 nsFlowAreaRect& aFlowArea,
5274 nscoord& aAvailableSpaceBSize,
5275 bool* aKeepReflowGoing) {
5276 // Try to position the floats in a nowrap context.
5277 aLineLayout.FlushNoWrapFloats();
5279 // Trim extra white-space from the line before placing the frames
5280 aLineLayout.TrimTrailingWhiteSpace();
5282 // Vertically align the frames on this line.
5284 // According to the CSS2 spec, section 12.6.1, the "marker" box
5285 // participates in the height calculation of the list-item box's
5286 // first line box.
5288 // There are exactly two places a ::marker can be placed: near the
5289 // first or second line. It's only placed on the second line in a
5290 // rare case: when the first line is empty.
5291 WritingMode wm = aState.mReflowInput.GetWritingMode();
5292 bool addedMarker = false;
5293 if (HasOutsideMarker() &&
5294 ((aLine == mLines.front() &&
5295 (!aLineLayout.IsZeroBSize() || (aLine == mLines.back()))) ||
5296 (mLines.front() != mLines.back() && 0 == mLines.front()->BSize() &&
5297 aLine == mLines.begin().next()))) {
5298 ReflowOutput metrics(aState.mReflowInput);
5299 nsIFrame* marker = GetOutsideMarker();
5300 ReflowOutsideMarker(marker, aState, metrics, aState.mBCoord);
5301 NS_ASSERTION(!MarkerIsEmpty() || metrics.BSize(wm) == 0,
5302 "empty ::marker frame took up space");
5303 aLineLayout.AddMarkerFrame(marker, metrics);
5304 addedMarker = true;
5306 aLineLayout.VerticalAlignLine();
5308 // We want to consider the floats in the current line when determining
5309 // whether the float available space is shrunk. If mLineBSize doesn't
5310 // exist, we are in the first pass trying to place the line. Calling
5311 // GetFloatAvailableSpace() like we did in BlockReflowState::AddFloat()
5312 // for UpdateBand().
5314 // floatAvailableSpaceWithOldLineBSize is the float available space with
5315 // the old BSize, but including the floats that were added in this line.
5316 LogicalRect floatAvailableSpaceWithOldLineBSize =
5317 aState.mLineBSize.isNothing()
5318 ? aState.GetFloatAvailableSpace(aLine->BStart()).mRect
5319 : aState
5320 .GetFloatAvailableSpaceForBSize(
5321 aLine->BStart(), aState.mLineBSize.value(), nullptr)
5322 .mRect;
5324 // As we redo for floats, we can't reduce the amount of BSize we're
5325 // checking.
5326 aAvailableSpaceBSize = std::max(aAvailableSpaceBSize, aLine->BSize());
5327 LogicalRect floatAvailableSpaceWithLineBSize =
5328 aState
5329 .GetFloatAvailableSpaceForBSize(aLine->BStart(), aAvailableSpaceBSize,
5330 nullptr)
5331 .mRect;
5333 // If the available space between the floats is smaller now that we
5334 // know the BSize, return false (and cause another pass with
5335 // LineReflowStatus::RedoMoreFloats). We ensure aAvailableSpaceBSize
5336 // never decreases, which means that we can't reduce the set of floats
5337 // we intersect, which means that the available space cannot grow.
5338 if (AvailableSpaceShrunk(wm, floatAvailableSpaceWithOldLineBSize,
5339 floatAvailableSpaceWithLineBSize, false)) {
5340 // Prepare data for redoing the line.
5341 aState.mLineBSize = Some(aLine->BSize());
5343 // Since we want to redo the line, we update aFlowArea by using the
5344 // aFloatStateBeforeLine, which is the float manager's state before the
5345 // line is placed.
5346 LogicalRect oldFloatAvailableSpace(aFlowArea.mRect);
5347 aFlowArea = aState.GetFloatAvailableSpaceForBSize(
5348 aLine->BStart(), aAvailableSpaceBSize, aFloatStateBeforeLine);
5350 NS_ASSERTION(
5351 aFlowArea.mRect.BStart(wm) == oldFloatAvailableSpace.BStart(wm),
5352 "yikes");
5353 // Restore the BSize to the position of the next band.
5354 aFlowArea.mRect.BSize(wm) = oldFloatAvailableSpace.BSize(wm);
5356 // Enforce both IStart() and IEnd() never move outwards to prevent
5357 // infinite grow-shrink loops.
5358 const nscoord iStartDiff =
5359 aFlowArea.mRect.IStart(wm) - oldFloatAvailableSpace.IStart(wm);
5360 const nscoord iEndDiff =
5361 aFlowArea.mRect.IEnd(wm) - oldFloatAvailableSpace.IEnd(wm);
5362 if (iStartDiff < 0) {
5363 aFlowArea.mRect.IStart(wm) -= iStartDiff;
5364 aFlowArea.mRect.ISize(wm) += iStartDiff;
5366 if (iEndDiff > 0) {
5367 aFlowArea.mRect.ISize(wm) -= iEndDiff;
5370 return false;
5373 #ifdef DEBUG
5374 if (!GetParent()->IsAbsurdSizeAssertSuppressed()) {
5375 static nscoord lastHeight = 0;
5376 if (ABSURD_SIZE(aLine->BStart())) {
5377 lastHeight = aLine->BStart();
5378 if (abs(aLine->BStart() - lastHeight) > ABSURD_COORD / 10) {
5379 nsIFrame::ListTag(stdout);
5380 printf(": line=%p y=%d line.bounds.height=%d\n",
5381 static_cast<void*>(aLine.get()), aLine->BStart(),
5382 aLine->BSize());
5384 } else {
5385 lastHeight = 0;
5388 #endif
5390 // Only block frames horizontally align their children because
5391 // inline frames "shrink-wrap" around their children (therefore
5392 // there is no extra horizontal space).
5393 const nsStyleText* styleText = StyleText();
5396 * We don't care checking for IsLastLine properly if we don't care (if it
5397 * can't change the used text-align value for the line).
5399 * In other words, isLastLine really means isLastLineAndWeCare.
5401 const bool isLastLine =
5402 !IsInSVGTextSubtree() &&
5403 styleText->TextAlignForLastLine() != styleText->mTextAlign &&
5404 (aLineLayout.GetLineEndsInBR() || IsLastLine(aState, aLine));
5406 aLineLayout.TextAlignLine(aLine, isLastLine);
5408 // From here on, pfd->mBounds rectangles are incorrect because bidi
5409 // might have moved frames around!
5410 OverflowAreas overflowAreas;
5411 aLineLayout.RelativePositionFrames(overflowAreas);
5412 aLine->SetOverflowAreas(overflowAreas);
5413 if (addedMarker) {
5414 aLineLayout.RemoveMarkerFrame(GetOutsideMarker());
5417 // Inline lines do not have margins themselves; however they are
5418 // impacted by prior block margins. If this line ends up having some
5419 // height then we zero out the previous block-end margin value that was
5420 // already applied to the line's starting Y coordinate. Otherwise we
5421 // leave it be so that the previous blocks block-end margin can be
5422 // collapsed with a block that follows.
5423 nscoord newBCoord;
5425 if (!aLine->CachedIsEmpty()) {
5426 // This line has some height. Therefore the application of the
5427 // previous-bottom-margin should stick.
5428 aState.mPrevBEndMargin.Zero();
5429 newBCoord = aLine->BEnd();
5430 } else {
5431 // Don't let the previous-bottom-margin value affect the newBCoord
5432 // coordinate (it was applied in ReflowInlineFrames speculatively)
5433 // since the line is empty.
5434 // We already called |ShouldApplyBStartMargin|, and if we applied it
5435 // then mShouldApplyBStartMargin is set.
5436 nscoord dy = aState.mFlags.mShouldApplyBStartMargin
5437 ? -aState.mPrevBEndMargin.get()
5438 : 0;
5439 newBCoord = aState.mBCoord + dy;
5442 if (!aState.mReflowStatus.IsFullyComplete() &&
5443 ShouldAvoidBreakInside(aState.mReflowInput)) {
5444 aLine->AppendFloats(std::move(aState.mCurrentLineFloats));
5445 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
5446 return true;
5449 // See if the line fit (our first line always does).
5450 if (mLines.front() != aLine &&
5451 aState.ContentBSize() != NS_UNCONSTRAINEDSIZE &&
5452 newBCoord > aState.ContentBEnd()) {
5453 NS_ASSERTION(aState.mCurrentLine == aLine, "oops");
5454 if (ShouldAvoidBreakInside(aState.mReflowInput)) {
5455 // All our content doesn't fit, start on the next page.
5456 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
5457 } else {
5458 // Push aLine and all of its children and anything else that
5459 // follows to our next-in-flow.
5460 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
5462 return true;
5465 // Note that any early return before this update of aState.mBCoord
5466 // must either (a) return false or (b) set aKeepReflowGoing to false.
5467 // Otherwise we'll keep reflowing later lines at an incorrect
5468 // position, and we might not come back and clean up the damage later.
5469 aState.mBCoord = newBCoord;
5471 // Add the already placed current-line floats to the line
5472 aLine->AppendFloats(std::move(aState.mCurrentLineFloats));
5474 // Any below current line floats to place?
5475 if (!aState.mBelowCurrentLineFloats.IsEmpty()) {
5476 // Reflow the below-current-line floats, which places on the line's
5477 // float list.
5478 aState.PlaceBelowCurrentLineFloats(aLine);
5481 // When a line has floats, factor them into the overflow areas computations.
5482 if (aLine->HasFloats()) {
5483 // Union the float overflow areas (stored in aState) and the value computed
5484 // by the line layout code.
5485 OverflowAreas lineOverflowAreas = aState.mFloatOverflowAreas;
5486 lineOverflowAreas.UnionWith(aLine->GetOverflowAreas());
5487 aLine->SetOverflowAreas(lineOverflowAreas);
5489 #ifdef NOISY_OVERFLOW_AREAS
5490 printf("%s: Line %p, InkOverflowRect=%s, ScrollableOverflowRect=%s\n",
5491 ListTag().get(), aLine.get(),
5492 ToString(aLine->InkOverflowRect()).c_str(),
5493 ToString(aLine->ScrollableOverflowRect()).c_str());
5494 #endif
5497 // Apply break-after clearing if necessary
5498 // This must stay in sync with |ReflowDirtyLines|.
5499 if (aLine->HasFloatClearTypeAfter()) {
5500 std::tie(aState.mBCoord, std::ignore) =
5501 aState.ClearFloats(aState.mBCoord, aLine->FloatClearTypeAfter());
5503 return true;
5506 void nsBlockFrame::PushLines(BlockReflowState& aState,
5507 nsLineList::iterator aLineBefore) {
5508 // NOTE: aLineBefore is always a normal line, not an overflow line.
5509 // The following expression will assert otherwise.
5510 DebugOnly<bool> check = aLineBefore == mLines.begin();
5512 nsLineList::iterator overBegin(aLineBefore.next());
5514 // PushTruncatedPlaceholderLine sometimes pushes the first line. Ugh.
5515 bool firstLine = overBegin == LinesBegin();
5517 if (overBegin != LinesEnd()) {
5518 // Remove floats in the lines from mFloats
5519 nsFrameList floats;
5520 CollectFloats(overBegin->mFirstChild, floats, true);
5522 if (floats.NotEmpty()) {
5523 #ifdef DEBUG
5524 for (nsIFrame* f : floats) {
5525 MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
5526 "CollectFloats should've removed that bit");
5528 #endif
5529 // Push the floats onto the front of the overflow out-of-flows list
5530 nsAutoOOFFrameList oofs(this);
5531 oofs.mList.InsertFrames(nullptr, nullptr, std::move(floats));
5534 // overflow lines can already exist in some cases, in particular,
5535 // when shrinkwrapping and we discover that the shrinkwap causes
5536 // the height of some child block to grow which creates additional
5537 // overflowing content. In such cases we must prepend the new
5538 // overflow to the existing overflow.
5539 FrameLines* overflowLines = RemoveOverflowLines();
5540 if (!overflowLines) {
5541 // XXXldb use presshell arena!
5542 overflowLines = new FrameLines();
5544 if (overflowLines) {
5545 nsIFrame* lineBeforeLastFrame;
5546 if (firstLine) {
5547 lineBeforeLastFrame = nullptr; // removes all frames
5548 } else {
5549 nsIFrame* f = overBegin->mFirstChild;
5550 lineBeforeLastFrame = f ? f->GetPrevSibling() : mFrames.LastChild();
5551 NS_ASSERTION(!f || lineBeforeLastFrame == aLineBefore->LastChild(),
5552 "unexpected line frames");
5554 nsFrameList pushedFrames = mFrames.TakeFramesAfter(lineBeforeLastFrame);
5555 overflowLines->mFrames.InsertFrames(nullptr, nullptr,
5556 std::move(pushedFrames));
5558 overflowLines->mLines.splice(overflowLines->mLines.begin(), mLines,
5559 overBegin, LinesEnd());
5560 NS_ASSERTION(!overflowLines->mLines.empty(), "should not be empty");
5561 // this takes ownership but it won't delete it immediately so we
5562 // can keep using it.
5563 SetOverflowLines(overflowLines);
5565 // Mark all the overflow lines dirty so that they get reflowed when
5566 // they are pulled up by our next-in-flow.
5568 // XXXldb Can this get called O(N) times making the whole thing O(N^2)?
5569 for (LineIterator line = overflowLines->mLines.begin(),
5570 line_end = overflowLines->mLines.end();
5571 line != line_end; ++line) {
5572 line->MarkDirty();
5573 line->MarkPreviousMarginDirty();
5574 line->SetMovedFragments();
5575 line->SetBoundsEmpty();
5576 if (line->HasFloats()) {
5577 line->ClearFloats();
5583 #ifdef DEBUG
5584 VerifyOverflowSituation();
5585 #endif
5588 // The overflowLines property is stored as a pointer to a line list,
5589 // which must be deleted. However, the following functions all maintain
5590 // the invariant that the property is never set if the list is empty.
5592 bool nsBlockFrame::DrainOverflowLines() {
5593 #ifdef DEBUG
5594 VerifyOverflowSituation();
5595 #endif
5597 // Steal the prev-in-flow's overflow lines and prepend them.
5598 bool didFindOverflow = false;
5599 nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow());
5600 if (prevBlock) {
5601 prevBlock->ClearLineCursors();
5602 FrameLines* overflowLines = prevBlock->RemoveOverflowLines();
5603 if (overflowLines) {
5604 // Make all the frames on the overflow line list mine.
5605 ReparentFrames(overflowLines->mFrames, prevBlock, this);
5607 // Collect overflow containers from our OverflowContainers list that are
5608 // continuations from the frames we picked up from our prev-in-flow, then
5609 // prepend those to ExcessOverflowContainers to ensure the continuations
5610 // are ordered.
5611 if (GetOverflowContainers()) {
5612 nsFrameList ocContinuations;
5613 for (auto* f : overflowLines->mFrames) {
5614 auto* cont = f;
5615 bool done = false;
5616 while (!done && (cont = cont->GetNextContinuation()) &&
5617 cont->GetParent() == this) {
5618 bool onlyChild = !cont->GetPrevSibling() && !cont->GetNextSibling();
5619 if (cont->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER) &&
5620 TryRemoveFrame(OverflowContainersProperty(), cont)) {
5621 ocContinuations.AppendFrame(nullptr, cont);
5622 done = onlyChild;
5623 continue;
5625 break;
5627 if (done) {
5628 break;
5631 if (!ocContinuations.IsEmpty()) {
5632 if (nsFrameList* eoc = GetExcessOverflowContainers()) {
5633 eoc->InsertFrames(nullptr, nullptr, std::move(ocContinuations));
5634 } else {
5635 SetExcessOverflowContainers(std::move(ocContinuations));
5640 // Make the overflow out-of-flow frames mine too.
5641 nsAutoOOFFrameList oofs(prevBlock);
5642 if (oofs.mList.NotEmpty()) {
5643 // In case we own any next-in-flows of any of the drained frames, then
5644 // move those to the PushedFloat list.
5645 nsFrameList pushedFloats;
5646 for (nsIFrame* f : oofs.mList) {
5647 nsIFrame* nif = f->GetNextInFlow();
5648 for (; nif && nif->GetParent() == this; nif = nif->GetNextInFlow()) {
5649 MOZ_ASSERT(nif->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT));
5650 RemoveFloat(nif);
5651 pushedFloats.AppendFrame(nullptr, nif);
5654 ReparentFrames(oofs.mList, prevBlock, this);
5655 mFloats.InsertFrames(nullptr, nullptr, std::move(oofs.mList));
5656 if (!pushedFloats.IsEmpty()) {
5657 nsFrameList* pf = EnsurePushedFloats();
5658 pf->InsertFrames(nullptr, nullptr, std::move(pushedFloats));
5662 if (!mLines.empty()) {
5663 // Remember to recompute the margins on the first line. This will
5664 // also recompute the correct deltaBCoord if necessary.
5665 mLines.front()->MarkPreviousMarginDirty();
5667 // The overflow lines have already been marked dirty and their previous
5668 // margins marked dirty also.
5670 // Prepend the overflow frames/lines to our principal list.
5671 mFrames.InsertFrames(nullptr, nullptr, std::move(overflowLines->mFrames));
5672 mLines.splice(mLines.begin(), overflowLines->mLines);
5673 NS_ASSERTION(overflowLines->mLines.empty(), "splice should empty list");
5674 delete overflowLines;
5675 didFindOverflow = true;
5679 // Now append our own overflow lines.
5680 return DrainSelfOverflowList() || didFindOverflow;
5683 bool nsBlockFrame::DrainSelfOverflowList() {
5684 UniquePtr<FrameLines> ourOverflowLines(RemoveOverflowLines());
5685 if (!ourOverflowLines) {
5686 return false;
5689 // No need to reparent frames in our own overflow lines/oofs, because they're
5690 // already ours. But we should put overflow floats back in mFloats.
5691 // (explicit scope to remove the OOF list before VerifyOverflowSituation)
5693 nsAutoOOFFrameList oofs(this);
5694 if (oofs.mList.NotEmpty()) {
5695 #ifdef DEBUG
5696 for (nsIFrame* f : oofs.mList) {
5697 MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
5698 "CollectFloats should've removed that bit");
5700 #endif
5701 // The overflow floats go after our regular floats.
5702 mFloats.AppendFrames(nullptr, std::move(oofs).mList);
5705 if (!ourOverflowLines->mLines.empty()) {
5706 mFrames.AppendFrames(nullptr, std::move(ourOverflowLines->mFrames));
5707 mLines.splice(mLines.end(), ourOverflowLines->mLines);
5710 #ifdef DEBUG
5711 VerifyOverflowSituation();
5712 #endif
5713 return true;
5717 * Pushed floats are floats whose placeholders are in a previous
5718 * continuation. They might themselves be next-continuations of a float
5719 * that partially fit in an earlier continuation, or they might be the
5720 * first continuation of a float that couldn't be placed at all.
5722 * Pushed floats live permanently at the beginning of a block's float
5723 * list, where they must live *before* any floats whose placeholders are
5724 * in that block.
5726 * Temporarily, during reflow, they also live on the pushed floats list,
5727 * which only holds them between (a) when one continuation pushes them to
5728 * its pushed floats list because they don't fit and (b) when the next
5729 * continuation pulls them onto the beginning of its float list.
5731 * DrainPushedFloats sets up pushed floats the way we need them at the
5732 * start of reflow; they are then reflowed by ReflowPushedFloats (which
5733 * might push some of them on). Floats with placeholders in this block
5734 * are reflowed by (BlockReflowState/nsLineLayout)::AddFloat, which
5735 * also maintains these invariants.
5737 * DrainSelfPushedFloats moves any pushed floats from this block's own
5738 * PushedFloats list back into mFloats. DrainPushedFloats additionally
5739 * moves frames from its prev-in-flow's PushedFloats list into mFloats.
5741 void nsBlockFrame::DrainSelfPushedFloats() {
5742 // If we're getting reflowed multiple times without our
5743 // next-continuation being reflowed, we might need to pull back floats
5744 // that we just put in the list to be pushed to our next-in-flow.
5745 // We don't want to pull back any next-in-flows of floats on our own
5746 // float list, and we only need to pull back first-in-flows whose
5747 // placeholders were in earlier blocks (since first-in-flows whose
5748 // placeholders are in this block will get pulled appropriately by
5749 // AddFloat, and will then be more likely to be in the correct order).
5750 mozilla::PresShell* presShell = PresShell();
5751 nsFrameList* ourPushedFloats = GetPushedFloats();
5752 if (ourPushedFloats) {
5753 // When we pull back floats, we want to put them with the pushed
5754 // floats, which must live at the start of our float list, but we
5755 // want them at the end of those pushed floats.
5756 // FIXME: This isn't quite right! What if they're all pushed floats?
5757 nsIFrame* insertionPrevSibling = nullptr; /* beginning of list */
5758 for (nsIFrame* f = mFloats.FirstChild();
5759 f && f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT);
5760 f = f->GetNextSibling()) {
5761 insertionPrevSibling = f;
5764 nsIFrame* f = ourPushedFloats->LastChild();
5765 while (f) {
5766 nsIFrame* prevSibling = f->GetPrevSibling();
5768 nsPlaceholderFrame* placeholder = f->GetPlaceholderFrame();
5769 nsIFrame* floatOriginalParent =
5770 presShell->FrameConstructor()->GetFloatContainingBlock(placeholder);
5771 if (floatOriginalParent != this) {
5772 // This is a first continuation that was pushed from one of our
5773 // previous continuations. Take it out of the pushed floats
5774 // list and put it in our floats list, before any of our
5775 // floats, but after other pushed floats.
5776 ourPushedFloats->RemoveFrame(f);
5777 mFloats.InsertFrame(nullptr, insertionPrevSibling, f);
5780 f = prevSibling;
5783 if (ourPushedFloats->IsEmpty()) {
5784 RemovePushedFloats()->Delete(presShell);
5789 void nsBlockFrame::DrainPushedFloats() {
5790 DrainSelfPushedFloats();
5792 // After our prev-in-flow has completed reflow, it may have a pushed
5793 // floats list, containing floats that we need to own. Take these.
5794 nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow());
5795 if (prevBlock) {
5796 AutoFrameListPtr list(PresContext(), prevBlock->RemovePushedFloats());
5797 if (list && list->NotEmpty()) {
5798 mFloats.InsertFrames(this, nullptr, std::move(*list));
5803 nsBlockFrame::FrameLines* nsBlockFrame::GetOverflowLines() const {
5804 if (!HasOverflowLines()) {
5805 return nullptr;
5807 FrameLines* prop = GetProperty(OverflowLinesProperty());
5808 NS_ASSERTION(
5809 prop && !prop->mLines.empty() &&
5810 prop->mLines.front()->GetChildCount() == 0
5811 ? prop->mFrames.IsEmpty()
5812 : prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(),
5813 "value should always be stored and non-empty when state set");
5814 return prop;
5817 nsBlockFrame::FrameLines* nsBlockFrame::RemoveOverflowLines() {
5818 if (!HasOverflowLines()) {
5819 return nullptr;
5821 FrameLines* prop = TakeProperty(OverflowLinesProperty());
5822 NS_ASSERTION(
5823 prop && !prop->mLines.empty() &&
5824 prop->mLines.front()->GetChildCount() == 0
5825 ? prop->mFrames.IsEmpty()
5826 : prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(),
5827 "value should always be stored and non-empty when state set");
5828 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
5829 return prop;
5832 void nsBlockFrame::DestroyOverflowLines() {
5833 NS_ASSERTION(HasOverflowLines(), "huh?");
5834 FrameLines* prop = TakeProperty(OverflowLinesProperty());
5835 NS_ASSERTION(prop && prop->mLines.empty(),
5836 "value should always be stored but empty when destroying");
5837 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
5838 delete prop;
5841 // This takes ownership of aOverflowLines.
5842 // XXX We should allocate overflowLines from presShell arena!
5843 void nsBlockFrame::SetOverflowLines(FrameLines* aOverflowLines) {
5844 NS_ASSERTION(aOverflowLines, "null lines");
5845 NS_ASSERTION(!aOverflowLines->mLines.empty(), "empty lines");
5846 NS_ASSERTION(aOverflowLines->mLines.front()->mFirstChild ==
5847 aOverflowLines->mFrames.FirstChild(),
5848 "invalid overflow lines / frames");
5849 NS_ASSERTION(!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_LINES),
5850 "Overwriting existing overflow lines");
5852 // Verify that we won't overwrite an existing overflow list
5853 NS_ASSERTION(!GetProperty(OverflowLinesProperty()), "existing overflow list");
5854 SetProperty(OverflowLinesProperty(), aOverflowLines);
5855 AddStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
5858 nsFrameList* nsBlockFrame::GetOverflowOutOfFlows() const {
5859 if (!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
5860 return nullptr;
5862 nsFrameList* result = GetProperty(OverflowOutOfFlowsProperty());
5863 NS_ASSERTION(result, "value should always be non-empty when state set");
5864 return result;
5867 void nsBlockFrame::SetOverflowOutOfFlows(nsFrameList&& aList,
5868 nsFrameList* aPropValue) {
5869 MOZ_ASSERT(
5870 HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS) == !!aPropValue,
5871 "state does not match value");
5873 if (aList.IsEmpty()) {
5874 if (!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
5875 return;
5877 nsFrameList* list = TakeProperty(OverflowOutOfFlowsProperty());
5878 NS_ASSERTION(aPropValue == list, "prop value mismatch");
5879 list->Clear();
5880 list->Delete(PresShell());
5881 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
5882 } else if (HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
5883 NS_ASSERTION(aPropValue == GetProperty(OverflowOutOfFlowsProperty()),
5884 "prop value mismatch");
5885 *aPropValue = std::move(aList);
5886 } else {
5887 SetProperty(OverflowOutOfFlowsProperty(),
5888 new (PresShell()) nsFrameList(std::move(aList)));
5889 AddStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
5893 nsIFrame* nsBlockFrame::GetInsideMarker() const {
5894 if (!HasInsideMarker()) {
5895 return nullptr;
5897 NS_ASSERTION(!HasOutsideMarker(), "invalid marker state");
5898 nsIFrame* frame = GetProperty(InsideMarkerProperty());
5899 NS_ASSERTION(frame, "bogus inside ::marker frame");
5900 return frame;
5903 nsIFrame* nsBlockFrame::GetOutsideMarker() const {
5904 nsFrameList* list = GetOutsideMarkerList();
5905 return list ? list->FirstChild() : nullptr;
5908 nsFrameList* nsBlockFrame::GetOutsideMarkerList() const {
5909 if (!HasOutsideMarker()) {
5910 return nullptr;
5912 NS_ASSERTION(!HasInsideMarker(), "invalid marker state");
5913 nsFrameList* list = GetProperty(OutsideMarkerProperty());
5914 NS_ASSERTION(list && list->GetLength() == 1, "bogus outside ::marker list");
5915 return list;
5918 nsFrameList* nsBlockFrame::GetPushedFloats() const {
5919 if (!HasPushedFloats()) {
5920 return nullptr;
5922 nsFrameList* result = GetProperty(PushedFloatProperty());
5923 NS_ASSERTION(result, "value should always be non-empty when state set");
5924 return result;
5927 nsFrameList* nsBlockFrame::EnsurePushedFloats() {
5928 nsFrameList* result = GetPushedFloats();
5929 if (result) {
5930 return result;
5933 result = new (PresShell()) nsFrameList;
5934 SetProperty(PushedFloatProperty(), result);
5935 AddStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
5937 return result;
5940 nsFrameList* nsBlockFrame::RemovePushedFloats() {
5941 if (!HasPushedFloats()) {
5942 return nullptr;
5944 nsFrameList* result = TakeProperty(PushedFloatProperty());
5945 RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
5946 NS_ASSERTION(result, "value should always be non-empty when state set");
5947 return result;
5950 //////////////////////////////////////////////////////////////////////
5951 // Frame list manipulation routines
5953 void nsBlockFrame::AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) {
5954 if (aFrameList.IsEmpty()) {
5955 return;
5957 if (aListID != FrameChildListID::Principal) {
5958 if (FrameChildListID::Float == aListID) {
5959 DrainSelfPushedFloats(); // ensure the last frame is in mFloats
5960 mFloats.AppendFrames(nullptr, std::move(aFrameList));
5961 return;
5963 MOZ_ASSERT(FrameChildListID::NoReflowPrincipal == aListID,
5964 "unexpected child list");
5967 // Find the proper last-child for where the append should go
5968 nsIFrame* lastKid = mFrames.LastChild();
5969 NS_ASSERTION(
5970 (mLines.empty() ? nullptr : mLines.back()->LastChild()) == lastKid,
5971 "out-of-sync mLines / mFrames");
5973 #ifdef NOISY_REFLOW_REASON
5974 ListTag(stdout);
5975 printf(": append ");
5976 for (nsIFrame* frame : aFrameList) {
5977 frame->ListTag(stdout);
5979 if (lastKid) {
5980 printf(" after ");
5981 lastKid->ListTag(stdout);
5983 printf("\n");
5984 #endif
5986 if (IsInSVGTextSubtree()) {
5987 MOZ_ASSERT(GetParent()->IsSVGTextFrame(),
5988 "unexpected block frame in SVG text");
5989 // Workaround for bug 1399425 in case this bit has been removed from the
5990 // SVGTextFrame just before the parser adds more descendant nodes.
5991 GetParent()->AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY);
5994 AddFrames(std::move(aFrameList), lastKid, nullptr);
5995 if (aListID != FrameChildListID::NoReflowPrincipal) {
5996 PresShell()->FrameNeedsReflow(
5997 this, IntrinsicDirty::FrameAndAncestors,
5998 NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
6002 void nsBlockFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
6003 const nsLineList::iterator* aPrevFrameLine,
6004 nsFrameList&& aFrameList) {
6005 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
6006 "inserting after sibling frame with different parent");
6008 if (aListID != FrameChildListID::Principal) {
6009 if (FrameChildListID::Float == aListID) {
6010 DrainSelfPushedFloats(); // ensure aPrevFrame is in mFloats
6011 mFloats.InsertFrames(this, aPrevFrame, std::move(aFrameList));
6012 return;
6014 MOZ_ASSERT(FrameChildListID::NoReflowPrincipal == aListID,
6015 "unexpected child list");
6018 #ifdef NOISY_REFLOW_REASON
6019 ListTag(stdout);
6020 printf(": insert ");
6021 for (nsIFrame* frame : aFrameList) {
6022 frame->ListTag(stdout);
6024 if (aPrevFrame) {
6025 printf(" after ");
6026 aPrevFrame->ListTag(stdout);
6028 printf("\n");
6029 #endif
6031 AddFrames(std::move(aFrameList), aPrevFrame, aPrevFrameLine);
6032 if (aListID != FrameChildListID::NoReflowPrincipal) {
6033 PresShell()->FrameNeedsReflow(
6034 this, IntrinsicDirty::FrameAndAncestors,
6035 NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
6039 void nsBlockFrame::RemoveFrame(DestroyContext& aContext, ChildListID aListID,
6040 nsIFrame* aOldFrame) {
6041 #ifdef NOISY_REFLOW_REASON
6042 ListTag(stdout);
6043 printf(": remove ");
6044 aOldFrame->ListTag(stdout);
6045 printf("\n");
6046 #endif
6048 if (aListID == FrameChildListID::Principal) {
6049 bool hasFloats = BlockHasAnyFloats(aOldFrame);
6050 DoRemoveFrame(aContext, aOldFrame, REMOVE_FIXED_CONTINUATIONS);
6051 if (hasFloats) {
6052 MarkSameFloatManagerLinesDirty(this);
6054 } else if (FrameChildListID::Float == aListID) {
6055 // Make sure to mark affected lines dirty for the float frame
6056 // we are removing; this way is a bit messy, but so is the rest of the code.
6057 // See bug 390762.
6058 NS_ASSERTION(!aOldFrame->GetPrevContinuation(),
6059 "RemoveFrame should not be called on pushed floats.");
6060 for (nsIFrame* f = aOldFrame;
6061 f && !f->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
6062 f = f->GetNextContinuation()) {
6063 MarkSameFloatManagerLinesDirty(
6064 static_cast<nsBlockFrame*>(f->GetParent()));
6066 DoRemoveOutOfFlowFrame(aContext, aOldFrame);
6067 } else if (FrameChildListID::NoReflowPrincipal == aListID) {
6068 // Skip the call to |FrameNeedsReflow| below by returning now.
6069 DoRemoveFrame(aContext, aOldFrame, REMOVE_FIXED_CONTINUATIONS);
6070 return;
6071 } else {
6072 MOZ_CRASH("unexpected child list");
6075 PresShell()->FrameNeedsReflow(
6076 this, IntrinsicDirty::FrameAndAncestors,
6077 NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
6080 static bool ShouldPutNextSiblingOnNewLine(nsIFrame* aLastFrame) {
6081 LayoutFrameType type = aLastFrame->Type();
6082 if (type == LayoutFrameType::Br) {
6083 return true;
6085 // XXX the TEXT_OFFSETS_NEED_FIXING check is a wallpaper for bug 822910.
6086 if (type == LayoutFrameType::Text &&
6087 !aLastFrame->HasAnyStateBits(TEXT_OFFSETS_NEED_FIXING)) {
6088 return aLastFrame->HasSignificantTerminalNewline();
6090 return false;
6093 void nsBlockFrame::AddFrames(nsFrameList&& aFrameList, nsIFrame* aPrevSibling,
6094 const nsLineList::iterator* aPrevSiblingLine) {
6095 // Clear our line cursor, since our lines may change.
6096 ClearLineCursors();
6098 if (aFrameList.IsEmpty()) {
6099 return;
6102 // Attempt to find the line that contains the previous sibling
6103 nsLineList* lineList = &mLines;
6104 nsFrameList* frames = &mFrames;
6105 nsLineList::iterator prevSibLine;
6106 int32_t prevSiblingIndex;
6107 if (aPrevSiblingLine) {
6108 MOZ_ASSERT(aPrevSibling);
6109 prevSibLine = *aPrevSiblingLine;
6110 FrameLines* overflowLines = GetOverflowLines();
6111 MOZ_ASSERT(prevSibLine.IsInSameList(mLines.begin()) ||
6112 (overflowLines &&
6113 prevSibLine.IsInSameList(overflowLines->mLines.begin())),
6114 "must be one of our line lists");
6115 if (overflowLines) {
6116 // We need to find out which list it's actually in. Assume that
6117 // *if* we have overflow lines, that our primary lines aren't
6118 // huge, but our overflow lines might be.
6119 nsLineList::iterator line = mLines.begin(), lineEnd = mLines.end();
6120 while (line != lineEnd) {
6121 if (line == prevSibLine) {
6122 break;
6124 ++line;
6126 if (line == lineEnd) {
6127 // By elimination, the line must be in our overflow lines.
6128 lineList = &overflowLines->mLines;
6129 frames = &overflowLines->mFrames;
6133 nsLineList::iterator nextLine = prevSibLine.next();
6134 nsIFrame* lastFrameInLine = nextLine == lineList->end()
6135 ? frames->LastChild()
6136 : nextLine->mFirstChild->GetPrevSibling();
6137 prevSiblingIndex = prevSibLine->RIndexOf(aPrevSibling, lastFrameInLine);
6138 MOZ_ASSERT(prevSiblingIndex >= 0,
6139 "aPrevSibling must be in aPrevSiblingLine");
6140 } else {
6141 prevSibLine = lineList->end();
6142 prevSiblingIndex = -1;
6143 if (aPrevSibling) {
6144 // XXX_perf This is technically O(N^2) in some cases, but by using
6145 // RFind instead of Find, we make it O(N) in the most common case,
6146 // which is appending content.
6148 // Find the line that contains the previous sibling
6149 if (!nsLineBox::RFindLineContaining(aPrevSibling, lineList->begin(),
6150 prevSibLine, mFrames.LastChild(),
6151 &prevSiblingIndex)) {
6152 // Not in mLines - try overflow lines.
6153 FrameLines* overflowLines = GetOverflowLines();
6154 bool found = false;
6155 if (overflowLines) {
6156 prevSibLine = overflowLines->mLines.end();
6157 prevSiblingIndex = -1;
6158 found = nsLineBox::RFindLineContaining(
6159 aPrevSibling, overflowLines->mLines.begin(), prevSibLine,
6160 overflowLines->mFrames.LastChild(), &prevSiblingIndex);
6162 if (MOZ_LIKELY(found)) {
6163 lineList = &overflowLines->mLines;
6164 frames = &overflowLines->mFrames;
6165 } else {
6166 // Note: defensive code! RFindLineContaining must not return
6167 // false in this case, so if it does...
6168 MOZ_ASSERT_UNREACHABLE("prev sibling not in line list");
6169 aPrevSibling = nullptr;
6170 prevSibLine = lineList->end();
6176 // Find the frame following aPrevSibling so that we can join up the
6177 // two lists of frames.
6178 if (aPrevSibling) {
6179 // Split line containing aPrevSibling in two if the insertion
6180 // point is somewhere in the middle of the line.
6181 int32_t rem = prevSibLine->GetChildCount() - prevSiblingIndex - 1;
6182 if (rem) {
6183 // Split the line in two where the frame(s) are being inserted.
6184 nsLineBox* line =
6185 NewLineBox(prevSibLine, aPrevSibling->GetNextSibling(), rem);
6186 lineList->after_insert(prevSibLine, line);
6187 // Mark prevSibLine dirty and as needing textrun invalidation, since
6188 // we may be breaking up text in the line. Its previous line may also
6189 // need to be invalidated because it may be able to pull some text up.
6190 MarkLineDirty(prevSibLine, lineList);
6191 // The new line will also need its textruns recomputed because of the
6192 // frame changes.
6193 line->MarkDirty();
6194 line->SetInvalidateTextRuns(true);
6196 } else if (!lineList->empty()) {
6197 lineList->front()->MarkDirty();
6198 lineList->front()->SetInvalidateTextRuns(true);
6200 const nsFrameList::Slice& newFrames =
6201 frames->InsertFrames(nullptr, aPrevSibling, std::move(aFrameList));
6203 // Walk through the new frames being added and update the line data
6204 // structures to fit.
6205 for (nsIFrame* newFrame : newFrames) {
6206 NS_ASSERTION(!aPrevSibling || aPrevSibling->GetNextSibling() == newFrame,
6207 "Unexpected aPrevSibling");
6208 NS_ASSERTION(
6209 !newFrame->IsPlaceholderFrame() ||
6210 (!newFrame->IsAbsolutelyPositioned() && !newFrame->IsFloating()),
6211 "Placeholders should not float or be positioned");
6213 bool isBlock = newFrame->IsBlockOutside();
6215 // If the frame is a block frame, or if there is no previous line or if the
6216 // previous line is a block line we need to make a new line. We also make
6217 // a new line, as an optimization, in the two cases we know we'll need it:
6218 // if the previous line ended with a <br>, or if it has significant
6219 // whitespace and ended in a newline.
6220 if (isBlock || prevSibLine == lineList->end() || prevSibLine->IsBlock() ||
6221 (aPrevSibling && ShouldPutNextSiblingOnNewLine(aPrevSibling))) {
6222 // Create a new line for the frame and add its line to the line
6223 // list.
6224 nsLineBox* line = NewLineBox(newFrame, isBlock);
6225 if (prevSibLine != lineList->end()) {
6226 // Append new line after prevSibLine
6227 lineList->after_insert(prevSibLine, line);
6228 ++prevSibLine;
6229 } else {
6230 // New line is going before the other lines
6231 lineList->push_front(line);
6232 prevSibLine = lineList->begin();
6234 } else {
6235 prevSibLine->NoteFrameAdded(newFrame);
6236 // We're adding inline content to prevSibLine, so we need to mark it
6237 // dirty, ensure its textruns are recomputed, and possibly do the same
6238 // to its previous line since that line may be able to pull content up.
6239 MarkLineDirty(prevSibLine, lineList);
6242 aPrevSibling = newFrame;
6245 #ifdef DEBUG
6246 MOZ_ASSERT(aFrameList.IsEmpty());
6247 VerifyLines(true);
6248 #endif
6251 nsContainerFrame* nsBlockFrame::GetRubyContentPseudoFrame() {
6252 auto* firstChild = PrincipalChildList().FirstChild();
6253 if (firstChild && firstChild->IsRubyFrame() &&
6254 firstChild->Style()->GetPseudoType() ==
6255 mozilla::PseudoStyleType::blockRubyContent) {
6256 return static_cast<nsContainerFrame*>(firstChild);
6258 return nullptr;
6261 nsContainerFrame* nsBlockFrame::GetContentInsertionFrame() {
6262 // 'display:block ruby' use the inner (Ruby) frame for insertions.
6263 if (auto* rubyContentPseudoFrame = GetRubyContentPseudoFrame()) {
6264 return rubyContentPseudoFrame;
6266 return this;
6269 void nsBlockFrame::AppendDirectlyOwnedAnonBoxes(
6270 nsTArray<OwnedAnonBox>& aResult) {
6271 if (auto* rubyContentPseudoFrame = GetRubyContentPseudoFrame()) {
6272 aResult.AppendElement(OwnedAnonBox(rubyContentPseudoFrame));
6276 void nsBlockFrame::RemoveFloatFromFloatCache(nsIFrame* aFloat) {
6277 // Find which line contains the float, so we can update
6278 // the float cache.
6279 for (auto& line : Lines()) {
6280 if (line.IsInline() && line.RemoveFloat(aFloat)) {
6281 break;
6286 void nsBlockFrame::RemoveFloat(nsIFrame* aFloat) {
6287 #ifdef DEBUG
6288 // Floats live in mFloats, or in the PushedFloat or OverflowOutOfFlows
6289 // frame list properties.
6290 if (!mFloats.ContainsFrame(aFloat)) {
6291 MOZ_ASSERT(
6292 (GetOverflowOutOfFlows() &&
6293 GetOverflowOutOfFlows()->ContainsFrame(aFloat)) ||
6294 (GetPushedFloats() && GetPushedFloats()->ContainsFrame(aFloat)),
6295 "aFloat is not our child or on an unexpected frame list");
6297 #endif
6299 if (mFloats.StartRemoveFrame(aFloat)) {
6300 return;
6303 nsFrameList* list = GetPushedFloats();
6304 if (list && list->ContinueRemoveFrame(aFloat)) {
6305 #if 0
6306 // XXXmats not yet - need to investigate BlockReflowState::mPushedFloats
6307 // first so we don't leave it pointing to a deleted list.
6308 if (list->IsEmpty()) {
6309 delete RemovePushedFloats();
6311 #endif
6312 return;
6316 nsAutoOOFFrameList oofs(this);
6317 if (oofs.mList.ContinueRemoveFrame(aFloat)) {
6318 return;
6323 void nsBlockFrame::DoRemoveOutOfFlowFrame(DestroyContext& aContext,
6324 nsIFrame* aFrame) {
6325 // The containing block is always the parent of aFrame.
6326 nsBlockFrame* block = (nsBlockFrame*)aFrame->GetParent();
6328 // Remove aFrame from the appropriate list.
6329 if (aFrame->IsAbsolutelyPositioned()) {
6330 // This also deletes the next-in-flows
6331 block->GetAbsoluteContainingBlock()->RemoveFrame(
6332 aContext, FrameChildListID::Absolute, aFrame);
6333 } else {
6334 // First remove aFrame's next-in-flows.
6335 if (nsIFrame* nif = aFrame->GetNextInFlow()) {
6336 nif->GetParent()->DeleteNextInFlowChild(aContext, nif, false);
6338 // Now remove aFrame from its child list and Destroy it.
6339 block->RemoveFloatFromFloatCache(aFrame);
6340 block->RemoveFloat(aFrame);
6341 aFrame->Destroy(aContext);
6346 * This helps us iterate over the list of all normal + overflow lines
6348 void nsBlockFrame::TryAllLines(nsLineList::iterator* aIterator,
6349 nsLineList::iterator* aStartIterator,
6350 nsLineList::iterator* aEndIterator,
6351 bool* aInOverflowLines,
6352 FrameLines** aOverflowLines) {
6353 if (*aIterator == *aEndIterator) {
6354 if (!*aInOverflowLines) {
6355 // Try the overflow lines
6356 *aInOverflowLines = true;
6357 FrameLines* lines = GetOverflowLines();
6358 if (lines) {
6359 *aStartIterator = lines->mLines.begin();
6360 *aIterator = *aStartIterator;
6361 *aEndIterator = lines->mLines.end();
6362 *aOverflowLines = lines;
6368 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
6369 LineIterator aLine)
6370 : mFrame(aFrame), mLine(aLine), mLineList(&aFrame->mLines) {
6371 // This will assert if aLine isn't in mLines of aFrame:
6372 DebugOnly<bool> check = aLine == mFrame->LinesBegin();
6375 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
6376 LineIterator aLine,
6377 bool aInOverflow)
6378 : mFrame(aFrame),
6379 mLine(aLine),
6380 mLineList(aInOverflow ? &aFrame->GetOverflowLines()->mLines
6381 : &aFrame->mLines) {}
6383 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
6384 bool* aFoundValidLine)
6385 : mFrame(aFrame), mLineList(&aFrame->mLines) {
6386 mLine = aFrame->LinesBegin();
6387 *aFoundValidLine = FindValidLine();
6390 void nsBlockFrame::UpdateFirstLetterStyle(ServoRestyleState& aRestyleState) {
6391 nsIFrame* letterFrame = GetFirstLetter();
6392 if (!letterFrame) {
6393 return;
6396 // Figure out what the right style parent is. This needs to match
6397 // nsCSSFrameConstructor::CreateLetterFrame.
6398 nsIFrame* inFlowFrame = letterFrame;
6399 if (inFlowFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
6400 inFlowFrame = inFlowFrame->GetPlaceholderFrame();
6402 nsIFrame* styleParent = CorrectStyleParentFrame(inFlowFrame->GetParent(),
6403 PseudoStyleType::firstLetter);
6404 ComputedStyle* parentStyle = styleParent->Style();
6405 RefPtr<ComputedStyle> firstLetterStyle =
6406 aRestyleState.StyleSet().ResolvePseudoElementStyle(
6407 *mContent->AsElement(), PseudoStyleType::firstLetter, nullptr,
6408 parentStyle);
6409 // Note that we don't need to worry about changehints for the continuation
6410 // styles: those will be handled by the styleParent already.
6411 RefPtr<ComputedStyle> continuationStyle =
6412 aRestyleState.StyleSet().ResolveStyleForFirstLetterContinuation(
6413 parentStyle);
6414 UpdateStyleOfOwnedChildFrame(letterFrame, firstLetterStyle, aRestyleState,
6415 Some(continuationStyle.get()));
6417 // We also want to update the style on the textframe inside the first-letter.
6418 // We don't need to compute a changehint for this, though, since any changes
6419 // to it are handled by the first-letter anyway.
6420 nsIFrame* textFrame = letterFrame->PrincipalChildList().FirstChild();
6421 RefPtr<ComputedStyle> firstTextStyle =
6422 aRestyleState.StyleSet().ResolveStyleForText(textFrame->GetContent(),
6423 firstLetterStyle);
6424 textFrame->SetComputedStyle(firstTextStyle);
6426 // We don't need to update style for textFrame's continuations: it's already
6427 // set up to inherit from parentStyle, which is what we want.
6430 static nsIFrame* FindChildContaining(nsBlockFrame* aFrame,
6431 nsIFrame* aFindFrame) {
6432 NS_ASSERTION(aFrame, "must have frame");
6433 nsIFrame* child;
6434 while (true) {
6435 nsIFrame* block = aFrame;
6436 do {
6437 child = nsLayoutUtils::FindChildContainingDescendant(block, aFindFrame);
6438 if (child) {
6439 break;
6441 block = block->GetNextContinuation();
6442 } while (block);
6443 if (!child) {
6444 return nullptr;
6446 if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
6447 break;
6449 aFindFrame = child->GetPlaceholderFrame();
6452 return child;
6455 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
6456 nsIFrame* aFindFrame,
6457 bool* aFoundValidLine)
6458 : mFrame(aFrame), mLineList(&aFrame->mLines) {
6459 *aFoundValidLine = false;
6461 nsIFrame* child = FindChildContaining(aFrame, aFindFrame);
6462 if (!child) {
6463 return;
6466 LineIterator line_end = aFrame->LinesEnd();
6467 mLine = aFrame->LinesBegin();
6468 if (mLine != line_end && mLine.next() == line_end &&
6469 !aFrame->HasOverflowLines()) {
6470 // The block has a single line - that must be it!
6471 *aFoundValidLine = true;
6472 return;
6475 // Try to use the cursor if it exists, otherwise fall back to the first line
6476 if (nsLineBox* const cursor = aFrame->GetLineCursorForQuery()) {
6477 mLine = line_end;
6478 // Perform a simultaneous forward and reverse search starting from the
6479 // line cursor.
6480 nsBlockFrame::LineIterator line = aFrame->LinesBeginFrom(cursor);
6481 nsBlockFrame::ReverseLineIterator rline = aFrame->LinesRBeginFrom(cursor);
6482 nsBlockFrame::ReverseLineIterator rline_end = aFrame->LinesREnd();
6483 // rline is positioned on the line containing 'cursor', so it's not
6484 // rline_end. So we can safely increment it (i.e. move it to one line
6485 // earlier) to start searching there.
6486 ++rline;
6487 while (line != line_end || rline != rline_end) {
6488 if (line != line_end) {
6489 if (line->Contains(child)) {
6490 mLine = line;
6491 break;
6493 ++line;
6495 if (rline != rline_end) {
6496 if (rline->Contains(child)) {
6497 mLine = rline;
6498 break;
6500 ++rline;
6503 if (mLine != line_end) {
6504 *aFoundValidLine = true;
6505 if (mLine != cursor) {
6506 aFrame->SetProperty(nsBlockFrame::LineCursorPropertyQuery(), mLine);
6508 return;
6510 } else {
6511 for (mLine = aFrame->LinesBegin(); mLine != line_end; ++mLine) {
6512 if (mLine->Contains(child)) {
6513 *aFoundValidLine = true;
6514 return;
6518 // Didn't find the line
6519 MOZ_ASSERT(mLine == line_end, "mLine should be line_end at this point");
6521 // If we reach here, it means that we have not been able to find the
6522 // desired frame in our in-flow lines. So we should start looking at
6523 // our overflow lines. In order to do that, we set mLine to the end
6524 // iterator so that FindValidLine starts to look at overflow lines,
6525 // if any.
6527 if (!FindValidLine()) {
6528 return;
6531 do {
6532 if (mLine->Contains(child)) {
6533 *aFoundValidLine = true;
6534 return;
6536 } while (Next());
6539 nsBlockFrame::LineIterator nsBlockInFlowLineIterator::End() {
6540 return mLineList->end();
6543 bool nsBlockInFlowLineIterator::IsLastLineInList() {
6544 LineIterator end = End();
6545 return mLine != end && mLine.next() == end;
6548 bool nsBlockInFlowLineIterator::Next() {
6549 ++mLine;
6550 return FindValidLine();
6553 bool nsBlockInFlowLineIterator::Prev() {
6554 LineIterator begin = mLineList->begin();
6555 if (mLine != begin) {
6556 --mLine;
6557 return true;
6559 bool currentlyInOverflowLines = GetInOverflow();
6560 while (true) {
6561 if (currentlyInOverflowLines) {
6562 mLineList = &mFrame->mLines;
6563 mLine = mLineList->end();
6564 if (mLine != mLineList->begin()) {
6565 --mLine;
6566 return true;
6568 } else {
6569 mFrame = static_cast<nsBlockFrame*>(mFrame->GetPrevInFlow());
6570 if (!mFrame) {
6571 return false;
6573 nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines();
6574 if (overflowLines) {
6575 mLineList = &overflowLines->mLines;
6576 mLine = mLineList->end();
6577 NS_ASSERTION(mLine != mLineList->begin(), "empty overflow line list?");
6578 --mLine;
6579 return true;
6582 currentlyInOverflowLines = !currentlyInOverflowLines;
6586 bool nsBlockInFlowLineIterator::FindValidLine() {
6587 LineIterator end = mLineList->end();
6588 if (mLine != end) {
6589 return true;
6591 bool currentlyInOverflowLines = GetInOverflow();
6592 while (true) {
6593 if (currentlyInOverflowLines) {
6594 mFrame = static_cast<nsBlockFrame*>(mFrame->GetNextInFlow());
6595 if (!mFrame) {
6596 return false;
6598 mLineList = &mFrame->mLines;
6599 mLine = mLineList->begin();
6600 if (mLine != mLineList->end()) {
6601 return true;
6603 } else {
6604 nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines();
6605 if (overflowLines) {
6606 mLineList = &overflowLines->mLines;
6607 mLine = mLineList->begin();
6608 NS_ASSERTION(mLine != mLineList->end(), "empty overflow line list?");
6609 return true;
6612 currentlyInOverflowLines = !currentlyInOverflowLines;
6616 // This function removes aDeletedFrame and all its continuations. It
6617 // is optimized for deleting a whole series of frames. The easy
6618 // implementation would invoke itself recursively on
6619 // aDeletedFrame->GetNextContinuation, then locate the line containing
6620 // aDeletedFrame and remove aDeletedFrame from that line. But here we
6621 // start by locating aDeletedFrame and then scanning from that point
6622 // on looking for continuations.
6623 void nsBlockFrame::DoRemoveFrame(DestroyContext& aContext,
6624 nsIFrame* aDeletedFrame, uint32_t aFlags) {
6625 // Clear our line cursor, since our lines may change.
6626 ClearLineCursors();
6628 if (aDeletedFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW |
6629 NS_FRAME_IS_OVERFLOW_CONTAINER)) {
6630 if (!aDeletedFrame->GetPrevInFlow()) {
6631 NS_ASSERTION(aDeletedFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
6632 "Expected out-of-flow frame");
6633 DoRemoveOutOfFlowFrame(aContext, aDeletedFrame);
6634 } else {
6635 // FIXME(emilio): aContext is lost here, maybe it's not a big deal?
6636 nsContainerFrame::DeleteNextInFlowChild(aContext, aDeletedFrame,
6637 (aFlags & FRAMES_ARE_EMPTY) != 0);
6639 return;
6642 // Find the line that contains deletedFrame
6643 nsLineList::iterator line_start = mLines.begin(), line_end = mLines.end();
6644 nsLineList::iterator line = line_start;
6645 FrameLines* overflowLines = nullptr;
6646 bool searchingOverflowList = false;
6647 // Make sure we look in the overflow lines even if the normal line
6648 // list is empty
6649 TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
6650 &overflowLines);
6651 while (line != line_end) {
6652 if (line->Contains(aDeletedFrame)) {
6653 break;
6655 ++line;
6656 TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
6657 &overflowLines);
6660 if (line == line_end) {
6661 NS_ERROR("can't find deleted frame in lines");
6662 return;
6665 if (!(aFlags & FRAMES_ARE_EMPTY)) {
6666 if (line != line_start) {
6667 line.prev()->MarkDirty();
6668 line.prev()->SetInvalidateTextRuns(true);
6669 } else if (searchingOverflowList && !mLines.empty()) {
6670 mLines.back()->MarkDirty();
6671 mLines.back()->SetInvalidateTextRuns(true);
6675 while (line != line_end && aDeletedFrame) {
6676 MOZ_ASSERT(this == aDeletedFrame->GetParent(), "messed up delete code");
6677 MOZ_ASSERT(line->Contains(aDeletedFrame), "frame not in line");
6679 if (!(aFlags & FRAMES_ARE_EMPTY)) {
6680 line->MarkDirty();
6681 line->SetInvalidateTextRuns(true);
6684 // If the frame being deleted is the last one on the line then
6685 // optimize away the line->Contains(next-in-flow) call below.
6686 bool isLastFrameOnLine = 1 == line->GetChildCount();
6687 if (!isLastFrameOnLine) {
6688 LineIterator next = line.next();
6689 nsIFrame* lastFrame =
6690 next != line_end
6691 ? next->mFirstChild->GetPrevSibling()
6692 : (searchingOverflowList ? overflowLines->mFrames.LastChild()
6693 : mFrames.LastChild());
6694 NS_ASSERTION(next == line_end || lastFrame == line->LastChild(),
6695 "unexpected line frames");
6696 isLastFrameOnLine = lastFrame == aDeletedFrame;
6699 // Remove aDeletedFrame from the line
6700 if (line->mFirstChild == aDeletedFrame) {
6701 // We should be setting this to null if aDeletedFrame
6702 // is the only frame on the line. HOWEVER in that case
6703 // we will be removing the line anyway, see below.
6704 line->mFirstChild = aDeletedFrame->GetNextSibling();
6707 // Hmm, this won't do anything if we're removing a frame in the first
6708 // overflow line... Hopefully doesn't matter
6709 --line;
6710 if (line != line_end && !line->IsBlock()) {
6711 // Since we just removed a frame that follows some inline
6712 // frames, we need to reflow the previous line.
6713 line->MarkDirty();
6715 ++line;
6717 // Take aDeletedFrame out of the sibling list. Note that
6718 // prevSibling will only be nullptr when we are deleting the very
6719 // first frame in the main or overflow list.
6720 if (searchingOverflowList) {
6721 overflowLines->mFrames.RemoveFrame(aDeletedFrame);
6722 } else {
6723 mFrames.RemoveFrame(aDeletedFrame);
6726 // Update the child count of the line to be accurate
6727 line->NoteFrameRemoved(aDeletedFrame);
6729 // Destroy frame; capture its next continuation first in case we need
6730 // to destroy that too.
6731 nsIFrame* deletedNextContinuation =
6732 (aFlags & REMOVE_FIXED_CONTINUATIONS)
6733 ? aDeletedFrame->GetNextContinuation()
6734 : aDeletedFrame->GetNextInFlow();
6735 #ifdef NOISY_REMOVE_FRAME
6736 printf("DoRemoveFrame: %s line=%p frame=",
6737 searchingOverflowList ? "overflow" : "normal", line.get());
6738 aDeletedFrame->ListTag(stdout);
6739 printf(" prevSibling=%p deletedNextContinuation=%p\n",
6740 aDeletedFrame->GetPrevSibling(), deletedNextContinuation);
6741 #endif
6743 // If next-in-flow is an overflow container, must remove it first.
6744 // FIXME: Can we do this unconditionally?
6745 if (deletedNextContinuation && deletedNextContinuation->HasAnyStateBits(
6746 NS_FRAME_IS_OVERFLOW_CONTAINER)) {
6747 deletedNextContinuation->GetParent()->DeleteNextInFlowChild(
6748 aContext, deletedNextContinuation, false);
6749 deletedNextContinuation = nullptr;
6752 aDeletedFrame->Destroy(aContext);
6753 aDeletedFrame = deletedNextContinuation;
6755 bool haveAdvancedToNextLine = false;
6756 // If line is empty, remove it now.
6757 if (0 == line->GetChildCount()) {
6758 #ifdef NOISY_REMOVE_FRAME
6759 printf("DoRemoveFrame: %s line=%p became empty so it will be removed\n",
6760 searchingOverflowList ? "overflow" : "normal", line.get());
6761 #endif
6762 nsLineBox* cur = line;
6763 if (!searchingOverflowList) {
6764 line = mLines.erase(line);
6765 // Invalidate the space taken up by the line.
6766 // XXX We need to do this if we're removing a frame as a result of
6767 // a call to RemoveFrame(), but we may not need to do this in all
6768 // cases...
6769 #ifdef NOISY_BLOCK_INVALIDATE
6770 nsRect inkOverflow(cur->InkOverflowRect());
6771 printf("%p invalidate 10 (%d, %d, %d, %d)\n", this, inkOverflow.x,
6772 inkOverflow.y, inkOverflow.width, inkOverflow.height);
6773 #endif
6774 } else {
6775 line = overflowLines->mLines.erase(line);
6776 if (overflowLines->mLines.empty()) {
6777 DestroyOverflowLines();
6778 overflowLines = nullptr;
6779 // We just invalidated our iterators. Since we were in
6780 // the overflow lines list, which is now empty, set them
6781 // so we're at the end of the regular line list.
6782 line_start = mLines.begin();
6783 line_end = mLines.end();
6784 line = line_end;
6787 FreeLineBox(cur);
6789 // If we're removing a line, ReflowDirtyLines isn't going to
6790 // know that it needs to slide lines unless something is marked
6791 // dirty. So mark the previous margin of the next line dirty if
6792 // there is one.
6793 if (line != line_end) {
6794 line->MarkPreviousMarginDirty();
6796 haveAdvancedToNextLine = true;
6797 } else {
6798 // Make the line that just lost a frame dirty, and advance to
6799 // the next line.
6800 if (!deletedNextContinuation || isLastFrameOnLine ||
6801 !line->Contains(deletedNextContinuation)) {
6802 line->MarkDirty();
6803 ++line;
6804 haveAdvancedToNextLine = true;
6808 if (deletedNextContinuation) {
6809 // See if we should keep looking in the current flow's line list.
6810 if (deletedNextContinuation->GetParent() != this) {
6811 // The deceased frames continuation is not a child of the
6812 // current block. So break out of the loop so that we advance
6813 // to the next parent.
6815 // If we have a continuation in a different block then all bets are
6816 // off regarding whether we are deleting frames without actual content,
6817 // so don't propagate FRAMES_ARE_EMPTY any further.
6818 aFlags &= ~FRAMES_ARE_EMPTY;
6819 break;
6822 // If we advanced to the next line then check if we should switch to the
6823 // overflow line list.
6824 if (haveAdvancedToNextLine) {
6825 if (line != line_end && !searchingOverflowList &&
6826 !line->Contains(deletedNextContinuation)) {
6827 // We have advanced to the next *normal* line but the next-in-flow
6828 // is not there - force a switch to the overflow line list.
6829 line = line_end;
6832 TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
6833 &overflowLines);
6834 #ifdef NOISY_REMOVE_FRAME
6835 printf("DoRemoveFrame: now on %s line=%p\n",
6836 searchingOverflowList ? "overflow" : "normal", line.get());
6837 #endif
6842 if (!(aFlags & FRAMES_ARE_EMPTY) && line.next() != line_end) {
6843 line.next()->MarkDirty();
6844 line.next()->SetInvalidateTextRuns(true);
6847 #ifdef DEBUG
6848 VerifyLines(true);
6849 VerifyOverflowSituation();
6850 #endif
6852 // Advance to next flow block if the frame has more continuations.
6853 if (!aDeletedFrame) {
6854 return;
6856 nsBlockFrame* nextBlock = do_QueryFrame(aDeletedFrame->GetParent());
6857 NS_ASSERTION(nextBlock, "Our child's continuation's parent is not a block?");
6858 uint32_t flags = (aFlags & REMOVE_FIXED_CONTINUATIONS);
6859 nextBlock->DoRemoveFrame(aContext, aDeletedFrame, flags);
6862 static bool FindBlockLineFor(nsIFrame* aChild, nsLineList::iterator aBegin,
6863 nsLineList::iterator aEnd,
6864 nsLineList::iterator* aResult) {
6865 MOZ_ASSERT(aChild->IsBlockOutside());
6866 for (nsLineList::iterator line = aBegin; line != aEnd; ++line) {
6867 MOZ_ASSERT(line->GetChildCount() > 0);
6868 if (line->IsBlock() && line->mFirstChild == aChild) {
6869 MOZ_ASSERT(line->GetChildCount() == 1);
6870 *aResult = line;
6871 return true;
6874 return false;
6877 static bool FindInlineLineFor(nsIFrame* aChild, const nsFrameList& aFrameList,
6878 nsLineList::iterator aBegin,
6879 nsLineList::iterator aEnd,
6880 nsLineList::iterator* aResult) {
6881 MOZ_ASSERT(!aChild->IsBlockOutside());
6882 for (nsLineList::iterator line = aBegin; line != aEnd; ++line) {
6883 MOZ_ASSERT(line->GetChildCount() > 0);
6884 if (!line->IsBlock()) {
6885 // Optimize by comparing the line's last child first.
6886 nsLineList::iterator next = line.next();
6887 if (aChild == (next == aEnd ? aFrameList.LastChild()
6888 : next->mFirstChild->GetPrevSibling()) ||
6889 line->Contains(aChild)) {
6890 *aResult = line;
6891 return true;
6895 return false;
6898 static bool FindLineFor(nsIFrame* aChild, const nsFrameList& aFrameList,
6899 nsLineList::iterator aBegin, nsLineList::iterator aEnd,
6900 nsLineList::iterator* aResult) {
6901 return aChild->IsBlockOutside()
6902 ? FindBlockLineFor(aChild, aBegin, aEnd, aResult)
6903 : FindInlineLineFor(aChild, aFrameList, aBegin, aEnd, aResult);
6906 void nsBlockFrame::StealFrame(nsIFrame* aChild) {
6907 MOZ_ASSERT(aChild->GetParent() == this);
6909 if (aChild->IsFloating()) {
6910 RemoveFloat(aChild);
6911 return;
6914 if (MaybeStealOverflowContainerFrame(aChild)) {
6915 return;
6918 MOZ_ASSERT(!aChild->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
6920 nsLineList::iterator line;
6921 if (FindLineFor(aChild, mFrames, mLines.begin(), mLines.end(), &line)) {
6922 RemoveFrameFromLine(aChild, line, mFrames, mLines);
6923 } else {
6924 FrameLines* overflowLines = GetOverflowLines();
6925 DebugOnly<bool> found;
6926 found = FindLineFor(aChild, overflowLines->mFrames,
6927 overflowLines->mLines.begin(),
6928 overflowLines->mLines.end(), &line);
6929 MOZ_ASSERT(found, "Why can't we find aChild in our overflow lines?");
6930 RemoveFrameFromLine(aChild, line, overflowLines->mFrames,
6931 overflowLines->mLines);
6932 if (overflowLines->mLines.empty()) {
6933 DestroyOverflowLines();
6938 void nsBlockFrame::RemoveFrameFromLine(nsIFrame* aChild,
6939 nsLineList::iterator aLine,
6940 nsFrameList& aFrameList,
6941 nsLineList& aLineList) {
6942 aFrameList.RemoveFrame(aChild);
6943 if (aChild == aLine->mFirstChild) {
6944 aLine->mFirstChild = aChild->GetNextSibling();
6946 aLine->NoteFrameRemoved(aChild);
6947 if (aLine->GetChildCount() > 0) {
6948 aLine->MarkDirty();
6949 } else {
6950 // The line became empty - destroy it.
6951 nsLineBox* lineBox = aLine;
6952 aLine = aLineList.erase(aLine);
6953 if (aLine != aLineList.end()) {
6954 aLine->MarkPreviousMarginDirty();
6956 FreeLineBox(lineBox);
6960 void nsBlockFrame::DeleteNextInFlowChild(DestroyContext& aContext,
6961 nsIFrame* aNextInFlow,
6962 bool aDeletingEmptyFrames) {
6963 MOZ_ASSERT(aNextInFlow->GetPrevInFlow(), "bad next-in-flow");
6965 if (aNextInFlow->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW |
6966 NS_FRAME_IS_OVERFLOW_CONTAINER)) {
6967 nsContainerFrame::DeleteNextInFlowChild(aContext, aNextInFlow,
6968 aDeletingEmptyFrames);
6969 } else {
6970 #ifdef DEBUG
6971 if (aDeletingEmptyFrames) {
6972 nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(aNextInFlow);
6974 #endif
6975 DoRemoveFrame(aContext, aNextInFlow,
6976 aDeletingEmptyFrames ? FRAMES_ARE_EMPTY : 0);
6980 const nsStyleText* nsBlockFrame::StyleTextForLineLayout() {
6981 // Return the pointer to an unmodified style text
6982 return StyleText();
6985 void nsBlockFrame::ReflowFloat(BlockReflowState& aState, ReflowInput& aFloatRI,
6986 nsIFrame* aFloat,
6987 nsReflowStatus& aReflowStatus) {
6988 MOZ_ASSERT(aReflowStatus.IsEmpty(),
6989 "Caller should pass a fresh reflow status!");
6990 MOZ_ASSERT(aFloat->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
6991 "aFloat must be an out-of-flow frame");
6993 WritingMode wm = aState.mReflowInput.GetWritingMode();
6995 // Setup a block reflow context to reflow the float.
6996 nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput);
6998 nsIFrame* clearanceFrame = nullptr;
6999 do {
7000 nsCollapsingMargin margin;
7001 bool mayNeedRetry = false;
7002 aFloatRI.mDiscoveredClearance = nullptr;
7003 // Only first in flow gets a block-start margin.
7004 if (!aFloat->GetPrevInFlow()) {
7005 brc.ComputeCollapsedBStartMargin(aFloatRI, &margin, clearanceFrame,
7006 &mayNeedRetry);
7008 if (mayNeedRetry && !clearanceFrame) {
7009 aFloatRI.mDiscoveredClearance = &clearanceFrame;
7010 // We don't need to push the float manager state because the the block
7011 // has its own float manager that will be destroyed and recreated
7015 // When reflowing a float, aSpace argument doesn't matter because we pass
7016 // nullptr to aLine and we don't call nsBlockReflowContext::PlaceBlock()
7017 // later.
7018 brc.ReflowBlock(LogicalRect(wm), true, margin, 0, nullptr, aFloatRI,
7019 aReflowStatus, aState);
7020 } while (clearanceFrame);
7022 if (aFloat->IsLetterFrame()) {
7023 // We never split floating first letters; an incomplete status for such
7024 // frames simply means that there is more content to be reflowed on the
7025 // line.
7026 if (aReflowStatus.IsIncomplete()) {
7027 aReflowStatus.Reset();
7031 NS_ASSERTION(aReflowStatus.IsFullyComplete() ||
7032 aFloatRI.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
7033 "The status can only be incomplete or overflow-incomplete if "
7034 "the available block-size is constrained!");
7036 if (aReflowStatus.NextInFlowNeedsReflow()) {
7037 aState.mReflowStatus.SetNextInFlowNeedsReflow();
7040 const ReflowOutput& metrics = brc.GetMetrics();
7042 // Set the rect, make sure the view is properly sized and positioned,
7043 // and tell the frame we're done reflowing it
7044 // XXXldb This seems like the wrong place to be doing this -- shouldn't
7045 // we be doing this in BlockReflowState::FlowAndPlaceFloat after
7046 // we've positioned the float, and shouldn't we be doing the equivalent
7047 // of |PlaceFrameView| here?
7048 WritingMode metricsWM = metrics.GetWritingMode();
7049 aFloat->SetSize(metricsWM, metrics.Size(metricsWM));
7050 if (aFloat->HasView()) {
7051 nsContainerFrame::SyncFrameViewAfterReflow(
7052 aState.mPresContext, aFloat, aFloat->GetView(), metrics.InkOverflow(),
7053 ReflowChildFlags::NoMoveView);
7055 aFloat->DidReflow(aState.mPresContext, &aFloatRI);
7058 StyleClear nsBlockFrame::FindTrailingClear() {
7059 for (nsBlockFrame* b = this; b;
7060 b = static_cast<nsBlockFrame*>(b->GetPrevInFlow())) {
7061 auto endLine = b->LinesRBegin();
7062 if (endLine != b->LinesREnd()) {
7063 return endLine->FloatClearTypeAfter();
7066 return StyleClear::None;
7069 void nsBlockFrame::ReflowPushedFloats(BlockReflowState& aState,
7070 OverflowAreas& aOverflowAreas) {
7071 // Pushed floats live at the start of our float list; see comment
7072 // above nsBlockFrame::DrainPushedFloats.
7073 nsIFrame* f = mFloats.FirstChild();
7074 nsIFrame* prev = nullptr;
7075 while (f && f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
7076 MOZ_ASSERT(prev == f->GetPrevSibling());
7077 // When we push a first-continuation float in a non-initial reflow,
7078 // it's possible that we end up with two continuations with the same
7079 // parent. This happens if, on the previous reflow of the block or
7080 // a previous reflow of the line containing the block, the float was
7081 // split between continuations A and B of the parent, but on the
7082 // current reflow, none of the float can fit in A.
7084 // When this happens, we might even have the two continuations
7085 // out-of-order due to the management of the pushed floats. In
7086 // particular, if the float's placeholder was in a pushed line that
7087 // we reflowed before it was pushed, and we split the float during
7088 // that reflow, we might have the continuation of the float before
7089 // the float itself. (In the general case, however, it's correct
7090 // for floats in the pushed floats list to come before floats
7091 // anchored in pushed lines; however, in this case it's wrong. We
7092 // should probably find a way to fix it somehow, since it leads to
7093 // incorrect layout in some cases.)
7095 // When we have these out-of-order continuations, we might hit the
7096 // next-continuation before the previous-continuation. When that
7097 // happens, just push it. When we reflow the next continuation,
7098 // we'll either pull all of its content back and destroy it (by
7099 // calling DeleteNextInFlowChild), or nsBlockFrame::SplitFloat will
7100 // pull it out of its current position and push it again (and
7101 // potentially repeat this cycle for the next continuation, although
7102 // hopefully then they'll be in the right order).
7104 // We should also need this code for the in-order case if the first
7105 // continuation of a float gets moved across more than one
7106 // continuation of the containing block. In this case we'd manage
7107 // to push the second continuation without this check, but not the
7108 // third and later.
7109 nsIFrame* prevContinuation = f->GetPrevContinuation();
7110 if (prevContinuation && prevContinuation->GetParent() == f->GetParent()) {
7111 mFloats.RemoveFrame(f);
7112 aState.AppendPushedFloatChain(f);
7113 f = !prev ? mFloats.FirstChild() : prev->GetNextSibling();
7114 continue;
7117 // Always call FlowAndPlaceFloat; we might need to place this float if it
7118 // didn't belong to this block the last time it was reflowed. Note that if
7119 // the float doesn't get placed, we don't consider its overflow areas.
7120 // (Not-getting-placed means it didn't fit and we pushed it instead of
7121 // placing it, and its position could be stale.)
7122 if (aState.FlowAndPlaceFloat(f) ==
7123 BlockReflowState::PlaceFloatResult::Placed) {
7124 ConsiderChildOverflow(aOverflowAreas, f);
7127 nsIFrame* next = !prev ? mFloats.FirstChild() : prev->GetNextSibling();
7128 if (next == f) {
7129 // We didn't push |f| so its next-sibling is next.
7130 next = f->GetNextSibling();
7131 prev = f;
7132 } // else: we did push |f| so |prev|'s new next-sibling is next.
7133 f = next;
7136 // If there are pushed or split floats, then we may need to continue BR
7137 // clearance
7138 if (auto [bCoord, result] = aState.ClearFloats(0, StyleClear::Both);
7139 result != ClearFloatsResult::BCoordNoChange) {
7140 Unused << bCoord;
7141 if (auto* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow())) {
7142 aState.mTrailingClearFromPIF = prevBlock->FindTrailingClear();
7147 void nsBlockFrame::RecoverFloats(nsFloatManager& aFloatManager, WritingMode aWM,
7148 const nsSize& aContainerSize) {
7149 // Recover our own floats
7150 nsIFrame* stop = nullptr; // Stop before we reach pushed floats that
7151 // belong to our next-in-flow
7152 for (nsIFrame* f = mFloats.FirstChild(); f && f != stop;
7153 f = f->GetNextSibling()) {
7154 LogicalRect region = nsFloatManager::GetRegionFor(aWM, f, aContainerSize);
7155 aFloatManager.AddFloat(f, region, aWM, aContainerSize);
7156 if (!stop && f->GetNextInFlow()) {
7157 stop = f->GetNextInFlow();
7161 // Recurse into our overflow container children
7162 for (nsIFrame* oc =
7163 GetChildList(FrameChildListID::OverflowContainers).FirstChild();
7164 oc; oc = oc->GetNextSibling()) {
7165 RecoverFloatsFor(oc, aFloatManager, aWM, aContainerSize);
7168 // Recurse into our normal children
7169 for (const auto& line : Lines()) {
7170 if (line.IsBlock()) {
7171 RecoverFloatsFor(line.mFirstChild, aFloatManager, aWM, aContainerSize);
7176 void nsBlockFrame::RecoverFloatsFor(nsIFrame* aFrame,
7177 nsFloatManager& aFloatManager,
7178 WritingMode aWM,
7179 const nsSize& aContainerSize) {
7180 MOZ_ASSERT(aFrame, "null frame");
7182 // Only blocks have floats
7183 nsBlockFrame* block = do_QueryFrame(aFrame);
7184 // Don't recover any state inside a block that has its own float manager
7185 // (we don't currently have any blocks like this, though, thanks to our
7186 // use of extra frames for 'overflow')
7187 if (block && !nsBlockFrame::BlockNeedsFloatManager(block)) {
7188 // If the element is relatively positioned, then adjust x and y
7189 // accordingly so that we consider relatively positioned frames
7190 // at their original position.
7192 const LogicalRect rect = block->GetLogicalNormalRect(aWM, aContainerSize);
7193 nscoord lineLeft = rect.LineLeft(aWM, aContainerSize);
7194 nscoord blockStart = rect.BStart(aWM);
7195 aFloatManager.Translate(lineLeft, blockStart);
7196 block->RecoverFloats(aFloatManager, aWM, aContainerSize);
7197 aFloatManager.Translate(-lineLeft, -blockStart);
7201 bool nsBlockFrame::HasPushedFloatsFromPrevContinuation() const {
7202 if (!mFloats.IsEmpty()) {
7203 // If we have pushed floats, then they should be at the beginning of our
7204 // float list.
7205 if (mFloats.FirstChild()->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
7206 return true;
7210 #ifdef DEBUG
7211 // Double-check the above assertion that pushed floats should be at the
7212 // beginning of our floats list.
7213 for (nsIFrame* f : mFloats) {
7214 NS_ASSERTION(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
7215 "pushed floats must be at the beginning of the float list");
7217 #endif
7219 // We may have a pending push of pushed floats too:
7220 if (HasPushedFloats()) {
7221 // XXX we can return 'true' here once we make HasPushedFloats
7222 // not lie. (see nsBlockFrame::RemoveFloat)
7223 auto* pushedFloats = GetPushedFloats();
7224 return pushedFloats && !pushedFloats->IsEmpty();
7226 return false;
7229 //////////////////////////////////////////////////////////////////////
7230 // Painting, event handling
7232 #ifdef DEBUG
7233 static void ComputeInkOverflowArea(nsLineList& aLines, nscoord aWidth,
7234 nscoord aHeight, nsRect& aResult) {
7235 nscoord xa = 0, ya = 0, xb = aWidth, yb = aHeight;
7236 for (nsLineList::iterator line = aLines.begin(), line_end = aLines.end();
7237 line != line_end; ++line) {
7238 // Compute min and max x/y values for the reflowed frame's
7239 // combined areas
7240 nsRect inkOverflow(line->InkOverflowRect());
7241 nscoord x = inkOverflow.x;
7242 nscoord y = inkOverflow.y;
7243 nscoord xmost = x + inkOverflow.width;
7244 nscoord ymost = y + inkOverflow.height;
7245 if (x < xa) {
7246 xa = x;
7248 if (xmost > xb) {
7249 xb = xmost;
7251 if (y < ya) {
7252 ya = y;
7254 if (ymost > yb) {
7255 yb = ymost;
7259 aResult.x = xa;
7260 aResult.y = ya;
7261 aResult.width = xb - xa;
7262 aResult.height = yb - ya;
7264 #endif
7266 #ifdef DEBUG
7267 static void DebugOutputDrawLine(int32_t aDepth, nsLineBox* aLine, bool aDrawn) {
7268 if (nsBlockFrame::gNoisyDamageRepair) {
7269 nsIFrame::IndentBy(stdout, aDepth + 1);
7270 nsRect lineArea = aLine->InkOverflowRect();
7271 printf("%s line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
7272 aDrawn ? "draw" : "skip", static_cast<void*>(aLine), aLine->IStart(),
7273 aLine->BStart(), aLine->ISize(), aLine->BSize(), lineArea.x,
7274 lineArea.y, lineArea.width, lineArea.height);
7277 #endif
7279 static void DisplayLine(nsDisplayListBuilder* aBuilder,
7280 nsBlockFrame::LineIterator& aLine,
7281 const bool aLineInLine, const nsDisplayListSet& aLists,
7282 nsBlockFrame* aFrame, TextOverflow* aTextOverflow,
7283 uint32_t aLineNumberForTextOverflow, int32_t aDepth,
7284 int32_t& aDrawnLines) {
7285 #ifdef DEBUG
7286 if (nsBlockFrame::gLamePaintMetrics) {
7287 aDrawnLines++;
7289 const bool intersect =
7290 aLine->InkOverflowRect().Intersects(aBuilder->GetDirtyRect());
7291 DebugOutputDrawLine(aDepth, aLine.get(), intersect);
7292 #endif
7294 // Collect our line's display items in a temporary nsDisplayListCollection,
7295 // so that we can apply any "text-overflow" clipping to the entire collection
7296 // without affecting previous lines.
7297 nsDisplayListCollection collection(aBuilder);
7299 // Block-level child backgrounds go on the blockBorderBackgrounds list ...
7300 // Inline-level child backgrounds go on the regular child content list.
7301 nsDisplayListSet childLists(
7302 collection,
7303 aLineInLine ? collection.Content() : collection.BlockBorderBackgrounds());
7305 auto flags =
7306 aLineInLine
7307 ? nsIFrame::DisplayChildFlags(nsIFrame::DisplayChildFlag::Inline)
7308 : nsIFrame::DisplayChildFlags();
7310 nsIFrame* kid = aLine->mFirstChild;
7311 int32_t n = aLine->GetChildCount();
7312 while (--n >= 0) {
7313 aFrame->BuildDisplayListForChild(aBuilder, kid, childLists, flags);
7314 kid = kid->GetNextSibling();
7317 if (aTextOverflow && aLineInLine) {
7318 aTextOverflow->ProcessLine(collection, aLine.get(),
7319 aLineNumberForTextOverflow);
7322 collection.MoveTo(aLists);
7325 void nsBlockFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
7326 const nsDisplayListSet& aLists) {
7327 int32_t drawnLines; // Will only be used if set (gLamePaintMetrics).
7328 int32_t depth = 0;
7329 #ifdef DEBUG
7330 if (gNoisyDamageRepair) {
7331 nsRect dirty = aBuilder->GetDirtyRect();
7332 depth = GetDepth();
7333 nsRect ca;
7334 ::ComputeInkOverflowArea(mLines, mRect.width, mRect.height, ca);
7335 nsIFrame::IndentBy(stdout, depth);
7336 ListTag(stdout);
7337 printf(": bounds=%d,%d,%d,%d dirty(absolute)=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
7338 mRect.x, mRect.y, mRect.width, mRect.height, dirty.x, dirty.y,
7339 dirty.width, dirty.height, ca.x, ca.y, ca.width, ca.height);
7341 PRTime start = 0; // Initialize these variables to silence the compiler.
7342 if (gLamePaintMetrics) {
7343 start = PR_Now();
7344 drawnLines = 0;
7346 #endif
7348 // TODO(heycam): Should we boost the load priority of any shape-outside
7349 // images using CATEGORY_DISPLAY, now that this block is being displayed?
7350 // We don't have a float manager here.
7352 DisplayBorderBackgroundOutline(aBuilder, aLists);
7354 if (GetPrevInFlow()) {
7355 DisplayOverflowContainers(aBuilder, aLists);
7356 for (nsIFrame* f : mFloats) {
7357 if (f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
7358 BuildDisplayListForChild(aBuilder, f, aLists);
7363 aBuilder->MarkFramesForDisplayList(this, mFloats);
7365 if (HasOutsideMarker()) {
7366 // Display outside ::marker manually.
7367 BuildDisplayListForChild(aBuilder, GetOutsideMarker(), aLists);
7370 // Prepare for text-overflow processing.
7371 Maybe<TextOverflow> textOverflow =
7372 TextOverflow::WillProcessLines(aBuilder, this);
7374 const bool hasDescendantPlaceHolders =
7375 HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) ||
7376 ForceDescendIntoIfVisible() || aBuilder->GetIncludeAllOutOfFlows();
7378 const auto ShouldDescendIntoLine = [&](const nsRect& aLineArea) -> bool {
7379 // TODO(miko): Unfortunately |descendAlways| cannot be cached, because with
7380 // some frame trees, building display list for child lines can change it.
7381 // See bug 1552789.
7382 const bool descendAlways =
7383 HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) ||
7384 aBuilder->GetIncludeAllOutOfFlows();
7386 return descendAlways || aLineArea.Intersects(aBuilder->GetDirtyRect()) ||
7387 (ForceDescendIntoIfVisible() &&
7388 aLineArea.Intersects(aBuilder->GetVisibleRect()));
7391 Maybe<nscolor> backplateColor;
7393 // We'll try to draw an accessibility backplate behind text (to ensure it's
7394 // readable over any possible background-images), if all of the following
7395 // hold:
7396 // (A) the backplate feature is preffed on
7397 // (B) we are not honoring the document colors
7398 // (C) the force color adjust property is set to auto
7399 if (StaticPrefs::browser_display_permit_backplate() &&
7400 PresContext()->ForcingColors() && !IsComboboxControlFrame() &&
7401 StyleText()->mForcedColorAdjust != StyleForcedColorAdjust::None) {
7402 backplateColor.emplace(GetBackplateColor(this));
7405 // Don't use the line cursor if we might have a descendant placeholder ...
7406 // it might skip lines that contain placeholders but don't themselves
7407 // intersect with the dirty area.
7408 // In particular, we really want to check ShouldDescendIntoFrame()
7409 // on all our child frames, but that might be expensive. So we
7410 // approximate it by checking it on |this|; if it's true for any
7411 // frame in our child list, it's also true for |this|.
7412 // Also skip the cursor if we're creating text overflow markers,
7413 // since we need to know what line number we're up to in order
7414 // to generate unique display item keys.
7415 // Lastly, the cursor should be skipped if we're drawing
7416 // backplates behind text. When backplating we consider consecutive
7417 // runs of text as a whole, which requires we iterate through all lines
7418 // to find our backplate size.
7419 nsLineBox* cursor =
7420 (hasDescendantPlaceHolders || textOverflow.isSome() || backplateColor)
7421 ? nullptr
7422 : GetFirstLineContaining(aBuilder->GetDirtyRect().y);
7423 LineIterator line_end = LinesEnd();
7425 TextOverflow* textOverflowPtr = textOverflow.ptrOr(nullptr);
7427 if (cursor) {
7428 for (LineIterator line = mLines.begin(cursor); line != line_end; ++line) {
7429 const nsRect lineArea = line->InkOverflowRect();
7430 if (!lineArea.IsEmpty()) {
7431 // Because we have a cursor, the combinedArea.ys are non-decreasing.
7432 // Once we've passed aDirtyRect.YMost(), we can never see it again.
7433 if (lineArea.y >= aBuilder->GetDirtyRect().YMost()) {
7434 break;
7436 MOZ_ASSERT(textOverflow.isNothing());
7438 if (ShouldDescendIntoLine(lineArea)) {
7439 DisplayLine(aBuilder, line, line->IsInline(), aLists, this, nullptr,
7440 0, depth, drawnLines);
7444 } else {
7445 bool nonDecreasingYs = true;
7446 uint32_t lineCount = 0;
7447 nscoord lastY = INT32_MIN;
7448 nscoord lastYMost = INT32_MIN;
7450 // A frame's display list cannot contain more than one copy of a
7451 // given display item unless the items are uniquely identifiable.
7452 // Because backplate occasionally requires multiple
7453 // SolidColor items, we use an index (backplateIndex) to maintain
7454 // uniqueness among them. Note this is a mapping of index to
7455 // item, and the mapping is stable even if the dirty rect changes.
7456 uint16_t backplateIndex = 0;
7457 nsRect curBackplateArea;
7459 auto AddBackplate = [&]() {
7460 aLists.BorderBackground()->AppendNewToTopWithIndex<nsDisplaySolidColor>(
7461 aBuilder, this, backplateIndex, curBackplateArea,
7462 backplateColor.value());
7465 for (LineIterator line = LinesBegin(); line != line_end; ++line) {
7466 const nsRect lineArea = line->InkOverflowRect();
7467 const bool lineInLine = line->IsInline();
7469 if ((lineInLine && textOverflowPtr) || ShouldDescendIntoLine(lineArea)) {
7470 DisplayLine(aBuilder, line, lineInLine, aLists, this, textOverflowPtr,
7471 lineCount, depth, drawnLines);
7474 if (!lineInLine && !curBackplateArea.IsEmpty()) {
7475 // If we have encountered a non-inline line but were previously
7476 // forming a backplate, we should add the backplate to the display
7477 // list as-is and render future backplates disjointly.
7478 MOZ_ASSERT(backplateColor,
7479 "if this master switch is off, curBackplateArea "
7480 "must be empty and we shouldn't get here");
7481 AddBackplate();
7482 backplateIndex++;
7483 curBackplateArea = nsRect();
7486 if (!lineArea.IsEmpty()) {
7487 if (lineArea.y < lastY || lineArea.YMost() < lastYMost) {
7488 nonDecreasingYs = false;
7490 lastY = lineArea.y;
7491 lastYMost = lineArea.YMost();
7492 if (lineInLine && backplateColor && LineHasVisibleInlineContent(line)) {
7493 nsRect lineBackplate = GetLineTextArea(line, aBuilder) +
7494 aBuilder->ToReferenceFrame(this);
7495 if (curBackplateArea.IsEmpty()) {
7496 curBackplateArea = lineBackplate;
7497 } else {
7498 curBackplateArea.OrWith(lineBackplate);
7502 lineCount++;
7505 if (nonDecreasingYs && lineCount >= MIN_LINES_NEEDING_CURSOR) {
7506 SetupLineCursorForDisplay();
7509 if (!curBackplateArea.IsEmpty()) {
7510 AddBackplate();
7514 if (textOverflow.isSome()) {
7515 // Put any text-overflow:ellipsis markers on top of the non-positioned
7516 // content of the block's lines. (If we ever start sorting the Content()
7517 // list this will end up in the wrong place.)
7518 aLists.Content()->AppendToTop(&textOverflow->GetMarkers());
7521 #ifdef DEBUG
7522 if (gLamePaintMetrics) {
7523 PRTime end = PR_Now();
7525 int32_t numLines = mLines.size();
7526 if (!numLines) {
7527 numLines = 1;
7529 PRTime lines, deltaPerLine, delta;
7530 lines = int64_t(numLines);
7531 delta = end - start;
7532 deltaPerLine = delta / lines;
7534 ListTag(stdout);
7535 char buf[400];
7536 SprintfLiteral(buf,
7537 ": %" PRId64 " elapsed (%" PRId64
7538 " per line) lines=%d drawn=%d skip=%d",
7539 delta, deltaPerLine, numLines, drawnLines,
7540 numLines - drawnLines);
7541 printf("%s\n", buf);
7543 #endif
7546 #ifdef ACCESSIBILITY
7547 a11y::AccType nsBlockFrame::AccessibleType() {
7548 if (IsTableCaption()) {
7549 return GetRect().IsEmpty() ? a11y::eNoType : a11y::eHTMLCaptionType;
7552 // block frame may be for <hr>
7553 if (mContent->IsHTMLElement(nsGkAtoms::hr)) {
7554 return a11y::eHTMLHRType;
7557 if (!HasMarker() || !PresContext()) {
7558 // XXXsmaug What if we're in the shadow dom?
7559 if (!mContent->GetParent()) {
7560 // Don't create accessible objects for the root content node, they are
7561 // redundant with the nsDocAccessible object created with the document
7562 // node
7563 return a11y::eNoType;
7566 if (mContent == mContent->OwnerDoc()->GetBody()) {
7567 // Don't create accessible objects for the body, they are redundant with
7568 // the nsDocAccessible object created with the document node
7569 return a11y::eNoType;
7572 // Not a list item with a ::marker, treat as normal HTML container.
7573 return a11y::eHyperTextType;
7576 // Create special list item accessible since we have a ::marker.
7577 return a11y::eHTMLLiType;
7579 #endif
7581 void nsBlockFrame::SetupLineCursorForDisplay() {
7582 if (mLines.empty() || HasProperty(LineCursorPropertyDisplay())) {
7583 return;
7586 SetProperty(LineCursorPropertyDisplay(), mLines.front());
7587 AddStateBits(NS_BLOCK_HAS_LINE_CURSOR);
7590 void nsBlockFrame::SetupLineCursorForQuery() {
7591 if (mLines.empty() || HasProperty(LineCursorPropertyQuery())) {
7592 return;
7595 SetProperty(LineCursorPropertyQuery(), mLines.front());
7596 AddStateBits(NS_BLOCK_HAS_LINE_CURSOR);
7599 nsLineBox* nsBlockFrame::GetFirstLineContaining(nscoord y) {
7600 // Although this looks like a "querying" method, it is used by the
7601 // display-list building code, so uses the Display cursor.
7602 nsLineBox* property = GetLineCursorForDisplay();
7603 if (!property) {
7604 return nullptr;
7606 LineIterator cursor = mLines.begin(property);
7607 nsRect cursorArea = cursor->InkOverflowRect();
7609 while ((cursorArea.IsEmpty() || cursorArea.YMost() > y) &&
7610 cursor != mLines.front()) {
7611 cursor = cursor.prev();
7612 cursorArea = cursor->InkOverflowRect();
7614 while ((cursorArea.IsEmpty() || cursorArea.YMost() <= y) &&
7615 cursor != mLines.back()) {
7616 cursor = cursor.next();
7617 cursorArea = cursor->InkOverflowRect();
7620 if (cursor.get() != property) {
7621 SetProperty(LineCursorPropertyDisplay(), cursor.get());
7624 return cursor.get();
7627 /* virtual */
7628 void nsBlockFrame::ChildIsDirty(nsIFrame* aChild) {
7629 // See if the child is absolutely positioned
7630 if (aChild->IsAbsolutelyPositioned()) {
7631 // do nothing
7632 } else if (aChild == GetOutsideMarker()) {
7633 // The ::marker lives in the first line, unless the first line has
7634 // height 0 and there is a second line, in which case it lives
7635 // in the second line.
7636 LineIterator markerLine = LinesBegin();
7637 if (markerLine != LinesEnd() && markerLine->BSize() == 0 &&
7638 markerLine != mLines.back()) {
7639 markerLine = markerLine.next();
7642 if (markerLine != LinesEnd()) {
7643 MarkLineDirty(markerLine, &mLines);
7645 // otherwise we have an empty line list, and ReflowDirtyLines
7646 // will handle reflowing the ::marker.
7647 } else {
7648 // Note that we should go through our children to mark lines dirty
7649 // before the next reflow. Doing it now could make things O(N^2)
7650 // since finding the right line is O(N).
7651 // We don't need to worry about marking lines on the overflow list
7652 // as dirty; we're guaranteed to reflow them if we take them off the
7653 // overflow list.
7654 // However, we might have gotten a float, in which case we need to
7655 // reflow the line containing its placeholder. So find the
7656 // ancestor-or-self of the placeholder that's a child of the block,
7657 // and mark it as NS_FRAME_HAS_DIRTY_CHILDREN too, so that we mark
7658 // its line dirty when we handle NS_BLOCK_LOOK_FOR_DIRTY_FRAMES.
7659 // We need to take some care to handle the case where a float is in
7660 // a different continuation than its placeholder, including marking
7661 // an extra block with NS_BLOCK_LOOK_FOR_DIRTY_FRAMES.
7662 if (!aChild->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
7663 AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
7664 } else {
7665 NS_ASSERTION(aChild->IsFloating(), "should be a float");
7666 nsIFrame* thisFC = FirstContinuation();
7667 nsIFrame* placeholderPath = aChild->GetPlaceholderFrame();
7668 // SVG code sometimes sends FrameNeedsReflow notifications during
7669 // frame destruction, leading to null placeholders, but we're safe
7670 // ignoring those.
7671 if (placeholderPath) {
7672 for (;;) {
7673 nsIFrame* parent = placeholderPath->GetParent();
7674 if (parent->GetContent() == mContent &&
7675 parent->FirstContinuation() == thisFC) {
7676 parent->AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
7677 break;
7679 placeholderPath = parent;
7681 placeholderPath->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
7686 nsContainerFrame::ChildIsDirty(aChild);
7689 void nsBlockFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
7690 nsIFrame* aPrevInFlow) {
7691 // These are all the block specific frame bits, they are copied from
7692 // the prev-in-flow to a newly created next-in-flow, except for the
7693 // NS_BLOCK_FLAGS_NON_INHERITED_MASK bits below.
7694 constexpr nsFrameState NS_BLOCK_FLAGS_MASK =
7695 NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS |
7696 NS_BLOCK_CLIP_PAGINATED_OVERFLOW | NS_BLOCK_HAS_FIRST_LETTER_STYLE |
7697 NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER | NS_BLOCK_HAS_FIRST_LETTER_CHILD |
7698 NS_BLOCK_FRAME_HAS_INSIDE_MARKER;
7700 // This is the subset of NS_BLOCK_FLAGS_MASK that is NOT inherited
7701 // by default. They should only be set on the first-in-flow.
7702 constexpr nsFrameState NS_BLOCK_FLAGS_NON_INHERITED_MASK =
7703 NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER | NS_BLOCK_HAS_FIRST_LETTER_CHILD |
7704 NS_BLOCK_FRAME_HAS_INSIDE_MARKER;
7706 if (aPrevInFlow) {
7707 // Copy over the inherited block frame bits from the prev-in-flow.
7708 RemoveStateBits(NS_BLOCK_FLAGS_MASK);
7709 AddStateBits(aPrevInFlow->GetStateBits() &
7710 (NS_BLOCK_FLAGS_MASK & ~NS_BLOCK_FLAGS_NON_INHERITED_MASK));
7713 nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
7715 if (!aPrevInFlow ||
7716 aPrevInFlow->HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION)) {
7717 AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
7720 // A display:flow-root box establishes a block formatting context.
7722 // If a box has a different writing-mode value than its containing block:
7723 // ...
7724 // If the box is a block container, then it establishes a new block
7725 // formatting context.
7726 // (https://drafts.csswg.org/css-writing-modes/#block-flow)
7728 // If the box has contain: paint or contain:layout (or contain:strict),
7729 // then it should also establish a formatting context.
7731 // Per spec, a column-span always establishes a new block formatting context.
7732 if (StyleDisplay()->mDisplay == mozilla::StyleDisplay::FlowRoot ||
7733 (GetParent() &&
7734 (GetWritingMode().GetBlockDir() !=
7735 GetParent()->GetWritingMode().GetBlockDir() ||
7736 GetWritingMode().IsVerticalSideways() !=
7737 GetParent()->GetWritingMode().IsVerticalSideways())) ||
7738 StyleDisplay()->IsContainPaint() || StyleDisplay()->IsContainLayout() ||
7739 IsColumnSpan()) {
7740 AddStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS);
7743 if (HasAllStateBits(NS_FRAME_FONT_INFLATION_CONTAINER | NS_BLOCK_FLOAT_MGR)) {
7744 AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
7748 void nsBlockFrame::SetInitialChildList(ChildListID aListID,
7749 nsFrameList&& aChildList) {
7750 if (FrameChildListID::Float == aListID) {
7751 mFloats = std::move(aChildList);
7752 } else if (FrameChildListID::Principal == aListID) {
7753 #ifdef DEBUG
7754 // The only times a block that is an anonymous box is allowed to have a
7755 // first-letter frame are when it's the block inside a non-anonymous cell,
7756 // the block inside a fieldset, button or column set, or a scrolled content
7757 // block, except for <select>. Note that this means that blocks which are
7758 // the anonymous block in {ib} splits do NOT get first-letter frames.
7759 // Note that NS_BLOCK_HAS_FIRST_LETTER_STYLE gets set on all continuations
7760 // of the block.
7761 auto pseudo = Style()->GetPseudoType();
7762 bool haveFirstLetterStyle =
7763 (pseudo == PseudoStyleType::NotPseudo ||
7764 (pseudo == PseudoStyleType::cellContent &&
7765 !GetParent()->Style()->IsPseudoOrAnonBox()) ||
7766 pseudo == PseudoStyleType::fieldsetContent ||
7767 pseudo == PseudoStyleType::buttonContent ||
7768 pseudo == PseudoStyleType::columnContent ||
7769 (pseudo == PseudoStyleType::scrolledContent &&
7770 !GetParent()->IsListControlFrame()) ||
7771 pseudo == PseudoStyleType::mozSVGText) &&
7772 !IsComboboxControlFrame() && !IsFrameOfType(eMathML) &&
7773 !IsColumnSetWrapperFrame() &&
7774 RefPtr<ComputedStyle>(GetFirstLetterStyle(PresContext())) != nullptr;
7775 NS_ASSERTION(haveFirstLetterStyle ==
7776 HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE),
7777 "NS_BLOCK_HAS_FIRST_LETTER_STYLE state out of sync");
7778 #endif
7780 AddFrames(std::move(aChildList), nullptr, nullptr);
7781 } else {
7782 nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
7786 void nsBlockFrame::SetMarkerFrameForListItem(nsIFrame* aMarkerFrame) {
7787 MOZ_ASSERT(aMarkerFrame);
7788 MOZ_ASSERT(!HasAnyStateBits(NS_BLOCK_FRAME_HAS_INSIDE_MARKER |
7789 NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER),
7790 "How can we have a ::marker frame already?");
7792 if (StyleList()->mListStylePosition == StyleListStylePosition::Inside) {
7793 SetProperty(InsideMarkerProperty(), aMarkerFrame);
7794 AddStateBits(NS_BLOCK_FRAME_HAS_INSIDE_MARKER);
7795 } else {
7796 if (nsBlockFrame* marker = do_QueryFrame(aMarkerFrame)) {
7797 // An outside ::marker needs to be an independent formatting context
7798 // to avoid being influenced by the float manager etc.
7799 marker->AddStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS);
7801 SetProperty(OutsideMarkerProperty(),
7802 new (PresShell()) nsFrameList(aMarkerFrame, aMarkerFrame));
7803 AddStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER);
7807 bool nsBlockFrame::MarkerIsEmpty() const {
7808 NS_ASSERTION(mContent->GetPrimaryFrame()->StyleDisplay()->IsListItem() &&
7809 HasOutsideMarker(),
7810 "should only care when we have an outside ::marker");
7811 nsIFrame* marker = GetMarker();
7812 const nsStyleList* list = marker->StyleList();
7813 return marker->StyleContent()->mContent.IsNone() ||
7814 (list->mCounterStyle.IsNone() && list->mListStyleImage.IsNone() &&
7815 marker->StyleContent()->ContentCount() == 0);
7818 void nsBlockFrame::ReflowOutsideMarker(nsIFrame* aMarkerFrame,
7819 BlockReflowState& aState,
7820 ReflowOutput& aMetrics,
7821 nscoord aLineTop) {
7822 const ReflowInput& ri = aState.mReflowInput;
7824 WritingMode markerWM = aMarkerFrame->GetWritingMode();
7825 LogicalSize availSize(markerWM);
7826 // Make up an inline-size since it doesn't really matter (XXX).
7827 availSize.ISize(markerWM) = aState.ContentISize();
7828 availSize.BSize(markerWM) = NS_UNCONSTRAINEDSIZE;
7830 ReflowInput reflowInput(aState.mPresContext, ri, aMarkerFrame, availSize,
7831 Nothing(), {}, {}, {ComputeSizeFlag::ShrinkWrap});
7832 nsReflowStatus status;
7833 aMarkerFrame->Reflow(aState.mPresContext, aMetrics, reflowInput, status);
7835 // Get the float available space using our saved state from before we
7836 // started reflowing the block, so that we ignore any floats inside
7837 // the block.
7838 // FIXME: aLineTop isn't actually set correctly by some callers, since
7839 // they reposition the line.
7840 LogicalRect floatAvailSpace =
7841 aState
7842 .GetFloatAvailableSpaceWithState(aLineTop, ShapeType::ShapeOutside,
7843 &aState.mFloatManagerStateBefore)
7844 .mRect;
7845 // FIXME (bug 25888): need to check the entire region that the first
7846 // line overlaps, not just the top pixel.
7848 // Place the ::marker now. We want to place the ::marker relative to the
7849 // border-box of the associated block (using the right/left margin of
7850 // the ::marker frame as separation). However, if a line box would be
7851 // displaced by floats that are *outside* the associated block, we
7852 // want to displace it by the same amount. That is, we act as though
7853 // the edge of the floats is the content-edge of the block, and place
7854 // the ::marker at a position offset from there by the block's padding,
7855 // the block's border, and the ::marker frame's margin.
7857 // IStart from floatAvailSpace gives us the content/float start edge
7858 // in the current writing mode. Then we subtract out the start
7859 // border/padding and the ::marker's width and margin to offset the position.
7860 WritingMode wm = ri.GetWritingMode();
7861 // Get the ::marker's margin, converted to our writing mode so that we can
7862 // combine it with other logical values here.
7863 LogicalMargin markerMargin = reflowInput.ComputedLogicalMargin(wm);
7864 nscoord iStart = floatAvailSpace.IStart(wm) -
7865 ri.ComputedLogicalBorderPadding(wm).IStart(wm) -
7866 markerMargin.IEnd(wm) - aMetrics.ISize(wm);
7868 // Approximate the ::marker's position; vertical alignment will provide
7869 // the final vertical location. We pass our writing-mode here, because
7870 // it may be different from the ::marker frame's mode.
7871 nscoord bStart = floatAvailSpace.BStart(wm);
7872 aMarkerFrame->SetRect(
7874 LogicalRect(wm, iStart, bStart, aMetrics.ISize(wm), aMetrics.BSize(wm)),
7875 aState.ContainerSize());
7876 aMarkerFrame->DidReflow(aState.mPresContext, &aState.mReflowInput);
7879 // This is used to scan frames for any float placeholders, add their
7880 // floats to the list represented by aList, and remove the
7881 // floats from whatever list they might be in. We don't search descendants
7882 // that are float containing blocks. Floats that or not children of 'this'
7883 // are ignored (they are not added to aList).
7884 void nsBlockFrame::DoCollectFloats(nsIFrame* aFrame, nsFrameList& aList,
7885 bool aCollectSiblings) {
7886 while (aFrame) {
7887 // Don't descend into float containing blocks.
7888 if (!aFrame->IsFloatContainingBlock()) {
7889 nsIFrame* outOfFlowFrame =
7890 aFrame->IsPlaceholderFrame()
7891 ? nsLayoutUtils::GetFloatFromPlaceholder(aFrame)
7892 : nullptr;
7893 while (outOfFlowFrame && outOfFlowFrame->GetParent() == this) {
7894 RemoveFloat(outOfFlowFrame);
7895 // Remove the IS_PUSHED_FLOAT bit, in case |outOfFlowFrame| came from
7896 // the PushedFloats list.
7897 outOfFlowFrame->RemoveStateBits(NS_FRAME_IS_PUSHED_FLOAT);
7898 aList.AppendFrame(nullptr, outOfFlowFrame);
7899 outOfFlowFrame = outOfFlowFrame->GetNextInFlow();
7900 // FIXME: By not pulling floats whose parent is one of our
7901 // later siblings, are we risking the pushed floats getting
7902 // out-of-order?
7903 // XXXmats nsInlineFrame's lazy reparenting depends on NOT doing that.
7906 DoCollectFloats(aFrame->PrincipalChildList().FirstChild(), aList, true);
7907 DoCollectFloats(
7908 aFrame->GetChildList(FrameChildListID::Overflow).FirstChild(), aList,
7909 true);
7911 if (!aCollectSiblings) {
7912 break;
7914 aFrame = aFrame->GetNextSibling();
7918 void nsBlockFrame::CheckFloats(BlockReflowState& aState) {
7919 #ifdef DEBUG
7920 // If any line is still dirty, that must mean we're going to reflow this
7921 // block again soon (e.g. because we bailed out after noticing that
7922 // clearance was imposed), so don't worry if the floats are out of sync.
7923 bool anyLineDirty = false;
7925 // Check that the float list is what we would have built
7926 AutoTArray<nsIFrame*, 8> lineFloats;
7927 for (auto& line : Lines()) {
7928 if (line.HasFloats()) {
7929 lineFloats.AppendElements(line.Floats());
7931 if (line.IsDirty()) {
7932 anyLineDirty = true;
7936 AutoTArray<nsIFrame*, 8> storedFloats;
7937 bool equal = true;
7938 bool hasHiddenFloats = false;
7939 uint32_t i = 0;
7940 for (nsIFrame* f : mFloats) {
7941 if (f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
7942 continue;
7944 // There are chances that the float children won't be added to lines,
7945 // because in nsBlockFrame::ReflowLine, it skips reflow line if the first
7946 // child of the line is IsHiddenByContentVisibilityOfInFlowParentForLayout.
7947 // There are also chances that the floats in line are out of date, for
7948 // instance, lines could reflow if
7949 // PresShell::IsForcingLayoutForHiddenContent, and after forcingLayout is
7950 // off, the reflow of lines could be skipped, but the floats are still in
7951 // there. Here we can't know whether the floats hidden by c-v are included
7952 // in the lines or not. So we use hasHiddenFloats to skip the float length
7953 // checking.
7954 if (!hasHiddenFloats &&
7955 f->IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
7956 hasHiddenFloats = true;
7958 storedFloats.AppendElement(f);
7959 if (i < lineFloats.Length() && lineFloats.ElementAt(i) != f) {
7960 equal = false;
7962 ++i;
7965 if ((!equal || lineFloats.Length() != storedFloats.Length()) &&
7966 !anyLineDirty && !hasHiddenFloats) {
7967 NS_ERROR(
7968 "nsBlockFrame::CheckFloats: Explicit float list is out of sync with "
7969 "float cache");
7970 # if defined(DEBUG_roc)
7971 nsIFrame::RootFrameList(PresContext(), stdout, 0);
7972 for (i = 0; i < lineFloats.Length(); ++i) {
7973 printf("Line float: %p\n", lineFloats.ElementAt(i));
7975 for (i = 0; i < storedFloats.Length(); ++i) {
7976 printf("Stored float: %p\n", storedFloats.ElementAt(i));
7978 # endif
7980 #endif
7982 const nsFrameList* oofs = GetOverflowOutOfFlows();
7983 if (oofs && oofs->NotEmpty()) {
7984 // Floats that were pushed should be removed from our float
7985 // manager. Otherwise the float manager's YMost or XMost might
7986 // be larger than necessary, causing this block to get an
7987 // incorrect desired height (or width). Some of these floats
7988 // may not actually have been added to the float manager because
7989 // they weren't reflowed before being pushed; that's OK,
7990 // RemoveRegions will ignore them. It is safe to do this here
7991 // because we know from here on the float manager will only be
7992 // used for its XMost and YMost, not to place new floats and
7993 // lines.
7994 aState.FloatManager()->RemoveTrailingRegions(oofs->FirstChild());
7998 void nsBlockFrame::IsMarginRoot(bool* aBStartMarginRoot,
7999 bool* aBEndMarginRoot) {
8000 nsIFrame* parent = GetParent();
8001 if (!HasAnyStateBits(NS_BLOCK_MARGIN_ROOT)) {
8002 if (!parent || parent->IsFloatContainingBlock()) {
8003 *aBStartMarginRoot = false;
8004 *aBEndMarginRoot = false;
8005 return;
8009 if (parent && parent->IsColumnSetFrame()) {
8010 // The first column is a start margin root and the last column is an end
8011 // margin root. (If the column-set is split by a column-span:all box then
8012 // the first and last column in each column-set fragment are margin roots.)
8013 *aBStartMarginRoot = GetPrevInFlow() == nullptr;
8014 *aBEndMarginRoot = GetNextInFlow() == nullptr;
8015 return;
8018 *aBStartMarginRoot = true;
8019 *aBEndMarginRoot = true;
8022 /* static */
8023 bool nsBlockFrame::BlockNeedsFloatManager(nsIFrame* aBlock) {
8024 MOZ_ASSERT(aBlock, "Must have a frame");
8025 NS_ASSERTION(aBlock->IsBlockFrameOrSubclass(), "aBlock must be a block");
8027 nsIFrame* parent = aBlock->GetParent();
8028 return aBlock->HasAnyStateBits(NS_BLOCK_FLOAT_MGR) ||
8029 (parent && !parent->IsFloatContainingBlock());
8032 /* static */
8033 bool nsBlockFrame::BlockCanIntersectFloats(nsIFrame* aFrame) {
8034 return aFrame->IsBlockFrameOrSubclass() &&
8035 !aFrame->IsFrameOfType(nsIFrame::eReplaced) &&
8036 !aFrame->HasAnyStateBits(NS_BLOCK_FLOAT_MGR);
8039 // Note that this width can vary based on the vertical position.
8040 // However, the cases where it varies are the cases where the width fits
8041 // in the available space given, which means that variation shouldn't
8042 // matter.
8043 /* static */
8044 nsBlockFrame::FloatAvoidingISizeToClear nsBlockFrame::ISizeToClearPastFloats(
8045 const BlockReflowState& aState, const LogicalRect& aFloatAvailableSpace,
8046 nsIFrame* aFloatAvoidingBlock) {
8047 nscoord inlineStartOffset, inlineEndOffset;
8048 WritingMode wm = aState.mReflowInput.GetWritingMode();
8050 FloatAvoidingISizeToClear result;
8051 aState.ComputeFloatAvoidingOffsets(aFloatAvoidingBlock, aFloatAvailableSpace,
8052 inlineStartOffset, inlineEndOffset);
8053 nscoord availISize =
8054 aState.mContentArea.ISize(wm) - inlineStartOffset - inlineEndOffset;
8056 // We actually don't want the min width here; see bug 427782; we only
8057 // want to displace if the width won't compute to a value small enough
8058 // to fit.
8059 // All we really need here is the result of ComputeSize, and we
8060 // could *almost* get that from an SizeComputationInput, except for the
8061 // last argument.
8062 WritingMode frWM = aFloatAvoidingBlock->GetWritingMode();
8063 LogicalSize availSpace =
8064 LogicalSize(wm, availISize, NS_UNCONSTRAINEDSIZE).ConvertTo(frWM, wm);
8065 ReflowInput reflowInput(aState.mPresContext, aState.mReflowInput,
8066 aFloatAvoidingBlock, availSpace);
8067 result.borderBoxISize =
8068 reflowInput.ComputedSizeWithBorderPadding(wm).ISize(wm);
8070 // Use the margins from sizingInput rather than reflowInput so that
8071 // they aren't reduced by ignoring margins in overconstrained cases.
8072 SizeComputationInput sizingInput(aFloatAvoidingBlock,
8073 aState.mReflowInput.mRenderingContext, wm,
8074 aState.mContentArea.ISize(wm));
8075 const LogicalMargin computedMargin = sizingInput.ComputedLogicalMargin(wm);
8077 nscoord marginISize = computedMargin.IStartEnd(wm);
8078 const auto& iSize = reflowInput.mStylePosition->ISize(wm);
8079 if (marginISize < 0 && (iSize.IsAuto() || iSize.IsMozAvailable())) {
8080 // If we get here, floatAvoidingBlock has a negative amount of inline-axis
8081 // margin and an 'auto' (or ~equivalently, -moz-available) inline
8082 // size. Under these circumstances, we use the margin to establish a
8083 // (positive) minimum size for the border-box, in order to satisfy the
8084 // equation in CSS2 10.3.3. That equation essentially simplifies to the
8085 // following:
8087 // iSize of margins + iSize of borderBox = iSize of containingBlock
8089 // ...where "iSize of borderBox" is the sum of floatAvoidingBlock's
8090 // inline-axis components of border, padding, and {width,height}.
8092 // Right now, in the above equation, "iSize of margins" is the only term
8093 // that we know for sure. (And we also know that it's negative, since we
8094 // got here.) The other terms are as-yet unresolved, since the frame has an
8095 // 'auto' iSize, and since we aren't yet sure if we'll clear this frame
8096 // beyond floats or place it alongside them.
8098 // However: we *do* know that the equation's "iSize of containingBlock"
8099 // term *must* be non-negative, since boxes' widths and heights generally
8100 // can't be negative in CSS. To satisfy that requirement, we can then
8101 // infer that the equation's "iSize of borderBox" term *must* be large
8102 // enough to cancel out the (known-to-be-negative) "iSize of margins"
8103 // term. Therefore, marginISize value (negated to make it positive)
8104 // establishes a lower-bound for how much inline-axis space our border-box
8105 // will really require in order to fit alongside any floats.
8107 // XXXdholbert This explanation is admittedly a bit hand-wavy and may not
8108 // precisely match what any particular spec requires. It's the best
8109 // reasoning I could come up with to explain engines' behavior. Also, our
8110 // behavior with -moz-available doesn't seem particularly correct here, per
8111 // bug 1767217, though that's probably due to a bug elsewhere in our float
8112 // handling code...
8113 result.borderBoxISize = std::max(result.borderBoxISize, -marginISize);
8116 result.marginIStart = computedMargin.IStart(wm);
8117 return result;
8120 /* static */
8121 nsBlockFrame* nsBlockFrame::GetNearestAncestorBlock(nsIFrame* aCandidate) {
8122 nsBlockFrame* block = nullptr;
8123 while (aCandidate) {
8124 block = do_QueryFrame(aCandidate);
8125 if (block) {
8126 // yay, candidate is a block!
8127 return block;
8129 // Not a block. Check its parent next.
8130 aCandidate = aCandidate->GetParent();
8132 MOZ_ASSERT_UNREACHABLE("Fell off frame tree looking for ancestor block!");
8133 return nullptr;
8136 nscoord nsBlockFrame::ComputeFinalBSize(BlockReflowState& aState,
8137 nscoord aBEndEdgeOfChildren) {
8138 const WritingMode wm = aState.mReflowInput.GetWritingMode();
8140 const nscoord effectiveContentBoxBSize =
8141 GetEffectiveComputedBSize(aState.mReflowInput, aState.mConsumedBSize);
8142 const nscoord blockStartBP = aState.BorderPadding().BStart(wm);
8143 const nscoord blockEndBP = aState.BorderPadding().BEnd(wm);
8145 NS_ASSERTION(
8146 !IsTrueOverflowContainer() || (effectiveContentBoxBSize == 0 &&
8147 blockStartBP == 0 && blockEndBP == 0),
8148 "An overflow container's effective content-box block-size, block-start "
8149 "BP, and block-end BP should all be zero!");
8151 const nscoord effectiveContentBoxBSizeWithBStartBP =
8152 NSCoordSaturatingAdd(blockStartBP, effectiveContentBoxBSize);
8153 const nscoord effectiveBorderBoxBSize =
8154 NSCoordSaturatingAdd(effectiveContentBoxBSizeWithBStartBP, blockEndBP);
8156 if (HasColumnSpanSiblings()) {
8157 MOZ_ASSERT(LastInFlow()->GetNextContinuation(),
8158 "Frame constructor should've created column-span siblings!");
8160 // If a block is split by any column-spans, we calculate the final
8161 // block-size by shrinkwrapping our children's block-size for all the
8162 // fragments except for those after the final column-span, but we should
8163 // take no more than our effective border-box block-size. If there's any
8164 // leftover block-size, our next continuations will take up rest.
8166 // We don't need to adjust aBri.mReflowStatus because our children's status
8167 // is the same as ours.
8168 return std::min(effectiveBorderBoxBSize, aBEndEdgeOfChildren);
8171 const nscoord availBSize = aState.mReflowInput.AvailableBSize();
8172 if (availBSize == NS_UNCONSTRAINEDSIZE) {
8173 return effectiveBorderBoxBSize;
8176 // Save our children's reflow status.
8177 const bool isChildStatusComplete = aState.mReflowStatus.IsComplete();
8178 if (isChildStatusComplete && effectiveContentBoxBSize > 0 &&
8179 effectiveBorderBoxBSize > availBSize &&
8180 ShouldAvoidBreakInside(aState.mReflowInput)) {
8181 aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
8182 return effectiveBorderBoxBSize;
8185 const bool isBDBClone =
8186 aState.mReflowInput.mStyleBorder->mBoxDecorationBreak ==
8187 StyleBoxDecorationBreak::Clone;
8189 // The maximum value our content-box block-size can take within the given
8190 // available block-size.
8191 const nscoord maxContentBoxBSize = aState.ContentBSize();
8193 // The block-end edge of our content-box (relative to this frame's origin) if
8194 // we consumed the maximum block-size available to us (maxContentBoxBSize).
8195 const nscoord maxContentBoxBEnd = aState.ContentBEnd();
8197 // These variables are uninitialized intentionally so that the compiler can
8198 // check they are assigned in every if-else branch below.
8199 nscoord finalContentBoxBSizeWithBStartBP;
8200 bool isOurStatusComplete;
8202 if (effectiveBorderBoxBSize <= availBSize) {
8203 // Our effective border-box block-size can fit in the available block-size,
8204 // so we are complete.
8205 finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP;
8206 isOurStatusComplete = true;
8207 } else if (effectiveContentBoxBSizeWithBStartBP <= maxContentBoxBEnd) {
8208 // Note: The following assertion should generally hold because, for
8209 // box-decoration-break:clone, this "else if" branch is mathematically
8210 // equivalent to the initial "if".
8211 NS_ASSERTION(!isBDBClone,
8212 "This else-if branch is handling a situation that's specific "
8213 "to box-decoration-break:slice, i.e. a case when we can skip "
8214 "our block-end border and padding!");
8216 // Our effective content-box block-size plus the block-start border and
8217 // padding can fit in the available block-size, but it cannot fit after
8218 // adding the block-end border and padding. Thus, we need a continuation
8219 // (unless we already weren't asking for any block-size, in which case we
8220 // stay complete to avoid looping forever).
8221 finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP;
8222 isOurStatusComplete = effectiveContentBoxBSize == 0;
8223 } else {
8224 // We aren't going to be able to fit our content-box in the space available
8225 // to it, which means we'll probably call ourselves incomplete to request a
8226 // continuation. But before making that decision, we check for certain
8227 // conditions which would force us to overflow beyond the available space --
8228 // these might result in us actually being complete if we're forced to
8229 // overflow far enough.
8230 if (MOZ_UNLIKELY(aState.mReflowInput.mFlags.mIsTopOfPage && isBDBClone &&
8231 maxContentBoxBSize <= 0 &&
8232 aBEndEdgeOfChildren == blockStartBP)) {
8233 // In this rare case, we are at the top of page/column, we have
8234 // box-decoration-break:clone and zero available block-size for our
8235 // content-box (e.g. our own block-start border and padding already exceed
8236 // the available block-size), and we didn't lay out any child to consume
8237 // our content-box block-size. To ensure we make progress (avoid looping
8238 // forever), use 1px as our content-box block-size regardless of our
8239 // effective content-box block-size, in the spirit of
8240 // https://drafts.csswg.org/css-break/#breaking-rules.
8241 finalContentBoxBSizeWithBStartBP = blockStartBP + AppUnitsPerCSSPixel();
8242 isOurStatusComplete = effectiveContentBoxBSize <= AppUnitsPerCSSPixel();
8243 } else if (aBEndEdgeOfChildren > maxContentBoxBEnd) {
8244 // We have a unbreakable child whose block-end edge exceeds the available
8245 // block-size for children.
8246 if (aBEndEdgeOfChildren >= effectiveContentBoxBSizeWithBStartBP) {
8247 // The unbreakable child's block-end edge forces us to consume all of
8248 // our effective content-box block-size.
8249 finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP;
8251 // Even though we've consumed all of our effective content-box
8252 // block-size, we may still need to report an incomplete status in order
8253 // to get another continuation, which will be responsible for laying out
8254 // & drawing our block-end border & padding. But if we have no such
8255 // border & padding, or if we're forced to apply that border & padding
8256 // on this frame due to box-decoration-break:clone, then we don't need
8257 // to bother with that additional continuation.
8258 isOurStatusComplete = (isBDBClone || blockEndBP == 0);
8259 } else {
8260 // The unbreakable child's block-end edge doesn't force us to consume
8261 // all of our effective content-box block-size.
8262 finalContentBoxBSizeWithBStartBP = aBEndEdgeOfChildren;
8263 isOurStatusComplete = false;
8265 } else {
8266 // The children's block-end edge can fit in the content-box space that we
8267 // have available for it. Consume all the space that is available so that
8268 // our inline-start/inline-end borders extend all the way to the block-end
8269 // edge of column/page.
8270 finalContentBoxBSizeWithBStartBP = maxContentBoxBEnd;
8271 isOurStatusComplete = false;
8275 nscoord finalBorderBoxBSize = finalContentBoxBSizeWithBStartBP;
8276 if (isOurStatusComplete) {
8277 finalBorderBoxBSize = NSCoordSaturatingAdd(finalBorderBoxBSize, blockEndBP);
8278 if (isChildStatusComplete) {
8279 // We want to use children's reflow status as ours, which can be overflow
8280 // incomplete. Suppress the urge to call aBri.mReflowStatus.Reset() here.
8281 } else {
8282 aState.mReflowStatus.SetOverflowIncomplete();
8284 } else {
8285 NS_ASSERTION(!IsTrueOverflowContainer(),
8286 "An overflow container should always be complete because of "
8287 "its zero border-box block-size!");
8288 if (isBDBClone) {
8289 finalBorderBoxBSize =
8290 NSCoordSaturatingAdd(finalBorderBoxBSize, blockEndBP);
8292 aState.mReflowStatus.SetIncomplete();
8293 if (!GetNextInFlow()) {
8294 aState.mReflowStatus.SetNextInFlowNeedsReflow();
8298 return finalBorderBoxBSize;
8301 nsresult nsBlockFrame::ResolveBidi() {
8302 NS_ASSERTION(!GetPrevInFlow(),
8303 "ResolveBidi called on non-first continuation");
8304 MOZ_ASSERT(PresContext()->BidiEnabled());
8305 return nsBidiPresUtils::Resolve(this);
8308 void nsBlockFrame::UpdatePseudoElementStyles(ServoRestyleState& aRestyleState) {
8309 // first-letter needs to be updated before first-line, because first-line can
8310 // change the style of the first-letter.
8311 if (HasFirstLetterChild()) {
8312 UpdateFirstLetterStyle(aRestyleState);
8315 if (nsIFrame* firstLineFrame = GetFirstLineFrame()) {
8316 nsIFrame* styleParent = CorrectStyleParentFrame(firstLineFrame->GetParent(),
8317 PseudoStyleType::firstLine);
8319 ComputedStyle* parentStyle = styleParent->Style();
8320 RefPtr<ComputedStyle> firstLineStyle =
8321 aRestyleState.StyleSet().ResolvePseudoElementStyle(
8322 *mContent->AsElement(), PseudoStyleType::firstLine, nullptr,
8323 parentStyle);
8325 // FIXME(bz): Can we make first-line continuations be non-inheriting anon
8326 // boxes?
8327 RefPtr<ComputedStyle> continuationStyle =
8328 aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle(
8329 PseudoStyleType::mozLineFrame, parentStyle);
8331 UpdateStyleOfOwnedChildFrame(firstLineFrame, firstLineStyle, aRestyleState,
8332 Some(continuationStyle.get()));
8334 // We also want to update the styles of the first-line's descendants. We
8335 // don't need to compute a changehint for this, though, since any changes to
8336 // them are handled by the first-line anyway.
8337 RestyleManager* manager = PresContext()->RestyleManager();
8338 for (nsIFrame* kid : firstLineFrame->PrincipalChildList()) {
8339 manager->ReparentComputedStyleForFirstLine(kid);
8344 nsIFrame* nsBlockFrame::GetFirstLetter() const {
8345 if (!HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE)) {
8346 // Certainly no first-letter frame.
8347 return nullptr;
8350 return GetProperty(FirstLetterProperty());
8353 nsIFrame* nsBlockFrame::GetFirstLineFrame() const {
8354 nsIFrame* maybeFirstLine = PrincipalChildList().FirstChild();
8355 if (maybeFirstLine && maybeFirstLine->IsLineFrame()) {
8356 return maybeFirstLine;
8359 return nullptr;
8362 #ifdef DEBUG
8363 void nsBlockFrame::VerifyLines(bool aFinalCheckOK) {
8364 if (!gVerifyLines) {
8365 return;
8367 if (mLines.empty()) {
8368 return;
8371 nsLineBox* cursor = GetLineCursorForQuery();
8373 // Add up the counts on each line. Also validate that IsFirstLine is
8374 // set properly.
8375 int32_t count = 0;
8376 for (const auto& line : Lines()) {
8377 if (&line == cursor) {
8378 cursor = nullptr;
8380 if (aFinalCheckOK) {
8381 MOZ_ASSERT(line.GetChildCount(), "empty line");
8382 if (line.IsBlock()) {
8383 NS_ASSERTION(1 == line.GetChildCount(), "bad first line");
8386 count += line.GetChildCount();
8389 // Then count the frames
8390 int32_t frameCount = 0;
8391 nsIFrame* frame = mLines.front()->mFirstChild;
8392 while (frame) {
8393 frameCount++;
8394 frame = frame->GetNextSibling();
8396 NS_ASSERTION(count == frameCount, "bad line list");
8398 // Next: test that each line has right number of frames on it
8399 for (LineIterator line = LinesBegin(), line_end = LinesEnd();
8400 line != line_end;) {
8401 count = line->GetChildCount();
8402 frame = line->mFirstChild;
8403 while (--count >= 0) {
8404 frame = frame->GetNextSibling();
8406 ++line;
8407 if ((line != line_end) && (0 != line->GetChildCount())) {
8408 NS_ASSERTION(frame == line->mFirstChild, "bad line list");
8412 if (cursor) {
8413 FrameLines* overflowLines = GetOverflowLines();
8414 if (overflowLines) {
8415 LineIterator line = overflowLines->mLines.begin();
8416 LineIterator line_end = overflowLines->mLines.end();
8417 for (; line != line_end; ++line) {
8418 if (line == cursor) {
8419 cursor = nullptr;
8420 break;
8425 NS_ASSERTION(!cursor, "stale LineCursorProperty");
8428 void nsBlockFrame::VerifyOverflowSituation() {
8429 // Overflow out-of-flows must not have a next-in-flow in mFloats or mFrames.
8430 nsFrameList* oofs = GetOverflowOutOfFlows();
8431 if (oofs) {
8432 for (nsIFrame* f : *oofs) {
8433 nsIFrame* nif = f->GetNextInFlow();
8434 MOZ_ASSERT(!nif ||
8435 (!mFloats.ContainsFrame(nif) && !mFrames.ContainsFrame(nif)));
8439 // Pushed floats must not have a next-in-flow in mFloats or mFrames.
8440 oofs = GetPushedFloats();
8441 if (oofs) {
8442 for (nsIFrame* f : *oofs) {
8443 nsIFrame* nif = f->GetNextInFlow();
8444 MOZ_ASSERT(!nif ||
8445 (!mFloats.ContainsFrame(nif) && !mFrames.ContainsFrame(nif)));
8449 // A child float next-in-flow's parent must be |this| or a next-in-flow of
8450 // |this|. Later next-in-flows must have the same or later parents.
8451 ChildListID childLists[] = {FrameChildListID::Float,
8452 FrameChildListID::PushedFloats};
8453 for (size_t i = 0; i < ArrayLength(childLists); ++i) {
8454 const nsFrameList& children = GetChildList(childLists[i]);
8455 for (nsIFrame* f : children) {
8456 nsIFrame* parent = this;
8457 nsIFrame* nif = f->GetNextInFlow();
8458 for (; nif; nif = nif->GetNextInFlow()) {
8459 bool found = false;
8460 for (nsIFrame* p = parent; p; p = p->GetNextInFlow()) {
8461 if (nif->GetParent() == p) {
8462 parent = p;
8463 found = true;
8464 break;
8467 MOZ_ASSERT(
8468 found,
8469 "next-in-flow is a child of parent earlier in the frame tree?");
8474 nsBlockFrame* flow = static_cast<nsBlockFrame*>(FirstInFlow());
8475 while (flow) {
8476 FrameLines* overflowLines = flow->GetOverflowLines();
8477 if (overflowLines) {
8478 NS_ASSERTION(!overflowLines->mLines.empty(),
8479 "should not be empty if present");
8480 NS_ASSERTION(overflowLines->mLines.front()->mFirstChild,
8481 "bad overflow lines");
8482 NS_ASSERTION(overflowLines->mLines.front()->mFirstChild ==
8483 overflowLines->mFrames.FirstChild(),
8484 "bad overflow frames / lines");
8486 auto checkCursor = [&](nsLineBox* cursor) -> bool {
8487 if (!cursor) {
8488 return true;
8490 LineIterator line = flow->LinesBegin();
8491 LineIterator line_end = flow->LinesEnd();
8492 for (; line != line_end && line != cursor; ++line)
8494 if (line == line_end && overflowLines) {
8495 line = overflowLines->mLines.begin();
8496 line_end = overflowLines->mLines.end();
8497 for (; line != line_end && line != cursor; ++line)
8500 return line != line_end;
8502 MOZ_ASSERT(checkCursor(flow->GetLineCursorForDisplay()),
8503 "stale LineCursorPropertyDisplay");
8504 MOZ_ASSERT(checkCursor(flow->GetLineCursorForQuery()),
8505 "stale LineCursorPropertyQuery");
8506 flow = static_cast<nsBlockFrame*>(flow->GetNextInFlow());
8510 int32_t nsBlockFrame::GetDepth() const {
8511 int32_t depth = 0;
8512 nsIFrame* parent = GetParent();
8513 while (parent) {
8514 parent = parent->GetParent();
8515 depth++;
8517 return depth;
8520 already_AddRefed<ComputedStyle> nsBlockFrame::GetFirstLetterStyle(
8521 nsPresContext* aPresContext) {
8522 return aPresContext->StyleSet()->ProbePseudoElementStyle(
8523 *mContent->AsElement(), PseudoStyleType::firstLetter, nullptr, Style());
8525 #endif