Backed out changeset 496886cb30a5 (bug 1867152) for bc failures on browser_user_input...
[gecko.git] / layout / generic / nsBlockFrame.cpp
blob370aaaff05d83b08cd43afb02e73a81250684a9e
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->IsLineParticipant()) {
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->IsLineParticipant()) {
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 // Whether this line is indented by the text-indent amount.
808 bool nsBlockFrame::TextIndentAppliesTo(const LineIterator& aLine) const {
809 const auto& textIndent = StyleText()->mTextIndent;
811 bool isFirstLineOrAfterHardBreak = [&] {
812 if (aLine != LinesBegin()) {
813 // If not the first line of the block, but 'each-line' is in effect,
814 // check if the previous line was not wrapped.
815 return textIndent.each_line && !aLine.prev()->IsLineWrapped();
817 if (nsBlockFrame* prevBlock = do_QueryFrame(GetPrevInFlow())) {
818 // There's a prev-in-flow, so this only counts as a first-line if
819 // 'each-line' and the prev-in-flow's last line was not wrapped.
820 return textIndent.each_line &&
821 (prevBlock->Lines().empty() ||
822 !prevBlock->LinesEnd().prev()->IsLineWrapped());
824 return true;
825 }();
827 // The 'hanging' option inverts which lines are/aren't indented.
828 return isFirstLineOrAfterHardBreak != textIndent.hanging;
831 /* virtual */
832 nscoord nsBlockFrame::GetMinISize(gfxContext* aRenderingContext) {
833 nsIFrame* firstInFlow = FirstContinuation();
834 if (firstInFlow != this) {
835 return firstInFlow->GetMinISize(aRenderingContext);
838 DISPLAY_MIN_INLINE_SIZE(this, mCachedMinISize);
840 CheckIntrinsicCacheAgainstShrinkWrapState();
842 if (mCachedMinISize != NS_INTRINSIC_ISIZE_UNKNOWN) {
843 return mCachedMinISize;
846 if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
847 mCachedMinISize = *containISize;
848 return mCachedMinISize;
851 #ifdef DEBUG
852 if (gNoisyIntrinsic) {
853 IndentBy(stdout, gNoiseIndent);
854 ListTag(stdout);
855 printf(": GetMinISize\n");
857 AutoNoisyIndenter indenter(gNoisyIntrinsic);
858 #endif
860 for (nsBlockFrame* curFrame = this; curFrame;
861 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
862 curFrame->LazyMarkLinesDirty();
865 if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
866 PresContext()->BidiEnabled()) {
867 ResolveBidi();
870 const bool whiteSpaceCanWrap = StyleText()->WhiteSpaceCanWrapStyle();
871 InlineMinISizeData data;
872 for (nsBlockFrame* curFrame = this; curFrame;
873 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
874 for (LineIterator line = curFrame->LinesBegin(),
875 line_end = curFrame->LinesEnd();
876 line != line_end; ++line) {
877 #ifdef DEBUG
878 if (gNoisyIntrinsic) {
879 IndentBy(stdout, gNoiseIndent);
880 printf("line (%s%s)\n", line->IsBlock() ? "block" : "inline",
881 line->IsEmpty() ? ", empty" : "");
883 AutoNoisyIndenter lineindent(gNoisyIntrinsic);
884 #endif
885 if (line->IsBlock()) {
886 data.ForceBreak();
887 data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(
888 aRenderingContext, line->mFirstChild, IntrinsicISizeType::MinISize);
889 data.ForceBreak();
890 } else {
891 if (!curFrame->GetPrevContinuation() && TextIndentAppliesTo(line)) {
892 data.mCurrentLine += StyleText()->mTextIndent.length.Resolve(0);
894 data.mLine = &line;
895 data.SetLineContainer(curFrame);
896 nsIFrame* kid = line->mFirstChild;
897 for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end;
898 ++i, kid = kid->GetNextSibling()) {
899 kid->AddInlineMinISize(aRenderingContext, &data);
900 if (whiteSpaceCanWrap && data.mTrailingWhitespace) {
901 data.OptionallyBreak();
905 #ifdef DEBUG
906 if (gNoisyIntrinsic) {
907 IndentBy(stdout, gNoiseIndent);
908 printf("min: [prevLines=%d currentLine=%d]\n", data.mPrevLines,
909 data.mCurrentLine);
911 #endif
914 data.ForceBreak();
916 mCachedMinISize = data.mPrevLines;
917 return mCachedMinISize;
920 /* virtual */
921 nscoord nsBlockFrame::GetPrefISize(gfxContext* aRenderingContext) {
922 nsIFrame* firstInFlow = FirstContinuation();
923 if (firstInFlow != this) {
924 return firstInFlow->GetPrefISize(aRenderingContext);
927 DISPLAY_PREF_INLINE_SIZE(this, mCachedPrefISize);
929 CheckIntrinsicCacheAgainstShrinkWrapState();
931 if (mCachedPrefISize != NS_INTRINSIC_ISIZE_UNKNOWN) {
932 return mCachedPrefISize;
935 if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
936 mCachedPrefISize = *containISize;
937 return mCachedPrefISize;
940 #ifdef DEBUG
941 if (gNoisyIntrinsic) {
942 IndentBy(stdout, gNoiseIndent);
943 ListTag(stdout);
944 printf(": GetPrefISize\n");
946 AutoNoisyIndenter indenter(gNoisyIntrinsic);
947 #endif
949 for (nsBlockFrame* curFrame = this; curFrame;
950 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
951 curFrame->LazyMarkLinesDirty();
954 if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
955 PresContext()->BidiEnabled()) {
956 ResolveBidi();
958 InlinePrefISizeData data;
959 for (nsBlockFrame* curFrame = this; curFrame;
960 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
961 for (LineIterator line = curFrame->LinesBegin(),
962 line_end = curFrame->LinesEnd();
963 line != line_end; ++line) {
964 #ifdef DEBUG
965 if (gNoisyIntrinsic) {
966 IndentBy(stdout, gNoiseIndent);
967 printf("line (%s%s)\n", line->IsBlock() ? "block" : "inline",
968 line->IsEmpty() ? ", empty" : "");
970 AutoNoisyIndenter lineindent(gNoisyIntrinsic);
971 #endif
972 if (line->IsBlock()) {
973 StyleClear clearType;
974 if (!data.mLineIsEmpty || BlockCanIntersectFloats(line->mFirstChild)) {
975 clearType = StyleClear::Both;
976 } else {
977 clearType = line->mFirstChild->StyleDisplay()->mClear;
979 data.ForceBreak(clearType);
980 data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(
981 aRenderingContext, line->mFirstChild,
982 IntrinsicISizeType::PrefISize);
983 data.ForceBreak();
984 } else {
985 if (!curFrame->GetPrevContinuation() && TextIndentAppliesTo(line)) {
986 nscoord indent = StyleText()->mTextIndent.length.Resolve(0);
987 data.mCurrentLine += indent;
988 // XXXmats should the test below be indent > 0?
989 if (indent != nscoord(0)) {
990 data.mLineIsEmpty = false;
993 data.mLine = &line;
994 data.SetLineContainer(curFrame);
995 nsIFrame* kid = line->mFirstChild;
996 for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end;
997 ++i, kid = kid->GetNextSibling()) {
998 kid->AddInlinePrefISize(aRenderingContext, &data);
1001 #ifdef DEBUG
1002 if (gNoisyIntrinsic) {
1003 IndentBy(stdout, gNoiseIndent);
1004 printf("pref: [prevLines=%d currentLine=%d]\n", data.mPrevLines,
1005 data.mCurrentLine);
1007 #endif
1010 data.ForceBreak();
1012 mCachedPrefISize = data.mPrevLines;
1013 return mCachedPrefISize;
1016 nsRect nsBlockFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const {
1017 // be conservative
1018 if (Style()->HasTextDecorationLines()) {
1019 return InkOverflowRect();
1021 return ComputeSimpleTightBounds(aDrawTarget);
1024 /* virtual */
1025 nsresult nsBlockFrame::GetPrefWidthTightBounds(gfxContext* aRenderingContext,
1026 nscoord* aX, nscoord* aXMost) {
1027 nsIFrame* firstInFlow = FirstContinuation();
1028 if (firstInFlow != this) {
1029 return firstInFlow->GetPrefWidthTightBounds(aRenderingContext, aX, aXMost);
1032 *aX = 0;
1033 *aXMost = 0;
1035 nsresult rv;
1036 InlinePrefISizeData data;
1037 for (nsBlockFrame* curFrame = this; curFrame;
1038 curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
1039 for (LineIterator line = curFrame->LinesBegin(),
1040 line_end = curFrame->LinesEnd();
1041 line != line_end; ++line) {
1042 nscoord childX, childXMost;
1043 if (line->IsBlock()) {
1044 data.ForceBreak();
1045 rv = line->mFirstChild->GetPrefWidthTightBounds(aRenderingContext,
1046 &childX, &childXMost);
1047 NS_ENSURE_SUCCESS(rv, rv);
1048 *aX = std::min(*aX, childX);
1049 *aXMost = std::max(*aXMost, childXMost);
1050 } else {
1051 if (!curFrame->GetPrevContinuation() && TextIndentAppliesTo(line)) {
1052 data.mCurrentLine += StyleText()->mTextIndent.length.Resolve(0);
1054 data.mLine = &line;
1055 data.SetLineContainer(curFrame);
1056 nsIFrame* kid = line->mFirstChild;
1057 for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end;
1058 ++i, kid = kid->GetNextSibling()) {
1059 rv = kid->GetPrefWidthTightBounds(aRenderingContext, &childX,
1060 &childXMost);
1061 NS_ENSURE_SUCCESS(rv, rv);
1062 *aX = std::min(*aX, data.mCurrentLine + childX);
1063 *aXMost = std::max(*aXMost, data.mCurrentLine + childXMost);
1064 kid->AddInlinePrefISize(aRenderingContext, &data);
1069 data.ForceBreak();
1071 return NS_OK;
1075 * Return whether aNewAvailableSpace is smaller *on either side*
1076 * (inline-start or inline-end) than aOldAvailableSpace, so that we know
1077 * if we need to redo layout on an line, replaced block, or block
1078 * formatting context, because its height (which we used to compute
1079 * aNewAvailableSpace) caused it to intersect additional floats.
1081 static bool AvailableSpaceShrunk(WritingMode aWM,
1082 const LogicalRect& aOldAvailableSpace,
1083 const LogicalRect& aNewAvailableSpace,
1084 bool aCanGrow /* debug-only */) {
1085 if (aNewAvailableSpace.ISize(aWM) == 0) {
1086 // Positions are not significant if the inline size is zero.
1087 return aOldAvailableSpace.ISize(aWM) != 0;
1089 if (aCanGrow) {
1090 NS_ASSERTION(
1091 aNewAvailableSpace.IStart(aWM) <= aOldAvailableSpace.IStart(aWM) ||
1092 aNewAvailableSpace.IEnd(aWM) <= aOldAvailableSpace.IEnd(aWM),
1093 "available space should not shrink on the start side and "
1094 "grow on the end side");
1095 NS_ASSERTION(
1096 aNewAvailableSpace.IStart(aWM) >= aOldAvailableSpace.IStart(aWM) ||
1097 aNewAvailableSpace.IEnd(aWM) >= aOldAvailableSpace.IEnd(aWM),
1098 "available space should not grow on the start side and "
1099 "shrink on the end side");
1100 } else {
1101 NS_ASSERTION(
1102 aOldAvailableSpace.IStart(aWM) <= aNewAvailableSpace.IStart(aWM) &&
1103 aOldAvailableSpace.IEnd(aWM) >= aNewAvailableSpace.IEnd(aWM),
1104 "available space should never grow");
1106 // Have we shrunk on either side?
1107 return aNewAvailableSpace.IStart(aWM) > aOldAvailableSpace.IStart(aWM) ||
1108 aNewAvailableSpace.IEnd(aWM) < aOldAvailableSpace.IEnd(aWM);
1111 static LogicalSize CalculateContainingBlockSizeForAbsolutes(
1112 WritingMode aWM, const ReflowInput& aReflowInput, LogicalSize aFrameSize) {
1113 // The issue here is that for a 'height' of 'auto' the reflow input
1114 // code won't know how to calculate the containing block height
1115 // because it's calculated bottom up. So we use our own computed
1116 // size as the dimensions.
1117 nsIFrame* frame = aReflowInput.mFrame;
1119 LogicalSize cbSize(aFrameSize);
1120 // Containing block is relative to the padding edge
1121 const LogicalMargin border = aReflowInput.ComputedLogicalBorder(aWM);
1122 cbSize.ISize(aWM) -= border.IStartEnd(aWM);
1123 cbSize.BSize(aWM) -= border.BStartEnd(aWM);
1125 if (frame->GetParent()->GetContent() != frame->GetContent() ||
1126 frame->GetParent()->IsCanvasFrame()) {
1127 return cbSize;
1130 // We are a wrapped frame for the content (and the wrapper is not the
1131 // canvas frame, whose size is not meaningful here).
1132 // Use the container's dimensions, if they have been precomputed.
1133 // XXX This is a hack! We really should be waiting until the outermost
1134 // frame is fully reflowed and using the resulting dimensions, even
1135 // if they're intrinsic.
1136 // In fact we should be attaching absolute children to the outermost
1137 // frame and not always sticking them in block frames.
1139 // First, find the reflow input for the outermost frame for this content.
1140 const ReflowInput* lastRI = &aReflowInput;
1141 DebugOnly<const ReflowInput*> lastButOneRI = &aReflowInput;
1142 while (lastRI->mParentReflowInput &&
1143 lastRI->mParentReflowInput->mFrame->GetContent() ==
1144 frame->GetContent()) {
1145 lastButOneRI = lastRI;
1146 lastRI = lastRI->mParentReflowInput;
1149 if (lastRI == &aReflowInput) {
1150 return cbSize;
1153 // For scroll containers, we can just use cbSize (which is the padding-box
1154 // size of the scrolled-content frame).
1155 if (nsIScrollableFrame* scrollFrame = do_QueryFrame(lastRI->mFrame)) {
1156 // Assert that we're not missing any frames between the abspos containing
1157 // block and the scroll container.
1158 // the parent.
1159 Unused << scrollFrame;
1160 MOZ_ASSERT(lastButOneRI == &aReflowInput);
1161 return cbSize;
1164 // Same for fieldsets, where the inner anonymous frame has the correct padding
1165 // area with the legend taken into account.
1166 if (lastRI->mFrame->IsFieldSetFrame()) {
1167 return cbSize;
1170 // We found a reflow input for the outermost wrapping frame, so use
1171 // its computed metrics if available, converted to our writing mode
1172 const LogicalSize lastRISize = lastRI->ComputedSize(aWM);
1173 const LogicalMargin lastRIPadding = lastRI->ComputedLogicalPadding(aWM);
1174 if (lastRISize.ISize(aWM) != NS_UNCONSTRAINEDSIZE) {
1175 cbSize.ISize(aWM) =
1176 std::max(0, lastRISize.ISize(aWM) + lastRIPadding.IStartEnd(aWM));
1178 if (lastRISize.BSize(aWM) != NS_UNCONSTRAINEDSIZE) {
1179 cbSize.BSize(aWM) =
1180 std::max(0, lastRISize.BSize(aWM) + lastRIPadding.BStartEnd(aWM));
1183 return cbSize;
1187 * Returns aFrame if it is a non-BFC block frame, and null otherwise.
1189 * This is used to determine whether to recurse into aFrame when applying
1190 * -webkit-line-clamp.
1192 static const nsBlockFrame* GetAsLineClampDescendant(const nsIFrame* aFrame) {
1193 if (const nsBlockFrame* block = do_QueryFrame(aFrame)) {
1194 if (!block->HasAllStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS)) {
1195 return block;
1198 return nullptr;
1201 static nsBlockFrame* GetAsLineClampDescendant(nsIFrame* aFrame) {
1202 return const_cast<nsBlockFrame*>(
1203 GetAsLineClampDescendant(const_cast<const nsIFrame*>(aFrame)));
1206 static bool IsLineClampRoot(const nsBlockFrame* aFrame) {
1207 if (!aFrame->StyleDisplay()->mWebkitLineClamp) {
1208 return false;
1211 if (!aFrame->HasAllStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS)) {
1212 return false;
1215 if (StaticPrefs::layout_css_webkit_line_clamp_block_enabled()) {
1216 return true;
1219 // For now, -webkit-box is the only thing allowed to be a line-clamp root.
1220 // Ideally we'd just make this work everywhere, but for now we're carrying
1221 // this forward as a limitation on the legacy -webkit-line-clamp feature,
1222 // since relaxing this limitation might create webcompat trouble.
1223 auto origDisplay = [&] {
1224 if (aFrame->Style()->GetPseudoType() == PseudoStyleType::scrolledContent) {
1225 // If we're the anonymous block inside the scroll frame, we need to look
1226 // at the original display of our parent frame.
1227 MOZ_ASSERT(aFrame->GetParent());
1228 const auto& parentDisp = *aFrame->GetParent()->StyleDisplay();
1229 MOZ_ASSERT(parentDisp.mWebkitLineClamp ==
1230 aFrame->StyleDisplay()->mWebkitLineClamp,
1231 ":-moz-scrolled-content should inherit -webkit-line-clamp, "
1232 "via rule in UA stylesheet");
1233 return parentDisp.mOriginalDisplay;
1235 return aFrame->StyleDisplay()->mOriginalDisplay;
1236 }();
1237 return origDisplay.Inside() == StyleDisplayInside::WebkitBox;
1240 bool nsBlockFrame::IsInLineClampContext() const {
1241 if (IsLineClampRoot(this)) {
1242 return true;
1244 const nsBlockFrame* cur = this;
1245 while (GetAsLineClampDescendant(cur)) {
1246 cur = do_QueryFrame(cur->GetParent());
1247 if (!cur) {
1248 return false;
1250 if (IsLineClampRoot(cur)) {
1251 return true;
1254 return false;
1258 * Iterator over all descendant inline line boxes, except for those that are
1259 * under an independent formatting context.
1261 class MOZ_RAII LineClampLineIterator {
1262 public:
1263 explicit LineClampLineIterator(nsBlockFrame* aFrame)
1264 : mCur(aFrame->LinesBegin()),
1265 mEnd(aFrame->LinesEnd()),
1266 mCurrentFrame(mCur == mEnd ? nullptr : aFrame) {
1267 if (mCur != mEnd && !mCur->IsInline()) {
1268 Advance();
1272 nsLineBox* GetCurrentLine() { return mCurrentFrame ? mCur.get() : nullptr; }
1273 nsBlockFrame* GetCurrentFrame() { return mCurrentFrame; }
1275 // Advances the iterator to the next line line.
1277 // Next() shouldn't be called once the iterator is at the end, which can be
1278 // checked for by GetCurrentLine() or GetCurrentFrame() returning null.
1279 void Next() {
1280 MOZ_ASSERT(mCur != mEnd && mCurrentFrame,
1281 "Don't call Next() when the iterator is at the end");
1282 ++mCur;
1283 Advance();
1286 private:
1287 void Advance() {
1288 for (;;) {
1289 if (mCur == mEnd) {
1290 // Reached the end of the current block. Pop the parent off the
1291 // stack; if there isn't one, then we've reached the end.
1292 if (mStack.IsEmpty()) {
1293 mCurrentFrame = nullptr;
1294 break;
1296 auto entry = mStack.PopLastElement();
1297 mCurrentFrame = entry.first;
1298 mCur = entry.second;
1299 mEnd = mCurrentFrame->LinesEnd();
1300 } else if (mCur->IsBlock()) {
1301 if (nsBlockFrame* child = GetAsLineClampDescendant(mCur->mFirstChild)) {
1302 nsBlockFrame::LineIterator next = mCur;
1303 ++next;
1304 mStack.AppendElement(std::make_pair(mCurrentFrame, next));
1305 mCur = child->LinesBegin();
1306 mEnd = child->LinesEnd();
1307 mCurrentFrame = child;
1308 } else {
1309 // Some kind of frame we shouldn't descend into.
1310 ++mCur;
1312 } else {
1313 MOZ_ASSERT(mCur->IsInline());
1314 break;
1319 // The current line within the current block.
1321 // When this is equal to mEnd, the iterator is at its end, and mCurrentFrame
1322 // is set to null.
1323 nsBlockFrame::LineIterator mCur;
1325 // The iterator end for the current block.
1326 nsBlockFrame::LineIterator mEnd;
1328 // The current block.
1329 nsBlockFrame* mCurrentFrame;
1331 // Stack of mCurrentFrame and mEnd values that we push and pop as we enter and
1332 // exist blocks.
1333 AutoTArray<std::pair<nsBlockFrame*, nsBlockFrame::LineIterator>, 8> mStack;
1336 static bool ClearLineClampEllipsis(nsBlockFrame* aFrame) {
1337 if (!aFrame->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS)) {
1338 for (nsIFrame* f : aFrame->PrincipalChildList()) {
1339 if (nsBlockFrame* child = GetAsLineClampDescendant(f)) {
1340 if (ClearLineClampEllipsis(child)) {
1341 return true;
1345 return false;
1348 aFrame->RemoveStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS);
1350 for (auto& line : aFrame->Lines()) {
1351 if (line.HasLineClampEllipsis()) {
1352 line.ClearHasLineClampEllipsis();
1353 return true;
1357 // We didn't find a line with the ellipsis; it must have been deleted already.
1358 return true;
1361 void nsBlockFrame::ClearLineClampEllipsis() { ::ClearLineClampEllipsis(this); }
1363 void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
1364 const ReflowInput& aReflowInput,
1365 nsReflowStatus& aStatus) {
1366 if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
1367 FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
1368 return;
1371 MarkInReflow();
1372 DO_GLOBAL_REFLOW_COUNT("nsBlockFrame");
1373 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
1374 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
1376 #ifdef DEBUG
1377 if (gNoisyReflow) {
1378 IndentBy(stdout, gNoiseIndent);
1379 ListTag(stdout);
1380 printf(": begin reflow availSize=%d,%d computedSize=%d,%d\n",
1381 aReflowInput.AvailableISize(), aReflowInput.AvailableBSize(),
1382 aReflowInput.ComputedISize(), aReflowInput.ComputedBSize());
1384 AutoNoisyIndenter indent(gNoisy);
1385 PRTime start = 0; // Initialize these variablies to silence the compiler.
1386 int32_t ctc = 0; // We only use these if they are set (gLameReflowMetrics).
1387 if (gLameReflowMetrics) {
1388 start = PR_Now();
1389 ctc = nsLineBox::GetCtorCount();
1391 #endif
1393 // ColumnSetWrapper's children depend on ColumnSetWrapper's block-size or
1394 // max-block-size because both affect the children's available block-size.
1395 if (IsColumnSetWrapperFrame()) {
1396 AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
1399 Maybe<nscoord> restoreReflowInputAvailBSize;
1400 auto MaybeRestore = MakeScopeExit([&] {
1401 if (MOZ_UNLIKELY(restoreReflowInputAvailBSize)) {
1402 const_cast<ReflowInput&>(aReflowInput)
1403 .SetAvailableBSize(*restoreReflowInputAvailBSize);
1407 WritingMode wm = aReflowInput.GetWritingMode();
1408 const nscoord consumedBSize = CalcAndCacheConsumedBSize();
1409 const nscoord effectiveContentBoxBSize =
1410 GetEffectiveComputedBSize(aReflowInput, consumedBSize);
1411 // If we have non-auto block size, we're clipping our kids and we fit,
1412 // make sure our kids fit too.
1413 const PhysicalAxes physicalBlockAxis =
1414 wm.IsVertical() ? PhysicalAxes::Horizontal : PhysicalAxes::Vertical;
1415 if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
1416 aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE &&
1417 (ShouldApplyOverflowClipping(aReflowInput.mStyleDisplay) &
1418 physicalBlockAxis)) {
1419 LogicalMargin blockDirExtras =
1420 aReflowInput.ComputedLogicalBorderPadding(wm);
1421 if (GetLogicalSkipSides().BStart()) {
1422 blockDirExtras.BStart(wm) = 0;
1423 } else {
1424 // Block-end margin never causes us to create continuations, so we
1425 // don't need to worry about whether it fits in its entirety.
1426 blockDirExtras.BStart(wm) +=
1427 aReflowInput.ComputedLogicalMargin(wm).BStart(wm);
1430 if (effectiveContentBoxBSize + blockDirExtras.BStartEnd(wm) <=
1431 aReflowInput.AvailableBSize()) {
1432 restoreReflowInputAvailBSize.emplace(aReflowInput.AvailableBSize());
1433 const_cast<ReflowInput&>(aReflowInput)
1434 .SetAvailableBSize(NS_UNCONSTRAINEDSIZE);
1438 if (IsFrameTreeTooDeep(aReflowInput, aMetrics, aStatus)) {
1439 return;
1442 // OK, some lines may be reflowed. Blow away any saved line cursor
1443 // because we may invalidate the nondecreasing
1444 // overflowArea.InkOverflow().y/yMost invariant, and we may even
1445 // delete the line with the line cursor.
1446 ClearLineCursors();
1448 // See comment below about oldSize. Use *only* for the
1449 // abs-pos-containing-block-size-change optimization!
1450 nsSize oldSize = GetSize();
1452 // Should we create a float manager?
1453 nsAutoFloatManager autoFloatManager(const_cast<ReflowInput&>(aReflowInput));
1455 // XXXldb If we start storing the float manager in the frame rather
1456 // than keeping it around only during reflow then we should create it
1457 // only when there are actually floats to manage. Otherwise things
1458 // like tables will gain significant bloat.
1459 bool needFloatManager = nsBlockFrame::BlockNeedsFloatManager(this);
1460 if (needFloatManager) {
1461 autoFloatManager.CreateFloatManager(aPresContext);
1464 if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
1465 PresContext()->BidiEnabled()) {
1466 static_cast<nsBlockFrame*>(FirstContinuation())->ResolveBidi();
1469 // Whether to apply text-wrap: balance behavior.
1470 bool tryBalance = StyleText()->mTextWrap == StyleTextWrap::Balance &&
1471 !GetPrevContinuation();
1473 // Struct used to hold the "target" number of lines or clamp position to
1474 // maintain when doing text-wrap: balance.
1475 struct BalanceTarget {
1476 // If line-clamp is in effect, mContent and mOffset indicate the starting
1477 // position of the first line after the clamp limit. If line-clamp is not
1478 // in use, mContent is null and mOffset is the total number of lines that
1479 // the block must contain.
1480 nsIContent* mContent = nullptr;
1481 int32_t mOffset = -1;
1483 bool operator==(const BalanceTarget& aOther) const {
1484 return mContent == aOther.mContent && mOffset == aOther.mOffset;
1486 bool operator!=(const BalanceTarget& aOther) const {
1487 return !(*this == aOther);
1491 BalanceTarget balanceTarget;
1493 // Helpers for text-wrap: balance implementation:
1495 // Count the number of lines in the mLines list, but return -1 (to suppress
1496 // balancing) instead if the count is going to exceed aLimit, or if we
1497 // encounter a block.
1498 auto countLinesUpTo = [&](int32_t aLimit) -> int32_t {
1499 int32_t n = 0;
1500 for (auto iter = mLines.begin(); iter != mLines.end(); ++iter) {
1501 if (++n > aLimit || iter->IsBlock()) {
1502 return -1;
1505 return n;
1508 // Return a BalanceTarget record representing the position at which line-clamp
1509 // will take effect for the current line list. Only to be used when there are
1510 // enough lines that the clamp will apply.
1511 auto getClampPosition = [&](uint32_t aClampCount) -> BalanceTarget {
1512 MOZ_ASSERT(aClampCount < mLines.size());
1513 auto iter = mLines.begin();
1514 for (uint32_t i = 0; i < aClampCount; i++) {
1515 ++iter;
1517 nsIFrame* firstChild = iter->mFirstChild;
1518 if (!firstChild) {
1519 return BalanceTarget{};
1521 nsIContent* content = firstChild->GetContent();
1522 if (!content) {
1523 return BalanceTarget{};
1525 int32_t offset = 0;
1526 if (firstChild->IsTextFrame()) {
1527 auto* textFrame = static_cast<nsTextFrame*>(firstChild);
1528 offset = textFrame->GetContentOffset();
1530 return BalanceTarget{content, offset};
1533 // "balancing" is implemented by shortening the effective inline-size of the
1534 // lines, so that content will tend to be pushed down to fill later lines of
1535 // the block. `balanceInset` is the current amount of "inset" to apply, and
1536 // `balanceStep` is the increment to adjust it by for the next iteration.
1537 nscoord balanceStep = 0;
1539 // text-wrap: balance loop, executed only once if balancing is not required.
1540 nsReflowStatus reflowStatus;
1541 TrialReflowState trialState(consumedBSize, effectiveContentBoxBSize,
1542 needFloatManager);
1543 while (true) {
1544 // Save the initial floatManager state for repeated trial reflows.
1545 // We'll restore (and re-save) the initial state each time we repeat the
1546 // reflow.
1547 nsFloatManager::SavedState floatManagerState;
1548 aReflowInput.mFloatManager->PushState(&floatManagerState);
1550 aMetrics = ReflowOutput(aMetrics.GetWritingMode());
1551 reflowStatus =
1552 TrialReflow(aPresContext, aMetrics, aReflowInput, trialState);
1554 // Do we need to start a `text-wrap: balance` iteration?
1555 if (tryBalance) {
1556 tryBalance = false;
1557 // Don't try to balance an incomplete block.
1558 if (!reflowStatus.IsFullyComplete()) {
1559 break;
1561 balanceTarget.mOffset =
1562 countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit());
1563 if (balanceTarget.mOffset < 2) {
1564 // If there are less than 2 lines, or the number exceeds the limit,
1565 // no balancing is needed; just break from the balance loop.
1566 break;
1568 // Initialize the amount of inset to try, and the iteration step size.
1569 balanceStep = aReflowInput.ComputedISize() / balanceTarget.mOffset;
1570 trialState.ResetForBalance(balanceStep);
1571 balanceStep /= 2;
1573 // If -webkit-line-clamp is in effect, then we need to maintain the
1574 // content location at which clamping occurs, rather than the total
1575 // number of lines in the block.
1576 if (StaticPrefs::layout_css_text_wrap_balance_after_clamp_enabled() &&
1577 IsLineClampRoot(this)) {
1578 uint32_t lineClampCount = aReflowInput.mStyleDisplay->mWebkitLineClamp;
1579 if (uint32_t(balanceTarget.mOffset) > lineClampCount) {
1580 auto t = getClampPosition(lineClampCount);
1581 if (t.mContent) {
1582 balanceTarget = t;
1587 // Restore initial floatManager state for a new trial with updated inset.
1588 aReflowInput.mFloatManager->PopState(&floatManagerState);
1589 continue;
1592 // Helper to determine whether the current trial succeeded (i.e. was able
1593 // to fit the content into the expected number of lines).
1594 auto trialSucceeded = [&]() -> bool {
1595 if (!reflowStatus.IsFullyComplete()) {
1596 return false;
1598 if (balanceTarget.mContent) {
1599 auto t = getClampPosition(aReflowInput.mStyleDisplay->mWebkitLineClamp);
1600 return t == balanceTarget;
1602 int32_t numLines =
1603 countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit());
1604 return numLines == balanceTarget.mOffset;
1607 // If we're in the process of a balance operation, check whether we've
1608 // inset by too much and either increase or reduce the inset for the next
1609 // iteration.
1610 if (balanceStep > 0) {
1611 if (trialSucceeded()) {
1612 trialState.ResetForBalance(balanceStep);
1613 } else {
1614 trialState.ResetForBalance(-balanceStep);
1616 balanceStep /= 2;
1618 aReflowInput.mFloatManager->PopState(&floatManagerState);
1619 continue;
1622 // If we were attempting to balance, check whether the final iteration was
1623 // successful, and if not, back up by one step.
1624 if (balanceTarget.mOffset >= 0) {
1625 if (trialSucceeded()) {
1626 break;
1628 trialState.ResetForBalance(-1);
1630 aReflowInput.mFloatManager->PopState(&floatManagerState);
1631 continue;
1634 // If we reach here, no balancing was required, so just exit; we don't
1635 // reset (pop) the floatManager state because this is the reflow we're
1636 // going to keep. So the saved state is just dropped.
1637 break;
1638 } // End of text-wrap: balance retry loop
1640 // If the block direction is right-to-left, we need to update the bounds of
1641 // lines that were placed relative to mContainerSize during reflow, as
1642 // we typically do not know the true container size until we've reflowed all
1643 // its children. So we use a dummy mContainerSize during reflow (see
1644 // BlockReflowState's constructor) and then fix up the positions of the
1645 // lines here, once the final block size is known.
1647 // Note that writing-mode:vertical-rl is the only case where the block
1648 // logical direction progresses in a negative physical direction, and
1649 // therefore block-dir coordinate conversion depends on knowing the width
1650 // of the coordinate space in order to translate between the logical and
1651 // physical origins.
1652 if (aReflowInput.GetWritingMode().IsVerticalRL()) {
1653 nsSize containerSize = aMetrics.PhysicalSize();
1654 nscoord deltaX = containerSize.width - trialState.mContainerWidth;
1655 if (deltaX != 0) {
1656 // We compute our lines and markers' overflow areas later in
1657 // ComputeOverflowAreas(), so we don't need to adjust their overflow areas
1658 // here.
1659 const nsPoint physicalDelta(deltaX, 0);
1660 for (auto& line : Lines()) {
1661 UpdateLineContainerSize(&line, containerSize);
1663 trialState.mFcBounds.Clear();
1664 for (nsIFrame* f : mFloats) {
1665 f->MovePositionBy(physicalDelta);
1666 ConsiderChildOverflow(trialState.mFcBounds, f);
1668 nsFrameList* markerList = GetOutsideMarkerList();
1669 if (markerList) {
1670 for (nsIFrame* f : *markerList) {
1671 f->MovePositionBy(physicalDelta);
1674 if (nsFrameList* overflowContainers = GetOverflowContainers()) {
1675 trialState.mOcBounds.Clear();
1676 for (nsIFrame* f : *overflowContainers) {
1677 f->MovePositionBy(physicalDelta);
1678 ConsiderChildOverflow(trialState.mOcBounds, f);
1684 aMetrics.SetOverflowAreasToDesiredBounds();
1685 ComputeOverflowAreas(aMetrics.mOverflowAreas,
1686 trialState.mBlockEndEdgeOfChildren,
1687 aReflowInput.mStyleDisplay);
1688 // Factor overflow container child bounds into the overflow area
1689 aMetrics.mOverflowAreas.UnionWith(trialState.mOcBounds);
1690 // Factor pushed float child bounds into the overflow area
1691 aMetrics.mOverflowAreas.UnionWith(trialState.mFcBounds);
1693 // Let the absolutely positioned container reflow any absolutely positioned
1694 // child frames that need to be reflowed, e.g., elements with a percentage
1695 // based width/height
1696 // We want to do this under either of two conditions:
1697 // 1. If we didn't do the incremental reflow above.
1698 // 2. If our size changed.
1699 // Even though it's the padding edge that's the containing block, we
1700 // can use our rect (the border edge) since if the border style
1701 // changed, the reflow would have been targeted at us so we'd satisfy
1702 // condition 1.
1703 // XXX checking oldSize is bogus, there are various reasons we might have
1704 // reflowed but our size might not have been changed to what we
1705 // asked for (e.g., we ended up being pushed to a new page)
1706 // When WillReflowAgainForClearance is true, we will reflow again without
1707 // resetting the size. Because of this, we must not reflow our abs-pos
1708 // children in that situation --- what we think is our "new size" will not be
1709 // our real new size. This also happens to be more efficient.
1710 WritingMode parentWM = aMetrics.GetWritingMode();
1711 if (HasAbsolutelyPositionedChildren()) {
1712 nsAbsoluteContainingBlock* absoluteContainer = GetAbsoluteContainingBlock();
1713 bool haveInterrupt = aPresContext->HasPendingInterrupt();
1714 if (aReflowInput.WillReflowAgainForClearance() || haveInterrupt) {
1715 // Make sure that when we reflow again we'll actually reflow all the abs
1716 // pos frames that might conceivably depend on our size (or all of them,
1717 // if we're dirty right now and interrupted; in that case we also need
1718 // to mark them all with NS_FRAME_IS_DIRTY). Sadly, we can't do much
1719 // better than that, because we don't really know what our size will be,
1720 // and it might in fact not change on the followup reflow!
1721 if (haveInterrupt && HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
1722 absoluteContainer->MarkAllFramesDirty();
1723 } else {
1724 absoluteContainer->MarkSizeDependentFramesDirty();
1726 if (haveInterrupt) {
1727 // We're not going to reflow absolute frames; make sure to account for
1728 // their existing overflow areas, which is usually a side effect of this
1729 // reflow.
1731 // TODO(emilio): nsAbsoluteContainingBlock::Reflow already checks for
1732 // interrupt, can we just rely on it and unconditionally take the else
1733 // branch below? That's a bit more subtle / risky, since I don't see
1734 // what would reflow them in that case if they depended on our size.
1735 for (nsIFrame* kid = absoluteContainer->GetChildList().FirstChild();
1736 kid; kid = kid->GetNextSibling()) {
1737 ConsiderChildOverflow(aMetrics.mOverflowAreas, kid);
1740 } else {
1741 LogicalSize containingBlockSize =
1742 CalculateContainingBlockSizeForAbsolutes(parentWM, aReflowInput,
1743 aMetrics.Size(parentWM));
1745 // Mark frames that depend on changes we just made to this frame as dirty:
1746 // Now we can assume that the padding edge hasn't moved.
1747 // We need to reflow the absolutes if one of them depends on
1748 // its placeholder position, or the containing block size in a
1749 // direction in which the containing block size might have
1750 // changed.
1752 // XXX "width" and "height" in this block will become ISize and BSize
1753 // when nsAbsoluteContainingBlock is logicalized
1754 bool cbWidthChanged = aMetrics.Width() != oldSize.width;
1755 bool isRoot = !GetContent()->GetParent();
1756 // If isRoot and we have auto height, then we are the initial
1757 // containing block and the containing block height is the
1758 // viewport height, which can't change during incremental
1759 // reflow.
1760 bool cbHeightChanged =
1761 !(isRoot && NS_UNCONSTRAINEDSIZE == aReflowInput.ComputedHeight()) &&
1762 aMetrics.Height() != oldSize.height;
1764 nsRect containingBlock(nsPoint(0, 0),
1765 containingBlockSize.GetPhysicalSize(parentWM));
1766 AbsPosReflowFlags flags = AbsPosReflowFlags::ConstrainHeight;
1767 if (cbWidthChanged) {
1768 flags |= AbsPosReflowFlags::CBWidthChanged;
1770 if (cbHeightChanged) {
1771 flags |= AbsPosReflowFlags::CBHeightChanged;
1773 // Setup the line cursor here to optimize line searching for
1774 // calculating hypothetical position of absolutely-positioned
1775 // frames.
1776 SetupLineCursorForQuery();
1777 absoluteContainer->Reflow(this, aPresContext, aReflowInput, reflowStatus,
1778 containingBlock, flags,
1779 &aMetrics.mOverflowAreas);
1783 FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
1785 aStatus = reflowStatus;
1787 #ifdef DEBUG
1788 // Between when we drain pushed floats and when we complete reflow,
1789 // we're allowed to have multiple continuations of the same float on
1790 // our floats list, since a first-in-flow might get pushed to a later
1791 // continuation of its containing block. But it's not permitted
1792 // outside that time.
1793 nsLayoutUtils::AssertNoDuplicateContinuations(this, mFloats);
1795 if (gNoisyReflow) {
1796 IndentBy(stdout, gNoiseIndent);
1797 ListTag(stdout);
1798 printf(": status=%s metrics=%d,%d carriedMargin=%d",
1799 ToString(aStatus).c_str(), aMetrics.ISize(parentWM),
1800 aMetrics.BSize(parentWM), aMetrics.mCarriedOutBEndMargin.get());
1801 if (HasOverflowAreas()) {
1802 printf(" overflow-vis={%d,%d,%d,%d}", aMetrics.InkOverflow().x,
1803 aMetrics.InkOverflow().y, aMetrics.InkOverflow().width,
1804 aMetrics.InkOverflow().height);
1805 printf(" overflow-scr={%d,%d,%d,%d}", aMetrics.ScrollableOverflow().x,
1806 aMetrics.ScrollableOverflow().y,
1807 aMetrics.ScrollableOverflow().width,
1808 aMetrics.ScrollableOverflow().height);
1810 printf("\n");
1813 if (gLameReflowMetrics) {
1814 PRTime end = PR_Now();
1816 int32_t ectc = nsLineBox::GetCtorCount();
1817 int32_t numLines = mLines.size();
1818 if (!numLines) {
1819 numLines = 1;
1821 PRTime delta, perLineDelta, lines;
1822 lines = int64_t(numLines);
1823 delta = end - start;
1824 perLineDelta = delta / lines;
1826 ListTag(stdout);
1827 char buf[400];
1828 SprintfLiteral(buf,
1829 ": %" PRId64 " elapsed (%" PRId64
1830 " per line) (%d lines; %d new lines)",
1831 delta, perLineDelta, numLines, ectc - ctc);
1832 printf("%s\n", buf);
1834 #endif
1837 nsReflowStatus nsBlockFrame::TrialReflow(nsPresContext* aPresContext,
1838 ReflowOutput& aMetrics,
1839 const ReflowInput& aReflowInput,
1840 TrialReflowState& aTrialState) {
1841 #ifdef DEBUG
1842 // Between when we drain pushed floats and when we complete reflow,
1843 // we're allowed to have multiple continuations of the same float on
1844 // our floats list, since a first-in-flow might get pushed to a later
1845 // continuation of its containing block. But it's not permitted
1846 // outside that time.
1847 nsLayoutUtils::AssertNoDuplicateContinuations(this, mFloats);
1848 #endif
1850 // ALWAYS drain overflow. We never want to leave the previnflow's
1851 // overflow lines hanging around; block reflow depends on the
1852 // overflow line lists being cleared out between reflow passes.
1853 DrainOverflowLines();
1855 bool blockStartMarginRoot, blockEndMarginRoot;
1856 IsMarginRoot(&blockStartMarginRoot, &blockEndMarginRoot);
1858 BlockReflowState state(aReflowInput, aPresContext, this, blockStartMarginRoot,
1859 blockEndMarginRoot, aTrialState.mNeedFloatManager,
1860 aTrialState.mConsumedBSize,
1861 aTrialState.mEffectiveContentBoxBSize,
1862 aTrialState.mInset);
1864 // Handle paginated overflow (see nsContainerFrame.h)
1865 nsReflowStatus ocStatus;
1866 if (GetPrevInFlow()) {
1867 ReflowOverflowContainerChildren(
1868 aPresContext, aReflowInput, aTrialState.mOcBounds,
1869 ReflowChildFlags::Default, ocStatus, DefaultChildFrameMerge,
1870 Some(state.ContainerSize()));
1873 // Now that we're done cleaning up our overflow container lists, we can
1874 // give |state| its nsOverflowContinuationTracker.
1875 nsOverflowContinuationTracker tracker(this, false);
1876 state.mOverflowTracker = &tracker;
1878 // Drain & handle pushed floats
1879 DrainPushedFloats();
1880 ReflowPushedFloats(state, aTrialState.mFcBounds);
1882 // If we're not dirty (which means we'll mark everything dirty later)
1883 // and our inline-size has changed, mark the lines dirty that we need to
1884 // mark dirty for a resize reflow.
1885 if (!HasAnyStateBits(NS_FRAME_IS_DIRTY) && aReflowInput.IsIResize()) {
1886 PrepareResizeReflow(state);
1889 // The same for percentage text-indent, except conditioned on the
1890 // parent resizing.
1891 if (!HasAnyStateBits(NS_FRAME_IS_DIRTY) && aReflowInput.mCBReflowInput &&
1892 aReflowInput.mCBReflowInput->IsIResize() &&
1893 StyleText()->mTextIndent.length.HasPercent() && !mLines.empty()) {
1894 mLines.front()->MarkDirty();
1897 // For text-wrap:balance trials, we need to reflow all the lines even if
1898 // they're not all "dirty".
1899 if (aTrialState.mBalancing) {
1900 MarkAllDescendantLinesDirty(this);
1901 } else {
1902 LazyMarkLinesDirty();
1905 // Now reflow...
1906 ReflowDirtyLines(state);
1908 // If we have a next-in-flow, and that next-in-flow has pushed floats from
1909 // this frame from a previous iteration of reflow, then we should not return
1910 // a status with IsFullyComplete() equals to true, since we actually have
1911 // overflow, it's just already been handled.
1913 // NOTE: This really shouldn't happen, since we _should_ pull back our floats
1914 // and reflow them, but just in case it does, this is a safety precaution so
1915 // we don't end up with a placeholder pointing to frames that have already
1916 // been deleted as part of removing our next-in-flow.
1917 if (state.mReflowStatus.IsFullyComplete()) {
1918 nsBlockFrame* nif = static_cast<nsBlockFrame*>(GetNextInFlow());
1919 while (nif) {
1920 if (nif->HasPushedFloatsFromPrevContinuation()) {
1921 if (nif->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
1922 state.mReflowStatus.SetOverflowIncomplete();
1923 } else {
1924 state.mReflowStatus.SetIncomplete();
1926 break;
1929 nif = static_cast<nsBlockFrame*>(nif->GetNextInFlow());
1933 state.mReflowStatus.MergeCompletionStatusFrom(ocStatus);
1935 // If we end in a BR with clear and affected floats continue,
1936 // we need to continue, too.
1937 if (NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize() &&
1938 state.mReflowStatus.IsComplete() &&
1939 state.FloatManager()->ClearContinues(FindTrailingClear())) {
1940 state.mReflowStatus.SetIncomplete();
1943 if (!state.mReflowStatus.IsFullyComplete()) {
1944 if (HasOverflowLines() || HasPushedFloats()) {
1945 state.mReflowStatus.SetNextInFlowNeedsReflow();
1948 #ifdef DEBUG_kipp
1949 ListTag(stdout);
1950 printf(": block is not fully complete\n");
1951 #endif
1954 // Place the ::marker's frame if it is placed next to a block child.
1956 // According to the CSS2 spec, section 12.6.1, the ::marker's box
1957 // participates in the height calculation of the list-item box's
1958 // first line box.
1960 // There are exactly two places a ::marker can be placed: near the
1961 // first or second line. It's only placed on the second line in a
1962 // rare case: an empty first line followed by a second line that
1963 // contains a block (example: <LI>\n<P>... ). This is where
1964 // the second case can happen.
1965 if (HasOutsideMarker() && !mLines.empty() &&
1966 (mLines.front()->IsBlock() ||
1967 (0 == mLines.front()->BSize() && mLines.front() != mLines.back() &&
1968 mLines.begin().next()->IsBlock()))) {
1969 // Reflow the ::marker's frame.
1970 ReflowOutput reflowOutput(aReflowInput);
1971 // XXX Use the entire line when we fix bug 25888.
1972 nsLayoutUtils::LinePosition position;
1973 WritingMode wm = aReflowInput.GetWritingMode();
1974 bool havePosition =
1975 nsLayoutUtils::GetFirstLinePosition(wm, this, &position);
1976 nscoord lineBStart =
1977 havePosition ? position.mBStart
1978 : aReflowInput.ComputedLogicalBorderPadding(wm).BStart(wm);
1979 nsIFrame* marker = GetOutsideMarker();
1980 ReflowOutsideMarker(marker, state, reflowOutput, lineBStart);
1981 NS_ASSERTION(!MarkerIsEmpty() || reflowOutput.BSize(wm) == 0,
1982 "empty ::marker frame took up space");
1984 if (havePosition && !MarkerIsEmpty()) {
1985 // We have some lines to align the ::marker with.
1987 // Doing the alignment using the baseline will also cater for
1988 // ::markers that are placed next to a child block (bug 92896)
1990 // Tall ::markers won't look particularly nice here...
1991 LogicalRect bbox =
1992 marker->GetLogicalRect(wm, reflowOutput.PhysicalSize());
1993 const auto baselineGroup = BaselineSharingGroup::First;
1994 Maybe<nscoord> result;
1995 if (MOZ_LIKELY(!wm.IsOrthogonalTo(marker->GetWritingMode()))) {
1996 result = marker->GetNaturalBaselineBOffset(
1997 wm, baselineGroup, BaselineExportContext::LineLayout);
1999 const auto markerBaseline = result.valueOrFrom([bbox, wm, marker]() {
2000 return bbox.BSize(wm) + marker->GetLogicalUsedMargin(wm).BEnd(wm);
2002 bbox.BStart(wm) = position.mBaseline - markerBaseline;
2003 marker->SetRect(wm, bbox, reflowOutput.PhysicalSize());
2005 // Otherwise just leave the ::marker where it is, up against our
2006 // block-start padding.
2009 // Clear any existing -webkit-line-clamp ellipsis.
2010 if (aReflowInput.mStyleDisplay->mWebkitLineClamp) {
2011 ClearLineClampEllipsis();
2014 CheckFloats(state);
2016 // Compute our final size (for this trial layout)
2017 aTrialState.mBlockEndEdgeOfChildren =
2018 ComputeFinalSize(aReflowInput, state, aMetrics);
2019 aTrialState.mContainerWidth = state.ContainerSize().width;
2021 return state.mReflowStatus;
2024 bool nsBlockFrame::CheckForCollapsedBEndMarginFromClearanceLine() {
2025 for (auto& line : Reversed(Lines())) {
2026 if (0 != line.BSize() || !line.CachedIsEmpty()) {
2027 return false;
2029 if (line.HasClearance()) {
2030 return true;
2033 return false;
2036 static nsLineBox* FindLineClampTarget(nsBlockFrame*& aFrame,
2037 StyleLineClamp aLineNumber) {
2038 MOZ_ASSERT(aLineNumber > 0);
2039 MOZ_ASSERT(!aFrame->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS),
2040 "Should have been removed earlier in nsBlockReflow::Reflow");
2042 nsLineBox* target = nullptr;
2043 nsBlockFrame* targetFrame = nullptr;
2044 bool foundFollowingLine = false;
2046 LineClampLineIterator iter(aFrame);
2048 while (nsLineBox* line = iter.GetCurrentLine()) {
2049 MOZ_ASSERT(!line->HasLineClampEllipsis(),
2050 "Should have been removed earlier in nsBlockFrame::Reflow");
2051 MOZ_ASSERT(!iter.GetCurrentFrame()->HasAnyStateBits(
2052 NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS),
2053 "Should have been removed earlier in nsBlockReflow::Reflow");
2055 // Don't count a line that only has collapsible white space (as might exist
2056 // after calling e.g. getBoxQuads).
2057 if (line->IsEmpty()) {
2058 iter.Next();
2059 continue;
2062 if (aLineNumber == 0) {
2063 // We already previously found our target line, and now we have
2064 // confirmed that there is another line after it.
2065 foundFollowingLine = true;
2066 break;
2069 if (--aLineNumber == 0) {
2070 // This is our target line. Continue looping to confirm that we
2071 // have another line after us.
2072 target = line;
2073 targetFrame = iter.GetCurrentFrame();
2076 iter.Next();
2079 if (!foundFollowingLine) {
2080 aFrame = nullptr;
2081 return nullptr;
2084 MOZ_ASSERT(target);
2085 MOZ_ASSERT(targetFrame);
2087 aFrame = targetFrame;
2088 return target;
2091 static nscoord ApplyLineClamp(const ReflowInput& aReflowInput,
2092 nsBlockFrame* aFrame,
2093 nscoord aContentBlockEndEdge) {
2094 if (!IsLineClampRoot(aFrame)) {
2095 return aContentBlockEndEdge;
2097 auto lineClamp = aReflowInput.mStyleDisplay->mWebkitLineClamp;
2098 nsBlockFrame* frame = aFrame;
2099 nsLineBox* line = FindLineClampTarget(frame, lineClamp);
2100 if (!line) {
2101 // The number of lines did not exceed the -webkit-line-clamp value.
2102 return aContentBlockEndEdge;
2105 // Mark the line as having an ellipsis so that TextOverflow will render it.
2106 line->SetHasLineClampEllipsis();
2107 frame->AddStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS);
2109 // Translate the b-end edge of the line up to aFrame's space.
2110 nscoord edge = line->BEnd();
2111 for (nsIFrame* f = frame; f != aFrame; f = f->GetParent()) {
2112 edge +=
2113 f->GetLogicalPosition(f->GetParent()->GetSize()).B(f->GetWritingMode());
2116 return edge;
2119 nscoord nsBlockFrame::ComputeFinalSize(const ReflowInput& aReflowInput,
2120 BlockReflowState& aState,
2121 ReflowOutput& aMetrics) {
2122 WritingMode wm = aState.mReflowInput.GetWritingMode();
2123 const LogicalMargin& borderPadding = aState.BorderPadding();
2124 #ifdef NOISY_FINAL_SIZE
2125 ListTag(stdout);
2126 printf(": mBCoord=%d mIsBEndMarginRoot=%s mPrevBEndMargin=%d bp=%d,%d\n",
2127 aState.mBCoord, aState.mFlags.mIsBEndMarginRoot ? "yes" : "no",
2128 aState.mPrevBEndMargin.get(), borderPadding.BStart(wm),
2129 borderPadding.BEnd(wm));
2130 #endif
2132 // Compute final inline size
2133 LogicalSize finalSize(wm);
2134 finalSize.ISize(wm) =
2135 NSCoordSaturatingAdd(NSCoordSaturatingAdd(borderPadding.IStart(wm),
2136 aReflowInput.ComputedISize()),
2137 borderPadding.IEnd(wm));
2139 // Return block-end margin information
2140 // rbs says he hit this assertion occasionally (see bug 86947), so
2141 // just set the margin to zero and we'll figure out why later
2142 // NS_ASSERTION(aMetrics.mCarriedOutBEndMargin.IsZero(),
2143 // "someone else set the margin");
2144 nscoord nonCarriedOutBDirMargin = 0;
2145 if (!aState.mFlags.mIsBEndMarginRoot) {
2146 // Apply rule from CSS 2.1 section 8.3.1. If we have some empty
2147 // line with clearance and a non-zero block-start margin and all
2148 // subsequent lines are empty, then we do not allow our children's
2149 // carried out block-end margin to be carried out of us and collapse
2150 // with our own block-end margin.
2151 if (CheckForCollapsedBEndMarginFromClearanceLine()) {
2152 // Convert the children's carried out margin to something that
2153 // we will include in our height
2154 nonCarriedOutBDirMargin = aState.mPrevBEndMargin.get();
2155 aState.mPrevBEndMargin.Zero();
2157 aMetrics.mCarriedOutBEndMargin = aState.mPrevBEndMargin;
2158 } else {
2159 aMetrics.mCarriedOutBEndMargin.Zero();
2162 nscoord blockEndEdgeOfChildren = aState.mBCoord + nonCarriedOutBDirMargin;
2163 // Shrink wrap our height around our contents.
2164 if (aState.mFlags.mIsBEndMarginRoot ||
2165 NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) {
2166 // When we are a block-end-margin root make sure that our last
2167 // child's block-end margin is fully applied. We also do this when
2168 // we have a computed height, since in that case the carried out
2169 // margin is not going to be applied anywhere, so we should note it
2170 // here to be included in the overflow area.
2171 // Apply the margin only if there's space for it.
2172 if (blockEndEdgeOfChildren < aState.mReflowInput.AvailableBSize()) {
2173 // Truncate block-end margin if it doesn't fit to our available BSize.
2174 blockEndEdgeOfChildren =
2175 std::min(blockEndEdgeOfChildren + aState.mPrevBEndMargin.get(),
2176 aState.mReflowInput.AvailableBSize());
2179 if (aState.mFlags.mBlockNeedsFloatManager) {
2180 // Include the float manager's state to properly account for the
2181 // block-end margin of any floated elements; e.g., inside a table cell.
2183 // Note: The block coordinate returned by ClearFloats is always greater than
2184 // or equal to blockEndEdgeOfChildren.
2185 std::tie(blockEndEdgeOfChildren, std::ignore) =
2186 aState.ClearFloats(blockEndEdgeOfChildren, StyleClear::Both);
2189 if (NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) {
2190 // Note: We don't use blockEndEdgeOfChildren because it includes the
2191 // previous margin.
2192 const nscoord contentBSizeWithBStartBP =
2193 aState.mBCoord + nonCarriedOutBDirMargin;
2195 // We don't care about ApplyLineClamp's return value (the line-clamped
2196 // content BSize) in this explicit-BSize codepath, but we do still need to
2197 // call ApplyLineClamp for ellipsis markers to be placed as-needed.
2198 ApplyLineClamp(aState.mReflowInput, this, contentBSizeWithBStartBP);
2200 finalSize.BSize(wm) = ComputeFinalBSize(aState, contentBSizeWithBStartBP);
2202 // If the content block-size is larger than the effective computed
2203 // block-size, we extend the block-size to contain all the content.
2204 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
2205 if (aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis()) {
2206 // Note: finalSize.BSize(wm) is the border-box size, so we compare it with
2207 // the content's block-size plus our border and padding..
2208 finalSize.BSize(wm) =
2209 std::max(finalSize.BSize(wm),
2210 contentBSizeWithBStartBP + borderPadding.BEnd(wm));
2213 // Don't carry out a block-end margin when our BSize is fixed.
2215 // Note: this also includes the case that aReflowInput.ComputedBSize() is
2216 // calculated from aspect-ratio. i.e. Don't carry out block margin-end if it
2217 // is replaced by the block size from aspect-ratio and inline size.
2218 aMetrics.mCarriedOutBEndMargin.Zero();
2219 } else {
2220 Maybe<nscoord> containBSize = ContainIntrinsicBSize(
2221 IsComboboxControlFrame() ? NS_UNCONSTRAINEDSIZE : 0);
2222 if (containBSize && *containBSize != NS_UNCONSTRAINEDSIZE) {
2223 // If we're size-containing in block axis and we don't have a specified
2224 // block size, then our final size should actually be computed from only
2225 // our border, padding and contain-intrinsic-block-size, ignoring the
2226 // actual contents. Hence this case is a simplified version of the case
2227 // below.
2229 // NOTE: We exempt the nsComboboxControlFrame subclass from taking this
2230 // special case when it has 'contain-intrinsic-block-size: none', because
2231 // comboboxes implicitly honors the size-containment behavior on its
2232 // nsComboboxDisplayFrame child (which it shrinkwraps) rather than on the
2233 // nsComboboxControlFrame. (Moreover, the DisplayFrame child doesn't even
2234 // need any special content-size-ignoring behavior in its reflow method,
2235 // because that method just resolves "auto" BSize values to one
2236 // line-height rather than by measuring its contents' BSize.)
2237 nscoord contentBSize = *containBSize;
2238 nscoord autoBSize =
2239 aReflowInput.ApplyMinMaxBSize(contentBSize, aState.mConsumedBSize);
2240 aMetrics.mCarriedOutBEndMargin.Zero();
2241 autoBSize += borderPadding.BStartEnd(wm);
2242 finalSize.BSize(wm) = autoBSize;
2243 } else if (aState.mReflowStatus.IsInlineBreakBefore()) {
2244 // Our parent is expected to push this frame to the next page/column so
2245 // what size we set here doesn't really matter.
2246 finalSize.BSize(wm) = aReflowInput.AvailableBSize();
2247 } else if (aState.mReflowStatus.IsComplete()) {
2248 const nscoord lineClampedContentBlockEndEdge =
2249 ApplyLineClamp(aReflowInput, this, blockEndEdgeOfChildren);
2251 const nscoord bpBStart = borderPadding.BStart(wm);
2252 const nscoord contentBSize = blockEndEdgeOfChildren - bpBStart;
2253 const nscoord lineClampedContentBSize =
2254 lineClampedContentBlockEndEdge - bpBStart;
2256 const nscoord autoBSize = aReflowInput.ApplyMinMaxBSize(
2257 lineClampedContentBSize, aState.mConsumedBSize);
2258 if (autoBSize != contentBSize) {
2259 // Our min-block-size, max-block-size, or -webkit-line-clamp value made
2260 // our bsize change. Don't carry out our kids' block-end margins.
2261 aMetrics.mCarriedOutBEndMargin.Zero();
2263 nscoord bSize = autoBSize + borderPadding.BStartEnd(wm);
2264 if (MOZ_UNLIKELY(autoBSize > contentBSize &&
2265 bSize > aReflowInput.AvailableBSize() &&
2266 aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE)) {
2267 // Applying `min-size` made us overflow our available size.
2268 // Clamp it and report that we're Incomplete, or BreakBefore if we have
2269 // 'break-inside: avoid' that is applicable.
2270 bSize = aReflowInput.AvailableBSize();
2271 if (ShouldAvoidBreakInside(aReflowInput)) {
2272 aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
2273 } else {
2274 aState.mReflowStatus.SetIncomplete();
2277 finalSize.BSize(wm) = bSize;
2278 } else {
2279 NS_ASSERTION(
2280 aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
2281 "Shouldn't be incomplete if availableBSize is UNCONSTRAINED.");
2282 nscoord bSize = std::max(aState.mBCoord, aReflowInput.AvailableBSize());
2283 if (aReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
2284 // This should never happen, but it does. See bug 414255
2285 bSize = aState.mBCoord;
2287 const nscoord maxBSize = aReflowInput.ComputedMaxBSize();
2288 if (maxBSize != NS_UNCONSTRAINEDSIZE &&
2289 aState.mConsumedBSize + bSize - borderPadding.BStart(wm) > maxBSize) {
2290 // Compute this fragment's block-size, with the max-block-size
2291 // constraint taken into consideration.
2292 const nscoord clampedBSizeWithoutEndBP =
2293 std::max(0, maxBSize - aState.mConsumedBSize) +
2294 borderPadding.BStart(wm);
2295 const nscoord clampedBSize =
2296 clampedBSizeWithoutEndBP + borderPadding.BEnd(wm);
2297 if (clampedBSize <= aReflowInput.AvailableBSize()) {
2298 // We actually fit after applying `max-size` so we should be
2299 // Overflow-Incomplete instead.
2300 bSize = clampedBSize;
2301 aState.mReflowStatus.SetOverflowIncomplete();
2302 } else {
2303 // We cannot fit after applying `max-size` with our block-end BP, so
2304 // we should draw it in our next continuation.
2305 bSize = clampedBSizeWithoutEndBP;
2308 finalSize.BSize(wm) = bSize;
2312 if (IsTrueOverflowContainer()) {
2313 if (aState.mReflowStatus.IsIncomplete()) {
2314 // Overflow containers can only be overflow complete.
2315 // Note that auto height overflow containers have no normal children
2316 NS_ASSERTION(finalSize.BSize(wm) == 0,
2317 "overflow containers must be zero-block-size");
2318 aState.mReflowStatus.SetOverflowIncomplete();
2320 } else if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
2321 !aState.mReflowStatus.IsInlineBreakBefore() &&
2322 aState.mReflowStatus.IsComplete()) {
2323 // Currently only used for grid items, but could be used in other contexts.
2324 // The FragStretchBSizeProperty is our expected non-fragmented block-size
2325 // we should stretch to (for align-self:stretch etc). In some fragmentation
2326 // cases though, the last fragment (this frame since we're complete), needs
2327 // to have extra size applied because earlier fragments consumed too much of
2328 // our computed size due to overflowing their containing block. (E.g. this
2329 // ensures we fill the last row when a multi-row grid item is fragmented).
2330 bool found;
2331 nscoord bSize = GetProperty(FragStretchBSizeProperty(), &found);
2332 if (found) {
2333 finalSize.BSize(wm) = std::max(bSize, finalSize.BSize(wm));
2337 // Clamp the content size to fit within the margin-box clamp size, if any.
2338 if (MOZ_UNLIKELY(aReflowInput.mComputeSizeFlags.contains(
2339 ComputeSizeFlag::BClampMarginBoxMinSize)) &&
2340 aState.mReflowStatus.IsComplete()) {
2341 bool found;
2342 nscoord cbSize = GetProperty(BClampMarginBoxMinSizeProperty(), &found);
2343 if (found) {
2344 auto marginBoxBSize =
2345 finalSize.BSize(wm) +
2346 aReflowInput.ComputedLogicalMargin(wm).BStartEnd(wm);
2347 auto overflow = marginBoxBSize - cbSize;
2348 if (overflow > 0) {
2349 auto contentBSize = finalSize.BSize(wm) - borderPadding.BStartEnd(wm);
2350 auto newContentBSize = std::max(nscoord(0), contentBSize - overflow);
2351 // XXXmats deal with percentages better somehow?
2352 finalSize.BSize(wm) -= contentBSize - newContentBSize;
2357 // Screen out negative block sizes --- can happen due to integer overflows :-(
2358 finalSize.BSize(wm) = std::max(0, finalSize.BSize(wm));
2360 if (blockEndEdgeOfChildren != finalSize.BSize(wm) - borderPadding.BEnd(wm)) {
2361 SetProperty(BlockEndEdgeOfChildrenProperty(), blockEndEdgeOfChildren);
2362 } else {
2363 RemoveProperty(BlockEndEdgeOfChildrenProperty());
2366 aMetrics.SetSize(wm, finalSize);
2368 #ifdef DEBUG_blocks
2369 if ((ABSURD_SIZE(aMetrics.Width()) || ABSURD_SIZE(aMetrics.Height())) &&
2370 !GetParent()->IsAbsurdSizeAssertSuppressed()) {
2371 ListTag(stdout);
2372 printf(": WARNING: desired:%d,%d\n", aMetrics.Width(), aMetrics.Height());
2374 #endif
2376 return blockEndEdgeOfChildren;
2379 void nsBlockFrame::ConsiderBlockEndEdgeOfChildren(
2380 OverflowAreas& aOverflowAreas, nscoord aBEndEdgeOfChildren,
2381 const nsStyleDisplay* aDisplay) const {
2382 const auto wm = GetWritingMode();
2384 // Factor in the block-end edge of the children. Child frames will be added
2385 // to the overflow area as we iterate through the lines, but their margins
2386 // won't, so we need to account for block-end margins here.
2387 // REVIEW: For now, we do this for both visual and scrollable area,
2388 // although when we make scrollable overflow area not be a subset of
2389 // visual, we can change this.
2391 if (Style()->GetPseudoType() == PseudoStyleType::scrolledContent) {
2392 // If we are a scrolled inner frame, add our block-end padding to our
2393 // children's block-end edge.
2395 // Note: aBEndEdgeOfChildren already includes our own block-start padding
2396 // because it is relative to our block-start edge of our border-box, which
2397 // is the same as our padding-box here.
2398 MOZ_ASSERT(GetLogicalUsedBorderAndPadding(wm) == GetLogicalUsedPadding(wm),
2399 "A scrolled inner frame shouldn't have any border!");
2400 aBEndEdgeOfChildren += GetLogicalUsedPadding(wm).BEnd(wm);
2403 // XXX Currently, overflow areas are stored as physical rects, so we have
2404 // to handle writing modes explicitly here. If we change overflow rects
2405 // to be stored logically, this can be simplified again.
2406 if (wm.IsVertical()) {
2407 if (wm.IsVerticalLR()) {
2408 for (const auto otype : AllOverflowTypes()) {
2409 if (!(aDisplay->IsContainLayout() &&
2410 otype == OverflowType::Scrollable)) {
2411 // Layout containment should force all overflow to be ink (visual)
2412 // overflow, so if we're layout-contained, we only add our children's
2413 // block-end edge to the ink (visual) overflow -- not to the
2414 // scrollable overflow.
2415 nsRect& o = aOverflowAreas.Overflow(otype);
2416 o.width = std::max(o.XMost(), aBEndEdgeOfChildren) - o.x;
2419 } else {
2420 for (const auto otype : AllOverflowTypes()) {
2421 if (!(aDisplay->IsContainLayout() &&
2422 otype == OverflowType::Scrollable)) {
2423 nsRect& o = aOverflowAreas.Overflow(otype);
2424 nscoord xmost = o.XMost();
2425 o.x = std::min(o.x, xmost - aBEndEdgeOfChildren);
2426 o.width = xmost - o.x;
2430 } else {
2431 for (const auto otype : AllOverflowTypes()) {
2432 if (!(aDisplay->IsContainLayout() && otype == OverflowType::Scrollable)) {
2433 nsRect& o = aOverflowAreas.Overflow(otype);
2434 o.height = std::max(o.YMost(), aBEndEdgeOfChildren) - o.y;
2440 void nsBlockFrame::ComputeOverflowAreas(OverflowAreas& aOverflowAreas,
2441 nscoord aBEndEdgeOfChildren,
2442 const nsStyleDisplay* aDisplay) const {
2443 // XXX_perf: This can be done incrementally. It is currently one of
2444 // the things that makes incremental reflow O(N^2).
2445 auto overflowClipAxes = ShouldApplyOverflowClipping(aDisplay);
2446 auto overflowClipMargin = OverflowClipMargin(overflowClipAxes);
2447 if (overflowClipAxes == PhysicalAxes::Both &&
2448 overflowClipMargin == nsSize()) {
2449 return;
2452 // We rely here on our caller having called SetOverflowAreasToDesiredBounds().
2453 nsRect frameBounds = aOverflowAreas.ScrollableOverflow();
2455 for (const auto& line : Lines()) {
2456 if (aDisplay->IsContainLayout()) {
2457 // If we have layout containment, we should only consider our child's
2458 // ink overflow, leaving the scrollable regions of the parent
2459 // unaffected.
2460 // Note: scrollable overflow is a subset of ink overflow,
2461 // so this has the same affect as unioning the child's visual and
2462 // scrollable overflow with its parent's ink overflow.
2463 nsRect childVisualRect = line.InkOverflowRect();
2464 OverflowAreas childVisualArea = OverflowAreas(childVisualRect, nsRect());
2465 aOverflowAreas.UnionWith(childVisualArea);
2466 } else {
2467 aOverflowAreas.UnionWith(line.GetOverflowAreas());
2471 // Factor an outside ::marker in; normally the ::marker will be factored
2472 // into the line-box's overflow areas. However, if the line is a block
2473 // line then it won't; if there are no lines, it won't. So just
2474 // factor it in anyway (it can't hurt if it was already done).
2475 // XXXldb Can we just fix GetOverflowArea instead?
2476 if (nsIFrame* outsideMarker = GetOutsideMarker()) {
2477 aOverflowAreas.UnionAllWith(outsideMarker->GetRect());
2480 ConsiderBlockEndEdgeOfChildren(aOverflowAreas, aBEndEdgeOfChildren, aDisplay);
2482 if (overflowClipAxes != PhysicalAxes::None) {
2483 aOverflowAreas.ApplyClipping(frameBounds, overflowClipAxes,
2484 overflowClipMargin);
2487 #ifdef NOISY_OVERFLOW_AREAS
2488 printf("%s: InkOverflowArea=%s, ScrollableOverflowArea=%s\n", ListTag().get(),
2489 ToString(aOverflowAreas.InkOverflow()).c_str(),
2490 ToString(aOverflowAreas.ScrollableOverflow()).c_str());
2491 #endif
2494 void nsBlockFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas) {
2495 // We need to update the overflow areas of lines manually, as they
2496 // get cached and re-used otherwise. Lines aren't exposed as normal
2497 // frame children, so calling UnionChildOverflow alone will end up
2498 // using the old cached values.
2499 for (auto& line : Lines()) {
2500 nsRect bounds = line.GetPhysicalBounds();
2501 OverflowAreas lineAreas(bounds, bounds);
2503 int32_t n = line.GetChildCount();
2504 for (nsIFrame* lineFrame = line.mFirstChild; n > 0;
2505 lineFrame = lineFrame->GetNextSibling(), --n) {
2506 ConsiderChildOverflow(lineAreas, lineFrame);
2509 // Consider the overflow areas of the floats attached to the line as well
2510 if (line.HasFloats()) {
2511 for (nsIFrame* f : line.Floats()) {
2512 ConsiderChildOverflow(lineAreas, f);
2516 line.SetOverflowAreas(lineAreas);
2517 aOverflowAreas.UnionWith(lineAreas);
2520 // Union with child frames, skipping the principal and float lists
2521 // since we already handled those using the line boxes.
2522 nsLayoutUtils::UnionChildOverflow(
2523 this, aOverflowAreas,
2524 {FrameChildListID::Principal, FrameChildListID::Float});
2527 bool nsBlockFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) {
2528 bool found;
2529 nscoord blockEndEdgeOfChildren =
2530 GetProperty(BlockEndEdgeOfChildrenProperty(), &found);
2531 if (found) {
2532 ConsiderBlockEndEdgeOfChildren(aOverflowAreas, blockEndEdgeOfChildren,
2533 StyleDisplay());
2536 // Line cursor invariants depend on the overflow areas of the lines, so
2537 // we must clear the line cursor since those areas may have changed.
2538 ClearLineCursors();
2539 return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
2542 void nsBlockFrame::LazyMarkLinesDirty() {
2543 if (HasAnyStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES)) {
2544 for (LineIterator line = LinesBegin(), line_end = LinesEnd();
2545 line != line_end; ++line) {
2546 int32_t n = line->GetChildCount();
2547 for (nsIFrame* lineFrame = line->mFirstChild; n > 0;
2548 lineFrame = lineFrame->GetNextSibling(), --n) {
2549 if (lineFrame->IsSubtreeDirty()) {
2550 // NOTE: MarkLineDirty does more than just marking the line dirty.
2551 MarkLineDirty(line, &mLines);
2552 break;
2556 RemoveStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
2560 void nsBlockFrame::MarkLineDirty(LineIterator aLine,
2561 const nsLineList* aLineList) {
2562 // Mark aLine dirty
2563 aLine->MarkDirty();
2564 aLine->SetInvalidateTextRuns(true);
2565 #ifdef DEBUG
2566 if (gNoisyReflow) {
2567 IndentBy(stdout, gNoiseIndent);
2568 ListTag(stdout);
2569 printf(": mark line %p dirty\n", static_cast<void*>(aLine.get()));
2571 #endif
2573 // Mark previous line dirty if it's an inline line so that it can
2574 // maybe pullup something from the line just affected.
2575 // XXX We don't need to do this if aPrevLine ends in a break-after...
2576 if (aLine != aLineList->front() && aLine->IsInline() &&
2577 aLine.prev()->IsInline()) {
2578 aLine.prev()->MarkDirty();
2579 aLine.prev()->SetInvalidateTextRuns(true);
2580 #ifdef DEBUG
2581 if (gNoisyReflow) {
2582 IndentBy(stdout, gNoiseIndent);
2583 ListTag(stdout);
2584 printf(": mark prev-line %p dirty\n",
2585 static_cast<void*>(aLine.prev().get()));
2587 #endif
2592 * Test whether lines are certain to be aligned left so that we can make
2593 * resizing optimizations
2595 static inline bool IsAlignedLeft(StyleTextAlign aAlignment,
2596 StyleDirection aDirection,
2597 StyleUnicodeBidi aUnicodeBidi,
2598 nsIFrame* aFrame) {
2599 return aFrame->IsInSVGTextSubtree() || StyleTextAlign::Left == aAlignment ||
2600 (((StyleTextAlign::Start == aAlignment &&
2601 StyleDirection::Ltr == aDirection) ||
2602 (StyleTextAlign::End == aAlignment &&
2603 StyleDirection::Rtl == aDirection)) &&
2604 aUnicodeBidi != StyleUnicodeBidi::Plaintext);
2607 void nsBlockFrame::PrepareResizeReflow(BlockReflowState& aState) {
2608 // See if we can try and avoid marking all the lines as dirty
2609 // FIXME(emilio): This should be writing-mode aware, I guess.
2610 bool tryAndSkipLines =
2611 // The left content-edge must be a constant distance from the left
2612 // border-edge.
2613 !StylePadding()->mPadding.Get(eSideLeft).HasPercent();
2615 #ifdef DEBUG
2616 if (gDisableResizeOpt) {
2617 tryAndSkipLines = false;
2619 if (gNoisyReflow) {
2620 if (!tryAndSkipLines) {
2621 IndentBy(stdout, gNoiseIndent);
2622 ListTag(stdout);
2623 printf(": marking all lines dirty: availISize=%d\n",
2624 aState.mReflowInput.AvailableISize());
2627 #endif
2629 if (tryAndSkipLines) {
2630 WritingMode wm = aState.mReflowInput.GetWritingMode();
2631 nscoord newAvailISize =
2632 aState.mReflowInput.ComputedLogicalBorderPadding(wm).IStart(wm) +
2633 aState.mReflowInput.ComputedISize();
2635 #ifdef DEBUG
2636 if (gNoisyReflow) {
2637 IndentBy(stdout, gNoiseIndent);
2638 ListTag(stdout);
2639 printf(": trying to avoid marking all lines dirty\n");
2641 #endif
2643 for (LineIterator line = LinesBegin(), line_end = LinesEnd();
2644 line != line_end; ++line) {
2645 // We let child blocks make their own decisions the same
2646 // way we are here.
2647 bool isLastLine = line == mLines.back() && !GetNextInFlow();
2648 if (line->IsBlock() || line->HasFloats() ||
2649 (!isLastLine && !line->HasForcedLineBreakAfter()) ||
2650 ((isLastLine || !line->IsLineWrapped())) ||
2651 line->ResizeReflowOptimizationDisabled() ||
2652 line->IsImpactedByFloat() || (line->IEnd() > newAvailISize)) {
2653 line->MarkDirty();
2656 #ifdef REALLY_NOISY_REFLOW
2657 if (!line->IsBlock()) {
2658 printf("PrepareResizeReflow thinks line %p is %simpacted by floats\n",
2659 line.get(), line->IsImpactedByFloat() ? "" : "not ");
2661 #endif
2662 #ifdef DEBUG
2663 if (gNoisyReflow && !line->IsDirty()) {
2664 IndentBy(stdout, gNoiseIndent + 1);
2665 printf(
2666 "skipped: line=%p next=%p %s %s%s%s clearTypeBefore/After=%s/%s "
2667 "xmost=%d\n",
2668 static_cast<void*>(line.get()),
2669 static_cast<void*>(
2670 (line.next() != LinesEnd() ? line.next().get() : nullptr)),
2671 line->IsBlock() ? "block" : "inline",
2672 line->HasForcedLineBreakAfter() ? "has-break-after " : "",
2673 line->HasFloats() ? "has-floats " : "",
2674 line->IsImpactedByFloat() ? "impacted " : "",
2675 line->StyleClearToString(line->FloatClearTypeBefore()),
2676 line->StyleClearToString(line->FloatClearTypeAfter()),
2677 line->IEnd());
2679 #endif
2681 } else {
2682 // Mark everything dirty
2683 for (auto& line : Lines()) {
2684 line.MarkDirty();
2689 //----------------------------------------
2692 * Propagate reflow "damage" from from earlier lines to the current
2693 * line. The reflow damage comes from the following sources:
2694 * 1. The regions of float damage remembered during reflow.
2695 * 2. The combination of nonzero |aDeltaBCoord| and any impact by a
2696 * float, either the previous reflow or now.
2698 * When entering this function, |aLine| is still at its old position and
2699 * |aDeltaBCoord| indicates how much it will later be slid (assuming it
2700 * doesn't get marked dirty and reflowed entirely).
2702 void nsBlockFrame::PropagateFloatDamage(BlockReflowState& aState,
2703 nsLineBox* aLine,
2704 nscoord aDeltaBCoord) {
2705 nsFloatManager* floatManager = aState.FloatManager();
2706 NS_ASSERTION(
2707 (aState.mReflowInput.mParentReflowInput &&
2708 aState.mReflowInput.mParentReflowInput->mFloatManager == floatManager) ||
2709 aState.mReflowInput.mBlockDelta == 0,
2710 "Bad block delta passed in");
2712 // Check to see if there are any floats; if there aren't, there can't
2713 // be any float damage
2714 if (!floatManager->HasAnyFloats()) {
2715 return;
2718 // Check the damage region recorded in the float damage.
2719 if (floatManager->HasFloatDamage()) {
2720 // Need to check mBounds *and* mCombinedArea to find intersections
2721 // with aLine's floats
2722 nscoord lineBCoordBefore = aLine->BStart() + aDeltaBCoord;
2723 nscoord lineBCoordAfter = lineBCoordBefore + aLine->BSize();
2724 // Scrollable overflow should be sufficient for things that affect
2725 // layout.
2726 WritingMode wm = aState.mReflowInput.GetWritingMode();
2727 nsSize containerSize = aState.ContainerSize();
2728 LogicalRect overflow =
2729 aLine->GetOverflowArea(OverflowType::Scrollable, wm, containerSize);
2730 nscoord lineBCoordCombinedBefore = overflow.BStart(wm) + aDeltaBCoord;
2731 nscoord lineBCoordCombinedAfter =
2732 lineBCoordCombinedBefore + overflow.BSize(wm);
2734 bool isDirty =
2735 floatManager->IntersectsDamage(lineBCoordBefore, lineBCoordAfter) ||
2736 floatManager->IntersectsDamage(lineBCoordCombinedBefore,
2737 lineBCoordCombinedAfter);
2738 if (isDirty) {
2739 aLine->MarkDirty();
2740 return;
2744 // Check if the line is moving relative to the float manager
2745 if (aDeltaBCoord + aState.mReflowInput.mBlockDelta != 0) {
2746 if (aLine->IsBlock()) {
2747 // Unconditionally reflow sliding blocks; we only really need to reflow
2748 // if there's a float impacting this block, but the current float manager
2749 // makes it difficult to check that. Therefore, we let the child block
2750 // decide what it needs to reflow.
2751 aLine->MarkDirty();
2752 } else {
2753 bool wasImpactedByFloat = aLine->IsImpactedByFloat();
2754 nsFlowAreaRect floatAvailableSpace =
2755 aState.GetFloatAvailableSpaceForBSize(aLine->BStart() + aDeltaBCoord,
2756 aLine->BSize(), nullptr);
2758 #ifdef REALLY_NOISY_REFLOW
2759 printf("nsBlockFrame::PropagateFloatDamage %p was = %d, is=%d\n", this,
2760 wasImpactedByFloat, floatAvailableSpace.HasFloats());
2761 #endif
2763 // Mark the line dirty if it was or is affected by a float
2764 // We actually only really need to reflow if the amount of impact
2765 // changes, but that's not straightforward to check
2766 if (wasImpactedByFloat || floatAvailableSpace.HasFloats()) {
2767 aLine->MarkDirty();
2773 static bool LineHasClear(nsLineBox* aLine) {
2774 return aLine->IsBlock()
2775 ? (aLine->HasForcedLineBreakBefore() ||
2776 aLine->mFirstChild->HasAnyStateBits(
2777 NS_BLOCK_HAS_CLEAR_CHILDREN) ||
2778 !nsBlockFrame::BlockCanIntersectFloats(aLine->mFirstChild))
2779 : aLine->HasFloatClearTypeAfter();
2783 * Reparent a whole list of floats from aOldParent to this block. The
2784 * floats might be taken from aOldParent's overflow list. They will be
2785 * removed from the list. They end up appended to our mFloats list.
2787 void nsBlockFrame::ReparentFloats(nsIFrame* aFirstFrame,
2788 nsBlockFrame* aOldParent,
2789 bool aReparentSiblings) {
2790 nsFrameList list;
2791 aOldParent->CollectFloats(aFirstFrame, list, aReparentSiblings);
2792 if (list.NotEmpty()) {
2793 for (nsIFrame* f : list) {
2794 MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
2795 "CollectFloats should've removed that bit");
2796 ReparentFrame(f, aOldParent, this);
2798 mFloats.AppendFrames(nullptr, std::move(list));
2802 static void DumpLine(const BlockReflowState& aState, nsLineBox* aLine,
2803 nscoord aDeltaBCoord, int32_t aDeltaIndent) {
2804 #ifdef DEBUG
2805 if (nsBlockFrame::gNoisyReflow) {
2806 nsRect ovis(aLine->InkOverflowRect());
2807 nsRect oscr(aLine->ScrollableOverflowRect());
2808 nsBlockFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent + aDeltaIndent);
2809 printf(
2810 "line=%p mBCoord=%d dirty=%s oldBounds={%d,%d,%d,%d} "
2811 "oldoverflow-vis={%d,%d,%d,%d} oldoverflow-scr={%d,%d,%d,%d} "
2812 "deltaBCoord=%d mPrevBEndMargin=%d childCount=%d\n",
2813 static_cast<void*>(aLine), aState.mBCoord,
2814 aLine->IsDirty() ? "yes" : "no", aLine->IStart(), aLine->BStart(),
2815 aLine->ISize(), aLine->BSize(), ovis.x, ovis.y, ovis.width, ovis.height,
2816 oscr.x, oscr.y, oscr.width, oscr.height, aDeltaBCoord,
2817 aState.mPrevBEndMargin.get(), aLine->GetChildCount());
2819 #endif
2822 static bool LinesAreEmpty(const nsLineList& aList) {
2823 for (const auto& line : aList) {
2824 if (!line.IsEmpty()) {
2825 return false;
2828 return true;
2831 void nsBlockFrame::ReflowDirtyLines(BlockReflowState& aState) {
2832 bool keepGoing = true;
2833 bool repositionViews = false; // should we really need this?
2834 bool foundAnyClears = aState.mTrailingClearFromPIF != StyleClear::None;
2835 bool willReflowAgain = false;
2837 #ifdef DEBUG
2838 if (gNoisyReflow) {
2839 IndentBy(stdout, gNoiseIndent);
2840 ListTag(stdout);
2841 printf(": reflowing dirty lines");
2842 printf(" computedISize=%d\n", aState.mReflowInput.ComputedISize());
2844 AutoNoisyIndenter indent(gNoisyReflow);
2845 #endif
2847 bool selfDirty = HasAnyStateBits(NS_FRAME_IS_DIRTY) ||
2848 (aState.mReflowInput.IsBResize() &&
2849 HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE));
2851 // Reflow our last line if our availableBSize has increased
2852 // so that we (and our last child) pull up content as necessary
2853 if (aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
2854 GetNextInFlow() &&
2855 aState.mReflowInput.AvailableBSize() >
2856 GetLogicalSize().BSize(aState.mReflowInput.GetWritingMode())) {
2857 LineIterator lastLine = LinesEnd();
2858 if (lastLine != LinesBegin()) {
2859 --lastLine;
2860 lastLine->MarkDirty();
2863 // the amount by which we will slide the current line if it is not
2864 // dirty
2865 nscoord deltaBCoord = 0;
2867 // whether we did NOT reflow the previous line and thus we need to
2868 // recompute the carried out margin before the line if we want to
2869 // reflow it or if its previous margin is dirty
2870 bool needToRecoverState = false;
2871 // Float continuations were reflowed in ReflowPushedFloats
2872 bool reflowedFloat =
2873 mFloats.NotEmpty() &&
2874 mFloats.FirstChild()->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT);
2875 bool lastLineMovedUp = false;
2876 // We save up information about BR-clearance here
2877 StyleClear inlineFloatClearType = aState.mTrailingClearFromPIF;
2879 LineIterator line = LinesBegin(), line_end = LinesEnd();
2881 // Determine if children of this frame could have breaks between them for
2882 // page names.
2884 // We need to check for paginated layout, the named-page pref, and if the
2885 // available block-size is constrained.
2887 // Note that we need to check for paginated layout as named-pages are only
2888 // used during paginated reflow. We need to additionally check for
2889 // unconstrained block-size to avoid introducing fragmentation breaks during
2890 // "measuring" reflows within an overall paginated reflow, and to avoid
2891 // fragmentation in monolithic containers like 'inline-block'.
2893 // Because we can only break for named pages using Class A breakpoints, we
2894 // also need to check that the block flow direction of the containing frame
2895 // of these items (which is this block) is parallel to that of this page.
2896 // See: https://www.w3.org/TR/css-break-3/#btw-blocks
2897 const nsPresContext* const presCtx = aState.mPresContext;
2898 const bool canBreakForPageNames =
2899 aState.mReflowInput.mFlags.mCanHaveClassABreakpoints &&
2900 aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
2901 presCtx->GetPresShell()->GetRootFrame()->GetWritingMode().IsVertical() ==
2902 GetWritingMode().IsVertical();
2904 // ReflowInput.mFlags.mCanHaveClassABreakpoints should respect the named
2905 // pages pref and presCtx->IsPaginated, so we did not explicitly check these
2906 // above when setting canBreakForPageNames.
2907 if (canBreakForPageNames) {
2908 MOZ_ASSERT(presCtx->IsPaginated(),
2909 "canBreakForPageNames should not be set during non-paginated "
2910 "reflow");
2913 // Reflow the lines that are already ours
2914 for (; line != line_end; ++line, aState.AdvanceToNextLine()) {
2915 DumpLine(aState, line, deltaBCoord, 0);
2916 #ifdef DEBUG
2917 AutoNoisyIndenter indent2(gNoisyReflow);
2918 #endif
2920 if (selfDirty) {
2921 line->MarkDirty();
2924 // This really sucks, but we have to look inside any blocks that have clear
2925 // elements inside them.
2926 // XXX what can we do smarter here?
2927 if (!line->IsDirty() && line->IsBlock() &&
2928 line->mFirstChild->HasAnyStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN)) {
2929 line->MarkDirty();
2932 nsIFrame* floatAvoidingBlock = nullptr;
2933 if (line->IsBlock() &&
2934 !nsBlockFrame::BlockCanIntersectFloats(line->mFirstChild)) {
2935 floatAvoidingBlock = line->mFirstChild;
2938 // We have to reflow the line if it's a block whose clearance
2939 // might have changed, so detect that.
2940 if (!line->IsDirty() &&
2941 (line->HasForcedLineBreakBefore() || floatAvoidingBlock)) {
2942 nscoord curBCoord = aState.mBCoord;
2943 // See where we would be after applying any clearance due to
2944 // BRs.
2945 if (inlineFloatClearType != StyleClear::None) {
2946 std::tie(curBCoord, std::ignore) =
2947 aState.ClearFloats(curBCoord, inlineFloatClearType);
2950 auto [newBCoord, result] = aState.ClearFloats(
2951 curBCoord, line->FloatClearTypeBefore(), floatAvoidingBlock);
2953 if (line->HasClearance()) {
2954 // Reflow the line if it might not have clearance anymore.
2955 if (result == ClearFloatsResult::BCoordNoChange
2956 // aState.mBCoord is the clearance point which should be the
2957 // block-start border-edge of the block frame. If sliding the
2958 // block by deltaBCoord isn't going to put it in the predicted
2959 // position, then we'd better reflow the line.
2960 || newBCoord != line->BStart() + deltaBCoord) {
2961 line->MarkDirty();
2963 } else {
2964 // Reflow the line if the line might have clearance now.
2965 if (result != ClearFloatsResult::BCoordNoChange) {
2966 line->MarkDirty();
2971 // We might have to reflow a line that is after a clearing BR.
2972 if (inlineFloatClearType != StyleClear::None) {
2973 std::tie(aState.mBCoord, std::ignore) =
2974 aState.ClearFloats(aState.mBCoord, inlineFloatClearType);
2975 if (aState.mBCoord != line->BStart() + deltaBCoord) {
2976 // SlideLine is not going to put the line where the clearance
2977 // put it. Reflow the line to be sure.
2978 line->MarkDirty();
2980 inlineFloatClearType = StyleClear::None;
2983 bool previousMarginWasDirty = line->IsPreviousMarginDirty();
2984 if (previousMarginWasDirty) {
2985 // If the previous margin is dirty, reflow the current line
2986 line->MarkDirty();
2987 line->ClearPreviousMarginDirty();
2988 } else if (aState.ContentBSize() != NS_UNCONSTRAINEDSIZE) {
2989 const nscoord scrollableOverflowBEnd =
2990 LogicalRect(line->mWritingMode, line->ScrollableOverflowRect(),
2991 line->mContainerSize)
2992 .BEnd(line->mWritingMode);
2993 if (scrollableOverflowBEnd + deltaBCoord > aState.ContentBEnd()) {
2994 // Lines that aren't dirty but get slid past our available block-size
2995 // constraint must be reflowed.
2996 line->MarkDirty();
3000 if (!line->IsDirty()) {
3001 const bool isPaginated =
3002 // Last column can be reflowed unconstrained during column balancing.
3003 // Hence the additional NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR bit check
3004 // as a fail-safe fallback.
3005 aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE ||
3006 HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) ||
3007 // Table can also be reflowed unconstrained during printing.
3008 aState.mPresContext->IsPaginated();
3009 if (isPaginated) {
3010 // We are in a paginated context, i.e. in columns or pages.
3011 const bool mayContainFloats =
3012 line->IsBlock() || line->HasFloats() || line->HadFloatPushed();
3013 if (mayContainFloats) {
3014 // The following if-else conditions check whether this line -- which
3015 // might have floats in its subtree, or has floats as direct children,
3016 // or had floats pushed -- needs to be reflowed.
3017 if (deltaBCoord != 0 || aState.mReflowInput.IsBResize()) {
3018 // The distance to the block-end edge might have changed. Reflow the
3019 // line both because the breakpoints within its floats may have
3020 // changed and because we might have to push/pull the floats in
3021 // their entirety.
3022 line->MarkDirty();
3023 } else if (HasPushedFloats()) {
3024 // We had pushed floats which haven't been drained by our
3025 // next-in-flow, which means our parent is currently reflowing us
3026 // again due to clearance without creating a next-in-flow for us.
3027 // Reflow the line to redo the floats split logic to correctly set
3028 // our reflow status.
3029 line->MarkDirty();
3030 } else if (aState.mReflowInput.mFlags.mMustReflowPlaceholders) {
3031 // Reflow the line (that may containing a float's placeholder frame)
3032 // if our parent tells us to do so.
3033 line->MarkDirty();
3034 } else if (aState.mReflowInput.mFlags.mMovedBlockFragments) {
3035 // Our parent's line containing us moved to a different fragment.
3036 // Reflow the line because the decision about whether the float fits
3037 // may be different in a different fragment.
3038 line->MarkDirty();
3044 if (!line->IsDirty()) {
3045 // See if there's any reflow damage that requires that we mark the
3046 // line dirty.
3047 PropagateFloatDamage(aState, line, deltaBCoord);
3050 // If the container size has changed, reset mContainerSize. If the
3051 // line's writing mode is not ltr, or if the line is not left-aligned, also
3052 // mark the line dirty.
3053 if (aState.ContainerSize() != line->mContainerSize) {
3054 line->mContainerSize = aState.ContainerSize();
3056 const bool isLastLine = line == mLines.back() && !GetNextInFlow();
3057 const auto align = isLastLine ? StyleText()->TextAlignForLastLine()
3058 : StyleText()->mTextAlign;
3059 if (line->mWritingMode.IsVertical() || line->mWritingMode.IsBidiRTL() ||
3060 !IsAlignedLeft(align, StyleVisibility()->mDirection,
3061 StyleTextReset()->mUnicodeBidi, this)) {
3062 line->MarkDirty();
3066 // Check for a page break caused by CSS named pages.
3068 // We should break for named pages when two frames meet at a class A
3069 // breakpoint, where the first frame has a different end page value to the
3070 // second frame's start page value. canBreakForPageNames is true iff
3071 // children of this frame can form class A breakpoints, and that we are not
3072 // in a measurement reflow or in a monolithic container such as
3073 // 'inline-block'.
3075 // We specifically do not want to cause a page-break for named pages when
3076 // we are at the top of a page. This would otherwise happen when the
3077 // previous sibling is an nsPageBreakFrame, or all previous siblings on the
3078 // current page are zero-height. The latter may not be per-spec, but is
3079 // compatible with Chrome's implementation of named pages.
3080 const nsAtom* nextPageName = nullptr;
3081 bool shouldBreakForPageName = false;
3082 if (canBreakForPageNames && (!aState.mReflowInput.mFlags.mIsTopOfPage ||
3083 !aState.IsAdjacentWithBStart())) {
3084 const nsIFrame* const frame = line->mFirstChild;
3085 if (!frame->IsPlaceholderFrame() && !frame->IsPageBreakFrame()) {
3086 nextPageName = frame->GetStartPageValue();
3087 // Walk back to the last frame that isn't a placeholder.
3088 const nsIFrame* prevFrame = frame->GetPrevSibling();
3089 while (prevFrame && prevFrame->IsPlaceholderFrame()) {
3090 prevFrame = prevFrame->GetPrevSibling();
3092 if (prevFrame && prevFrame->GetEndPageValue() != nextPageName) {
3093 shouldBreakForPageName = true;
3094 line->MarkDirty();
3099 if (needToRecoverState && line->IsDirty()) {
3100 // We need to reconstruct the block-end margin only if we didn't
3101 // reflow the previous line and we do need to reflow (or repair
3102 // the block-start position of) the next line.
3103 aState.ReconstructMarginBefore(line);
3106 bool reflowedPrevLine = !needToRecoverState;
3107 if (needToRecoverState) {
3108 needToRecoverState = false;
3110 // Update aState.mPrevChild as if we had reflowed all of the frames in
3111 // this line.
3112 if (line->IsDirty()) {
3113 NS_ASSERTION(
3114 line->mFirstChild->GetPrevSibling() == line.prev()->LastChild(),
3115 "unexpected line frames");
3116 aState.mPrevChild = line->mFirstChild->GetPrevSibling();
3120 // Now repair the line and update |aState.mBCoord| by calling
3121 // |ReflowLine| or |SlideLine|.
3122 // If we're going to reflow everything again, then no need to reflow
3123 // the dirty line ... unless the line has floats, in which case we'd
3124 // better reflow it now to refresh its float cache, which may contain
3125 // dangling frame pointers! Ugh! This reflow of the line may be
3126 // incorrect because we skipped reflowing previous lines (e.g., floats
3127 // may be placed incorrectly), but that's OK because we'll mark the
3128 // line dirty below under "if (aState.mReflowInput.mDiscoveredClearance..."
3129 if (line->IsDirty() && (line->HasFloats() || !willReflowAgain)) {
3130 lastLineMovedUp = true;
3132 bool maybeReflowingForFirstTime =
3133 line->IStart() == 0 && line->BStart() == 0 && line->ISize() == 0 &&
3134 line->BSize() == 0;
3136 // Compute the dirty lines "before" BEnd, after factoring in
3137 // the running deltaBCoord value - the running value is implicit in
3138 // aState.mBCoord.
3139 nscoord oldB = line->BStart();
3140 nscoord oldBMost = line->BEnd();
3142 NS_ASSERTION(!willReflowAgain || !line->IsBlock(),
3143 "Don't reflow blocks while willReflowAgain is true, reflow "
3144 "of block abs-pos children depends on this");
3146 if (shouldBreakForPageName) {
3147 // Immediately fragment for page-name. It is possible we could break
3148 // out of the loop right here, but this should make it more similar to
3149 // what happens when reflow causes fragmentation.
3150 PushTruncatedLine(aState, line, &keepGoing);
3151 PresShell()->FrameConstructor()->SetNextPageContentFramePageName(
3152 nextPageName ? nextPageName : GetAutoPageValue());
3153 } else {
3154 // Reflow the dirty line. If it's an incremental reflow, then force
3155 // it to invalidate the dirty area if necessary
3156 ReflowLine(aState, line, &keepGoing);
3159 if (aState.mReflowInput.WillReflowAgainForClearance()) {
3160 line->MarkDirty();
3161 willReflowAgain = true;
3162 // Note that once we've entered this state, every line that gets here
3163 // (e.g. because it has floats) gets marked dirty and reflowed again.
3164 // in the next pass. This is important, see above.
3167 if (line->HasFloats()) {
3168 reflowedFloat = true;
3171 if (!keepGoing) {
3172 DumpLine(aState, line, deltaBCoord, -1);
3173 if (0 == line->GetChildCount()) {
3174 DeleteLine(aState, line, line_end);
3176 break;
3179 // Test to see whether the margin that should be carried out
3180 // to the next line (NL) might have changed. In ReflowBlockFrame
3181 // we call nextLine->MarkPreviousMarginDirty if the block's
3182 // actual carried-out block-end margin changed. So here we only
3183 // need to worry about the following effects:
3184 // 1) the line was just created, and it might now be blocking
3185 // a carried-out block-end margin from previous lines that
3186 // used to reach NL from reaching NL
3187 // 2) the line used to be empty, and is now not empty,
3188 // thus blocking a carried-out block-end margin from previous lines
3189 // that used to reach NL from reaching NL
3190 // 3) the line wasn't empty, but now is, so a carried-out
3191 // block-end margin from previous lines that didn't used to reach NL
3192 // now does
3193 // 4) the line might have changed in a way that affects NL's
3194 // ShouldApplyBStartMargin decision. The three things that matter
3195 // are the line's emptiness, its adjacency to the block-start edge of the
3196 // block, and whether it has clearance (the latter only matters if the
3197 // block was and is adjacent to the block-start and empty).
3199 // If the line is empty now, we can't reliably tell if the line was empty
3200 // before, so we just assume it was and do
3201 // nextLine->MarkPreviousMarginDirty. This means the checks in 4) are
3202 // redundant; if the line is empty now we don't need to check 4), but if
3203 // the line is not empty now and we're sure it wasn't empty before, any
3204 // adjacency and clearance changes are irrelevant to the result of
3205 // nextLine->ShouldApplyBStartMargin.
3206 if (line.next() != LinesEnd()) {
3207 bool maybeWasEmpty = oldB == line.next()->BStart();
3208 bool isEmpty = line->CachedIsEmpty();
3209 if (maybeReflowingForFirstTime /*1*/ ||
3210 (isEmpty || maybeWasEmpty) /*2/3/4*/) {
3211 line.next()->MarkPreviousMarginDirty();
3212 // since it's marked dirty, nobody will care about |deltaBCoord|
3216 // If the line was just reflowed for the first time, then its
3217 // old mBounds cannot be trusted so this deltaBCoord computation is
3218 // bogus. But that's OK because we just did
3219 // MarkPreviousMarginDirty on the next line which will force it
3220 // to be reflowed, so this computation of deltaBCoord will not be
3221 // used.
3222 deltaBCoord = line->BEnd() - oldBMost;
3224 // Now do an interrupt check. We want to do this only in the case when we
3225 // actually reflow the line, so that if we get back in here we'll get
3226 // further on the reflow before interrupting.
3227 aState.mPresContext->CheckForInterrupt(this);
3228 } else {
3229 aState.mOverflowTracker->Skip(line->mFirstChild, aState.mReflowStatus);
3230 // Nop except for blocks (we don't create overflow container
3231 // continuations for any inlines atm), so only checking mFirstChild
3232 // is enough
3234 lastLineMovedUp = deltaBCoord < 0;
3236 if (deltaBCoord != 0) {
3237 SlideLine(aState, line, deltaBCoord);
3238 } else {
3239 repositionViews = true;
3242 NS_ASSERTION(!line->IsDirty() || !line->HasFloats(),
3243 "Possibly stale float cache here!");
3244 if (willReflowAgain && line->IsBlock()) {
3245 // If we're going to reflow everything again, and this line is a block,
3246 // then there is no need to recover float state. The line may contain
3247 // other lines with floats, but in that case RecoverStateFrom would only
3248 // add floats to the float manager. We don't need to do that because
3249 // everything's going to get reflowed again "for real". Calling
3250 // RecoverStateFrom in this situation could be lethal because the
3251 // block's descendant lines may have float caches containing dangling
3252 // frame pointers. Ugh!
3253 // If this line is inline, then we need to recover its state now
3254 // to make sure that we don't forget to move its floats by deltaBCoord.
3255 } else {
3256 // XXX EVIL O(N^2) EVIL
3257 aState.RecoverStateFrom(line, deltaBCoord);
3260 // Keep mBCoord up to date in case we're propagating reflow damage
3261 // and also because our final height may depend on it. If the
3262 // line is inlines, then only update mBCoord if the line is not
3263 // empty, because that's what PlaceLine does. (Empty blocks may
3264 // want to update mBCoord, e.g. if they have clearance.)
3265 if (line->IsBlock() || !line->CachedIsEmpty()) {
3266 aState.mBCoord = line->BEnd();
3269 needToRecoverState = true;
3271 if (reflowedPrevLine && !line->IsBlock() &&
3272 aState.mPresContext->HasPendingInterrupt()) {
3273 // Need to make sure to pull overflows from any prev-in-flows
3274 for (nsIFrame* inlineKid = line->mFirstChild; inlineKid;
3275 inlineKid = inlineKid->PrincipalChildList().FirstChild()) {
3276 inlineKid->PullOverflowsFromPrevInFlow();
3281 // Record if we need to clear floats before reflowing the next
3282 // line. Note that inlineFloatClearType will be handled and
3283 // cleared before the next line is processed, so there is no
3284 // need to combine break types here.
3285 if (line->HasFloatClearTypeAfter()) {
3286 inlineFloatClearType = line->FloatClearTypeAfter();
3289 if (LineHasClear(line.get())) {
3290 foundAnyClears = true;
3293 DumpLine(aState, line, deltaBCoord, -1);
3295 if (aState.mPresContext->HasPendingInterrupt()) {
3296 willReflowAgain = true;
3297 // Another option here might be to leave |line| clean if
3298 // !HasPendingInterrupt() before the CheckForInterrupt() call, since in
3299 // that case the line really did reflow as it should have. Not sure
3300 // whether that would be safe, so doing this for now instead. Also not
3301 // sure whether we really want to mark all lines dirty after an
3302 // interrupt, but until we get better at propagating float damage we
3303 // really do need to do it this way; see comments inside MarkLineDirty.
3304 MarkLineDirtyForInterrupt(line);
3308 // Handle BR-clearance from the last line of the block
3309 if (inlineFloatClearType != StyleClear::None) {
3310 std::tie(aState.mBCoord, std::ignore) =
3311 aState.ClearFloats(aState.mBCoord, inlineFloatClearType);
3314 if (needToRecoverState) {
3315 // Is this expensive?
3316 aState.ReconstructMarginBefore(line);
3318 // Update aState.mPrevChild as if we had reflowed all of the frames in
3319 // the last line.
3320 NS_ASSERTION(line == line_end || line->mFirstChild->GetPrevSibling() ==
3321 line.prev()->LastChild(),
3322 "unexpected line frames");
3323 aState.mPrevChild = line == line_end ? mFrames.LastChild()
3324 : line->mFirstChild->GetPrevSibling();
3327 // Should we really have to do this?
3328 if (repositionViews) {
3329 nsContainerFrame::PlaceFrameView(this);
3332 // We can skip trying to pull up the next line if our height is constrained
3333 // (so we can report being incomplete) and there is no next in flow or we
3334 // were told not to or we know it will be futile, i.e.,
3335 // -- the next in flow is not changing
3336 // -- and we cannot have added more space for its first line to be
3337 // pulled up into,
3338 // -- it's an incremental reflow of a descendant
3339 // -- and we didn't reflow any floats (so the available space
3340 // didn't change)
3341 // -- my chain of next-in-flows either has no first line, or its first
3342 // line isn't dirty.
3343 bool heightConstrained =
3344 aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE;
3345 bool skipPull = willReflowAgain && heightConstrained;
3346 if (!skipPull && heightConstrained && aState.mNextInFlow &&
3347 (aState.mReflowInput.mFlags.mNextInFlowUntouched && !lastLineMovedUp &&
3348 !HasAnyStateBits(NS_FRAME_IS_DIRTY) && !reflowedFloat)) {
3349 // We'll place lineIter at the last line of this block, so that
3350 // nsBlockInFlowLineIterator::Next() will take us to the first
3351 // line of my next-in-flow-chain. (But first, check that I
3352 // have any lines -- if I don't, just bail out of this
3353 // optimization.)
3354 LineIterator lineIter = this->LinesEnd();
3355 if (lineIter != this->LinesBegin()) {
3356 lineIter--; // I have lines; step back from dummy iterator to last line.
3357 nsBlockInFlowLineIterator bifLineIter(this, lineIter);
3359 // Check for next-in-flow-chain's first line.
3360 // (First, see if there is such a line, and second, see if it's clean)
3361 if (!bifLineIter.Next() || !bifLineIter.GetLine()->IsDirty()) {
3362 skipPull = true;
3367 if (skipPull && aState.mNextInFlow) {
3368 NS_ASSERTION(heightConstrained, "Height should be constrained here\n");
3369 if (aState.mNextInFlow->IsTrueOverflowContainer()) {
3370 aState.mReflowStatus.SetOverflowIncomplete();
3371 } else {
3372 aState.mReflowStatus.SetIncomplete();
3376 if (!skipPull && aState.mNextInFlow) {
3377 // Pull data from a next-in-flow if there's still room for more
3378 // content here.
3379 while (keepGoing && aState.mNextInFlow) {
3380 // Grab first line from our next-in-flow
3381 nsBlockFrame* nextInFlow = aState.mNextInFlow;
3382 nsLineBox* pulledLine;
3383 nsFrameList pulledFrames;
3384 if (!nextInFlow->mLines.empty()) {
3385 RemoveFirstLine(nextInFlow->mLines, nextInFlow->mFrames, &pulledLine,
3386 &pulledFrames);
3387 } else {
3388 // Grab an overflow line if there are any
3389 FrameLines* overflowLines = nextInFlow->GetOverflowLines();
3390 if (!overflowLines) {
3391 aState.mNextInFlow =
3392 static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow());
3393 continue;
3395 bool last =
3396 RemoveFirstLine(overflowLines->mLines, overflowLines->mFrames,
3397 &pulledLine, &pulledFrames);
3398 if (last) {
3399 nextInFlow->DestroyOverflowLines();
3403 if (pulledFrames.IsEmpty()) {
3404 // The line is empty. Try the next one.
3405 NS_ASSERTION(
3406 pulledLine->GetChildCount() == 0 && !pulledLine->mFirstChild,
3407 "bad empty line");
3408 nextInFlow->FreeLineBox(pulledLine);
3409 continue;
3412 if (nextInFlow->MaybeHasLineCursor()) {
3413 if (pulledLine == nextInFlow->GetLineCursorForDisplay()) {
3414 nextInFlow->ClearLineCursorForDisplay();
3416 if (pulledLine == nextInFlow->GetLineCursorForQuery()) {
3417 nextInFlow->ClearLineCursorForQuery();
3420 ReparentFrames(pulledFrames, nextInFlow, this);
3421 pulledLine->SetMovedFragments();
3423 NS_ASSERTION(pulledFrames.LastChild() == pulledLine->LastChild(),
3424 "Unexpected last frame");
3425 NS_ASSERTION(aState.mPrevChild || mLines.empty(),
3426 "should have a prevchild here");
3427 NS_ASSERTION(aState.mPrevChild == mFrames.LastChild(),
3428 "Incorrect aState.mPrevChild before inserting line at end");
3430 // Shift pulledLine's frames into our mFrames list.
3431 mFrames.AppendFrames(nullptr, std::move(pulledFrames));
3433 // Add line to our line list, and set its last child as our new prev-child
3434 line = mLines.before_insert(LinesEnd(), pulledLine);
3435 aState.mPrevChild = mFrames.LastChild();
3437 // Reparent floats whose placeholders are in the line.
3438 ReparentFloats(pulledLine->mFirstChild, nextInFlow, true);
3440 DumpLine(aState, pulledLine, deltaBCoord, 0);
3441 #ifdef DEBUG
3442 AutoNoisyIndenter indent2(gNoisyReflow);
3443 #endif
3445 if (aState.mPresContext->HasPendingInterrupt()) {
3446 MarkLineDirtyForInterrupt(line);
3447 } else {
3448 // Now reflow it and any lines that it makes during it's reflow
3449 // (we have to loop here because reflowing the line may cause a new
3450 // line to be created; see SplitLine's callers for examples of
3451 // when this happens).
3452 while (line != LinesEnd()) {
3453 ReflowLine(aState, line, &keepGoing);
3455 if (aState.mReflowInput.WillReflowAgainForClearance()) {
3456 line->MarkDirty();
3457 keepGoing = false;
3458 aState.mReflowStatus.SetIncomplete();
3459 break;
3462 DumpLine(aState, line, deltaBCoord, -1);
3463 if (!keepGoing) {
3464 if (0 == line->GetChildCount()) {
3465 DeleteLine(aState, line, line_end);
3467 break;
3470 if (LineHasClear(line.get())) {
3471 foundAnyClears = true;
3474 if (aState.mPresContext->CheckForInterrupt(this)) {
3475 MarkLineDirtyForInterrupt(line);
3476 break;
3479 // If this is an inline frame then its time to stop
3480 ++line;
3481 aState.AdvanceToNextLine();
3486 if (aState.mReflowStatus.IsIncomplete()) {
3487 aState.mReflowStatus.SetNextInFlowNeedsReflow();
3488 } // XXXfr shouldn't set this flag when nextinflow has no lines
3491 // Handle an odd-ball case: a list-item with no lines
3492 if (mLines.empty() && HasOutsideMarker()) {
3493 ReflowOutput metrics(aState.mReflowInput);
3494 nsIFrame* marker = GetOutsideMarker();
3495 WritingMode wm = aState.mReflowInput.GetWritingMode();
3496 ReflowOutsideMarker(
3497 marker, aState, metrics,
3498 aState.mReflowInput.ComputedPhysicalBorderPadding().top);
3499 NS_ASSERTION(!MarkerIsEmpty() || metrics.BSize(wm) == 0,
3500 "empty ::marker frame took up space");
3502 if (!MarkerIsEmpty()) {
3503 // There are no lines so we have to fake up some y motion so that
3504 // we end up with *some* height.
3505 // (Note: if we're layout-contained, we have to be sure to leave our
3506 // ReflowOutput's BlockStartAscent() (i.e. the baseline) untouched,
3507 // because layout-contained frames have no baseline.)
3508 if (!aState.mReflowInput.mStyleDisplay->IsContainLayout() &&
3509 metrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
3510 nscoord ascent;
3511 WritingMode wm = aState.mReflowInput.GetWritingMode();
3512 if (nsLayoutUtils::GetFirstLineBaseline(wm, marker, &ascent)) {
3513 metrics.SetBlockStartAscent(ascent);
3514 } else {
3515 metrics.SetBlockStartAscent(metrics.BSize(wm));
3519 RefPtr<nsFontMetrics> fm =
3520 nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
3522 nscoord minAscent = nsLayoutUtils::GetCenteredFontBaseline(
3523 fm, aState.mMinLineHeight, wm.IsLineInverted());
3524 nscoord minDescent = aState.mMinLineHeight - minAscent;
3526 aState.mBCoord +=
3527 std::max(minAscent, metrics.BlockStartAscent()) +
3528 std::max(minDescent, metrics.BSize(wm) - metrics.BlockStartAscent());
3530 nscoord offset = minAscent - metrics.BlockStartAscent();
3531 if (offset > 0) {
3532 marker->SetRect(marker->GetRect() + nsPoint(0, offset));
3537 if (LinesAreEmpty(mLines) && ShouldHaveLineIfEmpty()) {
3538 aState.mBCoord += aState.mMinLineHeight;
3541 if (foundAnyClears) {
3542 AddStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN);
3543 } else {
3544 RemoveStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN);
3547 #ifdef DEBUG
3548 VerifyLines(true);
3549 VerifyOverflowSituation();
3550 if (gNoisyReflow) {
3551 IndentBy(stdout, gNoiseIndent - 1);
3552 ListTag(stdout);
3553 printf(": done reflowing dirty lines (status=%s)\n",
3554 ToString(aState.mReflowStatus).c_str());
3556 #endif
3559 void nsBlockFrame::MarkLineDirtyForInterrupt(nsLineBox* aLine) {
3560 aLine->MarkDirty();
3562 // Just checking NS_FRAME_IS_DIRTY is ok, because we've already
3563 // marked the lines that need to be marked dirty based on our
3564 // vertical resize stuff. So we'll definitely reflow all those kids;
3565 // the only question is how they should behave.
3566 if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
3567 // Mark all our child frames dirty so we make sure to reflow them
3568 // later.
3569 int32_t n = aLine->GetChildCount();
3570 for (nsIFrame* f = aLine->mFirstChild; n > 0;
3571 f = f->GetNextSibling(), --n) {
3572 f->MarkSubtreeDirty();
3574 // And mark all the floats whose reflows we might be skipping dirty too.
3575 if (aLine->HasFloats()) {
3576 for (nsIFrame* f : aLine->Floats()) {
3577 f->MarkSubtreeDirty();
3580 } else {
3581 // Dirty all the descendant lines of block kids to handle float damage,
3582 // since our nsFloatManager will go away by the next time we're reflowing.
3583 // XXXbz Can we do something more like what PropagateFloatDamage does?
3584 // Would need to sort out the exact business with mBlockDelta for that....
3585 // This marks way too much dirty. If we ever make this better, revisit
3586 // which lines we mark dirty in the interrupt case in ReflowDirtyLines.
3587 nsBlockFrame* bf = do_QueryFrame(aLine->mFirstChild);
3588 if (bf) {
3589 MarkAllDescendantLinesDirty(bf);
3594 void nsBlockFrame::DeleteLine(BlockReflowState& aState,
3595 nsLineList::iterator aLine,
3596 nsLineList::iterator aLineEnd) {
3597 MOZ_ASSERT(0 == aLine->GetChildCount(), "can't delete !empty line");
3598 if (0 == aLine->GetChildCount()) {
3599 NS_ASSERTION(aState.mCurrentLine == aLine,
3600 "using function more generally than designed, "
3601 "but perhaps OK now");
3602 nsLineBox* line = aLine;
3603 aLine = mLines.erase(aLine);
3604 FreeLineBox(line);
3605 // Mark the previous margin of the next line dirty since we need to
3606 // recompute its top position.
3607 if (aLine != aLineEnd) {
3608 aLine->MarkPreviousMarginDirty();
3614 * Reflow a line. The line will either contain a single block frame
3615 * or contain 1 or more inline frames. aKeepReflowGoing indicates
3616 * whether or not the caller should continue to reflow more lines.
3618 void nsBlockFrame::ReflowLine(BlockReflowState& aState, LineIterator aLine,
3619 bool* aKeepReflowGoing) {
3620 MOZ_ASSERT(aLine->GetChildCount(), "reflowing empty line");
3622 // Setup the line-layout for the new line
3623 aState.mCurrentLine = aLine;
3624 aLine->ClearDirty();
3625 aLine->InvalidateCachedIsEmpty();
3626 aLine->ClearHadFloatPushed();
3628 // If this line contains a single block that is hidden by `content-visibility`
3629 // don't reflow the line. If this line contains inlines and the first one is
3630 // hidden by `content-visibility`, all of them are, so avoid reflow in that
3631 // case as well.
3632 // For frames that own anonymous children, even the first child is hidden by
3633 // `content-visibility`, there could be some anonymous children need reflow,
3634 // so we don't skip reflow this line.
3635 nsIFrame* firstChild = aLine->mFirstChild;
3636 if (firstChild->IsHiddenByContentVisibilityOfInFlowParentForLayout() &&
3637 !HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES)) {
3638 return;
3641 // Now that we know what kind of line we have, reflow it
3642 if (aLine->IsBlock()) {
3643 ReflowBlockFrame(aState, aLine, aKeepReflowGoing);
3644 } else {
3645 aLine->SetLineWrapped(false);
3646 ReflowInlineFrames(aState, aLine, aKeepReflowGoing);
3648 // Store the line's float edges for overflow marker analysis if needed.
3649 aLine->ClearFloatEdges();
3650 if (aState.mFlags.mCanHaveOverflowMarkers) {
3651 WritingMode wm = aLine->mWritingMode;
3652 nsFlowAreaRect r = aState.GetFloatAvailableSpaceForBSize(
3653 aLine->BStart(), aLine->BSize(), nullptr);
3654 if (r.HasFloats()) {
3655 LogicalRect so = aLine->GetOverflowArea(OverflowType::Scrollable, wm,
3656 aLine->mContainerSize);
3657 nscoord s = r.mRect.IStart(wm);
3658 nscoord e = r.mRect.IEnd(wm);
3659 if (so.IEnd(wm) > e || so.IStart(wm) < s) {
3660 // This line is overlapping a float - store the edges marking the area
3661 // between the floats for text-overflow analysis.
3662 aLine->SetFloatEdges(s, e);
3668 aLine->ClearMovedFragments();
3671 nsIFrame* nsBlockFrame::PullFrame(BlockReflowState& aState,
3672 LineIterator aLine) {
3673 // First check our remaining lines.
3674 if (LinesEnd() != aLine.next()) {
3675 return PullFrameFrom(aLine, this, aLine.next());
3678 NS_ASSERTION(
3679 !GetOverflowLines(),
3680 "Our overflow lines should have been removed at the start of reflow");
3682 // Try each next-in-flow.
3683 nsBlockFrame* nextInFlow = aState.mNextInFlow;
3684 while (nextInFlow) {
3685 if (nextInFlow->mLines.empty()) {
3686 nextInFlow->DrainSelfOverflowList();
3688 if (!nextInFlow->mLines.empty()) {
3689 return PullFrameFrom(aLine, nextInFlow, nextInFlow->mLines.begin());
3691 nextInFlow = static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow());
3692 aState.mNextInFlow = nextInFlow;
3695 return nullptr;
3698 nsIFrame* nsBlockFrame::PullFrameFrom(nsLineBox* aLine,
3699 nsBlockFrame* aFromContainer,
3700 nsLineList::iterator aFromLine) {
3701 nsLineBox* fromLine = aFromLine;
3702 MOZ_ASSERT(fromLine, "bad line to pull from");
3703 MOZ_ASSERT(fromLine->GetChildCount(), "empty line");
3704 MOZ_ASSERT(aLine->GetChildCount(), "empty line");
3705 MOZ_ASSERT(!HasProperty(LineIteratorProperty()),
3706 "Shouldn't have line iterators mid-reflow");
3708 NS_ASSERTION(fromLine->IsBlock() == fromLine->mFirstChild->IsBlockOutside(),
3709 "Disagreement about whether it's a block or not");
3711 if (fromLine->IsBlock()) {
3712 // If our line is not empty and the child in aFromLine is a block
3713 // then we cannot pull up the frame into this line. In this case
3714 // we stop pulling.
3715 return nullptr;
3717 // Take frame from fromLine
3718 nsIFrame* frame = fromLine->mFirstChild;
3719 nsIFrame* newFirstChild = frame->GetNextSibling();
3721 if (aFromContainer != this) {
3722 // The frame is being pulled from a next-in-flow; therefore we need to add
3723 // it to our sibling list.
3724 MOZ_ASSERT(aLine == mLines.back());
3725 MOZ_ASSERT(aFromLine == aFromContainer->mLines.begin(),
3726 "should only pull from first line");
3727 aFromContainer->mFrames.RemoveFrame(frame);
3729 // When pushing and pulling frames we need to check for whether any
3730 // views need to be reparented.
3731 ReparentFrame(frame, aFromContainer, this);
3732 mFrames.AppendFrame(nullptr, frame);
3734 // The frame might have (or contain) floats that need to be brought
3735 // over too. (pass 'false' since there are no siblings to check)
3736 ReparentFloats(frame, aFromContainer, false);
3737 } else {
3738 MOZ_ASSERT(aLine == aFromLine.prev());
3741 aLine->NoteFrameAdded(frame);
3742 fromLine->NoteFrameRemoved(frame);
3744 if (fromLine->GetChildCount() > 0) {
3745 // Mark line dirty now that we pulled a child
3746 fromLine->MarkDirty();
3747 fromLine->mFirstChild = newFirstChild;
3748 } else {
3749 // Free up the fromLine now that it's empty.
3750 // Its bounds might need to be redrawn, though.
3751 if (aFromLine.next() != aFromContainer->mLines.end()) {
3752 aFromLine.next()->MarkPreviousMarginDirty();
3754 aFromContainer->mLines.erase(aFromLine);
3755 // aFromLine is now invalid
3756 aFromContainer->FreeLineBox(fromLine);
3759 #ifdef DEBUG
3760 VerifyLines(true);
3761 VerifyOverflowSituation();
3762 #endif
3764 return frame;
3767 void nsBlockFrame::SlideLine(BlockReflowState& aState, nsLineBox* aLine,
3768 nscoord aDeltaBCoord) {
3769 MOZ_ASSERT(aDeltaBCoord != 0, "why slide a line nowhere?");
3771 // Adjust line state
3772 aLine->SlideBy(aDeltaBCoord, aState.ContainerSize());
3774 // Adjust the frames in the line
3775 MoveChildFramesOfLine(aLine, aDeltaBCoord);
3778 void nsBlockFrame::UpdateLineContainerSize(nsLineBox* aLine,
3779 const nsSize& aNewContainerSize) {
3780 if (aNewContainerSize == aLine->mContainerSize) {
3781 return;
3784 // Adjust line state
3785 nsSize sizeDelta = aLine->UpdateContainerSize(aNewContainerSize);
3787 // Changing container width only matters if writing mode is vertical-rl
3788 if (GetWritingMode().IsVerticalRL()) {
3789 MoveChildFramesOfLine(aLine, sizeDelta.width);
3793 void nsBlockFrame::MoveChildFramesOfLine(nsLineBox* aLine,
3794 nscoord aDeltaBCoord) {
3795 // Adjust the frames in the line
3796 nsIFrame* kid = aLine->mFirstChild;
3797 if (!kid) {
3798 return;
3801 WritingMode wm = GetWritingMode();
3802 LogicalPoint translation(wm, 0, aDeltaBCoord);
3804 if (aLine->IsBlock()) {
3805 if (aDeltaBCoord) {
3806 kid->MovePositionBy(wm, translation);
3809 // Make sure the frame's view and any child views are updated
3810 nsContainerFrame::PlaceFrameView(kid);
3811 } else {
3812 // Adjust the block-dir coordinate of the frames in the line.
3813 // Note: we need to re-position views even if aDeltaBCoord is 0, because
3814 // one of our parent frames may have moved and so the view's position
3815 // relative to its parent may have changed.
3816 int32_t n = aLine->GetChildCount();
3817 while (--n >= 0) {
3818 if (aDeltaBCoord) {
3819 kid->MovePositionBy(wm, translation);
3821 // Make sure the frame's view and any child views are updated
3822 nsContainerFrame::PlaceFrameView(kid);
3823 kid = kid->GetNextSibling();
3828 static inline bool IsNonAutoNonZeroBSize(const StyleSize& aCoord) {
3829 // The "extremum length" values (see ExtremumLength) were originally aimed at
3830 // inline-size (or width, as it was before logicalization). For now, let them
3831 // return false here, so we treat them like 'auto' pending a real
3832 // implementation. (See bug 1126420.)
3834 // FIXME (bug 567039, bug 527285) This isn't correct for the 'fill' value,
3835 // which should more likely (but not necessarily, depending on the available
3836 // space) be returning true.
3837 if (aCoord.BehavesLikeInitialValueOnBlockAxis()) {
3838 return false;
3840 MOZ_ASSERT(aCoord.IsLengthPercentage());
3841 // If we evaluate the length/percent/calc at a percentage basis of
3842 // both nscoord_MAX and 0, and it's zero both ways, then it's a zero
3843 // length, percent, or combination thereof. Test > 0 so we clamp
3844 // negative calc() results to 0.
3845 return aCoord.AsLengthPercentage().Resolve(nscoord_MAX) > 0 ||
3846 aCoord.AsLengthPercentage().Resolve(0) > 0;
3849 /* virtual */
3850 bool nsBlockFrame::IsSelfEmpty() {
3851 if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
3852 return true;
3855 // Blocks which are margin-roots (including inline-blocks) cannot be treated
3856 // as empty for margin-collapsing and other purposes. They're more like
3857 // replaced elements.
3858 if (HasAnyStateBits(NS_BLOCK_MARGIN_ROOT)) {
3859 return false;
3862 WritingMode wm = GetWritingMode();
3863 const nsStylePosition* position = StylePosition();
3865 if (IsNonAutoNonZeroBSize(position->MinBSize(wm)) ||
3866 IsNonAutoNonZeroBSize(position->BSize(wm))) {
3867 return false;
3870 // FIXME: Bug 1646100 - Take intrinsic size into account.
3871 // FIXME: Handle the case that both inline and block sizes are auto.
3872 // https://github.com/w3c/csswg-drafts/issues/5060.
3873 // Note: block-size could be zero or auto/intrinsic keywords here.
3874 if (position->BSize(wm).BehavesLikeInitialValueOnBlockAxis() &&
3875 position->mAspectRatio.HasFiniteRatio()) {
3876 return false;
3879 const nsStyleBorder* border = StyleBorder();
3880 const nsStylePadding* padding = StylePadding();
3882 if (border->GetComputedBorderWidth(wm.PhysicalSide(eLogicalSideBStart)) !=
3883 0 ||
3884 border->GetComputedBorderWidth(wm.PhysicalSide(eLogicalSideBEnd)) != 0 ||
3885 !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBStart(wm)) ||
3886 !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBEnd(wm))) {
3887 return false;
3890 if (HasOutsideMarker() && !MarkerIsEmpty()) {
3891 return false;
3894 return true;
3897 bool nsBlockFrame::CachedIsEmpty() {
3898 if (!IsSelfEmpty()) {
3899 return false;
3901 for (auto& line : mLines) {
3902 if (!line.CachedIsEmpty()) {
3903 return false;
3906 return true;
3909 bool nsBlockFrame::IsEmpty() {
3910 if (!IsSelfEmpty()) {
3911 return false;
3914 return LinesAreEmpty(mLines);
3917 bool nsBlockFrame::ShouldApplyBStartMargin(BlockReflowState& aState,
3918 nsLineBox* aLine) {
3919 if (aLine->mFirstChild->IsPageBreakFrame()) {
3920 // A page break frame consumes margins adjacent to it.
3921 // https://drafts.csswg.org/css-break/#break-margins
3922 return false;
3925 if (aState.mFlags.mShouldApplyBStartMargin) {
3926 // Apply short-circuit check to avoid searching the line list
3927 return true;
3930 if (!aState.IsAdjacentWithBStart()) {
3931 // If we aren't at the start block-coordinate then something of non-zero
3932 // height must have been placed. Therefore the childs block-start margin
3933 // applies.
3934 aState.mFlags.mShouldApplyBStartMargin = true;
3935 return true;
3938 // Determine if this line is "essentially" the first line
3939 LineIterator line = LinesBegin();
3940 if (aState.mFlags.mHasLineAdjacentToTop) {
3941 line = aState.mLineAdjacentToTop;
3943 while (line != aLine) {
3944 if (!line->CachedIsEmpty() || line->HasClearance()) {
3945 // A line which precedes aLine is non-empty, or has clearance,
3946 // so therefore the block-start margin applies.
3947 aState.mFlags.mShouldApplyBStartMargin = true;
3948 return true;
3950 // No need to apply the block-start margin if the line has floats. We
3951 // should collapse anyway (bug 44419)
3952 ++line;
3953 aState.mFlags.mHasLineAdjacentToTop = true;
3954 aState.mLineAdjacentToTop = line;
3957 // The line being reflowed is "essentially" the first line in the
3958 // block. Therefore its block-start margin will be collapsed by the
3959 // generational collapsing logic with its parent (us).
3960 return false;
3963 void nsBlockFrame::ReflowBlockFrame(BlockReflowState& aState,
3964 LineIterator aLine,
3965 bool* aKeepReflowGoing) {
3966 MOZ_ASSERT(*aKeepReflowGoing, "bad caller");
3968 nsIFrame* frame = aLine->mFirstChild;
3969 if (!frame) {
3970 NS_ASSERTION(false, "program error - unexpected empty line");
3971 return;
3974 // If the previous frame was a page-break-frame, then preemptively push this
3975 // frame to the next page.
3976 // This is primarily important for the placeholders for abspos frames, which
3977 // measure as zero height and then would be placed on this page.
3978 if (aState.ContentBSize() != NS_UNCONSTRAINEDSIZE) {
3979 const nsIFrame* const prev = frame->GetPrevSibling();
3980 if (prev && prev->IsPageBreakFrame()) {
3981 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
3982 return;
3986 // Prepare the block reflow engine
3987 nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput);
3989 StyleClear clearType = frame->StyleDisplay()->mClear;
3990 if (aState.mTrailingClearFromPIF != StyleClear::None) {
3991 clearType = nsLayoutUtils::CombineClearType(clearType,
3992 aState.mTrailingClearFromPIF);
3993 aState.mTrailingClearFromPIF = StyleClear::None;
3996 // Clear past floats before the block if the clear style is not none
3997 aLine->ClearForcedLineBreak();
3998 if (clearType != StyleClear::None) {
3999 aLine->SetForcedLineBreakBefore(clearType);
4002 // See if we should apply the block-start margin. If the block frame being
4003 // reflowed is a continuation, then we don't apply its block-start margin
4004 // because it's not significant. Otherwise, dig deeper.
4005 bool applyBStartMargin =
4006 !frame->GetPrevContinuation() && ShouldApplyBStartMargin(aState, aLine);
4007 if (applyBStartMargin) {
4008 // The HasClearance setting is only valid if ShouldApplyBStartMargin
4009 // returned false (in which case the block-start margin-root set our
4010 // clearance flag). Otherwise clear it now. We'll set it later on
4011 // ourselves if necessary.
4012 aLine->ClearHasClearance();
4014 bool treatWithClearance = aLine->HasClearance();
4016 bool mightClearFloats = clearType != StyleClear::None;
4017 nsIFrame* floatAvoidingBlock = nullptr;
4018 if (!nsBlockFrame::BlockCanIntersectFloats(frame)) {
4019 mightClearFloats = true;
4020 floatAvoidingBlock = frame;
4023 // If our block-start margin was counted as part of some parent's block-start
4024 // margin collapse, and we are being speculatively reflowed assuming this
4025 // frame DID NOT need clearance, then we need to check that
4026 // assumption.
4027 if (!treatWithClearance && !applyBStartMargin && mightClearFloats &&
4028 aState.mReflowInput.mDiscoveredClearance) {
4029 nscoord curBCoord = aState.mBCoord + aState.mPrevBEndMargin.get();
4030 if (auto [clearBCoord, result] =
4031 aState.ClearFloats(curBCoord, clearType, floatAvoidingBlock);
4032 result != ClearFloatsResult::BCoordNoChange) {
4033 Unused << clearBCoord;
4035 // Only record the first frame that requires clearance
4036 if (!*aState.mReflowInput.mDiscoveredClearance) {
4037 *aState.mReflowInput.mDiscoveredClearance = frame;
4039 aState.mPrevChild = frame;
4040 // Exactly what we do now is flexible since we'll definitely be
4041 // reflowed.
4042 return;
4045 if (treatWithClearance) {
4046 applyBStartMargin = true;
4049 nsIFrame* clearanceFrame = nullptr;
4050 const nscoord startingBCoord = aState.mBCoord;
4051 const nsCollapsingMargin incomingMargin = aState.mPrevBEndMargin;
4052 nscoord clearance;
4053 // Save the original position of the frame so that we can reposition
4054 // its view as needed.
4055 nsPoint originalPosition = frame->GetPosition();
4056 while (true) {
4057 clearance = 0;
4058 nscoord bStartMargin = 0;
4059 bool mayNeedRetry = false;
4060 bool clearedFloats = false;
4061 bool clearedPushedOrSplitFloat = false;
4062 if (applyBStartMargin) {
4063 // Precompute the blocks block-start margin value so that we can get the
4064 // correct available space (there might be a float that's
4065 // already been placed below the aState.mPrevBEndMargin
4067 // Setup a reflowInput to get the style computed block-start margin
4068 // value. We'll use a reason of `resize' so that we don't fudge
4069 // any incremental reflow input.
4071 // The availSpace here is irrelevant to our needs - all we want
4072 // out if this setup is the block-start margin value which doesn't depend
4073 // on the childs available space.
4074 // XXX building a complete ReflowInput just to get the block-start
4075 // margin seems like a waste. And we do this for almost every block!
4076 WritingMode wm = frame->GetWritingMode();
4077 LogicalSize availSpace = aState.ContentSize(wm);
4078 ReflowInput reflowInput(aState.mPresContext, aState.mReflowInput, frame,
4079 availSpace);
4081 if (treatWithClearance) {
4082 aState.mBCoord += aState.mPrevBEndMargin.get();
4083 aState.mPrevBEndMargin.Zero();
4086 // Now compute the collapsed margin-block-start value into
4087 // aState.mPrevBEndMargin, assuming that all child margins
4088 // collapse down to clearanceFrame.
4089 brc.ComputeCollapsedBStartMargin(reflowInput, &aState.mPrevBEndMargin,
4090 clearanceFrame, &mayNeedRetry);
4092 // XXX optimization; we could check the collapsing children to see if they
4093 // are sure to require clearance, and so avoid retrying them
4095 if (clearanceFrame) {
4096 // Don't allow retries on the second pass. The clearance decisions for
4097 // the blocks whose block-start margins collapse with ours are now
4098 // fixed.
4099 mayNeedRetry = false;
4102 if (!treatWithClearance && !clearanceFrame && mightClearFloats) {
4103 // We don't know if we need clearance and this is the first,
4104 // optimistic pass. So determine whether *this block* needs
4105 // clearance. Note that we do not allow the decision for whether
4106 // this block has clearance to change on the second pass; that
4107 // decision is only allowed to be made under the optimistic
4108 // first pass.
4109 nscoord curBCoord = aState.mBCoord + aState.mPrevBEndMargin.get();
4110 if (auto [clearBCoord, result] =
4111 aState.ClearFloats(curBCoord, clearType, floatAvoidingBlock);
4112 result != ClearFloatsResult::BCoordNoChange) {
4113 Unused << clearBCoord;
4115 // Looks like we need clearance and we didn't know about it already.
4116 // So recompute collapsed margin
4117 treatWithClearance = true;
4118 // Remember this decision, needed for incremental reflow
4119 aLine->SetHasClearance();
4121 // Apply incoming margins
4122 aState.mBCoord += aState.mPrevBEndMargin.get();
4123 aState.mPrevBEndMargin.Zero();
4125 // Compute the collapsed margin again, ignoring the incoming margin
4126 // this time
4127 mayNeedRetry = false;
4128 brc.ComputeCollapsedBStartMargin(reflowInput, &aState.mPrevBEndMargin,
4129 clearanceFrame, &mayNeedRetry);
4133 // Temporarily advance the running block-direction value so that the
4134 // GetFloatAvailableSpace method will return the right available space.
4135 // This undone as soon as the horizontal margins are computed.
4136 bStartMargin = aState.mPrevBEndMargin.get();
4138 if (treatWithClearance) {
4139 nscoord currentBCoord = aState.mBCoord;
4140 // advance mBCoord to the clear position.
4141 auto [clearBCoord, result] =
4142 aState.ClearFloats(aState.mBCoord, clearType, floatAvoidingBlock);
4143 aState.mBCoord = clearBCoord;
4145 clearedFloats = result != ClearFloatsResult::BCoordNoChange;
4146 clearedPushedOrSplitFloat =
4147 result == ClearFloatsResult::FloatsPushedOrSplit;
4149 // Compute clearance. It's the amount we need to add to the block-start
4150 // border-edge of the frame, after applying collapsed margins
4151 // from the frame and its children, to get it to line up with
4152 // the block-end of the floats. The former is
4153 // currentBCoord + bStartMargin, the latter is the current
4154 // aState.mBCoord.
4155 // Note that negative clearance is possible
4156 clearance = aState.mBCoord - (currentBCoord + bStartMargin);
4158 // Add clearance to our block-start margin while we compute available
4159 // space for the frame
4160 bStartMargin += clearance;
4162 // Note that aState.mBCoord should stay where it is: at the block-start
4163 // border-edge of the frame
4164 } else {
4165 // Advance aState.mBCoord to the block-start border-edge of the frame.
4166 aState.mBCoord += bStartMargin;
4170 aLine->SetLineIsImpactedByFloat(false);
4172 // Here aState.mBCoord is the block-start border-edge of the block.
4173 // Compute the available space for the block
4174 nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace();
4175 WritingMode wm = aState.mReflowInput.GetWritingMode();
4176 LogicalRect availSpace = aState.ComputeBlockAvailSpace(
4177 frame, floatAvailableSpace, (floatAvoidingBlock));
4179 // The check for
4180 // (!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats)
4181 // is to some degree out of paranoia: if we reliably eat up block-start
4182 // margins at the top of the page as we ought to, it wouldn't be
4183 // needed.
4184 if ((!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats) &&
4185 (availSpace.BSize(wm) < 0 || clearedPushedOrSplitFloat)) {
4186 // We know already that this child block won't fit on this
4187 // page/column due to the block-start margin or the clearance. So we
4188 // need to get out of here now. (If we don't, most blocks will handle
4189 // things fine, and report break-before, but zero-height blocks
4190 // won't, and will thus make their parent overly-large and force
4191 // *it* to be pushed in its entirety.)
4192 aState.mBCoord = startingBCoord;
4193 aState.mPrevBEndMargin = incomingMargin;
4194 if (ShouldAvoidBreakInside(aState.mReflowInput)) {
4195 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
4196 } else {
4197 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
4199 return;
4202 // Now put the block-dir coordinate back to the start of the
4203 // block-start-margin + clearance.
4204 aState.mBCoord -= bStartMargin;
4205 availSpace.BStart(wm) -= bStartMargin;
4206 if (NS_UNCONSTRAINEDSIZE != availSpace.BSize(wm)) {
4207 availSpace.BSize(wm) += bStartMargin;
4210 // Construct the reflow input for the block.
4211 Maybe<ReflowInput> childReflowInput;
4212 Maybe<LogicalSize> cbSize;
4213 LogicalSize availSize = availSpace.Size(wm);
4214 bool columnSetWrapperHasNoBSizeLeft = false;
4215 if (Style()->GetPseudoType() == PseudoStyleType::columnContent) {
4216 // Calculate the multicol containing block's block size so that the
4217 // children with percentage block size get correct percentage basis.
4218 const ReflowInput* cbReflowInput =
4219 aState.mReflowInput.mParentReflowInput->mCBReflowInput;
4220 MOZ_ASSERT(cbReflowInput->mFrame->StyleColumn()->IsColumnContainerStyle(),
4221 "Get unexpected reflow input of multicol containing block!");
4223 // Use column-width as the containing block's inline-size, i.e. the column
4224 // content's computed inline-size.
4225 cbSize.emplace(LogicalSize(wm, aState.mReflowInput.ComputedISize(),
4226 cbReflowInput->ComputedBSize())
4227 .ConvertTo(frame->GetWritingMode(), wm));
4229 // If a ColumnSetWrapper is in a balancing column content, it may be
4230 // pushed or pulled back and forth between column contents. Always add
4231 // NS_FRAME_HAS_DIRTY_CHILDREN bit to it so that its ColumnSet children
4232 // can have a chance to reflow under current block size constraint.
4233 if (aState.mReflowInput.mFlags.mIsColumnBalancing &&
4234 frame->IsColumnSetWrapperFrame()) {
4235 frame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
4237 } else if (IsColumnSetWrapperFrame()) {
4238 // If we are reflowing our ColumnSet children, we want to apply our block
4239 // size constraint to the available block size when constructing reflow
4240 // input for ColumnSet so that ColumnSet can use it to compute its max
4241 // column block size.
4242 if (frame->IsColumnSetFrame()) {
4243 nscoord contentBSize = aState.mReflowInput.ComputedBSize();
4244 if (aState.mReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) {
4245 contentBSize =
4246 std::min(contentBSize, aState.mReflowInput.ComputedMaxBSize());
4248 if (contentBSize != NS_UNCONSTRAINEDSIZE) {
4249 // To get the remaining content block-size, subtract the content
4250 // block-size consumed by our previous continuations.
4251 contentBSize -= aState.mConsumedBSize;
4253 // ColumnSet is not the outermost frame in the column container, so it
4254 // cannot have any margin. We don't need to consider any margin that
4255 // can be generated by "box-decoration-break: clone" as we do in
4256 // BlockReflowState::ComputeBlockAvailSpace().
4257 const nscoord availContentBSize = std::max(
4258 0, contentBSize - (aState.mBCoord - aState.ContentBStart()));
4259 if (availSize.BSize(wm) >= availContentBSize) {
4260 availSize.BSize(wm) = availContentBSize;
4261 columnSetWrapperHasNoBSizeLeft = true;
4267 childReflowInput.emplace(aState.mPresContext, aState.mReflowInput, frame,
4268 availSize.ConvertTo(frame->GetWritingMode(), wm),
4269 cbSize);
4271 childReflowInput->mFlags.mColumnSetWrapperHasNoBSizeLeft =
4272 columnSetWrapperHasNoBSizeLeft;
4274 if (aLine->MovedFragments()) {
4275 // We only need to set this the first reflow, since if we reflow
4276 // again (and replace childReflowInput) we'll be reflowing it
4277 // again in the same fragment as the previous time.
4278 childReflowInput->mFlags.mMovedBlockFragments = true;
4281 nsFloatManager::SavedState floatManagerState;
4282 nsReflowStatus frameReflowStatus;
4283 do {
4284 if (floatAvailableSpace.HasFloats()) {
4285 // Set if floatAvailableSpace.HasFloats() is true for any
4286 // iteration of the loop.
4287 aLine->SetLineIsImpactedByFloat(true);
4290 // We might need to store into mDiscoveredClearance later if it's
4291 // currently null; we want to overwrite any writes that
4292 // brc.ReflowBlock() below does, so we need to remember now
4293 // whether it's empty.
4294 const bool shouldStoreClearance =
4295 aState.mReflowInput.mDiscoveredClearance &&
4296 !*aState.mReflowInput.mDiscoveredClearance;
4298 // Reflow the block into the available space
4299 if (mayNeedRetry || floatAvoidingBlock) {
4300 aState.FloatManager()->PushState(&floatManagerState);
4303 if (mayNeedRetry) {
4304 childReflowInput->mDiscoveredClearance = &clearanceFrame;
4305 } else if (!applyBStartMargin) {
4306 childReflowInput->mDiscoveredClearance =
4307 aState.mReflowInput.mDiscoveredClearance;
4310 frameReflowStatus.Reset();
4311 brc.ReflowBlock(availSpace, applyBStartMargin, aState.mPrevBEndMargin,
4312 clearance, aLine.get(), *childReflowInput,
4313 frameReflowStatus, aState);
4315 if (frameReflowStatus.IsInlineBreakBefore()) {
4316 // No need to retry this loop if there is a break opportunity before the
4317 // child block.
4318 break;
4321 // Now the block has a height. Using that height, get the
4322 // available space again and call ComputeBlockAvailSpace again.
4323 // If ComputeBlockAvailSpace gives a different result, we need to
4324 // reflow again.
4325 if (!floatAvoidingBlock) {
4326 break;
4329 LogicalRect oldFloatAvailableSpaceRect(floatAvailableSpace.mRect);
4330 floatAvailableSpace = aState.GetFloatAvailableSpaceForBSize(
4331 aState.mBCoord + bStartMargin, brc.GetMetrics().BSize(wm),
4332 &floatManagerState);
4333 NS_ASSERTION(floatAvailableSpace.mRect.BStart(wm) ==
4334 oldFloatAvailableSpaceRect.BStart(wm),
4335 "yikes");
4336 // Restore the height to the position of the next band.
4337 floatAvailableSpace.mRect.BSize(wm) =
4338 oldFloatAvailableSpaceRect.BSize(wm);
4339 // Determine whether the available space shrunk on either side,
4340 // because (the first time round) we now know the block's height,
4341 // and it may intersect additional floats, or (on later
4342 // iterations) because narrowing the width relative to the
4343 // previous time may cause the block to become taller. Note that
4344 // since we're reflowing the block, narrowing the width might also
4345 // make it shorter, so we must pass aCanGrow as true.
4346 if (!AvailableSpaceShrunk(wm, oldFloatAvailableSpaceRect,
4347 floatAvailableSpace.mRect, true)) {
4348 // The size and position we chose before are fine (i.e., they
4349 // don't cause intersecting with floats that requires a change
4350 // in size or position), so we're done.
4351 break;
4354 bool advanced = false;
4355 if (!aState.FloatAvoidingBlockFitsInAvailSpace(floatAvoidingBlock,
4356 floatAvailableSpace)) {
4357 // Advance to the next band.
4358 nscoord newBCoord = aState.mBCoord;
4359 if (aState.AdvanceToNextBand(floatAvailableSpace.mRect, &newBCoord)) {
4360 advanced = true;
4362 // ClearFloats might be able to advance us further once we're there.
4363 std::tie(aState.mBCoord, std::ignore) =
4364 aState.ClearFloats(newBCoord, StyleClear::None, floatAvoidingBlock);
4366 // Start over with a new available space rect at the new height.
4367 floatAvailableSpace = aState.GetFloatAvailableSpaceWithState(
4368 aState.mBCoord, ShapeType::ShapeOutside, &floatManagerState);
4371 const LogicalRect oldAvailSpace = availSpace;
4372 availSpace = aState.ComputeBlockAvailSpace(frame, floatAvailableSpace,
4373 (floatAvoidingBlock));
4375 if (!advanced && availSpace.IsEqualEdges(oldAvailSpace)) {
4376 break;
4379 // We need another reflow.
4380 aState.FloatManager()->PopState(&floatManagerState);
4382 if (!treatWithClearance && !applyBStartMargin &&
4383 aState.mReflowInput.mDiscoveredClearance) {
4384 // We set shouldStoreClearance above to record only the first
4385 // frame that requires clearance.
4386 if (shouldStoreClearance) {
4387 *aState.mReflowInput.mDiscoveredClearance = frame;
4389 aState.mPrevChild = frame;
4390 // Exactly what we do now is flexible since we'll definitely be
4391 // reflowed.
4392 return;
4395 if (advanced) {
4396 // We're pushing down the border-box, so we don't apply margin anymore.
4397 // This should never cause us to move up since the call to
4398 // GetFloatAvailableSpaceForBSize above included the margin.
4399 applyBStartMargin = false;
4400 bStartMargin = 0;
4401 treatWithClearance = true; // avoid hitting test above
4402 clearance = 0;
4405 childReflowInput.reset();
4406 childReflowInput.emplace(
4407 aState.mPresContext, aState.mReflowInput, frame,
4408 availSpace.Size(wm).ConvertTo(frame->GetWritingMode(), wm));
4409 } while (true);
4411 if (mayNeedRetry && clearanceFrame) {
4412 // Found a clearance frame, so we need to reflow |frame| a second time.
4413 // Restore the states and start over again.
4414 aState.FloatManager()->PopState(&floatManagerState);
4415 aState.mBCoord = startingBCoord;
4416 aState.mPrevBEndMargin = incomingMargin;
4417 continue;
4420 aState.mPrevChild = frame;
4422 if (childReflowInput->WillReflowAgainForClearance()) {
4423 // If an ancestor of ours is going to reflow for clearance, we
4424 // need to avoid calling PlaceBlock, because it unsets dirty bits
4425 // on the child block (both itself, and through its call to
4426 // nsIFrame::DidReflow), and those dirty bits imply dirtiness for
4427 // all of the child block, including the lines it didn't reflow.
4428 NS_ASSERTION(originalPosition == frame->GetPosition(),
4429 "we need to call PositionChildViews");
4430 return;
4433 #if defined(REFLOW_STATUS_COVERAGE)
4434 RecordReflowStatus(true, frameReflowStatus);
4435 #endif
4437 if (frameReflowStatus.IsInlineBreakBefore()) {
4438 // None of the child block fits.
4439 if (ShouldAvoidBreakInside(aState.mReflowInput)) {
4440 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
4441 } else {
4442 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
4444 } else {
4445 // Note: line-break-after a block is a nop
4447 // Try to place the child block.
4448 // Don't force the block to fit if we have positive clearance, because
4449 // pushing it to the next page would give it more room.
4450 // Don't force the block to fit if it's impacted by a float. If it is,
4451 // then pushing it to the next page would give it more room. Note that
4452 // isImpacted doesn't include impact from the block's own floats.
4453 bool forceFit = aState.IsAdjacentWithBStart() && clearance <= 0 &&
4454 !floatAvailableSpace.HasFloats();
4455 nsCollapsingMargin collapsedBEndMargin;
4456 OverflowAreas overflowAreas;
4457 *aKeepReflowGoing =
4458 brc.PlaceBlock(*childReflowInput, forceFit, aLine.get(),
4459 collapsedBEndMargin, overflowAreas, frameReflowStatus);
4460 if (!frameReflowStatus.IsFullyComplete() &&
4461 ShouldAvoidBreakInside(aState.mReflowInput)) {
4462 *aKeepReflowGoing = false;
4463 aLine->MarkDirty();
4466 if (aLine->SetCarriedOutBEndMargin(collapsedBEndMargin)) {
4467 LineIterator nextLine = aLine;
4468 ++nextLine;
4469 if (nextLine != LinesEnd()) {
4470 nextLine->MarkPreviousMarginDirty();
4474 aLine->SetOverflowAreas(overflowAreas);
4475 if (*aKeepReflowGoing) {
4476 // Some of the child block fit
4478 // Advance to new Y position
4479 nscoord newBCoord = aLine->BEnd();
4480 aState.mBCoord = newBCoord;
4482 // Continue the block frame now if it didn't completely fit in
4483 // the available space.
4484 if (!frameReflowStatus.IsFullyComplete()) {
4485 bool madeContinuation = CreateContinuationFor(aState, nullptr, frame);
4487 nsIFrame* nextFrame = frame->GetNextInFlow();
4488 NS_ASSERTION(nextFrame,
4489 "We're supposed to have a next-in-flow by now");
4491 if (frameReflowStatus.IsIncomplete()) {
4492 // If nextFrame used to be an overflow container, make it a normal
4493 // block
4494 if (!madeContinuation &&
4495 nextFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
4496 nsOverflowContinuationTracker::AutoFinish fini(
4497 aState.mOverflowTracker, frame);
4498 nsContainerFrame* parent = nextFrame->GetParent();
4499 parent->StealFrame(nextFrame);
4500 if (parent != this) {
4501 ReparentFrame(nextFrame, parent, this);
4503 mFrames.InsertFrame(nullptr, frame, nextFrame);
4504 madeContinuation = true; // needs to be added to mLines
4505 nextFrame->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
4506 frameReflowStatus.SetNextInFlowNeedsReflow();
4509 // Push continuation to a new line, but only if we actually made
4510 // one.
4511 if (madeContinuation) {
4512 nsLineBox* line = NewLineBox(nextFrame, true);
4513 mLines.after_insert(aLine, line);
4516 PushTruncatedLine(aState, aLine.next(), aKeepReflowGoing);
4518 // If we need to reflow the continuation of the block child,
4519 // then we'd better reflow our continuation
4520 if (frameReflowStatus.NextInFlowNeedsReflow()) {
4521 aState.mReflowStatus.SetNextInFlowNeedsReflow();
4522 // We also need to make that continuation's line dirty so
4523 // it gets reflowed when we reflow our next in flow. The
4524 // nif's line must always be either a line of the nif's
4525 // parent block (only if we didn't make a continuation) or
4526 // else one of our own overflow lines. In the latter case
4527 // the line is already marked dirty, so just handle the
4528 // first case.
4529 if (!madeContinuation) {
4530 nsBlockFrame* nifBlock = do_QueryFrame(nextFrame->GetParent());
4531 NS_ASSERTION(
4532 nifBlock,
4533 "A block's child's next in flow's parent must be a block!");
4534 for (auto& line : nifBlock->Lines()) {
4535 if (line.Contains(nextFrame)) {
4536 line.MarkDirty();
4537 break;
4543 // The block-end margin for a block is only applied on the last
4544 // flow block. Since we just continued the child block frame,
4545 // we know that line->mFirstChild is not the last flow block
4546 // therefore zero out the running margin value.
4547 #ifdef NOISY_BLOCK_DIR_MARGINS
4548 ListTag(stdout);
4549 printf(": reflow incomplete, frame=");
4550 frame->ListTag(stdout);
4551 printf(" prevBEndMargin=%d, setting to zero\n",
4552 aState.mPrevBEndMargin.get());
4553 #endif
4554 aState.mPrevBEndMargin.Zero();
4555 } else { // frame is complete but its overflow is not complete
4556 // Disconnect the next-in-flow and put it in our overflow tracker
4557 if (!madeContinuation &&
4558 !nextFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
4559 // It already exists, but as a normal next-in-flow, so we need
4560 // to dig it out of the child lists.
4561 nextFrame->GetParent()->StealFrame(nextFrame);
4562 } else if (madeContinuation) {
4563 mFrames.RemoveFrame(nextFrame);
4566 // Put it in our overflow list
4567 aState.mOverflowTracker->Insert(nextFrame, frameReflowStatus);
4568 aState.mReflowStatus.MergeCompletionStatusFrom(frameReflowStatus);
4570 #ifdef NOISY_BLOCK_DIR_MARGINS
4571 ListTag(stdout);
4572 printf(": reflow complete but overflow incomplete for ");
4573 frame->ListTag(stdout);
4574 printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n",
4575 aState.mPrevBEndMargin.get(), collapsedBEndMargin.get());
4576 #endif
4577 aState.mPrevBEndMargin = collapsedBEndMargin;
4579 } else { // frame is fully complete
4580 #ifdef NOISY_BLOCK_DIR_MARGINS
4581 ListTag(stdout);
4582 printf(": reflow complete for ");
4583 frame->ListTag(stdout);
4584 printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n",
4585 aState.mPrevBEndMargin.get(), collapsedBEndMargin.get());
4586 #endif
4587 aState.mPrevBEndMargin = collapsedBEndMargin;
4589 #ifdef NOISY_BLOCK_DIR_MARGINS
4590 ListTag(stdout);
4591 printf(": frame=");
4592 frame->ListTag(stdout);
4593 printf(" carriedOutBEndMargin=%d collapsedBEndMargin=%d => %d\n",
4594 brc.GetCarriedOutBEndMargin().get(), collapsedBEndMargin.get(),
4595 aState.mPrevBEndMargin.get());
4596 #endif
4597 } else {
4598 if (!frameReflowStatus.IsFullyComplete()) {
4599 // The frame reported an incomplete status, but then it also didn't
4600 // fit. This means we need to reflow it again so that it can
4601 // (again) report the incomplete status.
4602 frame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
4605 if ((aLine == mLines.front() && !GetPrevInFlow()) ||
4606 ShouldAvoidBreakInside(aState.mReflowInput)) {
4607 // If it's our very first line *or* we're not at the top of the page
4608 // and we have page-break-inside:avoid, then we need to be pushed to
4609 // our parent's next-in-flow.
4610 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
4611 } else {
4612 // Push the line that didn't fit and any lines that follow it
4613 // to our next-in-flow.
4614 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
4618 break; // out of the reflow retry loop
4621 // Now that we've got its final position all figured out, position any child
4622 // views it may have. Note that the case when frame has a view got handled
4623 // by FinishReflowChild, but that function didn't have the coordinates needed
4624 // to correctly decide whether to reposition child views.
4625 if (originalPosition != frame->GetPosition() && !frame->HasView()) {
4626 nsContainerFrame::PositionChildViews(frame);
4629 #ifdef DEBUG
4630 VerifyLines(true);
4631 #endif
4634 void nsBlockFrame::ReflowInlineFrames(BlockReflowState& aState,
4635 LineIterator aLine,
4636 bool* aKeepReflowGoing) {
4637 *aKeepReflowGoing = true;
4639 aLine->SetLineIsImpactedByFloat(false);
4641 // Setup initial coordinate system for reflowing the inline frames
4642 // into. Apply a previous block frame's block-end margin first.
4643 if (ShouldApplyBStartMargin(aState, aLine)) {
4644 aState.mBCoord += aState.mPrevBEndMargin.get();
4646 nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace();
4648 LineReflowStatus lineReflowStatus;
4649 do {
4650 nscoord availableSpaceBSize = 0;
4651 aState.mLineBSize.reset();
4652 do {
4653 bool allowPullUp = true;
4654 nsIFrame* forceBreakInFrame = nullptr;
4655 int32_t forceBreakOffset = -1;
4656 gfxBreakPriority forceBreakPriority = gfxBreakPriority::eNoBreak;
4657 do {
4658 nsFloatManager::SavedState floatManagerState;
4659 aState.FloatManager()->PushState(&floatManagerState);
4661 // Once upon a time we allocated the first 30 nsLineLayout objects
4662 // on the stack, and then we switched to the heap. At that time
4663 // these objects were large (1100 bytes on a 32 bit system).
4664 // Then the nsLineLayout object was shrunk to 156 bytes by
4665 // removing some internal buffers. Given that it is so much
4666 // smaller, the complexity of 2 different ways of allocating
4667 // no longer makes sense. Now we always allocate on the stack.
4668 nsLineLayout lineLayout(aState.mPresContext, aState.FloatManager(),
4669 aState.mReflowInput, &aLine, nullptr);
4670 lineLayout.Init(&aState, aState.mMinLineHeight, aState.mLineNumber);
4671 if (forceBreakInFrame) {
4672 lineLayout.ForceBreakAtPosition(forceBreakInFrame, forceBreakOffset);
4674 DoReflowInlineFrames(aState, lineLayout, aLine, floatAvailableSpace,
4675 availableSpaceBSize, &floatManagerState,
4676 aKeepReflowGoing, &lineReflowStatus, allowPullUp);
4677 lineLayout.EndLineReflow();
4679 if (LineReflowStatus::RedoNoPull == lineReflowStatus ||
4680 LineReflowStatus::RedoMoreFloats == lineReflowStatus ||
4681 LineReflowStatus::RedoNextBand == lineReflowStatus) {
4682 if (lineLayout.NeedsBackup()) {
4683 NS_ASSERTION(!forceBreakInFrame,
4684 "Backing up twice; this should never be necessary");
4685 // If there is no saved break position, then this will set
4686 // set forceBreakInFrame to null and we won't back up, which is
4687 // correct.
4688 forceBreakInFrame = lineLayout.GetLastOptionalBreakPosition(
4689 &forceBreakOffset, &forceBreakPriority);
4690 } else {
4691 forceBreakInFrame = nullptr;
4693 // restore the float manager state
4694 aState.FloatManager()->PopState(&floatManagerState);
4695 // Clear out float lists
4696 aState.mCurrentLineFloats.Clear();
4697 aState.mBelowCurrentLineFloats.Clear();
4698 aState.mNoWrapFloats.Clear();
4701 // Don't allow pullup on a subsequent LineReflowStatus::RedoNoPull pass
4702 allowPullUp = false;
4703 } while (LineReflowStatus::RedoNoPull == lineReflowStatus);
4704 } while (LineReflowStatus::RedoMoreFloats == lineReflowStatus);
4705 } while (LineReflowStatus::RedoNextBand == lineReflowStatus);
4708 void nsBlockFrame::SetBreakBeforeStatusBeforeLine(BlockReflowState& aState,
4709 LineIterator aLine,
4710 bool* aKeepReflowGoing) {
4711 aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
4712 // Reflow the line again when we reflow at our new position.
4713 aLine->MarkDirty();
4714 *aKeepReflowGoing = false;
4717 void nsBlockFrame::PushTruncatedLine(BlockReflowState& aState,
4718 LineIterator aLine,
4719 bool* aKeepReflowGoing) {
4720 PushLines(aState, aLine.prev());
4721 *aKeepReflowGoing = false;
4722 aState.mReflowStatus.SetIncomplete();
4725 void nsBlockFrame::DoReflowInlineFrames(
4726 BlockReflowState& aState, nsLineLayout& aLineLayout, LineIterator aLine,
4727 nsFlowAreaRect& aFloatAvailableSpace, nscoord& aAvailableSpaceBSize,
4728 nsFloatManager::SavedState* aFloatStateBeforeLine, bool* aKeepReflowGoing,
4729 LineReflowStatus* aLineReflowStatus, bool aAllowPullUp) {
4730 // Forget all of the floats on the line
4731 aLine->ClearFloats();
4732 aState.mFloatOverflowAreas.Clear();
4734 // We need to set this flag on the line if any of our reflow passes
4735 // are impacted by floats.
4736 if (aFloatAvailableSpace.HasFloats()) {
4737 aLine->SetLineIsImpactedByFloat(true);
4739 #ifdef REALLY_NOISY_REFLOW
4740 printf("nsBlockFrame::DoReflowInlineFrames %p impacted = %d\n", this,
4741 aFloatAvailableSpace.HasFloats());
4742 #endif
4744 WritingMode outerWM = aState.mReflowInput.GetWritingMode();
4745 WritingMode lineWM = WritingModeForLine(outerWM, aLine->mFirstChild);
4746 LogicalRect lineRect = aFloatAvailableSpace.mRect.ConvertTo(
4747 lineWM, outerWM, aState.ContainerSize());
4749 nscoord iStart = lineRect.IStart(lineWM);
4750 nscoord availISize = lineRect.ISize(lineWM);
4751 nscoord availBSize;
4752 if (aState.mReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
4753 availBSize = NS_UNCONSTRAINEDSIZE;
4754 } else {
4755 /* XXX get the height right! */
4756 availBSize = lineRect.BSize(lineWM);
4759 // Make sure to enable resize optimization before we call BeginLineReflow
4760 // because it might get disabled there
4761 aLine->EnableResizeReflowOptimization();
4763 aLineLayout.BeginLineReflow(
4764 iStart, aState.mBCoord, availISize, availBSize,
4765 aFloatAvailableSpace.HasFloats(), false, /*XXX isTopOfPage*/
4766 lineWM, aState.mContainerSize, aState.mInsetForBalance);
4768 aState.mFlags.mIsLineLayoutEmpty = false;
4770 // XXX Unfortunately we need to know this before reflowing the first
4771 // inline frame in the line. FIX ME.
4772 if (0 == aLineLayout.GetLineNumber() &&
4773 HasAllStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD |
4774 NS_BLOCK_HAS_FIRST_LETTER_STYLE)) {
4775 aLineLayout.SetFirstLetterStyleOK(true);
4777 NS_ASSERTION(!(HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD) &&
4778 GetPrevContinuation()),
4779 "first letter child bit should only be on first continuation");
4781 // Reflow the frames that are already on the line first
4782 LineReflowStatus lineReflowStatus = LineReflowStatus::OK;
4783 int32_t i;
4784 nsIFrame* frame = aLine->mFirstChild;
4786 if (aFloatAvailableSpace.HasFloats()) {
4787 // There is a soft break opportunity at the start of the line, because
4788 // we can always move this line down below float(s).
4789 if (aLineLayout.NotifyOptionalBreakPosition(
4790 frame, 0, true, gfxBreakPriority::eNormalBreak)) {
4791 lineReflowStatus = LineReflowStatus::RedoNextBand;
4795 // need to repeatedly call GetChildCount here, because the child
4796 // count can change during the loop!
4797 for (i = 0;
4798 LineReflowStatus::OK == lineReflowStatus && i < aLine->GetChildCount();
4799 i++, frame = frame->GetNextSibling()) {
4800 ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus);
4801 if (LineReflowStatus::OK != lineReflowStatus) {
4802 // It is possible that one or more of next lines are empty
4803 // (because of DeleteNextInFlowChild). If so, delete them now
4804 // in case we are finished.
4805 ++aLine;
4806 while ((aLine != LinesEnd()) && (0 == aLine->GetChildCount())) {
4807 // XXX Is this still necessary now that DeleteNextInFlowChild
4808 // uses DoRemoveFrame?
4809 nsLineBox* toremove = aLine;
4810 aLine = mLines.erase(aLine);
4811 NS_ASSERTION(nullptr == toremove->mFirstChild, "bad empty line");
4812 FreeLineBox(toremove);
4814 --aLine;
4816 NS_ASSERTION(lineReflowStatus != LineReflowStatus::Truncated,
4817 "ReflowInlineFrame should never determine that a line "
4818 "needs to go to the next page/column");
4822 // Don't pull up new frames into lines with continuation placeholders
4823 if (aAllowPullUp) {
4824 // Pull frames and reflow them until we can't
4825 while (LineReflowStatus::OK == lineReflowStatus) {
4826 frame = PullFrame(aState, aLine);
4827 if (!frame) {
4828 break;
4831 while (LineReflowStatus::OK == lineReflowStatus) {
4832 int32_t oldCount = aLine->GetChildCount();
4833 ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus);
4834 if (aLine->GetChildCount() != oldCount) {
4835 // We just created a continuation for aFrame AND its going
4836 // to end up on this line (e.g. :first-letter
4837 // situation). Therefore we have to loop here before trying
4838 // to pull another frame.
4839 frame = frame->GetNextSibling();
4840 } else {
4841 break;
4847 aState.mFlags.mIsLineLayoutEmpty = aLineLayout.LineIsEmpty();
4849 // We only need to backup if the line isn't going to be reflowed again anyway
4850 bool needsBackup = aLineLayout.NeedsBackup() &&
4851 (lineReflowStatus == LineReflowStatus::Stop ||
4852 lineReflowStatus == LineReflowStatus::OK);
4853 if (needsBackup && aLineLayout.HaveForcedBreakPosition()) {
4854 NS_WARNING(
4855 "We shouldn't be backing up more than once! "
4856 "Someone must have set a break opportunity beyond the available width, "
4857 "even though there were better break opportunities before it");
4858 needsBackup = false;
4860 if (needsBackup) {
4861 // We need to try backing up to before a text run
4862 // XXX It's possible, in fact not unusual, for the break opportunity to
4863 // already be the end of the line. We should detect that and optimize to not
4864 // re-do the line.
4865 if (aLineLayout.HasOptionalBreakPosition()) {
4866 // We can back up!
4867 lineReflowStatus = LineReflowStatus::RedoNoPull;
4869 } else {
4870 // In case we reflow this line again, remember that we don't
4871 // need to force any breaking
4872 aLineLayout.ClearOptionalBreakPosition();
4875 if (LineReflowStatus::RedoNextBand == lineReflowStatus) {
4876 // This happens only when we have a line that is impacted by
4877 // floats and the first element in the line doesn't fit with
4878 // the floats.
4880 // If there's block space available, we either try to reflow the line
4881 // past the current band (if it's non-zero and the band definitely won't
4882 // widen around a shape-outside), otherwise we try one pixel down. If
4883 // there's no block space available, we push the line to the next
4884 // page/column.
4885 NS_ASSERTION(
4886 NS_UNCONSTRAINEDSIZE != aFloatAvailableSpace.mRect.BSize(outerWM),
4887 "unconstrained block size on totally empty line");
4889 // See the analogous code for blocks in BlockReflowState::ClearFloats.
4890 nscoord bandBSize = aFloatAvailableSpace.mRect.BSize(outerWM);
4891 if (bandBSize > 0 ||
4892 NS_UNCONSTRAINEDSIZE == aState.mReflowInput.AvailableBSize()) {
4893 NS_ASSERTION(bandBSize == 0 || aFloatAvailableSpace.HasFloats(),
4894 "redo line on totally empty line with non-empty band...");
4895 // We should never hit this case if we've placed floats on the
4896 // line; if we have, then the GetFloatAvailableSpace call is wrong
4897 // and needs to happen after the caller pops the float manager
4898 // state.
4899 aState.FloatManager()->AssertStateMatches(aFloatStateBeforeLine);
4901 if (!aFloatAvailableSpace.MayWiden() && bandBSize > 0) {
4902 // Move it down far enough to clear the current band.
4903 aState.mBCoord += bandBSize;
4904 } else {
4905 // Move it down by one dev pixel.
4906 aState.mBCoord += aState.mPresContext->DevPixelsToAppUnits(1);
4909 aFloatAvailableSpace = aState.GetFloatAvailableSpace();
4910 } else {
4911 // There's nowhere to retry placing the line, so we want to push
4912 // it to the next page/column where its contents can fit not
4913 // next to a float.
4914 lineReflowStatus = LineReflowStatus::Truncated;
4915 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
4918 // XXX: a small optimization can be done here when paginating:
4919 // if the new Y coordinate is past the end of the block then
4920 // push the line and return now instead of later on after we are
4921 // past the float.
4922 } else if (LineReflowStatus::Truncated != lineReflowStatus &&
4923 LineReflowStatus::RedoNoPull != lineReflowStatus) {
4924 // If we are propagating out a break-before status then there is
4925 // no point in placing the line.
4926 if (!aState.mReflowStatus.IsInlineBreakBefore()) {
4927 if (!PlaceLine(aState, aLineLayout, aLine, aFloatStateBeforeLine,
4928 aFloatAvailableSpace, aAvailableSpaceBSize,
4929 aKeepReflowGoing)) {
4930 lineReflowStatus = LineReflowStatus::RedoMoreFloats;
4931 // PlaceLine already called GetFloatAvailableSpaceForBSize or its
4932 // variant for us.
4936 #ifdef DEBUG
4937 if (gNoisyReflow) {
4938 printf("Line reflow status = %s\n",
4939 LineReflowStatusToString(lineReflowStatus));
4941 #endif
4943 if (aLineLayout.GetDirtyNextLine()) {
4944 // aLine may have been pushed to the overflow lines.
4945 FrameLines* overflowLines = GetOverflowLines();
4946 // We can't just compare iterators front() to aLine here, since they may be
4947 // in different lists.
4948 bool pushedToOverflowLines =
4949 overflowLines && overflowLines->mLines.front() == aLine.get();
4950 if (pushedToOverflowLines) {
4951 // aLine is stale, it's associated with the main line list but it should
4952 // be associated with the overflow line list now
4953 aLine = overflowLines->mLines.begin();
4955 nsBlockInFlowLineIterator iter(this, aLine, pushedToOverflowLines);
4956 if (iter.Next() && iter.GetLine()->IsInline()) {
4957 iter.GetLine()->MarkDirty();
4958 if (iter.GetContainer() != this) {
4959 aState.mReflowStatus.SetNextInFlowNeedsReflow();
4964 *aLineReflowStatus = lineReflowStatus;
4968 * Reflow an inline frame. The reflow status is mapped from the frames
4969 * reflow status to the lines reflow status (not to our reflow status).
4970 * The line reflow status is simple: true means keep placing frames
4971 * on the line; false means don't (the line is done). If the line
4972 * has some sort of breaking affect then aLine's break-type will be set
4973 * to something other than StyleClear::None.
4975 void nsBlockFrame::ReflowInlineFrame(BlockReflowState& aState,
4976 nsLineLayout& aLineLayout,
4977 LineIterator aLine, nsIFrame* aFrame,
4978 LineReflowStatus* aLineReflowStatus) {
4979 MOZ_ASSERT(aFrame);
4980 *aLineReflowStatus = LineReflowStatus::OK;
4982 #ifdef NOISY_FIRST_LETTER
4983 ListTag(stdout);
4984 printf(": reflowing ");
4985 aFrame->ListTag(stdout);
4986 printf(" reflowingFirstLetter=%s\n",
4987 aLineLayout.GetFirstLetterStyleOK() ? "on" : "off");
4988 #endif
4990 if (aFrame->IsPlaceholderFrame()) {
4991 auto ph = static_cast<nsPlaceholderFrame*>(aFrame);
4992 ph->ForgetLineIsEmptySoFar();
4995 // Reflow the inline frame
4996 nsReflowStatus frameReflowStatus;
4997 bool pushedFrame;
4998 aLineLayout.ReflowFrame(aFrame, frameReflowStatus, nullptr, pushedFrame);
5000 if (frameReflowStatus.NextInFlowNeedsReflow()) {
5001 aLineLayout.SetDirtyNextLine();
5004 #ifdef REALLY_NOISY_REFLOW
5005 aFrame->ListTag(stdout);
5006 printf(": status=%s\n", ToString(frameReflowStatus).c_str());
5007 #endif
5009 #if defined(REFLOW_STATUS_COVERAGE)
5010 RecordReflowStatus(false, frameReflowStatus);
5011 #endif
5013 // Send post-reflow notification
5014 aState.mPrevChild = aFrame;
5016 /* XXX
5017 This is where we need to add logic to handle some odd behavior.
5018 For one thing, we should usually place at least one thing next
5019 to a left float, even when that float takes up all the width on a line.
5020 see bug 22496
5023 // Process the child frames reflow status. There are 5 cases:
5024 // complete, not-complete, break-before, break-after-complete,
5025 // break-after-not-complete. There are two situations: we are a
5026 // block or we are an inline. This makes a total of 10 cases
5027 // (fortunately, there is some overlap).
5028 aLine->ClearForcedLineBreak();
5029 if (frameReflowStatus.IsInlineBreak() ||
5030 aState.mTrailingClearFromPIF != StyleClear::None) {
5031 // Always abort the line reflow (because a line break is the
5032 // minimal amount of break we do).
5033 *aLineReflowStatus = LineReflowStatus::Stop;
5035 // XXX what should aLine's break-type be set to in all these cases?
5036 if (frameReflowStatus.IsInlineBreakBefore()) {
5037 // Break-before cases.
5038 if (aFrame == aLine->mFirstChild) {
5039 // If we break before the first frame on the line then we must
5040 // be trying to place content where there's no room (e.g. on a
5041 // line with wide floats). Inform the caller to reflow the
5042 // line after skipping past a float.
5043 *aLineReflowStatus = LineReflowStatus::RedoNextBand;
5044 } else {
5045 // It's not the first child on this line so go ahead and split
5046 // the line. We will see the frame again on the next-line.
5047 SplitLine(aState, aLineLayout, aLine, aFrame, aLineReflowStatus);
5049 // If we're splitting the line because the frame didn't fit and it
5050 // was pushed, then mark the line as having word wrapped. We need to
5051 // know that if we're shrink wrapping our width
5052 if (pushedFrame) {
5053 aLine->SetLineWrapped(true);
5056 } else {
5057 MOZ_ASSERT(frameReflowStatus.IsInlineBreakAfter() ||
5058 aState.mTrailingClearFromPIF != StyleClear::None,
5059 "We should've handled inline break-before in the if-branch!");
5061 // If a float split and its prev-in-flow was followed by a <BR>, then
5062 // combine the <BR>'s float clear type with the inline's float clear type
5063 // (the inline will be the very next frame after the split float).
5064 StyleClear clearType = frameReflowStatus.FloatClearType();
5065 if (aState.mTrailingClearFromPIF != StyleClear::None) {
5066 clearType = nsLayoutUtils::CombineClearType(
5067 clearType, aState.mTrailingClearFromPIF);
5068 aState.mTrailingClearFromPIF = StyleClear::None;
5070 // Break-after cases
5071 if (clearType != StyleClear::None || aLineLayout.GetLineEndsInBR()) {
5072 aLine->SetForcedLineBreakAfter(clearType);
5074 if (frameReflowStatus.IsComplete()) {
5075 // Split line, but after the frame just reflowed
5076 SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(),
5077 aLineReflowStatus);
5079 if (frameReflowStatus.IsInlineBreakAfter() &&
5080 !aLineLayout.GetLineEndsInBR()) {
5081 aLineLayout.SetDirtyNextLine();
5087 if (!frameReflowStatus.IsFullyComplete()) {
5088 // Create a continuation for the incomplete frame. Note that the
5089 // frame may already have a continuation.
5090 CreateContinuationFor(aState, aLine, aFrame);
5092 // Remember that the line has wrapped
5093 if (!aLineLayout.GetLineEndsInBR()) {
5094 aLine->SetLineWrapped(true);
5097 // If we just ended a first-letter frame or reflowed a placeholder then
5098 // don't split the line and don't stop the line reflow...
5099 // But if we are going to stop anyways we'd better split the line.
5100 if ((!frameReflowStatus.FirstLetterComplete() &&
5101 !aFrame->IsPlaceholderFrame()) ||
5102 *aLineReflowStatus == LineReflowStatus::Stop) {
5103 // Split line after the current frame
5104 *aLineReflowStatus = LineReflowStatus::Stop;
5105 SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(),
5106 aLineReflowStatus);
5111 bool nsBlockFrame::CreateContinuationFor(BlockReflowState& aState,
5112 nsLineBox* aLine, nsIFrame* aFrame) {
5113 nsIFrame* newFrame = nullptr;
5115 if (!aFrame->GetNextInFlow()) {
5116 newFrame =
5117 PresShell()->FrameConstructor()->CreateContinuingFrame(aFrame, this);
5119 mFrames.InsertFrame(nullptr, aFrame, newFrame);
5121 if (aLine) {
5122 aLine->NoteFrameAdded(newFrame);
5125 #ifdef DEBUG
5126 VerifyLines(false);
5127 #endif
5128 return !!newFrame;
5131 void nsBlockFrame::SplitFloat(BlockReflowState& aState, nsIFrame* aFloat,
5132 const nsReflowStatus& aFloatStatus) {
5133 MOZ_ASSERT(!aFloatStatus.IsFullyComplete(),
5134 "why split the frame if it's fully complete?");
5135 MOZ_ASSERT(aState.mBlock == this);
5137 nsIFrame* nextInFlow = aFloat->GetNextInFlow();
5138 if (nextInFlow) {
5139 nsContainerFrame* oldParent = nextInFlow->GetParent();
5140 oldParent->StealFrame(nextInFlow);
5141 if (oldParent != this) {
5142 ReparentFrame(nextInFlow, oldParent, this);
5144 if (!aFloatStatus.IsOverflowIncomplete()) {
5145 nextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
5147 } else {
5148 nextInFlow =
5149 PresShell()->FrameConstructor()->CreateContinuingFrame(aFloat, this);
5151 if (aFloatStatus.IsOverflowIncomplete()) {
5152 nextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
5155 StyleFloat floatStyle = aFloat->StyleDisplay()->mFloat;
5156 if (floatStyle == StyleFloat::Left) {
5157 aState.FloatManager()->SetSplitLeftFloatAcrossBreak();
5158 } else {
5159 MOZ_ASSERT(floatStyle == StyleFloat::Right, "Unexpected float side!");
5160 aState.FloatManager()->SetSplitRightFloatAcrossBreak();
5163 aState.AppendPushedFloatChain(nextInFlow);
5164 if (MOZ_LIKELY(!HasAnyStateBits(NS_BLOCK_FLOAT_MGR)) ||
5165 MOZ_UNLIKELY(IsTrueOverflowContainer())) {
5166 aState.mReflowStatus.SetOverflowIncomplete();
5167 } else {
5168 aState.mReflowStatus.SetIncomplete();
5172 static bool CheckPlaceholderInLine(nsIFrame* aBlock, nsLineBox* aLine,
5173 nsIFrame* aFloat) {
5174 if (!aFloat) {
5175 return true;
5177 NS_ASSERTION(!aFloat->GetPrevContinuation(),
5178 "float in a line should never be a continuation");
5179 NS_ASSERTION(!aFloat->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
5180 "float in a line should never be a pushed float");
5181 nsIFrame* ph = aFloat->FirstInFlow()->GetPlaceholderFrame();
5182 for (nsIFrame* f = ph; f; f = f->GetParent()) {
5183 if (f->GetParent() == aBlock) {
5184 return aLine->Contains(f);
5187 NS_ASSERTION(false, "aBlock is not an ancestor of aFrame!");
5188 return true;
5191 void nsBlockFrame::SplitLine(BlockReflowState& aState,
5192 nsLineLayout& aLineLayout, LineIterator aLine,
5193 nsIFrame* aFrame,
5194 LineReflowStatus* aLineReflowStatus) {
5195 MOZ_ASSERT(aLine->IsInline(), "illegal SplitLine on block line");
5197 int32_t pushCount =
5198 aLine->GetChildCount() - aLineLayout.GetCurrentSpanCount();
5199 MOZ_ASSERT(pushCount >= 0, "bad push count");
5201 #ifdef DEBUG
5202 if (gNoisyReflow) {
5203 nsIFrame::IndentBy(stdout, gNoiseIndent);
5204 printf("split line: from line=%p pushCount=%d aFrame=",
5205 static_cast<void*>(aLine.get()), pushCount);
5206 if (aFrame) {
5207 aFrame->ListTag(stdout);
5208 } else {
5209 printf("(null)");
5211 printf("\n");
5212 if (gReallyNoisyReflow) {
5213 aLine->List(stdout, gNoiseIndent + 1);
5216 #endif
5218 if (0 != pushCount) {
5219 MOZ_ASSERT(aLine->GetChildCount() > pushCount, "bad push");
5220 MOZ_ASSERT(nullptr != aFrame, "whoops");
5221 #ifdef DEBUG
5223 nsIFrame* f = aFrame;
5224 int32_t count = pushCount;
5225 while (f && count > 0) {
5226 f = f->GetNextSibling();
5227 --count;
5229 NS_ASSERTION(count == 0, "Not enough frames to push");
5231 #endif
5233 // Put frames being split out into their own line
5234 nsLineBox* newLine = NewLineBox(aLine, aFrame, pushCount);
5235 mLines.after_insert(aLine, newLine);
5236 #ifdef DEBUG
5237 if (gReallyNoisyReflow) {
5238 newLine->List(stdout, gNoiseIndent + 1);
5240 #endif
5242 // Let line layout know that some frames are no longer part of its
5243 // state.
5244 aLineLayout.SplitLineTo(aLine->GetChildCount());
5246 // If floats have been placed whose placeholders have been pushed to the new
5247 // line, we need to reflow the old line again. We don't want to look at the
5248 // frames in the new line, because as a large paragraph is laid out the
5249 // we'd get O(N^2) performance. So instead we just check that the last
5250 // float and the last below-current-line float are still in aLine.
5251 if (!CheckPlaceholderInLine(
5252 this, aLine,
5253 aLine->HasFloats() ? aLine->Floats().LastElement() : nullptr) ||
5254 !CheckPlaceholderInLine(
5255 this, aLine,
5256 aState.mBelowCurrentLineFloats.SafeLastElement(nullptr))) {
5257 *aLineReflowStatus = LineReflowStatus::RedoNoPull;
5260 #ifdef DEBUG
5261 VerifyLines(true);
5262 #endif
5266 bool nsBlockFrame::IsLastLine(BlockReflowState& aState, LineIterator aLine) {
5267 while (++aLine != LinesEnd()) {
5268 // There is another line
5269 if (0 != aLine->GetChildCount()) {
5270 // If the next line is a block line then this line is the last in a
5271 // group of inline lines.
5272 return aLine->IsBlock();
5274 // The next line is empty, try the next one
5277 // Try our next-in-flows lines to answer the question
5278 nsBlockFrame* nextInFlow = (nsBlockFrame*)GetNextInFlow();
5279 while (nullptr != nextInFlow) {
5280 for (const auto& line : nextInFlow->Lines()) {
5281 if (0 != line.GetChildCount()) {
5282 return line.IsBlock();
5285 nextInFlow = (nsBlockFrame*)nextInFlow->GetNextInFlow();
5288 // This is the last line - so don't allow justification
5289 return true;
5292 bool nsBlockFrame::PlaceLine(BlockReflowState& aState,
5293 nsLineLayout& aLineLayout, LineIterator aLine,
5294 nsFloatManager::SavedState* aFloatStateBeforeLine,
5295 nsFlowAreaRect& aFlowArea,
5296 nscoord& aAvailableSpaceBSize,
5297 bool* aKeepReflowGoing) {
5298 // Try to position the floats in a nowrap context.
5299 aLineLayout.FlushNoWrapFloats();
5301 // Trim extra white-space from the line before placing the frames
5302 aLineLayout.TrimTrailingWhiteSpace();
5304 // Vertically align the frames on this line.
5306 // According to the CSS2 spec, section 12.6.1, the "marker" box
5307 // participates in the height calculation of the list-item box's
5308 // first line box.
5310 // There are exactly two places a ::marker can be placed: near the
5311 // first or second line. It's only placed on the second line in a
5312 // rare case: when the first line is empty.
5313 WritingMode wm = aState.mReflowInput.GetWritingMode();
5314 bool addedMarker = false;
5315 if (HasOutsideMarker() &&
5316 ((aLine == mLines.front() &&
5317 (!aLineLayout.IsZeroBSize() || (aLine == mLines.back()))) ||
5318 (mLines.front() != mLines.back() && 0 == mLines.front()->BSize() &&
5319 aLine == mLines.begin().next()))) {
5320 ReflowOutput metrics(aState.mReflowInput);
5321 nsIFrame* marker = GetOutsideMarker();
5322 ReflowOutsideMarker(marker, aState, metrics, aState.mBCoord);
5323 NS_ASSERTION(!MarkerIsEmpty() || metrics.BSize(wm) == 0,
5324 "empty ::marker frame took up space");
5325 aLineLayout.AddMarkerFrame(marker, metrics);
5326 addedMarker = true;
5328 aLineLayout.VerticalAlignLine();
5330 // We want to consider the floats in the current line when determining
5331 // whether the float available space is shrunk. If mLineBSize doesn't
5332 // exist, we are in the first pass trying to place the line. Calling
5333 // GetFloatAvailableSpace() like we did in BlockReflowState::AddFloat()
5334 // for UpdateBand().
5336 // floatAvailableSpaceWithOldLineBSize is the float available space with
5337 // the old BSize, but including the floats that were added in this line.
5338 LogicalRect floatAvailableSpaceWithOldLineBSize =
5339 aState.mLineBSize.isNothing()
5340 ? aState.GetFloatAvailableSpace(aLine->BStart()).mRect
5341 : aState
5342 .GetFloatAvailableSpaceForBSize(
5343 aLine->BStart(), aState.mLineBSize.value(), nullptr)
5344 .mRect;
5346 // As we redo for floats, we can't reduce the amount of BSize we're
5347 // checking.
5348 aAvailableSpaceBSize = std::max(aAvailableSpaceBSize, aLine->BSize());
5349 LogicalRect floatAvailableSpaceWithLineBSize =
5350 aState
5351 .GetFloatAvailableSpaceForBSize(aLine->BStart(), aAvailableSpaceBSize,
5352 nullptr)
5353 .mRect;
5355 // If the available space between the floats is smaller now that we
5356 // know the BSize, return false (and cause another pass with
5357 // LineReflowStatus::RedoMoreFloats). We ensure aAvailableSpaceBSize
5358 // never decreases, which means that we can't reduce the set of floats
5359 // we intersect, which means that the available space cannot grow.
5360 if (AvailableSpaceShrunk(wm, floatAvailableSpaceWithOldLineBSize,
5361 floatAvailableSpaceWithLineBSize, false)) {
5362 // Prepare data for redoing the line.
5363 aState.mLineBSize = Some(aLine->BSize());
5365 // Since we want to redo the line, we update aFlowArea by using the
5366 // aFloatStateBeforeLine, which is the float manager's state before the
5367 // line is placed.
5368 LogicalRect oldFloatAvailableSpace(aFlowArea.mRect);
5369 aFlowArea = aState.GetFloatAvailableSpaceForBSize(
5370 aLine->BStart(), aAvailableSpaceBSize, aFloatStateBeforeLine);
5372 NS_ASSERTION(
5373 aFlowArea.mRect.BStart(wm) == oldFloatAvailableSpace.BStart(wm),
5374 "yikes");
5375 // Restore the BSize to the position of the next band.
5376 aFlowArea.mRect.BSize(wm) = oldFloatAvailableSpace.BSize(wm);
5378 // Enforce both IStart() and IEnd() never move outwards to prevent
5379 // infinite grow-shrink loops.
5380 const nscoord iStartDiff =
5381 aFlowArea.mRect.IStart(wm) - oldFloatAvailableSpace.IStart(wm);
5382 const nscoord iEndDiff =
5383 aFlowArea.mRect.IEnd(wm) - oldFloatAvailableSpace.IEnd(wm);
5384 if (iStartDiff < 0) {
5385 aFlowArea.mRect.IStart(wm) -= iStartDiff;
5386 aFlowArea.mRect.ISize(wm) += iStartDiff;
5388 if (iEndDiff > 0) {
5389 aFlowArea.mRect.ISize(wm) -= iEndDiff;
5392 return false;
5395 #ifdef DEBUG
5396 if (!GetParent()->IsAbsurdSizeAssertSuppressed()) {
5397 static nscoord lastHeight = 0;
5398 if (ABSURD_SIZE(aLine->BStart())) {
5399 lastHeight = aLine->BStart();
5400 if (abs(aLine->BStart() - lastHeight) > ABSURD_COORD / 10) {
5401 nsIFrame::ListTag(stdout);
5402 printf(": line=%p y=%d line.bounds.height=%d\n",
5403 static_cast<void*>(aLine.get()), aLine->BStart(),
5404 aLine->BSize());
5406 } else {
5407 lastHeight = 0;
5410 #endif
5412 // Only block frames horizontally align their children because
5413 // inline frames "shrink-wrap" around their children (therefore
5414 // there is no extra horizontal space).
5415 const nsStyleText* styleText = StyleText();
5418 * We don't care checking for IsLastLine properly if we don't care (if it
5419 * can't change the used text-align value for the line).
5421 * In other words, isLastLine really means isLastLineAndWeCare.
5423 const bool isLastLine =
5424 !IsInSVGTextSubtree() &&
5425 styleText->TextAlignForLastLine() != styleText->mTextAlign &&
5426 (aLineLayout.GetLineEndsInBR() || IsLastLine(aState, aLine));
5428 aLineLayout.TextAlignLine(aLine, isLastLine);
5430 // From here on, pfd->mBounds rectangles are incorrect because bidi
5431 // might have moved frames around!
5432 OverflowAreas overflowAreas;
5433 aLineLayout.RelativePositionFrames(overflowAreas);
5434 aLine->SetOverflowAreas(overflowAreas);
5435 if (addedMarker) {
5436 aLineLayout.RemoveMarkerFrame(GetOutsideMarker());
5439 // Inline lines do not have margins themselves; however they are
5440 // impacted by prior block margins. If this line ends up having some
5441 // height then we zero out the previous block-end margin value that was
5442 // already applied to the line's starting Y coordinate. Otherwise we
5443 // leave it be so that the previous blocks block-end margin can be
5444 // collapsed with a block that follows.
5445 nscoord newBCoord;
5447 if (!aLine->CachedIsEmpty()) {
5448 // This line has some height. Therefore the application of the
5449 // previous-bottom-margin should stick.
5450 aState.mPrevBEndMargin.Zero();
5451 newBCoord = aLine->BEnd();
5452 } else {
5453 // Don't let the previous-bottom-margin value affect the newBCoord
5454 // coordinate (it was applied in ReflowInlineFrames speculatively)
5455 // since the line is empty.
5456 // We already called |ShouldApplyBStartMargin|, and if we applied it
5457 // then mShouldApplyBStartMargin is set.
5458 nscoord dy = aState.mFlags.mShouldApplyBStartMargin
5459 ? -aState.mPrevBEndMargin.get()
5460 : 0;
5461 newBCoord = aState.mBCoord + dy;
5464 if (!aState.mReflowStatus.IsFullyComplete() &&
5465 ShouldAvoidBreakInside(aState.mReflowInput)) {
5466 aLine->AppendFloats(std::move(aState.mCurrentLineFloats));
5467 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
5468 return true;
5471 // See if the line fit (our first line always does).
5472 if (mLines.front() != aLine &&
5473 aState.ContentBSize() != NS_UNCONSTRAINEDSIZE &&
5474 newBCoord > aState.ContentBEnd()) {
5475 NS_ASSERTION(aState.mCurrentLine == aLine, "oops");
5476 if (ShouldAvoidBreakInside(aState.mReflowInput)) {
5477 // All our content doesn't fit, start on the next page.
5478 SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
5479 } else {
5480 // Push aLine and all of its children and anything else that
5481 // follows to our next-in-flow.
5482 PushTruncatedLine(aState, aLine, aKeepReflowGoing);
5484 return true;
5487 // Note that any early return before this update of aState.mBCoord
5488 // must either (a) return false or (b) set aKeepReflowGoing to false.
5489 // Otherwise we'll keep reflowing later lines at an incorrect
5490 // position, and we might not come back and clean up the damage later.
5491 aState.mBCoord = newBCoord;
5493 // Add the already placed current-line floats to the line
5494 aLine->AppendFloats(std::move(aState.mCurrentLineFloats));
5496 // Any below current line floats to place?
5497 if (!aState.mBelowCurrentLineFloats.IsEmpty()) {
5498 // Reflow the below-current-line floats, which places on the line's
5499 // float list.
5500 aState.PlaceBelowCurrentLineFloats(aLine);
5503 // When a line has floats, factor them into the overflow areas computations.
5504 if (aLine->HasFloats()) {
5505 // Union the float overflow areas (stored in aState) and the value computed
5506 // by the line layout code.
5507 OverflowAreas lineOverflowAreas = aState.mFloatOverflowAreas;
5508 lineOverflowAreas.UnionWith(aLine->GetOverflowAreas());
5509 aLine->SetOverflowAreas(lineOverflowAreas);
5511 #ifdef NOISY_OVERFLOW_AREAS
5512 printf("%s: Line %p, InkOverflowRect=%s, ScrollableOverflowRect=%s\n",
5513 ListTag().get(), aLine.get(),
5514 ToString(aLine->InkOverflowRect()).c_str(),
5515 ToString(aLine->ScrollableOverflowRect()).c_str());
5516 #endif
5519 // Apply break-after clearing if necessary
5520 // This must stay in sync with |ReflowDirtyLines|.
5521 if (aLine->HasFloatClearTypeAfter()) {
5522 std::tie(aState.mBCoord, std::ignore) =
5523 aState.ClearFloats(aState.mBCoord, aLine->FloatClearTypeAfter());
5525 return true;
5528 void nsBlockFrame::PushLines(BlockReflowState& aState,
5529 nsLineList::iterator aLineBefore) {
5530 // NOTE: aLineBefore is always a normal line, not an overflow line.
5531 // The following expression will assert otherwise.
5532 DebugOnly<bool> check = aLineBefore == mLines.begin();
5534 nsLineList::iterator overBegin(aLineBefore.next());
5536 // PushTruncatedPlaceholderLine sometimes pushes the first line. Ugh.
5537 bool firstLine = overBegin == LinesBegin();
5539 if (overBegin != LinesEnd()) {
5540 // Remove floats in the lines from mFloats
5541 nsFrameList floats;
5542 CollectFloats(overBegin->mFirstChild, floats, true);
5544 if (floats.NotEmpty()) {
5545 #ifdef DEBUG
5546 for (nsIFrame* f : floats) {
5547 MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
5548 "CollectFloats should've removed that bit");
5550 #endif
5551 // Push the floats onto the front of the overflow out-of-flows list
5552 nsAutoOOFFrameList oofs(this);
5553 oofs.mList.InsertFrames(nullptr, nullptr, std::move(floats));
5556 // overflow lines can already exist in some cases, in particular,
5557 // when shrinkwrapping and we discover that the shrinkwap causes
5558 // the height of some child block to grow which creates additional
5559 // overflowing content. In such cases we must prepend the new
5560 // overflow to the existing overflow.
5561 FrameLines* overflowLines = RemoveOverflowLines();
5562 if (!overflowLines) {
5563 // XXXldb use presshell arena!
5564 overflowLines = new FrameLines();
5566 if (overflowLines) {
5567 nsIFrame* lineBeforeLastFrame;
5568 if (firstLine) {
5569 lineBeforeLastFrame = nullptr; // removes all frames
5570 } else {
5571 nsIFrame* f = overBegin->mFirstChild;
5572 lineBeforeLastFrame = f ? f->GetPrevSibling() : mFrames.LastChild();
5573 NS_ASSERTION(!f || lineBeforeLastFrame == aLineBefore->LastChild(),
5574 "unexpected line frames");
5576 nsFrameList pushedFrames = mFrames.TakeFramesAfter(lineBeforeLastFrame);
5577 overflowLines->mFrames.InsertFrames(nullptr, nullptr,
5578 std::move(pushedFrames));
5580 overflowLines->mLines.splice(overflowLines->mLines.begin(), mLines,
5581 overBegin, LinesEnd());
5582 NS_ASSERTION(!overflowLines->mLines.empty(), "should not be empty");
5583 // this takes ownership but it won't delete it immediately so we
5584 // can keep using it.
5585 SetOverflowLines(overflowLines);
5587 // Mark all the overflow lines dirty so that they get reflowed when
5588 // they are pulled up by our next-in-flow.
5590 // XXXldb Can this get called O(N) times making the whole thing O(N^2)?
5591 for (LineIterator line = overflowLines->mLines.begin(),
5592 line_end = overflowLines->mLines.end();
5593 line != line_end; ++line) {
5594 line->MarkDirty();
5595 line->MarkPreviousMarginDirty();
5596 line->SetMovedFragments();
5597 line->SetBoundsEmpty();
5598 if (line->HasFloats()) {
5599 line->ClearFloats();
5605 #ifdef DEBUG
5606 VerifyOverflowSituation();
5607 #endif
5610 // The overflowLines property is stored as a pointer to a line list,
5611 // which must be deleted. However, the following functions all maintain
5612 // the invariant that the property is never set if the list is empty.
5614 bool nsBlockFrame::DrainOverflowLines() {
5615 #ifdef DEBUG
5616 VerifyOverflowSituation();
5617 #endif
5619 // Steal the prev-in-flow's overflow lines and prepend them.
5620 bool didFindOverflow = false;
5621 nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow());
5622 if (prevBlock) {
5623 prevBlock->ClearLineCursors();
5624 FrameLines* overflowLines = prevBlock->RemoveOverflowLines();
5625 if (overflowLines) {
5626 // Make all the frames on the overflow line list mine.
5627 ReparentFrames(overflowLines->mFrames, prevBlock, this);
5629 // Collect overflow containers from our OverflowContainers list that are
5630 // continuations from the frames we picked up from our prev-in-flow, then
5631 // prepend those to ExcessOverflowContainers to ensure the continuations
5632 // are ordered.
5633 if (GetOverflowContainers()) {
5634 nsFrameList ocContinuations;
5635 for (auto* f : overflowLines->mFrames) {
5636 auto* cont = f;
5637 bool done = false;
5638 while (!done && (cont = cont->GetNextContinuation()) &&
5639 cont->GetParent() == this) {
5640 bool onlyChild = !cont->GetPrevSibling() && !cont->GetNextSibling();
5641 if (cont->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER) &&
5642 TryRemoveFrame(OverflowContainersProperty(), cont)) {
5643 ocContinuations.AppendFrame(nullptr, cont);
5644 done = onlyChild;
5645 continue;
5647 break;
5649 if (done) {
5650 break;
5653 if (!ocContinuations.IsEmpty()) {
5654 if (nsFrameList* eoc = GetExcessOverflowContainers()) {
5655 eoc->InsertFrames(nullptr, nullptr, std::move(ocContinuations));
5656 } else {
5657 SetExcessOverflowContainers(std::move(ocContinuations));
5662 // Make the overflow out-of-flow frames mine too.
5663 nsAutoOOFFrameList oofs(prevBlock);
5664 if (oofs.mList.NotEmpty()) {
5665 // In case we own any next-in-flows of any of the drained frames, then
5666 // move those to the PushedFloat list.
5667 nsFrameList pushedFloats;
5668 for (nsIFrame* f : oofs.mList) {
5669 nsIFrame* nif = f->GetNextInFlow();
5670 for (; nif && nif->GetParent() == this; nif = nif->GetNextInFlow()) {
5671 MOZ_ASSERT(nif->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT));
5672 RemoveFloat(nif);
5673 pushedFloats.AppendFrame(nullptr, nif);
5676 ReparentFrames(oofs.mList, prevBlock, this);
5677 mFloats.InsertFrames(nullptr, nullptr, std::move(oofs.mList));
5678 if (!pushedFloats.IsEmpty()) {
5679 nsFrameList* pf = EnsurePushedFloats();
5680 pf->InsertFrames(nullptr, nullptr, std::move(pushedFloats));
5684 if (!mLines.empty()) {
5685 // Remember to recompute the margins on the first line. This will
5686 // also recompute the correct deltaBCoord if necessary.
5687 mLines.front()->MarkPreviousMarginDirty();
5689 // The overflow lines have already been marked dirty and their previous
5690 // margins marked dirty also.
5692 // Prepend the overflow frames/lines to our principal list.
5693 mFrames.InsertFrames(nullptr, nullptr, std::move(overflowLines->mFrames));
5694 mLines.splice(mLines.begin(), overflowLines->mLines);
5695 NS_ASSERTION(overflowLines->mLines.empty(), "splice should empty list");
5696 delete overflowLines;
5697 didFindOverflow = true;
5701 // Now append our own overflow lines.
5702 return DrainSelfOverflowList() || didFindOverflow;
5705 bool nsBlockFrame::DrainSelfOverflowList() {
5706 UniquePtr<FrameLines> ourOverflowLines(RemoveOverflowLines());
5707 if (!ourOverflowLines) {
5708 return false;
5711 // No need to reparent frames in our own overflow lines/oofs, because they're
5712 // already ours. But we should put overflow floats back in mFloats.
5713 // (explicit scope to remove the OOF list before VerifyOverflowSituation)
5715 nsAutoOOFFrameList oofs(this);
5716 if (oofs.mList.NotEmpty()) {
5717 #ifdef DEBUG
5718 for (nsIFrame* f : oofs.mList) {
5719 MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
5720 "CollectFloats should've removed that bit");
5722 #endif
5723 // The overflow floats go after our regular floats.
5724 mFloats.AppendFrames(nullptr, std::move(oofs).mList);
5727 if (!ourOverflowLines->mLines.empty()) {
5728 mFrames.AppendFrames(nullptr, std::move(ourOverflowLines->mFrames));
5729 mLines.splice(mLines.end(), ourOverflowLines->mLines);
5732 #ifdef DEBUG
5733 VerifyOverflowSituation();
5734 #endif
5735 return true;
5739 * Pushed floats are floats whose placeholders are in a previous
5740 * continuation. They might themselves be next-continuations of a float
5741 * that partially fit in an earlier continuation, or they might be the
5742 * first continuation of a float that couldn't be placed at all.
5744 * Pushed floats live permanently at the beginning of a block's float
5745 * list, where they must live *before* any floats whose placeholders are
5746 * in that block.
5748 * Temporarily, during reflow, they also live on the pushed floats list,
5749 * which only holds them between (a) when one continuation pushes them to
5750 * its pushed floats list because they don't fit and (b) when the next
5751 * continuation pulls them onto the beginning of its float list.
5753 * DrainPushedFloats sets up pushed floats the way we need them at the
5754 * start of reflow; they are then reflowed by ReflowPushedFloats (which
5755 * might push some of them on). Floats with placeholders in this block
5756 * are reflowed by (BlockReflowState/nsLineLayout)::AddFloat, which
5757 * also maintains these invariants.
5759 * DrainSelfPushedFloats moves any pushed floats from this block's own
5760 * PushedFloats list back into mFloats. DrainPushedFloats additionally
5761 * moves frames from its prev-in-flow's PushedFloats list into mFloats.
5763 void nsBlockFrame::DrainSelfPushedFloats() {
5764 // If we're getting reflowed multiple times without our
5765 // next-continuation being reflowed, we might need to pull back floats
5766 // that we just put in the list to be pushed to our next-in-flow.
5767 // We don't want to pull back any next-in-flows of floats on our own
5768 // float list, and we only need to pull back first-in-flows whose
5769 // placeholders were in earlier blocks (since first-in-flows whose
5770 // placeholders are in this block will get pulled appropriately by
5771 // AddFloat, and will then be more likely to be in the correct order).
5772 mozilla::PresShell* presShell = PresShell();
5773 nsFrameList* ourPushedFloats = GetPushedFloats();
5774 if (ourPushedFloats) {
5775 // When we pull back floats, we want to put them with the pushed
5776 // floats, which must live at the start of our float list, but we
5777 // want them at the end of those pushed floats.
5778 // FIXME: This isn't quite right! What if they're all pushed floats?
5779 nsIFrame* insertionPrevSibling = nullptr; /* beginning of list */
5780 for (nsIFrame* f = mFloats.FirstChild();
5781 f && f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT);
5782 f = f->GetNextSibling()) {
5783 insertionPrevSibling = f;
5786 nsIFrame* f = ourPushedFloats->LastChild();
5787 while (f) {
5788 nsIFrame* prevSibling = f->GetPrevSibling();
5790 nsPlaceholderFrame* placeholder = f->GetPlaceholderFrame();
5791 nsIFrame* floatOriginalParent =
5792 presShell->FrameConstructor()->GetFloatContainingBlock(placeholder);
5793 if (floatOriginalParent != this) {
5794 // This is a first continuation that was pushed from one of our
5795 // previous continuations. Take it out of the pushed floats
5796 // list and put it in our floats list, before any of our
5797 // floats, but after other pushed floats.
5798 ourPushedFloats->RemoveFrame(f);
5799 mFloats.InsertFrame(nullptr, insertionPrevSibling, f);
5802 f = prevSibling;
5805 if (ourPushedFloats->IsEmpty()) {
5806 RemovePushedFloats()->Delete(presShell);
5811 void nsBlockFrame::DrainPushedFloats() {
5812 DrainSelfPushedFloats();
5814 // After our prev-in-flow has completed reflow, it may have a pushed
5815 // floats list, containing floats that we need to own. Take these.
5816 nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow());
5817 if (prevBlock) {
5818 AutoFrameListPtr list(PresContext(), prevBlock->RemovePushedFloats());
5819 if (list && list->NotEmpty()) {
5820 mFloats.InsertFrames(this, nullptr, std::move(*list));
5825 nsBlockFrame::FrameLines* nsBlockFrame::GetOverflowLines() const {
5826 if (!HasOverflowLines()) {
5827 return nullptr;
5829 FrameLines* prop = GetProperty(OverflowLinesProperty());
5830 NS_ASSERTION(
5831 prop && !prop->mLines.empty() &&
5832 prop->mLines.front()->GetChildCount() == 0
5833 ? prop->mFrames.IsEmpty()
5834 : prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(),
5835 "value should always be stored and non-empty when state set");
5836 return prop;
5839 nsBlockFrame::FrameLines* nsBlockFrame::RemoveOverflowLines() {
5840 if (!HasOverflowLines()) {
5841 return nullptr;
5843 FrameLines* prop = TakeProperty(OverflowLinesProperty());
5844 NS_ASSERTION(
5845 prop && !prop->mLines.empty() &&
5846 prop->mLines.front()->GetChildCount() == 0
5847 ? prop->mFrames.IsEmpty()
5848 : prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(),
5849 "value should always be stored and non-empty when state set");
5850 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
5851 return prop;
5854 void nsBlockFrame::DestroyOverflowLines() {
5855 NS_ASSERTION(HasOverflowLines(), "huh?");
5856 FrameLines* prop = TakeProperty(OverflowLinesProperty());
5857 NS_ASSERTION(prop && prop->mLines.empty(),
5858 "value should always be stored but empty when destroying");
5859 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
5860 delete prop;
5863 // This takes ownership of aOverflowLines.
5864 // XXX We should allocate overflowLines from presShell arena!
5865 void nsBlockFrame::SetOverflowLines(FrameLines* aOverflowLines) {
5866 NS_ASSERTION(aOverflowLines, "null lines");
5867 NS_ASSERTION(!aOverflowLines->mLines.empty(), "empty lines");
5868 NS_ASSERTION(aOverflowLines->mLines.front()->mFirstChild ==
5869 aOverflowLines->mFrames.FirstChild(),
5870 "invalid overflow lines / frames");
5871 NS_ASSERTION(!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_LINES),
5872 "Overwriting existing overflow lines");
5874 // Verify that we won't overwrite an existing overflow list
5875 NS_ASSERTION(!GetProperty(OverflowLinesProperty()), "existing overflow list");
5876 SetProperty(OverflowLinesProperty(), aOverflowLines);
5877 AddStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
5880 nsFrameList* nsBlockFrame::GetOverflowOutOfFlows() const {
5881 if (!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
5882 return nullptr;
5884 nsFrameList* result = GetProperty(OverflowOutOfFlowsProperty());
5885 NS_ASSERTION(result, "value should always be non-empty when state set");
5886 return result;
5889 void nsBlockFrame::SetOverflowOutOfFlows(nsFrameList&& aList,
5890 nsFrameList* aPropValue) {
5891 MOZ_ASSERT(
5892 HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS) == !!aPropValue,
5893 "state does not match value");
5895 if (aList.IsEmpty()) {
5896 if (!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
5897 return;
5899 nsFrameList* list = TakeProperty(OverflowOutOfFlowsProperty());
5900 NS_ASSERTION(aPropValue == list, "prop value mismatch");
5901 list->Clear();
5902 list->Delete(PresShell());
5903 RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
5904 } else if (HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
5905 NS_ASSERTION(aPropValue == GetProperty(OverflowOutOfFlowsProperty()),
5906 "prop value mismatch");
5907 *aPropValue = std::move(aList);
5908 } else {
5909 SetProperty(OverflowOutOfFlowsProperty(),
5910 new (PresShell()) nsFrameList(std::move(aList)));
5911 AddStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
5915 nsIFrame* nsBlockFrame::GetInsideMarker() const {
5916 if (!HasInsideMarker()) {
5917 return nullptr;
5919 NS_ASSERTION(!HasOutsideMarker(), "invalid marker state");
5920 nsIFrame* frame = GetProperty(InsideMarkerProperty());
5921 NS_ASSERTION(frame, "bogus inside ::marker frame");
5922 return frame;
5925 nsIFrame* nsBlockFrame::GetOutsideMarker() const {
5926 nsFrameList* list = GetOutsideMarkerList();
5927 return list ? list->FirstChild() : nullptr;
5930 nsFrameList* nsBlockFrame::GetOutsideMarkerList() const {
5931 if (!HasOutsideMarker()) {
5932 return nullptr;
5934 NS_ASSERTION(!HasInsideMarker(), "invalid marker state");
5935 nsFrameList* list = GetProperty(OutsideMarkerProperty());
5936 NS_ASSERTION(list && list->GetLength() == 1, "bogus outside ::marker list");
5937 return list;
5940 nsFrameList* nsBlockFrame::GetPushedFloats() const {
5941 if (!HasPushedFloats()) {
5942 return nullptr;
5944 nsFrameList* result = GetProperty(PushedFloatProperty());
5945 NS_ASSERTION(result, "value should always be non-empty when state set");
5946 return result;
5949 nsFrameList* nsBlockFrame::EnsurePushedFloats() {
5950 nsFrameList* result = GetPushedFloats();
5951 if (result) {
5952 return result;
5955 result = new (PresShell()) nsFrameList;
5956 SetProperty(PushedFloatProperty(), result);
5957 AddStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
5959 return result;
5962 nsFrameList* nsBlockFrame::RemovePushedFloats() {
5963 if (!HasPushedFloats()) {
5964 return nullptr;
5966 nsFrameList* result = TakeProperty(PushedFloatProperty());
5967 RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
5968 NS_ASSERTION(result, "value should always be non-empty when state set");
5969 return result;
5972 //////////////////////////////////////////////////////////////////////
5973 // Frame list manipulation routines
5975 void nsBlockFrame::AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) {
5976 if (aFrameList.IsEmpty()) {
5977 return;
5979 if (aListID != FrameChildListID::Principal) {
5980 if (FrameChildListID::Float == aListID) {
5981 DrainSelfPushedFloats(); // ensure the last frame is in mFloats
5982 mFloats.AppendFrames(nullptr, std::move(aFrameList));
5983 return;
5985 MOZ_ASSERT(FrameChildListID::NoReflowPrincipal == aListID,
5986 "unexpected child list");
5989 // Find the proper last-child for where the append should go
5990 nsIFrame* lastKid = mFrames.LastChild();
5991 NS_ASSERTION(
5992 (mLines.empty() ? nullptr : mLines.back()->LastChild()) == lastKid,
5993 "out-of-sync mLines / mFrames");
5995 #ifdef NOISY_REFLOW_REASON
5996 ListTag(stdout);
5997 printf(": append ");
5998 for (nsIFrame* frame : aFrameList) {
5999 frame->ListTag(stdout);
6001 if (lastKid) {
6002 printf(" after ");
6003 lastKid->ListTag(stdout);
6005 printf("\n");
6006 #endif
6008 if (IsInSVGTextSubtree()) {
6009 MOZ_ASSERT(GetParent()->IsSVGTextFrame(),
6010 "unexpected block frame in SVG text");
6011 // Workaround for bug 1399425 in case this bit has been removed from the
6012 // SVGTextFrame just before the parser adds more descendant nodes.
6013 GetParent()->AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY);
6016 AddFrames(std::move(aFrameList), lastKid, nullptr);
6017 if (aListID != FrameChildListID::NoReflowPrincipal) {
6018 PresShell()->FrameNeedsReflow(
6019 this, IntrinsicDirty::FrameAndAncestors,
6020 NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
6024 void nsBlockFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
6025 const nsLineList::iterator* aPrevFrameLine,
6026 nsFrameList&& aFrameList) {
6027 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
6028 "inserting after sibling frame with different parent");
6030 if (aListID != FrameChildListID::Principal) {
6031 if (FrameChildListID::Float == aListID) {
6032 DrainSelfPushedFloats(); // ensure aPrevFrame is in mFloats
6033 mFloats.InsertFrames(this, aPrevFrame, std::move(aFrameList));
6034 return;
6036 MOZ_ASSERT(FrameChildListID::NoReflowPrincipal == aListID,
6037 "unexpected child list");
6040 #ifdef NOISY_REFLOW_REASON
6041 ListTag(stdout);
6042 printf(": insert ");
6043 for (nsIFrame* frame : aFrameList) {
6044 frame->ListTag(stdout);
6046 if (aPrevFrame) {
6047 printf(" after ");
6048 aPrevFrame->ListTag(stdout);
6050 printf("\n");
6051 #endif
6053 AddFrames(std::move(aFrameList), aPrevFrame, aPrevFrameLine);
6054 if (aListID != FrameChildListID::NoReflowPrincipal) {
6055 PresShell()->FrameNeedsReflow(
6056 this, IntrinsicDirty::FrameAndAncestors,
6057 NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
6061 void nsBlockFrame::RemoveFrame(DestroyContext& aContext, ChildListID aListID,
6062 nsIFrame* aOldFrame) {
6063 #ifdef NOISY_REFLOW_REASON
6064 ListTag(stdout);
6065 printf(": remove ");
6066 aOldFrame->ListTag(stdout);
6067 printf("\n");
6068 #endif
6070 if (aListID == FrameChildListID::Principal) {
6071 bool hasFloats = BlockHasAnyFloats(aOldFrame);
6072 DoRemoveFrame(aContext, aOldFrame, REMOVE_FIXED_CONTINUATIONS);
6073 if (hasFloats) {
6074 MarkSameFloatManagerLinesDirty(this);
6076 } else if (FrameChildListID::Float == aListID) {
6077 // Make sure to mark affected lines dirty for the float frame
6078 // we are removing; this way is a bit messy, but so is the rest of the code.
6079 // See bug 390762.
6080 NS_ASSERTION(!aOldFrame->GetPrevContinuation(),
6081 "RemoveFrame should not be called on pushed floats.");
6082 for (nsIFrame* f = aOldFrame;
6083 f && !f->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
6084 f = f->GetNextContinuation()) {
6085 MarkSameFloatManagerLinesDirty(
6086 static_cast<nsBlockFrame*>(f->GetParent()));
6088 DoRemoveOutOfFlowFrame(aContext, aOldFrame);
6089 } else if (FrameChildListID::NoReflowPrincipal == aListID) {
6090 // Skip the call to |FrameNeedsReflow| below by returning now.
6091 DoRemoveFrame(aContext, aOldFrame, REMOVE_FIXED_CONTINUATIONS);
6092 return;
6093 } else {
6094 MOZ_CRASH("unexpected child list");
6097 PresShell()->FrameNeedsReflow(
6098 this, IntrinsicDirty::FrameAndAncestors,
6099 NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
6102 static bool ShouldPutNextSiblingOnNewLine(nsIFrame* aLastFrame) {
6103 LayoutFrameType type = aLastFrame->Type();
6104 if (type == LayoutFrameType::Br) {
6105 return true;
6107 // XXX the TEXT_OFFSETS_NEED_FIXING check is a wallpaper for bug 822910.
6108 if (type == LayoutFrameType::Text &&
6109 !aLastFrame->HasAnyStateBits(TEXT_OFFSETS_NEED_FIXING)) {
6110 return aLastFrame->HasSignificantTerminalNewline();
6112 return false;
6115 void nsBlockFrame::AddFrames(nsFrameList&& aFrameList, nsIFrame* aPrevSibling,
6116 const nsLineList::iterator* aPrevSiblingLine) {
6117 // Clear our line cursor, since our lines may change.
6118 ClearLineCursors();
6120 if (aFrameList.IsEmpty()) {
6121 return;
6124 // Attempt to find the line that contains the previous sibling
6125 nsLineList* lineList = &mLines;
6126 nsFrameList* frames = &mFrames;
6127 nsLineList::iterator prevSibLine;
6128 int32_t prevSiblingIndex;
6129 if (aPrevSiblingLine) {
6130 MOZ_ASSERT(aPrevSibling);
6131 prevSibLine = *aPrevSiblingLine;
6132 FrameLines* overflowLines = GetOverflowLines();
6133 MOZ_ASSERT(prevSibLine.IsInSameList(mLines.begin()) ||
6134 (overflowLines &&
6135 prevSibLine.IsInSameList(overflowLines->mLines.begin())),
6136 "must be one of our line lists");
6137 if (overflowLines) {
6138 // We need to find out which list it's actually in. Assume that
6139 // *if* we have overflow lines, that our primary lines aren't
6140 // huge, but our overflow lines might be.
6141 nsLineList::iterator line = mLines.begin(), lineEnd = mLines.end();
6142 while (line != lineEnd) {
6143 if (line == prevSibLine) {
6144 break;
6146 ++line;
6148 if (line == lineEnd) {
6149 // By elimination, the line must be in our overflow lines.
6150 lineList = &overflowLines->mLines;
6151 frames = &overflowLines->mFrames;
6155 nsLineList::iterator nextLine = prevSibLine.next();
6156 nsIFrame* lastFrameInLine = nextLine == lineList->end()
6157 ? frames->LastChild()
6158 : nextLine->mFirstChild->GetPrevSibling();
6159 prevSiblingIndex = prevSibLine->RIndexOf(aPrevSibling, lastFrameInLine);
6160 MOZ_ASSERT(prevSiblingIndex >= 0,
6161 "aPrevSibling must be in aPrevSiblingLine");
6162 } else {
6163 prevSibLine = lineList->end();
6164 prevSiblingIndex = -1;
6165 if (aPrevSibling) {
6166 // XXX_perf This is technically O(N^2) in some cases, but by using
6167 // RFind instead of Find, we make it O(N) in the most common case,
6168 // which is appending content.
6170 // Find the line that contains the previous sibling
6171 if (!nsLineBox::RFindLineContaining(aPrevSibling, lineList->begin(),
6172 prevSibLine, mFrames.LastChild(),
6173 &prevSiblingIndex)) {
6174 // Not in mLines - try overflow lines.
6175 FrameLines* overflowLines = GetOverflowLines();
6176 bool found = false;
6177 if (overflowLines) {
6178 prevSibLine = overflowLines->mLines.end();
6179 prevSiblingIndex = -1;
6180 found = nsLineBox::RFindLineContaining(
6181 aPrevSibling, overflowLines->mLines.begin(), prevSibLine,
6182 overflowLines->mFrames.LastChild(), &prevSiblingIndex);
6184 if (MOZ_LIKELY(found)) {
6185 lineList = &overflowLines->mLines;
6186 frames = &overflowLines->mFrames;
6187 } else {
6188 // Note: defensive code! RFindLineContaining must not return
6189 // false in this case, so if it does...
6190 MOZ_ASSERT_UNREACHABLE("prev sibling not in line list");
6191 aPrevSibling = nullptr;
6192 prevSibLine = lineList->end();
6198 // Find the frame following aPrevSibling so that we can join up the
6199 // two lists of frames.
6200 if (aPrevSibling) {
6201 // Split line containing aPrevSibling in two if the insertion
6202 // point is somewhere in the middle of the line.
6203 int32_t rem = prevSibLine->GetChildCount() - prevSiblingIndex - 1;
6204 if (rem) {
6205 // Split the line in two where the frame(s) are being inserted.
6206 nsLineBox* line =
6207 NewLineBox(prevSibLine, aPrevSibling->GetNextSibling(), rem);
6208 lineList->after_insert(prevSibLine, line);
6209 // Mark prevSibLine dirty and as needing textrun invalidation, since
6210 // we may be breaking up text in the line. Its previous line may also
6211 // need to be invalidated because it may be able to pull some text up.
6212 MarkLineDirty(prevSibLine, lineList);
6213 // The new line will also need its textruns recomputed because of the
6214 // frame changes.
6215 line->MarkDirty();
6216 line->SetInvalidateTextRuns(true);
6218 } else if (!lineList->empty()) {
6219 lineList->front()->MarkDirty();
6220 lineList->front()->SetInvalidateTextRuns(true);
6222 const nsFrameList::Slice& newFrames =
6223 frames->InsertFrames(nullptr, aPrevSibling, std::move(aFrameList));
6225 // Walk through the new frames being added and update the line data
6226 // structures to fit.
6227 for (nsIFrame* newFrame : newFrames) {
6228 NS_ASSERTION(!aPrevSibling || aPrevSibling->GetNextSibling() == newFrame,
6229 "Unexpected aPrevSibling");
6230 NS_ASSERTION(
6231 !newFrame->IsPlaceholderFrame() ||
6232 (!newFrame->IsAbsolutelyPositioned() && !newFrame->IsFloating()),
6233 "Placeholders should not float or be positioned");
6235 bool isBlock = newFrame->IsBlockOutside();
6237 // If the frame is a block frame, or if there is no previous line or if the
6238 // previous line is a block line we need to make a new line. We also make
6239 // a new line, as an optimization, in the two cases we know we'll need it:
6240 // if the previous line ended with a <br>, or if it has significant
6241 // whitespace and ended in a newline.
6242 if (isBlock || prevSibLine == lineList->end() || prevSibLine->IsBlock() ||
6243 (aPrevSibling && ShouldPutNextSiblingOnNewLine(aPrevSibling))) {
6244 // Create a new line for the frame and add its line to the line
6245 // list.
6246 nsLineBox* line = NewLineBox(newFrame, isBlock);
6247 if (prevSibLine != lineList->end()) {
6248 // Append new line after prevSibLine
6249 lineList->after_insert(prevSibLine, line);
6250 ++prevSibLine;
6251 } else {
6252 // New line is going before the other lines
6253 lineList->push_front(line);
6254 prevSibLine = lineList->begin();
6256 } else {
6257 prevSibLine->NoteFrameAdded(newFrame);
6258 // We're adding inline content to prevSibLine, so we need to mark it
6259 // dirty, ensure its textruns are recomputed, and possibly do the same
6260 // to its previous line since that line may be able to pull content up.
6261 MarkLineDirty(prevSibLine, lineList);
6264 aPrevSibling = newFrame;
6267 #ifdef DEBUG
6268 MOZ_ASSERT(aFrameList.IsEmpty());
6269 VerifyLines(true);
6270 #endif
6273 nsContainerFrame* nsBlockFrame::GetRubyContentPseudoFrame() {
6274 auto* firstChild = PrincipalChildList().FirstChild();
6275 if (firstChild && firstChild->IsRubyFrame() &&
6276 firstChild->Style()->GetPseudoType() ==
6277 mozilla::PseudoStyleType::blockRubyContent) {
6278 return static_cast<nsContainerFrame*>(firstChild);
6280 return nullptr;
6283 nsContainerFrame* nsBlockFrame::GetContentInsertionFrame() {
6284 // 'display:block ruby' use the inner (Ruby) frame for insertions.
6285 if (auto* rubyContentPseudoFrame = GetRubyContentPseudoFrame()) {
6286 return rubyContentPseudoFrame;
6288 return this;
6291 void nsBlockFrame::AppendDirectlyOwnedAnonBoxes(
6292 nsTArray<OwnedAnonBox>& aResult) {
6293 if (auto* rubyContentPseudoFrame = GetRubyContentPseudoFrame()) {
6294 aResult.AppendElement(OwnedAnonBox(rubyContentPseudoFrame));
6298 void nsBlockFrame::RemoveFloatFromFloatCache(nsIFrame* aFloat) {
6299 // Find which line contains the float, so we can update
6300 // the float cache.
6301 for (auto& line : Lines()) {
6302 if (line.IsInline() && line.RemoveFloat(aFloat)) {
6303 break;
6308 void nsBlockFrame::RemoveFloat(nsIFrame* aFloat) {
6309 #ifdef DEBUG
6310 // Floats live in mFloats, or in the PushedFloat or OverflowOutOfFlows
6311 // frame list properties.
6312 if (!mFloats.ContainsFrame(aFloat)) {
6313 MOZ_ASSERT(
6314 (GetOverflowOutOfFlows() &&
6315 GetOverflowOutOfFlows()->ContainsFrame(aFloat)) ||
6316 (GetPushedFloats() && GetPushedFloats()->ContainsFrame(aFloat)),
6317 "aFloat is not our child or on an unexpected frame list");
6319 #endif
6321 if (mFloats.StartRemoveFrame(aFloat)) {
6322 return;
6325 nsFrameList* list = GetPushedFloats();
6326 if (list && list->ContinueRemoveFrame(aFloat)) {
6327 #if 0
6328 // XXXmats not yet - need to investigate BlockReflowState::mPushedFloats
6329 // first so we don't leave it pointing to a deleted list.
6330 if (list->IsEmpty()) {
6331 delete RemovePushedFloats();
6333 #endif
6334 return;
6338 nsAutoOOFFrameList oofs(this);
6339 if (oofs.mList.ContinueRemoveFrame(aFloat)) {
6340 return;
6345 void nsBlockFrame::DoRemoveOutOfFlowFrame(DestroyContext& aContext,
6346 nsIFrame* aFrame) {
6347 // The containing block is always the parent of aFrame.
6348 nsBlockFrame* block = (nsBlockFrame*)aFrame->GetParent();
6350 // Remove aFrame from the appropriate list.
6351 if (aFrame->IsAbsolutelyPositioned()) {
6352 // This also deletes the next-in-flows
6353 block->GetAbsoluteContainingBlock()->RemoveFrame(
6354 aContext, FrameChildListID::Absolute, aFrame);
6355 } else {
6356 // First remove aFrame's next-in-flows.
6357 if (nsIFrame* nif = aFrame->GetNextInFlow()) {
6358 nif->GetParent()->DeleteNextInFlowChild(aContext, nif, false);
6360 // Now remove aFrame from its child list and Destroy it.
6361 block->RemoveFloatFromFloatCache(aFrame);
6362 block->RemoveFloat(aFrame);
6363 aFrame->Destroy(aContext);
6368 * This helps us iterate over the list of all normal + overflow lines
6370 void nsBlockFrame::TryAllLines(nsLineList::iterator* aIterator,
6371 nsLineList::iterator* aStartIterator,
6372 nsLineList::iterator* aEndIterator,
6373 bool* aInOverflowLines,
6374 FrameLines** aOverflowLines) {
6375 if (*aIterator == *aEndIterator) {
6376 if (!*aInOverflowLines) {
6377 // Try the overflow lines
6378 *aInOverflowLines = true;
6379 FrameLines* lines = GetOverflowLines();
6380 if (lines) {
6381 *aStartIterator = lines->mLines.begin();
6382 *aIterator = *aStartIterator;
6383 *aEndIterator = lines->mLines.end();
6384 *aOverflowLines = lines;
6390 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
6391 LineIterator aLine)
6392 : mFrame(aFrame), mLine(aLine), mLineList(&aFrame->mLines) {
6393 // This will assert if aLine isn't in mLines of aFrame:
6394 DebugOnly<bool> check = aLine == mFrame->LinesBegin();
6397 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
6398 LineIterator aLine,
6399 bool aInOverflow)
6400 : mFrame(aFrame),
6401 mLine(aLine),
6402 mLineList(aInOverflow ? &aFrame->GetOverflowLines()->mLines
6403 : &aFrame->mLines) {}
6405 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
6406 bool* aFoundValidLine)
6407 : mFrame(aFrame), mLineList(&aFrame->mLines) {
6408 mLine = aFrame->LinesBegin();
6409 *aFoundValidLine = FindValidLine();
6412 void nsBlockFrame::UpdateFirstLetterStyle(ServoRestyleState& aRestyleState) {
6413 nsIFrame* letterFrame = GetFirstLetter();
6414 if (!letterFrame) {
6415 return;
6418 // Figure out what the right style parent is. This needs to match
6419 // nsCSSFrameConstructor::CreateLetterFrame.
6420 nsIFrame* inFlowFrame = letterFrame;
6421 if (inFlowFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
6422 inFlowFrame = inFlowFrame->GetPlaceholderFrame();
6424 nsIFrame* styleParent = CorrectStyleParentFrame(inFlowFrame->GetParent(),
6425 PseudoStyleType::firstLetter);
6426 ComputedStyle* parentStyle = styleParent->Style();
6427 RefPtr<ComputedStyle> firstLetterStyle =
6428 aRestyleState.StyleSet().ResolvePseudoElementStyle(
6429 *mContent->AsElement(), PseudoStyleType::firstLetter, nullptr,
6430 parentStyle);
6431 // Note that we don't need to worry about changehints for the continuation
6432 // styles: those will be handled by the styleParent already.
6433 RefPtr<ComputedStyle> continuationStyle =
6434 aRestyleState.StyleSet().ResolveStyleForFirstLetterContinuation(
6435 parentStyle);
6436 UpdateStyleOfOwnedChildFrame(letterFrame, firstLetterStyle, aRestyleState,
6437 Some(continuationStyle.get()));
6439 // We also want to update the style on the textframe inside the first-letter.
6440 // We don't need to compute a changehint for this, though, since any changes
6441 // to it are handled by the first-letter anyway.
6442 nsIFrame* textFrame = letterFrame->PrincipalChildList().FirstChild();
6443 RefPtr<ComputedStyle> firstTextStyle =
6444 aRestyleState.StyleSet().ResolveStyleForText(textFrame->GetContent(),
6445 firstLetterStyle);
6446 textFrame->SetComputedStyle(firstTextStyle);
6448 // We don't need to update style for textFrame's continuations: it's already
6449 // set up to inherit from parentStyle, which is what we want.
6452 static nsIFrame* FindChildContaining(nsBlockFrame* aFrame,
6453 nsIFrame* aFindFrame) {
6454 NS_ASSERTION(aFrame, "must have frame");
6455 nsIFrame* child;
6456 while (true) {
6457 nsIFrame* block = aFrame;
6458 do {
6459 child = nsLayoutUtils::FindChildContainingDescendant(block, aFindFrame);
6460 if (child) {
6461 break;
6463 block = block->GetNextContinuation();
6464 } while (block);
6465 if (!child) {
6466 return nullptr;
6468 if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
6469 break;
6471 aFindFrame = child->GetPlaceholderFrame();
6474 return child;
6477 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
6478 nsIFrame* aFindFrame,
6479 bool* aFoundValidLine)
6480 : mFrame(aFrame), mLineList(&aFrame->mLines) {
6481 *aFoundValidLine = false;
6483 nsIFrame* child = FindChildContaining(aFrame, aFindFrame);
6484 if (!child) {
6485 return;
6488 LineIterator line_end = aFrame->LinesEnd();
6489 mLine = aFrame->LinesBegin();
6490 if (mLine != line_end && mLine.next() == line_end &&
6491 !aFrame->HasOverflowLines()) {
6492 // The block has a single line - that must be it!
6493 *aFoundValidLine = true;
6494 return;
6497 // Try to use the cursor if it exists, otherwise fall back to the first line
6498 if (nsLineBox* const cursor = aFrame->GetLineCursorForQuery()) {
6499 mLine = line_end;
6500 // Perform a simultaneous forward and reverse search starting from the
6501 // line cursor.
6502 nsBlockFrame::LineIterator line = aFrame->LinesBeginFrom(cursor);
6503 nsBlockFrame::ReverseLineIterator rline = aFrame->LinesRBeginFrom(cursor);
6504 nsBlockFrame::ReverseLineIterator rline_end = aFrame->LinesREnd();
6505 // rline is positioned on the line containing 'cursor', so it's not
6506 // rline_end. So we can safely increment it (i.e. move it to one line
6507 // earlier) to start searching there.
6508 ++rline;
6509 while (line != line_end || rline != rline_end) {
6510 if (line != line_end) {
6511 if (line->Contains(child)) {
6512 mLine = line;
6513 break;
6515 ++line;
6517 if (rline != rline_end) {
6518 if (rline->Contains(child)) {
6519 mLine = rline;
6520 break;
6522 ++rline;
6525 if (mLine != line_end) {
6526 *aFoundValidLine = true;
6527 if (mLine != cursor) {
6528 aFrame->SetProperty(nsBlockFrame::LineCursorPropertyQuery(), mLine);
6530 return;
6532 } else {
6533 for (mLine = aFrame->LinesBegin(); mLine != line_end; ++mLine) {
6534 if (mLine->Contains(child)) {
6535 *aFoundValidLine = true;
6536 return;
6540 // Didn't find the line
6541 MOZ_ASSERT(mLine == line_end, "mLine should be line_end at this point");
6543 // If we reach here, it means that we have not been able to find the
6544 // desired frame in our in-flow lines. So we should start looking at
6545 // our overflow lines. In order to do that, we set mLine to the end
6546 // iterator so that FindValidLine starts to look at overflow lines,
6547 // if any.
6549 if (!FindValidLine()) {
6550 return;
6553 do {
6554 if (mLine->Contains(child)) {
6555 *aFoundValidLine = true;
6556 return;
6558 } while (Next());
6561 nsBlockFrame::LineIterator nsBlockInFlowLineIterator::End() {
6562 return mLineList->end();
6565 bool nsBlockInFlowLineIterator::IsLastLineInList() {
6566 LineIterator end = End();
6567 return mLine != end && mLine.next() == end;
6570 bool nsBlockInFlowLineIterator::Next() {
6571 ++mLine;
6572 return FindValidLine();
6575 bool nsBlockInFlowLineIterator::Prev() {
6576 LineIterator begin = mLineList->begin();
6577 if (mLine != begin) {
6578 --mLine;
6579 return true;
6581 bool currentlyInOverflowLines = GetInOverflow();
6582 while (true) {
6583 if (currentlyInOverflowLines) {
6584 mLineList = &mFrame->mLines;
6585 mLine = mLineList->end();
6586 if (mLine != mLineList->begin()) {
6587 --mLine;
6588 return true;
6590 } else {
6591 mFrame = static_cast<nsBlockFrame*>(mFrame->GetPrevInFlow());
6592 if (!mFrame) {
6593 return false;
6595 nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines();
6596 if (overflowLines) {
6597 mLineList = &overflowLines->mLines;
6598 mLine = mLineList->end();
6599 NS_ASSERTION(mLine != mLineList->begin(), "empty overflow line list?");
6600 --mLine;
6601 return true;
6604 currentlyInOverflowLines = !currentlyInOverflowLines;
6608 bool nsBlockInFlowLineIterator::FindValidLine() {
6609 LineIterator end = mLineList->end();
6610 if (mLine != end) {
6611 return true;
6613 bool currentlyInOverflowLines = GetInOverflow();
6614 while (true) {
6615 if (currentlyInOverflowLines) {
6616 mFrame = static_cast<nsBlockFrame*>(mFrame->GetNextInFlow());
6617 if (!mFrame) {
6618 return false;
6620 mLineList = &mFrame->mLines;
6621 mLine = mLineList->begin();
6622 if (mLine != mLineList->end()) {
6623 return true;
6625 } else {
6626 nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines();
6627 if (overflowLines) {
6628 mLineList = &overflowLines->mLines;
6629 mLine = mLineList->begin();
6630 NS_ASSERTION(mLine != mLineList->end(), "empty overflow line list?");
6631 return true;
6634 currentlyInOverflowLines = !currentlyInOverflowLines;
6638 // This function removes aDeletedFrame and all its continuations. It
6639 // is optimized for deleting a whole series of frames. The easy
6640 // implementation would invoke itself recursively on
6641 // aDeletedFrame->GetNextContinuation, then locate the line containing
6642 // aDeletedFrame and remove aDeletedFrame from that line. But here we
6643 // start by locating aDeletedFrame and then scanning from that point
6644 // on looking for continuations.
6645 void nsBlockFrame::DoRemoveFrame(DestroyContext& aContext,
6646 nsIFrame* aDeletedFrame, uint32_t aFlags) {
6647 // Clear our line cursor, since our lines may change.
6648 ClearLineCursors();
6650 if (aDeletedFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW |
6651 NS_FRAME_IS_OVERFLOW_CONTAINER)) {
6652 if (!aDeletedFrame->GetPrevInFlow()) {
6653 NS_ASSERTION(aDeletedFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
6654 "Expected out-of-flow frame");
6655 DoRemoveOutOfFlowFrame(aContext, aDeletedFrame);
6656 } else {
6657 // FIXME(emilio): aContext is lost here, maybe it's not a big deal?
6658 nsContainerFrame::DeleteNextInFlowChild(aContext, aDeletedFrame,
6659 (aFlags & FRAMES_ARE_EMPTY) != 0);
6661 return;
6664 // Find the line that contains deletedFrame
6665 nsLineList::iterator line_start = mLines.begin(), line_end = mLines.end();
6666 nsLineList::iterator line = line_start;
6667 FrameLines* overflowLines = nullptr;
6668 bool searchingOverflowList = false;
6669 // Make sure we look in the overflow lines even if the normal line
6670 // list is empty
6671 TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
6672 &overflowLines);
6673 while (line != line_end) {
6674 if (line->Contains(aDeletedFrame)) {
6675 break;
6677 ++line;
6678 TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
6679 &overflowLines);
6682 if (line == line_end) {
6683 NS_ERROR("can't find deleted frame in lines");
6684 return;
6687 if (!(aFlags & FRAMES_ARE_EMPTY)) {
6688 if (line != line_start) {
6689 line.prev()->MarkDirty();
6690 line.prev()->SetInvalidateTextRuns(true);
6691 } else if (searchingOverflowList && !mLines.empty()) {
6692 mLines.back()->MarkDirty();
6693 mLines.back()->SetInvalidateTextRuns(true);
6697 while (line != line_end && aDeletedFrame) {
6698 MOZ_ASSERT(this == aDeletedFrame->GetParent(), "messed up delete code");
6699 MOZ_ASSERT(line->Contains(aDeletedFrame), "frame not in line");
6701 if (!(aFlags & FRAMES_ARE_EMPTY)) {
6702 line->MarkDirty();
6703 line->SetInvalidateTextRuns(true);
6706 // If the frame being deleted is the last one on the line then
6707 // optimize away the line->Contains(next-in-flow) call below.
6708 bool isLastFrameOnLine = 1 == line->GetChildCount();
6709 if (!isLastFrameOnLine) {
6710 LineIterator next = line.next();
6711 nsIFrame* lastFrame =
6712 next != line_end
6713 ? next->mFirstChild->GetPrevSibling()
6714 : (searchingOverflowList ? overflowLines->mFrames.LastChild()
6715 : mFrames.LastChild());
6716 NS_ASSERTION(next == line_end || lastFrame == line->LastChild(),
6717 "unexpected line frames");
6718 isLastFrameOnLine = lastFrame == aDeletedFrame;
6721 // Remove aDeletedFrame from the line
6722 if (line->mFirstChild == aDeletedFrame) {
6723 // We should be setting this to null if aDeletedFrame
6724 // is the only frame on the line. HOWEVER in that case
6725 // we will be removing the line anyway, see below.
6726 line->mFirstChild = aDeletedFrame->GetNextSibling();
6729 // Hmm, this won't do anything if we're removing a frame in the first
6730 // overflow line... Hopefully doesn't matter
6731 --line;
6732 if (line != line_end && !line->IsBlock()) {
6733 // Since we just removed a frame that follows some inline
6734 // frames, we need to reflow the previous line.
6735 line->MarkDirty();
6737 ++line;
6739 // Take aDeletedFrame out of the sibling list. Note that
6740 // prevSibling will only be nullptr when we are deleting the very
6741 // first frame in the main or overflow list.
6742 if (searchingOverflowList) {
6743 overflowLines->mFrames.RemoveFrame(aDeletedFrame);
6744 } else {
6745 mFrames.RemoveFrame(aDeletedFrame);
6748 // Update the child count of the line to be accurate
6749 line->NoteFrameRemoved(aDeletedFrame);
6751 // Destroy frame; capture its next continuation first in case we need
6752 // to destroy that too.
6753 nsIFrame* deletedNextContinuation =
6754 (aFlags & REMOVE_FIXED_CONTINUATIONS)
6755 ? aDeletedFrame->GetNextContinuation()
6756 : aDeletedFrame->GetNextInFlow();
6757 #ifdef NOISY_REMOVE_FRAME
6758 printf("DoRemoveFrame: %s line=%p frame=",
6759 searchingOverflowList ? "overflow" : "normal", line.get());
6760 aDeletedFrame->ListTag(stdout);
6761 printf(" prevSibling=%p deletedNextContinuation=%p\n",
6762 aDeletedFrame->GetPrevSibling(), deletedNextContinuation);
6763 #endif
6765 // If next-in-flow is an overflow container, must remove it first.
6766 // FIXME: Can we do this unconditionally?
6767 if (deletedNextContinuation && deletedNextContinuation->HasAnyStateBits(
6768 NS_FRAME_IS_OVERFLOW_CONTAINER)) {
6769 deletedNextContinuation->GetParent()->DeleteNextInFlowChild(
6770 aContext, deletedNextContinuation, false);
6771 deletedNextContinuation = nullptr;
6774 aDeletedFrame->Destroy(aContext);
6775 aDeletedFrame = deletedNextContinuation;
6777 bool haveAdvancedToNextLine = false;
6778 // If line is empty, remove it now.
6779 if (0 == line->GetChildCount()) {
6780 #ifdef NOISY_REMOVE_FRAME
6781 printf("DoRemoveFrame: %s line=%p became empty so it will be removed\n",
6782 searchingOverflowList ? "overflow" : "normal", line.get());
6783 #endif
6784 nsLineBox* cur = line;
6785 if (!searchingOverflowList) {
6786 line = mLines.erase(line);
6787 // Invalidate the space taken up by the line.
6788 // XXX We need to do this if we're removing a frame as a result of
6789 // a call to RemoveFrame(), but we may not need to do this in all
6790 // cases...
6791 #ifdef NOISY_BLOCK_INVALIDATE
6792 nsRect inkOverflow(cur->InkOverflowRect());
6793 printf("%p invalidate 10 (%d, %d, %d, %d)\n", this, inkOverflow.x,
6794 inkOverflow.y, inkOverflow.width, inkOverflow.height);
6795 #endif
6796 } else {
6797 line = overflowLines->mLines.erase(line);
6798 if (overflowLines->mLines.empty()) {
6799 DestroyOverflowLines();
6800 overflowLines = nullptr;
6801 // We just invalidated our iterators. Since we were in
6802 // the overflow lines list, which is now empty, set them
6803 // so we're at the end of the regular line list.
6804 line_start = mLines.begin();
6805 line_end = mLines.end();
6806 line = line_end;
6809 FreeLineBox(cur);
6811 // If we're removing a line, ReflowDirtyLines isn't going to
6812 // know that it needs to slide lines unless something is marked
6813 // dirty. So mark the previous margin of the next line dirty if
6814 // there is one.
6815 if (line != line_end) {
6816 line->MarkPreviousMarginDirty();
6818 haveAdvancedToNextLine = true;
6819 } else {
6820 // Make the line that just lost a frame dirty, and advance to
6821 // the next line.
6822 if (!deletedNextContinuation || isLastFrameOnLine ||
6823 !line->Contains(deletedNextContinuation)) {
6824 line->MarkDirty();
6825 ++line;
6826 haveAdvancedToNextLine = true;
6830 if (deletedNextContinuation) {
6831 // See if we should keep looking in the current flow's line list.
6832 if (deletedNextContinuation->GetParent() != this) {
6833 // The deceased frames continuation is not a child of the
6834 // current block. So break out of the loop so that we advance
6835 // to the next parent.
6837 // If we have a continuation in a different block then all bets are
6838 // off regarding whether we are deleting frames without actual content,
6839 // so don't propagate FRAMES_ARE_EMPTY any further.
6840 aFlags &= ~FRAMES_ARE_EMPTY;
6841 break;
6844 // If we advanced to the next line then check if we should switch to the
6845 // overflow line list.
6846 if (haveAdvancedToNextLine) {
6847 if (line != line_end && !searchingOverflowList &&
6848 !line->Contains(deletedNextContinuation)) {
6849 // We have advanced to the next *normal* line but the next-in-flow
6850 // is not there - force a switch to the overflow line list.
6851 line = line_end;
6854 TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
6855 &overflowLines);
6856 #ifdef NOISY_REMOVE_FRAME
6857 printf("DoRemoveFrame: now on %s line=%p\n",
6858 searchingOverflowList ? "overflow" : "normal", line.get());
6859 #endif
6864 if (!(aFlags & FRAMES_ARE_EMPTY) && line.next() != line_end) {
6865 line.next()->MarkDirty();
6866 line.next()->SetInvalidateTextRuns(true);
6869 #ifdef DEBUG
6870 VerifyLines(true);
6871 VerifyOverflowSituation();
6872 #endif
6874 // Advance to next flow block if the frame has more continuations.
6875 if (!aDeletedFrame) {
6876 return;
6878 nsBlockFrame* nextBlock = do_QueryFrame(aDeletedFrame->GetParent());
6879 NS_ASSERTION(nextBlock, "Our child's continuation's parent is not a block?");
6880 uint32_t flags = (aFlags & REMOVE_FIXED_CONTINUATIONS);
6881 nextBlock->DoRemoveFrame(aContext, aDeletedFrame, flags);
6884 static bool FindBlockLineFor(nsIFrame* aChild, nsLineList::iterator aBegin,
6885 nsLineList::iterator aEnd,
6886 nsLineList::iterator* aResult) {
6887 MOZ_ASSERT(aChild->IsBlockOutside());
6888 for (nsLineList::iterator line = aBegin; line != aEnd; ++line) {
6889 MOZ_ASSERT(line->GetChildCount() > 0);
6890 if (line->IsBlock() && line->mFirstChild == aChild) {
6891 MOZ_ASSERT(line->GetChildCount() == 1);
6892 *aResult = line;
6893 return true;
6896 return false;
6899 static bool FindInlineLineFor(nsIFrame* aChild, const nsFrameList& aFrameList,
6900 nsLineList::iterator aBegin,
6901 nsLineList::iterator aEnd,
6902 nsLineList::iterator* aResult) {
6903 MOZ_ASSERT(!aChild->IsBlockOutside());
6904 for (nsLineList::iterator line = aBegin; line != aEnd; ++line) {
6905 MOZ_ASSERT(line->GetChildCount() > 0);
6906 if (!line->IsBlock()) {
6907 // Optimize by comparing the line's last child first.
6908 nsLineList::iterator next = line.next();
6909 if (aChild == (next == aEnd ? aFrameList.LastChild()
6910 : next->mFirstChild->GetPrevSibling()) ||
6911 line->Contains(aChild)) {
6912 *aResult = line;
6913 return true;
6917 return false;
6920 static bool FindLineFor(nsIFrame* aChild, const nsFrameList& aFrameList,
6921 nsLineList::iterator aBegin, nsLineList::iterator aEnd,
6922 nsLineList::iterator* aResult) {
6923 return aChild->IsBlockOutside()
6924 ? FindBlockLineFor(aChild, aBegin, aEnd, aResult)
6925 : FindInlineLineFor(aChild, aFrameList, aBegin, aEnd, aResult);
6928 void nsBlockFrame::StealFrame(nsIFrame* aChild) {
6929 MOZ_ASSERT(aChild->GetParent() == this);
6931 if (aChild->IsFloating()) {
6932 RemoveFloat(aChild);
6933 return;
6936 if (MaybeStealOverflowContainerFrame(aChild)) {
6937 return;
6940 MOZ_ASSERT(!aChild->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
6942 nsLineList::iterator line;
6943 if (FindLineFor(aChild, mFrames, mLines.begin(), mLines.end(), &line)) {
6944 RemoveFrameFromLine(aChild, line, mFrames, mLines);
6945 } else {
6946 FrameLines* overflowLines = GetOverflowLines();
6947 DebugOnly<bool> found;
6948 found = FindLineFor(aChild, overflowLines->mFrames,
6949 overflowLines->mLines.begin(),
6950 overflowLines->mLines.end(), &line);
6951 MOZ_ASSERT(found, "Why can't we find aChild in our overflow lines?");
6952 RemoveFrameFromLine(aChild, line, overflowLines->mFrames,
6953 overflowLines->mLines);
6954 if (overflowLines->mLines.empty()) {
6955 DestroyOverflowLines();
6960 void nsBlockFrame::RemoveFrameFromLine(nsIFrame* aChild,
6961 nsLineList::iterator aLine,
6962 nsFrameList& aFrameList,
6963 nsLineList& aLineList) {
6964 aFrameList.RemoveFrame(aChild);
6965 if (aChild == aLine->mFirstChild) {
6966 aLine->mFirstChild = aChild->GetNextSibling();
6968 aLine->NoteFrameRemoved(aChild);
6969 if (aLine->GetChildCount() > 0) {
6970 aLine->MarkDirty();
6971 } else {
6972 // The line became empty - destroy it.
6973 nsLineBox* lineBox = aLine;
6974 aLine = aLineList.erase(aLine);
6975 if (aLine != aLineList.end()) {
6976 aLine->MarkPreviousMarginDirty();
6978 FreeLineBox(lineBox);
6982 void nsBlockFrame::DeleteNextInFlowChild(DestroyContext& aContext,
6983 nsIFrame* aNextInFlow,
6984 bool aDeletingEmptyFrames) {
6985 MOZ_ASSERT(aNextInFlow->GetPrevInFlow(), "bad next-in-flow");
6987 if (aNextInFlow->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW |
6988 NS_FRAME_IS_OVERFLOW_CONTAINER)) {
6989 nsContainerFrame::DeleteNextInFlowChild(aContext, aNextInFlow,
6990 aDeletingEmptyFrames);
6991 } else {
6992 #ifdef DEBUG
6993 if (aDeletingEmptyFrames) {
6994 nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(aNextInFlow);
6996 #endif
6997 DoRemoveFrame(aContext, aNextInFlow,
6998 aDeletingEmptyFrames ? FRAMES_ARE_EMPTY : 0);
7002 const nsStyleText* nsBlockFrame::StyleTextForLineLayout() {
7003 // Return the pointer to an unmodified style text
7004 return StyleText();
7007 void nsBlockFrame::ReflowFloat(BlockReflowState& aState, ReflowInput& aFloatRI,
7008 nsIFrame* aFloat,
7009 nsReflowStatus& aReflowStatus) {
7010 MOZ_ASSERT(aReflowStatus.IsEmpty(),
7011 "Caller should pass a fresh reflow status!");
7012 MOZ_ASSERT(aFloat->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
7013 "aFloat must be an out-of-flow frame");
7015 WritingMode wm = aState.mReflowInput.GetWritingMode();
7017 // Setup a block reflow context to reflow the float.
7018 nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput);
7020 nsIFrame* clearanceFrame = nullptr;
7021 do {
7022 nsCollapsingMargin margin;
7023 bool mayNeedRetry = false;
7024 aFloatRI.mDiscoveredClearance = nullptr;
7025 // Only first in flow gets a block-start margin.
7026 if (!aFloat->GetPrevInFlow()) {
7027 brc.ComputeCollapsedBStartMargin(aFloatRI, &margin, clearanceFrame,
7028 &mayNeedRetry);
7030 if (mayNeedRetry && !clearanceFrame) {
7031 aFloatRI.mDiscoveredClearance = &clearanceFrame;
7032 // We don't need to push the float manager state because the the block
7033 // has its own float manager that will be destroyed and recreated
7037 // When reflowing a float, aSpace argument doesn't matter because we pass
7038 // nullptr to aLine and we don't call nsBlockReflowContext::PlaceBlock()
7039 // later.
7040 brc.ReflowBlock(LogicalRect(wm), true, margin, 0, nullptr, aFloatRI,
7041 aReflowStatus, aState);
7042 } while (clearanceFrame);
7044 if (aFloat->IsLetterFrame()) {
7045 // We never split floating first letters; an incomplete status for such
7046 // frames simply means that there is more content to be reflowed on the
7047 // line.
7048 if (aReflowStatus.IsIncomplete()) {
7049 aReflowStatus.Reset();
7053 NS_ASSERTION(aReflowStatus.IsFullyComplete() ||
7054 aFloatRI.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
7055 "The status can only be incomplete or overflow-incomplete if "
7056 "the available block-size is constrained!");
7058 if (aReflowStatus.NextInFlowNeedsReflow()) {
7059 aState.mReflowStatus.SetNextInFlowNeedsReflow();
7062 const ReflowOutput& metrics = brc.GetMetrics();
7064 // Set the rect, make sure the view is properly sized and positioned,
7065 // and tell the frame we're done reflowing it
7066 // XXXldb This seems like the wrong place to be doing this -- shouldn't
7067 // we be doing this in BlockReflowState::FlowAndPlaceFloat after
7068 // we've positioned the float, and shouldn't we be doing the equivalent
7069 // of |PlaceFrameView| here?
7070 WritingMode metricsWM = metrics.GetWritingMode();
7071 aFloat->SetSize(metricsWM, metrics.Size(metricsWM));
7072 if (aFloat->HasView()) {
7073 nsContainerFrame::SyncFrameViewAfterReflow(
7074 aState.mPresContext, aFloat, aFloat->GetView(), metrics.InkOverflow(),
7075 ReflowChildFlags::NoMoveView);
7077 aFloat->DidReflow(aState.mPresContext, &aFloatRI);
7080 StyleClear nsBlockFrame::FindTrailingClear() {
7081 for (nsBlockFrame* b = this; b;
7082 b = static_cast<nsBlockFrame*>(b->GetPrevInFlow())) {
7083 auto endLine = b->LinesRBegin();
7084 if (endLine != b->LinesREnd()) {
7085 return endLine->FloatClearTypeAfter();
7088 return StyleClear::None;
7091 void nsBlockFrame::ReflowPushedFloats(BlockReflowState& aState,
7092 OverflowAreas& aOverflowAreas) {
7093 // Pushed floats live at the start of our float list; see comment
7094 // above nsBlockFrame::DrainPushedFloats.
7095 nsIFrame* f = mFloats.FirstChild();
7096 nsIFrame* prev = nullptr;
7097 while (f && f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
7098 MOZ_ASSERT(prev == f->GetPrevSibling());
7099 // When we push a first-continuation float in a non-initial reflow,
7100 // it's possible that we end up with two continuations with the same
7101 // parent. This happens if, on the previous reflow of the block or
7102 // a previous reflow of the line containing the block, the float was
7103 // split between continuations A and B of the parent, but on the
7104 // current reflow, none of the float can fit in A.
7106 // When this happens, we might even have the two continuations
7107 // out-of-order due to the management of the pushed floats. In
7108 // particular, if the float's placeholder was in a pushed line that
7109 // we reflowed before it was pushed, and we split the float during
7110 // that reflow, we might have the continuation of the float before
7111 // the float itself. (In the general case, however, it's correct
7112 // for floats in the pushed floats list to come before floats
7113 // anchored in pushed lines; however, in this case it's wrong. We
7114 // should probably find a way to fix it somehow, since it leads to
7115 // incorrect layout in some cases.)
7117 // When we have these out-of-order continuations, we might hit the
7118 // next-continuation before the previous-continuation. When that
7119 // happens, just push it. When we reflow the next continuation,
7120 // we'll either pull all of its content back and destroy it (by
7121 // calling DeleteNextInFlowChild), or nsBlockFrame::SplitFloat will
7122 // pull it out of its current position and push it again (and
7123 // potentially repeat this cycle for the next continuation, although
7124 // hopefully then they'll be in the right order).
7126 // We should also need this code for the in-order case if the first
7127 // continuation of a float gets moved across more than one
7128 // continuation of the containing block. In this case we'd manage
7129 // to push the second continuation without this check, but not the
7130 // third and later.
7131 nsIFrame* prevContinuation = f->GetPrevContinuation();
7132 if (prevContinuation && prevContinuation->GetParent() == f->GetParent()) {
7133 mFloats.RemoveFrame(f);
7134 aState.AppendPushedFloatChain(f);
7135 f = !prev ? mFloats.FirstChild() : prev->GetNextSibling();
7136 continue;
7139 // Always call FlowAndPlaceFloat; we might need to place this float if it
7140 // didn't belong to this block the last time it was reflowed. Note that if
7141 // the float doesn't get placed, we don't consider its overflow areas.
7142 // (Not-getting-placed means it didn't fit and we pushed it instead of
7143 // placing it, and its position could be stale.)
7144 if (aState.FlowAndPlaceFloat(f) ==
7145 BlockReflowState::PlaceFloatResult::Placed) {
7146 ConsiderChildOverflow(aOverflowAreas, f);
7149 nsIFrame* next = !prev ? mFloats.FirstChild() : prev->GetNextSibling();
7150 if (next == f) {
7151 // We didn't push |f| so its next-sibling is next.
7152 next = f->GetNextSibling();
7153 prev = f;
7154 } // else: we did push |f| so |prev|'s new next-sibling is next.
7155 f = next;
7158 // If there are pushed or split floats, then we may need to continue BR
7159 // clearance
7160 if (auto [bCoord, result] = aState.ClearFloats(0, StyleClear::Both);
7161 result != ClearFloatsResult::BCoordNoChange) {
7162 Unused << bCoord;
7163 if (auto* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow())) {
7164 aState.mTrailingClearFromPIF = prevBlock->FindTrailingClear();
7169 void nsBlockFrame::RecoverFloats(nsFloatManager& aFloatManager, WritingMode aWM,
7170 const nsSize& aContainerSize) {
7171 // Recover our own floats
7172 nsIFrame* stop = nullptr; // Stop before we reach pushed floats that
7173 // belong to our next-in-flow
7174 for (nsIFrame* f = mFloats.FirstChild(); f && f != stop;
7175 f = f->GetNextSibling()) {
7176 LogicalRect region = nsFloatManager::GetRegionFor(aWM, f, aContainerSize);
7177 aFloatManager.AddFloat(f, region, aWM, aContainerSize);
7178 if (!stop && f->GetNextInFlow()) {
7179 stop = f->GetNextInFlow();
7183 // Recurse into our overflow container children
7184 for (nsIFrame* oc =
7185 GetChildList(FrameChildListID::OverflowContainers).FirstChild();
7186 oc; oc = oc->GetNextSibling()) {
7187 RecoverFloatsFor(oc, aFloatManager, aWM, aContainerSize);
7190 // Recurse into our normal children
7191 for (const auto& line : Lines()) {
7192 if (line.IsBlock()) {
7193 RecoverFloatsFor(line.mFirstChild, aFloatManager, aWM, aContainerSize);
7198 void nsBlockFrame::RecoverFloatsFor(nsIFrame* aFrame,
7199 nsFloatManager& aFloatManager,
7200 WritingMode aWM,
7201 const nsSize& aContainerSize) {
7202 MOZ_ASSERT(aFrame, "null frame");
7204 // Only blocks have floats
7205 nsBlockFrame* block = do_QueryFrame(aFrame);
7206 // Don't recover any state inside a block that has its own float manager
7207 // (we don't currently have any blocks like this, though, thanks to our
7208 // use of extra frames for 'overflow')
7209 if (block && !nsBlockFrame::BlockNeedsFloatManager(block)) {
7210 // If the element is relatively positioned, then adjust x and y
7211 // accordingly so that we consider relatively positioned frames
7212 // at their original position.
7214 const LogicalRect rect = block->GetLogicalNormalRect(aWM, aContainerSize);
7215 nscoord lineLeft = rect.LineLeft(aWM, aContainerSize);
7216 nscoord blockStart = rect.BStart(aWM);
7217 aFloatManager.Translate(lineLeft, blockStart);
7218 block->RecoverFloats(aFloatManager, aWM, aContainerSize);
7219 aFloatManager.Translate(-lineLeft, -blockStart);
7223 bool nsBlockFrame::HasPushedFloatsFromPrevContinuation() const {
7224 if (!mFloats.IsEmpty()) {
7225 // If we have pushed floats, then they should be at the beginning of our
7226 // float list.
7227 if (mFloats.FirstChild()->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
7228 return true;
7232 #ifdef DEBUG
7233 // Double-check the above assertion that pushed floats should be at the
7234 // beginning of our floats list.
7235 for (nsIFrame* f : mFloats) {
7236 NS_ASSERTION(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
7237 "pushed floats must be at the beginning of the float list");
7239 #endif
7241 // We may have a pending push of pushed floats too:
7242 if (HasPushedFloats()) {
7243 // XXX we can return 'true' here once we make HasPushedFloats
7244 // not lie. (see nsBlockFrame::RemoveFloat)
7245 auto* pushedFloats = GetPushedFloats();
7246 return pushedFloats && !pushedFloats->IsEmpty();
7248 return false;
7251 //////////////////////////////////////////////////////////////////////
7252 // Painting, event handling
7254 #ifdef DEBUG
7255 static void ComputeInkOverflowArea(nsLineList& aLines, nscoord aWidth,
7256 nscoord aHeight, nsRect& aResult) {
7257 nscoord xa = 0, ya = 0, xb = aWidth, yb = aHeight;
7258 for (nsLineList::iterator line = aLines.begin(), line_end = aLines.end();
7259 line != line_end; ++line) {
7260 // Compute min and max x/y values for the reflowed frame's
7261 // combined areas
7262 nsRect inkOverflow(line->InkOverflowRect());
7263 nscoord x = inkOverflow.x;
7264 nscoord y = inkOverflow.y;
7265 nscoord xmost = x + inkOverflow.width;
7266 nscoord ymost = y + inkOverflow.height;
7267 if (x < xa) {
7268 xa = x;
7270 if (xmost > xb) {
7271 xb = xmost;
7273 if (y < ya) {
7274 ya = y;
7276 if (ymost > yb) {
7277 yb = ymost;
7281 aResult.x = xa;
7282 aResult.y = ya;
7283 aResult.width = xb - xa;
7284 aResult.height = yb - ya;
7286 #endif
7288 #ifdef DEBUG
7289 static void DebugOutputDrawLine(int32_t aDepth, nsLineBox* aLine, bool aDrawn) {
7290 if (nsBlockFrame::gNoisyDamageRepair) {
7291 nsIFrame::IndentBy(stdout, aDepth + 1);
7292 nsRect lineArea = aLine->InkOverflowRect();
7293 printf("%s line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
7294 aDrawn ? "draw" : "skip", static_cast<void*>(aLine), aLine->IStart(),
7295 aLine->BStart(), aLine->ISize(), aLine->BSize(), lineArea.x,
7296 lineArea.y, lineArea.width, lineArea.height);
7299 #endif
7301 static void DisplayLine(nsDisplayListBuilder* aBuilder,
7302 nsBlockFrame::LineIterator& aLine,
7303 const bool aLineInLine, const nsDisplayListSet& aLists,
7304 nsBlockFrame* aFrame, TextOverflow* aTextOverflow,
7305 uint32_t aLineNumberForTextOverflow, int32_t aDepth,
7306 int32_t& aDrawnLines) {
7307 #ifdef DEBUG
7308 if (nsBlockFrame::gLamePaintMetrics) {
7309 aDrawnLines++;
7311 const bool intersect =
7312 aLine->InkOverflowRect().Intersects(aBuilder->GetDirtyRect());
7313 DebugOutputDrawLine(aDepth, aLine.get(), intersect);
7314 #endif
7316 // Collect our line's display items in a temporary nsDisplayListCollection,
7317 // so that we can apply any "text-overflow" clipping to the entire collection
7318 // without affecting previous lines.
7319 nsDisplayListCollection collection(aBuilder);
7321 // Block-level child backgrounds go on the blockBorderBackgrounds list ...
7322 // Inline-level child backgrounds go on the regular child content list.
7323 nsDisplayListSet childLists(
7324 collection,
7325 aLineInLine ? collection.Content() : collection.BlockBorderBackgrounds());
7327 auto flags =
7328 aLineInLine
7329 ? nsIFrame::DisplayChildFlags(nsIFrame::DisplayChildFlag::Inline)
7330 : nsIFrame::DisplayChildFlags();
7332 nsIFrame* kid = aLine->mFirstChild;
7333 int32_t n = aLine->GetChildCount();
7334 while (--n >= 0) {
7335 aFrame->BuildDisplayListForChild(aBuilder, kid, childLists, flags);
7336 kid = kid->GetNextSibling();
7339 if (aTextOverflow && aLineInLine) {
7340 aTextOverflow->ProcessLine(collection, aLine.get(),
7341 aLineNumberForTextOverflow);
7344 collection.MoveTo(aLists);
7347 void nsBlockFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
7348 const nsDisplayListSet& aLists) {
7349 int32_t drawnLines; // Will only be used if set (gLamePaintMetrics).
7350 int32_t depth = 0;
7351 #ifdef DEBUG
7352 if (gNoisyDamageRepair) {
7353 nsRect dirty = aBuilder->GetDirtyRect();
7354 depth = GetDepth();
7355 nsRect ca;
7356 ::ComputeInkOverflowArea(mLines, mRect.width, mRect.height, ca);
7357 nsIFrame::IndentBy(stdout, depth);
7358 ListTag(stdout);
7359 printf(": bounds=%d,%d,%d,%d dirty(absolute)=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
7360 mRect.x, mRect.y, mRect.width, mRect.height, dirty.x, dirty.y,
7361 dirty.width, dirty.height, ca.x, ca.y, ca.width, ca.height);
7363 PRTime start = 0; // Initialize these variables to silence the compiler.
7364 if (gLamePaintMetrics) {
7365 start = PR_Now();
7366 drawnLines = 0;
7368 #endif
7370 // TODO(heycam): Should we boost the load priority of any shape-outside
7371 // images using CATEGORY_DISPLAY, now that this block is being displayed?
7372 // We don't have a float manager here.
7374 DisplayBorderBackgroundOutline(aBuilder, aLists);
7376 if (GetPrevInFlow()) {
7377 DisplayOverflowContainers(aBuilder, aLists);
7378 for (nsIFrame* f : mFloats) {
7379 if (f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
7380 BuildDisplayListForChild(aBuilder, f, aLists);
7385 aBuilder->MarkFramesForDisplayList(this, mFloats);
7387 if (HasOutsideMarker()) {
7388 // Display outside ::marker manually.
7389 BuildDisplayListForChild(aBuilder, GetOutsideMarker(), aLists);
7392 // Prepare for text-overflow processing.
7393 Maybe<TextOverflow> textOverflow =
7394 TextOverflow::WillProcessLines(aBuilder, this);
7396 const bool hasDescendantPlaceHolders =
7397 HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) ||
7398 ForceDescendIntoIfVisible() || aBuilder->GetIncludeAllOutOfFlows();
7400 const auto ShouldDescendIntoLine = [&](const nsRect& aLineArea) -> bool {
7401 // TODO(miko): Unfortunately |descendAlways| cannot be cached, because with
7402 // some frame trees, building display list for child lines can change it.
7403 // See bug 1552789.
7404 const bool descendAlways =
7405 HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) ||
7406 aBuilder->GetIncludeAllOutOfFlows();
7408 return descendAlways || aLineArea.Intersects(aBuilder->GetDirtyRect()) ||
7409 (ForceDescendIntoIfVisible() &&
7410 aLineArea.Intersects(aBuilder->GetVisibleRect()));
7413 Maybe<nscolor> backplateColor;
7415 // We'll try to draw an accessibility backplate behind text (to ensure it's
7416 // readable over any possible background-images), if all of the following
7417 // hold:
7418 // (A) the backplate feature is preffed on
7419 // (B) we are not honoring the document colors
7420 // (C) the force color adjust property is set to auto
7421 if (StaticPrefs::browser_display_permit_backplate() &&
7422 PresContext()->ForcingColors() && !IsComboboxControlFrame() &&
7423 StyleText()->mForcedColorAdjust != StyleForcedColorAdjust::None) {
7424 backplateColor.emplace(GetBackplateColor(this));
7427 // Don't use the line cursor if we might have a descendant placeholder ...
7428 // it might skip lines that contain placeholders but don't themselves
7429 // intersect with the dirty area.
7430 // In particular, we really want to check ShouldDescendIntoFrame()
7431 // on all our child frames, but that might be expensive. So we
7432 // approximate it by checking it on |this|; if it's true for any
7433 // frame in our child list, it's also true for |this|.
7434 // Also skip the cursor if we're creating text overflow markers,
7435 // since we need to know what line number we're up to in order
7436 // to generate unique display item keys.
7437 // Lastly, the cursor should be skipped if we're drawing
7438 // backplates behind text. When backplating we consider consecutive
7439 // runs of text as a whole, which requires we iterate through all lines
7440 // to find our backplate size.
7441 nsLineBox* cursor =
7442 (hasDescendantPlaceHolders || textOverflow.isSome() || backplateColor)
7443 ? nullptr
7444 : GetFirstLineContaining(aBuilder->GetDirtyRect().y);
7445 LineIterator line_end = LinesEnd();
7447 TextOverflow* textOverflowPtr = textOverflow.ptrOr(nullptr);
7449 if (cursor) {
7450 for (LineIterator line = mLines.begin(cursor); line != line_end; ++line) {
7451 const nsRect lineArea = line->InkOverflowRect();
7452 if (!lineArea.IsEmpty()) {
7453 // Because we have a cursor, the combinedArea.ys are non-decreasing.
7454 // Once we've passed aDirtyRect.YMost(), we can never see it again.
7455 if (lineArea.y >= aBuilder->GetDirtyRect().YMost()) {
7456 break;
7458 MOZ_ASSERT(textOverflow.isNothing());
7460 if (ShouldDescendIntoLine(lineArea)) {
7461 DisplayLine(aBuilder, line, line->IsInline(), aLists, this, nullptr,
7462 0, depth, drawnLines);
7466 } else {
7467 bool nonDecreasingYs = true;
7468 uint32_t lineCount = 0;
7469 nscoord lastY = INT32_MIN;
7470 nscoord lastYMost = INT32_MIN;
7472 // A frame's display list cannot contain more than one copy of a
7473 // given display item unless the items are uniquely identifiable.
7474 // Because backplate occasionally requires multiple
7475 // SolidColor items, we use an index (backplateIndex) to maintain
7476 // uniqueness among them. Note this is a mapping of index to
7477 // item, and the mapping is stable even if the dirty rect changes.
7478 uint16_t backplateIndex = 0;
7479 nsRect curBackplateArea;
7481 auto AddBackplate = [&]() {
7482 aLists.BorderBackground()->AppendNewToTopWithIndex<nsDisplaySolidColor>(
7483 aBuilder, this, backplateIndex, curBackplateArea,
7484 backplateColor.value());
7487 for (LineIterator line = LinesBegin(); line != line_end; ++line) {
7488 const nsRect lineArea = line->InkOverflowRect();
7489 const bool lineInLine = line->IsInline();
7491 if ((lineInLine && textOverflowPtr) || ShouldDescendIntoLine(lineArea)) {
7492 DisplayLine(aBuilder, line, lineInLine, aLists, this, textOverflowPtr,
7493 lineCount, depth, drawnLines);
7496 if (!lineInLine && !curBackplateArea.IsEmpty()) {
7497 // If we have encountered a non-inline line but were previously
7498 // forming a backplate, we should add the backplate to the display
7499 // list as-is and render future backplates disjointly.
7500 MOZ_ASSERT(backplateColor,
7501 "if this master switch is off, curBackplateArea "
7502 "must be empty and we shouldn't get here");
7503 AddBackplate();
7504 backplateIndex++;
7505 curBackplateArea = nsRect();
7508 if (!lineArea.IsEmpty()) {
7509 if (lineArea.y < lastY || lineArea.YMost() < lastYMost) {
7510 nonDecreasingYs = false;
7512 lastY = lineArea.y;
7513 lastYMost = lineArea.YMost();
7514 if (lineInLine && backplateColor && LineHasVisibleInlineContent(line)) {
7515 nsRect lineBackplate = GetLineTextArea(line, aBuilder) +
7516 aBuilder->ToReferenceFrame(this);
7517 if (curBackplateArea.IsEmpty()) {
7518 curBackplateArea = lineBackplate;
7519 } else {
7520 curBackplateArea.OrWith(lineBackplate);
7524 lineCount++;
7527 if (nonDecreasingYs && lineCount >= MIN_LINES_NEEDING_CURSOR) {
7528 SetupLineCursorForDisplay();
7531 if (!curBackplateArea.IsEmpty()) {
7532 AddBackplate();
7536 if (textOverflow.isSome()) {
7537 // Put any text-overflow:ellipsis markers on top of the non-positioned
7538 // content of the block's lines. (If we ever start sorting the Content()
7539 // list this will end up in the wrong place.)
7540 aLists.Content()->AppendToTop(&textOverflow->GetMarkers());
7543 #ifdef DEBUG
7544 if (gLamePaintMetrics) {
7545 PRTime end = PR_Now();
7547 int32_t numLines = mLines.size();
7548 if (!numLines) {
7549 numLines = 1;
7551 PRTime lines, deltaPerLine, delta;
7552 lines = int64_t(numLines);
7553 delta = end - start;
7554 deltaPerLine = delta / lines;
7556 ListTag(stdout);
7557 char buf[400];
7558 SprintfLiteral(buf,
7559 ": %" PRId64 " elapsed (%" PRId64
7560 " per line) lines=%d drawn=%d skip=%d",
7561 delta, deltaPerLine, numLines, drawnLines,
7562 numLines - drawnLines);
7563 printf("%s\n", buf);
7565 #endif
7568 #ifdef ACCESSIBILITY
7569 a11y::AccType nsBlockFrame::AccessibleType() {
7570 if (IsTableCaption()) {
7571 return GetRect().IsEmpty() ? a11y::eNoType : a11y::eHTMLCaptionType;
7574 // block frame may be for <hr>
7575 if (mContent->IsHTMLElement(nsGkAtoms::hr)) {
7576 return a11y::eHTMLHRType;
7579 if (!HasMarker() || !PresContext()) {
7580 // XXXsmaug What if we're in the shadow dom?
7581 if (!mContent->GetParent()) {
7582 // Don't create accessible objects for the root content node, they are
7583 // redundant with the nsDocAccessible object created with the document
7584 // node
7585 return a11y::eNoType;
7588 if (mContent == mContent->OwnerDoc()->GetBody()) {
7589 // Don't create accessible objects for the body, they are redundant with
7590 // the nsDocAccessible object created with the document node
7591 return a11y::eNoType;
7594 // Not a list item with a ::marker, treat as normal HTML container.
7595 return a11y::eHyperTextType;
7598 // Create special list item accessible since we have a ::marker.
7599 return a11y::eHTMLLiType;
7601 #endif
7603 void nsBlockFrame::SetupLineCursorForDisplay() {
7604 if (mLines.empty() || HasProperty(LineCursorPropertyDisplay())) {
7605 return;
7608 SetProperty(LineCursorPropertyDisplay(), mLines.front());
7609 AddStateBits(NS_BLOCK_HAS_LINE_CURSOR);
7612 void nsBlockFrame::SetupLineCursorForQuery() {
7613 if (mLines.empty() || HasProperty(LineCursorPropertyQuery())) {
7614 return;
7617 SetProperty(LineCursorPropertyQuery(), mLines.front());
7618 AddStateBits(NS_BLOCK_HAS_LINE_CURSOR);
7621 nsLineBox* nsBlockFrame::GetFirstLineContaining(nscoord y) {
7622 // Although this looks like a "querying" method, it is used by the
7623 // display-list building code, so uses the Display cursor.
7624 nsLineBox* property = GetLineCursorForDisplay();
7625 if (!property) {
7626 return nullptr;
7628 LineIterator cursor = mLines.begin(property);
7629 nsRect cursorArea = cursor->InkOverflowRect();
7631 while ((cursorArea.IsEmpty() || cursorArea.YMost() > y) &&
7632 cursor != mLines.front()) {
7633 cursor = cursor.prev();
7634 cursorArea = cursor->InkOverflowRect();
7636 while ((cursorArea.IsEmpty() || cursorArea.YMost() <= y) &&
7637 cursor != mLines.back()) {
7638 cursor = cursor.next();
7639 cursorArea = cursor->InkOverflowRect();
7642 if (cursor.get() != property) {
7643 SetProperty(LineCursorPropertyDisplay(), cursor.get());
7646 return cursor.get();
7649 /* virtual */
7650 void nsBlockFrame::ChildIsDirty(nsIFrame* aChild) {
7651 // See if the child is absolutely positioned
7652 if (aChild->IsAbsolutelyPositioned()) {
7653 // do nothing
7654 } else if (aChild == GetOutsideMarker()) {
7655 // The ::marker lives in the first line, unless the first line has
7656 // height 0 and there is a second line, in which case it lives
7657 // in the second line.
7658 LineIterator markerLine = LinesBegin();
7659 if (markerLine != LinesEnd() && markerLine->BSize() == 0 &&
7660 markerLine != mLines.back()) {
7661 markerLine = markerLine.next();
7664 if (markerLine != LinesEnd()) {
7665 MarkLineDirty(markerLine, &mLines);
7667 // otherwise we have an empty line list, and ReflowDirtyLines
7668 // will handle reflowing the ::marker.
7669 } else {
7670 // Note that we should go through our children to mark lines dirty
7671 // before the next reflow. Doing it now could make things O(N^2)
7672 // since finding the right line is O(N).
7673 // We don't need to worry about marking lines on the overflow list
7674 // as dirty; we're guaranteed to reflow them if we take them off the
7675 // overflow list.
7676 // However, we might have gotten a float, in which case we need to
7677 // reflow the line containing its placeholder. So find the
7678 // ancestor-or-self of the placeholder that's a child of the block,
7679 // and mark it as NS_FRAME_HAS_DIRTY_CHILDREN too, so that we mark
7680 // its line dirty when we handle NS_BLOCK_LOOK_FOR_DIRTY_FRAMES.
7681 // We need to take some care to handle the case where a float is in
7682 // a different continuation than its placeholder, including marking
7683 // an extra block with NS_BLOCK_LOOK_FOR_DIRTY_FRAMES.
7684 if (!aChild->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
7685 AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
7686 } else {
7687 NS_ASSERTION(aChild->IsFloating(), "should be a float");
7688 nsIFrame* thisFC = FirstContinuation();
7689 nsIFrame* placeholderPath = aChild->GetPlaceholderFrame();
7690 // SVG code sometimes sends FrameNeedsReflow notifications during
7691 // frame destruction, leading to null placeholders, but we're safe
7692 // ignoring those.
7693 if (placeholderPath) {
7694 for (;;) {
7695 nsIFrame* parent = placeholderPath->GetParent();
7696 if (parent->GetContent() == mContent &&
7697 parent->FirstContinuation() == thisFC) {
7698 parent->AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
7699 break;
7701 placeholderPath = parent;
7703 placeholderPath->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
7708 nsContainerFrame::ChildIsDirty(aChild);
7711 void nsBlockFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
7712 nsIFrame* aPrevInFlow) {
7713 // These are all the block specific frame bits, they are copied from
7714 // the prev-in-flow to a newly created next-in-flow, except for the
7715 // NS_BLOCK_FLAGS_NON_INHERITED_MASK bits below.
7716 constexpr nsFrameState NS_BLOCK_FLAGS_MASK =
7717 NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS |
7718 NS_BLOCK_CLIP_PAGINATED_OVERFLOW | NS_BLOCK_HAS_FIRST_LETTER_STYLE |
7719 NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER | NS_BLOCK_HAS_FIRST_LETTER_CHILD |
7720 NS_BLOCK_FRAME_HAS_INSIDE_MARKER;
7722 // This is the subset of NS_BLOCK_FLAGS_MASK that is NOT inherited
7723 // by default. They should only be set on the first-in-flow.
7724 constexpr nsFrameState NS_BLOCK_FLAGS_NON_INHERITED_MASK =
7725 NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER | NS_BLOCK_HAS_FIRST_LETTER_CHILD |
7726 NS_BLOCK_FRAME_HAS_INSIDE_MARKER;
7728 if (aPrevInFlow) {
7729 // Copy over the inherited block frame bits from the prev-in-flow.
7730 RemoveStateBits(NS_BLOCK_FLAGS_MASK);
7731 AddStateBits(aPrevInFlow->GetStateBits() &
7732 (NS_BLOCK_FLAGS_MASK & ~NS_BLOCK_FLAGS_NON_INHERITED_MASK));
7735 nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
7737 if (!aPrevInFlow ||
7738 aPrevInFlow->HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION)) {
7739 AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
7742 // A display:flow-root box establishes a block formatting context.
7744 // If a box has a different writing-mode value than its containing block:
7745 // ...
7746 // If the box is a block container, then it establishes a new block
7747 // formatting context.
7748 // (https://drafts.csswg.org/css-writing-modes/#block-flow)
7750 // If the box has contain: paint or contain:layout (or contain:strict),
7751 // then it should also establish a formatting context.
7753 // Per spec, a column-span always establishes a new block formatting context.
7754 if (StyleDisplay()->mDisplay == mozilla::StyleDisplay::FlowRoot ||
7755 (GetParent() &&
7756 (GetWritingMode().GetBlockDir() !=
7757 GetParent()->GetWritingMode().GetBlockDir() ||
7758 GetWritingMode().IsVerticalSideways() !=
7759 GetParent()->GetWritingMode().IsVerticalSideways())) ||
7760 StyleDisplay()->IsContainPaint() || StyleDisplay()->IsContainLayout() ||
7761 IsColumnSpan()) {
7762 AddStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS);
7765 if (HasAllStateBits(NS_FRAME_FONT_INFLATION_CONTAINER | NS_BLOCK_FLOAT_MGR)) {
7766 AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
7770 void nsBlockFrame::SetInitialChildList(ChildListID aListID,
7771 nsFrameList&& aChildList) {
7772 if (FrameChildListID::Float == aListID) {
7773 mFloats = std::move(aChildList);
7774 } else if (FrameChildListID::Principal == aListID) {
7775 #ifdef DEBUG
7776 // The only times a block that is an anonymous box is allowed to have a
7777 // first-letter frame are when it's the block inside a non-anonymous cell,
7778 // the block inside a fieldset, button or column set, or a scrolled content
7779 // block, except for <select>. Note that this means that blocks which are
7780 // the anonymous block in {ib} splits do NOT get first-letter frames.
7781 // Note that NS_BLOCK_HAS_FIRST_LETTER_STYLE gets set on all continuations
7782 // of the block.
7783 auto pseudo = Style()->GetPseudoType();
7784 bool haveFirstLetterStyle =
7785 (pseudo == PseudoStyleType::NotPseudo ||
7786 (pseudo == PseudoStyleType::cellContent &&
7787 !GetParent()->Style()->IsPseudoOrAnonBox()) ||
7788 pseudo == PseudoStyleType::fieldsetContent ||
7789 pseudo == PseudoStyleType::buttonContent ||
7790 pseudo == PseudoStyleType::columnContent ||
7791 (pseudo == PseudoStyleType::scrolledContent &&
7792 !GetParent()->IsListControlFrame()) ||
7793 pseudo == PseudoStyleType::mozSVGText) &&
7794 !IsComboboxControlFrame() && !IsMathMLFrame() &&
7795 !IsColumnSetWrapperFrame() &&
7796 RefPtr<ComputedStyle>(GetFirstLetterStyle(PresContext())) != nullptr;
7797 NS_ASSERTION(haveFirstLetterStyle ==
7798 HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE),
7799 "NS_BLOCK_HAS_FIRST_LETTER_STYLE state out of sync");
7800 #endif
7802 AddFrames(std::move(aChildList), nullptr, nullptr);
7803 } else {
7804 nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
7808 void nsBlockFrame::SetMarkerFrameForListItem(nsIFrame* aMarkerFrame) {
7809 MOZ_ASSERT(aMarkerFrame);
7810 MOZ_ASSERT(!HasAnyStateBits(NS_BLOCK_FRAME_HAS_INSIDE_MARKER |
7811 NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER),
7812 "How can we have a ::marker frame already?");
7814 if (StyleList()->mListStylePosition == StyleListStylePosition::Inside) {
7815 SetProperty(InsideMarkerProperty(), aMarkerFrame);
7816 AddStateBits(NS_BLOCK_FRAME_HAS_INSIDE_MARKER);
7817 } else {
7818 if (nsBlockFrame* marker = do_QueryFrame(aMarkerFrame)) {
7819 // An outside ::marker needs to be an independent formatting context
7820 // to avoid being influenced by the float manager etc.
7821 marker->AddStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS);
7823 SetProperty(OutsideMarkerProperty(),
7824 new (PresShell()) nsFrameList(aMarkerFrame, aMarkerFrame));
7825 AddStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER);
7829 bool nsBlockFrame::MarkerIsEmpty() const {
7830 NS_ASSERTION(mContent->GetPrimaryFrame()->StyleDisplay()->IsListItem() &&
7831 HasOutsideMarker(),
7832 "should only care when we have an outside ::marker");
7833 nsIFrame* marker = GetMarker();
7834 const nsStyleList* list = marker->StyleList();
7835 return marker->StyleContent()->mContent.IsNone() ||
7836 (list->mCounterStyle.IsNone() && list->mListStyleImage.IsNone() &&
7837 marker->StyleContent()->ContentCount() == 0);
7840 void nsBlockFrame::ReflowOutsideMarker(nsIFrame* aMarkerFrame,
7841 BlockReflowState& aState,
7842 ReflowOutput& aMetrics,
7843 nscoord aLineTop) {
7844 const ReflowInput& ri = aState.mReflowInput;
7846 WritingMode markerWM = aMarkerFrame->GetWritingMode();
7847 LogicalSize availSize(markerWM);
7848 // Make up an inline-size since it doesn't really matter (XXX).
7849 availSize.ISize(markerWM) = aState.ContentISize();
7850 availSize.BSize(markerWM) = NS_UNCONSTRAINEDSIZE;
7852 ReflowInput reflowInput(aState.mPresContext, ri, aMarkerFrame, availSize,
7853 Nothing(), {}, {}, {ComputeSizeFlag::ShrinkWrap});
7854 nsReflowStatus status;
7855 aMarkerFrame->Reflow(aState.mPresContext, aMetrics, reflowInput, status);
7857 // Get the float available space using our saved state from before we
7858 // started reflowing the block, so that we ignore any floats inside
7859 // the block.
7860 // FIXME: aLineTop isn't actually set correctly by some callers, since
7861 // they reposition the line.
7862 LogicalRect floatAvailSpace =
7863 aState
7864 .GetFloatAvailableSpaceWithState(aLineTop, ShapeType::ShapeOutside,
7865 &aState.mFloatManagerStateBefore)
7866 .mRect;
7867 // FIXME (bug 25888): need to check the entire region that the first
7868 // line overlaps, not just the top pixel.
7870 // Place the ::marker now. We want to place the ::marker relative to the
7871 // border-box of the associated block (using the right/left margin of
7872 // the ::marker frame as separation). However, if a line box would be
7873 // displaced by floats that are *outside* the associated block, we
7874 // want to displace it by the same amount. That is, we act as though
7875 // the edge of the floats is the content-edge of the block, and place
7876 // the ::marker at a position offset from there by the block's padding,
7877 // the block's border, and the ::marker frame's margin.
7879 // IStart from floatAvailSpace gives us the content/float start edge
7880 // in the current writing mode. Then we subtract out the start
7881 // border/padding and the ::marker's width and margin to offset the position.
7882 WritingMode wm = ri.GetWritingMode();
7883 // Get the ::marker's margin, converted to our writing mode so that we can
7884 // combine it with other logical values here.
7885 LogicalMargin markerMargin = reflowInput.ComputedLogicalMargin(wm);
7886 nscoord iStart = floatAvailSpace.IStart(wm) -
7887 ri.ComputedLogicalBorderPadding(wm).IStart(wm) -
7888 markerMargin.IEnd(wm) - aMetrics.ISize(wm);
7890 // Approximate the ::marker's position; vertical alignment will provide
7891 // the final vertical location. We pass our writing-mode here, because
7892 // it may be different from the ::marker frame's mode.
7893 nscoord bStart = floatAvailSpace.BStart(wm);
7894 aMarkerFrame->SetRect(
7896 LogicalRect(wm, iStart, bStart, aMetrics.ISize(wm), aMetrics.BSize(wm)),
7897 aState.ContainerSize());
7898 aMarkerFrame->DidReflow(aState.mPresContext, &aState.mReflowInput);
7901 // This is used to scan frames for any float placeholders, add their
7902 // floats to the list represented by aList, and remove the
7903 // floats from whatever list they might be in. We don't search descendants
7904 // that are float containing blocks. Floats that or not children of 'this'
7905 // are ignored (they are not added to aList).
7906 void nsBlockFrame::DoCollectFloats(nsIFrame* aFrame, nsFrameList& aList,
7907 bool aCollectSiblings) {
7908 while (aFrame) {
7909 // Don't descend into float containing blocks.
7910 if (!aFrame->IsFloatContainingBlock()) {
7911 nsIFrame* outOfFlowFrame =
7912 aFrame->IsPlaceholderFrame()
7913 ? nsLayoutUtils::GetFloatFromPlaceholder(aFrame)
7914 : nullptr;
7915 while (outOfFlowFrame && outOfFlowFrame->GetParent() == this) {
7916 RemoveFloat(outOfFlowFrame);
7917 // Remove the IS_PUSHED_FLOAT bit, in case |outOfFlowFrame| came from
7918 // the PushedFloats list.
7919 outOfFlowFrame->RemoveStateBits(NS_FRAME_IS_PUSHED_FLOAT);
7920 aList.AppendFrame(nullptr, outOfFlowFrame);
7921 outOfFlowFrame = outOfFlowFrame->GetNextInFlow();
7922 // FIXME: By not pulling floats whose parent is one of our
7923 // later siblings, are we risking the pushed floats getting
7924 // out-of-order?
7925 // XXXmats nsInlineFrame's lazy reparenting depends on NOT doing that.
7928 DoCollectFloats(aFrame->PrincipalChildList().FirstChild(), aList, true);
7929 DoCollectFloats(
7930 aFrame->GetChildList(FrameChildListID::Overflow).FirstChild(), aList,
7931 true);
7933 if (!aCollectSiblings) {
7934 break;
7936 aFrame = aFrame->GetNextSibling();
7940 void nsBlockFrame::CheckFloats(BlockReflowState& aState) {
7941 #ifdef DEBUG
7942 // If any line is still dirty, that must mean we're going to reflow this
7943 // block again soon (e.g. because we bailed out after noticing that
7944 // clearance was imposed), so don't worry if the floats are out of sync.
7945 bool anyLineDirty = false;
7947 // Check that the float list is what we would have built
7948 AutoTArray<nsIFrame*, 8> lineFloats;
7949 for (auto& line : Lines()) {
7950 if (line.HasFloats()) {
7951 lineFloats.AppendElements(line.Floats());
7953 if (line.IsDirty()) {
7954 anyLineDirty = true;
7958 AutoTArray<nsIFrame*, 8> storedFloats;
7959 bool equal = true;
7960 bool hasHiddenFloats = false;
7961 uint32_t i = 0;
7962 for (nsIFrame* f : mFloats) {
7963 if (f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
7964 continue;
7966 // There are chances that the float children won't be added to lines,
7967 // because in nsBlockFrame::ReflowLine, it skips reflow line if the first
7968 // child of the line is IsHiddenByContentVisibilityOfInFlowParentForLayout.
7969 // There are also chances that the floats in line are out of date, for
7970 // instance, lines could reflow if
7971 // PresShell::IsForcingLayoutForHiddenContent, and after forcingLayout is
7972 // off, the reflow of lines could be skipped, but the floats are still in
7973 // there. Here we can't know whether the floats hidden by c-v are included
7974 // in the lines or not. So we use hasHiddenFloats to skip the float length
7975 // checking.
7976 if (!hasHiddenFloats &&
7977 f->IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
7978 hasHiddenFloats = true;
7980 storedFloats.AppendElement(f);
7981 if (i < lineFloats.Length() && lineFloats.ElementAt(i) != f) {
7982 equal = false;
7984 ++i;
7987 if ((!equal || lineFloats.Length() != storedFloats.Length()) &&
7988 !anyLineDirty && !hasHiddenFloats) {
7989 NS_ERROR(
7990 "nsBlockFrame::CheckFloats: Explicit float list is out of sync with "
7991 "float cache");
7992 # if defined(DEBUG_roc)
7993 nsIFrame::RootFrameList(PresContext(), stdout, 0);
7994 for (i = 0; i < lineFloats.Length(); ++i) {
7995 printf("Line float: %p\n", lineFloats.ElementAt(i));
7997 for (i = 0; i < storedFloats.Length(); ++i) {
7998 printf("Stored float: %p\n", storedFloats.ElementAt(i));
8000 # endif
8002 #endif
8004 const nsFrameList* oofs = GetOverflowOutOfFlows();
8005 if (oofs && oofs->NotEmpty()) {
8006 // Floats that were pushed should be removed from our float
8007 // manager. Otherwise the float manager's YMost or XMost might
8008 // be larger than necessary, causing this block to get an
8009 // incorrect desired height (or width). Some of these floats
8010 // may not actually have been added to the float manager because
8011 // they weren't reflowed before being pushed; that's OK,
8012 // RemoveRegions will ignore them. It is safe to do this here
8013 // because we know from here on the float manager will only be
8014 // used for its XMost and YMost, not to place new floats and
8015 // lines.
8016 aState.FloatManager()->RemoveTrailingRegions(oofs->FirstChild());
8020 void nsBlockFrame::IsMarginRoot(bool* aBStartMarginRoot,
8021 bool* aBEndMarginRoot) {
8022 nsIFrame* parent = GetParent();
8023 if (!HasAnyStateBits(NS_BLOCK_MARGIN_ROOT)) {
8024 if (!parent || parent->IsFloatContainingBlock()) {
8025 *aBStartMarginRoot = false;
8026 *aBEndMarginRoot = false;
8027 return;
8031 if (parent && parent->IsColumnSetFrame()) {
8032 // The first column is a start margin root and the last column is an end
8033 // margin root. (If the column-set is split by a column-span:all box then
8034 // the first and last column in each column-set fragment are margin roots.)
8035 *aBStartMarginRoot = GetPrevInFlow() == nullptr;
8036 *aBEndMarginRoot = GetNextInFlow() == nullptr;
8037 return;
8040 *aBStartMarginRoot = true;
8041 *aBEndMarginRoot = true;
8044 /* static */
8045 bool nsBlockFrame::BlockNeedsFloatManager(nsIFrame* aBlock) {
8046 MOZ_ASSERT(aBlock, "Must have a frame");
8047 NS_ASSERTION(aBlock->IsBlockFrameOrSubclass(), "aBlock must be a block");
8049 nsIFrame* parent = aBlock->GetParent();
8050 return aBlock->HasAnyStateBits(NS_BLOCK_FLOAT_MGR) ||
8051 (parent && !parent->IsFloatContainingBlock());
8054 /* static */
8055 bool nsBlockFrame::BlockCanIntersectFloats(nsIFrame* aFrame) {
8056 return aFrame->IsBlockFrameOrSubclass() && !aFrame->IsReplaced() &&
8057 !aFrame->HasAnyStateBits(NS_BLOCK_FLOAT_MGR);
8060 // Note that this width can vary based on the vertical position.
8061 // However, the cases where it varies are the cases where the width fits
8062 // in the available space given, which means that variation shouldn't
8063 // matter.
8064 /* static */
8065 nsBlockFrame::FloatAvoidingISizeToClear nsBlockFrame::ISizeToClearPastFloats(
8066 const BlockReflowState& aState, const LogicalRect& aFloatAvailableSpace,
8067 nsIFrame* aFloatAvoidingBlock) {
8068 nscoord inlineStartOffset, inlineEndOffset;
8069 WritingMode wm = aState.mReflowInput.GetWritingMode();
8071 FloatAvoidingISizeToClear result;
8072 aState.ComputeFloatAvoidingOffsets(aFloatAvoidingBlock, aFloatAvailableSpace,
8073 inlineStartOffset, inlineEndOffset);
8074 nscoord availISize =
8075 aState.mContentArea.ISize(wm) - inlineStartOffset - inlineEndOffset;
8077 // We actually don't want the min width here; see bug 427782; we only
8078 // want to displace if the width won't compute to a value small enough
8079 // to fit.
8080 // All we really need here is the result of ComputeSize, and we
8081 // could *almost* get that from an SizeComputationInput, except for the
8082 // last argument.
8083 WritingMode frWM = aFloatAvoidingBlock->GetWritingMode();
8084 LogicalSize availSpace =
8085 LogicalSize(wm, availISize, NS_UNCONSTRAINEDSIZE).ConvertTo(frWM, wm);
8086 ReflowInput reflowInput(aState.mPresContext, aState.mReflowInput,
8087 aFloatAvoidingBlock, availSpace);
8088 result.borderBoxISize =
8089 reflowInput.ComputedSizeWithBorderPadding(wm).ISize(wm);
8091 // Use the margins from sizingInput rather than reflowInput so that
8092 // they aren't reduced by ignoring margins in overconstrained cases.
8093 SizeComputationInput sizingInput(aFloatAvoidingBlock,
8094 aState.mReflowInput.mRenderingContext, wm,
8095 aState.mContentArea.ISize(wm));
8096 const LogicalMargin computedMargin = sizingInput.ComputedLogicalMargin(wm);
8098 nscoord marginISize = computedMargin.IStartEnd(wm);
8099 const auto& iSize = reflowInput.mStylePosition->ISize(wm);
8100 if (marginISize < 0 && (iSize.IsAuto() || iSize.IsMozAvailable())) {
8101 // If we get here, floatAvoidingBlock has a negative amount of inline-axis
8102 // margin and an 'auto' (or ~equivalently, -moz-available) inline
8103 // size. Under these circumstances, we use the margin to establish a
8104 // (positive) minimum size for the border-box, in order to satisfy the
8105 // equation in CSS2 10.3.3. That equation essentially simplifies to the
8106 // following:
8108 // iSize of margins + iSize of borderBox = iSize of containingBlock
8110 // ...where "iSize of borderBox" is the sum of floatAvoidingBlock's
8111 // inline-axis components of border, padding, and {width,height}.
8113 // Right now, in the above equation, "iSize of margins" is the only term
8114 // that we know for sure. (And we also know that it's negative, since we
8115 // got here.) The other terms are as-yet unresolved, since the frame has an
8116 // 'auto' iSize, and since we aren't yet sure if we'll clear this frame
8117 // beyond floats or place it alongside them.
8119 // However: we *do* know that the equation's "iSize of containingBlock"
8120 // term *must* be non-negative, since boxes' widths and heights generally
8121 // can't be negative in CSS. To satisfy that requirement, we can then
8122 // infer that the equation's "iSize of borderBox" term *must* be large
8123 // enough to cancel out the (known-to-be-negative) "iSize of margins"
8124 // term. Therefore, marginISize value (negated to make it positive)
8125 // establishes a lower-bound for how much inline-axis space our border-box
8126 // will really require in order to fit alongside any floats.
8128 // XXXdholbert This explanation is admittedly a bit hand-wavy and may not
8129 // precisely match what any particular spec requires. It's the best
8130 // reasoning I could come up with to explain engines' behavior. Also, our
8131 // behavior with -moz-available doesn't seem particularly correct here, per
8132 // bug 1767217, though that's probably due to a bug elsewhere in our float
8133 // handling code...
8134 result.borderBoxISize = std::max(result.borderBoxISize, -marginISize);
8137 result.marginIStart = computedMargin.IStart(wm);
8138 return result;
8141 /* static */
8142 nsBlockFrame* nsBlockFrame::GetNearestAncestorBlock(nsIFrame* aCandidate) {
8143 nsBlockFrame* block = nullptr;
8144 while (aCandidate) {
8145 block = do_QueryFrame(aCandidate);
8146 if (block) {
8147 // yay, candidate is a block!
8148 return block;
8150 // Not a block. Check its parent next.
8151 aCandidate = aCandidate->GetParent();
8153 MOZ_ASSERT_UNREACHABLE("Fell off frame tree looking for ancestor block!");
8154 return nullptr;
8157 nscoord nsBlockFrame::ComputeFinalBSize(BlockReflowState& aState,
8158 nscoord aBEndEdgeOfChildren) {
8159 const WritingMode wm = aState.mReflowInput.GetWritingMode();
8161 const nscoord effectiveContentBoxBSize =
8162 GetEffectiveComputedBSize(aState.mReflowInput, aState.mConsumedBSize);
8163 const nscoord blockStartBP = aState.BorderPadding().BStart(wm);
8164 const nscoord blockEndBP = aState.BorderPadding().BEnd(wm);
8166 NS_ASSERTION(
8167 !IsTrueOverflowContainer() || (effectiveContentBoxBSize == 0 &&
8168 blockStartBP == 0 && blockEndBP == 0),
8169 "An overflow container's effective content-box block-size, block-start "
8170 "BP, and block-end BP should all be zero!");
8172 const nscoord effectiveContentBoxBSizeWithBStartBP =
8173 NSCoordSaturatingAdd(blockStartBP, effectiveContentBoxBSize);
8174 const nscoord effectiveBorderBoxBSize =
8175 NSCoordSaturatingAdd(effectiveContentBoxBSizeWithBStartBP, blockEndBP);
8177 if (HasColumnSpanSiblings()) {
8178 MOZ_ASSERT(LastInFlow()->GetNextContinuation(),
8179 "Frame constructor should've created column-span siblings!");
8181 // If a block is split by any column-spans, we calculate the final
8182 // block-size by shrinkwrapping our children's block-size for all the
8183 // fragments except for those after the final column-span, but we should
8184 // take no more than our effective border-box block-size. If there's any
8185 // leftover block-size, our next continuations will take up rest.
8187 // We don't need to adjust aBri.mReflowStatus because our children's status
8188 // is the same as ours.
8189 return std::min(effectiveBorderBoxBSize, aBEndEdgeOfChildren);
8192 const nscoord availBSize = aState.mReflowInput.AvailableBSize();
8193 if (availBSize == NS_UNCONSTRAINEDSIZE) {
8194 return effectiveBorderBoxBSize;
8197 // Save our children's reflow status.
8198 const bool isChildStatusComplete = aState.mReflowStatus.IsComplete();
8199 if (isChildStatusComplete && effectiveContentBoxBSize > 0 &&
8200 effectiveBorderBoxBSize > availBSize &&
8201 ShouldAvoidBreakInside(aState.mReflowInput)) {
8202 aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
8203 return effectiveBorderBoxBSize;
8206 const bool isBDBClone =
8207 aState.mReflowInput.mStyleBorder->mBoxDecorationBreak ==
8208 StyleBoxDecorationBreak::Clone;
8210 // The maximum value our content-box block-size can take within the given
8211 // available block-size.
8212 const nscoord maxContentBoxBSize = aState.ContentBSize();
8214 // The block-end edge of our content-box (relative to this frame's origin) if
8215 // we consumed the maximum block-size available to us (maxContentBoxBSize).
8216 const nscoord maxContentBoxBEnd = aState.ContentBEnd();
8218 // These variables are uninitialized intentionally so that the compiler can
8219 // check they are assigned in every if-else branch below.
8220 nscoord finalContentBoxBSizeWithBStartBP;
8221 bool isOurStatusComplete;
8223 if (effectiveBorderBoxBSize <= availBSize) {
8224 // Our effective border-box block-size can fit in the available block-size,
8225 // so we are complete.
8226 finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP;
8227 isOurStatusComplete = true;
8228 } else if (effectiveContentBoxBSizeWithBStartBP <= maxContentBoxBEnd) {
8229 // Note: The following assertion should generally hold because, for
8230 // box-decoration-break:clone, this "else if" branch is mathematically
8231 // equivalent to the initial "if".
8232 NS_ASSERTION(!isBDBClone,
8233 "This else-if branch is handling a situation that's specific "
8234 "to box-decoration-break:slice, i.e. a case when we can skip "
8235 "our block-end border and padding!");
8237 // Our effective content-box block-size plus the block-start border and
8238 // padding can fit in the available block-size, but it cannot fit after
8239 // adding the block-end border and padding. Thus, we need a continuation
8240 // (unless we already weren't asking for any block-size, in which case we
8241 // stay complete to avoid looping forever).
8242 finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP;
8243 isOurStatusComplete = effectiveContentBoxBSize == 0;
8244 } else {
8245 // We aren't going to be able to fit our content-box in the space available
8246 // to it, which means we'll probably call ourselves incomplete to request a
8247 // continuation. But before making that decision, we check for certain
8248 // conditions which would force us to overflow beyond the available space --
8249 // these might result in us actually being complete if we're forced to
8250 // overflow far enough.
8251 if (MOZ_UNLIKELY(aState.mReflowInput.mFlags.mIsTopOfPage && isBDBClone &&
8252 maxContentBoxBSize <= 0 &&
8253 aBEndEdgeOfChildren == blockStartBP)) {
8254 // In this rare case, we are at the top of page/column, we have
8255 // box-decoration-break:clone and zero available block-size for our
8256 // content-box (e.g. our own block-start border and padding already exceed
8257 // the available block-size), and we didn't lay out any child to consume
8258 // our content-box block-size. To ensure we make progress (avoid looping
8259 // forever), use 1px as our content-box block-size regardless of our
8260 // effective content-box block-size, in the spirit of
8261 // https://drafts.csswg.org/css-break/#breaking-rules.
8262 finalContentBoxBSizeWithBStartBP = blockStartBP + AppUnitsPerCSSPixel();
8263 isOurStatusComplete = effectiveContentBoxBSize <= AppUnitsPerCSSPixel();
8264 } else if (aBEndEdgeOfChildren > maxContentBoxBEnd) {
8265 // We have a unbreakable child whose block-end edge exceeds the available
8266 // block-size for children.
8267 if (aBEndEdgeOfChildren >= effectiveContentBoxBSizeWithBStartBP) {
8268 // The unbreakable child's block-end edge forces us to consume all of
8269 // our effective content-box block-size.
8270 finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP;
8272 // Even though we've consumed all of our effective content-box
8273 // block-size, we may still need to report an incomplete status in order
8274 // to get another continuation, which will be responsible for laying out
8275 // & drawing our block-end border & padding. But if we have no such
8276 // border & padding, or if we're forced to apply that border & padding
8277 // on this frame due to box-decoration-break:clone, then we don't need
8278 // to bother with that additional continuation.
8279 isOurStatusComplete = (isBDBClone || blockEndBP == 0);
8280 } else {
8281 // The unbreakable child's block-end edge doesn't force us to consume
8282 // all of our effective content-box block-size.
8283 finalContentBoxBSizeWithBStartBP = aBEndEdgeOfChildren;
8284 isOurStatusComplete = false;
8286 } else {
8287 // The children's block-end edge can fit in the content-box space that we
8288 // have available for it. Consume all the space that is available so that
8289 // our inline-start/inline-end borders extend all the way to the block-end
8290 // edge of column/page.
8291 finalContentBoxBSizeWithBStartBP = maxContentBoxBEnd;
8292 isOurStatusComplete = false;
8296 nscoord finalBorderBoxBSize = finalContentBoxBSizeWithBStartBP;
8297 if (isOurStatusComplete) {
8298 finalBorderBoxBSize = NSCoordSaturatingAdd(finalBorderBoxBSize, blockEndBP);
8299 if (isChildStatusComplete) {
8300 // We want to use children's reflow status as ours, which can be overflow
8301 // incomplete. Suppress the urge to call aBri.mReflowStatus.Reset() here.
8302 } else {
8303 aState.mReflowStatus.SetOverflowIncomplete();
8305 } else {
8306 NS_ASSERTION(!IsTrueOverflowContainer(),
8307 "An overflow container should always be complete because of "
8308 "its zero border-box block-size!");
8309 if (isBDBClone) {
8310 finalBorderBoxBSize =
8311 NSCoordSaturatingAdd(finalBorderBoxBSize, blockEndBP);
8313 aState.mReflowStatus.SetIncomplete();
8314 if (!GetNextInFlow()) {
8315 aState.mReflowStatus.SetNextInFlowNeedsReflow();
8319 return finalBorderBoxBSize;
8322 nsresult nsBlockFrame::ResolveBidi() {
8323 NS_ASSERTION(!GetPrevInFlow(),
8324 "ResolveBidi called on non-first continuation");
8325 MOZ_ASSERT(PresContext()->BidiEnabled());
8326 return nsBidiPresUtils::Resolve(this);
8329 void nsBlockFrame::UpdatePseudoElementStyles(ServoRestyleState& aRestyleState) {
8330 // first-letter needs to be updated before first-line, because first-line can
8331 // change the style of the first-letter.
8332 if (HasFirstLetterChild()) {
8333 UpdateFirstLetterStyle(aRestyleState);
8336 if (nsIFrame* firstLineFrame = GetFirstLineFrame()) {
8337 nsIFrame* styleParent = CorrectStyleParentFrame(firstLineFrame->GetParent(),
8338 PseudoStyleType::firstLine);
8340 ComputedStyle* parentStyle = styleParent->Style();
8341 RefPtr<ComputedStyle> firstLineStyle =
8342 aRestyleState.StyleSet().ResolvePseudoElementStyle(
8343 *mContent->AsElement(), PseudoStyleType::firstLine, nullptr,
8344 parentStyle);
8346 // FIXME(bz): Can we make first-line continuations be non-inheriting anon
8347 // boxes?
8348 RefPtr<ComputedStyle> continuationStyle =
8349 aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle(
8350 PseudoStyleType::mozLineFrame, parentStyle);
8352 UpdateStyleOfOwnedChildFrame(firstLineFrame, firstLineStyle, aRestyleState,
8353 Some(continuationStyle.get()));
8355 // We also want to update the styles of the first-line's descendants. We
8356 // don't need to compute a changehint for this, though, since any changes to
8357 // them are handled by the first-line anyway.
8358 RestyleManager* manager = PresContext()->RestyleManager();
8359 for (nsIFrame* kid : firstLineFrame->PrincipalChildList()) {
8360 manager->ReparentComputedStyleForFirstLine(kid);
8365 nsIFrame* nsBlockFrame::GetFirstLetter() const {
8366 if (!HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE)) {
8367 // Certainly no first-letter frame.
8368 return nullptr;
8371 return GetProperty(FirstLetterProperty());
8374 nsIFrame* nsBlockFrame::GetFirstLineFrame() const {
8375 nsIFrame* maybeFirstLine = PrincipalChildList().FirstChild();
8376 if (maybeFirstLine && maybeFirstLine->IsLineFrame()) {
8377 return maybeFirstLine;
8380 return nullptr;
8383 #ifdef DEBUG
8384 void nsBlockFrame::VerifyLines(bool aFinalCheckOK) {
8385 if (!gVerifyLines) {
8386 return;
8388 if (mLines.empty()) {
8389 return;
8392 nsLineBox* cursor = GetLineCursorForQuery();
8394 // Add up the counts on each line. Also validate that IsFirstLine is
8395 // set properly.
8396 int32_t count = 0;
8397 for (const auto& line : Lines()) {
8398 if (&line == cursor) {
8399 cursor = nullptr;
8401 if (aFinalCheckOK) {
8402 MOZ_ASSERT(line.GetChildCount(), "empty line");
8403 if (line.IsBlock()) {
8404 NS_ASSERTION(1 == line.GetChildCount(), "bad first line");
8407 count += line.GetChildCount();
8410 // Then count the frames
8411 int32_t frameCount = 0;
8412 nsIFrame* frame = mLines.front()->mFirstChild;
8413 while (frame) {
8414 frameCount++;
8415 frame = frame->GetNextSibling();
8417 NS_ASSERTION(count == frameCount, "bad line list");
8419 // Next: test that each line has right number of frames on it
8420 for (LineIterator line = LinesBegin(), line_end = LinesEnd();
8421 line != line_end;) {
8422 count = line->GetChildCount();
8423 frame = line->mFirstChild;
8424 while (--count >= 0) {
8425 frame = frame->GetNextSibling();
8427 ++line;
8428 if ((line != line_end) && (0 != line->GetChildCount())) {
8429 NS_ASSERTION(frame == line->mFirstChild, "bad line list");
8433 if (cursor) {
8434 FrameLines* overflowLines = GetOverflowLines();
8435 if (overflowLines) {
8436 LineIterator line = overflowLines->mLines.begin();
8437 LineIterator line_end = overflowLines->mLines.end();
8438 for (; line != line_end; ++line) {
8439 if (line == cursor) {
8440 cursor = nullptr;
8441 break;
8446 NS_ASSERTION(!cursor, "stale LineCursorProperty");
8449 void nsBlockFrame::VerifyOverflowSituation() {
8450 // Overflow out-of-flows must not have a next-in-flow in mFloats or mFrames.
8451 nsFrameList* oofs = GetOverflowOutOfFlows();
8452 if (oofs) {
8453 for (nsIFrame* f : *oofs) {
8454 nsIFrame* nif = f->GetNextInFlow();
8455 MOZ_ASSERT(!nif ||
8456 (!mFloats.ContainsFrame(nif) && !mFrames.ContainsFrame(nif)));
8460 // Pushed floats must not have a next-in-flow in mFloats or mFrames.
8461 oofs = GetPushedFloats();
8462 if (oofs) {
8463 for (nsIFrame* f : *oofs) {
8464 nsIFrame* nif = f->GetNextInFlow();
8465 MOZ_ASSERT(!nif ||
8466 (!mFloats.ContainsFrame(nif) && !mFrames.ContainsFrame(nif)));
8470 // A child float next-in-flow's parent must be |this| or a next-in-flow of
8471 // |this|. Later next-in-flows must have the same or later parents.
8472 ChildListID childLists[] = {FrameChildListID::Float,
8473 FrameChildListID::PushedFloats};
8474 for (size_t i = 0; i < ArrayLength(childLists); ++i) {
8475 const nsFrameList& children = GetChildList(childLists[i]);
8476 for (nsIFrame* f : children) {
8477 nsIFrame* parent = this;
8478 nsIFrame* nif = f->GetNextInFlow();
8479 for (; nif; nif = nif->GetNextInFlow()) {
8480 bool found = false;
8481 for (nsIFrame* p = parent; p; p = p->GetNextInFlow()) {
8482 if (nif->GetParent() == p) {
8483 parent = p;
8484 found = true;
8485 break;
8488 MOZ_ASSERT(
8489 found,
8490 "next-in-flow is a child of parent earlier in the frame tree?");
8495 nsBlockFrame* flow = static_cast<nsBlockFrame*>(FirstInFlow());
8496 while (flow) {
8497 FrameLines* overflowLines = flow->GetOverflowLines();
8498 if (overflowLines) {
8499 NS_ASSERTION(!overflowLines->mLines.empty(),
8500 "should not be empty if present");
8501 NS_ASSERTION(overflowLines->mLines.front()->mFirstChild,
8502 "bad overflow lines");
8503 NS_ASSERTION(overflowLines->mLines.front()->mFirstChild ==
8504 overflowLines->mFrames.FirstChild(),
8505 "bad overflow frames / lines");
8507 auto checkCursor = [&](nsLineBox* cursor) -> bool {
8508 if (!cursor) {
8509 return true;
8511 LineIterator line = flow->LinesBegin();
8512 LineIterator line_end = flow->LinesEnd();
8513 for (; line != line_end && line != cursor; ++line)
8515 if (line == line_end && overflowLines) {
8516 line = overflowLines->mLines.begin();
8517 line_end = overflowLines->mLines.end();
8518 for (; line != line_end && line != cursor; ++line)
8521 return line != line_end;
8523 MOZ_ASSERT(checkCursor(flow->GetLineCursorForDisplay()),
8524 "stale LineCursorPropertyDisplay");
8525 MOZ_ASSERT(checkCursor(flow->GetLineCursorForQuery()),
8526 "stale LineCursorPropertyQuery");
8527 flow = static_cast<nsBlockFrame*>(flow->GetNextInFlow());
8531 int32_t nsBlockFrame::GetDepth() const {
8532 int32_t depth = 0;
8533 nsIFrame* parent = GetParent();
8534 while (parent) {
8535 parent = parent->GetParent();
8536 depth++;
8538 return depth;
8541 already_AddRefed<ComputedStyle> nsBlockFrame::GetFirstLetterStyle(
8542 nsPresContext* aPresContext) {
8543 return aPresContext->StyleSet()->ProbePseudoElementStyle(
8544 *mContent->AsElement(), PseudoStyleType::firstLetter, nullptr, Style());
8546 #endif