diff options
Diffstat (limited to '')
-rw-r--r-- | layout/generic/nsBlockFrame.cpp | 8626 |
1 files changed, 8626 insertions, 0 deletions
diff --git a/layout/generic/nsBlockFrame.cpp b/layout/generic/nsBlockFrame.cpp new file mode 100644 index 0000000000..54976ecc47 --- /dev/null +++ b/layout/generic/nsBlockFrame.cpp @@ -0,0 +1,8626 @@ +/* -*- 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 "nsAttrValueInlines.h" +#include "mozilla/Sprintf.h" +#include "nsFloatManager.h" +#include "prenv.h" +#include "nsError.h" +#include "nsIScrollableFrame.h" +#include <algorithm> +#include "nsLayoutUtils.h" +#include "nsDisplayList.h" +#include "nsCSSAnonBoxes.h" +#include "nsCSSFrameConstructor.h" +#include "TextOverflow.h" +#include "nsIFrameInlines.h" +#include "CounterStyleManager.h" +#include "mozilla/dom/HTMLDetailsElement.h" +#include "mozilla/dom/HTMLSummaryElement.h" +#include "mozilla/dom/Selection.h" +#include "mozilla/PresShell.h" +#include "mozilla/RestyleManager.h" +#include "mozilla/ServoStyleSet.h" +#include "mozilla/Telemetry.h" +#include "nsFlexContainerFrame.h" +#include "nsFileControlFrame.h" +#include "nsMathMLContainerFrame.h" +#include "nsSelectsAreaFrame.h" + +#include "nsBidiPresUtils.h" + +#include <inttypes.h> + +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_STATE_BITS)) { + 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 or has + * visible children that participate in the same line. Frames + * that are not line participants do not have their + * children checked. + */ +static bool FrameHasVisibleInlineContent(nsIFrame* aFrame) { + MOZ_ASSERT(aFrame, "Frame argument cannot be null"); + + if (aFrame->StyleVisibility()->IsVisible()) { + return true; + } + + if (aFrame->IsLineParticipant()) { + for (nsIFrame* kid : aFrame->PrincipalChildList()) { + if (kid->StyleVisibility()->IsVisible() || + FrameHasVisibleInlineContent(kid)) { + return true; + } + } + } + return false; +} + +/** + * Determines whether any of the frames descended from the + * given line have inline content with 'visibility: visible'. + * This function calls FrameHasVisibleInlineContent to process + * each frame in the line's child list. + */ +static bool LineHasVisibleInlineContent(nsLineBox* aLine) { + nsIFrame* kid = aLine->mFirstChild; + int32_t n = aLine->GetChildCount(); + while (n-- > 0) { + if (FrameHasVisibleInlineContent(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()); +} + +nsBlockFrame* NS_NewBlockFormattingContext(PresShell* aPresShell, + ComputedStyle* aComputedStyle) { + nsBlockFrame* blockFrame = NS_NewBlockFrame(aPresShell, aComputedStyle); + blockFrame->AddStateBits(NS_BLOCK_STATIC_BFC); + return blockFrame; +} + +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 <typename LineIteratorType> +Maybe<nscoord> nsBlockFrame::GetBaselineBOffset( + LineIteratorType aStart, LineIteratorType aEnd, WritingMode aWM, + BaselineSharingGroup aBaselineGroup, + BaselineExportContext aExportContext) const { + MOZ_ASSERT((std::is_same_v<LineIteratorType, ConstLineIterator> && + aBaselineGroup == BaselineSharingGroup::First) || + (std::is_same_v<LineIteratorType, ConstReverseLineIterator> && + 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()) { + // `<table>` 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<nscoord> 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<nsFontMetrics> 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<ChildList>* 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<nsBlockFrame*>(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<nscoord> 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<nsBlockFrame*>(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<nsBlockFrame*>(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<nscoord> 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<nsBlockFrame*>(curFrame->GetNextContinuation())) { + curFrame->LazyMarkLinesDirty(); + } + + if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) && + PresContext()->BidiEnabled()) { + ResolveBidi(); + } + InlinePrefISizeData data; + for (nsBlockFrame* curFrame = this; curFrame; + curFrame = static_cast<nsBlockFrame*>(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<nsBlockFrame*>(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<const ReflowInput*> 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_STATE_BITS)) { + return block; + } + } + return nullptr; +} + +static nsBlockFrame* GetAsLineClampDescendant(nsIFrame* aFrame) { + return const_cast<nsBlockFrame*>( + GetAsLineClampDescendant(const_cast<const nsIFrame*>(aFrame))); +} + +static bool IsLineClampRoot(const nsBlockFrame* aFrame) { + if (!aFrame->StyleDisplay()->mWebkitLineClamp) { + return false; + } + + if (!aFrame->HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS)) { + 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<std::pair<nsBlockFrame*, nsBlockFrame::LineIterator>, 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<nscoord> restoreReflowInputAvailBSize; + auto MaybeRestore = MakeScopeExit([&] { + if (MOZ_UNLIKELY(restoreReflowInputAvailBSize)) { + const_cast<ReflowInput&>(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<ReflowInput&>(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<ReflowInput&>(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<nsBlockFrame*>(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<nsTextFrame*>(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<nsBlockFrame*>(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<nsBlockFrame*>(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: <LI>\n<P>... ). 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<nscoord> 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; + + 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); + } + + 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 { + Maybe<nscoord> containBSize = ContainIntrinsicBSize( + IsComboboxControlFrame() ? NS_UNCONSTRAINEDSIZE : 0); + if (containBSize && *containBSize != NS_UNCONSTRAINEDSIZE) { + // 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. + // + // NOTE: We exempt the nsComboboxControlFrame subclass from taking this + // special case when it has 'contain-intrinsic-block-size: none', because + // comboboxes implicitly honors the size-containment behavior on its + // nsComboboxDisplayFrame child (which it shrinkwraps) rather than on the + // nsComboboxControlFrame. (Moreover, the DisplayFrame child doesn't even + // need any special content-size-ignoring behavior in its reflow method, + // because that method just resolves "auto" BSize values to one + // line-height rather than by measuring its contents' BSize.) + 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::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<void*>(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<void*>(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<void*>(line.get()), + static_cast<void*>( + (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<void*>(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. + PushTruncatedLine(aState, line, &keepGoing); + PresShell()->FrameConstructor()->SetNextPageContentFramePageName( + nextPageName ? nextPageName : GetAutoPageValue()); + } 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<nsBlockFrame*>(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<nsFontMetrics> 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<nsBlockFrame*>(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_STATE_BITS)) { + 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<ReflowInput> childReflowInput; + Maybe<LogicalSize> 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) { + PushLines(aState, aLine.prev()); + *aKeepReflowGoing = false; + 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<nsPlaceholderFrame*>(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 <BR>, then + // combine the <BR>'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_STATE_BITS)) || + 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<void*>(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<void*>(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<bool> 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<nsBlockFrame*>(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<FrameLines> 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<nsBlockFrame*>(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<nsBlockFrame*>(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 <br>, 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() == + mozilla::PseudoStyleType::blockRubyContent) { + return static_cast<nsContainerFrame*>(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<OwnedAnonBox>& 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<bool> 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 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-multicol/#columns + return aStyle->StyleDisplay()->IsContainPaint() || + aStyle->StyleDisplay()->IsContainLayout() || + aStyle->GetPseudoType() == PseudoStyleType::columnContent; +} + +void nsBlockFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) { + nsContainerFrame::DidSetComputedStyle(aOldStyle); + if (!aOldStyle) { + return; + } + + // If NS_BLOCK_STATIC_BFC flag was set when the frame was initialized, it + // remains set during the lifetime of the frame and always forces it to be + // treated as a BFC, independently of the value of NS_BLOCK_DYNAMIC_BFC. + // Consequently, we don't bother invalidating or updating that latter flag. + if (HasAnyStateBits(NS_BLOCK_STATIC_BFC)) { + return; + } + + bool isBFC = StyleEstablishesBFC(Style()); + if (StyleEstablishesBFC(aOldStyle) != 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_DYNAMIC_BFC); + MarkSameFloatManagerLinesDirty(this); + } + AddOrRemoveStateBits(NS_BLOCK_DYNAMIC_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<ComputedStyle> 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<ComputedStyle> 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<ComputedStyle> 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<nsBlockFrame*>(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<nsBlockFrame*>(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<bool> 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<nsBlockFrame*>(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<nsBlockFrame*>(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<void*>(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 = + 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<nscolor> 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) the backplate feature is preffed on + // (B) we are not honoring the document colors + // (C) the force color adjust property is set to auto + if (StaticPrefs::browser_display_permit_backplate() && + PresContext()->ForcingColors() && !IsComboboxControlFrame() && + 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<nsDisplaySolidColor>( + 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 && LineHasVisibleInlineContent(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 <hr> + 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); +} + +static bool AlwaysEstablishesBFC(const nsBlockFrame* aFrame) { + switch (aFrame->Type()) { + case LayoutFrameType::ColumnSetWrapper: + // CSS Multi-column level 1 section 2: A multi-column container + // establishes a new block formatting context, as per CSS 2.1 section + // 9.4.1. + case LayoutFrameType::ComboboxControl: + return true; + case LayoutFrameType::Block: + return static_cast<const nsFileControlFrame*>(do_QueryFrame(aFrame)) || + // Ensure that the options inside the select aren't expanded by + // right floats outside the select. + static_cast<const nsSelectsAreaFrame*>(do_QueryFrame(aFrame)) || + // See bug 1373767 and bug 353894. + static_cast<const nsMathMLmathBlockFrame*>(do_QueryFrame(aFrame)); + default: + return false; + } +} + +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_STATE_BITS | NS_BLOCK_CLIP_PAGINATED_OVERFLOW | + 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); + } + + // A display:flow-root box establishes a block formatting context. + // + // 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) + // + // If the box has contain: paint or contain:layout (or contain:strict), + // then it should also establish a formatting context. + // + // Per spec, a column-span always establishes a new block formatting context. + // + // Other more specific frame types also always establish a BFC. + // + if (StyleDisplay()->mDisplay == mozilla::StyleDisplay::FlowRoot || + (GetParent() && + (GetWritingMode().GetBlockDir() != + GetParent()->GetWritingMode().GetBlockDir() || + GetWritingMode().IsVerticalSideways() != + GetParent()->GetWritingMode().IsVerticalSideways())) || + IsColumnSpan() || AlwaysEstablishesBFC(this)) { + AddStateBits(NS_BLOCK_STATIC_BFC); + } + + if (StyleEstablishesBFC(Style())) { + AddStateBits(NS_BLOCK_DYNAMIC_BFC); + } + + if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER) && + HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS)) { + 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 <select>. Note that this means that blocks which are + // the anonymous block in {ib} splits do NOT get first-letter frames. + // Note that NS_BLOCK_HAS_FIRST_LETTER_STYLE gets set on all continuations + // of the block. + auto pseudo = Style()->GetPseudoType(); + bool haveFirstLetterStyle = + (pseudo == PseudoStyleType::NotPseudo || + (pseudo == PseudoStyleType::cellContent && + !GetParent()->Style()->IsPseudoOrAnonBox()) || + pseudo == PseudoStyleType::fieldsetContent || + pseudo == PseudoStyleType::buttonContent || + pseudo == PseudoStyleType::columnContent || + (pseudo == PseudoStyleType::scrolledContent && + !GetParent()->IsListControlFrame()) || + pseudo == PseudoStyleType::mozSVGText) && + !IsComboboxControlFrame() && !IsMathMLFrame() && + !IsColumnSetWrapperFrame() && + RefPtr<ComputedStyle>(GetFirstLetterStyle(PresContext())) != nullptr; + NS_ASSERTION(haveFirstLetterStyle == + HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE), + "NS_BLOCK_HAS_FIRST_LETTER_STYLE state out of sync"); +#endif + + AddFrames(std::move(aChildList), nullptr, nullptr); + } else { + nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList)); + } +} + +void nsBlockFrame::SetMarkerFrameForListItem(nsIFrame* aMarkerFrame) { + MOZ_ASSERT(aMarkerFrame); + MOZ_ASSERT(!HasAnyStateBits(NS_BLOCK_FRAME_HAS_INSIDE_MARKER | + NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER), + "How can we have a ::marker frame already?"); + + if (StyleList()->mListStylePosition == StyleListStylePosition::Inside) { + SetProperty(InsideMarkerProperty(), aMarkerFrame); + AddStateBits(NS_BLOCK_FRAME_HAS_INSIDE_MARKER); + } else { + if (nsBlockFrame* marker = do_QueryFrame(aMarkerFrame)) { + // An outside ::marker needs to be an independent formatting context + // to avoid being influenced by the float manager etc. + marker->AddStateBits(NS_BLOCK_STATIC_BFC); + } + SetProperty(OutsideMarkerProperty(), + new (PresShell()) nsFrameList(aMarkerFrame, aMarkerFrame)); + AddStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER); + } +} + +bool nsBlockFrame::MarkerIsEmpty() const { + NS_ASSERTION(mContent->GetPrimaryFrame()->StyleDisplay()->IsListItem() && + HasOutsideMarker(), + "should only care when we have an outside ::marker"); + nsIFrame* marker = GetMarker(); + const nsStyleList* list = marker->StyleList(); + return marker->StyleContent()->mContent.IsNone() || + (list->mCounterStyle.IsNone() && list->mListStyleImage.IsNone() && + marker->StyleContent()->ContentCount() == 0); +} + +void nsBlockFrame::ReflowOutsideMarker(nsIFrame* aMarkerFrame, + BlockReflowState& aState, + ReflowOutput& aMetrics, + nscoord aLineTop) { + const ReflowInput& ri = aState.mReflowInput; + + WritingMode markerWM = aMarkerFrame->GetWritingMode(); + LogicalSize availSize(markerWM); + // Make up an inline-size since it doesn't really matter (XXX). + availSize.ISize(markerWM) = aState.ContentISize(); + availSize.BSize(markerWM) = NS_UNCONSTRAINEDSIZE; + + ReflowInput reflowInput(aState.mPresContext, ri, aMarkerFrame, availSize, + Nothing(), {}, {}, {ComputeSizeFlag::ShrinkWrap}); + nsReflowStatus status; + aMarkerFrame->Reflow(aState.mPresContext, aMetrics, reflowInput, status); + + // Get the float available space using our saved state from before we + // started reflowing the block, so that we ignore any floats inside + // the block. + // FIXME: aLineTop isn't actually set correctly by some callers, since + // they reposition the line. + LogicalRect floatAvailSpace = + aState + .GetFloatAvailableSpaceWithState(aLineTop, ShapeType::ShapeOutside, + &aState.mFloatManagerStateBefore) + .mRect; + // FIXME (bug 25888): need to check the entire region that the first + // line overlaps, not just the top pixel. + + // Place the ::marker now. We want to place the ::marker relative to the + // border-box of the associated block (using the right/left margin of + // the ::marker frame as separation). However, if a line box would be + // displaced by floats that are *outside* the associated block, we + // want to displace it by the same amount. That is, we act as though + // the edge of the floats is the content-edge of the block, and place + // the ::marker at a position offset from there by the block's padding, + // the block's border, and the ::marker frame's margin. + + // IStart from floatAvailSpace gives us the content/float start edge + // in the current writing mode. Then we subtract out the start + // border/padding and the ::marker's width and margin to offset the position. + WritingMode wm = ri.GetWritingMode(); + // Get the ::marker's margin, converted to our writing mode so that we can + // combine it with other logical values here. + LogicalMargin markerMargin = reflowInput.ComputedLogicalMargin(wm); + nscoord iStart = floatAvailSpace.IStart(wm) - + ri.ComputedLogicalBorderPadding(wm).IStart(wm) - + markerMargin.IEnd(wm) - aMetrics.ISize(wm); + + // Approximate the ::marker's position; vertical alignment will provide + // the final vertical location. We pass our writing-mode here, because + // it may be different from the ::marker frame's mode. + nscoord bStart = floatAvailSpace.BStart(wm); + aMarkerFrame->SetRect( + wm, + LogicalRect(wm, iStart, bStart, aMetrics.ISize(wm), aMetrics.BSize(wm)), + aState.ContainerSize()); + aMarkerFrame->DidReflow(aState.mPresContext, &aState.mReflowInput); +} + +// This is used to scan frames for any float placeholders, add their +// floats to the list represented by aList, and remove the +// floats from whatever list they might be in. We don't search descendants +// that are float containing blocks. Floats that or not children of 'this' +// are ignored (they are not added to aList). +void nsBlockFrame::DoCollectFloats(nsIFrame* aFrame, nsFrameList& aList, + bool aCollectSiblings) { + while (aFrame) { + // Don't descend into float containing blocks. + if (!aFrame->IsFloatContainingBlock()) { + nsIFrame* outOfFlowFrame = + aFrame->IsPlaceholderFrame() + ? nsLayoutUtils::GetFloatFromPlaceholder(aFrame) + : nullptr; + while (outOfFlowFrame && outOfFlowFrame->GetParent() == this) { + RemoveFloat(outOfFlowFrame); + // Remove the IS_PUSHED_FLOAT bit, in case |outOfFlowFrame| came from + // the PushedFloats list. + outOfFlowFrame->RemoveStateBits(NS_FRAME_IS_PUSHED_FLOAT); + aList.AppendFrame(nullptr, outOfFlowFrame); + outOfFlowFrame = outOfFlowFrame->GetNextInFlow(); + // FIXME: By not pulling floats whose parent is one of our + // later siblings, are we risking the pushed floats getting + // out-of-order? + // XXXmats nsInlineFrame's lazy reparenting depends on NOT doing that. + } + + DoCollectFloats(aFrame->PrincipalChildList().FirstChild(), aList, true); + DoCollectFloats( + aFrame->GetChildList(FrameChildListID::Overflow).FirstChild(), aList, + true); + } + if (!aCollectSiblings) { + break; + } + aFrame = aFrame->GetNextSibling(); + } +} + +void nsBlockFrame::CheckFloats(BlockReflowState& aState) { +#ifdef DEBUG + // If any line is still dirty, that must mean we're going to reflow this + // block again soon (e.g. because we bailed out after noticing that + // clearance was imposed), so don't worry if the floats are out of sync. + bool anyLineDirty = false; + + // Check that the float list is what we would have built + AutoTArray<nsIFrame*, 8> lineFloats; + for (auto& line : Lines()) { + if (line.HasFloats()) { + lineFloats.AppendElements(line.Floats()); + } + if (line.IsDirty()) { + anyLineDirty = true; + } + } + + AutoTArray<nsIFrame*, 8> storedFloats; + bool equal = true; + bool hasHiddenFloats = false; + uint32_t i = 0; + for (nsIFrame* f : mFloats) { + if (f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) { + continue; + } + // There are chances that the float children won't be added to lines, + // because in nsBlockFrame::ReflowLine, it skips reflow line if the first + // child of the line is IsHiddenByContentVisibilityOfInFlowParentForLayout. + // There are also chances that the floats in line are out of date, for + // instance, lines could reflow if + // PresShell::IsForcingLayoutForHiddenContent, and after forcingLayout is + // off, the reflow of lines could be skipped, but the floats are still in + // there. Here we can't know whether the floats hidden by c-v are included + // in the lines or not. So we use hasHiddenFloats to skip the float length + // checking. + if (!hasHiddenFloats && + f->IsHiddenByContentVisibilityOfInFlowParentForLayout()) { + hasHiddenFloats = true; + } + storedFloats.AppendElement(f); + if (i < lineFloats.Length() && lineFloats.ElementAt(i) != f) { + equal = false; + } + ++i; + } + + if ((!equal || lineFloats.Length() != storedFloats.Length()) && + !anyLineDirty && !hasHiddenFloats) { + NS_ERROR( + "nsBlockFrame::CheckFloats: Explicit float list is out of sync with " + "float cache"); +# if defined(DEBUG_roc) + nsIFrame::RootFrameList(PresContext(), stdout, 0); + for (i = 0; i < lineFloats.Length(); ++i) { + printf("Line float: %p\n", lineFloats.ElementAt(i)); + } + for (i = 0; i < storedFloats.Length(); ++i) { + printf("Stored float: %p\n", storedFloats.ElementAt(i)); + } +# endif + } +#endif + + const nsFrameList* oofs = GetOverflowOutOfFlows(); + if (oofs && oofs->NotEmpty()) { + // Floats that were pushed should be removed from our float + // manager. Otherwise the float manager's YMost or XMost might + // be larger than necessary, causing this block to get an + // incorrect desired height (or width). Some of these floats + // may not actually have been added to the float manager because + // they weren't reflowed before being pushed; that's OK, + // RemoveRegions will ignore them. It is safe to do this here + // because we know from here on the float manager will only be + // used for its XMost and YMost, not to place new floats and + // lines. + aState.FloatManager()->RemoveTrailingRegions(oofs->FirstChild()); + } +} + +void nsBlockFrame::IsMarginRoot(bool* aBStartMarginRoot, + bool* aBEndMarginRoot) { + nsIFrame* parent = GetParent(); + if (!HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS)) { + if (!parent || parent->IsFloatContainingBlock()) { + *aBStartMarginRoot = false; + *aBEndMarginRoot = false; + return; + } + } + + if (parent && parent->IsColumnSetFrame()) { + // The first column is a start margin root and the last column is an end + // margin root. (If the column-set is split by a column-span:all box then + // the first and last column in each column-set fragment are margin roots.) + *aBStartMarginRoot = GetPrevInFlow() == nullptr; + *aBEndMarginRoot = GetNextInFlow() == nullptr; + return; + } + + *aBStartMarginRoot = true; + *aBEndMarginRoot = true; +} + +/* static */ +bool nsBlockFrame::BlockNeedsFloatManager(nsIFrame* aBlock) { + MOZ_ASSERT(aBlock, "Must have a frame"); + NS_ASSERTION(aBlock->IsBlockFrameOrSubclass(), "aBlock must be a block"); + + nsIFrame* parent = aBlock->GetParent(); + return aBlock->HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS) || + (parent && !parent->IsFloatContainingBlock()); +} + +/* static */ +bool nsBlockFrame::BlockCanIntersectFloats(nsIFrame* aFrame) { + return aFrame->IsBlockFrameOrSubclass() && !aFrame->IsReplaced() && + !aFrame->HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS); +} + +// Note that this width can vary based on the vertical position. +// However, the cases where it varies are the cases where the width fits +// in the available space given, which means that variation shouldn't +// matter. +/* static */ +nsBlockFrame::FloatAvoidingISizeToClear nsBlockFrame::ISizeToClearPastFloats( + const BlockReflowState& aState, const LogicalRect& aFloatAvailableSpace, + nsIFrame* aFloatAvoidingBlock) { + nscoord inlineStartOffset, inlineEndOffset; + WritingMode wm = aState.mReflowInput.GetWritingMode(); + + FloatAvoidingISizeToClear result; + aState.ComputeFloatAvoidingOffsets(aFloatAvoidingBlock, aFloatAvailableSpace, + inlineStartOffset, inlineEndOffset); + nscoord availISize = + aState.mContentArea.ISize(wm) - inlineStartOffset - inlineEndOffset; + + // We actually don't want the min width here; see bug 427782; we only + // want to displace if the width won't compute to a value small enough + // to fit. + // All we really need here is the result of ComputeSize, and we + // could *almost* get that from an SizeComputationInput, except for the + // last argument. + WritingMode frWM = aFloatAvoidingBlock->GetWritingMode(); + LogicalSize availSpace = + LogicalSize(wm, availISize, NS_UNCONSTRAINEDSIZE).ConvertTo(frWM, wm); + ReflowInput reflowInput(aState.mPresContext, aState.mReflowInput, + aFloatAvoidingBlock, availSpace); + result.borderBoxISize = + reflowInput.ComputedSizeWithBorderPadding(wm).ISize(wm); + + // Use the margins from sizingInput rather than reflowInput so that + // they aren't reduced by ignoring margins in overconstrained cases. + SizeComputationInput sizingInput(aFloatAvoidingBlock, + aState.mReflowInput.mRenderingContext, wm, + aState.mContentArea.ISize(wm)); + const LogicalMargin computedMargin = sizingInput.ComputedLogicalMargin(wm); + + nscoord marginISize = computedMargin.IStartEnd(wm); + const auto& iSize = reflowInput.mStylePosition->ISize(wm); + if (marginISize < 0 && (iSize.IsAuto() || iSize.IsMozAvailable())) { + // If we get here, floatAvoidingBlock has a negative amount of inline-axis + // margin and an 'auto' (or ~equivalently, -moz-available) inline + // size. Under these circumstances, we use the margin to establish a + // (positive) minimum size for the border-box, in order to satisfy the + // equation in CSS2 10.3.3. That equation essentially simplifies to the + // following: + // + // iSize of margins + iSize of borderBox = iSize of containingBlock + // + // ...where "iSize of borderBox" is the sum of floatAvoidingBlock's + // inline-axis components of border, padding, and {width,height}. + // + // Right now, in the above equation, "iSize of margins" is the only term + // that we know for sure. (And we also know that it's negative, since we + // got here.) The other terms are as-yet unresolved, since the frame has an + // 'auto' iSize, and since we aren't yet sure if we'll clear this frame + // beyond floats or place it alongside them. + // + // However: we *do* know that the equation's "iSize of containingBlock" + // term *must* be non-negative, since boxes' widths and heights generally + // can't be negative in CSS. To satisfy that requirement, we can then + // infer that the equation's "iSize of borderBox" term *must* be large + // enough to cancel out the (known-to-be-negative) "iSize of margins" + // term. Therefore, marginISize value (negated to make it positive) + // establishes a lower-bound for how much inline-axis space our border-box + // will really require in order to fit alongside any floats. + // + // XXXdholbert This explanation is admittedly a bit hand-wavy and may not + // precisely match what any particular spec requires. It's the best + // reasoning I could come up with to explain engines' behavior. Also, our + // behavior with -moz-available doesn't seem particularly correct here, per + // bug 1767217, though that's probably due to a bug elsewhere in our float + // handling code... + result.borderBoxISize = std::max(result.borderBoxISize, -marginISize); + } + + result.marginIStart = computedMargin.IStart(wm); + return result; +} + +/* static */ +nsBlockFrame* nsBlockFrame::GetNearestAncestorBlock(nsIFrame* aCandidate) { + nsBlockFrame* block = nullptr; + while (aCandidate) { + block = do_QueryFrame(aCandidate); + if (block) { + // yay, candidate is a block! + return block; + } + // Not a block. Check its parent next. + aCandidate = aCandidate->GetParent(); + } + MOZ_ASSERT_UNREACHABLE("Fell off frame tree looking for ancestor block!"); + return nullptr; +} + +nscoord nsBlockFrame::ComputeFinalBSize(BlockReflowState& aState, + nscoord aBEndEdgeOfChildren) { + const WritingMode wm = aState.mReflowInput.GetWritingMode(); + + const nscoord effectiveContentBoxBSize = + GetEffectiveComputedBSize(aState.mReflowInput, aState.mConsumedBSize); + const nscoord blockStartBP = aState.BorderPadding().BStart(wm); + const nscoord blockEndBP = aState.BorderPadding().BEnd(wm); + + NS_ASSERTION( + !IsTrueOverflowContainer() || (effectiveContentBoxBSize == 0 && + blockStartBP == 0 && blockEndBP == 0), + "An overflow container's effective content-box block-size, block-start " + "BP, and block-end BP should all be zero!"); + + const nscoord effectiveContentBoxBSizeWithBStartBP = + NSCoordSaturatingAdd(blockStartBP, effectiveContentBoxBSize); + const nscoord effectiveBorderBoxBSize = + NSCoordSaturatingAdd(effectiveContentBoxBSizeWithBStartBP, blockEndBP); + + if (HasColumnSpanSiblings()) { + MOZ_ASSERT(LastInFlow()->GetNextContinuation(), + "Frame constructor should've created column-span siblings!"); + + // If a block is split by any column-spans, we calculate the final + // block-size by shrinkwrapping our children's block-size for all the + // fragments except for those after the final column-span, but we should + // take no more than our effective border-box block-size. If there's any + // leftover block-size, our next continuations will take up rest. + // + // We don't need to adjust aBri.mReflowStatus because our children's status + // is the same as ours. + return std::min(effectiveBorderBoxBSize, aBEndEdgeOfChildren); + } + + const nscoord availBSize = aState.mReflowInput.AvailableBSize(); + if (availBSize == NS_UNCONSTRAINEDSIZE) { + return effectiveBorderBoxBSize; + } + + // Save our children's reflow status. + const bool isChildStatusComplete = aState.mReflowStatus.IsComplete(); + if (isChildStatusComplete && effectiveContentBoxBSize > 0 && + effectiveBorderBoxBSize > availBSize && + ShouldAvoidBreakInside(aState.mReflowInput)) { + aState.mReflowStatus.SetInlineLineBreakBeforeAndReset(); + return effectiveBorderBoxBSize; + } + + const bool isBDBClone = + aState.mReflowInput.mStyleBorder->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone; + + // The maximum value our content-box block-size can take within the given + // available block-size. + const nscoord maxContentBoxBSize = aState.ContentBSize(); + + // The block-end edge of our content-box (relative to this frame's origin) if + // we consumed the maximum block-size available to us (maxContentBoxBSize). + const nscoord maxContentBoxBEnd = aState.ContentBEnd(); + + // These variables are uninitialized intentionally so that the compiler can + // check they are assigned in every if-else branch below. + nscoord finalContentBoxBSizeWithBStartBP; + bool isOurStatusComplete; + + if (effectiveBorderBoxBSize <= availBSize) { + // Our effective border-box block-size can fit in the available block-size, + // so we are complete. + finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP; + isOurStatusComplete = true; + } else if (effectiveContentBoxBSizeWithBStartBP <= maxContentBoxBEnd) { + // Note: The following assertion should generally hold because, for + // box-decoration-break:clone, this "else if" branch is mathematically + // equivalent to the initial "if". + NS_ASSERTION(!isBDBClone, + "This else-if branch is handling a situation that's specific " + "to box-decoration-break:slice, i.e. a case when we can skip " + "our block-end border and padding!"); + + // Our effective content-box block-size plus the block-start border and + // padding can fit in the available block-size, but it cannot fit after + // adding the block-end border and padding. Thus, we need a continuation + // (unless we already weren't asking for any block-size, in which case we + // stay complete to avoid looping forever). + finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP; + isOurStatusComplete = effectiveContentBoxBSize == 0; + } else { + // We aren't going to be able to fit our content-box in the space available + // to it, which means we'll probably call ourselves incomplete to request a + // continuation. But before making that decision, we check for certain + // conditions which would force us to overflow beyond the available space -- + // these might result in us actually being complete if we're forced to + // overflow far enough. + if (MOZ_UNLIKELY(aState.mReflowInput.mFlags.mIsTopOfPage && isBDBClone && + maxContentBoxBSize <= 0 && + aBEndEdgeOfChildren == blockStartBP)) { + // In this rare case, we are at the top of page/column, we have + // box-decoration-break:clone and zero available block-size for our + // content-box (e.g. our own block-start border and padding already exceed + // the available block-size), and we didn't lay out any child to consume + // our content-box block-size. To ensure we make progress (avoid looping + // forever), use 1px as our content-box block-size regardless of our + // effective content-box block-size, in the spirit of + // https://drafts.csswg.org/css-break/#breaking-rules. + finalContentBoxBSizeWithBStartBP = blockStartBP + AppUnitsPerCSSPixel(); + isOurStatusComplete = effectiveContentBoxBSize <= AppUnitsPerCSSPixel(); + } else if (aBEndEdgeOfChildren > maxContentBoxBEnd) { + // We have a unbreakable child whose block-end edge exceeds the available + // block-size for children. + if (aBEndEdgeOfChildren >= effectiveContentBoxBSizeWithBStartBP) { + // The unbreakable child's block-end edge forces us to consume all of + // our effective content-box block-size. + finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP; + + // Even though we've consumed all of our effective content-box + // block-size, we may still need to report an incomplete status in order + // to get another continuation, which will be responsible for laying out + // & drawing our block-end border & padding. But if we have no such + // border & padding, or if we're forced to apply that border & padding + // on this frame due to box-decoration-break:clone, then we don't need + // to bother with that additional continuation. + isOurStatusComplete = (isBDBClone || blockEndBP == 0); + } else { + // The unbreakable child's block-end edge doesn't force us to consume + // all of our effective content-box block-size. + finalContentBoxBSizeWithBStartBP = aBEndEdgeOfChildren; + isOurStatusComplete = false; + } + } else { + // The children's block-end edge can fit in the content-box space that we + // have available for it. Consume all the space that is available so that + // our inline-start/inline-end borders extend all the way to the block-end + // edge of column/page. + finalContentBoxBSizeWithBStartBP = maxContentBoxBEnd; + isOurStatusComplete = false; + } + } + + nscoord finalBorderBoxBSize = finalContentBoxBSizeWithBStartBP; + if (isOurStatusComplete) { + finalBorderBoxBSize = NSCoordSaturatingAdd(finalBorderBoxBSize, blockEndBP); + if (isChildStatusComplete) { + // We want to use children's reflow status as ours, which can be overflow + // incomplete. Suppress the urge to call aBri.mReflowStatus.Reset() here. + } else { + aState.mReflowStatus.SetOverflowIncomplete(); + } + } else { + NS_ASSERTION(!IsTrueOverflowContainer(), + "An overflow container should always be complete because of " + "its zero border-box block-size!"); + if (isBDBClone) { + finalBorderBoxBSize = + NSCoordSaturatingAdd(finalBorderBoxBSize, blockEndBP); + } + aState.mReflowStatus.SetIncomplete(); + if (!GetNextInFlow()) { + aState.mReflowStatus.SetNextInFlowNeedsReflow(); + } + } + + return finalBorderBoxBSize; +} + +nsresult nsBlockFrame::ResolveBidi() { + NS_ASSERTION(!GetPrevInFlow(), + "ResolveBidi called on non-first continuation"); + MOZ_ASSERT(PresContext()->BidiEnabled()); + return nsBidiPresUtils::Resolve(this); +} + +void nsBlockFrame::UpdatePseudoElementStyles(ServoRestyleState& aRestyleState) { + // first-letter needs to be updated before first-line, because first-line can + // change the style of the first-letter. + if (HasFirstLetterChild()) { + UpdateFirstLetterStyle(aRestyleState); + } + + if (nsIFrame* firstLineFrame = GetFirstLineFrame()) { + nsIFrame* styleParent = CorrectStyleParentFrame(firstLineFrame->GetParent(), + PseudoStyleType::firstLine); + + ComputedStyle* parentStyle = styleParent->Style(); + RefPtr<ComputedStyle> firstLineStyle = + aRestyleState.StyleSet().ResolvePseudoElementStyle( + *mContent->AsElement(), PseudoStyleType::firstLine, nullptr, + parentStyle); + + // FIXME(bz): Can we make first-line continuations be non-inheriting anon + // boxes? + RefPtr<ComputedStyle> continuationStyle = + aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle( + PseudoStyleType::mozLineFrame, parentStyle); + + UpdateStyleOfOwnedChildFrame(firstLineFrame, firstLineStyle, aRestyleState, + Some(continuationStyle.get())); + + // We also want to update the styles of the first-line's descendants. We + // don't need to compute a changehint for this, though, since any changes to + // them are handled by the first-line anyway. + RestyleManager* manager = PresContext()->RestyleManager(); + for (nsIFrame* kid : firstLineFrame->PrincipalChildList()) { + manager->ReparentComputedStyleForFirstLine(kid); + } + } +} + +nsIFrame* nsBlockFrame::GetFirstLetter() const { + if (!HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE)) { + // Certainly no first-letter frame. + return nullptr; + } + + return GetProperty(FirstLetterProperty()); +} + +nsIFrame* nsBlockFrame::GetFirstLineFrame() const { + nsIFrame* maybeFirstLine = PrincipalChildList().FirstChild(); + if (maybeFirstLine && maybeFirstLine->IsLineFrame()) { + return maybeFirstLine; + } + + return nullptr; +} + +#ifdef DEBUG +void nsBlockFrame::VerifyLines(bool aFinalCheckOK) { + if (!gVerifyLines) { + return; + } + if (mLines.empty()) { + return; + } + + nsLineBox* cursor = GetLineCursorForQuery(); + + // Add up the counts on each line. Also validate that IsFirstLine is + // set properly. + int32_t count = 0; + for (const auto& line : Lines()) { + if (&line == cursor) { + cursor = nullptr; + } + if (aFinalCheckOK) { + MOZ_ASSERT(line.GetChildCount(), "empty line"); + if (line.IsBlock()) { + NS_ASSERTION(1 == line.GetChildCount(), "bad first line"); + } + } + count += line.GetChildCount(); + } + + // Then count the frames + int32_t frameCount = 0; + nsIFrame* frame = mLines.front()->mFirstChild; + while (frame) { + frameCount++; + frame = frame->GetNextSibling(); + } + NS_ASSERTION(count == frameCount, "bad line list"); + + // Next: test that each line has right number of frames on it + for (LineIterator line = LinesBegin(), line_end = LinesEnd(); + line != line_end;) { + count = line->GetChildCount(); + frame = line->mFirstChild; + while (--count >= 0) { + frame = frame->GetNextSibling(); + } + ++line; + if ((line != line_end) && (0 != line->GetChildCount())) { + NS_ASSERTION(frame == line->mFirstChild, "bad line list"); + } + } + + if (cursor) { + FrameLines* overflowLines = GetOverflowLines(); + if (overflowLines) { + LineIterator line = overflowLines->mLines.begin(); + LineIterator line_end = overflowLines->mLines.end(); + for (; line != line_end; ++line) { + if (line == cursor) { + cursor = nullptr; + break; + } + } + } + } + NS_ASSERTION(!cursor, "stale LineCursorProperty"); +} + +void nsBlockFrame::VerifyOverflowSituation() { + // Overflow out-of-flows must not have a next-in-flow in mFloats or mFrames. + nsFrameList* oofs = GetOverflowOutOfFlows(); + if (oofs) { + for (nsIFrame* f : *oofs) { + nsIFrame* nif = f->GetNextInFlow(); + MOZ_ASSERT(!nif || + (!mFloats.ContainsFrame(nif) && !mFrames.ContainsFrame(nif))); + } + } + + // Pushed floats must not have a next-in-flow in mFloats or mFrames. + oofs = GetPushedFloats(); + if (oofs) { + for (nsIFrame* f : *oofs) { + nsIFrame* nif = f->GetNextInFlow(); + MOZ_ASSERT(!nif || + (!mFloats.ContainsFrame(nif) && !mFrames.ContainsFrame(nif))); + } + } + + // A child float next-in-flow's parent must be |this| or a next-in-flow of + // |this|. Later next-in-flows must have the same or later parents. + ChildListID childLists[] = {FrameChildListID::Float, + FrameChildListID::PushedFloats}; + for (size_t i = 0; i < ArrayLength(childLists); ++i) { + const nsFrameList& children = GetChildList(childLists[i]); + for (nsIFrame* f : children) { + nsIFrame* parent = this; + nsIFrame* nif = f->GetNextInFlow(); + for (; nif; nif = nif->GetNextInFlow()) { + bool found = false; + for (nsIFrame* p = parent; p; p = p->GetNextInFlow()) { + if (nif->GetParent() == p) { + parent = p; + found = true; + break; + } + } + MOZ_ASSERT( + found, + "next-in-flow is a child of parent earlier in the frame tree?"); + } + } + } + + nsBlockFrame* flow = static_cast<nsBlockFrame*>(FirstInFlow()); + while (flow) { + FrameLines* overflowLines = flow->GetOverflowLines(); + if (overflowLines) { + NS_ASSERTION(!overflowLines->mLines.empty(), + "should not be empty if present"); + NS_ASSERTION(overflowLines->mLines.front()->mFirstChild, + "bad overflow lines"); + NS_ASSERTION(overflowLines->mLines.front()->mFirstChild == + overflowLines->mFrames.FirstChild(), + "bad overflow frames / lines"); + } + auto checkCursor = [&](nsLineBox* cursor) -> bool { + if (!cursor) { + return true; + } + LineIterator line = flow->LinesBegin(); + LineIterator line_end = flow->LinesEnd(); + for (; line != line_end && line != cursor; ++line) + ; + if (line == line_end && overflowLines) { + line = overflowLines->mLines.begin(); + line_end = overflowLines->mLines.end(); + for (; line != line_end && line != cursor; ++line) + ; + } + return line != line_end; + }; + MOZ_ASSERT(checkCursor(flow->GetLineCursorForDisplay()), + "stale LineCursorPropertyDisplay"); + MOZ_ASSERT(checkCursor(flow->GetLineCursorForQuery()), + "stale LineCursorPropertyQuery"); + flow = static_cast<nsBlockFrame*>(flow->GetNextInFlow()); + } +} + +int32_t nsBlockFrame::GetDepth() const { + int32_t depth = 0; + nsIFrame* parent = GetParent(); + while (parent) { + parent = parent->GetParent(); + depth++; + } + return depth; +} + +already_AddRefed<ComputedStyle> nsBlockFrame::GetFirstLetterStyle( + nsPresContext* aPresContext) { + return aPresContext->StyleSet()->ProbePseudoElementStyle( + *mContent->AsElement(), PseudoStyleType::firstLetter, nullptr, Style()); +} +#endif |