Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/layout/generic/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 343 kB image not shown  

Quelle  nsBlockFrame.cpp   Sprache: C

 
/* -*- 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/Likely.h"
#include "mozilla/Maybe.h"
#include "mozilla/PresShell.h"
#include "mozilla/ScrollContainerFrame.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/SVGUtils.h"
#include "mozilla/ToString.h"
#include "mozilla/UniquePtr.h"

#include "nsCRT.h"
#include "nsCOMPtr.h"
#include "nsCSSRendering.h"
#include "nsAbsoluteContainingBlock.h"
#include "nsBlockReflowContext.h"
#include "BlockReflowState.h"
#include "nsFontMetrics.h"
#include "nsGenericHTMLElement.h"
#include "nsLineBox.h"
#include "nsLineLayout.h"
#include "nsPlaceholderFrame.h"
#include "nsStyleConsts.h"
#include "nsFrameManager.h"
#include "nsPresContext.h"
#include "nsPresContextInlines.h"
#include "nsHTMLParts.h"
#include "nsGkAtoms.h"
#include "mozilla/Sprintf.h"
#include "nsFloatManager.h"
#include "prenv.h"
#include "nsError.h"
#include <algorithm>
#include "nsLayoutUtils.h"
#include "nsDisplayList.h"
#include "nsCSSFrameConstructor.h"
#include "TextOverflow.h"
#include "nsIFrameInlines.h"
#include "CounterStyleManager.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/PresShell.h"
#include "mozilla/RestyleManager.h"
#include "mozilla/ServoStyleSet.h"
#include "nsFlexContainerFrame.h"
#include "nsTextControlFrame.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 MarkAllInlineLinesDirty(nsBlockFrame* aBlock) {
  for (auto& line : aBlock->Lines()) {
    if (line.IsInline()) {
      line.MarkDirty();
    }
  }
}

static void MarkAllDescendantLinesDirty(nsBlockFrame* aBlock) {
  for (auto& line : aBlock->Lines()) {
    if (line.IsBlock()) {
      nsBlockFrame* bf = do_QueryFrame(line.mFirstChild);
      if (bf) {
        MarkAllDescendantLinesDirty(bf);
      }
    }
    line.MarkDirty();
  }
}

static void MarkSameFloatManagerLinesDirty(nsBlockFrame* aBlock) {
  nsBlockFrame* blockWithFloatMgr = aBlock;
  while (!blockWithFloatMgr->HasAnyStateBits(NS_BLOCK_BFC)) {
    nsBlockFrame* bf = do_QueryFrame(blockWithFloatMgr->GetParent());
    if (!bf) {
      break;
    }
    blockWithFloatMgr = bf;
  }

  // Mark every line at and below the line where the float was
  // dirty, and mark their lines dirty too. We could probably do
  // something more efficient --- e.g., just dirty the lines that intersect
  // the float vertically.
  MarkAllDescendantLinesDirty(blockWithFloatMgr);
}

/**
 * Returns true if aFrame is a block that has one or more float children.
 */

static bool BlockHasAnyFloats(nsIFrame* aFrame) {
  nsBlockFrame* block = do_QueryFrame(aFrame);
  if (!block) {
    return false;
  }
  if (block->GetChildList(FrameChildListID::Float).FirstChild()) {
    return true;
  }

  for (const auto& line : block->Lines()) {
    if (line.IsBlock() && BlockHasAnyFloats(line.mFirstChild)) {
      return true;
    }
  }
  return false;
}

// Determines whether the given frame is visible text or has visible text that
// participate in the same line. Frames that are not line participants do not
// have their children checked.
static bool FrameHasVisibleInlineText(nsIFrame* aFrame) {
  MOZ_ASSERT(aFrame, "Frame argument cannot be null");
  if (!aFrame->IsLineParticipant()) {
    return false;
  }
  if (aFrame->IsTextFrame()) {
    return aFrame->StyleVisibility()->IsVisible() &&
           NS_GET_A(aFrame->StyleText()->mWebkitTextFillColor.CalcColor(
               aFrame)) != 0;
  }
  for (nsIFrame* kid : aFrame->PrincipalChildList()) {
    if (FrameHasVisibleInlineText(kid)) {
      return true;
    }
  }
  return false;
}

