/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * rendering object for CSS display:block, inline-block, and list-item * boxes, also used for various anonymous boxes */ #include "nsBlockFrame.h" #include "gfxContext.h" #include "mozilla/AppUnits.h" #include "mozilla/Baseline.h" #include "mozilla/ComputedStyle.h" #include "mozilla/DebugOnly.h" #include "mozilla/Maybe.h" #include "mozilla/PresShell.h" #include "mozilla/StaticPrefs_browser.h" #include "mozilla/StaticPrefs_layout.h" #include "mozilla/ToString.h" #include "mozilla/UniquePtr.h" #include "nsCRT.h" #include "nsCOMPtr.h" #include "nsCSSRendering.h" #include "nsAbsoluteContainingBlock.h" #include "nsBlockReflowContext.h" #include "BlockReflowState.h" #include "nsFontMetrics.h" #include "nsGenericHTMLElement.h" #include "nsLineBox.h" #include "nsLineLayout.h" #include "nsPlaceholderFrame.h" #include "nsStyleConsts.h" #include "nsFrameManager.h" #include "nsPresContext.h" #include "nsPresContextInlines.h" #include "nsHTMLParts.h" #include "nsGkAtoms.h" #include "mozilla/Sprintf.h" #include "nsFloatManager.h" #include "prenv.h" #include "nsError.h" #include "nsIScrollableFrame.h" #include #include "nsLayoutUtils.h" #include "nsDisplayList.h" #include "nsCSSFrameConstructor.h" #include "TextOverflow.h" #include "nsIFrameInlines.h" #include "CounterStyleManager.h" #include "mozilla/dom/Selection.h" #include "mozilla/PresShell.h" #include "mozilla/RestyleManager.h" #include "mozilla/ServoStyleSet.h" #include "nsFlexContainerFrame.h" #include "nsBidiPresUtils.h" #include static const int MIN_LINES_NEEDING_CURSOR = 20; using namespace mozilla; using namespace mozilla::css; using namespace mozilla::dom; using namespace mozilla::layout; using AbsPosReflowFlags = nsAbsoluteContainingBlock::AbsPosReflowFlags; using ClearFloatsResult = BlockReflowState::ClearFloatsResult; using ShapeType = nsFloatManager::ShapeType; static void MarkAllDescendantLinesDirty(nsBlockFrame* aBlock) { for (auto& line : aBlock->Lines()) { if (line.IsBlock()) { nsBlockFrame* bf = do_QueryFrame(line.mFirstChild); if (bf) { MarkAllDescendantLinesDirty(bf); } } line.MarkDirty(); } } static void MarkSameFloatManagerLinesDirty(nsBlockFrame* aBlock) { nsBlockFrame* blockWithFloatMgr = aBlock; while (!blockWithFloatMgr->HasAnyStateBits(NS_BLOCK_BFC)) { nsBlockFrame* bf = do_QueryFrame(blockWithFloatMgr->GetParent()); if (!bf) { break; } blockWithFloatMgr = bf; } // Mark every line at and below the line where the float was // dirty, and mark their lines dirty too. We could probably do // something more efficient --- e.g., just dirty the lines that intersect // the float vertically. MarkAllDescendantLinesDirty(blockWithFloatMgr); } /** * Returns true if aFrame is a block that has one or more float children. */ static bool BlockHasAnyFloats(nsIFrame* aFrame) { nsBlockFrame* block = do_QueryFrame(aFrame); if (!block) { return false; } if (block->GetChildList(FrameChildListID::Float).FirstChild()) { return true; } for (const auto& line : block->Lines()) { if (line.IsBlock() && BlockHasAnyFloats(line.mFirstChild)) { return true; } } return false; } // Determines whether the given frame is visible text or has visible text that // participate in the same line. Frames that are not line participants do not // have their children checked. static bool FrameHasVisibleInlineText(nsIFrame* aFrame) { MOZ_ASSERT(aFrame, "Frame argument cannot be null"); if (!aFrame->IsLineParticipant()) { return false; } if (aFrame->IsTextFrame()) { return aFrame->StyleVisibility()->IsVisible() && NS_GET_A(aFrame->StyleText()->mWebkitTextFillColor.CalcColor( aFrame)) != 0; } for (nsIFrame* kid : aFrame->PrincipalChildList()) { if (FrameHasVisibleInlineText(kid)) { return true; } } return false; } // Determines whether any of the frames from the given line have visible text. static bool LineHasVisibleInlineText(nsLineBox* aLine) { nsIFrame* kid = aLine->mFirstChild; int32_t n = aLine->GetChildCount(); while (n-- > 0) { if (FrameHasVisibleInlineText(kid)) { return true; } kid = kid->GetNextSibling(); } return false; } /** * Iterates through the frame's in-flow children and * unions the ink overflow of all text frames which * participate in the line aFrame belongs to. * If a child of aFrame is not a text frame, * we recurse with the child as the aFrame argument. * If aFrame isn't a line participant, we skip it entirely * and return an empty rect. * The resulting nsRect is offset relative to the parent of aFrame. */ static nsRect GetFrameTextArea(nsIFrame* aFrame, nsDisplayListBuilder* aBuilder) { nsRect textArea; if (const nsTextFrame* textFrame = do_QueryFrame(aFrame)) { if (!textFrame->IsEntirelyWhitespace()) { textArea = aFrame->InkOverflowRect(); } } else if (aFrame->IsLineParticipant()) { for (nsIFrame* kid : aFrame->PrincipalChildList()) { nsRect kidTextArea = GetFrameTextArea(kid, aBuilder); textArea.OrWith(kidTextArea); } } // add aFrame's position to keep textArea relative to aFrame's parent return textArea + aFrame->GetPosition(); } /** * Iterates through the line's children and * unions the ink overflow of all text frames. * GetFrameTextArea unions and returns the ink overflow * from all line-participating text frames within the given child. * The nsRect returned from GetLineTextArea is offset * relative to the given line. */ static nsRect GetLineTextArea(nsLineBox* aLine, nsDisplayListBuilder* aBuilder) { nsRect textArea; nsIFrame* kid = aLine->mFirstChild; int32_t n = aLine->GetChildCount(); while (n-- > 0) { nsRect kidTextArea = GetFrameTextArea(kid, aBuilder); textArea.OrWith(kidTextArea); kid = kid->GetNextSibling(); } return textArea; } /** * Starting with aFrame, iterates upward through parent frames and checks for * non-transparent background colors. If one is found, we use that as our * backplate color. Otheriwse, we use the default background color from * our high contrast theme. */ static nscolor GetBackplateColor(nsIFrame* aFrame) { nsPresContext* pc = aFrame->PresContext(); nscolor currentBackgroundColor = NS_TRANSPARENT; for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) { // NOTE(emilio): We assume themed frames (frame->IsThemed()) have correct // background-color information so as to compute the right backplate color. // // This holds because HTML widgets with author-specified backgrounds or // borders disable theming. So as long as the UA-specified background colors // match the actual theme (which they should because we always use system // colors with the non-native theme, and native system colors should also // match the native theme), then we're alright and we should compute an // appropriate backplate color. const auto* style = frame->Style(); if (style->StyleBackground()->IsTransparent(style)) { continue; } bool drawImage = false, drawColor = false; nscolor backgroundColor = nsCSSRendering::DetermineBackgroundColor( pc, style, frame, drawImage, drawColor); if (!drawColor && !drawImage) { continue; } if (NS_GET_A(backgroundColor) == 0) { // Even if there's a background image, if there's no background color we // keep going up the frame tree, see bug 1723938. continue; } if (NS_GET_A(currentBackgroundColor) == 0) { // Try to avoid somewhat expensive math in the common case. currentBackgroundColor = backgroundColor; } else { currentBackgroundColor = NS_ComposeColors(backgroundColor, currentBackgroundColor); } if (NS_GET_A(currentBackgroundColor) == 0xff) { // If fully opaque, we're done, otherwise keep going up blending with our // background. return currentBackgroundColor; } } nscolor backgroundColor = aFrame->PresContext()->DefaultBackgroundColor(); if (NS_GET_A(currentBackgroundColor) == 0) { return backgroundColor; } return NS_ComposeColors(backgroundColor, currentBackgroundColor); } #ifdef DEBUG # include "nsBlockDebugFlags.h" bool nsBlockFrame::gLamePaintMetrics; bool nsBlockFrame::gLameReflowMetrics; bool nsBlockFrame::gNoisy; bool nsBlockFrame::gNoisyDamageRepair; bool nsBlockFrame::gNoisyIntrinsic; bool nsBlockFrame::gNoisyReflow; bool nsBlockFrame::gReallyNoisyReflow; bool nsBlockFrame::gNoisyFloatManager; bool nsBlockFrame::gVerifyLines; bool nsBlockFrame::gDisableResizeOpt; int32_t nsBlockFrame::gNoiseIndent; struct BlockDebugFlags { const char* name; bool* on; }; static const BlockDebugFlags gFlags[] = { {"reflow", &nsBlockFrame::gNoisyReflow}, {"really-noisy-reflow", &nsBlockFrame::gReallyNoisyReflow}, {"intrinsic", &nsBlockFrame::gNoisyIntrinsic}, {"float-manager", &nsBlockFrame::gNoisyFloatManager}, {"verify-lines", &nsBlockFrame::gVerifyLines}, {"damage-repair", &nsBlockFrame::gNoisyDamageRepair}, {"lame-paint-metrics", &nsBlockFrame::gLamePaintMetrics}, {"lame-reflow-metrics", &nsBlockFrame::gLameReflowMetrics}, {"disable-resize-opt", &nsBlockFrame::gDisableResizeOpt}, }; # define NUM_DEBUG_FLAGS (sizeof(gFlags) / sizeof(gFlags[0])) static void ShowDebugFlags() { printf("Here are the available GECKO_BLOCK_DEBUG_FLAGS:\n"); const BlockDebugFlags* bdf = gFlags; const BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS; for (; bdf < end; bdf++) { printf(" %s\n", bdf->name); } printf("Note: GECKO_BLOCK_DEBUG_FLAGS is a comma separated list of flag\n"); printf("names (no whitespace)\n"); } void nsBlockFrame::InitDebugFlags() { static bool firstTime = true; if (firstTime) { firstTime = false; char* flags = PR_GetEnv("GECKO_BLOCK_DEBUG_FLAGS"); if (flags) { bool error = false; for (;;) { char* cm = strchr(flags, ','); if (cm) { *cm = '\0'; } bool found = false; const BlockDebugFlags* bdf = gFlags; const BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS; for (; bdf < end; bdf++) { if (nsCRT::strcasecmp(bdf->name, flags) == 0) { *(bdf->on) = true; printf("nsBlockFrame: setting %s debug flag on\n", bdf->name); gNoisy = true; found = true; break; } } if (!found) { error = true; } if (!cm) { break; } *cm = ','; flags = cm + 1; } if (error) { ShowDebugFlags(); } } } } #endif //---------------------------------------------------------------------- // Debugging support code #ifdef DEBUG const char* nsBlockFrame::kReflowCommandType[] = { "ContentChanged", "StyleChanged", "ReflowDirty", "Timeout", "UserDefined", }; const char* nsBlockFrame::LineReflowStatusToString( LineReflowStatus aLineReflowStatus) const { switch (aLineReflowStatus) { case LineReflowStatus::OK: return "LINE_REFLOW_OK"; case LineReflowStatus::Stop: return "LINE_REFLOW_STOP"; case LineReflowStatus::RedoNoPull: return "LINE_REFLOW_REDO_NO_PULL"; case LineReflowStatus::RedoMoreFloats: return "LINE_REFLOW_REDO_MORE_FLOATS"; case LineReflowStatus::RedoNextBand: return "LINE_REFLOW_REDO_NEXT_BAND"; case LineReflowStatus::Truncated: return "LINE_REFLOW_TRUNCATED"; } return "unknown"; } #endif #ifdef REFLOW_STATUS_COVERAGE static void RecordReflowStatus(bool aChildIsBlock, const nsReflowStatus& aFrameReflowStatus) { static uint32_t record[2]; // 0: child-is-block // 1: child-is-inline int index = 0; if (!aChildIsBlock) { index |= 1; } // Compute new status uint32_t newS = record[index]; if (aFrameReflowStatus.IsInlineBreak()) { if (aFrameReflowStatus.IsInlineBreakBefore()) { newS |= 1; } else if (aFrameReflowStatus.IsIncomplete()) { newS |= 2; } else { newS |= 4; } } else if (aFrameReflowStatus.IsIncomplete()) { newS |= 8; } else { newS |= 16; } // Log updates to the status that yield different values if (record[index] != newS) { record[index] = newS; printf("record(%d): %02x %02x\n", index, record[0], record[1]); } } #endif NS_DECLARE_FRAME_PROPERTY_WITH_DTOR_NEVER_CALLED(OverflowLinesProperty, nsBlockFrame::FrameLines) NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OverflowOutOfFlowsProperty) NS_DECLARE_FRAME_PROPERTY_FRAMELIST(PushedFloatProperty) NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OutsideMarkerProperty) NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(InsideMarkerProperty, nsIFrame) NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(BlockEndEdgeOfChildrenProperty, nscoord) //---------------------------------------------------------------------- nsBlockFrame* NS_NewBlockFrame(PresShell* aPresShell, ComputedStyle* aStyle) { return new (aPresShell) nsBlockFrame(aStyle, aPresShell->GetPresContext()); } NS_IMPL_FRAMEARENA_HELPERS(nsBlockFrame) nsBlockFrame::~nsBlockFrame() = default; void nsBlockFrame::AddSizeOfExcludingThisForTree( nsWindowSizes& aWindowSizes) const { nsContainerFrame::AddSizeOfExcludingThisForTree(aWindowSizes); // Add the size of any nsLineBox::mFrames hashtables we might have: for (const auto& line : Lines()) { line.AddSizeOfExcludingThis(aWindowSizes); } const FrameLines* overflowLines = GetOverflowLines(); if (overflowLines) { ConstLineIterator line = overflowLines->mLines.begin(), line_end = overflowLines->mLines.end(); for (; line != line_end; ++line) { line->AddSizeOfExcludingThis(aWindowSizes); } } } void nsBlockFrame::Destroy(DestroyContext& aContext) { ClearLineCursors(); DestroyAbsoluteFrames(aContext); mFloats.DestroyFrames(aContext); nsPresContext* presContext = PresContext(); mozilla::PresShell* presShell = presContext->PresShell(); nsLineBox::DeleteLineList(presContext, mLines, &mFrames, aContext); if (HasPushedFloats()) { SafelyDestroyFrameListProp(aContext, presShell, PushedFloatProperty()); RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS); } // destroy overflow lines now FrameLines* overflowLines = RemoveOverflowLines(); if (overflowLines) { nsLineBox::DeleteLineList(presContext, overflowLines->mLines, &overflowLines->mFrames, aContext); delete overflowLines; } if (HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) { SafelyDestroyFrameListProp(aContext, presShell, OverflowOutOfFlowsProperty()); RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS); } if (HasOutsideMarker()) { SafelyDestroyFrameListProp(aContext, presShell, OutsideMarkerProperty()); RemoveStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER); } nsContainerFrame::Destroy(aContext); } /* virtual */ nsILineIterator* nsBlockFrame::GetLineIterator() { nsLineIterator* iter = GetProperty(LineIteratorProperty()); if (!iter) { const nsStyleVisibility* visibility = StyleVisibility(); iter = new nsLineIterator(mLines, visibility->mDirection == StyleDirection::Rtl); SetProperty(LineIteratorProperty(), iter); } return iter; } NS_QUERYFRAME_HEAD(nsBlockFrame) NS_QUERYFRAME_ENTRY(nsBlockFrame) NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) #ifdef DEBUG_FRAME_DUMP void nsBlockFrame::List(FILE* out, const char* aPrefix, ListFlags aFlags) const { nsCString str; ListGeneric(str, aPrefix, aFlags); fprintf_stderr(out, "%s <\n", str.get()); nsCString pfx(aPrefix); pfx += " "; // Output the lines if (!mLines.empty()) { ConstLineIterator line = LinesBegin(), line_end = LinesEnd(); for (; line != line_end; ++line) { line->List(out, pfx.get(), aFlags); } } // Output the overflow lines. const FrameLines* overflowLines = GetOverflowLines(); if (overflowLines && !overflowLines->mLines.empty()) { fprintf_stderr(out, "%sOverflow-lines %p/%p <\n", pfx.get(), overflowLines, &overflowLines->mFrames); nsCString nestedPfx(pfx); nestedPfx += " "; ConstLineIterator line = overflowLines->mLines.begin(), line_end = overflowLines->mLines.end(); for (; line != line_end; ++line) { line->List(out, nestedPfx.get(), aFlags); } fprintf_stderr(out, "%s>\n", pfx.get()); } // skip the principal list - we printed the lines above // skip the overflow list - we printed the overflow lines above ChildListIDs skip = {FrameChildListID::Principal, FrameChildListID::Overflow}; ListChildLists(out, pfx.get(), aFlags, skip); fprintf_stderr(out, "%s>\n", aPrefix); } nsresult nsBlockFrame::GetFrameName(nsAString& aResult) const { return MakeFrameName(u"Block"_ns, aResult); } #endif void nsBlockFrame::InvalidateFrame(uint32_t aDisplayItemKey, bool aRebuildDisplayItems) { if (IsInSVGTextSubtree()) { NS_ASSERTION(GetParent()->IsSVGTextFrame(), "unexpected block frame in SVG text"); GetParent()->InvalidateFrame(); return; } nsContainerFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems); } void nsBlockFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey, bool aRebuildDisplayItems) { if (IsInSVGTextSubtree()) { NS_ASSERTION(GetParent()->IsSVGTextFrame(), "unexpected block frame in SVG text"); GetParent()->InvalidateFrame(); return; } nsContainerFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey, aRebuildDisplayItems); } nscoord nsBlockFrame::SynthesizeFallbackBaseline( WritingMode aWM, BaselineSharingGroup aBaselineGroup) const { return Baseline::SynthesizeBOffsetFromMarginBox(this, aWM, aBaselineGroup); } template Maybe nsBlockFrame::GetBaselineBOffset( LineIteratorType aStart, LineIteratorType aEnd, WritingMode aWM, BaselineSharingGroup aBaselineGroup, BaselineExportContext aExportContext) const { MOZ_ASSERT((std::is_same_v && aBaselineGroup == BaselineSharingGroup::First) || (std::is_same_v && aBaselineGroup == BaselineSharingGroup::Last), "Iterator direction must match baseline sharing group."); for (auto line = aStart; line != aEnd; ++line) { if (!line->IsBlock()) { // XXX Is this the right test? We have some bogus empty lines // floating around, but IsEmpty is perhaps too weak. if (line->BSize() != 0 || !line->IsEmpty()) { const auto ascent = line->BStart() + line->GetLogicalAscent(); if (aBaselineGroup == BaselineSharingGroup::Last) { return Some(BSize(aWM) - ascent); } return Some(ascent); } continue; } nsIFrame* kid = line->mFirstChild; if (aWM.IsOrthogonalTo(kid->GetWritingMode())) { continue; } if (aExportContext == BaselineExportContext::LineLayout && kid->IsTableWrapperFrame()) { // `` in inline-block context does not export any baseline. continue; } const auto kidBaselineGroup = aExportContext == BaselineExportContext::LineLayout ? kid->GetDefaultBaselineSharingGroup() : aBaselineGroup; const auto kidBaseline = kid->GetNaturalBaselineBOffset(aWM, kidBaselineGroup, aExportContext); if (!kidBaseline) { continue; } auto result = *kidBaseline; if (kidBaselineGroup == BaselineSharingGroup::Last) { result = kid->BSize(aWM) - result; } // Ignore relative positioning for baseline calculations. const nsSize& sz = line->mContainerSize; result += kid->GetLogicalNormalPosition(aWM, sz).B(aWM); if (aBaselineGroup == BaselineSharingGroup::Last) { return Some(BSize(aWM) - result); } return Some(result); } return Nothing{}; } Maybe nsBlockFrame::GetNaturalBaselineBOffset( WritingMode aWM, BaselineSharingGroup aBaselineGroup, BaselineExportContext aExportContext) const { if (StyleDisplay()->IsContainLayout()) { return Nothing{}; } if (aBaselineGroup == BaselineSharingGroup::First) { return GetBaselineBOffset(LinesBegin(), LinesEnd(), aWM, aBaselineGroup, aExportContext); } return GetBaselineBOffset(LinesRBegin(), LinesREnd(), aWM, aBaselineGroup, aExportContext); } nscoord nsBlockFrame::GetCaretBaseline() const { nsRect contentRect = GetContentRect(); nsMargin bp = GetUsedBorderAndPadding(); if (!mLines.empty()) { ConstLineIterator line = LinesBegin(); if (!line->IsEmpty()) { if (line->IsBlock()) { return bp.top + line->mFirstChild->GetCaretBaseline(); } return line->BStart() + line->GetLogicalAscent(); } } float inflation = nsLayoutUtils::FontSizeInflationFor(this); RefPtr fm = nsLayoutUtils::GetFontMetricsForFrame(this, inflation); nscoord lineHeight = ReflowInput::CalcLineHeight( *Style(), PresContext(), GetContent(), contentRect.height, inflation); const WritingMode wm = GetWritingMode(); return nsLayoutUtils::GetCenteredFontBaseline(fm, lineHeight, wm.IsLineInverted()) + bp.top; } ///////////////////////////////////////////////////////////////////////////// // Child frame enumeration const nsFrameList& nsBlockFrame::GetChildList(ChildListID aListID) const { switch (aListID) { case FrameChildListID::Principal: return mFrames; case FrameChildListID::Overflow: { FrameLines* overflowLines = GetOverflowLines(); return overflowLines ? overflowLines->mFrames : nsFrameList::EmptyList(); } case FrameChildListID::Float: return mFloats; case FrameChildListID::OverflowOutOfFlow: { const nsFrameList* list = GetOverflowOutOfFlows(); return list ? *list : nsFrameList::EmptyList(); } case FrameChildListID::PushedFloats: { const nsFrameList* list = GetPushedFloats(); return list ? *list : nsFrameList::EmptyList(); } case FrameChildListID::Bullet: { const nsFrameList* list = GetOutsideMarkerList(); return list ? *list : nsFrameList::EmptyList(); } default: return nsContainerFrame::GetChildList(aListID); } } void nsBlockFrame::GetChildLists(nsTArray* aLists) const { nsContainerFrame::GetChildLists(aLists); FrameLines* overflowLines = GetOverflowLines(); if (overflowLines) { overflowLines->mFrames.AppendIfNonempty(aLists, FrameChildListID::Overflow); } const nsFrameList* list = GetOverflowOutOfFlows(); if (list) { list->AppendIfNonempty(aLists, FrameChildListID::OverflowOutOfFlow); } mFloats.AppendIfNonempty(aLists, FrameChildListID::Float); list = GetOutsideMarkerList(); if (list) { list->AppendIfNonempty(aLists, FrameChildListID::Bullet); } list = GetPushedFloats(); if (list) { list->AppendIfNonempty(aLists, FrameChildListID::PushedFloats); } } /* virtual */ bool nsBlockFrame::IsFloatContainingBlock() const { return true; } /** * Remove the first line from aFromLines and adjust the associated frame list * aFromFrames accordingly. The removed line is assigned to *aOutLine and * a frame list with its frames is assigned to *aOutFrames, i.e. the frames * that were extracted from the head of aFromFrames. * aFromLines must contain at least one line, the line may be empty. * @return true if aFromLines becomes empty */ static bool RemoveFirstLine(nsLineList& aFromLines, nsFrameList& aFromFrames, nsLineBox** aOutLine, nsFrameList* aOutFrames) { nsLineList_iterator removedLine = aFromLines.begin(); *aOutLine = removedLine; nsLineList_iterator next = aFromLines.erase(removedLine); bool isLastLine = next == aFromLines.end(); nsIFrame* firstFrameInNextLine = isLastLine ? nullptr : next->mFirstChild; *aOutFrames = aFromFrames.TakeFramesBefore(firstFrameInNextLine); return isLastLine; } ////////////////////////////////////////////////////////////////////// // Reflow methods /* virtual */ void nsBlockFrame::MarkIntrinsicISizesDirty() { nsBlockFrame* dirtyBlock = static_cast(FirstContinuation()); dirtyBlock->mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN; dirtyBlock->mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN; if (!HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION)) { for (nsIFrame* frame = dirtyBlock; frame; frame = frame->GetNextContinuation()) { frame->AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION); } } nsContainerFrame::MarkIntrinsicISizesDirty(); } void nsBlockFrame::CheckIntrinsicCacheAgainstShrinkWrapState() { nsPresContext* presContext = PresContext(); if (!nsLayoutUtils::FontSizeInflationEnabled(presContext)) { return; } bool inflationEnabled = !presContext->mInflationDisabledForShrinkWrap; if (inflationEnabled != HasAnyStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED)) { mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN; mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN; if (inflationEnabled) { AddStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED); } else { RemoveStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED); } } } // Whether this line is indented by the text-indent amount. bool nsBlockFrame::TextIndentAppliesTo(const LineIterator& aLine) const { const auto& textIndent = StyleText()->mTextIndent; bool isFirstLineOrAfterHardBreak = [&] { if (aLine != LinesBegin()) { // If not the first line of the block, but 'each-line' is in effect, // check if the previous line was not wrapped. return textIndent.each_line && !aLine.prev()->IsLineWrapped(); } if (nsBlockFrame* prevBlock = do_QueryFrame(GetPrevInFlow())) { // There's a prev-in-flow, so this only counts as a first-line if // 'each-line' and the prev-in-flow's last line was not wrapped. return textIndent.each_line && (prevBlock->Lines().empty() || !prevBlock->LinesEnd().prev()->IsLineWrapped()); } return true; }(); // The 'hanging' option inverts which lines are/aren't indented. return isFirstLineOrAfterHardBreak != textIndent.hanging; } /* virtual */ nscoord nsBlockFrame::GetMinISize(gfxContext* aRenderingContext) { nsIFrame* firstInFlow = FirstContinuation(); if (firstInFlow != this) { return firstInFlow->GetMinISize(aRenderingContext); } DISPLAY_MIN_INLINE_SIZE(this, mCachedMinISize); CheckIntrinsicCacheAgainstShrinkWrapState(); if (mCachedMinISize != NS_INTRINSIC_ISIZE_UNKNOWN) { return mCachedMinISize; } if (Maybe containISize = ContainIntrinsicISize()) { mCachedMinISize = *containISize; return mCachedMinISize; } #ifdef DEBUG if (gNoisyIntrinsic) { IndentBy(stdout, gNoiseIndent); ListTag(stdout); printf(": GetMinISize\n"); } AutoNoisyIndenter indenter(gNoisyIntrinsic); #endif for (nsBlockFrame* curFrame = this; curFrame; curFrame = static_cast(curFrame->GetNextContinuation())) { curFrame->LazyMarkLinesDirty(); } if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) && PresContext()->BidiEnabled()) { ResolveBidi(); } const bool whiteSpaceCanWrap = StyleText()->WhiteSpaceCanWrapStyle(); InlineMinISizeData data; for (nsBlockFrame* curFrame = this; curFrame; curFrame = static_cast(curFrame->GetNextContinuation())) { for (LineIterator line = curFrame->LinesBegin(), line_end = curFrame->LinesEnd(); line != line_end; ++line) { #ifdef DEBUG if (gNoisyIntrinsic) { IndentBy(stdout, gNoiseIndent); printf("line (%s%s)\n", line->IsBlock() ? "block" : "inline", line->IsEmpty() ? ", empty" : ""); } AutoNoisyIndenter lineindent(gNoisyIntrinsic); #endif if (line->IsBlock()) { data.ForceBreak(); data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer( aRenderingContext, line->mFirstChild, IntrinsicISizeType::MinISize); data.ForceBreak(); } else { if (!curFrame->GetPrevContinuation() && TextIndentAppliesTo(line)) { data.mCurrentLine += StyleText()->mTextIndent.length.Resolve(0); } data.mLine = &line; data.SetLineContainer(curFrame); nsIFrame* kid = line->mFirstChild; for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end; ++i, kid = kid->GetNextSibling()) { kid->AddInlineMinISize(aRenderingContext, &data); if (whiteSpaceCanWrap && data.mTrailingWhitespace) { data.OptionallyBreak(); } } } #ifdef DEBUG if (gNoisyIntrinsic) { IndentBy(stdout, gNoiseIndent); printf("min: [prevLines=%d currentLine=%d]\n", data.mPrevLines, data.mCurrentLine); } #endif } } data.ForceBreak(); mCachedMinISize = data.mPrevLines; return mCachedMinISize; } /* virtual */ nscoord nsBlockFrame::GetPrefISize(gfxContext* aRenderingContext) { nsIFrame* firstInFlow = FirstContinuation(); if (firstInFlow != this) { return firstInFlow->GetPrefISize(aRenderingContext); } DISPLAY_PREF_INLINE_SIZE(this, mCachedPrefISize); CheckIntrinsicCacheAgainstShrinkWrapState(); if (mCachedPrefISize != NS_INTRINSIC_ISIZE_UNKNOWN) { return mCachedPrefISize; } if (Maybe containISize = ContainIntrinsicISize()) { mCachedPrefISize = *containISize; return mCachedPrefISize; } #ifdef DEBUG if (gNoisyIntrinsic) { IndentBy(stdout, gNoiseIndent); ListTag(stdout); printf(": GetPrefISize\n"); } AutoNoisyIndenter indenter(gNoisyIntrinsic); #endif for (nsBlockFrame* curFrame = this; curFrame; curFrame = static_cast(curFrame->GetNextContinuation())) { curFrame->LazyMarkLinesDirty(); } if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) && PresContext()->BidiEnabled()) { ResolveBidi(); } InlinePrefISizeData data; for (nsBlockFrame* curFrame = this; curFrame; curFrame = static_cast(curFrame->GetNextContinuation())) { for (LineIterator line = curFrame->LinesBegin(), line_end = curFrame->LinesEnd(); line != line_end; ++line) { #ifdef DEBUG if (gNoisyIntrinsic) { IndentBy(stdout, gNoiseIndent); printf("line (%s%s)\n", line->IsBlock() ? "block" : "inline", line->IsEmpty() ? ", empty" : ""); } AutoNoisyIndenter lineindent(gNoisyIntrinsic); #endif if (line->IsBlock()) { StyleClear clearType; if (!data.mLineIsEmpty || BlockCanIntersectFloats(line->mFirstChild)) { clearType = StyleClear::Both; } else { clearType = line->mFirstChild->StyleDisplay()->mClear; } data.ForceBreak(clearType); data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer( aRenderingContext, line->mFirstChild, IntrinsicISizeType::PrefISize); data.ForceBreak(); } else { if (!curFrame->GetPrevContinuation() && TextIndentAppliesTo(line)) { nscoord indent = StyleText()->mTextIndent.length.Resolve(0); data.mCurrentLine += indent; // XXXmats should the test below be indent > 0? if (indent != nscoord(0)) { data.mLineIsEmpty = false; } } data.mLine = &line; data.SetLineContainer(curFrame); nsIFrame* kid = line->mFirstChild; for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end; ++i, kid = kid->GetNextSibling()) { kid->AddInlinePrefISize(aRenderingContext, &data); } } #ifdef DEBUG if (gNoisyIntrinsic) { IndentBy(stdout, gNoiseIndent); printf("pref: [prevLines=%d currentLine=%d]\n", data.mPrevLines, data.mCurrentLine); } #endif } } data.ForceBreak(); mCachedPrefISize = data.mPrevLines; return mCachedPrefISize; } nsRect nsBlockFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const { // be conservative if (Style()->HasTextDecorationLines()) { return InkOverflowRect(); } return ComputeSimpleTightBounds(aDrawTarget); } /* virtual */ nsresult nsBlockFrame::GetPrefWidthTightBounds(gfxContext* aRenderingContext, nscoord* aX, nscoord* aXMost) { nsIFrame* firstInFlow = FirstContinuation(); if (firstInFlow != this) { return firstInFlow->GetPrefWidthTightBounds(aRenderingContext, aX, aXMost); } *aX = 0; *aXMost = 0; nsresult rv; InlinePrefISizeData data; for (nsBlockFrame* curFrame = this; curFrame; curFrame = static_cast(curFrame->GetNextContinuation())) { for (LineIterator line = curFrame->LinesBegin(), line_end = curFrame->LinesEnd(); line != line_end; ++line) { nscoord childX, childXMost; if (line->IsBlock()) { data.ForceBreak(); rv = line->mFirstChild->GetPrefWidthTightBounds(aRenderingContext, &childX, &childXMost); NS_ENSURE_SUCCESS(rv, rv); *aX = std::min(*aX, childX); *aXMost = std::max(*aXMost, childXMost); } else { if (!curFrame->GetPrevContinuation() && TextIndentAppliesTo(line)) { data.mCurrentLine += StyleText()->mTextIndent.length.Resolve(0); } data.mLine = &line; data.SetLineContainer(curFrame); nsIFrame* kid = line->mFirstChild; for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end; ++i, kid = kid->GetNextSibling()) { rv = kid->GetPrefWidthTightBounds(aRenderingContext, &childX, &childXMost); NS_ENSURE_SUCCESS(rv, rv); *aX = std::min(*aX, data.mCurrentLine + childX); *aXMost = std::max(*aXMost, data.mCurrentLine + childXMost); kid->AddInlinePrefISize(aRenderingContext, &data); } } } } data.ForceBreak(); return NS_OK; } /** * Return whether aNewAvailableSpace is smaller *on either side* * (inline-start or inline-end) than aOldAvailableSpace, so that we know * if we need to redo layout on an line, replaced block, or block * formatting context, because its height (which we used to compute * aNewAvailableSpace) caused it to intersect additional floats. */ static bool AvailableSpaceShrunk(WritingMode aWM, const LogicalRect& aOldAvailableSpace, const LogicalRect& aNewAvailableSpace, bool aCanGrow /* debug-only */) { if (aNewAvailableSpace.ISize(aWM) == 0) { // Positions are not significant if the inline size is zero. return aOldAvailableSpace.ISize(aWM) != 0; } if (aCanGrow) { NS_ASSERTION( aNewAvailableSpace.IStart(aWM) <= aOldAvailableSpace.IStart(aWM) || aNewAvailableSpace.IEnd(aWM) <= aOldAvailableSpace.IEnd(aWM), "available space should not shrink on the start side and " "grow on the end side"); NS_ASSERTION( aNewAvailableSpace.IStart(aWM) >= aOldAvailableSpace.IStart(aWM) || aNewAvailableSpace.IEnd(aWM) >= aOldAvailableSpace.IEnd(aWM), "available space should not grow on the start side and " "shrink on the end side"); } else { NS_ASSERTION( aOldAvailableSpace.IStart(aWM) <= aNewAvailableSpace.IStart(aWM) && aOldAvailableSpace.IEnd(aWM) >= aNewAvailableSpace.IEnd(aWM), "available space should never grow"); } // Have we shrunk on either side? return aNewAvailableSpace.IStart(aWM) > aOldAvailableSpace.IStart(aWM) || aNewAvailableSpace.IEnd(aWM) < aOldAvailableSpace.IEnd(aWM); } static LogicalSize CalculateContainingBlockSizeForAbsolutes( WritingMode aWM, const ReflowInput& aReflowInput, LogicalSize aFrameSize) { // The issue here is that for a 'height' of 'auto' the reflow input // code won't know how to calculate the containing block height // because it's calculated bottom up. So we use our own computed // size as the dimensions. nsIFrame* frame = aReflowInput.mFrame; LogicalSize cbSize(aFrameSize); // Containing block is relative to the padding edge const LogicalMargin border = aReflowInput.ComputedLogicalBorder(aWM); cbSize.ISize(aWM) -= border.IStartEnd(aWM); cbSize.BSize(aWM) -= border.BStartEnd(aWM); if (frame->GetParent()->GetContent() != frame->GetContent() || frame->GetParent()->IsCanvasFrame()) { return cbSize; } // We are a wrapped frame for the content (and the wrapper is not the // canvas frame, whose size is not meaningful here). // Use the container's dimensions, if they have been precomputed. // XXX This is a hack! We really should be waiting until the outermost // frame is fully reflowed and using the resulting dimensions, even // if they're intrinsic. // In fact we should be attaching absolute children to the outermost // frame and not always sticking them in block frames. // First, find the reflow input for the outermost frame for this content. const ReflowInput* lastRI = &aReflowInput; DebugOnly lastButOneRI = &aReflowInput; while (lastRI->mParentReflowInput && lastRI->mParentReflowInput->mFrame->GetContent() == frame->GetContent()) { lastButOneRI = lastRI; lastRI = lastRI->mParentReflowInput; } if (lastRI == &aReflowInput) { return cbSize; } // For scroll containers, we can just use cbSize (which is the padding-box // size of the scrolled-content frame). if (nsIScrollableFrame* scrollFrame = do_QueryFrame(lastRI->mFrame)) { // Assert that we're not missing any frames between the abspos containing // block and the scroll container. // the parent. Unused << scrollFrame; MOZ_ASSERT(lastButOneRI == &aReflowInput); return cbSize; } // Same for fieldsets, where the inner anonymous frame has the correct padding // area with the legend taken into account. if (lastRI->mFrame->IsFieldSetFrame()) { return cbSize; } // We found a reflow input for the outermost wrapping frame, so use // its computed metrics if available, converted to our writing mode const LogicalSize lastRISize = lastRI->ComputedSize(aWM); const LogicalMargin lastRIPadding = lastRI->ComputedLogicalPadding(aWM); if (lastRISize.ISize(aWM) != NS_UNCONSTRAINEDSIZE) { cbSize.ISize(aWM) = std::max(0, lastRISize.ISize(aWM) + lastRIPadding.IStartEnd(aWM)); } if (lastRISize.BSize(aWM) != NS_UNCONSTRAINEDSIZE) { cbSize.BSize(aWM) = std::max(0, lastRISize.BSize(aWM) + lastRIPadding.BStartEnd(aWM)); } return cbSize; } /** * Returns aFrame if it is a non-BFC block frame, and null otherwise. * * This is used to determine whether to recurse into aFrame when applying * -webkit-line-clamp. */ static const nsBlockFrame* GetAsLineClampDescendant(const nsIFrame* aFrame) { if (const nsBlockFrame* block = do_QueryFrame(aFrame)) { if (!block->HasAnyStateBits(NS_BLOCK_BFC)) { return block; } } return nullptr; } static nsBlockFrame* GetAsLineClampDescendant(nsIFrame* aFrame) { return const_cast( GetAsLineClampDescendant(const_cast(aFrame))); } static bool IsLineClampRoot(const nsBlockFrame* aFrame) { if (!aFrame->StyleDisplay()->mWebkitLineClamp) { return false; } if (!aFrame->HasAnyStateBits(NS_BLOCK_BFC)) { return false; } if (StaticPrefs::layout_css_webkit_line_clamp_block_enabled()) { return true; } // For now, -webkit-box is the only thing allowed to be a line-clamp root. // Ideally we'd just make this work everywhere, but for now we're carrying // this forward as a limitation on the legacy -webkit-line-clamp feature, // since relaxing this limitation might create webcompat trouble. auto origDisplay = [&] { if (aFrame->Style()->GetPseudoType() == PseudoStyleType::scrolledContent) { // If we're the anonymous block inside the scroll frame, we need to look // at the original display of our parent frame. MOZ_ASSERT(aFrame->GetParent()); const auto& parentDisp = *aFrame->GetParent()->StyleDisplay(); MOZ_ASSERT(parentDisp.mWebkitLineClamp == aFrame->StyleDisplay()->mWebkitLineClamp, ":-moz-scrolled-content should inherit -webkit-line-clamp, " "via rule in UA stylesheet"); return parentDisp.mOriginalDisplay; } return aFrame->StyleDisplay()->mOriginalDisplay; }(); return origDisplay.Inside() == StyleDisplayInside::WebkitBox; } bool nsBlockFrame::IsInLineClampContext() const { if (IsLineClampRoot(this)) { return true; } const nsBlockFrame* cur = this; while (GetAsLineClampDescendant(cur)) { cur = do_QueryFrame(cur->GetParent()); if (!cur) { return false; } if (IsLineClampRoot(cur)) { return true; } } return false; } /** * Iterator over all descendant inline line boxes, except for those that are * under an independent formatting context. */ class MOZ_RAII LineClampLineIterator { public: explicit LineClampLineIterator(nsBlockFrame* aFrame) : mCur(aFrame->LinesBegin()), mEnd(aFrame->LinesEnd()), mCurrentFrame(mCur == mEnd ? nullptr : aFrame) { if (mCur != mEnd && !mCur->IsInline()) { Advance(); } } nsLineBox* GetCurrentLine() { return mCurrentFrame ? mCur.get() : nullptr; } nsBlockFrame* GetCurrentFrame() { return mCurrentFrame; } // Advances the iterator to the next line line. // // Next() shouldn't be called once the iterator is at the end, which can be // checked for by GetCurrentLine() or GetCurrentFrame() returning null. void Next() { MOZ_ASSERT(mCur != mEnd && mCurrentFrame, "Don't call Next() when the iterator is at the end"); ++mCur; Advance(); } private: void Advance() { for (;;) { if (mCur == mEnd) { // Reached the end of the current block. Pop the parent off the // stack; if there isn't one, then we've reached the end. if (mStack.IsEmpty()) { mCurrentFrame = nullptr; break; } auto entry = mStack.PopLastElement(); mCurrentFrame = entry.first; mCur = entry.second; mEnd = mCurrentFrame->LinesEnd(); } else if (mCur->IsBlock()) { if (nsBlockFrame* child = GetAsLineClampDescendant(mCur->mFirstChild)) { nsBlockFrame::LineIterator next = mCur; ++next; mStack.AppendElement(std::make_pair(mCurrentFrame, next)); mCur = child->LinesBegin(); mEnd = child->LinesEnd(); mCurrentFrame = child; } else { // Some kind of frame we shouldn't descend into. ++mCur; } } else { MOZ_ASSERT(mCur->IsInline()); break; } } } // The current line within the current block. // // When this is equal to mEnd, the iterator is at its end, and mCurrentFrame // is set to null. nsBlockFrame::LineIterator mCur; // The iterator end for the current block. nsBlockFrame::LineIterator mEnd; // The current block. nsBlockFrame* mCurrentFrame; // Stack of mCurrentFrame and mEnd values that we push and pop as we enter and // exist blocks. AutoTArray, 8> mStack; }; static bool ClearLineClampEllipsis(nsBlockFrame* aFrame) { if (!aFrame->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS)) { for (nsIFrame* f : aFrame->PrincipalChildList()) { if (nsBlockFrame* child = GetAsLineClampDescendant(f)) { if (ClearLineClampEllipsis(child)) { return true; } } } return false; } aFrame->RemoveStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS); for (auto& line : aFrame->Lines()) { if (line.HasLineClampEllipsis()) { line.ClearHasLineClampEllipsis(); return true; } } // We didn't find a line with the ellipsis; it must have been deleted already. return true; } void nsBlockFrame::ClearLineClampEllipsis() { ::ClearLineClampEllipsis(this); } void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics, const ReflowInput& aReflowInput, nsReflowStatus& aStatus) { if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) { FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay); return; } MarkInReflow(); DO_GLOBAL_REFLOW_COUNT("nsBlockFrame"); DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus); MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); #ifdef DEBUG if (gNoisyReflow) { IndentBy(stdout, gNoiseIndent); ListTag(stdout); printf(": begin reflow availSize=%d,%d computedSize=%d,%d\n", aReflowInput.AvailableISize(), aReflowInput.AvailableBSize(), aReflowInput.ComputedISize(), aReflowInput.ComputedBSize()); } AutoNoisyIndenter indent(gNoisy); PRTime start = 0; // Initialize these variablies to silence the compiler. int32_t ctc = 0; // We only use these if they are set (gLameReflowMetrics). if (gLameReflowMetrics) { start = PR_Now(); ctc = nsLineBox::GetCtorCount(); } #endif // ColumnSetWrapper's children depend on ColumnSetWrapper's block-size or // max-block-size because both affect the children's available block-size. if (IsColumnSetWrapperFrame()) { AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE); } Maybe restoreReflowInputAvailBSize; auto MaybeRestore = MakeScopeExit([&] { if (MOZ_UNLIKELY(restoreReflowInputAvailBSize)) { const_cast(aReflowInput) .SetAvailableBSize(*restoreReflowInputAvailBSize); } }); WritingMode wm = aReflowInput.GetWritingMode(); const nscoord consumedBSize = CalcAndCacheConsumedBSize(); const nscoord effectiveContentBoxBSize = GetEffectiveComputedBSize(aReflowInput, consumedBSize); // If we have non-auto block size, we're clipping our kids and we fit, // make sure our kids fit too. const PhysicalAxes physicalBlockAxis = wm.IsVertical() ? PhysicalAxes::Horizontal : PhysicalAxes::Vertical; if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE && aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE && (ShouldApplyOverflowClipping(aReflowInput.mStyleDisplay) & physicalBlockAxis)) { LogicalMargin blockDirExtras = aReflowInput.ComputedLogicalBorderPadding(wm); if (GetLogicalSkipSides().BStart()) { blockDirExtras.BStart(wm) = 0; } else { // Block-end margin never causes us to create continuations, so we // don't need to worry about whether it fits in its entirety. blockDirExtras.BStart(wm) += aReflowInput.ComputedLogicalMargin(wm).BStart(wm); } if (effectiveContentBoxBSize + blockDirExtras.BStartEnd(wm) <= aReflowInput.AvailableBSize()) { restoreReflowInputAvailBSize.emplace(aReflowInput.AvailableBSize()); const_cast(aReflowInput) .SetAvailableBSize(NS_UNCONSTRAINEDSIZE); } } if (IsFrameTreeTooDeep(aReflowInput, aMetrics, aStatus)) { return; } // OK, some lines may be reflowed. Blow away any saved line cursor // because we may invalidate the nondecreasing // overflowArea.InkOverflow().y/yMost invariant, and we may even // delete the line with the line cursor. ClearLineCursors(); // See comment below about oldSize. Use *only* for the // abs-pos-containing-block-size-change optimization! nsSize oldSize = GetSize(); // Should we create a float manager? nsAutoFloatManager autoFloatManager(const_cast(aReflowInput)); // XXXldb If we start storing the float manager in the frame rather // than keeping it around only during reflow then we should create it // only when there are actually floats to manage. Otherwise things // like tables will gain significant bloat. bool needFloatManager = nsBlockFrame::BlockNeedsFloatManager(this); if (needFloatManager) { autoFloatManager.CreateFloatManager(aPresContext); } if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) && PresContext()->BidiEnabled()) { static_cast(FirstContinuation())->ResolveBidi(); } // Whether to apply text-wrap: balance behavior. bool tryBalance = StyleText()->mTextWrapStyle == StyleTextWrapStyle::Balance && !GetPrevContinuation(); // Struct used to hold the "target" number of lines or clamp position to // maintain when doing text-wrap: balance. struct BalanceTarget { // If line-clamp is in effect, mContent and mOffset indicate the starting // position of the first line after the clamp limit. If line-clamp is not // in use, mContent is null and mOffset is the total number of lines that // the block must contain. nsIContent* mContent = nullptr; int32_t mOffset = -1; bool operator==(const BalanceTarget& aOther) const { return mContent == aOther.mContent && mOffset == aOther.mOffset; } bool operator!=(const BalanceTarget& aOther) const { return !(*this == aOther); } }; BalanceTarget balanceTarget; // Helpers for text-wrap: balance implementation: // Count the number of lines in the mLines list, but return -1 (to suppress // balancing) instead if the count is going to exceed aLimit, or if we // encounter a block. auto countLinesUpTo = [&](int32_t aLimit) -> int32_t { int32_t n = 0; for (auto iter = mLines.begin(); iter != mLines.end(); ++iter) { if (++n > aLimit || iter->IsBlock()) { return -1; } } return n; }; // Return a BalanceTarget record representing the position at which line-clamp // will take effect for the current line list. Only to be used when there are // enough lines that the clamp will apply. auto getClampPosition = [&](uint32_t aClampCount) -> BalanceTarget { MOZ_ASSERT(aClampCount < mLines.size()); auto iter = mLines.begin(); for (uint32_t i = 0; i < aClampCount; i++) { ++iter; } nsIFrame* firstChild = iter->mFirstChild; if (!firstChild) { return BalanceTarget{}; } nsIContent* content = firstChild->GetContent(); if (!content) { return BalanceTarget{}; } int32_t offset = 0; if (firstChild->IsTextFrame()) { auto* textFrame = static_cast(firstChild); offset = textFrame->GetContentOffset(); } return BalanceTarget{content, offset}; }; // "balancing" is implemented by shortening the effective inline-size of the // lines, so that content will tend to be pushed down to fill later lines of // the block. `balanceInset` is the current amount of "inset" to apply, and // `balanceStep` is the increment to adjust it by for the next iteration. nscoord balanceStep = 0; // text-wrap: balance loop, executed only once if balancing is not required. nsReflowStatus reflowStatus; TrialReflowState trialState(consumedBSize, effectiveContentBoxBSize, needFloatManager); while (true) { // Save the initial floatManager state for repeated trial reflows. // We'll restore (and re-save) the initial state each time we repeat the // reflow. nsFloatManager::SavedState floatManagerState; aReflowInput.mFloatManager->PushState(&floatManagerState); aMetrics = ReflowOutput(aMetrics.GetWritingMode()); reflowStatus = TrialReflow(aPresContext, aMetrics, aReflowInput, trialState); // Do we need to start a `text-wrap: balance` iteration? if (tryBalance) { tryBalance = false; // Don't try to balance an incomplete block, or if we had to use an // overflow-wrap break position in the initial reflow. if (!reflowStatus.IsFullyComplete() || trialState.mUsedOverflowWrap) { break; } balanceTarget.mOffset = countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit()); if (balanceTarget.mOffset < 2) { // If there are less than 2 lines, or the number exceeds the limit, // no balancing is needed; just break from the balance loop. break; } // Initialize the amount of inset to try, and the iteration step size. balanceStep = aReflowInput.ComputedISize() / balanceTarget.mOffset; trialState.ResetForBalance(balanceStep); balanceStep /= 2; // If -webkit-line-clamp is in effect, then we need to maintain the // content location at which clamping occurs, rather than the total // number of lines in the block. if (StaticPrefs::layout_css_text_wrap_balance_after_clamp_enabled() && IsLineClampRoot(this)) { uint32_t lineClampCount = aReflowInput.mStyleDisplay->mWebkitLineClamp; if (uint32_t(balanceTarget.mOffset) > lineClampCount) { auto t = getClampPosition(lineClampCount); if (t.mContent) { balanceTarget = t; } } } // Restore initial floatManager state for a new trial with updated inset. aReflowInput.mFloatManager->PopState(&floatManagerState); continue; } // Helper to determine whether the current trial succeeded (i.e. was able // to fit the content into the expected number of lines). auto trialSucceeded = [&]() -> bool { if (!reflowStatus.IsFullyComplete() || trialState.mUsedOverflowWrap) { return false; } if (balanceTarget.mContent) { auto t = getClampPosition(aReflowInput.mStyleDisplay->mWebkitLineClamp); return t == balanceTarget; } int32_t numLines = countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit()); return numLines == balanceTarget.mOffset; }; // If we're in the process of a balance operation, check whether we've // inset by too much and either increase or reduce the inset for the next // iteration. if (balanceStep > 0) { if (trialSucceeded()) { trialState.ResetForBalance(balanceStep); } else { trialState.ResetForBalance(-balanceStep); } balanceStep /= 2; aReflowInput.mFloatManager->PopState(&floatManagerState); continue; } // If we were attempting to balance, check whether the final iteration was // successful, and if not, back up by one step. if (balanceTarget.mOffset >= 0) { if (!trialState.mInset || trialSucceeded()) { break; } trialState.ResetForBalance(-1); aReflowInput.mFloatManager->PopState(&floatManagerState); continue; } // If we reach here, no balancing was required, so just exit; we don't // reset (pop) the floatManager state because this is the reflow we're // going to keep. So the saved state is just dropped. break; } // End of text-wrap: balance retry loop // If the block direction is right-to-left, we need to update the bounds of // lines that were placed relative to mContainerSize during reflow, as // we typically do not know the true container size until we've reflowed all // its children. So we use a dummy mContainerSize during reflow (see // BlockReflowState's constructor) and then fix up the positions of the // lines here, once the final block size is known. // // Note that writing-mode:vertical-rl is the only case where the block // logical direction progresses in a negative physical direction, and // therefore block-dir coordinate conversion depends on knowing the width // of the coordinate space in order to translate between the logical and // physical origins. if (aReflowInput.GetWritingMode().IsVerticalRL()) { nsSize containerSize = aMetrics.PhysicalSize(); nscoord deltaX = containerSize.width - trialState.mContainerWidth; if (deltaX != 0) { // We compute our lines and markers' overflow areas later in // ComputeOverflowAreas(), so we don't need to adjust their overflow areas // here. const nsPoint physicalDelta(deltaX, 0); for (auto& line : Lines()) { UpdateLineContainerSize(&line, containerSize); } trialState.mFcBounds.Clear(); for (nsIFrame* f : mFloats) { f->MovePositionBy(physicalDelta); ConsiderChildOverflow(trialState.mFcBounds, f); } nsFrameList* markerList = GetOutsideMarkerList(); if (markerList) { for (nsIFrame* f : *markerList) { f->MovePositionBy(physicalDelta); } } if (nsFrameList* overflowContainers = GetOverflowContainers()) { trialState.mOcBounds.Clear(); for (nsIFrame* f : *overflowContainers) { f->MovePositionBy(physicalDelta); ConsiderChildOverflow(trialState.mOcBounds, f); } } } } aMetrics.SetOverflowAreasToDesiredBounds(); ComputeOverflowAreas(aMetrics.mOverflowAreas, trialState.mBlockEndEdgeOfChildren, aReflowInput.mStyleDisplay); // Factor overflow container child bounds into the overflow area aMetrics.mOverflowAreas.UnionWith(trialState.mOcBounds); // Factor pushed float child bounds into the overflow area aMetrics.mOverflowAreas.UnionWith(trialState.mFcBounds); // Let the absolutely positioned container reflow any absolutely positioned // child frames that need to be reflowed, e.g., elements with a percentage // based width/height // We want to do this under either of two conditions: // 1. If we didn't do the incremental reflow above. // 2. If our size changed. // Even though it's the padding edge that's the containing block, we // can use our rect (the border edge) since if the border style // changed, the reflow would have been targeted at us so we'd satisfy // condition 1. // XXX checking oldSize is bogus, there are various reasons we might have // reflowed but our size might not have been changed to what we // asked for (e.g., we ended up being pushed to a new page) // When WillReflowAgainForClearance is true, we will reflow again without // resetting the size. Because of this, we must not reflow our abs-pos // children in that situation --- what we think is our "new size" will not be // our real new size. This also happens to be more efficient. WritingMode parentWM = aMetrics.GetWritingMode(); if (HasAbsolutelyPositionedChildren()) { nsAbsoluteContainingBlock* absoluteContainer = GetAbsoluteContainingBlock(); bool haveInterrupt = aPresContext->HasPendingInterrupt(); if (aReflowInput.WillReflowAgainForClearance() || haveInterrupt) { // Make sure that when we reflow again we'll actually reflow all the abs // pos frames that might conceivably depend on our size (or all of them, // if we're dirty right now and interrupted; in that case we also need // to mark them all with NS_FRAME_IS_DIRTY). Sadly, we can't do much // better than that, because we don't really know what our size will be, // and it might in fact not change on the followup reflow! if (haveInterrupt && HasAnyStateBits(NS_FRAME_IS_DIRTY)) { absoluteContainer->MarkAllFramesDirty(); } else { absoluteContainer->MarkSizeDependentFramesDirty(); } if (haveInterrupt) { // We're not going to reflow absolute frames; make sure to account for // their existing overflow areas, which is usually a side effect of this // reflow. // // TODO(emilio): nsAbsoluteContainingBlock::Reflow already checks for // interrupt, can we just rely on it and unconditionally take the else // branch below? That's a bit more subtle / risky, since I don't see // what would reflow them in that case if they depended on our size. for (nsIFrame* kid = absoluteContainer->GetChildList().FirstChild(); kid; kid = kid->GetNextSibling()) { ConsiderChildOverflow(aMetrics.mOverflowAreas, kid); } } } else { LogicalSize containingBlockSize = CalculateContainingBlockSizeForAbsolutes(parentWM, aReflowInput, aMetrics.Size(parentWM)); // Mark frames that depend on changes we just made to this frame as dirty: // Now we can assume that the padding edge hasn't moved. // We need to reflow the absolutes if one of them depends on // its placeholder position, or the containing block size in a // direction in which the containing block size might have // changed. // XXX "width" and "height" in this block will become ISize and BSize // when nsAbsoluteContainingBlock is logicalized bool cbWidthChanged = aMetrics.Width() != oldSize.width; bool isRoot = !GetContent()->GetParent(); // If isRoot and we have auto height, then we are the initial // containing block and the containing block height is the // viewport height, which can't change during incremental // reflow. bool cbHeightChanged = !(isRoot && NS_UNCONSTRAINEDSIZE == aReflowInput.ComputedHeight()) && aMetrics.Height() != oldSize.height; nsRect containingBlock(nsPoint(0, 0), containingBlockSize.GetPhysicalSize(parentWM)); AbsPosReflowFlags flags = AbsPosReflowFlags::ConstrainHeight; if (cbWidthChanged) { flags |= AbsPosReflowFlags::CBWidthChanged; } if (cbHeightChanged) { flags |= AbsPosReflowFlags::CBHeightChanged; } // Setup the line cursor here to optimize line searching for // calculating hypothetical position of absolutely-positioned // frames. SetupLineCursorForQuery(); absoluteContainer->Reflow(this, aPresContext, aReflowInput, reflowStatus, containingBlock, flags, &aMetrics.mOverflowAreas); } } FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay); aStatus = reflowStatus; #ifdef DEBUG // Between when we drain pushed floats and when we complete reflow, // we're allowed to have multiple continuations of the same float on // our floats list, since a first-in-flow might get pushed to a later // continuation of its containing block. But it's not permitted // outside that time. nsLayoutUtils::AssertNoDuplicateContinuations(this, mFloats); if (gNoisyReflow) { IndentBy(stdout, gNoiseIndent); ListTag(stdout); printf(": status=%s metrics=%d,%d carriedMargin=%d", ToString(aStatus).c_str(), aMetrics.ISize(parentWM), aMetrics.BSize(parentWM), aMetrics.mCarriedOutBEndMargin.get()); if (HasOverflowAreas()) { printf(" overflow-vis={%d,%d,%d,%d}", aMetrics.InkOverflow().x, aMetrics.InkOverflow().y, aMetrics.InkOverflow().width, aMetrics.InkOverflow().height); printf(" overflow-scr={%d,%d,%d,%d}", aMetrics.ScrollableOverflow().x, aMetrics.ScrollableOverflow().y, aMetrics.ScrollableOverflow().width, aMetrics.ScrollableOverflow().height); } printf("\n"); } if (gLameReflowMetrics) { PRTime end = PR_Now(); int32_t ectc = nsLineBox::GetCtorCount(); int32_t numLines = mLines.size(); if (!numLines) { numLines = 1; } PRTime delta, perLineDelta, lines; lines = int64_t(numLines); delta = end - start; perLineDelta = delta / lines; ListTag(stdout); char buf[400]; SprintfLiteral(buf, ": %" PRId64 " elapsed (%" PRId64 " per line) (%d lines; %d new lines)", delta, perLineDelta, numLines, ectc - ctc); printf("%s\n", buf); } #endif } nsReflowStatus nsBlockFrame::TrialReflow(nsPresContext* aPresContext, ReflowOutput& aMetrics, const ReflowInput& aReflowInput, TrialReflowState& aTrialState) { #ifdef DEBUG // Between when we drain pushed floats and when we complete reflow, // we're allowed to have multiple continuations of the same float on // our floats list, since a first-in-flow might get pushed to a later // continuation of its containing block. But it's not permitted // outside that time. nsLayoutUtils::AssertNoDuplicateContinuations(this, mFloats); #endif // ALWAYS drain overflow. We never want to leave the previnflow's // overflow lines hanging around; block reflow depends on the // overflow line lists being cleared out between reflow passes. DrainOverflowLines(); bool blockStartMarginRoot, blockEndMarginRoot; IsMarginRoot(&blockStartMarginRoot, &blockEndMarginRoot); BlockReflowState state(aReflowInput, aPresContext, this, blockStartMarginRoot, blockEndMarginRoot, aTrialState.mNeedFloatManager, aTrialState.mConsumedBSize, aTrialState.mEffectiveContentBoxBSize, aTrialState.mInset); // Handle paginated overflow (see nsContainerFrame.h) nsReflowStatus ocStatus; if (GetPrevInFlow()) { ReflowOverflowContainerChildren( aPresContext, aReflowInput, aTrialState.mOcBounds, ReflowChildFlags::Default, ocStatus, DefaultChildFrameMerge, Some(state.ContainerSize())); } // Now that we're done cleaning up our overflow container lists, we can // give |state| its nsOverflowContinuationTracker. nsOverflowContinuationTracker tracker(this, false); state.mOverflowTracker = &tracker; // Drain & handle pushed floats DrainPushedFloats(); ReflowPushedFloats(state, aTrialState.mFcBounds); // If we're not dirty (which means we'll mark everything dirty later) // and our inline-size has changed, mark the lines dirty that we need to // mark dirty for a resize reflow. if (!HasAnyStateBits(NS_FRAME_IS_DIRTY) && aReflowInput.IsIResize()) { PrepareResizeReflow(state); } // The same for percentage text-indent, except conditioned on the // parent resizing. if (!HasAnyStateBits(NS_FRAME_IS_DIRTY) && aReflowInput.mCBReflowInput && aReflowInput.mCBReflowInput->IsIResize() && StyleText()->mTextIndent.length.HasPercent() && !mLines.empty()) { mLines.front()->MarkDirty(); } // For text-wrap:balance trials, we need to reflow all the lines even if // they're not all "dirty". if (aTrialState.mBalancing) { MarkAllDescendantLinesDirty(this); } else { LazyMarkLinesDirty(); } // Now reflow... aTrialState.mUsedOverflowWrap = ReflowDirtyLines(state); // If we have a next-in-flow, and that next-in-flow has pushed floats from // this frame from a previous iteration of reflow, then we should not return // a status with IsFullyComplete() equals to true, since we actually have // overflow, it's just already been handled. // NOTE: This really shouldn't happen, since we _should_ pull back our floats // and reflow them, but just in case it does, this is a safety precaution so // we don't end up with a placeholder pointing to frames that have already // been deleted as part of removing our next-in-flow. if (state.mReflowStatus.IsFullyComplete()) { nsBlockFrame* nif = static_cast(GetNextInFlow()); while (nif) { if (nif->HasPushedFloatsFromPrevContinuation()) { if (nif->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { state.mReflowStatus.SetOverflowIncomplete(); } else { state.mReflowStatus.SetIncomplete(); } break; } nif = static_cast(nif->GetNextInFlow()); } } state.mReflowStatus.MergeCompletionStatusFrom(ocStatus); // If we end in a BR with clear and affected floats continue, // we need to continue, too. if (NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize() && state.mReflowStatus.IsComplete() && state.FloatManager()->ClearContinues(FindTrailingClear())) { state.mReflowStatus.SetIncomplete(); } if (!state.mReflowStatus.IsFullyComplete()) { if (HasOverflowLines() || HasPushedFloats()) { state.mReflowStatus.SetNextInFlowNeedsReflow(); } #ifdef DEBUG_kipp ListTag(stdout); printf(": block is not fully complete\n"); #endif } // Place the ::marker's frame if it is placed next to a block child. // // According to the CSS2 spec, section 12.6.1, the ::marker's box // participates in the height calculation of the list-item box's // first line box. // // There are exactly two places a ::marker can be placed: near the // first or second line. It's only placed on the second line in a // rare case: an empty first line followed by a second line that // contains a block (example:
  • \n

    ... ). This is where // the second case can happen. if (HasOutsideMarker() && !mLines.empty() && (mLines.front()->IsBlock() || (0 == mLines.front()->BSize() && mLines.front() != mLines.back() && mLines.begin().next()->IsBlock()))) { // Reflow the ::marker's frame. ReflowOutput reflowOutput(aReflowInput); // XXX Use the entire line when we fix bug 25888. nsLayoutUtils::LinePosition position; WritingMode wm = aReflowInput.GetWritingMode(); bool havePosition = nsLayoutUtils::GetFirstLinePosition(wm, this, &position); nscoord lineBStart = havePosition ? position.mBStart : aReflowInput.ComputedLogicalBorderPadding(wm).BStart(wm); nsIFrame* marker = GetOutsideMarker(); ReflowOutsideMarker(marker, state, reflowOutput, lineBStart); NS_ASSERTION(!MarkerIsEmpty() || reflowOutput.BSize(wm) == 0, "empty ::marker frame took up space"); if (havePosition && !MarkerIsEmpty()) { // We have some lines to align the ::marker with. // Doing the alignment using the baseline will also cater for // ::markers that are placed next to a child block (bug 92896) // Tall ::markers won't look particularly nice here... LogicalRect bbox = marker->GetLogicalRect(wm, reflowOutput.PhysicalSize()); const auto baselineGroup = BaselineSharingGroup::First; Maybe result; if (MOZ_LIKELY(!wm.IsOrthogonalTo(marker->GetWritingMode()))) { result = marker->GetNaturalBaselineBOffset( wm, baselineGroup, BaselineExportContext::LineLayout); } const auto markerBaseline = result.valueOrFrom([bbox, wm, marker]() { return bbox.BSize(wm) + marker->GetLogicalUsedMargin(wm).BEnd(wm); }); bbox.BStart(wm) = position.mBaseline - markerBaseline; marker->SetRect(wm, bbox, reflowOutput.PhysicalSize()); } // Otherwise just leave the ::marker where it is, up against our // block-start padding. } // Clear any existing -webkit-line-clamp ellipsis. if (aReflowInput.mStyleDisplay->mWebkitLineClamp) { ClearLineClampEllipsis(); } CheckFloats(state); // Compute our final size (for this trial layout) aTrialState.mBlockEndEdgeOfChildren = ComputeFinalSize(aReflowInput, state, aMetrics); aTrialState.mContainerWidth = state.ContainerSize().width; // Align content AlignContent(state, aMetrics, aTrialState.mBlockEndEdgeOfChildren); return state.mReflowStatus; } bool nsBlockFrame::CheckForCollapsedBEndMarginFromClearanceLine() { for (auto& line : Reversed(Lines())) { if (0 != line.BSize() || !line.CachedIsEmpty()) { return false; } if (line.HasClearance()) { return true; } } return false; } static nsLineBox* FindLineClampTarget(nsBlockFrame*& aFrame, StyleLineClamp aLineNumber) { MOZ_ASSERT(aLineNumber > 0); MOZ_ASSERT(!aFrame->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS), "Should have been removed earlier in nsBlockReflow::Reflow"); nsLineBox* target = nullptr; nsBlockFrame* targetFrame = nullptr; bool foundFollowingLine = false; LineClampLineIterator iter(aFrame); while (nsLineBox* line = iter.GetCurrentLine()) { MOZ_ASSERT(!line->HasLineClampEllipsis(), "Should have been removed earlier in nsBlockFrame::Reflow"); MOZ_ASSERT(!iter.GetCurrentFrame()->HasAnyStateBits( NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS), "Should have been removed earlier in nsBlockReflow::Reflow"); // Don't count a line that only has collapsible white space (as might exist // after calling e.g. getBoxQuads). if (line->IsEmpty()) { iter.Next(); continue; } if (aLineNumber == 0) { // We already previously found our target line, and now we have // confirmed that there is another line after it. foundFollowingLine = true; break; } if (--aLineNumber == 0) { // This is our target line. Continue looping to confirm that we // have another line after us. target = line; targetFrame = iter.GetCurrentFrame(); } iter.Next(); } if (!foundFollowingLine) { aFrame = nullptr; return nullptr; } MOZ_ASSERT(target); MOZ_ASSERT(targetFrame); aFrame = targetFrame; return target; } static nscoord ApplyLineClamp(const ReflowInput& aReflowInput, nsBlockFrame* aFrame, nscoord aContentBlockEndEdge) { if (!IsLineClampRoot(aFrame)) { return aContentBlockEndEdge; } auto lineClamp = aReflowInput.mStyleDisplay->mWebkitLineClamp; nsBlockFrame* frame = aFrame; nsLineBox* line = FindLineClampTarget(frame, lineClamp); if (!line) { // The number of lines did not exceed the -webkit-line-clamp value. return aContentBlockEndEdge; } // Mark the line as having an ellipsis so that TextOverflow will render it. line->SetHasLineClampEllipsis(); frame->AddStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS); // Translate the b-end edge of the line up to aFrame's space. nscoord edge = line->BEnd(); for (nsIFrame* f = frame; f != aFrame; f = f->GetParent()) { edge += f->GetLogicalPosition(f->GetParent()->GetSize()).B(f->GetWritingMode()); } return edge; } nscoord nsBlockFrame::ComputeFinalSize(const ReflowInput& aReflowInput, BlockReflowState& aState, ReflowOutput& aMetrics) { WritingMode wm = aState.mReflowInput.GetWritingMode(); const LogicalMargin& borderPadding = aState.BorderPadding(); #ifdef NOISY_FINAL_SIZE ListTag(stdout); printf(": mBCoord=%d mIsBEndMarginRoot=%s mPrevBEndMargin=%d bp=%d,%d\n", aState.mBCoord, aState.mFlags.mIsBEndMarginRoot ? "yes" : "no", aState.mPrevBEndMargin.get(), borderPadding.BStart(wm), borderPadding.BEnd(wm)); #endif // Compute final inline size LogicalSize finalSize(wm); finalSize.ISize(wm) = NSCoordSaturatingAdd(NSCoordSaturatingAdd(borderPadding.IStart(wm), aReflowInput.ComputedISize()), borderPadding.IEnd(wm)); // Return block-end margin information // rbs says he hit this assertion occasionally (see bug 86947), so // just set the margin to zero and we'll figure out why later // NS_ASSERTION(aMetrics.mCarriedOutBEndMargin.IsZero(), // "someone else set the margin"); nscoord nonCarriedOutBDirMargin = 0; if (!aState.mFlags.mIsBEndMarginRoot) { // Apply rule from CSS 2.1 section 8.3.1. If we have some empty // line with clearance and a non-zero block-start margin and all // subsequent lines are empty, then we do not allow our children's // carried out block-end margin to be carried out of us and collapse // with our own block-end margin. if (CheckForCollapsedBEndMarginFromClearanceLine()) { // Convert the children's carried out margin to something that // we will include in our height nonCarriedOutBDirMargin = aState.mPrevBEndMargin.get(); aState.mPrevBEndMargin.Zero(); } aMetrics.mCarriedOutBEndMargin = aState.mPrevBEndMargin; } else { aMetrics.mCarriedOutBEndMargin.Zero(); } nscoord blockEndEdgeOfChildren = aState.mBCoord + nonCarriedOutBDirMargin; // Shrink wrap our height around our contents. if (aState.mFlags.mIsBEndMarginRoot || NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) { // When we are a block-end-margin root make sure that our last // child's block-end margin is fully applied. We also do this when // we have a computed height, since in that case the carried out // margin is not going to be applied anywhere, so we should note it // here to be included in the overflow area. // Apply the margin only if there's space for it. if (blockEndEdgeOfChildren < aState.mReflowInput.AvailableBSize()) { // Truncate block-end margin if it doesn't fit to our available BSize. blockEndEdgeOfChildren = std::min(blockEndEdgeOfChildren + aState.mPrevBEndMargin.get(), aState.mReflowInput.AvailableBSize()); } } if (aState.mFlags.mBlockNeedsFloatManager) { // Include the float manager's state to properly account for the // block-end margin of any floated elements; e.g., inside a table cell. // // Note: The block coordinate returned by ClearFloats is always greater than // or equal to blockEndEdgeOfChildren. std::tie(blockEndEdgeOfChildren, std::ignore) = aState.ClearFloats(blockEndEdgeOfChildren, StyleClear::Both); } // undo cached alignment shift for sizing purposes // (we used shifted positions because the float manager uses them) blockEndEdgeOfChildren -= aState.mAlignContentShift; aState.UndoAlignContentShift(); if (NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) { // Note: We don't use blockEndEdgeOfChildren because it includes the // previous margin. const nscoord contentBSizeWithBStartBP = aState.mBCoord + nonCarriedOutBDirMargin; // We don't care about ApplyLineClamp's return value (the line-clamped // content BSize) in this explicit-BSize codepath, but we do still need to // call ApplyLineClamp for ellipsis markers to be placed as-needed. ApplyLineClamp(aState.mReflowInput, this, contentBSizeWithBStartBP); finalSize.BSize(wm) = ComputeFinalBSize(aState, contentBSizeWithBStartBP); // If the content block-size is larger than the effective computed // block-size, we extend the block-size to contain all the content. // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum if (aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis()) { // Note: finalSize.BSize(wm) is the border-box size, so we compare it with // the content's block-size plus our border and padding.. finalSize.BSize(wm) = std::max(finalSize.BSize(wm), contentBSizeWithBStartBP + borderPadding.BEnd(wm)); } // Don't carry out a block-end margin when our BSize is fixed. // // Note: this also includes the case that aReflowInput.ComputedBSize() is // calculated from aspect-ratio. i.e. Don't carry out block margin-end if it // is replaced by the block size from aspect-ratio and inline size. aMetrics.mCarriedOutBEndMargin.Zero(); } else if (Maybe containBSize = ContainIntrinsicBSize()) { // If we're size-containing in block axis and we don't have a specified // block size, then our final size should actually be computed from only // our border, padding and contain-intrinsic-block-size, ignoring the // actual contents. Hence this case is a simplified version of the case // below. nscoord contentBSize = *containBSize; nscoord autoBSize = aReflowInput.ApplyMinMaxBSize(contentBSize, aState.mConsumedBSize); aMetrics.mCarriedOutBEndMargin.Zero(); autoBSize += borderPadding.BStartEnd(wm); finalSize.BSize(wm) = autoBSize; } else if (aState.mReflowStatus.IsInlineBreakBefore()) { // Our parent is expected to push this frame to the next page/column so // what size we set here doesn't really matter. finalSize.BSize(wm) = aReflowInput.AvailableBSize(); } else if (aState.mReflowStatus.IsComplete()) { const nscoord lineClampedContentBlockEndEdge = ApplyLineClamp(aReflowInput, this, blockEndEdgeOfChildren); const nscoord bpBStart = borderPadding.BStart(wm); const nscoord contentBSize = blockEndEdgeOfChildren - bpBStart; const nscoord lineClampedContentBSize = lineClampedContentBlockEndEdge - bpBStart; const nscoord autoBSize = aReflowInput.ApplyMinMaxBSize( lineClampedContentBSize, aState.mConsumedBSize); if (autoBSize != contentBSize) { // Our min-block-size, max-block-size, or -webkit-line-clamp value made // our bsize change. Don't carry out our kids' block-end margins. aMetrics.mCarriedOutBEndMargin.Zero(); } nscoord bSize = autoBSize + borderPadding.BStartEnd(wm); if (MOZ_UNLIKELY(autoBSize > contentBSize && bSize > aReflowInput.AvailableBSize() && aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE)) { // Applying `min-size` made us overflow our available size. // Clamp it and report that we're Incomplete, or BreakBefore if we have // 'break-inside: avoid' that is applicable. bSize = aReflowInput.AvailableBSize(); if (ShouldAvoidBreakInside(aReflowInput)) { aState.mReflowStatus.SetInlineLineBreakBeforeAndReset(); } else { aState.mReflowStatus.SetIncomplete(); } } finalSize.BSize(wm) = bSize; } else { NS_ASSERTION(aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE, "Shouldn't be incomplete if availableBSize is UNCONSTRAINED."); nscoord bSize = std::max(aState.mBCoord, aReflowInput.AvailableBSize()); if (aReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) { // This should never happen, but it does. See bug 414255 bSize = aState.mBCoord; } const nscoord maxBSize = aReflowInput.ComputedMaxBSize(); if (maxBSize != NS_UNCONSTRAINEDSIZE && aState.mConsumedBSize + bSize - borderPadding.BStart(wm) > maxBSize) { // Compute this fragment's block-size, with the max-block-size // constraint taken into consideration. const nscoord clampedBSizeWithoutEndBP = std::max(0, maxBSize - aState.mConsumedBSize) + borderPadding.BStart(wm); const nscoord clampedBSize = clampedBSizeWithoutEndBP + borderPadding.BEnd(wm); if (clampedBSize <= aReflowInput.AvailableBSize()) { // We actually fit after applying `max-size` so we should be // Overflow-Incomplete instead. bSize = clampedBSize; aState.mReflowStatus.SetOverflowIncomplete(); } else { // We cannot fit after applying `max-size` with our block-end BP, so // we should draw it in our next continuation. bSize = clampedBSizeWithoutEndBP; } } finalSize.BSize(wm) = bSize; } if (IsTrueOverflowContainer()) { if (aState.mReflowStatus.IsIncomplete()) { // Overflow containers can only be overflow complete. // Note that auto height overflow containers have no normal children NS_ASSERTION(finalSize.BSize(wm) == 0, "overflow containers must be zero-block-size"); aState.mReflowStatus.SetOverflowIncomplete(); } } else if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE && !aState.mReflowStatus.IsInlineBreakBefore() && aState.mReflowStatus.IsComplete()) { // Currently only used for grid items, but could be used in other contexts. // The FragStretchBSizeProperty is our expected non-fragmented block-size // we should stretch to (for align-self:stretch etc). In some fragmentation // cases though, the last fragment (this frame since we're complete), needs // to have extra size applied because earlier fragments consumed too much of // our computed size due to overflowing their containing block. (E.g. this // ensures we fill the last row when a multi-row grid item is fragmented). bool found; nscoord bSize = GetProperty(FragStretchBSizeProperty(), &found); if (found) { finalSize.BSize(wm) = std::max(bSize, finalSize.BSize(wm)); } } // Clamp the content size to fit within the margin-box clamp size, if any. if (MOZ_UNLIKELY(aReflowInput.mComputeSizeFlags.contains( ComputeSizeFlag::BClampMarginBoxMinSize)) && aState.mReflowStatus.IsComplete()) { bool found; nscoord cbSize = GetProperty(BClampMarginBoxMinSizeProperty(), &found); if (found) { auto marginBoxBSize = finalSize.BSize(wm) + aReflowInput.ComputedLogicalMargin(wm).BStartEnd(wm); auto overflow = marginBoxBSize - cbSize; if (overflow > 0) { auto contentBSize = finalSize.BSize(wm) - borderPadding.BStartEnd(wm); auto newContentBSize = std::max(nscoord(0), contentBSize - overflow); // XXXmats deal with percentages better somehow? finalSize.BSize(wm) -= contentBSize - newContentBSize; } } } // Screen out negative block sizes --- can happen due to integer overflows :-( finalSize.BSize(wm) = std::max(0, finalSize.BSize(wm)); if (blockEndEdgeOfChildren != finalSize.BSize(wm) - borderPadding.BEnd(wm)) { SetProperty(BlockEndEdgeOfChildrenProperty(), blockEndEdgeOfChildren); } else { RemoveProperty(BlockEndEdgeOfChildrenProperty()); } aMetrics.SetSize(wm, finalSize); #ifdef DEBUG_blocks if ((ABSURD_SIZE(aMetrics.Width()) || ABSURD_SIZE(aMetrics.Height())) && !GetParent()->IsAbsurdSizeAssertSuppressed()) { ListTag(stdout); printf(": WARNING: desired:%d,%d\n", aMetrics.Width(), aMetrics.Height()); } #endif return blockEndEdgeOfChildren; } void nsBlockFrame::AlignContent(BlockReflowState& aState, ReflowOutput& aMetrics, nscoord aBEndEdgeOfChildren) { if (!StaticPrefs::layout_css_align_content_blocks_enabled()) { return; } StyleAlignFlags alignment = StylePosition()->mAlignContent.primary; alignment &= ~StyleAlignFlags::FLAG_BITS; // Short circuit const bool isCentered = alignment == StyleAlignFlags::CENTER || alignment == StyleAlignFlags::SPACE_AROUND || alignment == StyleAlignFlags::SPACE_EVENLY; const bool isEndAlign = alignment == StyleAlignFlags::END || alignment == StyleAlignFlags::FLEX_END || alignment == StyleAlignFlags::LAST_BASELINE; if (!isEndAlign && !isCentered && !aState.mAlignContentShift) { // desired shift = 0, no cached shift to undo return; } // NOTE: ComputeFinalSize already called aState.UndoAlignContentShift(), // so metrics no longer include cached shift. // NOTE: Content is currently positioned at cached shift // NOTE: Content has been fragmented against 0-shift assumption. // Calculate shift nscoord shift = 0; WritingMode wm = aState.mReflowInput.GetWritingMode(); if ((isCentered || isEndAlign) && !mLines.empty() && aState.mReflowStatus.IsFullyComplete() && !GetPrevInFlow()) { nscoord availB = aState.mReflowInput.AvailableBSize(); nscoord endB = aMetrics.BSize(wm) - aState.BorderPadding().BEnd(wm); shift = std::min(availB, endB) - aBEndEdgeOfChildren; // note: these measures all include start BP, so it subtracts out if (!(StylePosition()->mAlignContent.primary & StyleAlignFlags::UNSAFE)) { shift = std::max(0, shift); } if (isCentered) { shift = shift / 2; } } // else: zero shift if start-aligned or if fragmented nscoord delta = shift - aState.mAlignContentShift; if (delta) { // Shift children LogicalPoint translation(wm, 0, delta); for (nsLineBox& line : Lines()) { SlideLine(aState, &line, delta); } for (nsIFrame* kid : GetChildList(FrameChildListID::Float)) { kid->MovePositionBy(wm, translation); nsContainerFrame::PlaceFrameView(kid); } if (HasOutsideMarker() && !mLines.empty()) { nsIFrame* marker = GetOutsideMarker(); marker->MovePositionBy(wm, translation); } } if (shift) { // Cache shift SetProperty(AlignContentShift(), shift); } else { RemoveProperty(AlignContentShift()); } } void nsBlockFrame::ConsiderBlockEndEdgeOfChildren( OverflowAreas& aOverflowAreas, nscoord aBEndEdgeOfChildren, const nsStyleDisplay* aDisplay) const { const auto wm = GetWritingMode(); // Factor in the block-end edge of the children. Child frames will be added // to the overflow area as we iterate through the lines, but their margins // won't, so we need to account for block-end margins here. // REVIEW: For now, we do this for both visual and scrollable area, // although when we make scrollable overflow area not be a subset of // visual, we can change this. if (Style()->GetPseudoType() == PseudoStyleType::scrolledContent) { // If we are a scrolled inner frame, add our block-end padding to our // children's block-end edge. // // Note: aBEndEdgeOfChildren already includes our own block-start padding // because it is relative to our block-start edge of our border-box, which // is the same as our padding-box here. MOZ_ASSERT(GetLogicalUsedBorderAndPadding(wm) == GetLogicalUsedPadding(wm), "A scrolled inner frame shouldn't have any border!"); aBEndEdgeOfChildren += GetLogicalUsedPadding(wm).BEnd(wm); } // XXX Currently, overflow areas are stored as physical rects, so we have // to handle writing modes explicitly here. If we change overflow rects // to be stored logically, this can be simplified again. if (wm.IsVertical()) { if (wm.IsVerticalLR()) { for (const auto otype : AllOverflowTypes()) { if (!(aDisplay->IsContainLayout() && otype == OverflowType::Scrollable)) { // Layout containment should force all overflow to be ink (visual) // overflow, so if we're layout-contained, we only add our children's // block-end edge to the ink (visual) overflow -- not to the // scrollable overflow. nsRect& o = aOverflowAreas.Overflow(otype); o.width = std::max(o.XMost(), aBEndEdgeOfChildren) - o.x; } } } else { for (const auto otype : AllOverflowTypes()) { if (!(aDisplay->IsContainLayout() && otype == OverflowType::Scrollable)) { nsRect& o = aOverflowAreas.Overflow(otype); nscoord xmost = o.XMost(); o.x = std::min(o.x, xmost - aBEndEdgeOfChildren); o.width = xmost - o.x; } } } } else { for (const auto otype : AllOverflowTypes()) { if (!(aDisplay->IsContainLayout() && otype == OverflowType::Scrollable)) { nsRect& o = aOverflowAreas.Overflow(otype); o.height = std::max(o.YMost(), aBEndEdgeOfChildren) - o.y; } } } } void nsBlockFrame::ComputeOverflowAreas(OverflowAreas& aOverflowAreas, nscoord aBEndEdgeOfChildren, const nsStyleDisplay* aDisplay) const { // XXX_perf: This can be done incrementally. It is currently one of // the things that makes incremental reflow O(N^2). auto overflowClipAxes = ShouldApplyOverflowClipping(aDisplay); auto overflowClipMargin = OverflowClipMargin(overflowClipAxes); if (overflowClipAxes == PhysicalAxes::Both && overflowClipMargin == nsSize()) { return; } // We rely here on our caller having called SetOverflowAreasToDesiredBounds(). nsRect frameBounds = aOverflowAreas.ScrollableOverflow(); for (const auto& line : Lines()) { if (aDisplay->IsContainLayout()) { // If we have layout containment, we should only consider our child's // ink overflow, leaving the scrollable regions of the parent // unaffected. // Note: scrollable overflow is a subset of ink overflow, // so this has the same affect as unioning the child's visual and // scrollable overflow with its parent's ink overflow. nsRect childVisualRect = line.InkOverflowRect(); OverflowAreas childVisualArea = OverflowAreas(childVisualRect, nsRect()); aOverflowAreas.UnionWith(childVisualArea); } else { aOverflowAreas.UnionWith(line.GetOverflowAreas()); } } // Factor an outside ::marker in; normally the ::marker will be factored // into the line-box's overflow areas. However, if the line is a block // line then it won't; if there are no lines, it won't. So just // factor it in anyway (it can't hurt if it was already done). // XXXldb Can we just fix GetOverflowArea instead? if (nsIFrame* outsideMarker = GetOutsideMarker()) { aOverflowAreas.UnionAllWith(outsideMarker->GetRect()); } ConsiderBlockEndEdgeOfChildren(aOverflowAreas, aBEndEdgeOfChildren, aDisplay); if (overflowClipAxes != PhysicalAxes::None) { aOverflowAreas.ApplyClipping(frameBounds, overflowClipAxes, overflowClipMargin); } #ifdef NOISY_OVERFLOW_AREAS printf("%s: InkOverflowArea=%s, ScrollableOverflowArea=%s\n", ListTag().get(), ToString(aOverflowAreas.InkOverflow()).c_str(), ToString(aOverflowAreas.ScrollableOverflow()).c_str()); #endif } void nsBlockFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas) { // We need to update the overflow areas of lines manually, as they // get cached and re-used otherwise. Lines aren't exposed as normal // frame children, so calling UnionChildOverflow alone will end up // using the old cached values. for (auto& line : Lines()) { nsRect bounds = line.GetPhysicalBounds(); OverflowAreas lineAreas(bounds, bounds); int32_t n = line.GetChildCount(); for (nsIFrame* lineFrame = line.mFirstChild; n > 0; lineFrame = lineFrame->GetNextSibling(), --n) { ConsiderChildOverflow(lineAreas, lineFrame); } // Consider the overflow areas of the floats attached to the line as well if (line.HasFloats()) { for (nsIFrame* f : line.Floats()) { ConsiderChildOverflow(lineAreas, f); } } line.SetOverflowAreas(lineAreas); aOverflowAreas.UnionWith(lineAreas); } // Union with child frames, skipping the principal and float lists // since we already handled those using the line boxes. nsLayoutUtils::UnionChildOverflow( this, aOverflowAreas, {FrameChildListID::Principal, FrameChildListID::Float}); } bool nsBlockFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) { bool found; nscoord blockEndEdgeOfChildren = GetProperty(BlockEndEdgeOfChildrenProperty(), &found); if (found) { ConsiderBlockEndEdgeOfChildren(aOverflowAreas, blockEndEdgeOfChildren, StyleDisplay()); } // Line cursor invariants depend on the overflow areas of the lines, so // we must clear the line cursor since those areas may have changed. ClearLineCursors(); return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas); } void nsBlockFrame::LazyMarkLinesDirty() { if (HasAnyStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES)) { for (LineIterator line = LinesBegin(), line_end = LinesEnd(); line != line_end; ++line) { int32_t n = line->GetChildCount(); for (nsIFrame* lineFrame = line->mFirstChild; n > 0; lineFrame = lineFrame->GetNextSibling(), --n) { if (lineFrame->IsSubtreeDirty()) { // NOTE: MarkLineDirty does more than just marking the line dirty. MarkLineDirty(line, &mLines); break; } } } RemoveStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES); } } void nsBlockFrame::MarkLineDirty(LineIterator aLine, const nsLineList* aLineList) { // Mark aLine dirty aLine->MarkDirty(); aLine->SetInvalidateTextRuns(true); #ifdef DEBUG if (gNoisyReflow) { IndentBy(stdout, gNoiseIndent); ListTag(stdout); printf(": mark line %p dirty\n", static_cast(aLine.get())); } #endif // Mark previous line dirty if it's an inline line so that it can // maybe pullup something from the line just affected. // XXX We don't need to do this if aPrevLine ends in a break-after... if (aLine != aLineList->front() && aLine->IsInline() && aLine.prev()->IsInline()) { aLine.prev()->MarkDirty(); aLine.prev()->SetInvalidateTextRuns(true); #ifdef DEBUG if (gNoisyReflow) { IndentBy(stdout, gNoiseIndent); ListTag(stdout); printf(": mark prev-line %p dirty\n", static_cast(aLine.prev().get())); } #endif } } /** * Test whether lines are certain to be aligned left so that we can make * resizing optimizations */ static inline bool IsAlignedLeft(StyleTextAlign aAlignment, StyleDirection aDirection, StyleUnicodeBidi aUnicodeBidi, nsIFrame* aFrame) { return aFrame->IsInSVGTextSubtree() || StyleTextAlign::Left == aAlignment || (((StyleTextAlign::Start == aAlignment && StyleDirection::Ltr == aDirection) || (StyleTextAlign::End == aAlignment && StyleDirection::Rtl == aDirection)) && aUnicodeBidi != StyleUnicodeBidi::Plaintext); } void nsBlockFrame::PrepareResizeReflow(BlockReflowState& aState) { // See if we can try and avoid marking all the lines as dirty // FIXME(emilio): This should be writing-mode aware, I guess. bool tryAndSkipLines = // The left content-edge must be a constant distance from the left // border-edge. !StylePadding()->mPadding.Get(eSideLeft).HasPercent(); #ifdef DEBUG if (gDisableResizeOpt) { tryAndSkipLines = false; } if (gNoisyReflow) { if (!tryAndSkipLines) { IndentBy(stdout, gNoiseIndent); ListTag(stdout); printf(": marking all lines dirty: availISize=%d\n", aState.mReflowInput.AvailableISize()); } } #endif if (tryAndSkipLines) { WritingMode wm = aState.mReflowInput.GetWritingMode(); nscoord newAvailISize = aState.mReflowInput.ComputedLogicalBorderPadding(wm).IStart(wm) + aState.mReflowInput.ComputedISize(); #ifdef DEBUG if (gNoisyReflow) { IndentBy(stdout, gNoiseIndent); ListTag(stdout); printf(": trying to avoid marking all lines dirty\n"); } #endif for (LineIterator line = LinesBegin(), line_end = LinesEnd(); line != line_end; ++line) { // We let child blocks make their own decisions the same // way we are here. bool isLastLine = line == mLines.back() && !GetNextInFlow(); if (line->IsBlock() || line->HasFloats() || (!isLastLine && !line->HasForcedLineBreakAfter()) || ((isLastLine || !line->IsLineWrapped())) || line->ResizeReflowOptimizationDisabled() || line->IsImpactedByFloat() || (line->IEnd() > newAvailISize)) { line->MarkDirty(); } #ifdef REALLY_NOISY_REFLOW if (!line->IsBlock()) { printf("PrepareResizeReflow thinks line %p is %simpacted by floats\n", line.get(), line->IsImpactedByFloat() ? "" : "not "); } #endif #ifdef DEBUG if (gNoisyReflow && !line->IsDirty()) { IndentBy(stdout, gNoiseIndent + 1); printf( "skipped: line=%p next=%p %s %s%s%s clearTypeBefore/After=%s/%s " "xmost=%d\n", static_cast(line.get()), static_cast( (line.next() != LinesEnd() ? line.next().get() : nullptr)), line->IsBlock() ? "block" : "inline", line->HasForcedLineBreakAfter() ? "has-break-after " : "", line->HasFloats() ? "has-floats " : "", line->IsImpactedByFloat() ? "impacted " : "", line->StyleClearToString(line->FloatClearTypeBefore()), line->StyleClearToString(line->FloatClearTypeAfter()), line->IEnd()); } #endif } } else { // Mark everything dirty for (auto& line : Lines()) { line.MarkDirty(); } } } //---------------------------------------- /** * Propagate reflow "damage" from from earlier lines to the current * line. The reflow damage comes from the following sources: * 1. The regions of float damage remembered during reflow. * 2. The combination of nonzero |aDeltaBCoord| and any impact by a * float, either the previous reflow or now. * * When entering this function, |aLine| is still at its old position and * |aDeltaBCoord| indicates how much it will later be slid (assuming it * doesn't get marked dirty and reflowed entirely). */ void nsBlockFrame::PropagateFloatDamage(BlockReflowState& aState, nsLineBox* aLine, nscoord aDeltaBCoord) { nsFloatManager* floatManager = aState.FloatManager(); NS_ASSERTION( (aState.mReflowInput.mParentReflowInput && aState.mReflowInput.mParentReflowInput->mFloatManager == floatManager) || aState.mReflowInput.mBlockDelta == 0, "Bad block delta passed in"); // Check to see if there are any floats; if there aren't, there can't // be any float damage if (!floatManager->HasAnyFloats()) { return; } // Check the damage region recorded in the float damage. if (floatManager->HasFloatDamage()) { // Need to check mBounds *and* mCombinedArea to find intersections // with aLine's floats nscoord lineBCoordBefore = aLine->BStart() + aDeltaBCoord; nscoord lineBCoordAfter = lineBCoordBefore + aLine->BSize(); // Scrollable overflow should be sufficient for things that affect // layout. WritingMode wm = aState.mReflowInput.GetWritingMode(); nsSize containerSize = aState.ContainerSize(); LogicalRect overflow = aLine->GetOverflowArea(OverflowType::Scrollable, wm, containerSize); nscoord lineBCoordCombinedBefore = overflow.BStart(wm) + aDeltaBCoord; nscoord lineBCoordCombinedAfter = lineBCoordCombinedBefore + overflow.BSize(wm); bool isDirty = floatManager->IntersectsDamage(lineBCoordBefore, lineBCoordAfter) || floatManager->IntersectsDamage(lineBCoordCombinedBefore, lineBCoordCombinedAfter); if (isDirty) { aLine->MarkDirty(); return; } } // Check if the line is moving relative to the float manager if (aDeltaBCoord + aState.mReflowInput.mBlockDelta != 0) { if (aLine->IsBlock()) { // Unconditionally reflow sliding blocks; we only really need to reflow // if there's a float impacting this block, but the current float manager // makes it difficult to check that. Therefore, we let the child block // decide what it needs to reflow. aLine->MarkDirty(); } else { bool wasImpactedByFloat = aLine->IsImpactedByFloat(); nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpaceForBSize(aLine->BStart() + aDeltaBCoord, aLine->BSize(), nullptr); #ifdef REALLY_NOISY_REFLOW printf("nsBlockFrame::PropagateFloatDamage %p was = %d, is=%d\n", this, wasImpactedByFloat, floatAvailableSpace.HasFloats()); #endif // Mark the line dirty if it was or is affected by a float // We actually only really need to reflow if the amount of impact // changes, but that's not straightforward to check if (wasImpactedByFloat || floatAvailableSpace.HasFloats()) { aLine->MarkDirty(); } } } } static bool LineHasClear(nsLineBox* aLine) { return aLine->IsBlock() ? (aLine->HasForcedLineBreakBefore() || aLine->mFirstChild->HasAnyStateBits( NS_BLOCK_HAS_CLEAR_CHILDREN) || !nsBlockFrame::BlockCanIntersectFloats(aLine->mFirstChild)) : aLine->HasFloatClearTypeAfter(); } /** * Reparent a whole list of floats from aOldParent to this block. The * floats might be taken from aOldParent's overflow list. They will be * removed from the list. They end up appended to our mFloats list. */ void nsBlockFrame::ReparentFloats(nsIFrame* aFirstFrame, nsBlockFrame* aOldParent, bool aReparentSiblings) { nsFrameList list; aOldParent->CollectFloats(aFirstFrame, list, aReparentSiblings); if (list.NotEmpty()) { for (nsIFrame* f : list) { MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT), "CollectFloats should've removed that bit"); ReparentFrame(f, aOldParent, this); } mFloats.AppendFrames(nullptr, std::move(list)); } } static void DumpLine(const BlockReflowState& aState, nsLineBox* aLine, nscoord aDeltaBCoord, int32_t aDeltaIndent) { #ifdef DEBUG if (nsBlockFrame::gNoisyReflow) { nsRect ovis(aLine->InkOverflowRect()); nsRect oscr(aLine->ScrollableOverflowRect()); nsBlockFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent + aDeltaIndent); printf( "line=%p mBCoord=%d dirty=%s oldBounds={%d,%d,%d,%d} " "oldoverflow-vis={%d,%d,%d,%d} oldoverflow-scr={%d,%d,%d,%d} " "deltaBCoord=%d mPrevBEndMargin=%d childCount=%d\n", static_cast(aLine), aState.mBCoord, aLine->IsDirty() ? "yes" : "no", aLine->IStart(), aLine->BStart(), aLine->ISize(), aLine->BSize(), ovis.x, ovis.y, ovis.width, ovis.height, oscr.x, oscr.y, oscr.width, oscr.height, aDeltaBCoord, aState.mPrevBEndMargin.get(), aLine->GetChildCount()); } #endif } static bool LinesAreEmpty(const nsLineList& aList) { for (const auto& line : aList) { if (!line.IsEmpty()) { return false; } } return true; } bool nsBlockFrame::ReflowDirtyLines(BlockReflowState& aState) { bool keepGoing = true; bool repositionViews = false; // should we really need this? bool foundAnyClears = aState.mTrailingClearFromPIF != StyleClear::None; bool willReflowAgain = false; bool usedOverflowWrap = false; #ifdef DEBUG if (gNoisyReflow) { IndentBy(stdout, gNoiseIndent); ListTag(stdout); printf(": reflowing dirty lines"); printf(" computedISize=%d\n", aState.mReflowInput.ComputedISize()); } AutoNoisyIndenter indent(gNoisyReflow); #endif bool selfDirty = HasAnyStateBits(NS_FRAME_IS_DIRTY) || (aState.mReflowInput.IsBResize() && HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)); // Reflow our last line if our availableBSize has increased // so that we (and our last child) pull up content as necessary if (aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE && GetNextInFlow() && aState.mReflowInput.AvailableBSize() > GetLogicalSize().BSize(aState.mReflowInput.GetWritingMode())) { LineIterator lastLine = LinesEnd(); if (lastLine != LinesBegin()) { --lastLine; lastLine->MarkDirty(); } } // the amount by which we will slide the current line if it is not // dirty nscoord deltaBCoord = 0; // whether we did NOT reflow the previous line and thus we need to // recompute the carried out margin before the line if we want to // reflow it or if its previous margin is dirty bool needToRecoverState = false; // Float continuations were reflowed in ReflowPushedFloats bool reflowedFloat = mFloats.NotEmpty() && mFloats.FirstChild()->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT); bool lastLineMovedUp = false; // We save up information about BR-clearance here StyleClear inlineFloatClearType = aState.mTrailingClearFromPIF; LineIterator line = LinesBegin(), line_end = LinesEnd(); // Determine if children of this frame could have breaks between them for // page names. // // We need to check for paginated layout, the named-page pref, and if the // available block-size is constrained. // // Note that we need to check for paginated layout as named-pages are only // used during paginated reflow. We need to additionally check for // unconstrained block-size to avoid introducing fragmentation breaks during // "measuring" reflows within an overall paginated reflow, and to avoid // fragmentation in monolithic containers like 'inline-block'. // // Because we can only break for named pages using Class A breakpoints, we // also need to check that the block flow direction of the containing frame // of these items (which is this block) is parallel to that of this page. // See: https://www.w3.org/TR/css-break-3/#btw-blocks const nsPresContext* const presCtx = aState.mPresContext; const bool canBreakForPageNames = aState.mReflowInput.mFlags.mCanHaveClassABreakpoints && aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE && presCtx->GetPresShell()->GetRootFrame()->GetWritingMode().IsVertical() == GetWritingMode().IsVertical(); // ReflowInput.mFlags.mCanHaveClassABreakpoints should respect the named // pages pref and presCtx->IsPaginated, so we did not explicitly check these // above when setting canBreakForPageNames. if (canBreakForPageNames) { MOZ_ASSERT(presCtx->IsPaginated(), "canBreakForPageNames should not be set during non-paginated " "reflow"); } // Reflow the lines that are already ours for (; line != line_end; ++line, aState.AdvanceToNextLine()) { DumpLine(aState, line, deltaBCoord, 0); #ifdef DEBUG AutoNoisyIndenter indent2(gNoisyReflow); #endif if (selfDirty) { line->MarkDirty(); } // This really sucks, but we have to look inside any blocks that have clear // elements inside them. // XXX what can we do smarter here? if (!line->IsDirty() && line->IsBlock() && line->mFirstChild->HasAnyStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN)) { line->MarkDirty(); } nsIFrame* floatAvoidingBlock = nullptr; if (line->IsBlock() && !nsBlockFrame::BlockCanIntersectFloats(line->mFirstChild)) { floatAvoidingBlock = line->mFirstChild; } // We have to reflow the line if it's a block whose clearance // might have changed, so detect that. if (!line->IsDirty() && (line->HasForcedLineBreakBefore() || floatAvoidingBlock)) { nscoord curBCoord = aState.mBCoord; // See where we would be after applying any clearance due to // BRs. if (inlineFloatClearType != StyleClear::None) { std::tie(curBCoord, std::ignore) = aState.ClearFloats(curBCoord, inlineFloatClearType); } auto [newBCoord, result] = aState.ClearFloats( curBCoord, line->FloatClearTypeBefore(), floatAvoidingBlock); if (line->HasClearance()) { // Reflow the line if it might not have clearance anymore. if (result == ClearFloatsResult::BCoordNoChange // aState.mBCoord is the clearance point which should be the // block-start border-edge of the block frame. If sliding the // block by deltaBCoord isn't going to put it in the predicted // position, then we'd better reflow the line. || newBCoord != line->BStart() + deltaBCoord) { line->MarkDirty(); } } else { // Reflow the line if the line might have clearance now. if (result != ClearFloatsResult::BCoordNoChange) { line->MarkDirty(); } } } // We might have to reflow a line that is after a clearing BR. if (inlineFloatClearType != StyleClear::None) { std::tie(aState.mBCoord, std::ignore) = aState.ClearFloats(aState.mBCoord, inlineFloatClearType); if (aState.mBCoord != line->BStart() + deltaBCoord) { // SlideLine is not going to put the line where the clearance // put it. Reflow the line to be sure. line->MarkDirty(); } inlineFloatClearType = StyleClear::None; } bool previousMarginWasDirty = line->IsPreviousMarginDirty(); if (previousMarginWasDirty) { // If the previous margin is dirty, reflow the current line line->MarkDirty(); line->ClearPreviousMarginDirty(); } else if (aState.ContentBSize() != NS_UNCONSTRAINEDSIZE) { const nscoord scrollableOverflowBEnd = LogicalRect(line->mWritingMode, line->ScrollableOverflowRect(), line->mContainerSize) .BEnd(line->mWritingMode); if (scrollableOverflowBEnd + deltaBCoord > aState.ContentBEnd()) { // Lines that aren't dirty but get slid past our available block-size // constraint must be reflowed. line->MarkDirty(); } } if (!line->IsDirty()) { const bool isPaginated = // Last column can be reflowed unconstrained during column balancing. // Hence the additional NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR bit check // as a fail-safe fallback. aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE || HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) || // Table can also be reflowed unconstrained during printing. aState.mPresContext->IsPaginated(); if (isPaginated) { // We are in a paginated context, i.e. in columns or pages. const bool mayContainFloats = line->IsBlock() || line->HasFloats() || line->HadFloatPushed(); if (mayContainFloats) { // The following if-else conditions check whether this line -- which // might have floats in its subtree, or has floats as direct children, // or had floats pushed -- needs to be reflowed. if (deltaBCoord != 0 || aState.mReflowInput.IsBResize()) { // The distance to the block-end edge might have changed. Reflow the // line both because the breakpoints within its floats may have // changed and because we might have to push/pull the floats in // their entirety. line->MarkDirty(); } else if (HasPushedFloats()) { // We had pushed floats which haven't been drained by our // next-in-flow, which means our parent is currently reflowing us // again due to clearance without creating a next-in-flow for us. // Reflow the line to redo the floats split logic to correctly set // our reflow status. line->MarkDirty(); } else if (aState.mReflowInput.mFlags.mMustReflowPlaceholders) { // Reflow the line (that may containing a float's placeholder frame) // if our parent tells us to do so. line->MarkDirty(); } else if (aState.mReflowInput.mFlags.mMovedBlockFragments) { // Our parent's line containing us moved to a different fragment. // Reflow the line because the decision about whether the float fits // may be different in a different fragment. line->MarkDirty(); } } } } if (!line->IsDirty()) { // See if there's any reflow damage that requires that we mark the // line dirty. PropagateFloatDamage(aState, line, deltaBCoord); } // If the container size has changed, reset mContainerSize. If the // line's writing mode is not ltr, or if the line is not left-aligned, also // mark the line dirty. if (aState.ContainerSize() != line->mContainerSize) { line->mContainerSize = aState.ContainerSize(); const bool isLastLine = line == mLines.back() && !GetNextInFlow(); const auto align = isLastLine ? StyleText()->TextAlignForLastLine() : StyleText()->mTextAlign; if (line->mWritingMode.IsVertical() || line->mWritingMode.IsBidiRTL() || !IsAlignedLeft(align, StyleVisibility()->mDirection, StyleTextReset()->mUnicodeBidi, this)) { line->MarkDirty(); } } // Check for a page break caused by CSS named pages. // // We should break for named pages when two frames meet at a class A // breakpoint, where the first frame has a different end page value to the // second frame's start page value. canBreakForPageNames is true iff // children of this frame can form class A breakpoints, and that we are not // in a measurement reflow or in a monolithic container such as // 'inline-block'. // // We specifically do not want to cause a page-break for named pages when // we are at the top of a page. This would otherwise happen when the // previous sibling is an nsPageBreakFrame, or all previous siblings on the // current page are zero-height. The latter may not be per-spec, but is // compatible with Chrome's implementation of named pages. const nsAtom* nextPageName = nullptr; bool shouldBreakForPageName = false; if (canBreakForPageNames && (!aState.mReflowInput.mFlags.mIsTopOfPage || !aState.IsAdjacentWithBStart())) { const nsIFrame* const frame = line->mFirstChild; if (!frame->IsPlaceholderFrame() && !frame->IsPageBreakFrame()) { nextPageName = frame->GetStartPageValue(); // Walk back to the last frame that isn't a placeholder. const nsIFrame* prevFrame = frame->GetPrevSibling(); while (prevFrame && prevFrame->IsPlaceholderFrame()) { prevFrame = prevFrame->GetPrevSibling(); } if (prevFrame && prevFrame->GetEndPageValue() != nextPageName) { shouldBreakForPageName = true; line->MarkDirty(); } } } if (needToRecoverState && line->IsDirty()) { // We need to reconstruct the block-end margin only if we didn't // reflow the previous line and we do need to reflow (or repair // the block-start position of) the next line. aState.ReconstructMarginBefore(line); } bool reflowedPrevLine = !needToRecoverState; if (needToRecoverState) { needToRecoverState = false; // Update aState.mPrevChild as if we had reflowed all of the frames in // this line. if (line->IsDirty()) { NS_ASSERTION( line->mFirstChild->GetPrevSibling() == line.prev()->LastChild(), "unexpected line frames"); aState.mPrevChild = line->mFirstChild->GetPrevSibling(); } } // Now repair the line and update |aState.mBCoord| by calling // |ReflowLine| or |SlideLine|. // If we're going to reflow everything again, then no need to reflow // the dirty line ... unless the line has floats, in which case we'd // better reflow it now to refresh its float cache, which may contain // dangling frame pointers! Ugh! This reflow of the line may be // incorrect because we skipped reflowing previous lines (e.g., floats // may be placed incorrectly), but that's OK because we'll mark the // line dirty below under "if (aState.mReflowInput.mDiscoveredClearance..." if (line->IsDirty() && (line->HasFloats() || !willReflowAgain)) { lastLineMovedUp = true; bool maybeReflowingForFirstTime = line->IStart() == 0 && line->BStart() == 0 && line->ISize() == 0 && line->BSize() == 0; // Compute the dirty lines "before" BEnd, after factoring in // the running deltaBCoord value - the running value is implicit in // aState.mBCoord. nscoord oldB = line->BStart(); nscoord oldBMost = line->BEnd(); NS_ASSERTION(!willReflowAgain || !line->IsBlock(), "Don't reflow blocks while willReflowAgain is true, reflow " "of block abs-pos children depends on this"); if (shouldBreakForPageName) { // Immediately fragment for page-name. It is possible we could break // out of the loop right here, but this should make it more similar to // what happens when reflow causes fragmentation. // Set the page name, so that PushTruncatedLine does not need to // recalculate the new page name. PresShell()->FrameConstructor()->SetNextPageContentFramePageName( nextPageName ? nextPageName : GetAutoPageValue()); PushTruncatedLine(aState, line, &keepGoing, ComputeNewPageNameIfNeeded::No); } else { // Reflow the dirty line. If it's an incremental reflow, then force // it to invalidate the dirty area if necessary usedOverflowWrap |= ReflowLine(aState, line, &keepGoing); } if (aState.mReflowInput.WillReflowAgainForClearance()) { line->MarkDirty(); willReflowAgain = true; // Note that once we've entered this state, every line that gets here // (e.g. because it has floats) gets marked dirty and reflowed again. // in the next pass. This is important, see above. } if (line->HasFloats()) { reflowedFloat = true; } if (!keepGoing) { DumpLine(aState, line, deltaBCoord, -1); if (0 == line->GetChildCount()) { DeleteLine(aState, line, line_end); } break; } // Test to see whether the margin that should be carried out // to the next line (NL) might have changed. In ReflowBlockFrame // we call nextLine->MarkPreviousMarginDirty if the block's // actual carried-out block-end margin changed. So here we only // need to worry about the following effects: // 1) the line was just created, and it might now be blocking // a carried-out block-end margin from previous lines that // used to reach NL from reaching NL // 2) the line used to be empty, and is now not empty, // thus blocking a carried-out block-end margin from previous lines // that used to reach NL from reaching NL // 3) the line wasn't empty, but now is, so a carried-out // block-end margin from previous lines that didn't used to reach NL // now does // 4) the line might have changed in a way that affects NL's // ShouldApplyBStartMargin decision. The three things that matter // are the line's emptiness, its adjacency to the block-start edge of the // block, and whether it has clearance (the latter only matters if the // block was and is adjacent to the block-start and empty). // // If the line is empty now, we can't reliably tell if the line was empty // before, so we just assume it was and do // nextLine->MarkPreviousMarginDirty. This means the checks in 4) are // redundant; if the line is empty now we don't need to check 4), but if // the line is not empty now and we're sure it wasn't empty before, any // adjacency and clearance changes are irrelevant to the result of // nextLine->ShouldApplyBStartMargin. if (line.next() != LinesEnd()) { bool maybeWasEmpty = oldB == line.next()->BStart(); bool isEmpty = line->CachedIsEmpty(); if (maybeReflowingForFirstTime /*1*/ || (isEmpty || maybeWasEmpty) /*2/3/4*/) { line.next()->MarkPreviousMarginDirty(); // since it's marked dirty, nobody will care about |deltaBCoord| } } // If the line was just reflowed for the first time, then its // old mBounds cannot be trusted so this deltaBCoord computation is // bogus. But that's OK because we just did // MarkPreviousMarginDirty on the next line which will force it // to be reflowed, so this computation of deltaBCoord will not be // used. deltaBCoord = line->BEnd() - oldBMost; // Now do an interrupt check. We want to do this only in the case when we // actually reflow the line, so that if we get back in here we'll get // further on the reflow before interrupting. aState.mPresContext->CheckForInterrupt(this); } else { aState.mOverflowTracker->Skip(line->mFirstChild, aState.mReflowStatus); // Nop except for blocks (we don't create overflow container // continuations for any inlines atm), so only checking mFirstChild // is enough lastLineMovedUp = deltaBCoord < 0; if (deltaBCoord != 0) { SlideLine(aState, line, deltaBCoord); } else { repositionViews = true; } NS_ASSERTION(!line->IsDirty() || !line->HasFloats(), "Possibly stale float cache here!"); if (willReflowAgain && line->IsBlock()) { // If we're going to reflow everything again, and this line is a block, // then there is no need to recover float state. The line may contain // other lines with floats, but in that case RecoverStateFrom would only // add floats to the float manager. We don't need to do that because // everything's going to get reflowed again "for real". Calling // RecoverStateFrom in this situation could be lethal because the // block's descendant lines may have float caches containing dangling // frame pointers. Ugh! // If this line is inline, then we need to recover its state now // to make sure that we don't forget to move its floats by deltaBCoord. } else { // XXX EVIL O(N^2) EVIL aState.RecoverStateFrom(line, deltaBCoord); } // Keep mBCoord up to date in case we're propagating reflow damage // and also because our final height may depend on it. If the // line is inlines, then only update mBCoord if the line is not // empty, because that's what PlaceLine does. (Empty blocks may // want to update mBCoord, e.g. if they have clearance.) if (line->IsBlock() || !line->CachedIsEmpty()) { aState.mBCoord = line->BEnd(); } needToRecoverState = true; if (reflowedPrevLine && !line->IsBlock() && aState.mPresContext->HasPendingInterrupt()) { // Need to make sure to pull overflows from any prev-in-flows for (nsIFrame* inlineKid = line->mFirstChild; inlineKid; inlineKid = inlineKid->PrincipalChildList().FirstChild()) { inlineKid->PullOverflowsFromPrevInFlow(); } } } // Record if we need to clear floats before reflowing the next // line. Note that inlineFloatClearType will be handled and // cleared before the next line is processed, so there is no // need to combine break types here. if (line->HasFloatClearTypeAfter()) { inlineFloatClearType = line->FloatClearTypeAfter(); } if (LineHasClear(line.get())) { foundAnyClears = true; } DumpLine(aState, line, deltaBCoord, -1); if (aState.mPresContext->HasPendingInterrupt()) { willReflowAgain = true; // Another option here might be to leave |line| clean if // !HasPendingInterrupt() before the CheckForInterrupt() call, since in // that case the line really did reflow as it should have. Not sure // whether that would be safe, so doing this for now instead. Also not // sure whether we really want to mark all lines dirty after an // interrupt, but until we get better at propagating float damage we // really do need to do it this way; see comments inside MarkLineDirty. MarkLineDirtyForInterrupt(line); } } // Handle BR-clearance from the last line of the block if (inlineFloatClearType != StyleClear::None) { std::tie(aState.mBCoord, std::ignore) = aState.ClearFloats(aState.mBCoord, inlineFloatClearType); } if (needToRecoverState) { // Is this expensive? aState.ReconstructMarginBefore(line); // Update aState.mPrevChild as if we had reflowed all of the frames in // the last line. NS_ASSERTION(line == line_end || line->mFirstChild->GetPrevSibling() == line.prev()->LastChild(), "unexpected line frames"); aState.mPrevChild = line == line_end ? mFrames.LastChild() : line->mFirstChild->GetPrevSibling(); } // Should we really have to do this? if (repositionViews) { nsContainerFrame::PlaceFrameView(this); } // We can skip trying to pull up the next line if our height is constrained // (so we can report being incomplete) and there is no next in flow or we // were told not to or we know it will be futile, i.e., // -- the next in flow is not changing // -- and we cannot have added more space for its first line to be // pulled up into, // -- it's an incremental reflow of a descendant // -- and we didn't reflow any floats (so the available space // didn't change) // -- my chain of next-in-flows either has no first line, or its first // line isn't dirty. bool heightConstrained = aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE; bool skipPull = willReflowAgain && heightConstrained; if (!skipPull && heightConstrained && aState.mNextInFlow && (aState.mReflowInput.mFlags.mNextInFlowUntouched && !lastLineMovedUp && !HasAnyStateBits(NS_FRAME_IS_DIRTY) && !reflowedFloat)) { // We'll place lineIter at the last line of this block, so that // nsBlockInFlowLineIterator::Next() will take us to the first // line of my next-in-flow-chain. (But first, check that I // have any lines -- if I don't, just bail out of this // optimization.) LineIterator lineIter = this->LinesEnd(); if (lineIter != this->LinesBegin()) { lineIter--; // I have lines; step back from dummy iterator to last line. nsBlockInFlowLineIterator bifLineIter(this, lineIter); // Check for next-in-flow-chain's first line. // (First, see if there is such a line, and second, see if it's clean) if (!bifLineIter.Next() || !bifLineIter.GetLine()->IsDirty()) { skipPull = true; } } } if (skipPull && aState.mNextInFlow) { NS_ASSERTION(heightConstrained, "Height should be constrained here\n"); if (aState.mNextInFlow->IsTrueOverflowContainer()) { aState.mReflowStatus.SetOverflowIncomplete(); } else { aState.mReflowStatus.SetIncomplete(); } } if (!skipPull && aState.mNextInFlow) { // Pull data from a next-in-flow if there's still room for more // content here. while (keepGoing && aState.mNextInFlow) { // Grab first line from our next-in-flow nsBlockFrame* nextInFlow = aState.mNextInFlow; nsLineBox* pulledLine; nsFrameList pulledFrames; if (!nextInFlow->mLines.empty()) { RemoveFirstLine(nextInFlow->mLines, nextInFlow->mFrames, &pulledLine, &pulledFrames); } else { // Grab an overflow line if there are any FrameLines* overflowLines = nextInFlow->GetOverflowLines(); if (!overflowLines) { aState.mNextInFlow = static_cast(nextInFlow->GetNextInFlow()); continue; } bool last = RemoveFirstLine(overflowLines->mLines, overflowLines->mFrames, &pulledLine, &pulledFrames); if (last) { nextInFlow->DestroyOverflowLines(); } } if (pulledFrames.IsEmpty()) { // The line is empty. Try the next one. NS_ASSERTION( pulledLine->GetChildCount() == 0 && !pulledLine->mFirstChild, "bad empty line"); nextInFlow->FreeLineBox(pulledLine); continue; } if (nextInFlow->MaybeHasLineCursor()) { if (pulledLine == nextInFlow->GetLineCursorForDisplay()) { nextInFlow->ClearLineCursorForDisplay(); } if (pulledLine == nextInFlow->GetLineCursorForQuery()) { nextInFlow->ClearLineCursorForQuery(); } } ReparentFrames(pulledFrames, nextInFlow, this); pulledLine->SetMovedFragments(); NS_ASSERTION(pulledFrames.LastChild() == pulledLine->LastChild(), "Unexpected last frame"); NS_ASSERTION(aState.mPrevChild || mLines.empty(), "should have a prevchild here"); NS_ASSERTION(aState.mPrevChild == mFrames.LastChild(), "Incorrect aState.mPrevChild before inserting line at end"); // Shift pulledLine's frames into our mFrames list. mFrames.AppendFrames(nullptr, std::move(pulledFrames)); // Add line to our line list, and set its last child as our new prev-child line = mLines.before_insert(LinesEnd(), pulledLine); aState.mPrevChild = mFrames.LastChild(); // Reparent floats whose placeholders are in the line. ReparentFloats(pulledLine->mFirstChild, nextInFlow, true); DumpLine(aState, pulledLine, deltaBCoord, 0); #ifdef DEBUG AutoNoisyIndenter indent2(gNoisyReflow); #endif if (aState.mPresContext->HasPendingInterrupt()) { MarkLineDirtyForInterrupt(line); } else { // Now reflow it and any lines that it makes during it's reflow // (we have to loop here because reflowing the line may cause a new // line to be created; see SplitLine's callers for examples of // when this happens). while (line != LinesEnd()) { usedOverflowWrap |= ReflowLine(aState, line, &keepGoing); if (aState.mReflowInput.WillReflowAgainForClearance()) { line->MarkDirty(); keepGoing = false; aState.mReflowStatus.SetIncomplete(); break; } DumpLine(aState, line, deltaBCoord, -1); if (!keepGoing) { if (0 == line->GetChildCount()) { DeleteLine(aState, line, line_end); } break; } if (LineHasClear(line.get())) { foundAnyClears = true; } if (aState.mPresContext->CheckForInterrupt(this)) { MarkLineDirtyForInterrupt(line); break; } // If this is an inline frame then its time to stop ++line; aState.AdvanceToNextLine(); } } } if (aState.mReflowStatus.IsIncomplete()) { aState.mReflowStatus.SetNextInFlowNeedsReflow(); } // XXXfr shouldn't set this flag when nextinflow has no lines } // Handle an odd-ball case: a list-item with no lines if (mLines.empty() && HasOutsideMarker()) { ReflowOutput metrics(aState.mReflowInput); nsIFrame* marker = GetOutsideMarker(); WritingMode wm = aState.mReflowInput.GetWritingMode(); ReflowOutsideMarker( marker, aState, metrics, aState.mReflowInput.ComputedPhysicalBorderPadding().top); NS_ASSERTION(!MarkerIsEmpty() || metrics.BSize(wm) == 0, "empty ::marker frame took up space"); if (!MarkerIsEmpty()) { // There are no lines so we have to fake up some y motion so that // we end up with *some* height. // (Note: if we're layout-contained, we have to be sure to leave our // ReflowOutput's BlockStartAscent() (i.e. the baseline) untouched, // because layout-contained frames have no baseline.) if (!aState.mReflowInput.mStyleDisplay->IsContainLayout() && metrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) { nscoord ascent; WritingMode wm = aState.mReflowInput.GetWritingMode(); if (nsLayoutUtils::GetFirstLineBaseline(wm, marker, &ascent)) { metrics.SetBlockStartAscent(ascent); } else { metrics.SetBlockStartAscent(metrics.BSize(wm)); } } RefPtr fm = nsLayoutUtils::GetInflatedFontMetricsForFrame(this); nscoord minAscent = nsLayoutUtils::GetCenteredFontBaseline( fm, aState.mMinLineHeight, wm.IsLineInverted()); nscoord minDescent = aState.mMinLineHeight - minAscent; aState.mBCoord += std::max(minAscent, metrics.BlockStartAscent()) + std::max(minDescent, metrics.BSize(wm) - metrics.BlockStartAscent()); nscoord offset = minAscent - metrics.BlockStartAscent(); if (offset > 0) { marker->SetRect(marker->GetRect() + nsPoint(0, offset)); } } } if (LinesAreEmpty(mLines) && ShouldHaveLineIfEmpty()) { aState.mBCoord += aState.mMinLineHeight; } if (foundAnyClears) { AddStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN); } else { RemoveStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN); } #ifdef DEBUG VerifyLines(true); VerifyOverflowSituation(); if (gNoisyReflow) { IndentBy(stdout, gNoiseIndent - 1); ListTag(stdout); printf(": done reflowing dirty lines (status=%s)\n", ToString(aState.mReflowStatus).c_str()); } #endif return usedOverflowWrap; } void nsBlockFrame::MarkLineDirtyForInterrupt(nsLineBox* aLine) { aLine->MarkDirty(); // Just checking NS_FRAME_IS_DIRTY is ok, because we've already // marked the lines that need to be marked dirty based on our // vertical resize stuff. So we'll definitely reflow all those kids; // the only question is how they should behave. if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) { // Mark all our child frames dirty so we make sure to reflow them // later. int32_t n = aLine->GetChildCount(); for (nsIFrame* f = aLine->mFirstChild; n > 0; f = f->GetNextSibling(), --n) { f->MarkSubtreeDirty(); } // And mark all the floats whose reflows we might be skipping dirty too. if (aLine->HasFloats()) { for (nsIFrame* f : aLine->Floats()) { f->MarkSubtreeDirty(); } } } else { // Dirty all the descendant lines of block kids to handle float damage, // since our nsFloatManager will go away by the next time we're reflowing. // XXXbz Can we do something more like what PropagateFloatDamage does? // Would need to sort out the exact business with mBlockDelta for that.... // This marks way too much dirty. If we ever make this better, revisit // which lines we mark dirty in the interrupt case in ReflowDirtyLines. nsBlockFrame* bf = do_QueryFrame(aLine->mFirstChild); if (bf) { MarkAllDescendantLinesDirty(bf); } } } void nsBlockFrame::DeleteLine(BlockReflowState& aState, nsLineList::iterator aLine, nsLineList::iterator aLineEnd) { MOZ_ASSERT(0 == aLine->GetChildCount(), "can't delete !empty line"); if (0 == aLine->GetChildCount()) { NS_ASSERTION(aState.mCurrentLine == aLine, "using function more generally than designed, " "but perhaps OK now"); nsLineBox* line = aLine; aLine = mLines.erase(aLine); FreeLineBox(line); // Mark the previous margin of the next line dirty since we need to // recompute its top position. if (aLine != aLineEnd) { aLine->MarkPreviousMarginDirty(); } } } /** * Reflow a line. The line will either contain a single block frame * or contain 1 or more inline frames. aKeepReflowGoing indicates * whether or not the caller should continue to reflow more lines. * Returns true if the reflow used an overflow-wrap breakpoint. */ bool nsBlockFrame::ReflowLine(BlockReflowState& aState, LineIterator aLine, bool* aKeepReflowGoing) { MOZ_ASSERT(aLine->GetChildCount(), "reflowing empty line"); // Setup the line-layout for the new line aState.mCurrentLine = aLine; aLine->ClearDirty(); aLine->InvalidateCachedIsEmpty(); aLine->ClearHadFloatPushed(); // If this line contains a single block that is hidden by `content-visibility` // don't reflow the line. If this line contains inlines and the first one is // hidden by `content-visibility`, all of them are, so avoid reflow in that // case as well. // For frames that own anonymous children, even the first child is hidden by // `content-visibility`, there could be some anonymous children need reflow, // so we don't skip reflow this line. nsIFrame* firstChild = aLine->mFirstChild; if (firstChild->IsHiddenByContentVisibilityOfInFlowParentForLayout() && !HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES)) { return false; } // Now that we know what kind of line we have, reflow it bool usedOverflowWrap = false; if (aLine->IsBlock()) { ReflowBlockFrame(aState, aLine, aKeepReflowGoing); } else { aLine->SetLineWrapped(false); usedOverflowWrap = ReflowInlineFrames(aState, aLine, aKeepReflowGoing); // Store the line's float edges for overflow marker analysis if needed. aLine->ClearFloatEdges(); if (aState.mFlags.mCanHaveOverflowMarkers) { WritingMode wm = aLine->mWritingMode; nsFlowAreaRect r = aState.GetFloatAvailableSpaceForBSize( aLine->BStart(), aLine->BSize(), nullptr); if (r.HasFloats()) { LogicalRect so = aLine->GetOverflowArea(OverflowType::Scrollable, wm, aLine->mContainerSize); nscoord s = r.mRect.IStart(wm); nscoord e = r.mRect.IEnd(wm); if (so.IEnd(wm) > e || so.IStart(wm) < s) { // This line is overlapping a float - store the edges marking the area // between the floats for text-overflow analysis. aLine->SetFloatEdges(s, e); } } } } aLine->ClearMovedFragments(); return usedOverflowWrap; } nsIFrame* nsBlockFrame::PullFrame(BlockReflowState& aState, LineIterator aLine) { // First check our remaining lines. if (LinesEnd() != aLine.next()) { return PullFrameFrom(aLine, this, aLine.next()); } NS_ASSERTION( !GetOverflowLines(), "Our overflow lines should have been removed at the start of reflow"); // Try each next-in-flow. nsBlockFrame* nextInFlow = aState.mNextInFlow; while (nextInFlow) { if (nextInFlow->mLines.empty()) { nextInFlow->DrainSelfOverflowList(); } if (!nextInFlow->mLines.empty()) { return PullFrameFrom(aLine, nextInFlow, nextInFlow->mLines.begin()); } nextInFlow = static_cast(nextInFlow->GetNextInFlow()); aState.mNextInFlow = nextInFlow; } return nullptr; } nsIFrame* nsBlockFrame::PullFrameFrom(nsLineBox* aLine, nsBlockFrame* aFromContainer, nsLineList::iterator aFromLine) { nsLineBox* fromLine = aFromLine; MOZ_ASSERT(fromLine, "bad line to pull from"); MOZ_ASSERT(fromLine->GetChildCount(), "empty line"); MOZ_ASSERT(aLine->GetChildCount(), "empty line"); MOZ_ASSERT(!HasProperty(LineIteratorProperty()), "Shouldn't have line iterators mid-reflow"); NS_ASSERTION(fromLine->IsBlock() == fromLine->mFirstChild->IsBlockOutside(), "Disagreement about whether it's a block or not"); if (fromLine->IsBlock()) { // If our line is not empty and the child in aFromLine is a block // then we cannot pull up the frame into this line. In this case // we stop pulling. return nullptr; } // Take frame from fromLine nsIFrame* frame = fromLine->mFirstChild; nsIFrame* newFirstChild = frame->GetNextSibling(); if (aFromContainer != this) { // The frame is being pulled from a next-in-flow; therefore we need to add // it to our sibling list. MOZ_ASSERT(aLine == mLines.back()); MOZ_ASSERT(aFromLine == aFromContainer->mLines.begin(), "should only pull from first line"); aFromContainer->mFrames.RemoveFrame(frame); // When pushing and pulling frames we need to check for whether any // views need to be reparented. ReparentFrame(frame, aFromContainer, this); mFrames.AppendFrame(nullptr, frame); // The frame might have (or contain) floats that need to be brought // over too. (pass 'false' since there are no siblings to check) ReparentFloats(frame, aFromContainer, false); } else { MOZ_ASSERT(aLine == aFromLine.prev()); } aLine->NoteFrameAdded(frame); fromLine->NoteFrameRemoved(frame); if (fromLine->GetChildCount() > 0) { // Mark line dirty now that we pulled a child fromLine->MarkDirty(); fromLine->mFirstChild = newFirstChild; } else { // Free up the fromLine now that it's empty. // Its bounds might need to be redrawn, though. if (aFromLine.next() != aFromContainer->mLines.end()) { aFromLine.next()->MarkPreviousMarginDirty(); } aFromContainer->mLines.erase(aFromLine); // aFromLine is now invalid aFromContainer->FreeLineBox(fromLine); } #ifdef DEBUG VerifyLines(true); VerifyOverflowSituation(); #endif return frame; } void nsBlockFrame::SlideLine(BlockReflowState& aState, nsLineBox* aLine, nscoord aDeltaBCoord) { MOZ_ASSERT(aDeltaBCoord != 0, "why slide a line nowhere?"); // Adjust line state aLine->SlideBy(aDeltaBCoord, aState.ContainerSize()); // Adjust the frames in the line MoveChildFramesOfLine(aLine, aDeltaBCoord); } void nsBlockFrame::UpdateLineContainerSize(nsLineBox* aLine, const nsSize& aNewContainerSize) { if (aNewContainerSize == aLine->mContainerSize) { return; } // Adjust line state nsSize sizeDelta = aLine->UpdateContainerSize(aNewContainerSize); // Changing container width only matters if writing mode is vertical-rl if (GetWritingMode().IsVerticalRL()) { MoveChildFramesOfLine(aLine, sizeDelta.width); } } void nsBlockFrame::MoveChildFramesOfLine(nsLineBox* aLine, nscoord aDeltaBCoord) { // Adjust the frames in the line nsIFrame* kid = aLine->mFirstChild; if (!kid) { return; } WritingMode wm = GetWritingMode(); LogicalPoint translation(wm, 0, aDeltaBCoord); if (aLine->IsBlock()) { if (aDeltaBCoord) { kid->MovePositionBy(wm, translation); } // Make sure the frame's view and any child views are updated nsContainerFrame::PlaceFrameView(kid); } else { // Adjust the block-dir coordinate of the frames in the line. // Note: we need to re-position views even if aDeltaBCoord is 0, because // one of our parent frames may have moved and so the view's position // relative to its parent may have changed. int32_t n = aLine->GetChildCount(); while (--n >= 0) { if (aDeltaBCoord) { kid->MovePositionBy(wm, translation); } // Make sure the frame's view and any child views are updated nsContainerFrame::PlaceFrameView(kid); kid = kid->GetNextSibling(); } } } static inline bool IsNonAutoNonZeroBSize(const StyleSize& aCoord) { // The "extremum length" values (see ExtremumLength) were originally aimed at // inline-size (or width, as it was before logicalization). For now, let them // return false here, so we treat them like 'auto' pending a real // implementation. (See bug 1126420.) // // FIXME (bug 567039, bug 527285) This isn't correct for the 'fill' value, // which should more likely (but not necessarily, depending on the available // space) be returning true. if (aCoord.BehavesLikeInitialValueOnBlockAxis()) { return false; } MOZ_ASSERT(aCoord.IsLengthPercentage()); // If we evaluate the length/percent/calc at a percentage basis of // both nscoord_MAX and 0, and it's zero both ways, then it's a zero // length, percent, or combination thereof. Test > 0 so we clamp // negative calc() results to 0. return aCoord.AsLengthPercentage().Resolve(nscoord_MAX) > 0 || aCoord.AsLengthPercentage().Resolve(0) > 0; } /* virtual */ bool nsBlockFrame::IsSelfEmpty() { if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) { return true; } // Blocks which are margin-roots (including inline-blocks) cannot be treated // as empty for margin-collapsing and other purposes. They're more like // replaced elements. if (HasAnyStateBits(NS_BLOCK_BFC)) { return false; } WritingMode wm = GetWritingMode(); const nsStylePosition* position = StylePosition(); if (IsNonAutoNonZeroBSize(position->MinBSize(wm)) || IsNonAutoNonZeroBSize(position->BSize(wm))) { return false; } // FIXME: Bug 1646100 - Take intrinsic size into account. // FIXME: Handle the case that both inline and block sizes are auto. // https://github.com/w3c/csswg-drafts/issues/5060. // Note: block-size could be zero or auto/intrinsic keywords here. if (position->BSize(wm).BehavesLikeInitialValueOnBlockAxis() && position->mAspectRatio.HasFiniteRatio()) { return false; } const nsStyleBorder* border = StyleBorder(); const nsStylePadding* padding = StylePadding(); if (border->GetComputedBorderWidth(wm.PhysicalSide(eLogicalSideBStart)) != 0 || border->GetComputedBorderWidth(wm.PhysicalSide(eLogicalSideBEnd)) != 0 || !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBStart(wm)) || !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBEnd(wm))) { return false; } if (HasOutsideMarker() && !MarkerIsEmpty()) { return false; } return true; } bool nsBlockFrame::CachedIsEmpty() { if (!IsSelfEmpty()) { return false; } for (auto& line : mLines) { if (!line.CachedIsEmpty()) { return false; } } return true; } bool nsBlockFrame::IsEmpty() { if (!IsSelfEmpty()) { return false; } return LinesAreEmpty(mLines); } bool nsBlockFrame::ShouldApplyBStartMargin(BlockReflowState& aState, nsLineBox* aLine) { if (aLine->mFirstChild->IsPageBreakFrame()) { // A page break frame consumes margins adjacent to it. // https://drafts.csswg.org/css-break/#break-margins return false; } if (aState.mFlags.mShouldApplyBStartMargin) { // Apply short-circuit check to avoid searching the line list return true; } if (!aState.IsAdjacentWithBStart()) { // If we aren't at the start block-coordinate then something of non-zero // height must have been placed. Therefore the childs block-start margin // applies. aState.mFlags.mShouldApplyBStartMargin = true; return true; } // Determine if this line is "essentially" the first line LineIterator line = LinesBegin(); if (aState.mFlags.mHasLineAdjacentToTop) { line = aState.mLineAdjacentToTop; } while (line != aLine) { if (!line->CachedIsEmpty() || line->HasClearance()) { // A line which precedes aLine is non-empty, or has clearance, // so therefore the block-start margin applies. aState.mFlags.mShouldApplyBStartMargin = true; return true; } // No need to apply the block-start margin if the line has floats. We // should collapse anyway (bug 44419) ++line; aState.mFlags.mHasLineAdjacentToTop = true; aState.mLineAdjacentToTop = line; } // The line being reflowed is "essentially" the first line in the // block. Therefore its block-start margin will be collapsed by the // generational collapsing logic with its parent (us). return false; } void nsBlockFrame::ReflowBlockFrame(BlockReflowState& aState, LineIterator aLine, bool* aKeepReflowGoing) { MOZ_ASSERT(*aKeepReflowGoing, "bad caller"); nsIFrame* frame = aLine->mFirstChild; if (!frame) { NS_ASSERTION(false, "program error - unexpected empty line"); return; } // If the previous frame was a page-break-frame, then preemptively push this // frame to the next page. // This is primarily important for the placeholders for abspos frames, which // measure as zero height and then would be placed on this page. if (aState.ContentBSize() != NS_UNCONSTRAINEDSIZE) { const nsIFrame* const prev = frame->GetPrevSibling(); if (prev && prev->IsPageBreakFrame()) { PushTruncatedLine(aState, aLine, aKeepReflowGoing); return; } } // Prepare the block reflow engine nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput); StyleClear clearType = frame->StyleDisplay()->mClear; if (aState.mTrailingClearFromPIF != StyleClear::None) { clearType = nsLayoutUtils::CombineClearType(clearType, aState.mTrailingClearFromPIF); aState.mTrailingClearFromPIF = StyleClear::None; } // Clear past floats before the block if the clear style is not none aLine->ClearForcedLineBreak(); if (clearType != StyleClear::None) { aLine->SetForcedLineBreakBefore(clearType); } // See if we should apply the block-start margin. If the block frame being // reflowed is a continuation, then we don't apply its block-start margin // because it's not significant. Otherwise, dig deeper. bool applyBStartMargin = !frame->GetPrevContinuation() && ShouldApplyBStartMargin(aState, aLine); if (applyBStartMargin) { // The HasClearance setting is only valid if ShouldApplyBStartMargin // returned false (in which case the block-start margin-root set our // clearance flag). Otherwise clear it now. We'll set it later on // ourselves if necessary. aLine->ClearHasClearance(); } bool treatWithClearance = aLine->HasClearance(); bool mightClearFloats = clearType != StyleClear::None; nsIFrame* floatAvoidingBlock = nullptr; if (!nsBlockFrame::BlockCanIntersectFloats(frame)) { mightClearFloats = true; floatAvoidingBlock = frame; } // If our block-start margin was counted as part of some parent's block-start // margin collapse, and we are being speculatively reflowed assuming this // frame DID NOT need clearance, then we need to check that // assumption. if (!treatWithClearance && !applyBStartMargin && mightClearFloats && aState.mReflowInput.mDiscoveredClearance) { nscoord curBCoord = aState.mBCoord + aState.mPrevBEndMargin.get(); if (auto [clearBCoord, result] = aState.ClearFloats(curBCoord, clearType, floatAvoidingBlock); result != ClearFloatsResult::BCoordNoChange) { Unused << clearBCoord; // Only record the first frame that requires clearance if (!*aState.mReflowInput.mDiscoveredClearance) { *aState.mReflowInput.mDiscoveredClearance = frame; } aState.mPrevChild = frame; // Exactly what we do now is flexible since we'll definitely be // reflowed. return; } } if (treatWithClearance) { applyBStartMargin = true; } nsIFrame* clearanceFrame = nullptr; const nscoord startingBCoord = aState.mBCoord; const nsCollapsingMargin incomingMargin = aState.mPrevBEndMargin; nscoord clearance; // Save the original position of the frame so that we can reposition // its view as needed. nsPoint originalPosition = frame->GetPosition(); while (true) { clearance = 0; nscoord bStartMargin = 0; bool mayNeedRetry = false; bool clearedFloats = false; bool clearedPushedOrSplitFloat = false; if (applyBStartMargin) { // Precompute the blocks block-start margin value so that we can get the // correct available space (there might be a float that's // already been placed below the aState.mPrevBEndMargin // Setup a reflowInput to get the style computed block-start margin // value. We'll use a reason of `resize' so that we don't fudge // any incremental reflow input. // The availSpace here is irrelevant to our needs - all we want // out if this setup is the block-start margin value which doesn't depend // on the childs available space. // XXX building a complete ReflowInput just to get the block-start // margin seems like a waste. And we do this for almost every block! WritingMode wm = frame->GetWritingMode(); LogicalSize availSpace = aState.ContentSize(wm); ReflowInput reflowInput(aState.mPresContext, aState.mReflowInput, frame, availSpace); if (treatWithClearance) { aState.mBCoord += aState.mPrevBEndMargin.get(); aState.mPrevBEndMargin.Zero(); } // Now compute the collapsed margin-block-start value into // aState.mPrevBEndMargin, assuming that all child margins // collapse down to clearanceFrame. brc.ComputeCollapsedBStartMargin(reflowInput, &aState.mPrevBEndMargin, clearanceFrame, &mayNeedRetry); // XXX optimization; we could check the collapsing children to see if they // are sure to require clearance, and so avoid retrying them if (clearanceFrame) { // Don't allow retries on the second pass. The clearance decisions for // the blocks whose block-start margins collapse with ours are now // fixed. mayNeedRetry = false; } if (!treatWithClearance && !clearanceFrame && mightClearFloats) { // We don't know if we need clearance and this is the first, // optimistic pass. So determine whether *this block* needs // clearance. Note that we do not allow the decision for whether // this block has clearance to change on the second pass; that // decision is only allowed to be made under the optimistic // first pass. nscoord curBCoord = aState.mBCoord + aState.mPrevBEndMargin.get(); if (auto [clearBCoord, result] = aState.ClearFloats(curBCoord, clearType, floatAvoidingBlock); result != ClearFloatsResult::BCoordNoChange) { Unused << clearBCoord; // Looks like we need clearance and we didn't know about it already. // So recompute collapsed margin treatWithClearance = true; // Remember this decision, needed for incremental reflow aLine->SetHasClearance(); // Apply incoming margins aState.mBCoord += aState.mPrevBEndMargin.get(); aState.mPrevBEndMargin.Zero(); // Compute the collapsed margin again, ignoring the incoming margin // this time mayNeedRetry = false; brc.ComputeCollapsedBStartMargin(reflowInput, &aState.mPrevBEndMargin, clearanceFrame, &mayNeedRetry); } } // Temporarily advance the running block-direction value so that the // GetFloatAvailableSpace method will return the right available space. // This undone as soon as the horizontal margins are computed. bStartMargin = aState.mPrevBEndMargin.get(); if (treatWithClearance) { nscoord currentBCoord = aState.mBCoord; // advance mBCoord to the clear position. auto [clearBCoord, result] = aState.ClearFloats(aState.mBCoord, clearType, floatAvoidingBlock); aState.mBCoord = clearBCoord; clearedFloats = result != ClearFloatsResult::BCoordNoChange; clearedPushedOrSplitFloat = result == ClearFloatsResult::FloatsPushedOrSplit; // Compute clearance. It's the amount we need to add to the block-start // border-edge of the frame, after applying collapsed margins // from the frame and its children, to get it to line up with // the block-end of the floats. The former is // currentBCoord + bStartMargin, the latter is the current // aState.mBCoord. // Note that negative clearance is possible clearance = aState.mBCoord - (currentBCoord + bStartMargin); // Add clearance to our block-start margin while we compute available // space for the frame bStartMargin += clearance; // Note that aState.mBCoord should stay where it is: at the block-start // border-edge of the frame } else { // Advance aState.mBCoord to the block-start border-edge of the frame. aState.mBCoord += bStartMargin; } } aLine->SetLineIsImpactedByFloat(false); // Here aState.mBCoord is the block-start border-edge of the block. // Compute the available space for the block nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace(); WritingMode wm = aState.mReflowInput.GetWritingMode(); LogicalRect availSpace = aState.ComputeBlockAvailSpace( frame, floatAvailableSpace, (floatAvoidingBlock)); // The check for // (!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats) // is to some degree out of paranoia: if we reliably eat up block-start // margins at the top of the page as we ought to, it wouldn't be // needed. if ((!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats) && (availSpace.BSize(wm) < 0 || clearedPushedOrSplitFloat)) { // We know already that this child block won't fit on this // page/column due to the block-start margin or the clearance. So we // need to get out of here now. (If we don't, most blocks will handle // things fine, and report break-before, but zero-height blocks // won't, and will thus make their parent overly-large and force // *it* to be pushed in its entirety.) aState.mBCoord = startingBCoord; aState.mPrevBEndMargin = incomingMargin; if (ShouldAvoidBreakInside(aState.mReflowInput)) { SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing); } else { PushTruncatedLine(aState, aLine, aKeepReflowGoing); } return; } // Now put the block-dir coordinate back to the start of the // block-start-margin + clearance. aState.mBCoord -= bStartMargin; availSpace.BStart(wm) -= bStartMargin; if (NS_UNCONSTRAINEDSIZE != availSpace.BSize(wm)) { availSpace.BSize(wm) += bStartMargin; } // Construct the reflow input for the block. Maybe childReflowInput; Maybe cbSize; LogicalSize availSize = availSpace.Size(wm); bool columnSetWrapperHasNoBSizeLeft = false; if (Style()->GetPseudoType() == PseudoStyleType::columnContent) { // Calculate the multicol containing block's block size so that the // children with percentage block size get correct percentage basis. const ReflowInput* cbReflowInput = aState.mReflowInput.mParentReflowInput->mCBReflowInput; MOZ_ASSERT(cbReflowInput->mFrame->StyleColumn()->IsColumnContainerStyle(), "Get unexpected reflow input of multicol containing block!"); // Use column-width as the containing block's inline-size, i.e. the column // content's computed inline-size. cbSize.emplace(LogicalSize(wm, aState.mReflowInput.ComputedISize(), cbReflowInput->ComputedBSize()) .ConvertTo(frame->GetWritingMode(), wm)); // If a ColumnSetWrapper is in a balancing column content, it may be // pushed or pulled back and forth between column contents. Always add // NS_FRAME_HAS_DIRTY_CHILDREN bit to it so that its ColumnSet children // can have a chance to reflow under current block size constraint. if (aState.mReflowInput.mFlags.mIsColumnBalancing && frame->IsColumnSetWrapperFrame()) { frame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); } } else if (IsColumnSetWrapperFrame()) { // If we are reflowing our ColumnSet children, we want to apply our block // size constraint to the available block size when constructing reflow // input for ColumnSet so that ColumnSet can use it to compute its max // column block size. if (frame->IsColumnSetFrame()) { nscoord contentBSize = aState.mReflowInput.ComputedBSize(); if (aState.mReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) { contentBSize = std::min(contentBSize, aState.mReflowInput.ComputedMaxBSize()); } if (contentBSize != NS_UNCONSTRAINEDSIZE) { // To get the remaining content block-size, subtract the content // block-size consumed by our previous continuations. contentBSize -= aState.mConsumedBSize; // ColumnSet is not the outermost frame in the column container, so it // cannot have any margin. We don't need to consider any margin that // can be generated by "box-decoration-break: clone" as we do in // BlockReflowState::ComputeBlockAvailSpace(). const nscoord availContentBSize = std::max( 0, contentBSize - (aState.mBCoord - aState.ContentBStart())); if (availSize.BSize(wm) >= availContentBSize) { availSize.BSize(wm) = availContentBSize; columnSetWrapperHasNoBSizeLeft = true; } } } } childReflowInput.emplace(aState.mPresContext, aState.mReflowInput, frame, availSize.ConvertTo(frame->GetWritingMode(), wm), cbSize); childReflowInput->mFlags.mColumnSetWrapperHasNoBSizeLeft = columnSetWrapperHasNoBSizeLeft; if (aLine->MovedFragments()) { // We only need to set this the first reflow, since if we reflow // again (and replace childReflowInput) we'll be reflowing it // again in the same fragment as the previous time. childReflowInput->mFlags.mMovedBlockFragments = true; } nsFloatManager::SavedState floatManagerState; nsReflowStatus frameReflowStatus; do { if (floatAvailableSpace.HasFloats()) { // Set if floatAvailableSpace.HasFloats() is true for any // iteration of the loop. aLine->SetLineIsImpactedByFloat(true); } // We might need to store into mDiscoveredClearance later if it's // currently null; we want to overwrite any writes that // brc.ReflowBlock() below does, so we need to remember now // whether it's empty. const bool shouldStoreClearance = aState.mReflowInput.mDiscoveredClearance && !*aState.mReflowInput.mDiscoveredClearance; // Reflow the block into the available space if (mayNeedRetry || floatAvoidingBlock) { aState.FloatManager()->PushState(&floatManagerState); } if (mayNeedRetry) { childReflowInput->mDiscoveredClearance = &clearanceFrame; } else if (!applyBStartMargin) { childReflowInput->mDiscoveredClearance = aState.mReflowInput.mDiscoveredClearance; } frameReflowStatus.Reset(); brc.ReflowBlock(availSpace, applyBStartMargin, aState.mPrevBEndMargin, clearance, aLine.get(), *childReflowInput, frameReflowStatus, aState); if (frameReflowStatus.IsInlineBreakBefore()) { // No need to retry this loop if there is a break opportunity before the // child block. break; } // Now the block has a height. Using that height, get the // available space again and call ComputeBlockAvailSpace again. // If ComputeBlockAvailSpace gives a different result, we need to // reflow again. if (!floatAvoidingBlock) { break; } LogicalRect oldFloatAvailableSpaceRect(floatAvailableSpace.mRect); floatAvailableSpace = aState.GetFloatAvailableSpaceForBSize( aState.mBCoord + bStartMargin, brc.GetMetrics().BSize(wm), &floatManagerState); NS_ASSERTION(floatAvailableSpace.mRect.BStart(wm) == oldFloatAvailableSpaceRect.BStart(wm), "yikes"); // Restore the height to the position of the next band. floatAvailableSpace.mRect.BSize(wm) = oldFloatAvailableSpaceRect.BSize(wm); // Determine whether the available space shrunk on either side, // because (the first time round) we now know the block's height, // and it may intersect additional floats, or (on later // iterations) because narrowing the width relative to the // previous time may cause the block to become taller. Note that // since we're reflowing the block, narrowing the width might also // make it shorter, so we must pass aCanGrow as true. if (!AvailableSpaceShrunk(wm, oldFloatAvailableSpaceRect, floatAvailableSpace.mRect, true)) { // The size and position we chose before are fine (i.e., they // don't cause intersecting with floats that requires a change // in size or position), so we're done. break; } bool advanced = false; if (!aState.FloatAvoidingBlockFitsInAvailSpace(floatAvoidingBlock, floatAvailableSpace)) { // Advance to the next band. nscoord newBCoord = aState.mBCoord; if (aState.AdvanceToNextBand(floatAvailableSpace.mRect, &newBCoord)) { advanced = true; } // ClearFloats might be able to advance us further once we're there. std::tie(aState.mBCoord, std::ignore) = aState.ClearFloats(newBCoord, StyleClear::None, floatAvoidingBlock); // Start over with a new available space rect at the new height. floatAvailableSpace = aState.GetFloatAvailableSpaceWithState( aState.mBCoord, ShapeType::ShapeOutside, &floatManagerState); } const LogicalRect oldAvailSpace = availSpace; availSpace = aState.ComputeBlockAvailSpace(frame, floatAvailableSpace, (floatAvoidingBlock)); if (!advanced && availSpace.IsEqualEdges(oldAvailSpace)) { break; } // We need another reflow. aState.FloatManager()->PopState(&floatManagerState); if (!treatWithClearance && !applyBStartMargin && aState.mReflowInput.mDiscoveredClearance) { // We set shouldStoreClearance above to record only the first // frame that requires clearance. if (shouldStoreClearance) { *aState.mReflowInput.mDiscoveredClearance = frame; } aState.mPrevChild = frame; // Exactly what we do now is flexible since we'll definitely be // reflowed. return; } if (advanced) { // We're pushing down the border-box, so we don't apply margin anymore. // This should never cause us to move up since the call to // GetFloatAvailableSpaceForBSize above included the margin. applyBStartMargin = false; bStartMargin = 0; treatWithClearance = true; // avoid hitting test above clearance = 0; } childReflowInput.reset(); childReflowInput.emplace( aState.mPresContext, aState.mReflowInput, frame, availSpace.Size(wm).ConvertTo(frame->GetWritingMode(), wm)); } while (true); if (mayNeedRetry && clearanceFrame) { // Found a clearance frame, so we need to reflow |frame| a second time. // Restore the states and start over again. aState.FloatManager()->PopState(&floatManagerState); aState.mBCoord = startingBCoord; aState.mPrevBEndMargin = incomingMargin; continue; } aState.mPrevChild = frame; if (childReflowInput->WillReflowAgainForClearance()) { // If an ancestor of ours is going to reflow for clearance, we // need to avoid calling PlaceBlock, because it unsets dirty bits // on the child block (both itself, and through its call to // nsIFrame::DidReflow), and those dirty bits imply dirtiness for // all of the child block, including the lines it didn't reflow. NS_ASSERTION(originalPosition == frame->GetPosition(), "we need to call PositionChildViews"); return; } #if defined(REFLOW_STATUS_COVERAGE) RecordReflowStatus(true, frameReflowStatus); #endif if (frameReflowStatus.IsInlineBreakBefore()) { // None of the child block fits. if (ShouldAvoidBreakInside(aState.mReflowInput)) { SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing); } else { PushTruncatedLine(aState, aLine, aKeepReflowGoing); } } else { // Note: line-break-after a block is a nop // Try to place the child block. // Don't force the block to fit if we have positive clearance, because // pushing it to the next page would give it more room. // Don't force the block to fit if it's impacted by a float. If it is, // then pushing it to the next page would give it more room. Note that // isImpacted doesn't include impact from the block's own floats. bool forceFit = aState.IsAdjacentWithBStart() && clearance <= 0 && !floatAvailableSpace.HasFloats(); nsCollapsingMargin collapsedBEndMargin; OverflowAreas overflowAreas; *aKeepReflowGoing = brc.PlaceBlock(*childReflowInput, forceFit, aLine.get(), collapsedBEndMargin, overflowAreas, frameReflowStatus); if (!frameReflowStatus.IsFullyComplete() && ShouldAvoidBreakInside(aState.mReflowInput)) { *aKeepReflowGoing = false; aLine->MarkDirty(); } if (aLine->SetCarriedOutBEndMargin(collapsedBEndMargin)) { LineIterator nextLine = aLine; ++nextLine; if (nextLine != LinesEnd()) { nextLine->MarkPreviousMarginDirty(); } } aLine->SetOverflowAreas(overflowAreas); if (*aKeepReflowGoing) { // Some of the child block fit // Advance to new Y position nscoord newBCoord = aLine->BEnd(); aState.mBCoord = newBCoord; // Continue the block frame now if it didn't completely fit in // the available space. if (!frameReflowStatus.IsFullyComplete()) { bool madeContinuation = CreateContinuationFor(aState, nullptr, frame); nsIFrame* nextFrame = frame->GetNextInFlow(); NS_ASSERTION(nextFrame, "We're supposed to have a next-in-flow by now"); if (frameReflowStatus.IsIncomplete()) { // If nextFrame used to be an overflow container, make it a normal // block if (!madeContinuation && nextFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { nsOverflowContinuationTracker::AutoFinish fini( aState.mOverflowTracker, frame); nsContainerFrame* parent = nextFrame->GetParent(); parent->StealFrame(nextFrame); if (parent != this) { ReparentFrame(nextFrame, parent, this); } mFrames.InsertFrame(nullptr, frame, nextFrame); madeContinuation = true; // needs to be added to mLines nextFrame->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); frameReflowStatus.SetNextInFlowNeedsReflow(); } // Push continuation to a new line, but only if we actually made // one. if (madeContinuation) { nsLineBox* line = NewLineBox(nextFrame, true); mLines.after_insert(aLine, line); } PushTruncatedLine(aState, aLine.next(), aKeepReflowGoing); // If we need to reflow the continuation of the block child, // then we'd better reflow our continuation if (frameReflowStatus.NextInFlowNeedsReflow()) { aState.mReflowStatus.SetNextInFlowNeedsReflow(); // We also need to make that continuation's line dirty so // it gets reflowed when we reflow our next in flow. The // nif's line must always be either a line of the nif's // parent block (only if we didn't make a continuation) or // else one of our own overflow lines. In the latter case // the line is already marked dirty, so just handle the // first case. if (!madeContinuation) { nsBlockFrame* nifBlock = do_QueryFrame(nextFrame->GetParent()); NS_ASSERTION( nifBlock, "A block's child's next in flow's parent must be a block!"); for (auto& line : nifBlock->Lines()) { if (line.Contains(nextFrame)) { line.MarkDirty(); break; } } } } // The block-end margin for a block is only applied on the last // flow block. Since we just continued the child block frame, // we know that line->mFirstChild is not the last flow block // therefore zero out the running margin value. #ifdef NOISY_BLOCK_DIR_MARGINS ListTag(stdout); printf(": reflow incomplete, frame="); frame->ListTag(stdout); printf(" prevBEndMargin=%d, setting to zero\n", aState.mPrevBEndMargin.get()); #endif aState.mPrevBEndMargin.Zero(); } else { // frame is complete but its overflow is not complete // Disconnect the next-in-flow and put it in our overflow tracker if (!madeContinuation && !nextFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { // It already exists, but as a normal next-in-flow, so we need // to dig it out of the child lists. nextFrame->GetParent()->StealFrame(nextFrame); } else if (madeContinuation) { mFrames.RemoveFrame(nextFrame); } // Put it in our overflow list aState.mOverflowTracker->Insert(nextFrame, frameReflowStatus); aState.mReflowStatus.MergeCompletionStatusFrom(frameReflowStatus); #ifdef NOISY_BLOCK_DIR_MARGINS ListTag(stdout); printf(": reflow complete but overflow incomplete for "); frame->ListTag(stdout); printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n", aState.mPrevBEndMargin.get(), collapsedBEndMargin.get()); #endif aState.mPrevBEndMargin = collapsedBEndMargin; } } else { // frame is fully complete #ifdef NOISY_BLOCK_DIR_MARGINS ListTag(stdout); printf(": reflow complete for "); frame->ListTag(stdout); printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n", aState.mPrevBEndMargin.get(), collapsedBEndMargin.get()); #endif aState.mPrevBEndMargin = collapsedBEndMargin; } #ifdef NOISY_BLOCK_DIR_MARGINS ListTag(stdout); printf(": frame="); frame->ListTag(stdout); printf(" carriedOutBEndMargin=%d collapsedBEndMargin=%d => %d\n", brc.GetCarriedOutBEndMargin().get(), collapsedBEndMargin.get(), aState.mPrevBEndMargin.get()); #endif } else { if (!frameReflowStatus.IsFullyComplete()) { // The frame reported an incomplete status, but then it also didn't // fit. This means we need to reflow it again so that it can // (again) report the incomplete status. frame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); } if ((aLine == mLines.front() && !GetPrevInFlow()) || ShouldAvoidBreakInside(aState.mReflowInput)) { // If it's our very first line *or* we're not at the top of the page // and we have page-break-inside:avoid, then we need to be pushed to // our parent's next-in-flow. SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing); } else { // Push the line that didn't fit and any lines that follow it // to our next-in-flow. PushTruncatedLine(aState, aLine, aKeepReflowGoing); } } } break; // out of the reflow retry loop } // Now that we've got its final position all figured out, position any child // views it may have. Note that the case when frame has a view got handled // by FinishReflowChild, but that function didn't have the coordinates needed // to correctly decide whether to reposition child views. if (originalPosition != frame->GetPosition() && !frame->HasView()) { nsContainerFrame::PositionChildViews(frame); } #ifdef DEBUG VerifyLines(true); #endif } // Returns true if an overflow-wrap break was used. bool nsBlockFrame::ReflowInlineFrames(BlockReflowState& aState, LineIterator aLine, bool* aKeepReflowGoing) { *aKeepReflowGoing = true; bool usedOverflowWrap = false; aLine->SetLineIsImpactedByFloat(false); // Setup initial coordinate system for reflowing the inline frames // into. Apply a previous block frame's block-end margin first. if (ShouldApplyBStartMargin(aState, aLine)) { aState.mBCoord += aState.mPrevBEndMargin.get(); } nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace(); LineReflowStatus lineReflowStatus; do { nscoord availableSpaceBSize = 0; aState.mLineBSize.reset(); do { bool allowPullUp = true; nsIFrame* forceBreakInFrame = nullptr; int32_t forceBreakOffset = -1; gfxBreakPriority forceBreakPriority = gfxBreakPriority::eNoBreak; do { nsFloatManager::SavedState floatManagerState; aState.FloatManager()->PushState(&floatManagerState); // Once upon a time we allocated the first 30 nsLineLayout objects // on the stack, and then we switched to the heap. At that time // these objects were large (1100 bytes on a 32 bit system). // Then the nsLineLayout object was shrunk to 156 bytes by // removing some internal buffers. Given that it is so much // smaller, the complexity of 2 different ways of allocating // no longer makes sense. Now we always allocate on the stack. nsLineLayout lineLayout(aState.mPresContext, aState.FloatManager(), aState.mReflowInput, &aLine, nullptr); lineLayout.Init(&aState, aState.mMinLineHeight, aState.mLineNumber); if (forceBreakInFrame) { lineLayout.ForceBreakAtPosition(forceBreakInFrame, forceBreakOffset); } DoReflowInlineFrames(aState, lineLayout, aLine, floatAvailableSpace, availableSpaceBSize, &floatManagerState, aKeepReflowGoing, &lineReflowStatus, allowPullUp); usedOverflowWrap = lineLayout.EndLineReflow(); if (LineReflowStatus::RedoNoPull == lineReflowStatus || LineReflowStatus::RedoMoreFloats == lineReflowStatus || LineReflowStatus::RedoNextBand == lineReflowStatus) { if (lineLayout.NeedsBackup()) { NS_ASSERTION(!forceBreakInFrame, "Backing up twice; this should never be necessary"); // If there is no saved break position, then this will set // set forceBreakInFrame to null and we won't back up, which is // correct. forceBreakInFrame = lineLayout.GetLastOptionalBreakPosition( &forceBreakOffset, &forceBreakPriority); } else { forceBreakInFrame = nullptr; } // restore the float manager state aState.FloatManager()->PopState(&floatManagerState); // Clear out float lists aState.mCurrentLineFloats.Clear(); aState.mBelowCurrentLineFloats.Clear(); aState.mNoWrapFloats.Clear(); } // Don't allow pullup on a subsequent LineReflowStatus::RedoNoPull pass allowPullUp = false; } while (LineReflowStatus::RedoNoPull == lineReflowStatus); } while (LineReflowStatus::RedoMoreFloats == lineReflowStatus); } while (LineReflowStatus::RedoNextBand == lineReflowStatus); return usedOverflowWrap; } void nsBlockFrame::SetBreakBeforeStatusBeforeLine(BlockReflowState& aState, LineIterator aLine, bool* aKeepReflowGoing) { aState.mReflowStatus.SetInlineLineBreakBeforeAndReset(); // Reflow the line again when we reflow at our new position. aLine->MarkDirty(); *aKeepReflowGoing = false; } void nsBlockFrame::PushTruncatedLine( BlockReflowState& aState, LineIterator aLine, bool* aKeepReflowGoing, ComputeNewPageNameIfNeeded aComputeNewPageName) { PushLines(aState, aLine.prev()); *aKeepReflowGoing = false; if (aComputeNewPageName == ComputeNewPageNameIfNeeded::Yes) { // mCanHaveClassABreakpoints can only be true during paginated reflow, and // we expect this function to only be called when the available bsize is // constrained. const WritingMode wm = GetWritingMode(); const bool canBreakForPageNames = aState.mReflowInput.mFlags.mCanHaveClassABreakpoints && !PresShell()->GetRootFrame()->GetWritingMode().IsOrthogonalTo(wm); if (canBreakForPageNames) { PresShell()->FrameConstructor()->MaybeSetNextPageContentFramePageName( aLine->mFirstChild); } } aState.mReflowStatus.SetIncomplete(); } void nsBlockFrame::DoReflowInlineFrames( BlockReflowState& aState, nsLineLayout& aLineLayout, LineIterator aLine, nsFlowAreaRect& aFloatAvailableSpace, nscoord& aAvailableSpaceBSize, nsFloatManager::SavedState* aFloatStateBeforeLine, bool* aKeepReflowGoing, LineReflowStatus* aLineReflowStatus, bool aAllowPullUp) { // Forget all of the floats on the line aLine->ClearFloats(); aState.mFloatOverflowAreas.Clear(); // We need to set this flag on the line if any of our reflow passes // are impacted by floats. if (aFloatAvailableSpace.HasFloats()) { aLine->SetLineIsImpactedByFloat(true); } #ifdef REALLY_NOISY_REFLOW printf("nsBlockFrame::DoReflowInlineFrames %p impacted = %d\n", this, aFloatAvailableSpace.HasFloats()); #endif WritingMode outerWM = aState.mReflowInput.GetWritingMode(); WritingMode lineWM = WritingModeForLine(outerWM, aLine->mFirstChild); LogicalRect lineRect = aFloatAvailableSpace.mRect.ConvertTo( lineWM, outerWM, aState.ContainerSize()); nscoord iStart = lineRect.IStart(lineWM); nscoord availISize = lineRect.ISize(lineWM); nscoord availBSize; if (aState.mReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) { availBSize = NS_UNCONSTRAINEDSIZE; } else { /* XXX get the height right! */ availBSize = lineRect.BSize(lineWM); } // Make sure to enable resize optimization before we call BeginLineReflow // because it might get disabled there aLine->EnableResizeReflowOptimization(); aLineLayout.BeginLineReflow( iStart, aState.mBCoord, availISize, availBSize, aFloatAvailableSpace.HasFloats(), false, /*XXX isTopOfPage*/ lineWM, aState.mContainerSize, aState.mInsetForBalance); aState.mFlags.mIsLineLayoutEmpty = false; // XXX Unfortunately we need to know this before reflowing the first // inline frame in the line. FIX ME. if (0 == aLineLayout.GetLineNumber() && HasAllStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD | NS_BLOCK_HAS_FIRST_LETTER_STYLE)) { aLineLayout.SetFirstLetterStyleOK(true); } NS_ASSERTION(!(HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD) && GetPrevContinuation()), "first letter child bit should only be on first continuation"); // Reflow the frames that are already on the line first LineReflowStatus lineReflowStatus = LineReflowStatus::OK; int32_t i; nsIFrame* frame = aLine->mFirstChild; if (aFloatAvailableSpace.HasFloats()) { // There is a soft break opportunity at the start of the line, because // we can always move this line down below float(s). if (aLineLayout.NotifyOptionalBreakPosition( frame, 0, true, gfxBreakPriority::eNormalBreak)) { lineReflowStatus = LineReflowStatus::RedoNextBand; } } // need to repeatedly call GetChildCount here, because the child // count can change during the loop! for (i = 0; LineReflowStatus::OK == lineReflowStatus && i < aLine->GetChildCount(); i++, frame = frame->GetNextSibling()) { ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus); if (LineReflowStatus::OK != lineReflowStatus) { // It is possible that one or more of next lines are empty // (because of DeleteNextInFlowChild). If so, delete them now // in case we are finished. ++aLine; while ((aLine != LinesEnd()) && (0 == aLine->GetChildCount())) { // XXX Is this still necessary now that DeleteNextInFlowChild // uses DoRemoveFrame? nsLineBox* toremove = aLine; aLine = mLines.erase(aLine); NS_ASSERTION(nullptr == toremove->mFirstChild, "bad empty line"); FreeLineBox(toremove); } --aLine; NS_ASSERTION(lineReflowStatus != LineReflowStatus::Truncated, "ReflowInlineFrame should never determine that a line " "needs to go to the next page/column"); } } // Don't pull up new frames into lines with continuation placeholders if (aAllowPullUp) { // Pull frames and reflow them until we can't while (LineReflowStatus::OK == lineReflowStatus) { frame = PullFrame(aState, aLine); if (!frame) { break; } while (LineReflowStatus::OK == lineReflowStatus) { int32_t oldCount = aLine->GetChildCount(); ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus); if (aLine->GetChildCount() != oldCount) { // We just created a continuation for aFrame AND its going // to end up on this line (e.g. :first-letter // situation). Therefore we have to loop here before trying // to pull another frame. frame = frame->GetNextSibling(); } else { break; } } } } aState.mFlags.mIsLineLayoutEmpty = aLineLayout.LineIsEmpty(); // We only need to backup if the line isn't going to be reflowed again anyway bool needsBackup = aLineLayout.NeedsBackup() && (lineReflowStatus == LineReflowStatus::Stop || lineReflowStatus == LineReflowStatus::OK); if (needsBackup && aLineLayout.HaveForcedBreakPosition()) { NS_WARNING( "We shouldn't be backing up more than once! " "Someone must have set a break opportunity beyond the available width, " "even though there were better break opportunities before it"); needsBackup = false; } if (needsBackup) { // We need to try backing up to before a text run // XXX It's possible, in fact not unusual, for the break opportunity to // already be the end of the line. We should detect that and optimize to not // re-do the line. if (aLineLayout.HasOptionalBreakPosition()) { // We can back up! lineReflowStatus = LineReflowStatus::RedoNoPull; } } else { // In case we reflow this line again, remember that we don't // need to force any breaking aLineLayout.ClearOptionalBreakPosition(); } if (LineReflowStatus::RedoNextBand == lineReflowStatus) { // This happens only when we have a line that is impacted by // floats and the first element in the line doesn't fit with // the floats. // // If there's block space available, we either try to reflow the line // past the current band (if it's non-zero and the band definitely won't // widen around a shape-outside), otherwise we try one pixel down. If // there's no block space available, we push the line to the next // page/column. NS_ASSERTION( NS_UNCONSTRAINEDSIZE != aFloatAvailableSpace.mRect.BSize(outerWM), "unconstrained block size on totally empty line"); // See the analogous code for blocks in BlockReflowState::ClearFloats. nscoord bandBSize = aFloatAvailableSpace.mRect.BSize(outerWM); if (bandBSize > 0 || NS_UNCONSTRAINEDSIZE == aState.mReflowInput.AvailableBSize()) { NS_ASSERTION(bandBSize == 0 || aFloatAvailableSpace.HasFloats(), "redo line on totally empty line with non-empty band..."); // We should never hit this case if we've placed floats on the // line; if we have, then the GetFloatAvailableSpace call is wrong // and needs to happen after the caller pops the float manager // state. aState.FloatManager()->AssertStateMatches(aFloatStateBeforeLine); if (!aFloatAvailableSpace.MayWiden() && bandBSize > 0) { // Move it down far enough to clear the current band. aState.mBCoord += bandBSize; } else { // Move it down by one dev pixel. aState.mBCoord += aState.mPresContext->DevPixelsToAppUnits(1); } aFloatAvailableSpace = aState.GetFloatAvailableSpace(); } else { // There's nowhere to retry placing the line, so we want to push // it to the next page/column where its contents can fit not // next to a float. lineReflowStatus = LineReflowStatus::Truncated; PushTruncatedLine(aState, aLine, aKeepReflowGoing); } // XXX: a small optimization can be done here when paginating: // if the new Y coordinate is past the end of the block then // push the line and return now instead of later on after we are // past the float. } else if (LineReflowStatus::Truncated != lineReflowStatus && LineReflowStatus::RedoNoPull != lineReflowStatus) { // If we are propagating out a break-before status then there is // no point in placing the line. if (!aState.mReflowStatus.IsInlineBreakBefore()) { if (!PlaceLine(aState, aLineLayout, aLine, aFloatStateBeforeLine, aFloatAvailableSpace, aAvailableSpaceBSize, aKeepReflowGoing)) { lineReflowStatus = LineReflowStatus::RedoMoreFloats; // PlaceLine already called GetFloatAvailableSpaceForBSize or its // variant for us. } } } #ifdef DEBUG if (gNoisyReflow) { printf("Line reflow status = %s\n", LineReflowStatusToString(lineReflowStatus)); } #endif if (aLineLayout.GetDirtyNextLine()) { // aLine may have been pushed to the overflow lines. FrameLines* overflowLines = GetOverflowLines(); // We can't just compare iterators front() to aLine here, since they may be // in different lists. bool pushedToOverflowLines = overflowLines && overflowLines->mLines.front() == aLine.get(); if (pushedToOverflowLines) { // aLine is stale, it's associated with the main line list but it should // be associated with the overflow line list now aLine = overflowLines->mLines.begin(); } nsBlockInFlowLineIterator iter(this, aLine, pushedToOverflowLines); if (iter.Next() && iter.GetLine()->IsInline()) { iter.GetLine()->MarkDirty(); if (iter.GetContainer() != this) { aState.mReflowStatus.SetNextInFlowNeedsReflow(); } } } *aLineReflowStatus = lineReflowStatus; } /** * Reflow an inline frame. The reflow status is mapped from the frames * reflow status to the lines reflow status (not to our reflow status). * The line reflow status is simple: true means keep placing frames * on the line; false means don't (the line is done). If the line * has some sort of breaking affect then aLine's break-type will be set * to something other than StyleClear::None. */ void nsBlockFrame::ReflowInlineFrame(BlockReflowState& aState, nsLineLayout& aLineLayout, LineIterator aLine, nsIFrame* aFrame, LineReflowStatus* aLineReflowStatus) { MOZ_ASSERT(aFrame); *aLineReflowStatus = LineReflowStatus::OK; #ifdef NOISY_FIRST_LETTER ListTag(stdout); printf(": reflowing "); aFrame->ListTag(stdout); printf(" reflowingFirstLetter=%s\n", aLineLayout.GetFirstLetterStyleOK() ? "on" : "off"); #endif if (aFrame->IsPlaceholderFrame()) { auto ph = static_cast(aFrame); ph->ForgetLineIsEmptySoFar(); } // Reflow the inline frame nsReflowStatus frameReflowStatus; bool pushedFrame; aLineLayout.ReflowFrame(aFrame, frameReflowStatus, nullptr, pushedFrame); if (frameReflowStatus.NextInFlowNeedsReflow()) { aLineLayout.SetDirtyNextLine(); } #ifdef REALLY_NOISY_REFLOW aFrame->ListTag(stdout); printf(": status=%s\n", ToString(frameReflowStatus).c_str()); #endif #if defined(REFLOW_STATUS_COVERAGE) RecordReflowStatus(false, frameReflowStatus); #endif // Send post-reflow notification aState.mPrevChild = aFrame; /* XXX This is where we need to add logic to handle some odd behavior. For one thing, we should usually place at least one thing next to a left float, even when that float takes up all the width on a line. see bug 22496 */ // Process the child frames reflow status. There are 5 cases: // complete, not-complete, break-before, break-after-complete, // break-after-not-complete. There are two situations: we are a // block or we are an inline. This makes a total of 10 cases // (fortunately, there is some overlap). aLine->ClearForcedLineBreak(); if (frameReflowStatus.IsInlineBreak() || aState.mTrailingClearFromPIF != StyleClear::None) { // Always abort the line reflow (because a line break is the // minimal amount of break we do). *aLineReflowStatus = LineReflowStatus::Stop; // XXX what should aLine's break-type be set to in all these cases? if (frameReflowStatus.IsInlineBreakBefore()) { // Break-before cases. if (aFrame == aLine->mFirstChild) { // If we break before the first frame on the line then we must // be trying to place content where there's no room (e.g. on a // line with wide floats). Inform the caller to reflow the // line after skipping past a float. *aLineReflowStatus = LineReflowStatus::RedoNextBand; } else { // It's not the first child on this line so go ahead and split // the line. We will see the frame again on the next-line. SplitLine(aState, aLineLayout, aLine, aFrame, aLineReflowStatus); // If we're splitting the line because the frame didn't fit and it // was pushed, then mark the line as having word wrapped. We need to // know that if we're shrink wrapping our width if (pushedFrame) { aLine->SetLineWrapped(true); } } } else { MOZ_ASSERT(frameReflowStatus.IsInlineBreakAfter() || aState.mTrailingClearFromPIF != StyleClear::None, "We should've handled inline break-before in the if-branch!"); // If a float split and its prev-in-flow was followed by a
    , then // combine the
    's float clear type with the inline's float clear type // (the inline will be the very next frame after the split float). StyleClear clearType = frameReflowStatus.FloatClearType(); if (aState.mTrailingClearFromPIF != StyleClear::None) { clearType = nsLayoutUtils::CombineClearType( clearType, aState.mTrailingClearFromPIF); aState.mTrailingClearFromPIF = StyleClear::None; } // Break-after cases if (clearType != StyleClear::None || aLineLayout.GetLineEndsInBR()) { aLine->SetForcedLineBreakAfter(clearType); } if (frameReflowStatus.IsComplete()) { // Split line, but after the frame just reflowed SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(), aLineReflowStatus); if (frameReflowStatus.IsInlineBreakAfter() && !aLineLayout.GetLineEndsInBR()) { aLineLayout.SetDirtyNextLine(); } } } } if (!frameReflowStatus.IsFullyComplete()) { // Create a continuation for the incomplete frame. Note that the // frame may already have a continuation. CreateContinuationFor(aState, aLine, aFrame); // Remember that the line has wrapped if (!aLineLayout.GetLineEndsInBR()) { aLine->SetLineWrapped(true); } // If we just ended a first-letter frame or reflowed a placeholder then // don't split the line and don't stop the line reflow... // But if we are going to stop anyways we'd better split the line. if ((!frameReflowStatus.FirstLetterComplete() && !aFrame->IsPlaceholderFrame()) || *aLineReflowStatus == LineReflowStatus::Stop) { // Split line after the current frame *aLineReflowStatus = LineReflowStatus::Stop; SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(), aLineReflowStatus); } } } bool nsBlockFrame::CreateContinuationFor(BlockReflowState& aState, nsLineBox* aLine, nsIFrame* aFrame) { nsIFrame* newFrame = nullptr; if (!aFrame->GetNextInFlow()) { newFrame = PresShell()->FrameConstructor()->CreateContinuingFrame(aFrame, this); mFrames.InsertFrame(nullptr, aFrame, newFrame); if (aLine) { aLine->NoteFrameAdded(newFrame); } } #ifdef DEBUG VerifyLines(false); #endif return !!newFrame; } void nsBlockFrame::SplitFloat(BlockReflowState& aState, nsIFrame* aFloat, const nsReflowStatus& aFloatStatus) { MOZ_ASSERT(!aFloatStatus.IsFullyComplete(), "why split the frame if it's fully complete?"); MOZ_ASSERT(aState.mBlock == this); nsIFrame* nextInFlow = aFloat->GetNextInFlow(); if (nextInFlow) { nsContainerFrame* oldParent = nextInFlow->GetParent(); oldParent->StealFrame(nextInFlow); if (oldParent != this) { ReparentFrame(nextInFlow, oldParent, this); } if (!aFloatStatus.IsOverflowIncomplete()) { nextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); } } else { nextInFlow = PresShell()->FrameConstructor()->CreateContinuingFrame(aFloat, this); } if (aFloatStatus.IsOverflowIncomplete()) { nextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); } StyleFloat floatStyle = aFloat->StyleDisplay()->mFloat; if (floatStyle == StyleFloat::Left) { aState.FloatManager()->SetSplitLeftFloatAcrossBreak(); } else { MOZ_ASSERT(floatStyle == StyleFloat::Right, "Unexpected float side!"); aState.FloatManager()->SetSplitRightFloatAcrossBreak(); } aState.AppendPushedFloatChain(nextInFlow); if (MOZ_LIKELY(!HasAnyStateBits(NS_BLOCK_BFC)) || MOZ_UNLIKELY(IsTrueOverflowContainer())) { aState.mReflowStatus.SetOverflowIncomplete(); } else { aState.mReflowStatus.SetIncomplete(); } } static bool CheckPlaceholderInLine(nsIFrame* aBlock, nsLineBox* aLine, nsIFrame* aFloat) { if (!aFloat) { return true; } NS_ASSERTION(!aFloat->GetPrevContinuation(), "float in a line should never be a continuation"); NS_ASSERTION(!aFloat->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT), "float in a line should never be a pushed float"); nsIFrame* ph = aFloat->FirstInFlow()->GetPlaceholderFrame(); for (nsIFrame* f = ph; f; f = f->GetParent()) { if (f->GetParent() == aBlock) { return aLine->Contains(f); } } NS_ASSERTION(false, "aBlock is not an ancestor of aFrame!"); return true; } void nsBlockFrame::SplitLine(BlockReflowState& aState, nsLineLayout& aLineLayout, LineIterator aLine, nsIFrame* aFrame, LineReflowStatus* aLineReflowStatus) { MOZ_ASSERT(aLine->IsInline(), "illegal SplitLine on block line"); int32_t pushCount = aLine->GetChildCount() - aLineLayout.GetCurrentSpanCount(); MOZ_ASSERT(pushCount >= 0, "bad push count"); #ifdef DEBUG if (gNoisyReflow) { nsIFrame::IndentBy(stdout, gNoiseIndent); printf("split line: from line=%p pushCount=%d aFrame=", static_cast(aLine.get()), pushCount); if (aFrame) { aFrame->ListTag(stdout); } else { printf("(null)"); } printf("\n"); if (gReallyNoisyReflow) { aLine->List(stdout, gNoiseIndent + 1); } } #endif if (0 != pushCount) { MOZ_ASSERT(aLine->GetChildCount() > pushCount, "bad push"); MOZ_ASSERT(nullptr != aFrame, "whoops"); #ifdef DEBUG { nsIFrame* f = aFrame; int32_t count = pushCount; while (f && count > 0) { f = f->GetNextSibling(); --count; } NS_ASSERTION(count == 0, "Not enough frames to push"); } #endif // Put frames being split out into their own line nsLineBox* newLine = NewLineBox(aLine, aFrame, pushCount); mLines.after_insert(aLine, newLine); #ifdef DEBUG if (gReallyNoisyReflow) { newLine->List(stdout, gNoiseIndent + 1); } #endif // Let line layout know that some frames are no longer part of its // state. aLineLayout.SplitLineTo(aLine->GetChildCount()); // If floats have been placed whose placeholders have been pushed to the new // line, we need to reflow the old line again. We don't want to look at the // frames in the new line, because as a large paragraph is laid out the // we'd get O(N^2) performance. So instead we just check that the last // float and the last below-current-line float are still in aLine. if (!CheckPlaceholderInLine( this, aLine, aLine->HasFloats() ? aLine->Floats().LastElement() : nullptr) || !CheckPlaceholderInLine( this, aLine, aState.mBelowCurrentLineFloats.SafeLastElement(nullptr))) { *aLineReflowStatus = LineReflowStatus::RedoNoPull; } #ifdef DEBUG VerifyLines(true); #endif } } bool nsBlockFrame::IsLastLine(BlockReflowState& aState, LineIterator aLine) { while (++aLine != LinesEnd()) { // There is another line if (0 != aLine->GetChildCount()) { // If the next line is a block line then this line is the last in a // group of inline lines. return aLine->IsBlock(); } // The next line is empty, try the next one } // Try our next-in-flows lines to answer the question nsBlockFrame* nextInFlow = (nsBlockFrame*)GetNextInFlow(); while (nullptr != nextInFlow) { for (const auto& line : nextInFlow->Lines()) { if (0 != line.GetChildCount()) { return line.IsBlock(); } } nextInFlow = (nsBlockFrame*)nextInFlow->GetNextInFlow(); } // This is the last line - so don't allow justification return true; } bool nsBlockFrame::PlaceLine(BlockReflowState& aState, nsLineLayout& aLineLayout, LineIterator aLine, nsFloatManager::SavedState* aFloatStateBeforeLine, nsFlowAreaRect& aFlowArea, nscoord& aAvailableSpaceBSize, bool* aKeepReflowGoing) { // Try to position the floats in a nowrap context. aLineLayout.FlushNoWrapFloats(); // Trim extra white-space from the line before placing the frames aLineLayout.TrimTrailingWhiteSpace(); // Vertically align the frames on this line. // // According to the CSS2 spec, section 12.6.1, the "marker" box // participates in the height calculation of the list-item box's // first line box. // // There are exactly two places a ::marker can be placed: near the // first or second line. It's only placed on the second line in a // rare case: when the first line is empty. WritingMode wm = aState.mReflowInput.GetWritingMode(); bool addedMarker = false; if (HasOutsideMarker() && ((aLine == mLines.front() && (!aLineLayout.IsZeroBSize() || (aLine == mLines.back()))) || (mLines.front() != mLines.back() && 0 == mLines.front()->BSize() && aLine == mLines.begin().next()))) { ReflowOutput metrics(aState.mReflowInput); nsIFrame* marker = GetOutsideMarker(); ReflowOutsideMarker(marker, aState, metrics, aState.mBCoord); NS_ASSERTION(!MarkerIsEmpty() || metrics.BSize(wm) == 0, "empty ::marker frame took up space"); aLineLayout.AddMarkerFrame(marker, metrics); addedMarker = true; } aLineLayout.VerticalAlignLine(); // We want to consider the floats in the current line when determining // whether the float available space is shrunk. If mLineBSize doesn't // exist, we are in the first pass trying to place the line. Calling // GetFloatAvailableSpace() like we did in BlockReflowState::AddFloat() // for UpdateBand(). // floatAvailableSpaceWithOldLineBSize is the float available space with // the old BSize, but including the floats that were added in this line. LogicalRect floatAvailableSpaceWithOldLineBSize = aState.mLineBSize.isNothing() ? aState.GetFloatAvailableSpace(aLine->BStart()).mRect : aState .GetFloatAvailableSpaceForBSize( aLine->BStart(), aState.mLineBSize.value(), nullptr) .mRect; // As we redo for floats, we can't reduce the amount of BSize we're // checking. aAvailableSpaceBSize = std::max(aAvailableSpaceBSize, aLine->BSize()); LogicalRect floatAvailableSpaceWithLineBSize = aState .GetFloatAvailableSpaceForBSize(aLine->BStart(), aAvailableSpaceBSize, nullptr) .mRect; // If the available space between the floats is smaller now that we // know the BSize, return false (and cause another pass with // LineReflowStatus::RedoMoreFloats). We ensure aAvailableSpaceBSize // never decreases, which means that we can't reduce the set of floats // we intersect, which means that the available space cannot grow. if (AvailableSpaceShrunk(wm, floatAvailableSpaceWithOldLineBSize, floatAvailableSpaceWithLineBSize, false)) { // Prepare data for redoing the line. aState.mLineBSize = Some(aLine->BSize()); // Since we want to redo the line, we update aFlowArea by using the // aFloatStateBeforeLine, which is the float manager's state before the // line is placed. LogicalRect oldFloatAvailableSpace(aFlowArea.mRect); aFlowArea = aState.GetFloatAvailableSpaceForBSize( aLine->BStart(), aAvailableSpaceBSize, aFloatStateBeforeLine); NS_ASSERTION( aFlowArea.mRect.BStart(wm) == oldFloatAvailableSpace.BStart(wm), "yikes"); // Restore the BSize to the position of the next band. aFlowArea.mRect.BSize(wm) = oldFloatAvailableSpace.BSize(wm); // Enforce both IStart() and IEnd() never move outwards to prevent // infinite grow-shrink loops. const nscoord iStartDiff = aFlowArea.mRect.IStart(wm) - oldFloatAvailableSpace.IStart(wm); const nscoord iEndDiff = aFlowArea.mRect.IEnd(wm) - oldFloatAvailableSpace.IEnd(wm); if (iStartDiff < 0) { aFlowArea.mRect.IStart(wm) -= iStartDiff; aFlowArea.mRect.ISize(wm) += iStartDiff; } if (iEndDiff > 0) { aFlowArea.mRect.ISize(wm) -= iEndDiff; } return false; } #ifdef DEBUG if (!GetParent()->IsAbsurdSizeAssertSuppressed()) { static nscoord lastHeight = 0; if (ABSURD_SIZE(aLine->BStart())) { lastHeight = aLine->BStart(); if (abs(aLine->BStart() - lastHeight) > ABSURD_COORD / 10) { nsIFrame::ListTag(stdout); printf(": line=%p y=%d line.bounds.height=%d\n", static_cast(aLine.get()), aLine->BStart(), aLine->BSize()); } } else { lastHeight = 0; } } #endif // Only block frames horizontally align their children because // inline frames "shrink-wrap" around their children (therefore // there is no extra horizontal space). const nsStyleText* styleText = StyleText(); /** * We don't care checking for IsLastLine properly if we don't care (if it * can't change the used text-align value for the line). * * In other words, isLastLine really means isLastLineAndWeCare. */ const bool isLastLine = !IsInSVGTextSubtree() && styleText->TextAlignForLastLine() != styleText->mTextAlign && (aLineLayout.GetLineEndsInBR() || IsLastLine(aState, aLine)); aLineLayout.TextAlignLine(aLine, isLastLine); // From here on, pfd->mBounds rectangles are incorrect because bidi // might have moved frames around! OverflowAreas overflowAreas; aLineLayout.RelativePositionFrames(overflowAreas); aLine->SetOverflowAreas(overflowAreas); if (addedMarker) { aLineLayout.RemoveMarkerFrame(GetOutsideMarker()); } // Inline lines do not have margins themselves; however they are // impacted by prior block margins. If this line ends up having some // height then we zero out the previous block-end margin value that was // already applied to the line's starting Y coordinate. Otherwise we // leave it be so that the previous blocks block-end margin can be // collapsed with a block that follows. nscoord newBCoord; if (!aLine->CachedIsEmpty()) { // This line has some height. Therefore the application of the // previous-bottom-margin should stick. aState.mPrevBEndMargin.Zero(); newBCoord = aLine->BEnd(); } else { // Don't let the previous-bottom-margin value affect the newBCoord // coordinate (it was applied in ReflowInlineFrames speculatively) // since the line is empty. // We already called |ShouldApplyBStartMargin|, and if we applied it // then mShouldApplyBStartMargin is set. nscoord dy = aState.mFlags.mShouldApplyBStartMargin ? -aState.mPrevBEndMargin.get() : 0; newBCoord = aState.mBCoord + dy; } if (!aState.mReflowStatus.IsFullyComplete() && ShouldAvoidBreakInside(aState.mReflowInput)) { aLine->AppendFloats(std::move(aState.mCurrentLineFloats)); SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing); return true; } // See if the line fit (our first line always does). if (mLines.front() != aLine && aState.ContentBSize() != NS_UNCONSTRAINEDSIZE && newBCoord > aState.ContentBEnd()) { NS_ASSERTION(aState.mCurrentLine == aLine, "oops"); if (ShouldAvoidBreakInside(aState.mReflowInput)) { // All our content doesn't fit, start on the next page. SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing); } else { // Push aLine and all of its children and anything else that // follows to our next-in-flow. PushTruncatedLine(aState, aLine, aKeepReflowGoing); } return true; } // Note that any early return before this update of aState.mBCoord // must either (a) return false or (b) set aKeepReflowGoing to false. // Otherwise we'll keep reflowing later lines at an incorrect // position, and we might not come back and clean up the damage later. aState.mBCoord = newBCoord; // Add the already placed current-line floats to the line aLine->AppendFloats(std::move(aState.mCurrentLineFloats)); // Any below current line floats to place? if (!aState.mBelowCurrentLineFloats.IsEmpty()) { // Reflow the below-current-line floats, which places on the line's // float list. aState.PlaceBelowCurrentLineFloats(aLine); } // When a line has floats, factor them into the overflow areas computations. if (aLine->HasFloats()) { // Union the float overflow areas (stored in aState) and the value computed // by the line layout code. OverflowAreas lineOverflowAreas = aState.mFloatOverflowAreas; lineOverflowAreas.UnionWith(aLine->GetOverflowAreas()); aLine->SetOverflowAreas(lineOverflowAreas); #ifdef NOISY_OVERFLOW_AREAS printf("%s: Line %p, InkOverflowRect=%s, ScrollableOverflowRect=%s\n", ListTag().get(), aLine.get(), ToString(aLine->InkOverflowRect()).c_str(), ToString(aLine->ScrollableOverflowRect()).c_str()); #endif } // Apply break-after clearing if necessary // This must stay in sync with |ReflowDirtyLines|. if (aLine->HasFloatClearTypeAfter()) { std::tie(aState.mBCoord, std::ignore) = aState.ClearFloats(aState.mBCoord, aLine->FloatClearTypeAfter()); } return true; } void nsBlockFrame::PushLines(BlockReflowState& aState, nsLineList::iterator aLineBefore) { // NOTE: aLineBefore is always a normal line, not an overflow line. // The following expression will assert otherwise. DebugOnly check = aLineBefore == mLines.begin(); nsLineList::iterator overBegin(aLineBefore.next()); // PushTruncatedPlaceholderLine sometimes pushes the first line. Ugh. bool firstLine = overBegin == LinesBegin(); if (overBegin != LinesEnd()) { // Remove floats in the lines from mFloats nsFrameList floats; CollectFloats(overBegin->mFirstChild, floats, true); if (floats.NotEmpty()) { #ifdef DEBUG for (nsIFrame* f : floats) { MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT), "CollectFloats should've removed that bit"); } #endif // Push the floats onto the front of the overflow out-of-flows list nsAutoOOFFrameList oofs(this); oofs.mList.InsertFrames(nullptr, nullptr, std::move(floats)); } // overflow lines can already exist in some cases, in particular, // when shrinkwrapping and we discover that the shrinkwap causes // the height of some child block to grow which creates additional // overflowing content. In such cases we must prepend the new // overflow to the existing overflow. FrameLines* overflowLines = RemoveOverflowLines(); if (!overflowLines) { // XXXldb use presshell arena! overflowLines = new FrameLines(); } if (overflowLines) { nsIFrame* lineBeforeLastFrame; if (firstLine) { lineBeforeLastFrame = nullptr; // removes all frames } else { nsIFrame* f = overBegin->mFirstChild; lineBeforeLastFrame = f ? f->GetPrevSibling() : mFrames.LastChild(); NS_ASSERTION(!f || lineBeforeLastFrame == aLineBefore->LastChild(), "unexpected line frames"); } nsFrameList pushedFrames = mFrames.TakeFramesAfter(lineBeforeLastFrame); overflowLines->mFrames.InsertFrames(nullptr, nullptr, std::move(pushedFrames)); overflowLines->mLines.splice(overflowLines->mLines.begin(), mLines, overBegin, LinesEnd()); NS_ASSERTION(!overflowLines->mLines.empty(), "should not be empty"); // this takes ownership but it won't delete it immediately so we // can keep using it. SetOverflowLines(overflowLines); // Mark all the overflow lines dirty so that they get reflowed when // they are pulled up by our next-in-flow. // XXXldb Can this get called O(N) times making the whole thing O(N^2)? for (LineIterator line = overflowLines->mLines.begin(), line_end = overflowLines->mLines.end(); line != line_end; ++line) { line->MarkDirty(); line->MarkPreviousMarginDirty(); line->SetMovedFragments(); line->SetBoundsEmpty(); if (line->HasFloats()) { line->ClearFloats(); } } } } #ifdef DEBUG VerifyOverflowSituation(); #endif } // The overflowLines property is stored as a pointer to a line list, // which must be deleted. However, the following functions all maintain // the invariant that the property is never set if the list is empty. bool nsBlockFrame::DrainOverflowLines() { #ifdef DEBUG VerifyOverflowSituation(); #endif // Steal the prev-in-flow's overflow lines and prepend them. bool didFindOverflow = false; nsBlockFrame* prevBlock = static_cast(GetPrevInFlow()); if (prevBlock) { prevBlock->ClearLineCursors(); FrameLines* overflowLines = prevBlock->RemoveOverflowLines(); if (overflowLines) { // Make all the frames on the overflow line list mine. ReparentFrames(overflowLines->mFrames, prevBlock, this); // Collect overflow containers from our OverflowContainers list that are // continuations from the frames we picked up from our prev-in-flow, then // prepend those to ExcessOverflowContainers to ensure the continuations // are ordered. if (GetOverflowContainers()) { nsFrameList ocContinuations; for (auto* f : overflowLines->mFrames) { auto* cont = f; bool done = false; while (!done && (cont = cont->GetNextContinuation()) && cont->GetParent() == this) { bool onlyChild = !cont->GetPrevSibling() && !cont->GetNextSibling(); if (cont->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER) && TryRemoveFrame(OverflowContainersProperty(), cont)) { ocContinuations.AppendFrame(nullptr, cont); done = onlyChild; continue; } break; } if (done) { break; } } if (!ocContinuations.IsEmpty()) { if (nsFrameList* eoc = GetExcessOverflowContainers()) { eoc->InsertFrames(nullptr, nullptr, std::move(ocContinuations)); } else { SetExcessOverflowContainers(std::move(ocContinuations)); } } } // Make the overflow out-of-flow frames mine too. nsAutoOOFFrameList oofs(prevBlock); if (oofs.mList.NotEmpty()) { // In case we own any next-in-flows of any of the drained frames, then // move those to the PushedFloat list. nsFrameList pushedFloats; for (nsIFrame* f : oofs.mList) { nsIFrame* nif = f->GetNextInFlow(); for (; nif && nif->GetParent() == this; nif = nif->GetNextInFlow()) { MOZ_ASSERT(nif->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)); RemoveFloat(nif); pushedFloats.AppendFrame(nullptr, nif); } } ReparentFrames(oofs.mList, prevBlock, this); mFloats.InsertFrames(nullptr, nullptr, std::move(oofs.mList)); if (!pushedFloats.IsEmpty()) { nsFrameList* pf = EnsurePushedFloats(); pf->InsertFrames(nullptr, nullptr, std::move(pushedFloats)); } } if (!mLines.empty()) { // Remember to recompute the margins on the first line. This will // also recompute the correct deltaBCoord if necessary. mLines.front()->MarkPreviousMarginDirty(); } // The overflow lines have already been marked dirty and their previous // margins marked dirty also. // Prepend the overflow frames/lines to our principal list. mFrames.InsertFrames(nullptr, nullptr, std::move(overflowLines->mFrames)); mLines.splice(mLines.begin(), overflowLines->mLines); NS_ASSERTION(overflowLines->mLines.empty(), "splice should empty list"); delete overflowLines; didFindOverflow = true; } } // Now append our own overflow lines. return DrainSelfOverflowList() || didFindOverflow; } bool nsBlockFrame::DrainSelfOverflowList() { UniquePtr ourOverflowLines(RemoveOverflowLines()); if (!ourOverflowLines) { return false; } // No need to reparent frames in our own overflow lines/oofs, because they're // already ours. But we should put overflow floats back in mFloats. // (explicit scope to remove the OOF list before VerifyOverflowSituation) { nsAutoOOFFrameList oofs(this); if (oofs.mList.NotEmpty()) { #ifdef DEBUG for (nsIFrame* f : oofs.mList) { MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT), "CollectFloats should've removed that bit"); } #endif // The overflow floats go after our regular floats. mFloats.AppendFrames(nullptr, std::move(oofs).mList); } } if (!ourOverflowLines->mLines.empty()) { mFrames.AppendFrames(nullptr, std::move(ourOverflowLines->mFrames)); mLines.splice(mLines.end(), ourOverflowLines->mLines); } #ifdef DEBUG VerifyOverflowSituation(); #endif return true; } /** * Pushed floats are floats whose placeholders are in a previous * continuation. They might themselves be next-continuations of a float * that partially fit in an earlier continuation, or they might be the * first continuation of a float that couldn't be placed at all. * * Pushed floats live permanently at the beginning of a block's float * list, where they must live *before* any floats whose placeholders are * in that block. * * Temporarily, during reflow, they also live on the pushed floats list, * which only holds them between (a) when one continuation pushes them to * its pushed floats list because they don't fit and (b) when the next * continuation pulls them onto the beginning of its float list. * * DrainPushedFloats sets up pushed floats the way we need them at the * start of reflow; they are then reflowed by ReflowPushedFloats (which * might push some of them on). Floats with placeholders in this block * are reflowed by (BlockReflowState/nsLineLayout)::AddFloat, which * also maintains these invariants. * * DrainSelfPushedFloats moves any pushed floats from this block's own * PushedFloats list back into mFloats. DrainPushedFloats additionally * moves frames from its prev-in-flow's PushedFloats list into mFloats. */ void nsBlockFrame::DrainSelfPushedFloats() { // If we're getting reflowed multiple times without our // next-continuation being reflowed, we might need to pull back floats // that we just put in the list to be pushed to our next-in-flow. // We don't want to pull back any next-in-flows of floats on our own // float list, and we only need to pull back first-in-flows whose // placeholders were in earlier blocks (since first-in-flows whose // placeholders are in this block will get pulled appropriately by // AddFloat, and will then be more likely to be in the correct order). mozilla::PresShell* presShell = PresShell(); nsFrameList* ourPushedFloats = GetPushedFloats(); if (ourPushedFloats) { // When we pull back floats, we want to put them with the pushed // floats, which must live at the start of our float list, but we // want them at the end of those pushed floats. // FIXME: This isn't quite right! What if they're all pushed floats? nsIFrame* insertionPrevSibling = nullptr; /* beginning of list */ for (nsIFrame* f = mFloats.FirstChild(); f && f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT); f = f->GetNextSibling()) { insertionPrevSibling = f; } nsIFrame* f = ourPushedFloats->LastChild(); while (f) { nsIFrame* prevSibling = f->GetPrevSibling(); nsPlaceholderFrame* placeholder = f->GetPlaceholderFrame(); nsIFrame* floatOriginalParent = presShell->FrameConstructor()->GetFloatContainingBlock(placeholder); if (floatOriginalParent != this) { // This is a first continuation that was pushed from one of our // previous continuations. Take it out of the pushed floats // list and put it in our floats list, before any of our // floats, but after other pushed floats. ourPushedFloats->RemoveFrame(f); mFloats.InsertFrame(nullptr, insertionPrevSibling, f); } f = prevSibling; } if (ourPushedFloats->IsEmpty()) { RemovePushedFloats()->Delete(presShell); } } } void nsBlockFrame::DrainPushedFloats() { DrainSelfPushedFloats(); // After our prev-in-flow has completed reflow, it may have a pushed // floats list, containing floats that we need to own. Take these. nsBlockFrame* prevBlock = static_cast(GetPrevInFlow()); if (prevBlock) { AutoFrameListPtr list(PresContext(), prevBlock->RemovePushedFloats()); if (list && list->NotEmpty()) { mFloats.InsertFrames(this, nullptr, std::move(*list)); } } } nsBlockFrame::FrameLines* nsBlockFrame::GetOverflowLines() const { if (!HasOverflowLines()) { return nullptr; } FrameLines* prop = GetProperty(OverflowLinesProperty()); NS_ASSERTION( prop && !prop->mLines.empty() && prop->mLines.front()->GetChildCount() == 0 ? prop->mFrames.IsEmpty() : prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(), "value should always be stored and non-empty when state set"); return prop; } nsBlockFrame::FrameLines* nsBlockFrame::RemoveOverflowLines() { if (!HasOverflowLines()) { return nullptr; } FrameLines* prop = TakeProperty(OverflowLinesProperty()); NS_ASSERTION( prop && !prop->mLines.empty() && prop->mLines.front()->GetChildCount() == 0 ? prop->mFrames.IsEmpty() : prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(), "value should always be stored and non-empty when state set"); RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES); return prop; } void nsBlockFrame::DestroyOverflowLines() { NS_ASSERTION(HasOverflowLines(), "huh?"); FrameLines* prop = TakeProperty(OverflowLinesProperty()); NS_ASSERTION(prop && prop->mLines.empty(), "value should always be stored but empty when destroying"); RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES); delete prop; } // This takes ownership of aOverflowLines. // XXX We should allocate overflowLines from presShell arena! void nsBlockFrame::SetOverflowLines(FrameLines* aOverflowLines) { NS_ASSERTION(aOverflowLines, "null lines"); NS_ASSERTION(!aOverflowLines->mLines.empty(), "empty lines"); NS_ASSERTION(aOverflowLines->mLines.front()->mFirstChild == aOverflowLines->mFrames.FirstChild(), "invalid overflow lines / frames"); NS_ASSERTION(!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_LINES), "Overwriting existing overflow lines"); // Verify that we won't overwrite an existing overflow list NS_ASSERTION(!GetProperty(OverflowLinesProperty()), "existing overflow list"); SetProperty(OverflowLinesProperty(), aOverflowLines); AddStateBits(NS_BLOCK_HAS_OVERFLOW_LINES); } nsFrameList* nsBlockFrame::GetOverflowOutOfFlows() const { if (!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) { return nullptr; } nsFrameList* result = GetProperty(OverflowOutOfFlowsProperty()); NS_ASSERTION(result, "value should always be non-empty when state set"); return result; } void nsBlockFrame::SetOverflowOutOfFlows(nsFrameList&& aList, nsFrameList* aPropValue) { MOZ_ASSERT( HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS) == !!aPropValue, "state does not match value"); if (aList.IsEmpty()) { if (!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) { return; } nsFrameList* list = TakeProperty(OverflowOutOfFlowsProperty()); NS_ASSERTION(aPropValue == list, "prop value mismatch"); list->Clear(); list->Delete(PresShell()); RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS); } else if (HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) { NS_ASSERTION(aPropValue == GetProperty(OverflowOutOfFlowsProperty()), "prop value mismatch"); *aPropValue = std::move(aList); } else { SetProperty(OverflowOutOfFlowsProperty(), new (PresShell()) nsFrameList(std::move(aList))); AddStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS); } } nsIFrame* nsBlockFrame::GetInsideMarker() const { if (!HasInsideMarker()) { return nullptr; } NS_ASSERTION(!HasOutsideMarker(), "invalid marker state"); nsIFrame* frame = GetProperty(InsideMarkerProperty()); NS_ASSERTION(frame, "bogus inside ::marker frame"); return frame; } nsIFrame* nsBlockFrame::GetOutsideMarker() const { nsFrameList* list = GetOutsideMarkerList(); return list ? list->FirstChild() : nullptr; } nsFrameList* nsBlockFrame::GetOutsideMarkerList() const { if (!HasOutsideMarker()) { return nullptr; } NS_ASSERTION(!HasInsideMarker(), "invalid marker state"); nsFrameList* list = GetProperty(OutsideMarkerProperty()); NS_ASSERTION(list && list->GetLength() == 1, "bogus outside ::marker list"); return list; } nsFrameList* nsBlockFrame::GetPushedFloats() const { if (!HasPushedFloats()) { return nullptr; } nsFrameList* result = GetProperty(PushedFloatProperty()); NS_ASSERTION(result, "value should always be non-empty when state set"); return result; } nsFrameList* nsBlockFrame::EnsurePushedFloats() { nsFrameList* result = GetPushedFloats(); if (result) { return result; } result = new (PresShell()) nsFrameList; SetProperty(PushedFloatProperty(), result); AddStateBits(NS_BLOCK_HAS_PUSHED_FLOATS); return result; } nsFrameList* nsBlockFrame::RemovePushedFloats() { if (!HasPushedFloats()) { return nullptr; } nsFrameList* result = TakeProperty(PushedFloatProperty()); RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS); NS_ASSERTION(result, "value should always be non-empty when state set"); return result; } ////////////////////////////////////////////////////////////////////// // Frame list manipulation routines void nsBlockFrame::AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) { if (aFrameList.IsEmpty()) { return; } if (aListID != FrameChildListID::Principal) { if (FrameChildListID::Float == aListID) { DrainSelfPushedFloats(); // ensure the last frame is in mFloats mFloats.AppendFrames(nullptr, std::move(aFrameList)); return; } MOZ_ASSERT(FrameChildListID::NoReflowPrincipal == aListID, "unexpected child list"); } // Find the proper last-child for where the append should go nsIFrame* lastKid = mFrames.LastChild(); NS_ASSERTION( (mLines.empty() ? nullptr : mLines.back()->LastChild()) == lastKid, "out-of-sync mLines / mFrames"); #ifdef NOISY_REFLOW_REASON ListTag(stdout); printf(": append "); for (nsIFrame* frame : aFrameList) { frame->ListTag(stdout); } if (lastKid) { printf(" after "); lastKid->ListTag(stdout); } printf("\n"); #endif if (IsInSVGTextSubtree()) { MOZ_ASSERT(GetParent()->IsSVGTextFrame(), "unexpected block frame in SVG text"); // Workaround for bug 1399425 in case this bit has been removed from the // SVGTextFrame just before the parser adds more descendant nodes. GetParent()->AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY); } AddFrames(std::move(aFrameList), lastKid, nullptr); if (aListID != FrameChildListID::NoReflowPrincipal) { PresShell()->FrameNeedsReflow( this, IntrinsicDirty::FrameAndAncestors, NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient? } } void nsBlockFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) { NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, "inserting after sibling frame with different parent"); if (aListID != FrameChildListID::Principal) { if (FrameChildListID::Float == aListID) { DrainSelfPushedFloats(); // ensure aPrevFrame is in mFloats mFloats.InsertFrames(this, aPrevFrame, std::move(aFrameList)); return; } MOZ_ASSERT(FrameChildListID::NoReflowPrincipal == aListID, "unexpected child list"); } #ifdef NOISY_REFLOW_REASON ListTag(stdout); printf(": insert "); for (nsIFrame* frame : aFrameList) { frame->ListTag(stdout); } if (aPrevFrame) { printf(" after "); aPrevFrame->ListTag(stdout); } printf("\n"); #endif AddFrames(std::move(aFrameList), aPrevFrame, aPrevFrameLine); if (aListID != FrameChildListID::NoReflowPrincipal) { PresShell()->FrameNeedsReflow( this, IntrinsicDirty::FrameAndAncestors, NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient? } } void nsBlockFrame::RemoveFrame(DestroyContext& aContext, ChildListID aListID, nsIFrame* aOldFrame) { #ifdef NOISY_REFLOW_REASON ListTag(stdout); printf(": remove "); aOldFrame->ListTag(stdout); printf("\n"); #endif if (aListID == FrameChildListID::Principal) { bool hasFloats = BlockHasAnyFloats(aOldFrame); DoRemoveFrame(aContext, aOldFrame, REMOVE_FIXED_CONTINUATIONS); if (hasFloats) { MarkSameFloatManagerLinesDirty(this); } } else if (FrameChildListID::Float == aListID) { // Make sure to mark affected lines dirty for the float frame // we are removing; this way is a bit messy, but so is the rest of the code. // See bug 390762. NS_ASSERTION(!aOldFrame->GetPrevContinuation(), "RemoveFrame should not be called on pushed floats."); for (nsIFrame* f = aOldFrame; f && !f->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); f = f->GetNextContinuation()) { MarkSameFloatManagerLinesDirty( static_cast(f->GetParent())); } DoRemoveOutOfFlowFrame(aContext, aOldFrame); } else if (FrameChildListID::NoReflowPrincipal == aListID) { // Skip the call to |FrameNeedsReflow| below by returning now. DoRemoveFrame(aContext, aOldFrame, REMOVE_FIXED_CONTINUATIONS); return; } else { MOZ_CRASH("unexpected child list"); } PresShell()->FrameNeedsReflow( this, IntrinsicDirty::FrameAndAncestors, NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient? } static bool ShouldPutNextSiblingOnNewLine(nsIFrame* aLastFrame) { LayoutFrameType type = aLastFrame->Type(); if (type == LayoutFrameType::Br) { return true; } // XXX the TEXT_OFFSETS_NEED_FIXING check is a wallpaper for bug 822910. if (type == LayoutFrameType::Text && !aLastFrame->HasAnyStateBits(TEXT_OFFSETS_NEED_FIXING)) { return aLastFrame->HasSignificantTerminalNewline(); } return false; } void nsBlockFrame::AddFrames(nsFrameList&& aFrameList, nsIFrame* aPrevSibling, const nsLineList::iterator* aPrevSiblingLine) { // Clear our line cursor, since our lines may change. ClearLineCursors(); if (aFrameList.IsEmpty()) { return; } // Attempt to find the line that contains the previous sibling nsLineList* lineList = &mLines; nsFrameList* frames = &mFrames; nsLineList::iterator prevSibLine; int32_t prevSiblingIndex; if (aPrevSiblingLine) { MOZ_ASSERT(aPrevSibling); prevSibLine = *aPrevSiblingLine; FrameLines* overflowLines = GetOverflowLines(); MOZ_ASSERT(prevSibLine.IsInSameList(mLines.begin()) || (overflowLines && prevSibLine.IsInSameList(overflowLines->mLines.begin())), "must be one of our line lists"); if (overflowLines) { // We need to find out which list it's actually in. Assume that // *if* we have overflow lines, that our primary lines aren't // huge, but our overflow lines might be. nsLineList::iterator line = mLines.begin(), lineEnd = mLines.end(); while (line != lineEnd) { if (line == prevSibLine) { break; } ++line; } if (line == lineEnd) { // By elimination, the line must be in our overflow lines. lineList = &overflowLines->mLines; frames = &overflowLines->mFrames; } } nsLineList::iterator nextLine = prevSibLine.next(); nsIFrame* lastFrameInLine = nextLine == lineList->end() ? frames->LastChild() : nextLine->mFirstChild->GetPrevSibling(); prevSiblingIndex = prevSibLine->RIndexOf(aPrevSibling, lastFrameInLine); MOZ_ASSERT(prevSiblingIndex >= 0, "aPrevSibling must be in aPrevSiblingLine"); } else { prevSibLine = lineList->end(); prevSiblingIndex = -1; if (aPrevSibling) { // XXX_perf This is technically O(N^2) in some cases, but by using // RFind instead of Find, we make it O(N) in the most common case, // which is appending content. // Find the line that contains the previous sibling if (!nsLineBox::RFindLineContaining(aPrevSibling, lineList->begin(), prevSibLine, mFrames.LastChild(), &prevSiblingIndex)) { // Not in mLines - try overflow lines. FrameLines* overflowLines = GetOverflowLines(); bool found = false; if (overflowLines) { prevSibLine = overflowLines->mLines.end(); prevSiblingIndex = -1; found = nsLineBox::RFindLineContaining( aPrevSibling, overflowLines->mLines.begin(), prevSibLine, overflowLines->mFrames.LastChild(), &prevSiblingIndex); } if (MOZ_LIKELY(found)) { lineList = &overflowLines->mLines; frames = &overflowLines->mFrames; } else { // Note: defensive code! RFindLineContaining must not return // false in this case, so if it does... MOZ_ASSERT_UNREACHABLE("prev sibling not in line list"); aPrevSibling = nullptr; prevSibLine = lineList->end(); } } } } // Find the frame following aPrevSibling so that we can join up the // two lists of frames. if (aPrevSibling) { // Split line containing aPrevSibling in two if the insertion // point is somewhere in the middle of the line. int32_t rem = prevSibLine->GetChildCount() - prevSiblingIndex - 1; if (rem) { // Split the line in two where the frame(s) are being inserted. nsLineBox* line = NewLineBox(prevSibLine, aPrevSibling->GetNextSibling(), rem); lineList->after_insert(prevSibLine, line); // Mark prevSibLine dirty and as needing textrun invalidation, since // we may be breaking up text in the line. Its previous line may also // need to be invalidated because it may be able to pull some text up. MarkLineDirty(prevSibLine, lineList); // The new line will also need its textruns recomputed because of the // frame changes. line->MarkDirty(); line->SetInvalidateTextRuns(true); } } else if (!lineList->empty()) { lineList->front()->MarkDirty(); lineList->front()->SetInvalidateTextRuns(true); } const nsFrameList::Slice& newFrames = frames->InsertFrames(nullptr, aPrevSibling, std::move(aFrameList)); // Walk through the new frames being added and update the line data // structures to fit. for (nsIFrame* newFrame : newFrames) { NS_ASSERTION(!aPrevSibling || aPrevSibling->GetNextSibling() == newFrame, "Unexpected aPrevSibling"); NS_ASSERTION( !newFrame->IsPlaceholderFrame() || (!newFrame->IsAbsolutelyPositioned() && !newFrame->IsFloating()), "Placeholders should not float or be positioned"); bool isBlock = newFrame->IsBlockOutside(); // If the frame is a block frame, or if there is no previous line or if the // previous line is a block line we need to make a new line. We also make // a new line, as an optimization, in the two cases we know we'll need it: // if the previous line ended with a
    , or if it has significant // whitespace and ended in a newline. if (isBlock || prevSibLine == lineList->end() || prevSibLine->IsBlock() || (aPrevSibling && ShouldPutNextSiblingOnNewLine(aPrevSibling))) { // Create a new line for the frame and add its line to the line // list. nsLineBox* line = NewLineBox(newFrame, isBlock); if (prevSibLine != lineList->end()) { // Append new line after prevSibLine lineList->after_insert(prevSibLine, line); ++prevSibLine; } else { // New line is going before the other lines lineList->push_front(line); prevSibLine = lineList->begin(); } } else { prevSibLine->NoteFrameAdded(newFrame); // We're adding inline content to prevSibLine, so we need to mark it // dirty, ensure its textruns are recomputed, and possibly do the same // to its previous line since that line may be able to pull content up. MarkLineDirty(prevSibLine, lineList); } aPrevSibling = newFrame; } #ifdef DEBUG MOZ_ASSERT(aFrameList.IsEmpty()); VerifyLines(true); #endif } nsContainerFrame* nsBlockFrame::GetRubyContentPseudoFrame() { auto* firstChild = PrincipalChildList().FirstChild(); if (firstChild && firstChild->IsRubyFrame() && firstChild->Style()->GetPseudoType() == PseudoStyleType::blockRubyContent) { return static_cast(firstChild); } return nullptr; } nsContainerFrame* nsBlockFrame::GetContentInsertionFrame() { // 'display:block ruby' use the inner (Ruby) frame for insertions. if (auto* rubyContentPseudoFrame = GetRubyContentPseudoFrame()) { return rubyContentPseudoFrame; } return this; } void nsBlockFrame::AppendDirectlyOwnedAnonBoxes( nsTArray& aResult) { if (auto* rubyContentPseudoFrame = GetRubyContentPseudoFrame()) { aResult.AppendElement(OwnedAnonBox(rubyContentPseudoFrame)); } } void nsBlockFrame::RemoveFloatFromFloatCache(nsIFrame* aFloat) { // Find which line contains the float, so we can update // the float cache. for (auto& line : Lines()) { if (line.IsInline() && line.RemoveFloat(aFloat)) { break; } } } void nsBlockFrame::RemoveFloat(nsIFrame* aFloat) { #ifdef DEBUG // Floats live in mFloats, or in the PushedFloat or OverflowOutOfFlows // frame list properties. if (!mFloats.ContainsFrame(aFloat)) { MOZ_ASSERT( (GetOverflowOutOfFlows() && GetOverflowOutOfFlows()->ContainsFrame(aFloat)) || (GetPushedFloats() && GetPushedFloats()->ContainsFrame(aFloat)), "aFloat is not our child or on an unexpected frame list"); } #endif if (mFloats.StartRemoveFrame(aFloat)) { return; } nsFrameList* list = GetPushedFloats(); if (list && list->ContinueRemoveFrame(aFloat)) { #if 0 // XXXmats not yet - need to investigate BlockReflowState::mPushedFloats // first so we don't leave it pointing to a deleted list. if (list->IsEmpty()) { delete RemovePushedFloats(); } #endif return; } { nsAutoOOFFrameList oofs(this); if (oofs.mList.ContinueRemoveFrame(aFloat)) { return; } } } void nsBlockFrame::DoRemoveOutOfFlowFrame(DestroyContext& aContext, nsIFrame* aFrame) { // The containing block is always the parent of aFrame. nsBlockFrame* block = (nsBlockFrame*)aFrame->GetParent(); // Remove aFrame from the appropriate list. if (aFrame->IsAbsolutelyPositioned()) { // This also deletes the next-in-flows block->GetAbsoluteContainingBlock()->RemoveFrame( aContext, FrameChildListID::Absolute, aFrame); } else { // First remove aFrame's next-in-flows. if (nsIFrame* nif = aFrame->GetNextInFlow()) { nif->GetParent()->DeleteNextInFlowChild(aContext, nif, false); } // Now remove aFrame from its child list and Destroy it. block->RemoveFloatFromFloatCache(aFrame); block->RemoveFloat(aFrame); aFrame->Destroy(aContext); } } /** * This helps us iterate over the list of all normal + overflow lines */ void nsBlockFrame::TryAllLines(nsLineList::iterator* aIterator, nsLineList::iterator* aStartIterator, nsLineList::iterator* aEndIterator, bool* aInOverflowLines, FrameLines** aOverflowLines) { if (*aIterator == *aEndIterator) { if (!*aInOverflowLines) { // Try the overflow lines *aInOverflowLines = true; FrameLines* lines = GetOverflowLines(); if (lines) { *aStartIterator = lines->mLines.begin(); *aIterator = *aStartIterator; *aEndIterator = lines->mLines.end(); *aOverflowLines = lines; } } } } nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame, LineIterator aLine) : mFrame(aFrame), mLine(aLine), mLineList(&aFrame->mLines) { // This will assert if aLine isn't in mLines of aFrame: DebugOnly check = aLine == mFrame->LinesBegin(); } nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame, LineIterator aLine, bool aInOverflow) : mFrame(aFrame), mLine(aLine), mLineList(aInOverflow ? &aFrame->GetOverflowLines()->mLines : &aFrame->mLines) {} nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame, bool* aFoundValidLine) : mFrame(aFrame), mLineList(&aFrame->mLines) { mLine = aFrame->LinesBegin(); *aFoundValidLine = FindValidLine(); } static bool AnonymousBoxIsBFC(const ComputedStyle* aStyle) { switch (aStyle->GetPseudoType()) { case PseudoStyleType::fieldsetContent: case PseudoStyleType::columnContent: case PseudoStyleType::buttonContent: case PseudoStyleType::cellContent: case PseudoStyleType::scrolledContent: case PseudoStyleType::anonymousItem: return true; default: return false; } } static bool StyleEstablishesBFC(const ComputedStyle* aStyle) { // paint/layout containment boxes and multi-column containers establish an // independent formatting context. // https://drafts.csswg.org/css-contain/#containment-paint // https://drafts.csswg.org/css-contain/#containment-layout // https://drafts.csswg.org/css-align/#distribution-block // https://drafts.csswg.org/css-multicol/#columns const auto* disp = aStyle->StyleDisplay(); return disp->IsContainPaint() || disp->IsContainLayout() || disp->DisplayInside() == StyleDisplayInside::FlowRoot || disp->IsAbsolutelyPositionedStyle() || disp->IsFloatingStyle() || aStyle->StylePosition()->mAlignContent.primary != StyleAlignFlags::NORMAL || aStyle->IsRootElementStyle() || AnonymousBoxIsBFC(aStyle); } static bool EstablishesBFC(const nsBlockFrame* aFrame) { if (aFrame->HasAnyClassFlag(LayoutFrameClassFlags::BlockFormattingContext)) { return true; } if (nsIFrame* parent = aFrame->GetParent()) { if (parent->IsFieldSetFrame()) { // A rendered legend always establishes a new formatting context, and so // does the fieldset content frame, so we can just return true here. // https://html.spec.whatwg.org/#rendered-legend return true; } const auto wm = aFrame->GetWritingMode(); const auto parentWM = parent->GetWritingMode(); if (wm.GetBlockDir() != parentWM.GetBlockDir() || wm.IsVerticalSideways() != parentWM.IsVerticalSideways()) { // If a box has a different writing-mode value than its containing block // [...] if the box is a block container, then it establishes a new block // formatting context. // https://drafts.csswg.org/css-writing-modes/#block-flow return true; } } if (aFrame->IsColumnSpan()) { return true; } if (aFrame->IsSuppressedScrollableBlockForPrint()) { return true; } const auto* style = aFrame->Style(); if (style->GetPseudoType() == PseudoStyleType::marker) { if (aFrame->GetParent() && aFrame->GetParent()->StyleList()->mListStylePosition == StyleListStylePosition::Outside) { // An outside ::marker needs to be an independent formatting context // to avoid being influenced by the float manager etc. return true; } } return StyleEstablishesBFC(style); } void nsBlockFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) { nsContainerFrame::DidSetComputedStyle(aOldStyle); if (!aOldStyle) { return; } const bool isBFC = EstablishesBFC(this); if (HasAnyStateBits(NS_BLOCK_BFC) != isBFC) { if (MaybeHasFloats()) { // If the frame contains floats, this update may change their float // manager. Be safe by dirtying all descendant lines of the nearest // ancestor's float manager. RemoveStateBits(NS_BLOCK_BFC); MarkSameFloatManagerLinesDirty(this); } AddOrRemoveStateBits(NS_BLOCK_BFC, isBFC); } } void nsBlockFrame::UpdateFirstLetterStyle(ServoRestyleState& aRestyleState) { nsIFrame* letterFrame = GetFirstLetter(); if (!letterFrame) { return; } // Figure out what the right style parent is. This needs to match // nsCSSFrameConstructor::CreateLetterFrame. nsIFrame* inFlowFrame = letterFrame; if (inFlowFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { inFlowFrame = inFlowFrame->GetPlaceholderFrame(); } nsIFrame* styleParent = CorrectStyleParentFrame(inFlowFrame->GetParent(), PseudoStyleType::firstLetter); ComputedStyle* parentStyle = styleParent->Style(); RefPtr firstLetterStyle = aRestyleState.StyleSet().ResolvePseudoElementStyle( *mContent->AsElement(), PseudoStyleType::firstLetter, nullptr, parentStyle); // Note that we don't need to worry about changehints for the continuation // styles: those will be handled by the styleParent already. RefPtr continuationStyle = aRestyleState.StyleSet().ResolveStyleForFirstLetterContinuation( parentStyle); UpdateStyleOfOwnedChildFrame(letterFrame, firstLetterStyle, aRestyleState, Some(continuationStyle.get())); // We also want to update the style on the textframe inside the first-letter. // We don't need to compute a changehint for this, though, since any changes // to it are handled by the first-letter anyway. nsIFrame* textFrame = letterFrame->PrincipalChildList().FirstChild(); RefPtr firstTextStyle = aRestyleState.StyleSet().ResolveStyleForText(textFrame->GetContent(), firstLetterStyle); textFrame->SetComputedStyle(firstTextStyle); // We don't need to update style for textFrame's continuations: it's already // set up to inherit from parentStyle, which is what we want. } static nsIFrame* FindChildContaining(nsBlockFrame* aFrame, nsIFrame* aFindFrame) { NS_ASSERTION(aFrame, "must have frame"); nsIFrame* child; while (true) { nsIFrame* block = aFrame; do { child = nsLayoutUtils::FindChildContainingDescendant(block, aFindFrame); if (child) { break; } block = block->GetNextContinuation(); } while (block); if (!child) { return nullptr; } if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { break; } aFindFrame = child->GetPlaceholderFrame(); } return child; } nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame, nsIFrame* aFindFrame, bool* aFoundValidLine) : mFrame(aFrame), mLineList(&aFrame->mLines) { *aFoundValidLine = false; nsIFrame* child = FindChildContaining(aFrame, aFindFrame); if (!child) { return; } LineIterator line_end = aFrame->LinesEnd(); mLine = aFrame->LinesBegin(); if (mLine != line_end && mLine.next() == line_end && !aFrame->HasOverflowLines()) { // The block has a single line - that must be it! *aFoundValidLine = true; return; } // Try to use the cursor if it exists, otherwise fall back to the first line if (nsLineBox* const cursor = aFrame->GetLineCursorForQuery()) { mLine = line_end; // Perform a simultaneous forward and reverse search starting from the // line cursor. nsBlockFrame::LineIterator line = aFrame->LinesBeginFrom(cursor); nsBlockFrame::ReverseLineIterator rline = aFrame->LinesRBeginFrom(cursor); nsBlockFrame::ReverseLineIterator rline_end = aFrame->LinesREnd(); // rline is positioned on the line containing 'cursor', so it's not // rline_end. So we can safely increment it (i.e. move it to one line // earlier) to start searching there. ++rline; while (line != line_end || rline != rline_end) { if (line != line_end) { if (line->Contains(child)) { mLine = line; break; } ++line; } if (rline != rline_end) { if (rline->Contains(child)) { mLine = rline; break; } ++rline; } } if (mLine != line_end) { *aFoundValidLine = true; if (mLine != cursor) { aFrame->SetProperty(nsBlockFrame::LineCursorPropertyQuery(), mLine); } return; } } else { for (mLine = aFrame->LinesBegin(); mLine != line_end; ++mLine) { if (mLine->Contains(child)) { *aFoundValidLine = true; return; } } } // Didn't find the line MOZ_ASSERT(mLine == line_end, "mLine should be line_end at this point"); // If we reach here, it means that we have not been able to find the // desired frame in our in-flow lines. So we should start looking at // our overflow lines. In order to do that, we set mLine to the end // iterator so that FindValidLine starts to look at overflow lines, // if any. if (!FindValidLine()) { return; } do { if (mLine->Contains(child)) { *aFoundValidLine = true; return; } } while (Next()); } nsBlockFrame::LineIterator nsBlockInFlowLineIterator::End() { return mLineList->end(); } bool nsBlockInFlowLineIterator::IsLastLineInList() { LineIterator end = End(); return mLine != end && mLine.next() == end; } bool nsBlockInFlowLineIterator::Next() { ++mLine; return FindValidLine(); } bool nsBlockInFlowLineIterator::Prev() { LineIterator begin = mLineList->begin(); if (mLine != begin) { --mLine; return true; } bool currentlyInOverflowLines = GetInOverflow(); while (true) { if (currentlyInOverflowLines) { mLineList = &mFrame->mLines; mLine = mLineList->end(); if (mLine != mLineList->begin()) { --mLine; return true; } } else { mFrame = static_cast(mFrame->GetPrevInFlow()); if (!mFrame) { return false; } nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines(); if (overflowLines) { mLineList = &overflowLines->mLines; mLine = mLineList->end(); NS_ASSERTION(mLine != mLineList->begin(), "empty overflow line list?"); --mLine; return true; } } currentlyInOverflowLines = !currentlyInOverflowLines; } } bool nsBlockInFlowLineIterator::FindValidLine() { LineIterator end = mLineList->end(); if (mLine != end) { return true; } bool currentlyInOverflowLines = GetInOverflow(); while (true) { if (currentlyInOverflowLines) { mFrame = static_cast(mFrame->GetNextInFlow()); if (!mFrame) { return false; } mLineList = &mFrame->mLines; mLine = mLineList->begin(); if (mLine != mLineList->end()) { return true; } } else { nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines(); if (overflowLines) { mLineList = &overflowLines->mLines; mLine = mLineList->begin(); NS_ASSERTION(mLine != mLineList->end(), "empty overflow line list?"); return true; } } currentlyInOverflowLines = !currentlyInOverflowLines; } } // This function removes aDeletedFrame and all its continuations. It // is optimized for deleting a whole series of frames. The easy // implementation would invoke itself recursively on // aDeletedFrame->GetNextContinuation, then locate the line containing // aDeletedFrame and remove aDeletedFrame from that line. But here we // start by locating aDeletedFrame and then scanning from that point // on looking for continuations. void nsBlockFrame::DoRemoveFrame(DestroyContext& aContext, nsIFrame* aDeletedFrame, uint32_t aFlags) { // Clear our line cursor, since our lines may change. ClearLineCursors(); if (aDeletedFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW | NS_FRAME_IS_OVERFLOW_CONTAINER)) { if (!aDeletedFrame->GetPrevInFlow()) { NS_ASSERTION(aDeletedFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW), "Expected out-of-flow frame"); DoRemoveOutOfFlowFrame(aContext, aDeletedFrame); } else { // FIXME(emilio): aContext is lost here, maybe it's not a big deal? nsContainerFrame::DeleteNextInFlowChild(aContext, aDeletedFrame, (aFlags & FRAMES_ARE_EMPTY) != 0); } return; } // Find the line that contains deletedFrame nsLineList::iterator line_start = mLines.begin(), line_end = mLines.end(); nsLineList::iterator line = line_start; FrameLines* overflowLines = nullptr; bool searchingOverflowList = false; // Make sure we look in the overflow lines even if the normal line // list is empty TryAllLines(&line, &line_start, &line_end, &searchingOverflowList, &overflowLines); while (line != line_end) { if (line->Contains(aDeletedFrame)) { break; } ++line; TryAllLines(&line, &line_start, &line_end, &searchingOverflowList, &overflowLines); } if (line == line_end) { NS_ERROR("can't find deleted frame in lines"); return; } if (!(aFlags & FRAMES_ARE_EMPTY)) { if (line != line_start) { line.prev()->MarkDirty(); line.prev()->SetInvalidateTextRuns(true); } else if (searchingOverflowList && !mLines.empty()) { mLines.back()->MarkDirty(); mLines.back()->SetInvalidateTextRuns(true); } } while (line != line_end && aDeletedFrame) { MOZ_ASSERT(this == aDeletedFrame->GetParent(), "messed up delete code"); MOZ_ASSERT(line->Contains(aDeletedFrame), "frame not in line"); if (!(aFlags & FRAMES_ARE_EMPTY)) { line->MarkDirty(); line->SetInvalidateTextRuns(true); } // If the frame being deleted is the last one on the line then // optimize away the line->Contains(next-in-flow) call below. bool isLastFrameOnLine = 1 == line->GetChildCount(); if (!isLastFrameOnLine) { LineIterator next = line.next(); nsIFrame* lastFrame = next != line_end ? next->mFirstChild->GetPrevSibling() : (searchingOverflowList ? overflowLines->mFrames.LastChild() : mFrames.LastChild()); NS_ASSERTION(next == line_end || lastFrame == line->LastChild(), "unexpected line frames"); isLastFrameOnLine = lastFrame == aDeletedFrame; } // Remove aDeletedFrame from the line if (line->mFirstChild == aDeletedFrame) { // We should be setting this to null if aDeletedFrame // is the only frame on the line. HOWEVER in that case // we will be removing the line anyway, see below. line->mFirstChild = aDeletedFrame->GetNextSibling(); } // Hmm, this won't do anything if we're removing a frame in the first // overflow line... Hopefully doesn't matter --line; if (line != line_end && !line->IsBlock()) { // Since we just removed a frame that follows some inline // frames, we need to reflow the previous line. line->MarkDirty(); } ++line; // Take aDeletedFrame out of the sibling list. Note that // prevSibling will only be nullptr when we are deleting the very // first frame in the main or overflow list. if (searchingOverflowList) { overflowLines->mFrames.RemoveFrame(aDeletedFrame); } else { mFrames.RemoveFrame(aDeletedFrame); } // Update the child count of the line to be accurate line->NoteFrameRemoved(aDeletedFrame); // Destroy frame; capture its next continuation first in case we need // to destroy that too. nsIFrame* deletedNextContinuation = (aFlags & REMOVE_FIXED_CONTINUATIONS) ? aDeletedFrame->GetNextContinuation() : aDeletedFrame->GetNextInFlow(); #ifdef NOISY_REMOVE_FRAME printf("DoRemoveFrame: %s line=%p frame=", searchingOverflowList ? "overflow" : "normal", line.get()); aDeletedFrame->ListTag(stdout); printf(" prevSibling=%p deletedNextContinuation=%p\n", aDeletedFrame->GetPrevSibling(), deletedNextContinuation); #endif // If next-in-flow is an overflow container, must remove it first. // FIXME: Can we do this unconditionally? if (deletedNextContinuation && deletedNextContinuation->HasAnyStateBits( NS_FRAME_IS_OVERFLOW_CONTAINER)) { deletedNextContinuation->GetParent()->DeleteNextInFlowChild( aContext, deletedNextContinuation, false); deletedNextContinuation = nullptr; } aDeletedFrame->Destroy(aContext); aDeletedFrame = deletedNextContinuation; bool haveAdvancedToNextLine = false; // If line is empty, remove it now. if (0 == line->GetChildCount()) { #ifdef NOISY_REMOVE_FRAME printf("DoRemoveFrame: %s line=%p became empty so it will be removed\n", searchingOverflowList ? "overflow" : "normal", line.get()); #endif nsLineBox* cur = line; if (!searchingOverflowList) { line = mLines.erase(line); // Invalidate the space taken up by the line. // XXX We need to do this if we're removing a frame as a result of // a call to RemoveFrame(), but we may not need to do this in all // cases... #ifdef NOISY_BLOCK_INVALIDATE nsRect inkOverflow(cur->InkOverflowRect()); printf("%p invalidate 10 (%d, %d, %d, %d)\n", this, inkOverflow.x, inkOverflow.y, inkOverflow.width, inkOverflow.height); #endif } else { line = overflowLines->mLines.erase(line); if (overflowLines->mLines.empty()) { DestroyOverflowLines(); overflowLines = nullptr; // We just invalidated our iterators. Since we were in // the overflow lines list, which is now empty, set them // so we're at the end of the regular line list. line_start = mLines.begin(); line_end = mLines.end(); line = line_end; } } FreeLineBox(cur); // If we're removing a line, ReflowDirtyLines isn't going to // know that it needs to slide lines unless something is marked // dirty. So mark the previous margin of the next line dirty if // there is one. if (line != line_end) { line->MarkPreviousMarginDirty(); } haveAdvancedToNextLine = true; } else { // Make the line that just lost a frame dirty, and advance to // the next line. if (!deletedNextContinuation || isLastFrameOnLine || !line->Contains(deletedNextContinuation)) { line->MarkDirty(); ++line; haveAdvancedToNextLine = true; } } if (deletedNextContinuation) { // See if we should keep looking in the current flow's line list. if (deletedNextContinuation->GetParent() != this) { // The deceased frames continuation is not a child of the // current block. So break out of the loop so that we advance // to the next parent. // // If we have a continuation in a different block then all bets are // off regarding whether we are deleting frames without actual content, // so don't propagate FRAMES_ARE_EMPTY any further. aFlags &= ~FRAMES_ARE_EMPTY; break; } // If we advanced to the next line then check if we should switch to the // overflow line list. if (haveAdvancedToNextLine) { if (line != line_end && !searchingOverflowList && !line->Contains(deletedNextContinuation)) { // We have advanced to the next *normal* line but the next-in-flow // is not there - force a switch to the overflow line list. line = line_end; } TryAllLines(&line, &line_start, &line_end, &searchingOverflowList, &overflowLines); #ifdef NOISY_REMOVE_FRAME printf("DoRemoveFrame: now on %s line=%p\n", searchingOverflowList ? "overflow" : "normal", line.get()); #endif } } } if (!(aFlags & FRAMES_ARE_EMPTY) && line.next() != line_end) { line.next()->MarkDirty(); line.next()->SetInvalidateTextRuns(true); } #ifdef DEBUG VerifyLines(true); VerifyOverflowSituation(); #endif // Advance to next flow block if the frame has more continuations. if (!aDeletedFrame) { return; } nsBlockFrame* nextBlock = do_QueryFrame(aDeletedFrame->GetParent()); NS_ASSERTION(nextBlock, "Our child's continuation's parent is not a block?"); uint32_t flags = (aFlags & REMOVE_FIXED_CONTINUATIONS); nextBlock->DoRemoveFrame(aContext, aDeletedFrame, flags); } static bool FindBlockLineFor(nsIFrame* aChild, nsLineList::iterator aBegin, nsLineList::iterator aEnd, nsLineList::iterator* aResult) { MOZ_ASSERT(aChild->IsBlockOutside()); for (nsLineList::iterator line = aBegin; line != aEnd; ++line) { MOZ_ASSERT(line->GetChildCount() > 0); if (line->IsBlock() && line->mFirstChild == aChild) { MOZ_ASSERT(line->GetChildCount() == 1); *aResult = line; return true; } } return false; } static bool FindInlineLineFor(nsIFrame* aChild, const nsFrameList& aFrameList, nsLineList::iterator aBegin, nsLineList::iterator aEnd, nsLineList::iterator* aResult) { MOZ_ASSERT(!aChild->IsBlockOutside()); for (nsLineList::iterator line = aBegin; line != aEnd; ++line) { MOZ_ASSERT(line->GetChildCount() > 0); if (!line->IsBlock()) { // Optimize by comparing the line's last child first. nsLineList::iterator next = line.next(); if (aChild == (next == aEnd ? aFrameList.LastChild() : next->mFirstChild->GetPrevSibling()) || line->Contains(aChild)) { *aResult = line; return true; } } } return false; } static bool FindLineFor(nsIFrame* aChild, const nsFrameList& aFrameList, nsLineList::iterator aBegin, nsLineList::iterator aEnd, nsLineList::iterator* aResult) { return aChild->IsBlockOutside() ? FindBlockLineFor(aChild, aBegin, aEnd, aResult) : FindInlineLineFor(aChild, aFrameList, aBegin, aEnd, aResult); } void nsBlockFrame::StealFrame(nsIFrame* aChild) { MOZ_ASSERT(aChild->GetParent() == this); if (aChild->IsFloating()) { RemoveFloat(aChild); return; } if (MaybeStealOverflowContainerFrame(aChild)) { return; } MOZ_ASSERT(!aChild->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)); nsLineList::iterator line; if (FindLineFor(aChild, mFrames, mLines.begin(), mLines.end(), &line)) { RemoveFrameFromLine(aChild, line, mFrames, mLines); } else { FrameLines* overflowLines = GetOverflowLines(); DebugOnly found; found = FindLineFor(aChild, overflowLines->mFrames, overflowLines->mLines.begin(), overflowLines->mLines.end(), &line); MOZ_ASSERT(found, "Why can't we find aChild in our overflow lines?"); RemoveFrameFromLine(aChild, line, overflowLines->mFrames, overflowLines->mLines); if (overflowLines->mLines.empty()) { DestroyOverflowLines(); } } } void nsBlockFrame::RemoveFrameFromLine(nsIFrame* aChild, nsLineList::iterator aLine, nsFrameList& aFrameList, nsLineList& aLineList) { aFrameList.RemoveFrame(aChild); if (aChild == aLine->mFirstChild) { aLine->mFirstChild = aChild->GetNextSibling(); } aLine->NoteFrameRemoved(aChild); if (aLine->GetChildCount() > 0) { aLine->MarkDirty(); } else { // The line became empty - destroy it. nsLineBox* lineBox = aLine; aLine = aLineList.erase(aLine); if (aLine != aLineList.end()) { aLine->MarkPreviousMarginDirty(); } FreeLineBox(lineBox); } } void nsBlockFrame::DeleteNextInFlowChild(DestroyContext& aContext, nsIFrame* aNextInFlow, bool aDeletingEmptyFrames) { MOZ_ASSERT(aNextInFlow->GetPrevInFlow(), "bad next-in-flow"); if (aNextInFlow->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW | NS_FRAME_IS_OVERFLOW_CONTAINER)) { nsContainerFrame::DeleteNextInFlowChild(aContext, aNextInFlow, aDeletingEmptyFrames); } else { #ifdef DEBUG if (aDeletingEmptyFrames) { nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(aNextInFlow); } #endif DoRemoveFrame(aContext, aNextInFlow, aDeletingEmptyFrames ? FRAMES_ARE_EMPTY : 0); } } const nsStyleText* nsBlockFrame::StyleTextForLineLayout() { // Return the pointer to an unmodified style text return StyleText(); } void nsBlockFrame::ReflowFloat(BlockReflowState& aState, ReflowInput& aFloatRI, nsIFrame* aFloat, nsReflowStatus& aReflowStatus) { MOZ_ASSERT(aReflowStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); MOZ_ASSERT(aFloat->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW), "aFloat must be an out-of-flow frame"); WritingMode wm = aState.mReflowInput.GetWritingMode(); // Setup a block reflow context to reflow the float. nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput); nsIFrame* clearanceFrame = nullptr; do { nsCollapsingMargin margin; bool mayNeedRetry = false; aFloatRI.mDiscoveredClearance = nullptr; // Only first in flow gets a block-start margin. if (!aFloat->GetPrevInFlow()) { brc.ComputeCollapsedBStartMargin(aFloatRI, &margin, clearanceFrame, &mayNeedRetry); if (mayNeedRetry && !clearanceFrame) { aFloatRI.mDiscoveredClearance = &clearanceFrame; // We don't need to push the float manager state because the the block // has its own float manager that will be destroyed and recreated } } // When reflowing a float, aSpace argument doesn't matter because we pass // nullptr to aLine and we don't call nsBlockReflowContext::PlaceBlock() // later. brc.ReflowBlock(LogicalRect(wm), true, margin, 0, nullptr, aFloatRI, aReflowStatus, aState); } while (clearanceFrame); if (aFloat->IsLetterFrame()) { // We never split floating first letters; an incomplete status for such // frames simply means that there is more content to be reflowed on the // line. if (aReflowStatus.IsIncomplete()) { aReflowStatus.Reset(); } } NS_ASSERTION(aReflowStatus.IsFullyComplete() || aFloatRI.AvailableBSize() != NS_UNCONSTRAINEDSIZE, "The status can only be incomplete or overflow-incomplete if " "the available block-size is constrained!"); if (aReflowStatus.NextInFlowNeedsReflow()) { aState.mReflowStatus.SetNextInFlowNeedsReflow(); } const ReflowOutput& metrics = brc.GetMetrics(); // Set the rect, make sure the view is properly sized and positioned, // and tell the frame we're done reflowing it // XXXldb This seems like the wrong place to be doing this -- shouldn't // we be doing this in BlockReflowState::FlowAndPlaceFloat after // we've positioned the float, and shouldn't we be doing the equivalent // of |PlaceFrameView| here? WritingMode metricsWM = metrics.GetWritingMode(); aFloat->SetSize(metricsWM, metrics.Size(metricsWM)); if (aFloat->HasView()) { nsContainerFrame::SyncFrameViewAfterReflow( aState.mPresContext, aFloat, aFloat->GetView(), metrics.InkOverflow(), ReflowChildFlags::NoMoveView); } aFloat->DidReflow(aState.mPresContext, &aFloatRI); } StyleClear nsBlockFrame::FindTrailingClear() { for (nsBlockFrame* b = this; b; b = static_cast(b->GetPrevInFlow())) { auto endLine = b->LinesRBegin(); if (endLine != b->LinesREnd()) { return endLine->FloatClearTypeAfter(); } } return StyleClear::None; } void nsBlockFrame::ReflowPushedFloats(BlockReflowState& aState, OverflowAreas& aOverflowAreas) { // Pushed floats live at the start of our float list; see comment // above nsBlockFrame::DrainPushedFloats. nsIFrame* f = mFloats.FirstChild(); nsIFrame* prev = nullptr; while (f && f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) { MOZ_ASSERT(prev == f->GetPrevSibling()); // When we push a first-continuation float in a non-initial reflow, // it's possible that we end up with two continuations with the same // parent. This happens if, on the previous reflow of the block or // a previous reflow of the line containing the block, the float was // split between continuations A and B of the parent, but on the // current reflow, none of the float can fit in A. // // When this happens, we might even have the two continuations // out-of-order due to the management of the pushed floats. In // particular, if the float's placeholder was in a pushed line that // we reflowed before it was pushed, and we split the float during // that reflow, we might have the continuation of the float before // the float itself. (In the general case, however, it's correct // for floats in the pushed floats list to come before floats // anchored in pushed lines; however, in this case it's wrong. We // should probably find a way to fix it somehow, since it leads to // incorrect layout in some cases.) // // When we have these out-of-order continuations, we might hit the // next-continuation before the previous-continuation. When that // happens, just push it. When we reflow the next continuation, // we'll either pull all of its content back and destroy it (by // calling DeleteNextInFlowChild), or nsBlockFrame::SplitFloat will // pull it out of its current position and push it again (and // potentially repeat this cycle for the next continuation, although // hopefully then they'll be in the right order). // // We should also need this code for the in-order case if the first // continuation of a float gets moved across more than one // continuation of the containing block. In this case we'd manage // to push the second continuation without this check, but not the // third and later. nsIFrame* prevContinuation = f->GetPrevContinuation(); if (prevContinuation && prevContinuation->GetParent() == f->GetParent()) { mFloats.RemoveFrame(f); aState.AppendPushedFloatChain(f); f = !prev ? mFloats.FirstChild() : prev->GetNextSibling(); continue; } // Always call FlowAndPlaceFloat; we might need to place this float if it // didn't belong to this block the last time it was reflowed. Note that if // the float doesn't get placed, we don't consider its overflow areas. // (Not-getting-placed means it didn't fit and we pushed it instead of // placing it, and its position could be stale.) if (aState.FlowAndPlaceFloat(f) == BlockReflowState::PlaceFloatResult::Placed) { ConsiderChildOverflow(aOverflowAreas, f); } nsIFrame* next = !prev ? mFloats.FirstChild() : prev->GetNextSibling(); if (next == f) { // We didn't push |f| so its next-sibling is next. next = f->GetNextSibling(); prev = f; } // else: we did push |f| so |prev|'s new next-sibling is next. f = next; } // If there are pushed or split floats, then we may need to continue BR // clearance if (auto [bCoord, result] = aState.ClearFloats(0, StyleClear::Both); result != ClearFloatsResult::BCoordNoChange) { Unused << bCoord; if (auto* prevBlock = static_cast(GetPrevInFlow())) { aState.mTrailingClearFromPIF = prevBlock->FindTrailingClear(); } } } void nsBlockFrame::RecoverFloats(nsFloatManager& aFloatManager, WritingMode aWM, const nsSize& aContainerSize) { // Recover our own floats nsIFrame* stop = nullptr; // Stop before we reach pushed floats that // belong to our next-in-flow for (nsIFrame* f = mFloats.FirstChild(); f && f != stop; f = f->GetNextSibling()) { LogicalRect region = nsFloatManager::GetRegionFor(aWM, f, aContainerSize); aFloatManager.AddFloat(f, region, aWM, aContainerSize); if (!stop && f->GetNextInFlow()) { stop = f->GetNextInFlow(); } } // Recurse into our overflow container children for (nsIFrame* oc = GetChildList(FrameChildListID::OverflowContainers).FirstChild(); oc; oc = oc->GetNextSibling()) { RecoverFloatsFor(oc, aFloatManager, aWM, aContainerSize); } // Recurse into our normal children for (const auto& line : Lines()) { if (line.IsBlock()) { RecoverFloatsFor(line.mFirstChild, aFloatManager, aWM, aContainerSize); } } } void nsBlockFrame::RecoverFloatsFor(nsIFrame* aFrame, nsFloatManager& aFloatManager, WritingMode aWM, const nsSize& aContainerSize) { MOZ_ASSERT(aFrame, "null frame"); // Only blocks have floats nsBlockFrame* block = do_QueryFrame(aFrame); // Don't recover any state inside a block that has its own float manager // (we don't currently have any blocks like this, though, thanks to our // use of extra frames for 'overflow') if (block && !nsBlockFrame::BlockNeedsFloatManager(block)) { // If the element is relatively positioned, then adjust x and y // accordingly so that we consider relatively positioned frames // at their original position. const LogicalRect rect = block->GetLogicalNormalRect(aWM, aContainerSize); nscoord lineLeft = rect.LineLeft(aWM, aContainerSize); nscoord blockStart = rect.BStart(aWM); aFloatManager.Translate(lineLeft, blockStart); block->RecoverFloats(aFloatManager, aWM, aContainerSize); aFloatManager.Translate(-lineLeft, -blockStart); } } bool nsBlockFrame::HasPushedFloatsFromPrevContinuation() const { if (!mFloats.IsEmpty()) { // If we have pushed floats, then they should be at the beginning of our // float list. if (mFloats.FirstChild()->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) { return true; } } #ifdef DEBUG // Double-check the above assertion that pushed floats should be at the // beginning of our floats list. for (nsIFrame* f : mFloats) { NS_ASSERTION(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT), "pushed floats must be at the beginning of the float list"); } #endif // We may have a pending push of pushed floats too: if (HasPushedFloats()) { // XXX we can return 'true' here once we make HasPushedFloats // not lie. (see nsBlockFrame::RemoveFloat) auto* pushedFloats = GetPushedFloats(); return pushedFloats && !pushedFloats->IsEmpty(); } return false; } ////////////////////////////////////////////////////////////////////// // Painting, event handling #ifdef DEBUG static void ComputeInkOverflowArea(nsLineList& aLines, nscoord aWidth, nscoord aHeight, nsRect& aResult) { nscoord xa = 0, ya = 0, xb = aWidth, yb = aHeight; for (nsLineList::iterator line = aLines.begin(), line_end = aLines.end(); line != line_end; ++line) { // Compute min and max x/y values for the reflowed frame's // combined areas nsRect inkOverflow(line->InkOverflowRect()); nscoord x = inkOverflow.x; nscoord y = inkOverflow.y; nscoord xmost = x + inkOverflow.width; nscoord ymost = y + inkOverflow.height; if (x < xa) { xa = x; } if (xmost > xb) { xb = xmost; } if (y < ya) { ya = y; } if (ymost > yb) { yb = ymost; } } aResult.x = xa; aResult.y = ya; aResult.width = xb - xa; aResult.height = yb - ya; } #endif #ifdef DEBUG static void DebugOutputDrawLine(int32_t aDepth, nsLineBox* aLine, bool aDrawn) { if (nsBlockFrame::gNoisyDamageRepair) { nsIFrame::IndentBy(stdout, aDepth + 1); nsRect lineArea = aLine->InkOverflowRect(); printf("%s line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n", aDrawn ? "draw" : "skip", static_cast(aLine), aLine->IStart(), aLine->BStart(), aLine->ISize(), aLine->BSize(), lineArea.x, lineArea.y, lineArea.width, lineArea.height); } } #endif static void DisplayLine(nsDisplayListBuilder* aBuilder, nsBlockFrame::LineIterator& aLine, const bool aLineInLine, const nsDisplayListSet& aLists, nsBlockFrame* aFrame, TextOverflow* aTextOverflow, uint32_t aLineNumberForTextOverflow, int32_t aDepth, int32_t& aDrawnLines) { #ifdef DEBUG if (nsBlockFrame::gLamePaintMetrics) { aDrawnLines++; } const bool intersect = aLine->InkOverflowRect().Intersects(aBuilder->GetDirtyRect()); DebugOutputDrawLine(aDepth, aLine.get(), intersect); #endif // Collect our line's display items in a temporary nsDisplayListCollection, // so that we can apply any "text-overflow" clipping to the entire collection // without affecting previous lines. nsDisplayListCollection collection(aBuilder); // Block-level child backgrounds go on the blockBorderBackgrounds list ... // Inline-level child backgrounds go on the regular child content list. nsDisplayListSet childLists( collection, aLineInLine ? collection.Content() : collection.BlockBorderBackgrounds()); auto flags = aLineInLine ? nsIFrame::DisplayChildFlags(nsIFrame::DisplayChildFlag::Inline) : nsIFrame::DisplayChildFlags(); nsIFrame* kid = aLine->mFirstChild; int32_t n = aLine->GetChildCount(); while (--n >= 0) { aFrame->BuildDisplayListForChild(aBuilder, kid, childLists, flags); kid = kid->GetNextSibling(); } if (aTextOverflow && aLineInLine) { aTextOverflow->ProcessLine(collection, aLine.get(), aLineNumberForTextOverflow); } collection.MoveTo(aLists); } void nsBlockFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) { int32_t drawnLines; // Will only be used if set (gLamePaintMetrics). int32_t depth = 0; #ifdef DEBUG if (gNoisyDamageRepair) { nsRect dirty = aBuilder->GetDirtyRect(); depth = GetDepth(); nsRect ca; ::ComputeInkOverflowArea(mLines, mRect.width, mRect.height, ca); nsIFrame::IndentBy(stdout, depth); ListTag(stdout); printf(": bounds=%d,%d,%d,%d dirty(absolute)=%d,%d,%d,%d ca=%d,%d,%d,%d\n", mRect.x, mRect.y, mRect.width, mRect.height, dirty.x, dirty.y, dirty.width, dirty.height, ca.x, ca.y, ca.width, ca.height); } PRTime start = 0; // Initialize these variables to silence the compiler. if (gLamePaintMetrics) { start = PR_Now(); drawnLines = 0; } #endif // TODO(heycam): Should we boost the load priority of any shape-outside // images using CATEGORY_DISPLAY, now that this block is being displayed? // We don't have a float manager here. DisplayBorderBackgroundOutline(aBuilder, aLists); if (GetPrevInFlow()) { DisplayOverflowContainers(aBuilder, aLists); for (nsIFrame* f : mFloats) { if (f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) { BuildDisplayListForChild(aBuilder, f, aLists); } } } aBuilder->MarkFramesForDisplayList(this, mFloats); if (HasOutsideMarker()) { // Display outside ::marker manually. BuildDisplayListForChild(aBuilder, GetOutsideMarker(), aLists); } // Prepare for text-overflow processing. Maybe textOverflow = TextOverflow::WillProcessLines(aBuilder, this); const bool hasDescendantPlaceHolders = HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) || ForceDescendIntoIfVisible() || aBuilder->GetIncludeAllOutOfFlows(); const auto ShouldDescendIntoLine = [&](const nsRect& aLineArea) -> bool { // TODO(miko): Unfortunately |descendAlways| cannot be cached, because with // some frame trees, building display list for child lines can change it. // See bug 1552789. const bool descendAlways = HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) || aBuilder->GetIncludeAllOutOfFlows(); return descendAlways || aLineArea.Intersects(aBuilder->GetDirtyRect()) || (ForceDescendIntoIfVisible() && aLineArea.Intersects(aBuilder->GetVisibleRect())); }; Maybe backplateColor; // We'll try to draw an accessibility backplate behind text (to ensure it's // readable over any possible background-images), if all of the following // hold: // (A) we are not honoring the document colors // (B) the backplate feature is preffed on // (C) the force color adjust property is set to auto if (PresContext()->ForcingColors() && StaticPrefs::browser_display_permit_backplate() && StyleText()->mForcedColorAdjust != StyleForcedColorAdjust::None) { backplateColor.emplace(GetBackplateColor(this)); } // Don't use the line cursor if we might have a descendant placeholder ... // it might skip lines that contain placeholders but don't themselves // intersect with the dirty area. // In particular, we really want to check ShouldDescendIntoFrame() // on all our child frames, but that might be expensive. So we // approximate it by checking it on |this|; if it's true for any // frame in our child list, it's also true for |this|. // Also skip the cursor if we're creating text overflow markers, // since we need to know what line number we're up to in order // to generate unique display item keys. // Lastly, the cursor should be skipped if we're drawing // backplates behind text. When backplating we consider consecutive // runs of text as a whole, which requires we iterate through all lines // to find our backplate size. nsLineBox* cursor = (hasDescendantPlaceHolders || textOverflow.isSome() || backplateColor) ? nullptr : GetFirstLineContaining(aBuilder->GetDirtyRect().y); LineIterator line_end = LinesEnd(); TextOverflow* textOverflowPtr = textOverflow.ptrOr(nullptr); if (cursor) { for (LineIterator line = mLines.begin(cursor); line != line_end; ++line) { const nsRect lineArea = line->InkOverflowRect(); if (!lineArea.IsEmpty()) { // Because we have a cursor, the combinedArea.ys are non-decreasing. // Once we've passed aDirtyRect.YMost(), we can never see it again. if (lineArea.y >= aBuilder->GetDirtyRect().YMost()) { break; } MOZ_ASSERT(textOverflow.isNothing()); if (ShouldDescendIntoLine(lineArea)) { DisplayLine(aBuilder, line, line->IsInline(), aLists, this, nullptr, 0, depth, drawnLines); } } } } else { bool nonDecreasingYs = true; uint32_t lineCount = 0; nscoord lastY = INT32_MIN; nscoord lastYMost = INT32_MIN; // A frame's display list cannot contain more than one copy of a // given display item unless the items are uniquely identifiable. // Because backplate occasionally requires multiple // SolidColor items, we use an index (backplateIndex) to maintain // uniqueness among them. Note this is a mapping of index to // item, and the mapping is stable even if the dirty rect changes. uint16_t backplateIndex = 0; nsRect curBackplateArea; auto AddBackplate = [&]() { aLists.BorderBackground()->AppendNewToTopWithIndex( aBuilder, this, backplateIndex, curBackplateArea, backplateColor.value()); }; for (LineIterator line = LinesBegin(); line != line_end; ++line) { const nsRect lineArea = line->InkOverflowRect(); const bool lineInLine = line->IsInline(); if ((lineInLine && textOverflowPtr) || ShouldDescendIntoLine(lineArea)) { DisplayLine(aBuilder, line, lineInLine, aLists, this, textOverflowPtr, lineCount, depth, drawnLines); } if (!lineInLine && !curBackplateArea.IsEmpty()) { // If we have encountered a non-inline line but were previously // forming a backplate, we should add the backplate to the display // list as-is and render future backplates disjointly. MOZ_ASSERT(backplateColor, "if this master switch is off, curBackplateArea " "must be empty and we shouldn't get here"); AddBackplate(); backplateIndex++; curBackplateArea = nsRect(); } if (!lineArea.IsEmpty()) { if (lineArea.y < lastY || lineArea.YMost() < lastYMost) { nonDecreasingYs = false; } lastY = lineArea.y; lastYMost = lineArea.YMost(); if (lineInLine && backplateColor && LineHasVisibleInlineText(line)) { nsRect lineBackplate = GetLineTextArea(line, aBuilder) + aBuilder->ToReferenceFrame(this); if (curBackplateArea.IsEmpty()) { curBackplateArea = lineBackplate; } else { curBackplateArea.OrWith(lineBackplate); } } } lineCount++; } if (nonDecreasingYs && lineCount >= MIN_LINES_NEEDING_CURSOR) { SetupLineCursorForDisplay(); } if (!curBackplateArea.IsEmpty()) { AddBackplate(); } } if (textOverflow.isSome()) { // Put any text-overflow:ellipsis markers on top of the non-positioned // content of the block's lines. (If we ever start sorting the Content() // list this will end up in the wrong place.) aLists.Content()->AppendToTop(&textOverflow->GetMarkers()); } #ifdef DEBUG if (gLamePaintMetrics) { PRTime end = PR_Now(); int32_t numLines = mLines.size(); if (!numLines) { numLines = 1; } PRTime lines, deltaPerLine, delta; lines = int64_t(numLines); delta = end - start; deltaPerLine = delta / lines; ListTag(stdout); char buf[400]; SprintfLiteral(buf, ": %" PRId64 " elapsed (%" PRId64 " per line) lines=%d drawn=%d skip=%d", delta, deltaPerLine, numLines, drawnLines, numLines - drawnLines); printf("%s\n", buf); } #endif } #ifdef ACCESSIBILITY a11y::AccType nsBlockFrame::AccessibleType() { if (IsTableCaption()) { return GetRect().IsEmpty() ? a11y::eNoType : a11y::eHTMLCaptionType; } // block frame may be for


    if (mContent->IsHTMLElement(nsGkAtoms::hr)) { return a11y::eHTMLHRType; } if (!HasMarker() || !PresContext()) { // XXXsmaug What if we're in the shadow dom? if (!mContent->GetParent()) { // Don't create accessible objects for the root content node, they are // redundant with the nsDocAccessible object created with the document // node return a11y::eNoType; } if (mContent == mContent->OwnerDoc()->GetBody()) { // Don't create accessible objects for the body, they are redundant with // the nsDocAccessible object created with the document node return a11y::eNoType; } // Not a list item with a ::marker, treat as normal HTML container. return a11y::eHyperTextType; } // Create special list item accessible since we have a ::marker. return a11y::eHTMLLiType; } #endif void nsBlockFrame::SetupLineCursorForDisplay() { if (mLines.empty() || HasProperty(LineCursorPropertyDisplay())) { return; } SetProperty(LineCursorPropertyDisplay(), mLines.front()); AddStateBits(NS_BLOCK_HAS_LINE_CURSOR); } void nsBlockFrame::SetupLineCursorForQuery() { if (mLines.empty() || HasProperty(LineCursorPropertyQuery())) { return; } SetProperty(LineCursorPropertyQuery(), mLines.front()); AddStateBits(NS_BLOCK_HAS_LINE_CURSOR); } nsLineBox* nsBlockFrame::GetFirstLineContaining(nscoord y) { // Although this looks like a "querying" method, it is used by the // display-list building code, so uses the Display cursor. nsLineBox* property = GetLineCursorForDisplay(); if (!property) { return nullptr; } LineIterator cursor = mLines.begin(property); nsRect cursorArea = cursor->InkOverflowRect(); while ((cursorArea.IsEmpty() || cursorArea.YMost() > y) && cursor != mLines.front()) { cursor = cursor.prev(); cursorArea = cursor->InkOverflowRect(); } while ((cursorArea.IsEmpty() || cursorArea.YMost() <= y) && cursor != mLines.back()) { cursor = cursor.next(); cursorArea = cursor->InkOverflowRect(); } if (cursor.get() != property) { SetProperty(LineCursorPropertyDisplay(), cursor.get()); } return cursor.get(); } /* virtual */ void nsBlockFrame::ChildIsDirty(nsIFrame* aChild) { // See if the child is absolutely positioned if (aChild->IsAbsolutelyPositioned()) { // do nothing } else if (aChild == GetOutsideMarker()) { // The ::marker lives in the first line, unless the first line has // height 0 and there is a second line, in which case it lives // in the second line. LineIterator markerLine = LinesBegin(); if (markerLine != LinesEnd() && markerLine->BSize() == 0 && markerLine != mLines.back()) { markerLine = markerLine.next(); } if (markerLine != LinesEnd()) { MarkLineDirty(markerLine, &mLines); } // otherwise we have an empty line list, and ReflowDirtyLines // will handle reflowing the ::marker. } else { // Note that we should go through our children to mark lines dirty // before the next reflow. Doing it now could make things O(N^2) // since finding the right line is O(N). // We don't need to worry about marking lines on the overflow list // as dirty; we're guaranteed to reflow them if we take them off the // overflow list. // However, we might have gotten a float, in which case we need to // reflow the line containing its placeholder. So find the // ancestor-or-self of the placeholder that's a child of the block, // and mark it as NS_FRAME_HAS_DIRTY_CHILDREN too, so that we mark // its line dirty when we handle NS_BLOCK_LOOK_FOR_DIRTY_FRAMES. // We need to take some care to handle the case where a float is in // a different continuation than its placeholder, including marking // an extra block with NS_BLOCK_LOOK_FOR_DIRTY_FRAMES. if (!aChild->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES); } else { NS_ASSERTION(aChild->IsFloating(), "should be a float"); nsIFrame* thisFC = FirstContinuation(); nsIFrame* placeholderPath = aChild->GetPlaceholderFrame(); // SVG code sometimes sends FrameNeedsReflow notifications during // frame destruction, leading to null placeholders, but we're safe // ignoring those. if (placeholderPath) { for (;;) { nsIFrame* parent = placeholderPath->GetParent(); if (parent->GetContent() == mContent && parent->FirstContinuation() == thisFC) { parent->AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES); break; } placeholderPath = parent; } placeholderPath->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); } } } nsContainerFrame::ChildIsDirty(aChild); } void nsBlockFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, nsIFrame* aPrevInFlow) { // These are all the block specific frame bits, they are copied from // the prev-in-flow to a newly created next-in-flow, except for the // NS_BLOCK_FLAGS_NON_INHERITED_MASK bits below. constexpr nsFrameState NS_BLOCK_FLAGS_MASK = NS_BLOCK_BFC | NS_BLOCK_HAS_FIRST_LETTER_STYLE | NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER | NS_BLOCK_HAS_FIRST_LETTER_CHILD | NS_BLOCK_FRAME_HAS_INSIDE_MARKER; // This is the subset of NS_BLOCK_FLAGS_MASK that is NOT inherited // by default. They should only be set on the first-in-flow. constexpr nsFrameState NS_BLOCK_FLAGS_NON_INHERITED_MASK = NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER | NS_BLOCK_HAS_FIRST_LETTER_CHILD | NS_BLOCK_FRAME_HAS_INSIDE_MARKER; if (aPrevInFlow) { // Copy over the inherited block frame bits from the prev-in-flow. RemoveStateBits(NS_BLOCK_FLAGS_MASK); AddStateBits(aPrevInFlow->GetStateBits() & (NS_BLOCK_FLAGS_MASK & ~NS_BLOCK_FLAGS_NON_INHERITED_MASK)); } nsContainerFrame::Init(aContent, aParent, aPrevInFlow); if (!aPrevInFlow || aPrevInFlow->HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION)) { AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION); } if (EstablishesBFC(this)) { AddStateBits(NS_BLOCK_BFC); } if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER) && HasAnyStateBits(NS_BLOCK_BFC)) { AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT); } } void nsBlockFrame::SetInitialChildList(ChildListID aListID, nsFrameList&& aChildList) { if (FrameChildListID::Float == aListID) { mFloats = std::move(aChildList); } else if (FrameChildListID::Principal == aListID) { #ifdef DEBUG // The only times a block that is an anonymous box is allowed to have a // first-letter frame are when it's the block inside a non-anonymous cell, // the block inside a fieldset, button or column set, or a scrolled content // block, except for