// Determines whether any of the frames from the given line have visible text.
static bool LineHasVisibleInlineText(nsLineBox* aLine) {
  nsIFrame* kid = aLine->mFirstChild;
  int32_t n = aLine->GetChildCount();
  while (n-- > 0) {
    if (FrameHasVisibleInlineText(kid)) {
      return true;
    }
    kid = kid->GetNextSibling();
  }
  return false;
}

/**
 * Iterates through the frame's in-flow children and
 * unions the ink overflow of all text frames which
 * participate in the line aFrame belongs to.
 * If a child of aFrame is not a text frame,
 * we recurse with the child as the aFrame argument.
 * If aFrame isn't a line participant, we skip it entirely
 * and return an empty rect.
 * The resulting nsRect is offset relative to the parent of aFrame.
 */

static nsRect GetFrameTextArea(nsIFrame* aFrame,
                               nsDisplayListBuilder* aBuilder) {
  nsRect textArea;
  if (const nsTextFrame* textFrame = do_QueryFrame(aFrame)) {
    if (!textFrame->IsEntirelyWhitespace()) {
      textArea = aFrame->InkOverflowRect();
    }
  } else if (aFrame->IsLineParticipant()) {
    for (nsIFrame* kid : aFrame->PrincipalChildList()) {
      nsRect kidTextArea = GetFrameTextArea(kid, aBuilder);
      textArea.OrWith(kidTextArea);
    }
  }
  // add aFrame's position to keep textArea relative to aFrame's parent
  return textArea + aFrame->GetPosition();
}

/**
 * Iterates through the line's children and
 * unions the ink overflow of all text frames.
 * GetFrameTextArea unions and returns the ink overflow
 * from all line-participating text frames within the given child.
 * The nsRect returned from GetLineTextArea is offset
 * relative to the given line.
 */

static nsRect GetLineTextArea(nsLineBox* aLine,
                              nsDisplayListBuilder* aBuilder) {
  nsRect textArea;
  nsIFrame* kid = aLine->mFirstChild;
  int32_t n = aLine->GetChildCount();
  while (n-- > 0) {
    nsRect kidTextArea = GetFrameTextArea(kid, aBuilder);
    textArea.OrWith(kidTextArea);
    kid = kid->GetNextSibling();
  }

  return textArea;
}

/**
 * Starting with aFrame, iterates upward through parent frames and checks for
 * non-transparent background colors. If one is found, we use that as our
 * backplate color. Otheriwse, we use the default background color from
 * our high contrast theme.
 */

static nscolor GetBackplateColor(nsIFrame* aFrame) {
  nsPresContext* pc = aFrame->PresContext();
  nscolor currentBackgroundColor = NS_TRANSPARENT;
  for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
    // NOTE(emilio): We assume themed frames (frame->IsThemed()) have correct
    // background-color information so as to compute the right backplate color.
    //
    // This holds because HTML widgets with author-specified backgrounds or
    // borders disable theming. So as long as the UA-specified background colors
    // match the actual theme (which they should because we always use system
    // colors with the non-native theme, and native system colors should also
    // match the native theme), then we're alright and we should compute an
    // appropriate backplate color.
    const auto* style = frame->Style();
    if (style->StyleBackground()->IsTransparent(style)) {
      continue;
    }
    bool drawImage = false, drawColor = false;
    nscolor backgroundColor = nsCSSRendering::DetermineBackgroundColor(
        pc, style, frame, drawImage, drawColor);
    if (!drawColor && !drawImage) {
      continue;
    }
    if (NS_GET_A(backgroundColor) == 0) {
      // Even if there's a background image, if there's no background color we
      // keep going up the frame tree, see bug 1723938.
      continue;
    }
    if (NS_GET_A(currentBackgroundColor) == 0) {
      // Try to avoid somewhat expensive math in the common case.
      currentBackgroundColor = backgroundColor;
    } else {
      currentBackgroundColor =
          NS_ComposeColors(backgroundColor, currentBackgroundColor);
    }
    if (NS_GET_A(currentBackgroundColor) == 0xff) {
      // If fully opaque, we're done, otherwise keep going up blending with our
      // background.
      return currentBackgroundColor;
    }
  }
  nscolor backgroundColor = aFrame->PresContext()->DefaultBackgroundColor();
  if (NS_GET_A(currentBackgroundColor) == 0) {
    return backgroundColor;
  }
  return NS_ComposeColors(backgroundColor, currentBackgroundColor);
}

static nsRect GetNormalMarginRect(const nsIFrame& aFrame,
                                  bool aIncludePositiveMargins = true) {
  nsMargin m = aFrame.GetUsedMargin().ApplySkipSides(aFrame.GetSkipSides());
  if (!aIncludePositiveMargins) {
    m.EnsureAtMost(nsMargin());
  }
  auto rect = aFrame.GetRectRelativeToSelf();
  rect.Inflate(m);
  return rect + aFrame.GetNormalPosition();
}

#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(FloatsProperty)
NS_DECLARE_FRAME_PROPERTY_FRAMELIST(PushedFloatsProperty)
NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OutsideMarkerProperty)
NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(InsideMarkerProperty, nsIFrame)

//----------------------------------------------------------------------

nsBlockFrame* NS_NewBlockFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
  return new (aPresShell) nsBlockFrame(aStyle, aPresShell->GetPresContext());
}

NS_IMPL_FRAMEARENA_HELPERS(nsBlockFrame)

nsBlockFrame::~nsBlockFrame() = default;

void nsBlockFrame::AddSizeOfExcludingThisForTree(
    nsWindowSizes& aWindowSizes) const {
  nsContainerFrame::AddSizeOfExcludingThisForTree(aWindowSizes);

  // Add the size of any nsLineBox::mFrames hashtables we might have:
  for (const auto& line : Lines()) {
    line.AddSizeOfExcludingThis(aWindowSizes);
  }
  const FrameLines* overflowLines = GetOverflowLines();
  if (overflowLines) {
    ConstLineIterator line = overflowLines->mLines.begin(),
                      line_end = overflowLines->mLines.end();
    for (; line != line_end; ++line) {
      line->AddSizeOfExcludingThis(aWindowSizes);
    }
  }
}

void nsBlockFrame::Destroy(DestroyContext& aContext) {
  ClearLineCursors();
  DestroyAbsoluteFrames(aContext);
  nsPresContext* presContext = PresContext();
  mozilla::PresShell* presShell = presContext->PresShell();
  if (HasFloats()) {
    SafelyDestroyFrameListProp(aContext, presShell, FloatsProperty());
    RemoveStateBits(NS_BLOCK_HAS_FLOATS);
  }
  nsLineBox::DeleteLineList(presContext, mLines, &mFrames, aContext);

  if (HasPushedFloats()) {
    SafelyDestroyFrameListProp(aContext, presShell, PushedFloatsProperty());
    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 (HasMarker()) {
    SafelyDestroyFrameListProp(aContext, presShell, OutsideMarkerProperty());
    RemoveStateBits(NS_BLOCK_HAS_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 {
  const auto wm = GetWritingMode();
  if (!mLines.empty()) {
    ConstLineIterator line = LinesBegin();
    if (!line->IsEmpty()) {
      if (line->IsBlock()) {
        return GetLogicalUsedBorderAndPadding(wm).BStart(wm) +
               line->mFirstChild->GetCaretBaseline();
      }
      return line->BStart() + line->GetLogicalAscent();
    }
  }
  return GetFontMetricsDerivedCaretBaseline(ContentBSize(wm));
}

/////////////////////////////////////////////////////////////////////////////
// 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::OverflowOutOfFlow: {
      const nsFrameList* list = GetOverflowOutOfFlows();
      return list ? *list : nsFrameList::EmptyList();
    }
    case FrameChildListID::Float: {
      const nsFrameList* list = GetFloats();
      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);
  }
  if (const nsFrameList* list = GetOverflowOutOfFlows()) {
    list->AppendIfNonempty(aLists, FrameChildListID::OverflowOutOfFlow);
  }
  if (const nsFrameList* list = GetOutsideMarkerList()) {
    list->AppendIfNonempty(aLists, FrameChildListID::Bullet);
  }
  if (const nsFrameList* list = GetFloats()) {
    list->AppendIfNonempty(aLists, FrameChildListID::Float);
  }
  if (const nsFrameList* list = GetPushedFloats()) {
    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) {
  LineListIterator removedLine = aFromLines.begin();
  *aOutLine = removedLine;
  LineListIterator 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->mCachedIntrinsics.Clear();
  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_INTRINSICS_INFLATED)) {
    mCachedIntrinsics.Clear();
    AddOrRemoveStateBits(NS_BLOCK_INTRINSICS_INFLATED, inflationEnabled);
  }
}

// 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;
}

nscoord nsBlockFrame::IntrinsicISize(const IntrinsicSizeInput& aInput,
                                     IntrinsicISizeType aType) {
  nsIFrame* firstCont = FirstContinuation();
  if (firstCont != this) {
    return firstCont->IntrinsicISize(aInput, aType);
  }

  CheckIntrinsicCacheAgainstShrinkWrapState();

  return mCachedIntrinsics.GetOrSet(*this, aType, aInput, [&] {
    return aType == IntrinsicISizeType::MinISize ? MinISize(aInput)
                                                 : PrefISize(aInput);
  });
}

/* virtual */
nscoord nsBlockFrame::MinISize(const IntrinsicSizeInput& aInput) {
  if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
    return *containISize;
  }

#ifdef DEBUG
  if (gNoisyIntrinsic) {
    IndentBy(stdout, gNoiseIndent);
    ListTag(stdout);
    printf(": MinISize\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();
        nsIFrame* kid = line->mFirstChild;
        const IntrinsicSizeInput kidInput(aInput, kid->GetWritingMode(),
                                          GetWritingMode());
        data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(
            kidInput.mContext, kid, IntrinsicISizeType::MinISize,
            kidInput.mPercentageBasisForChildren);
        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()) {
          const IntrinsicSizeInput kidInput(aInput, kid->GetWritingMode(),
                                            GetWritingMode());
          kid->AddInlineMinISize(kidInput, &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();
  return data.mPrevLines;
}

/* virtual */
nscoord nsBlockFrame::PrefISize(const IntrinsicSizeInput& aInput) {
  if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
    return *containISize;
  }

#ifdef DEBUG
  if (gNoisyIntrinsic) {
    IndentBy(stdout, gNoiseIndent);
    ListTag(stdout);
    printf(": PrefISize\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()) {
        nsIFrame* kid = line->mFirstChild;
        UsedClear clearType;
        if (!data.mLineIsEmpty || BlockCanIntersectFloats(kid)) {
          clearType = UsedClear::Both;
        } else {
          clearType = kid->StyleDisplay()->UsedClear(GetWritingMode());
        }
        data.ForceBreak(clearType);
        const IntrinsicSizeInput kidInput(aInput, kid->GetWritingMode(),
                                          GetWritingMode());
        data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(
            kidInput.mContext, kid, IntrinsicISizeType::PrefISize,
            kidInput.mPercentageBasisForChildren);
        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()) {
          const IntrinsicSizeInput kidInput(aInput, kid->GetWritingMode(),
                                            GetWritingMode());
          kid->AddInlinePrefISize(kidInput, &data);
        }
      }
#ifdef DEBUG
      if (gNoisyIntrinsic) {
        IndentBy(stdout, gNoiseIndent);
        printf("pref: [prevLines=%d currentLine=%d]\n", data.mPrevLines,
               data.mCurrentLine);
      }
#endif
    }
  }
  data.ForceBreak();
  return data.mPrevLines;
}

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;
        // Per comment in nsIFrame::GetPrefWidthTightBounds(), the function is
        // only implemented for nsBlockFrame and nsTextFrame and is used to
        // determine the intrinsic inline sizes of MathML token elements. These
        // elements shouldn't have percentage block sizes that require a
        // percentage basis for resolution.
        const IntrinsicSizeInput kidInput(aRenderingContext, Nothing(),
                                          Nothing());
        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(kidInput, &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 (lastRI->mFrame->IsScrollContainerOrSubclass()) {
    // Assert that we're not missing any frames between the abspos containing
    // block and the scroll container.
    // the parent.
    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 an in-flow, 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) {
  const nsBlockFrame* block = do_QueryFrame(aFrame);
  if (!block || block->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW | NS_BLOCK_BFC)) {
    return nullptr;
  }
  return block;
}

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)) {
    return false;
  }

  if (StaticPrefs::layout_css_webkit_line_clamp_block_enabled() ||
      aFrame->PresContext()->Document()->ChromeRulesEnabled()) {
    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;
}

nsBlockFrame* nsBlockFrame::GetLineClampRoot() const {
  if (IsLineClampRoot(this)) {
    return const_cast<nsBlockFrame*>(this);
  }
  const nsBlockFrame* cur = this;
  while (GetAsLineClampDescendant(cur)) {
    cur = do_QueryFrame(cur->GetParent());
    if (!cur) {
      break;
    }
    if (IsLineClampRoot(cur)) {
      return const_cast<nsBlockFrame*>(cur);
    }
  }
  return nullptr;
}

bool nsBlockFrame::MaybeHasFloats() const {
  if (HasFloats()) {
    return true;
  }
  if (HasPushedFloats()) {
    return true;
  }
  // For the OverflowOutOfFlowsProperty I think we do enforce that, but it's
  // a mix of out-of-flow frames, so that's why the method name has "Maybe".
  return HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
}

/**
 * Iterator over all descendant inline line boxes, except for those that are
 * under an independent formatting context.
 */

class MOZ_RAII LineClampLineIterator {
 public:
  LineClampLineIterator(nsBlockFrame* aFrame, const nsBlockFrame* aStopAtFrame)
      : mCur(aFrame->LinesBegin()),
        mEnd(aFrame->LinesEnd()),
        mCurrentFrame(mCur == mEnd ? nullptr : aFrame),
        mStopAtFrame(aStopAtFrame) {
    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;
        }
        if (mCurrentFrame == mStopAtFrame) {
          mStack.Clear();
          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;

  // The block past which we can't look at line-clamp.
  const nsBlockFrame* mStopAtFrame;

  // 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->HasLineClampEllipsis()) {
    MOZ_ASSERT(!aFrame->HasLineClampEllipsisDescendant());
    aFrame->SetHasLineClampEllipsis(false);
    for (auto& line : aFrame->Lines()) {
      if (line.HasLineClampEllipsis()) {
        line.ClearHasLineClampEllipsis();
        break;
      }
    }
    return true;
  }

  if (aFrame->HasLineClampEllipsisDescendant()) {
    aFrame->SetHasLineClampEllipsisDescendant(false);
    for (nsIFrame* f : aFrame->PrincipalChildList()) {
      if (nsBlockFrame* child = GetAsLineClampDescendant(f)) {
        if (ClearLineClampEllipsis(child)) {
          return true;
        }
      }
    }
  }

  return false;
}

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");
  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.
  if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
      aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE &&
      ShouldApplyOverflowClipping(aReflowInput.mStyleDisplay)
          .contains(wm.PhysicalAxis(LogicalAxis::Block))) {
    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.
  //
  // See https://bugzilla.mozilla.org/show_bug.cgi?id=1931286:
  // if we're a reflow root and no float manager is provided by the caller
  // in aReflowInput, we'd normally expect the block to be a BFC and so
  // BlockNeedsFloatManager will return true. But sometimes the block may
  // have lost its BFC-ness since it was recorded as a dirty reflow root
  // but before the reflow actually happens. Creating a float manager here
  // avoids crashing, but may not be entirely correct in such a case.
  bool needFloatManager =
      !aReflowInput.mFloatManager || 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, and mBlockCoord is the
    // block-axis offset of its position.
    // If line-clamp is not in use, mContent is null, mOffset is the total
    // number of lines that the block must contain, and mBlockCoord is its end
    // edge in the block direction.
    nsIContent* mContent = nullptr;
    int32_t mOffset = -1;
    nscoord mBlockCoord = 0;

    bool operator==(const BalanceTarget& aOther) const {
      return mContent == aOther.mContent && mOffset == aOther.mOffset &&
             mBlockCoord == aOther.mBlockCoord;
    }
    bool operator!=(const BalanceTarget& aOther) const {
      return !(*this == aOther);
    }
  };

  BalanceTarget balanceTarget;

  // Helpers for text-wrap: balance implementation:

  // Count the number of inline lines in the mLines list, but return -1 (to
  // suppress balancing) instead if the count is going to exceed aLimit.
  auto countLinesUpTo = [&](int32_t aLimit) -> int32_t {
    int32_t n = 0;
    for (auto iter = mLines.begin(); iter != mLines.end(); ++iter) {
      // Block lines are ignored as they do not participate in balancing.
      if (iter->IsInline() && ++n > aLimit) {
        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, iter.get()->BStart()};
  };

  // "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;
      }
      balanceTarget.mBlockCoord = mLines.back()->BEnd();
      // 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 &&
             mLines.back()->BEnd() == balanceTarget.mBlockCoord;
    };

    // 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();
      if (nsFrameList* floats = GetFloats()) {
        for (nsIFrame* f : *floats) {
          f->MovePositionBy(physicalDelta);
          ConsiderChildOverflow(trialState.mFcBounds, f);
        }
      }
      if (nsFrameList* markerList = GetOutsideMarkerList()) {
        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, 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();
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=86 H=88 G=86

¤ Dauer der Verarbeitung: 0.22 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.