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 451 kB image not shown  

Quelle  nsIFrame.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/. */


/* base class of all rendering objects */

#include "nsIFrame.h"

#include <stdarg.h>
#include <algorithm>

#include "gfx2DGlue.h"
#include "gfxUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/CaretAssociationHint.h"
#include "mozilla/ComputedStyle.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/DisplayPortUtils.h"
#include "mozilla/EventForwards.h"
#include "mozilla/FocusModel.h"
#include "mozilla/dom/CSSAnimation.h"
#include "mozilla/dom/CSSTransition.h"
#include "mozilla/dom/ContentVisibilityAutoStateChangeEvent.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/AncestorIterator.h"
#include "mozilla/dom/ElementInlines.h"
#include "mozilla/dom/ImageTracker.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/PathHelpers.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/intl/BidiEmbeddingLevel.h"
#include "mozilla/Maybe.h"
#include "mozilla/PresShell.h"
#include "mozilla/PresShellInlines.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/ScrollContainerFrame.h"
#include "mozilla/SelectionMovementUtils.h"
#include "mozilla/Sprintf.h"
#include "mozilla/StaticAnalysisFunctions.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/StaticPrefs_print.h"
#include "mozilla/StaticPrefs_ui.h"
#include "mozilla/SVGMaskFrame.h"
#include "mozilla/SVGObserverUtils.h"
#include "mozilla/SVGTextFrame.h"
#include "mozilla/SVGIntegrationUtils.h"
#include "mozilla/SVGUtils.h"
#include "mozilla/TextControlElement.h"
#include "mozilla/ToString.h"
#include "mozilla/Try.h"
#include "mozilla/ViewportUtils.h"
#include "mozilla/WritingModes.h"

#include "nsCOMPtr.h"
#include "nsFieldSetFrame.h"
#include "nsFlexContainerFrame.h"
#include "nsFocusManager.h"
#include "nsFrameList.h"
#include "nsTextControlFrame.h"
#include "nsPlaceholderFrame.h"
#include "nsIBaseWindow.h"
#include "nsIContent.h"
#include "nsIContentInlines.h"
#include "nsContentUtils.h"
#include "nsCSSFrameConstructor.h"
#include "nsCSSProps.h"
#include "nsCSSPseudoElements.h"
#include "nsCSSRendering.h"
#include "nsAtom.h"
#include "nsString.h"
#include "nsReadableUtils.h"
#include "nsTableWrapperFrame.h"
#include "nsView.h"
#include "nsViewManager.h"
#include "nsPresContext.h"
#include "nsPresContextInlines.h"
#include "nsStyleConsts.h"
#include "mozilla/Logging.h"
#include "nsLayoutUtils.h"
#include "LayoutLogging.h"
#include "mozilla/RestyleManager.h"
#include "nsImageFrame.h"
#include "nsInlineFrame.h"
#include "nsFrameSelection.h"
#include "nsGkAtoms.h"
#include "nsGridContainerFrame.h"
#include "nsCSSAnonBoxes.h"
#include "nsCanvasFrame.h"

#include "nsFieldSetFrame.h"
#include "nsFrameTraversal.h"
#include "nsRange.h"
#include "nsNameSpaceManager.h"
#include "nsIPercentBSizeObserver.h"
#include "nsStyleStructInlines.h"

#include "nsBidiPresUtils.h"
#include "RubyUtils.h"
#include "TextOverflow.h"
#include "nsAnimationManager.h"

// For triple-click pref
#include "imgIRequest.h"
#include "nsError.h"
#include "nsContainerFrame.h"
#include "nsBlockFrame.h"
#include "nsDisplayList.h"
#include "nsChangeHint.h"
#include "nsSubDocumentFrame.h"
#include "RetainedDisplayListBuilder.h"

#include "gfxContext.h"
#include "nsAbsoluteContainingBlock.h"
#include "ScrollSnap.h"
#include "StickyScrollContainer.h"
#include "nsFontInflationData.h"
#include "nsRegion.h"
#include "nsIFrameInlines.h"
#include "nsStyleChangeList.h"
#include "nsWindowSizes.h"

#ifdef ACCESSIBILITY
#  include "nsAccessibilityService.h"
#endif

#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/CSSClipPathInstance.h"
#include "mozilla/EffectCompositor.h"
#include "mozilla/EffectSet.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/Preferences.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/ServoStyleSetInlines.h"
#include "mozilla/css/ImageLoader.h"
#include "mozilla/dom/HTMLBodyElement.h"
#include "mozilla/dom/SVGPathData.h"
#include "mozilla/dom/TouchEvent.h"
#include "mozilla/gfx/Tools.h"
#include "mozilla/layers/WebRenderUserData.h"
#include "mozilla/layout/ScrollAnchorContainer.h"
#include "nsPrintfCString.h"
#include "ActiveLayerTracker.h"

#include "nsITheme.h"

using namespace mozilla;
using namespace mozilla::css;
using namespace mozilla::dom;
using namespace mozilla::gfx;
using namespace mozilla::layers;
using namespace mozilla::layout;
typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags;
using nsStyleTransformMatrix::TransformReferenceBox;

nsIFrame* nsILineIterator::LineInfo::GetLastFrameOnLine() const {
  if (!mNumFramesOnLine) {
    return nullptr;  // empty line, not illegal
  }
  MOZ_ASSERT(mFirstFrameOnLine);
  nsIFrame* maybeLastFrame = mFirstFrameOnLine;
  for ([[maybe_unused]] int32_t i : IntegerRange(mNumFramesOnLine - 1)) {
    maybeLastFrame = maybeLastFrame->GetNextSibling();
    if (NS_WARN_IF(!maybeLastFrame)) {
      return nullptr;
    }
  }
  return maybeLastFrame;
}

#ifdef HAVE_64BIT_BUILD
static_assert(sizeof(nsIFrame) == 120, "nsIFrame should remain small");
#else
static_assert(sizeof(void*) == 4, "Odd build config?");
// FIXME(emilio): Investigate why win32 and android-arm32 have bigger sizes (80)
// than Linux32 (76).
static_assert(sizeof(nsIFrame) <= 80, "nsIFrame should remain small");
#endif

const mozilla::LayoutFrameType nsIFrame::sLayoutFrameTypes[kFrameClassCount] = {
#define FRAME_ID(class_, type_, ...) mozilla::LayoutFrameType::type_,
#define ABSTRACT_FRAME_ID(...)
#include "mozilla/FrameIdList.h"
#undef FRAME_ID
#undef ABSTRACT_FRAME_ID
};

const nsIFrame::ClassFlags nsIFrame::sLayoutFrameClassFlags[kFrameClassCount] =
    {
#define FRAME_ID(class_, type_, flags_, ...) flags_,
#define ABSTRACT_FRAME_ID(...)
#include "mozilla/FrameIdList.h"
#undef FRAME_ID
#undef ABSTRACT_FRAME_ID
};

std::ostream& operator<<(std::ostream& aStream, const nsDirection& aDirection) {
  return aStream << (aDirection == eDirNext ? "eDirNext" : "eDirPrevious");
}

struct nsContentAndOffset {
  nsIContent* mContent = nullptr;
  int32_t mOffset = 0;
};

#include "nsILineIterator.h"
#include "prenv.h"

// Utility function to set a nsRect-valued property table entry on aFrame,
// reusing the existing storage if the property happens to be already set.
template <typename T>
static void SetOrUpdateRectValuedProperty(
    nsIFrame* aFrame, FrameProperties::Descriptor<T> aProperty,
    const nsRect& aNewValue) {
  bool found;
  nsRect* rectStorage = aFrame->GetProperty(aProperty, &found);
  if (!found) {
    rectStorage = new nsRect(aNewValue);
    aFrame->AddProperty(aProperty, rectStorage);
  } else {
    *rectStorage = aNewValue;
  }
}

FrameDestroyContext::~FrameDestroyContext() {
  for (auto& content : mozilla::Reversed(mAnonymousContent)) {
    mPresShell->NativeAnonymousContentWillBeRemoved(content);
    content->UnbindFromTree();
  }
}

// Formerly the nsIFrameDebug interface

std::ostream& operator<<(std::ostream& aStream, const nsReflowStatus& aStatus) {
  char complete = 'Y';
  if (aStatus.IsIncomplete()) {
    complete = 'N';
  } else if (aStatus.IsOverflowIncomplete()) {
    complete = 'O';
  }

  char brk = 'N';
  if (aStatus.IsInlineBreakBefore()) {
    brk = 'B';
  } else if (aStatus.IsInlineBreakAfter()) {
    brk = 'A';
  }

  aStream << "["
          << "Complete=" << complete << ","
          << "NIF=" << (aStatus.NextInFlowNeedsReflow() ? 'Y' : 'N') << ","
          << "Break=" << brk << ","
          << "FirstLetter=" << (aStatus.FirstLetterComplete() ? 'Y' : 'N')
          << "]";
  return aStream;
}

#ifdef DEBUG

/**
 * Note: the log module is created during library initialization which
 * means that you cannot perform logging before then.
 */

mozilla::LazyLogModule nsIFrame::sFrameLogModule("frame");

#endif

NS_DECLARE_FRAME_PROPERTY_DELETABLE(AbsoluteContainingBlockProperty,
                                    nsAbsoluteContainingBlock)

bool nsIFrame::HasAbsolutelyPositionedChildren() const {
  return IsAbsoluteContainer() &&
         GetAbsoluteContainingBlock()->HasAbsoluteFrames();
}

nsAbsoluteContainingBlock* nsIFrame::GetAbsoluteContainingBlock() const {
  NS_ASSERTION(IsAbsoluteContainer(),
               "The frame is not marked as an abspos container correctly");
  nsAbsoluteContainingBlock* absCB =
      GetProperty(AbsoluteContainingBlockProperty());
  NS_ASSERTION(absCB,
               "The frame is marked as an abspos container but doesn't have "
               "the property");
  return absCB;
}

void nsIFrame::MarkAsAbsoluteContainingBlock() {
  MOZ_ASSERT(HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN));
  NS_ASSERTION(!GetProperty(AbsoluteContainingBlockProperty()),
               "Already has an abs-pos containing block property?");
  NS_ASSERTION(!HasAnyStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN),
               "Already has NS_FRAME_HAS_ABSPOS_CHILDREN state bit?");
  AddStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN);
  SetProperty(AbsoluteContainingBlockProperty(),
              new nsAbsoluteContainingBlock(GetAbsoluteListID()));
}

void nsIFrame::MarkAsNotAbsoluteContainingBlock() {
  NS_ASSERTION(!HasAbsolutelyPositionedChildren(), "Think of the children!");
  NS_ASSERTION(GetProperty(AbsoluteContainingBlockProperty()),
               "Should have an abs-pos containing block property");
  NS_ASSERTION(HasAnyStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN),
               "Should have NS_FRAME_HAS_ABSPOS_CHILDREN state bit");
  MOZ_ASSERT(HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN));
  RemoveStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN);
  RemoveProperty(AbsoluteContainingBlockProperty());
}

bool nsIFrame::CheckAndClearPaintedState() {
  bool result = HasAnyStateBits(NS_FRAME_PAINTED_THEBES);
  RemoveStateBits(NS_FRAME_PAINTED_THEBES);

  for (const auto& childList : ChildLists()) {
    for (nsIFrame* child : childList.mList) {
      if (child->CheckAndClearPaintedState()) {
        result = true;
      }
    }
  }
  return result;
}

nsIFrame* nsIFrame::FindLineContainer() const {
  MOZ_ASSERT(IsLineParticipant());
  nsIFrame* parent = GetParent();
  while (parent &&
         (parent->IsLineParticipant() || parent->CanContinueTextRun())) {
    parent = parent->GetParent();
  }
  return parent;
}

bool nsIFrame::CheckAndClearDisplayListState() {
  bool result = BuiltDisplayList();
  SetBuiltDisplayList(false);

  for (const auto& childList : ChildLists()) {
    for (nsIFrame* child : childList.mList) {
      if (child->CheckAndClearDisplayListState()) {
        result = true;
      }
    }
  }
  return result;
}

bool nsIFrame::IsVisibleConsideringAncestors(uint32_t aFlags) const {
  if (!StyleVisibility()->IsVisible()) {
    return false;
  }

  if (PresShell()->IsUnderHiddenEmbedderElement()) {
    return false;
  }

  const nsIFrame* frame = this;
  while (frame) {
    nsView* view = frame->GetView();
    if (view && view->GetVisibility() == ViewVisibility::Hide) {
      return false;
    }

    if (frame->StyleUIReset()->mMozSubtreeHiddenOnlyVisually) {
      return false;
    }

    // This method is used to determine if a frame is focusable, because it's
    // called by nsIFrame::IsFocusable. `content-visibility: auto` should not
    // force this frame to be unfocusable, so we only take into account
    // `content-visibility: hidden` here.
    if (this != frame &&
        frame->HidesContent(IncludeContentVisibility::Hidden)) {
      return false;
    }

    if (nsIFrame* parent = frame->GetParent()) {
      frame = parent;
    } else {
      parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame);
      if (!parent) {
        break;
      }

      if ((aFlags & nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY) == 0 &&
          parent->PresContext()->IsChrome() &&
          !frame->PresContext()->IsChrome()) {
        break;
      }

      frame = parent;
    }
  }

  return true;
}

void nsIFrame::FindCloserFrameForSelection(
    const nsPoint& aPoint, FrameWithDistance* aCurrentBestFrame) {
  if (nsLayoutUtils::PointIsCloserToRect(aPoint, mRect,
                                         aCurrentBestFrame->mXDistance,
                                         aCurrentBestFrame->mYDistance)) {
    aCurrentBestFrame->mFrame = this;
  }
}

void nsIFrame::ElementStateChanged(mozilla::dom::ElementState aStates) {}

void WeakFrame::Clear(mozilla::PresShell* aPresShell) {
  if (aPresShell) {
    aPresShell->RemoveWeakFrame(this);
  }
  mFrame = nullptr;
}

AutoWeakFrame::AutoWeakFrame(const WeakFrame& aOther)
    : mPrev(nullptr), mFrame(nullptr) {
  Init(aOther.GetFrame());
}

void AutoWeakFrame::Clear(mozilla::PresShell* aPresShell) {
  if (aPresShell) {
    aPresShell->RemoveAutoWeakFrame(this);
  }
  mFrame = nullptr;
  mPrev = nullptr;
}

AutoWeakFrame::~AutoWeakFrame() {
  Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr);
}

void AutoWeakFrame::Init(nsIFrame* aFrame) {
  Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr);
  mFrame = aFrame;
  if (mFrame) {
    mozilla::PresShell* presShell = mFrame->PresContext()->GetPresShell();
    NS_WARNING_ASSERTION(presShell, "Null PresShell in AutoWeakFrame!");
    if (presShell) {
      presShell->AddAutoWeakFrame(this);
    } else {
      mFrame = nullptr;
    }
  }
}

void WeakFrame::Init(nsIFrame* aFrame) {
  Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr);
  mFrame = aFrame;
  if (mFrame) {
    mozilla::PresShell* presShell = mFrame->PresContext()->GetPresShell();
    MOZ_ASSERT(presShell, "Null PresShell in WeakFrame!");
    if (presShell) {
      presShell->AddWeakFrame(this);
    } else {
      mFrame = nullptr;
    }
  }
}

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

nsIFrame::~nsIFrame() {
  MOZ_COUNT_DTOR(nsIFrame);

  MOZ_ASSERT(GetVisibility() != Visibility::ApproximatelyVisible,
             "Visible nsFrame is being destroyed");
}

NS_IMPL_FRAMEARENA_HELPERS(nsIFrame)

// Dummy operator delete.  Will never be called, but must be defined
// to satisfy some C++ ABIs.
void nsIFrame::operator delete(void*, size_t) {
  MOZ_CRASH("nsIFrame::operator delete should never be called");
}

NS_QUERYFRAME_HEAD(nsIFrame)
  NS_QUERYFRAME_ENTRY(nsIFrame)
NS_QUERYFRAME_TAIL_INHERITANCE_ROOT

/////////////////////////////////////////////////////////////////////////////
// nsIFrame

static bool IsFontSizeInflationContainer(nsIFrame* aFrame,
                                         const nsStyleDisplay* aStyleDisplay) {
  /*
   * Font size inflation is built around the idea that we're inflating
   * the fonts for a pan-and-zoom UI so that when the user scales up a
   * block or other container to fill the width of the device, the fonts
   * will be readable.  To do this, we need to pick what counts as a
   * container.
   *
   * From a code perspective, the only hard requirement is that frames
   * that are line participants (nsIFrame::IsLineParticipant) are never
   * containers, since line layout assumes that the inflation is consistent
   * within a line.
   *
   * This is not an imposition, since we obviously want a bunch of text
   * (possibly with inline elements) flowing within a block to count the
   * block (or higher) as its container.
   *
   * We also want form controls, including the text in the anonymous
   * content inside of them, to match each other and the text next to
   * them, so they and their anonymous content should also not be a
   * container.
   *
   * However, because we can't reliably compute sizes across XUL during
   * reflow, any XUL frame with a XUL parent is always a container.
   *
   * There are contexts where it would be nice if some blocks didn't
   * count as a container, so that, for example, an indented quotation
   * didn't end up with a smaller font size.  However, it's hard to
   * distinguish these situations where we really do want the indented
   * thing to count as a container, so we don't try, and blocks are
   * always containers.
   */


  // The root frame should always be an inflation container.
  if (!aFrame->GetParent()) {
    return true;
  }

  nsIContent* content = aFrame->GetContent();
  if (content && content->IsInNativeAnonymousSubtree()) {
    // Native anonymous content shouldn't be a font inflation root,
    // except for the canvas custom content container.
    nsCanvasFrame* canvas = aFrame->PresShell()->GetCanvasFrame();
    return canvas && canvas->GetCustomContentContainer() == content;
  }

  LayoutFrameType frameType = aFrame->Type();
  bool isInline =
      aFrame->GetDisplay().IsInlineFlow() || RubyUtils::IsRubyBox(frameType) ||
      (aStyleDisplay->IsFloatingStyle() &&
       frameType == LayoutFrameType::Letter) ||
      // Given multiple frames for the same node, only the
      // outer one should be considered a container.
      // (Important, e.g., for nsSelectsAreaFrame.)
      (aFrame->GetParent()->GetContent() == content) ||
      (content &&
       // Form controls shouldn't become inflation containers.
       (content->IsAnyOfHTMLElements(nsGkAtoms::option, nsGkAtoms::optgroup,
                                     nsGkAtoms::select, nsGkAtoms::input,
                                     nsGkAtoms::button, nsGkAtoms::textarea)));
  NS_ASSERTION(!aFrame->IsLineParticipant() || isInline ||
                   // br frames and mathml frames report being line
                   // participants even when their position or display is
                   // set
                   aFrame->IsBrFrame() || aFrame->IsMathMLFrame(),
               "line participants must not be containers");
  return !isInline;
}

static void MaybeScheduleReflowSVGNonDisplayText(nsIFrame* aFrame) {
  if (!aFrame->IsInSVGTextSubtree()) {
    return;
  }

  // We need to ensure that any non-display SVGTextFrames get reflowed when a
  // child text frame gets new style. Thus we need to schedule a reflow in
  // |DidSetComputedStyle|. We also need to call it from |DestroyFrom|,
  // because otherwise we won't get notified when style changes to
  // "display:none".
  SVGTextFrame* svgTextFrame = static_cast<SVGTextFrame*>(
      nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText));
  nsIFrame* anonBlock = svgTextFrame->PrincipalChildList().FirstChild();

  // Note that we must check NS_FRAME_FIRST_REFLOW on our SVGTextFrame's
  // anonymous block frame rather than our aFrame, since NS_FRAME_FIRST_REFLOW
  // may be set on us if we're a new frame that has been inserted after the
  // document's first reflow. (In which case this DidSetComputedStyle call may
  // be happening under frame construction under a Reflow() call.)
  if (!anonBlock || anonBlock->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
    return;
  }

  if (!svgTextFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) ||
      svgTextFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW)) {
    return;
  }

  svgTextFrame->ScheduleReflowSVGNonDisplayText(
      IntrinsicDirty::FrameAncestorsAndDescendants);
}

bool nsIFrame::ShouldPropagateRepaintsToRoot() const {
  if (!IsPrimaryFrame()) {
    // special case for table frames because style images are associated to the
    // table frame, but the table wrapper frame is the primary frame
    if (IsTableFrame()) {
      MOZ_ASSERT(GetParent() && GetParent()->IsTableWrapperFrame());
      return GetParent()->ShouldPropagateRepaintsToRoot();
    }

    return false;
  }
  nsIContent* content = GetContent();
  Document* document = content->OwnerDoc();
  return content == document->GetRootElement() ||
         content == document->GetBodyElement();
}

bool nsIFrame::IsRenderedLegend() const {
  if (auto* parent = GetParent(); parent && parent->IsFieldSetFrame()) {
    return static_cast<nsFieldSetFrame*>(parent)->GetLegend() == this;
  }
  return false;
}

void nsIFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
                    nsIFrame* aPrevInFlow) {
  MOZ_ASSERT(nsQueryFrame::FrameIID(mClass) == GetFrameId());
  MOZ_ASSERT(!mContent, "Double-initing a frame?");

  mContent = aContent;
  mParent = aParent;
  MOZ_ASSERT(!mParent || PresShell() == mParent->PresShell());

  if (aPrevInFlow) {
    mWritingMode = aPrevInFlow->GetWritingMode();

    // Copy some state bits from prev-in-flow (the bits that should apply
    // throughout a continuation chain). The bits are sorted according to their
    // order in nsFrameStateBits.h.

    // clang-format off
    AddStateBits(aPrevInFlow->GetStateBits() &
                 (NS_FRAME_GENERATED_CONTENT |
                  NS_FRAME_OUT_OF_FLOW |
                  NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN |
                  NS_FRAME_INDEPENDENT_SELECTION |
                  NS_FRAME_PART_OF_IBSPLIT |
                  NS_FRAME_MAY_BE_TRANSFORMED |
                  NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR));
    // clang-format on

    // Copy other bits in nsIFrame from prev-in-flow.
    mHasColumnSpanSiblings = aPrevInFlow->HasColumnSpanSiblings();
  } else {
    PresContext()->ConstructedFrame();
  }

  if (GetParent()) {
    if (MOZ_UNLIKELY(mContent == PresContext()->Document()->GetRootElement() &&
                     mContent == GetParent()->GetContent())) {
      // Our content is the root element and we have the same content as our
      // parent. That is, we are the internal anonymous frame of the root
      // element. Copy the used mWritingMode from our parent because
      // mDocElementContainingBlock gets its mWritingMode from <body>.
      mWritingMode = GetParent()->GetWritingMode();
    }

    // Copy some state bits from our parent (the bits that should apply
    // recursively throughout a subtree). The bits are sorted according to their
    // order in nsFrameStateBits.h.

    // clang-format off
    AddStateBits(GetParent()->GetStateBits() &
                 (NS_FRAME_GENERATED_CONTENT |
                  NS_FRAME_INDEPENDENT_SELECTION |
                  NS_FRAME_IS_SVG_TEXT |
                  NS_FRAME_IN_POPUP |
                  NS_FRAME_IS_NONDISPLAY));
    // clang-format on

    if (HasAnyStateBits(NS_FRAME_IN_POPUP) && TrackingVisibility()) {
      // Assume all frames in popups are visible.
      IncApproximateVisibleCount();
    }
  }
  if (aPrevInFlow) {
    mMayHaveOpacityAnimation = aPrevInFlow->MayHaveOpacityAnimation();
    mMayHaveTransformAnimation = aPrevInFlow->MayHaveTransformAnimation();
  } else if (mContent) {
    // It's fine to fetch the EffectSet for the style frame here because in the
    // following code we take care of the case where animations may target
    // a different frame.
    EffectSet* effectSet = EffectSet::GetForStyleFrame(this);
    if (effectSet) {
      mMayHaveOpacityAnimation = effectSet->MayHaveOpacityAnimation();

      if (effectSet->MayHaveTransformAnimation()) {
        // If we are the inner table frame for display:table content, then
        // transform animations should go on our parent frame (the table wrapper
        // frame).
        //
        // We do this when initializing the child frame (table inner frame),
        // because when initializng the table wrapper frame, we don't yet have
        // access to its children so we can't tell if we have transform
        // animations or not.
        if (SupportsCSSTransforms()) {
          mMayHaveTransformAnimation = true;
          AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
        } else if (aParent && nsLayoutUtils::GetStyleFrame(aParent) == this) {
          MOZ_ASSERT(
              aParent->SupportsCSSTransforms(),
              "Style frames that don't support transforms should have parents"
              " that do");
          aParent->mMayHaveTransformAnimation = true;
          aParent->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
        }
      }
    }
  }

  const nsStyleDisplay* disp = StyleDisplay();
  if (disp->HasTransform(this)) {
    // If 'transform' dynamically changes, RestyleManager takes care of
    // updating this bit.
    AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
  }

  if (nsLayoutUtils::FontSizeInflationEnabled(PresContext()) ||
      !GetParent()
#ifdef DEBUG
      // We have assertions that check inflation invariants even when
      // font size inflation is not enabled.
      || true
#endif
  ) {
    if (IsFontSizeInflationContainer(this, disp)) {
      AddStateBits(NS_FRAME_FONT_INFLATION_CONTAINER);
      if (!GetParent() ||
          // I'd use NS_FRAME_OUT_OF_FLOW, but it's not set yet.
          disp->IsFloating(this) || disp->IsAbsolutelyPositioned(this) ||
          GetParent()->IsFlexContainerFrame() ||
          GetParent()->IsGridContainerFrame()) {
        AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
      }
    }
    NS_ASSERTION(
        GetParent() || HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER),
        "root frame should always be a container");
  }

  if (TrackingVisibility() && PresShell()->AssumeAllFramesVisible()) {
    IncApproximateVisibleCount();
  }

  DidSetComputedStyle(nullptr);

  // For a newly created frame, we need to update this frame's visibility state.
  // Usually we update the state when the frame is restyled and has a
  // VisibilityChange change hint but we don't generate any change hints for
  // newly created frames.
  // Note: We don't need to do this for placeholders since placeholders have
  // different styles so that the styles don't have visibility:hidden even if
  // the parent has visibility:hidden style. We also don't need to update the
  // state when creating continuations because its visibility is the same as its
  // prev-in-flow, and the animation code cares only primary frames.
  if (!IsPlaceholderFrame() && !aPrevInFlow) {
    UpdateVisibleDescendantsState();
  }

  if (!aPrevInFlow && HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
    // We aren't going to get a reflow, so nothing else will call
    // InvalidateRenderingObservers, we have to do it here.
    SVGObserverUtils::InvalidateRenderingObservers(this);
  }
}

void nsIFrame::InitPrimaryFrame() {
  MOZ_ASSERT(IsPrimaryFrame());
  HandlePrimaryFrameStyleChange(nullptr);
}

void nsIFrame::HandlePrimaryFrameStyleChange(ComputedStyle* aOldStyle) {
  const nsStyleDisplay* disp = StyleDisplay();
  const nsStyleDisplay* oldDisp =
      aOldStyle ? aOldStyle->StyleDisplay() : nullptr;

  const bool wasQueryContainer = oldDisp && oldDisp->IsQueryContainer();
  const bool isQueryContainer = disp->IsQueryContainer();
  if (wasQueryContainer != isQueryContainer) {
    auto* pc = PresContext();
    if (isQueryContainer) {
      pc->RegisterContainerQueryFrame(this);
    } else {
      pc->UnregisterContainerQueryFrame(this);
    }
  }

  const auto cv = disp->ContentVisibility(*this);
  if (!oldDisp || oldDisp->ContentVisibility(*this) != cv) {
    if (cv == StyleContentVisibility::Auto) {
      PresShell()->RegisterContentVisibilityAutoFrame(this);
    } else {
      if (auto* element = Element::FromNodeOrNull(GetContent())) {
        element->ClearContentRelevancy();
      }
      PresShell()->UnregisterContentVisibilityAutoFrame(this);
    }
    PresContext()->SetNeedsToUpdateHiddenByContentVisibilityForAnimations();
  }

  HandleLastRememberedSize();
}

void nsIFrame::Destroy(DestroyContext& aContext) {
  NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
               "destroy called on frame while scripts not blocked");
  NS_ASSERTION(!GetNextSibling() && !GetPrevSibling(),
               "Frames should be removed before destruction.");
  MOZ_ASSERT(!HasAbsolutelyPositionedChildren());
  MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT),
             "NS_FRAME_PART_OF_IBSPLIT set on non-nsContainerFrame?");

  MaybeScheduleReflowSVGNonDisplayText(this);

  SVGObserverUtils::InvalidateDirectRenderingObservers(
      this, SVGObserverUtils::INVALIDATE_DESTROY);

  const auto* disp = StyleDisplay();
  if (disp->mPosition == StylePositionProperty::Sticky) {
    if (auto* ssc =
            StickyScrollContainer::GetStickyScrollContainerForFrame(this)) {
      ssc->RemoveFrame(this);
    }
  }

  if (HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
    if (nsPlaceholderFrame* placeholder = GetPlaceholderFrame()) {
      placeholder->SetOutOfFlowFrame(nullptr);
    }
  }

  nsPresContext* pc = PresContext();
  mozilla::PresShell* ps = pc->GetPresShell();
  if (IsPrimaryFrame()) {
    if (disp->IsQueryContainer()) {
      pc->UnregisterContainerQueryFrame(this);
    }
    if (disp->ContentVisibility(*this) == StyleContentVisibility::Auto) {
      ps->UnregisterContentVisibilityAutoFrame(this);
    }
    // This needs to happen before we clear our Properties() table.
    ActiveLayerTracker::TransferActivityToContent(this, mContent);
  }

  ScrollAnchorContainer* anchor = nullptr;
  if (IsScrollAnchor(&anchor)) {
    anchor->InvalidateAnchor();
  }

  if (HasCSSAnimations() || HasCSSTransitions() ||
      // It's fine to look up the style frame here since if we're destroying the
      // frames for display:table content we should be destroying both wrapper
      // and inner frame.
      EffectSet::GetForStyleFrame(this)) {
    // If no new frame for this element is created by the end of the
    // restyling process, stop animations and transitions for this frame
    RestyleManager::AnimationsWithDestroyedFrame* adf =
        pc->RestyleManager()->GetAnimationsWithDestroyedFrame();
    // AnimationsWithDestroyedFrame only lives during the restyling process.
    if (adf) {
      adf->Put(mContent, mComputedStyle);
    }
  }

  // Disable visibility tracking. Note that we have to do this before we clear
  // frame properties and lose track of whether we were previously visible.
  // XXX(seth): It'd be ideal to assert that we're already marked nonvisible
  // here, but it's unfortunately tricky to guarantee in the face of things like
  // frame reconstruction induced by style changes.
  DisableVisibilityTracking();

  // Ensure that we're not in the approximately visible list anymore.
  ps->RemoveFrameFromApproximatelyVisibleList(this);

  ps->NotifyDestroyingFrame(this);

  if (HasAnyStateBits(NS_FRAME_EXTERNAL_REFERENCE)) {
    ps->ClearFrameRefs(this);
  }

  nsView* view = GetView();
  if (view) {
    view->SetFrame(nullptr);
    view->Destroy();
  }

  // Make sure that our deleted frame can't be returned from GetPrimaryFrame()
  if (IsPrimaryFrame()) {
    mContent->SetPrimaryFrame(nullptr);

    // Pass the root of a generated content subtree (e.g. ::after/::before) to
    // aPostDestroyData to unbind it after frame destruction is done.
    if (HasAnyStateBits(NS_FRAME_GENERATED_CONTENT) &&
        mContent->IsRootOfNativeAnonymousSubtree()) {
      aContext.AddAnonymousContent(mContent.forget());
    }
  }

  // Remove all properties attached to the frame, to ensure any property
  // destructors that need the frame pointer are handled properly.
  RemoveAllProperties();

  // Must retrieve the object ID before calling destructors, so the
  // vtable is still valid.
  //
  // Note to future tweakers: having the method that returns the
  // object size call the destructor will not avoid an indirect call;
  // the compiler cannot devirtualize the call to the destructor even
  // if it's from a method defined in the same class.

  nsQueryFrame::FrameIID id = GetFrameId();
  this->~nsIFrame();

#ifdef DEBUG
  {
    nsIFrame* rootFrame = ps->GetRootFrame();
    MOZ_ASSERT(rootFrame);
    if (this != rootFrame) {
      auto* builder = nsLayoutUtils::GetRetainedDisplayListBuilder(rootFrame);
      auto* data = builder ? builder->Data() : nullptr;

      const bool inData =
          data && (data->IsModified(this) || data->HasProps(this));

      if (inData) {
        DL_LOG(LogLevel::Warning, "Frame %p found in retained data"this);
      }

      MOZ_ASSERT(!inData, "Deleted frame in retained data!");
    }
  }
#endif

  // Now that we're totally cleaned out, we need to add ourselves to
  // the presshell's recycler.
  ps->FreeFrame(id, this);
}

std::pair<int32_t, int32_t> nsIFrame::GetOffsets() const {
  return std::make_pair(0, 0);
}

static void CompareLayers(
    const nsStyleImageLayers* aFirstLayers,
    const nsStyleImageLayers* aSecondLayers,
    const std::function<void(imgRequestProxy* aReq)>& aCallback) {
  NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, (*aFirstLayers)) {
    const auto& image = aFirstLayers->mLayers[i].mImage;
    if (!image.IsImageRequestType() || !image.IsResolved()) {
      continue;
    }

    // aCallback is called when the style image in aFirstLayers is thought to
    // be different with the corresponded one in aSecondLayers
    if (!aSecondLayers || i >= aSecondLayers->mImageCount ||
        (!aSecondLayers->mLayers[i].mImage.IsResolved() ||
         image.GetImageRequest() !=
             aSecondLayers->mLayers[i].mImage.GetImageRequest())) {
      if (imgRequestProxy* req = image.GetImageRequest()) {
        aCallback(req);
      }
    }
  }
}

static void AddAndRemoveImageAssociations(
    ImageLoader& aImageLoader, nsIFrame* aFrame,
    const nsStyleImageLayers* aOldLayers,
    const nsStyleImageLayers* aNewLayers) {
  // If the old context had a background-image image, or mask-image image,
  // and new context does not have the same image, clear the image load
  // notifier (which keeps the image loading, if it still is) for the frame.
  // We want to do this conservatively because some frames paint their
  // backgrounds from some other frame's style data, and we don't want
  // to clear those notifiers unless we have to.  (They'll be reset
  // when we paint, although we could miss a notification in that
  // interval.)
  if (aOldLayers && aFrame->HasImageRequest()) {
    CompareLayers(aOldLayers, aNewLayers, [&](imgRequestProxy* aReq) {
      aImageLoader.DisassociateRequestFromFrame(aReq, aFrame);
    });
  }

  CompareLayers(aNewLayers, aOldLayers, [&](imgRequestProxy* aReq) {
    aImageLoader.AssociateRequestToFrame(aReq, aFrame);
  });
}

void nsIFrame::AddDisplayItem(nsDisplayItem* aItem) {
  MOZ_DIAGNOSTIC_ASSERT(!mDisplayItems.Contains(aItem));
  mDisplayItems.AppendElement(aItem);
#ifdef ACCESSIBILITY
  if (nsAccessibilityService* accService = GetAccService()) {
    accService->NotifyOfPossibleBoundsChange(PresShell(), mContent);
  }
#endif
}

bool nsIFrame::RemoveDisplayItem(nsDisplayItem* aItem) {
  return mDisplayItems.RemoveElement(aItem);
}

bool nsIFrame::HasDisplayItems() { return !mDisplayItems.IsEmpty(); }

bool nsIFrame::HasDisplayItem(nsDisplayItem* aItem) {
  return mDisplayItems.Contains(aItem);
}

bool nsIFrame::HasDisplayItem(uint32_t aKey) {
  for (nsDisplayItem* i : mDisplayItems) {
    if (i->GetPerFrameKey() == aKey) {
      return true;
    }
  }
  return false;
}

template <typename Condition>
static void DiscardDisplayItems(nsIFrame* aFrame, Condition aCondition) {
  for (nsDisplayItem* i : aFrame->DisplayItems()) {
    // Only discard items that are invalidated by this frame, as we're only
    // guaranteed to rebuild those items. Table background items are created by
    // the relevant table part, but have the cell frame as the primary frame,
    // and we don't want to remove them if this is the cell.
    if (aCondition(i) && i->FrameForInvalidation() == aFrame) {
      i->SetCantBeReused();
    }
  }
}

static void DiscardOldItems(nsIFrame* aFrame) {
  DiscardDisplayItems(aFrame,
                      [](nsDisplayItem* aItem) { return aItem->IsOldItem(); });
}

void nsIFrame::RemoveDisplayItemDataForDeletion() {
  // Destroying a WebRenderUserDataTable can cause destruction of other objects
  // which can remove frame properties in their destructor. If we delete a frame
  // property it runs the destructor of the stored object in the middle of
  // updating the frame property table, so if the destruction of that object
  // causes another update to the frame property table it would leave the frame
  // property table in an inconsistent state. So we remove it from the table and
  // then destroy it. (bug 1530657)
  WebRenderUserDataTable* userDataTable =
      TakeProperty(WebRenderUserDataProperty::Key());
  if (userDataTable) {
    for (const auto& data : userDataTable->Values()) {
      data->RemoveFromTable();
    }
    delete userDataTable;
  }

  if (!nsLayoutUtils::AreRetainedDisplayListsEnabled()) {
    // Retained display lists are disabled, no need to update
    // RetainedDisplayListData.
    return;
  }

  auto* builder = nsLayoutUtils::GetRetainedDisplayListBuilder(this);
  if (!builder) {
    MOZ_ASSERT(DisplayItems().IsEmpty());
    MOZ_ASSERT(!IsFrameModified());
    return;
  }

  for (nsDisplayItem* i : DisplayItems()) {
    if (i->GetDependentFrame() == this && !i->HasDeletedFrame()) {
      i->Frame()->MarkNeedsDisplayItemRebuild();
    }
    i->RemoveFrame(this);
  }

  DisplayItems().Clear();

  nsAutoString name;
#ifdef DEBUG_FRAME_DUMP
  if (DL_LOG_TEST(LogLevel::Debug)) {
    GetFrameName(name);
  }
#endif
  DL_LOGV("Removing display item data for frame %p (%s)"this,
          NS_ConvertUTF16toUTF8(name).get());

  auto* data = builder->Data();
  if (MayHaveWillChangeBudget()) {
    // Keep the frame in list, so it can be removed from the will-change budget.
    data->Flags(this) = RetainedDisplayListData::FrameFlag::HadWillChange;
  } else {
    data->Remove(this);
  }
}

void nsIFrame::MarkNeedsDisplayItemRebuild() {
  if (!nsLayoutUtils::AreRetainedDisplayListsEnabled() || IsFrameModified() ||
      HasAnyStateBits(NS_FRAME_IN_POPUP)) {
    // Skip frames that are already marked modified.
    return;
  }

  if (Type() == LayoutFrameType::Placeholder) {
    nsIFrame* oof = static_cast<nsPlaceholderFrame*>(this)->GetOutOfFlowFrame();
    if (oof) {
      oof->MarkNeedsDisplayItemRebuild();
    }
    // Do not mark placeholder frames modified.
    return;
  }

#ifdef ACCESSIBILITY
  if (nsAccessibilityService* accService = GetAccService()) {
    accService->NotifyOfPossibleBoundsChange(PresShell(), mContent);
  }
#endif

  nsIFrame* rootFrame = PresShell()->GetRootFrame();

  if (rootFrame->IsFrameModified()) {
    // The whole frame tree is modified.
    return;
  }

  auto* builder = nsLayoutUtils::GetRetainedDisplayListBuilder(this);
  if (!builder) {
    MOZ_ASSERT(DisplayItems().IsEmpty());
    return;
  }

  RetainedDisplayListData* data = builder->Data();
  MOZ_ASSERT(data);

  if (data->AtModifiedFrameLimit()) {
    // This marks the whole frame tree modified.
    // See |RetainedDisplayListBuilder::ShouldBuildPartial()|.
    data->AddModifiedFrame(rootFrame);
    return;
  }

  nsAutoString name;
#ifdef DEBUG_FRAME_DUMP
  if (DL_LOG_TEST(LogLevel::Debug)) {
    GetFrameName(name);
  }
#endif

  DL_LOGV("RDL - Rebuilding display items for frame %p (%s)"this,
          NS_ConvertUTF16toUTF8(name).get());

  data->AddModifiedFrame(this);

  MOZ_ASSERT(
      PresContext()->LayoutPhaseCount(nsLayoutPhase::DisplayListBuilding) == 0);

  // Hopefully this is cheap, but we could use a frame state bit to note
  // the presence of dependencies to speed it up.
  for (nsDisplayItem* i : DisplayItems()) {
    if (i->HasDeletedFrame() || i->Frame() == this) {
      // Ignore the items with deleted frames, and the items with |this| as
      // the primary frame.
      continue;
    }

    if (i->GetDependentFrame() == this) {
      // For items with |this| as a dependent frame, mark the primary frame
      // for rebuild.
      i->Frame()->MarkNeedsDisplayItemRebuild();
    }
  }
}

// Subclass hook for style post processing
/* virtual */
void nsIFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
#ifdef ACCESSIBILITY
  // Don't notify for reconstructed frames here, since the frame is still being
  // constructed at this point and so LocalAccessible::GetFrame() will return
  // null. Style changes for reconstructed frames are handled in
  // DocAccessible::PruneOrInsertSubtree.
  if (aOldComputedStyle) {
    if (nsAccessibilityService* accService = GetAccService()) {
      accService->NotifyOfComputedStyleChange(PresShell(), mContent);
    }
  }
#endif

  MaybeScheduleReflowSVGNonDisplayText(this);

  Document* doc = PresContext()->Document();
  ImageLoader* loader = doc->StyleImageLoader();
  // Continuing text frame doesn't initialize its continuation pointer before
  // reaching here for the first time, so we have to exclude text frames. This
  // doesn't affect correctness because text can't match selectors.
  //
  // FIXME(emilio): We should consider fixing that.
  //
  // TODO(emilio): Can we avoid doing some / all of the image stuff when
  // isNonTextFirstContinuation is false? We should consider doing this just for
  // primary frames and pseudos, but the first-line reparenting code makes it
  // all bad, should get around to bug 1465474 eventually :(
  const bool isNonText = !IsTextFrame();
  if (isNonText) {
    mComputedStyle->StartImageLoads(*doc, aOldComputedStyle);
  }

  const bool isRootElementStyle = Style()->IsRootElementStyle();
  if (isRootElementStyle) {
    PresShell()->SyncWindowProperties(/* aSync = */ false);
  }

  const nsStyleImageLayers* oldLayers =
      aOldComputedStyle ? &aOldComputedStyle->StyleBackground()->mImage
                        : nullptr;
  const nsStyleImageLayers* newLayers = &StyleBackground()->mImage;
  AddAndRemoveImageAssociations(*loader, this, oldLayers, newLayers);

  oldLayers =
      aOldComputedStyle ? &aOldComputedStyle->StyleSVGReset()->mMask : nullptr;
  newLayers = &StyleSVGReset()->mMask;
  AddAndRemoveImageAssociations(*loader, this, oldLayers, newLayers);

  const nsStyleDisplay* disp = StyleDisplay();
  bool handleStickyChange = false;
  if (aOldComputedStyle) {
    // Detect style changes that should trigger a scroll anchor adjustment
    // suppression.
    // https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers
    bool needAnchorSuppression = false;

    const nsStyleMargin* oldMargin = aOldComputedStyle->StyleMargin();
    if (!oldMargin->MarginEquals(*StyleMargin())) {
      needAnchorSuppression = true;
    }

    const nsStylePadding* oldPadding = aOldComputedStyle->StylePadding();
    if (oldPadding->mPadding != StylePadding()->mPadding) {
      SetHasPaddingChange(true);
      needAnchorSuppression = true;
    }

    const nsStyleDisplay* oldDisp = aOldComputedStyle->StyleDisplay();
    if (oldDisp->mOverflowAnchor != disp->mOverflowAnchor) {
      if (auto* container = ScrollAnchorContainer::FindFor(this)) {
        container->InvalidateAnchor();
      }
      if (ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(this)) {
        scrollContainerFrame->Anchor()->InvalidateAnchor();
      }
    }

    if (mInScrollAnchorChain) {
      const nsStylePosition* pos = StylePosition();
      const nsStylePosition* oldPos = aOldComputedStyle->StylePosition();
      if (!needAnchorSuppression &&
          (oldPos->mOffset != pos->mOffset ||
           oldPos->GetWidth() != pos->GetWidth() ||
           oldPos->GetMinWidth() != pos->GetMinWidth() ||
           oldPos->GetMaxWidth() != pos->GetMaxWidth() ||
           oldPos->GetHeight() != pos->GetHeight() ||
           oldPos->GetMinHeight() != pos->GetMinHeight() ||
           oldPos->GetMaxHeight() != pos->GetMaxHeight() ||
           oldDisp->mPosition != disp->mPosition ||
           oldDisp->mTransform != disp->mTransform)) {
        needAnchorSuppression = true;
      }

      if (needAnchorSuppression &&
          StaticPrefs::layout_css_scroll_anchoring_suppressions_enabled()) {
        ScrollAnchorContainer::FindFor(this)->SuppressAdjustments();
      }
    }

    if (disp->mPosition != oldDisp->mPosition) {
      if (!disp->IsRelativelyOrStickyPositionedStyle() &&
          oldDisp->IsRelativelyOrStickyPositionedStyle()) {
        RemoveProperty(NormalPositionProperty());
      }

      handleStickyChange = disp->mPosition == StylePositionProperty::Sticky ||
                           oldDisp->mPosition == StylePositionProperty::Sticky;
    }
    if (disp->mScrollSnapAlign != oldDisp->mScrollSnapAlign) {
      ScrollSnapUtils::PostPendingResnapFor(this);
    }
    if (isRootElementStyle &&
        disp->mScrollSnapType != oldDisp->mScrollSnapType) {
      if (ScrollContainerFrame* sf =
              PresShell()->GetRootScrollContainerFrame()) {
        sf->PostPendingResnap();
      }
    }
    if (StyleUIReset()->mMozSubtreeHiddenOnlyVisually &&
        !aOldComputedStyle->StyleUIReset()->mMozSubtreeHiddenOnlyVisually) {
      PresShell::ClearMouseCapture(this);
    }
  } else {  // !aOldComputedStyle
    handleStickyChange = disp->mPosition == StylePositionProperty::Sticky;
  }

  if (handleStickyChange && !HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) &&
      !GetPrevInFlow()) {
    // Note that we only add first continuations, but we really only
    // want to add first continuation-or-ib-split-siblings. But since we don't
    // yet know if we're a later part of a block-in-inline split, we'll just
    // add later members of a block-in-inline split here, and then
    // StickyScrollContainer will remove them later.
    if (auto* ssc =
            StickyScrollContainer::GetStickyScrollContainerForFrame(this)) {
      if (disp->mPosition == StylePositionProperty::Sticky) {
        ssc->AddFrame(this);
      } else {
        ssc->RemoveFrame(this);
      }
    }
  }

  imgIRequest* oldBorderImage =
      aOldComputedStyle
          ? aOldComputedStyle->StyleBorder()->GetBorderImageRequest()
          : nullptr;
  imgIRequest* newBorderImage = StyleBorder()->GetBorderImageRequest();
  // FIXME (Bug 759996): The following is no longer true.
  // For border-images, we can't be as conservative (we need to set the
  // new loaders if there has been any change) since the CalcDifference
  // call depended on the result of GetComputedBorder() and that result
  // depends on whether the image has loaded, start the image load now
  // so that we'll get notified when it completes loading and can do a
  // restyle.  Otherwise, the image might finish loading from the
  // network before we start listening to its notifications, and then
  // we'll never know that it's finished loading.  Likewise, we want to
  // do this for freshly-created frames to prevent a similar race if the
  // image loads between reflow (which can depend on whether the image
  // is loaded) and paint.  We also don't really care about any callers who try
  // to paint borders with a different style, because they won't have the
  // correct size for the border either.
  if (oldBorderImage != newBorderImage) {
    // stop and restart the image loading/notification
    if (oldBorderImage && HasImageRequest()) {
      loader->DisassociateRequestFromFrame(oldBorderImage, this);
    }
    if (newBorderImage) {
      loader->AssociateRequestToFrame(newBorderImage, this);
    }
  }

  auto GetShapeImageRequest = [](const ComputedStyle* aStyle) -> imgIRequest* {
    if (!aStyle) {
      return nullptr;
    }
    auto& shape = aStyle->StyleDisplay()->mShapeOutside;
    if (!shape.IsImage()) {
      return nullptr;
    }
    return shape.AsImage().GetImageRequest();
  };

  imgIRequest* oldShapeImage = GetShapeImageRequest(aOldComputedStyle);
  imgIRequest* newShapeImage = GetShapeImageRequest(Style());
  if (oldShapeImage != newShapeImage) {
    if (oldShapeImage && HasImageRequest()) {
      loader->DisassociateRequestFromFrame(oldShapeImage, this);
    }
    if (newShapeImage) {
      loader->AssociateRequestToFrame(
          newShapeImage, this,
          ImageLoader::Flags::
              RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking);
    }
  }

  // SVGObserverUtils::GetEffectProperties() asserts that we only invoke it with
  // the first continuation so we need to check that in advance.
  const bool isNonTextFirstContinuation = isNonText && !GetPrevContinuation();
  if (isNonTextFirstContinuation) {
    // Kick off loading of external SVG resources referenced from properties if
    // any. This currently includes filter, clip-path, and mask.
    SVGObserverUtils::InitiateResourceDocLoads(this);
  }

  // If the page contains markup that overrides text direction, and
  // does not contain any characters that would activate the Unicode
  // bidi algorithm, we need to call |SetBidiEnabled| on the pres
  // context before reflow starts.  See bug 115921.
  if (StyleVisibility()->mDirection == StyleDirection::Rtl) {
    PresContext()->SetBidiEnabled();
  }

  // The following part is for caching offset-path:path(). We cache the
  // flatten gfx path, so we don't have to rebuild and re-flattern it at
  // each cycle if we have animations on offset-* with a fixed offset-path.
  const StyleOffsetPath* oldPath =
      aOldComputedStyle ? &aOldComputedStyle->StyleDisplay()->mOffsetPath
                        : nullptr;
  const StyleOffsetPath& newPath = StyleDisplay()->mOffsetPath;
  if (!oldPath || *oldPath != newPath) {
    // FIXME: Bug 1837042. Cache all basic shapes.
    if (newPath.IsPath()) {
      RefPtr<gfx::PathBuilder> builder = MotionPathUtils::GetPathBuilder();
      RefPtr<gfx::Path> path =
          MotionPathUtils::BuildSVGPath(newPath.AsSVGPathData(), builder);
      if (path) {
        // The newPath could be path('') (i.e. empty path), so its gfx path
        // could be nullptr, and so we only set property for a non-empty path.
        SetProperty(nsIFrame::OffsetPathCache(), path.forget().take());
      } else {
        // May have an old cached path, so we have to delete it.
        RemoveProperty(nsIFrame::OffsetPathCache());
      }
    } else if (oldPath) {
      RemoveProperty(nsIFrame::OffsetPathCache());
    }
  }

  if (IsPrimaryFrame()) {
    MOZ_ASSERT(aOldComputedStyle);
    HandlePrimaryFrameStyleChange(aOldComputedStyle);
  }

  RemoveStateBits(NS_FRAME_SIMPLE_EVENT_REGIONS | NS_FRAME_SIMPLE_DISPLAYLIST);

  mMayHaveRoundedCorners = true;
}

void nsIFrame::HandleLastRememberedSize() {
  MOZ_ASSERT(IsPrimaryFrame());
  // Storing a last remembered size requires contain-intrinsic-size.
  if (!StaticPrefs::layout_css_contain_intrinsic_size_enabled()) {
    return;
  }
  auto* element = Element::FromNodeOrNull(mContent);
  if (!element) {
    return;
  }
  const WritingMode wm = GetWritingMode();
  const nsStylePosition* stylePos = StylePosition();
  bool canRememberBSize = stylePos->ContainIntrinsicBSize(wm).HasAuto();
  bool canRememberISize = stylePos->ContainIntrinsicISize(wm).HasAuto();
  if (!canRememberBSize) {
    element->RemoveLastRememberedBSize();
  }
  if (!canRememberISize) {
    element->RemoveLastRememberedISize();
  }
  if ((canRememberBSize || canRememberISize) && !HidesContent()) {
    bool isNonReplacedInline = IsLineParticipant() && !IsReplaced();
    if (!isNonReplacedInline) {
      PresContext()->Document()->ObserveForLastRememberedSize(*element);
      return;
    }
  }
  PresContext()->Document()->UnobserveForLastRememberedSize(*element);
}

#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
void nsIFrame::AssertNewStyleIsSane(ComputedStyle& aNewStyle) {
  MOZ_DIAGNOSTIC_ASSERT(
      aNewStyle.GetPseudoType() == mComputedStyle->GetPseudoType() ||
      // ::first-line continuations are weird, this should probably be fixed via
      // bug 1465474.
      (mComputedStyle->GetPseudoType() == PseudoStyleType::firstLine &&
       aNewStyle.GetPseudoType() == PseudoStyleType::mozLineFrame) ||
      // ::first-letter continuations are broken, in particular floating ones,
      // see bug 1490281. The construction code tries to fix this up after the
      // fact, then restyling undoes it...
      (mComputedStyle->GetPseudoType() == PseudoStyleType::mozText &&
       aNewStyle.GetPseudoType() == PseudoStyleType::firstLetterContinuation) ||
      (mComputedStyle->GetPseudoType() ==
           PseudoStyleType::firstLetterContinuation &&
       aNewStyle.GetPseudoType() == PseudoStyleType::mozText));
}
#endif

void nsIFrame::ReparentFrameViewTo(nsViewManager* aViewManager,
                                   nsView* aNewParentView) {
  if (HasView()) {
    if (IsMenuPopupFrame()) {
      // This view must be parented by the root view, don't reparent it.
      return;
    }
    nsView* view = GetView();
    aViewManager->RemoveChild(view);

    // The view will remember the Z-order and other attributes that have been
    // set on it.
    nsView* insertBefore =
        nsLayoutUtils::FindSiblingViewFor(aNewParentView, this);
    aViewManager->InsertChild(aNewParentView, view, insertBefore,
                              insertBefore != nullptr);
  } else if (HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW)) {
    for (const auto& childList : ChildLists()) {
      // Iterate the child frames, and check each child frame to see if it has
      // a view
      for (nsIFrame* child : childList.mList) {
        child->ReparentFrameViewTo(aViewManager, aNewParentView);
      }
    }
  }
}

void nsIFrame::SyncFrameViewProperties(nsView* aView) {
  if (!aView) {
    aView = GetView();
    if (!aView) {
      return;
    }
  }

  nsViewManager* vm = aView->GetViewManager();

  // Make sure visibility is correct. This only affects nsSubDocumentFrame.
  if (!SupportsVisibilityHidden()) {
    // See if the view should be hidden or visible
    ComputedStyle* sc = Style();
    vm->SetViewVisibility(aView, sc->StyleVisibility()->IsVisible()
                                     ? ViewVisibility::Show
                                     : ViewVisibility::Hide);
  }
}

/* virtual */
nsMargin nsIFrame::GetUsedMargin() const {
  nsMargin margin;
  if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) ||
      IsInSVGTextSubtree()) {
    return margin;
  }

  if (nsMargin* m = GetProperty(UsedMarginProperty())) {
    margin = *m;
  } else if (!StyleMargin()->GetMargin(margin)) {
    // If we get here, our caller probably shouldn't be calling us...
    NS_ERROR(
        "Returning bogus 0-sized margin, because this margin "
        "depends on layout & isn't cached!");
  }
  return margin;
}

/* virtual */
nsMargin nsIFrame::GetUsedBorder() const {
  if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) ||
      IsInSVGTextSubtree()) {
    return {};
  }

  const nsStyleDisplay* disp = StyleDisplay();
  if (IsThemed(disp)) {
    // Theme methods don't use const-ness.
    auto* mutable_this = const_cast<nsIFrame*>(this);
    nsPresContext* pc = PresContext();
    LayoutDeviceIntMargin widgetBorder = pc->Theme()->GetWidgetBorder(
        pc->DeviceContext(), mutable_this, disp->EffectiveAppearance());
    return LayoutDevicePixel::ToAppUnits(widgetBorder,
                                         pc->AppUnitsPerDevPixel());
  }

  return StyleBorder()->GetComputedBorder();
}

/* virtual */
nsMargin nsIFrame::GetUsedPadding() const {
  nsMargin padding;
  if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) ||
      IsInSVGTextSubtree()) {
    return padding;
  }

  const nsStyleDisplay* disp = StyleDisplay();
  if (IsThemed(disp)) {
    // Theme methods don't use const-ness.
    nsIFrame* mutable_this = const_cast<nsIFrame*>(this);
    nsPresContext* pc = PresContext();
    LayoutDeviceIntMargin widgetPadding;
    if (pc->Theme()->GetWidgetPadding(pc->DeviceContext(), mutable_this,
                                      disp->EffectiveAppearance(),
                                      &widgetPadding)) {
      return LayoutDevicePixel::ToAppUnits(widgetPadding,
                                           pc->AppUnitsPerDevPixel());
    }
  }

  if (nsMargin* p = GetProperty(UsedPaddingProperty())) {
    padding = *p;
  } else if (!StylePadding()->GetPadding(padding)) {
    // If we get here, our caller probably shouldn't be calling us...
    NS_ERROR(
        "Returning bogus 0-sized padding, because this padding "
        "depends on layout & isn't cached!");
  }
  return padding;
}

nsIFrame::Sides nsIFrame::GetSkipSides() const {
  if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
                   StyleBoxDecorationBreak::Clone) &&
      !HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
    return Sides();
  }

  // Convert the logical skip sides to physical sides using the frame's
  // writing mode
  WritingMode writingMode = GetWritingMode();
  LogicalSides logicalSkip = GetLogicalSkipSides();
  Sides skip;

  if (logicalSkip.BStart()) {
    if (writingMode.IsVertical()) {
      skip |= writingMode.IsVerticalLR() ? SideBits::eLeft : SideBits::eRight;
    } else {
      skip |= SideBits::eTop;
    }
  }

  if (logicalSkip.BEnd()) {
    if (writingMode.IsVertical()) {
      skip |= writingMode.IsVerticalLR() ? SideBits::eRight : SideBits::eLeft;
    } else {
      skip |= SideBits::eBottom;
    }
  }

  if (logicalSkip.IStart()) {
    if (writingMode.IsVertical()) {
      skip |= SideBits::eTop;
    } else {
      skip |= writingMode.IsBidiLTR() ? SideBits::eLeft : SideBits::eRight;
    }
  }

  if (logicalSkip.IEnd()) {
    if (writingMode.IsVertical()) {
      skip |= SideBits::eBottom;
    } else {
      skip |= writingMode.IsBidiLTR() ? SideBits::eRight : SideBits::eLeft;
    }
  }
  return skip;
}

nsRect nsIFrame::GetPaddingRectRelativeToSelf() const {
  nsMargin border = GetUsedBorder().ApplySkipSides(GetSkipSides());
  nsRect r(0, 0, mRect.width, mRect.height);
  r.Deflate(border);
  return r;
}

nsRect nsIFrame::GetPaddingRect() const {
  return GetPaddingRectRelativeToSelf() + GetPosition();
}

WritingMode nsIFrame::WritingModeForLine(WritingMode aSelfWM,
                                         nsIFrame* aSubFrame) const {
  MOZ_ASSERT(aSelfWM == GetWritingMode());
  WritingMode writingMode = aSelfWM;

  if (StyleTextReset()->mUnicodeBidi == StyleUnicodeBidi::Plaintext) {
    mozilla::intl::BidiEmbeddingLevel frameLevel =
        nsBidiPresUtils::GetFrameBaseLevel(aSubFrame);
    writingMode.SetDirectionFromBidiLevel(frameLevel);
  }

  return writingMode;
}

nsRect nsIFrame::GetMarginRect() const {
  return GetMarginRectRelativeToSelf() + GetPosition();
}

nsRect nsIFrame::GetMarginRectRelativeToSelf() const {
  nsMargin m = GetUsedMargin().ApplySkipSides(GetSkipSides());
  nsRect r(0, 0, mRect.width, mRect.height);
  r.Inflate(m);
  return r;
}

bool nsIFrame::IsTransformed() const {
  if (!HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED)) {
    MOZ_ASSERT(!IsCSSTransformed());
    MOZ_ASSERT(!GetParentSVGTransforms());
    return false;
  }
  return IsCSSTransformed() || GetParentSVGTransforms();
}

bool nsIFrame::IsCSSTransformed() const {
  return HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED) &&
         (StyleDisplay()->HasTransform(this) || HasAnimationOfTransform());
}

bool nsIFrame::HasAnimationOfTransform() const {
  if (!MayHaveTransformAnimation()) {
    MOZ_ASSERT(!IsPrimaryFrame() || !SupportsCSSTransforms() ||
               !nsLayoutUtils::HasAnimationOfTransformAndMotionPath(this));
    return false;
  }
  return IsPrimaryFrame() && SupportsCSSTransforms() &&
         nsLayoutUtils::HasAnimationOfTransformAndMotionPath(this);
}

bool nsIFrame::ChildrenHavePerspective(
    const nsStyleDisplay* aStyleDisplay) const {
  MOZ_ASSERT(aStyleDisplay == StyleDisplay());
  return aStyleDisplay->HasPerspective(this);
}

bool nsIFrame::HasAnimationOfOpacity(EffectSet* aEffectSet) const {
  return ((nsLayoutUtils::IsPrimaryStyleFrame(this) ||
           nsLayoutUtils::FirstContinuationOrIBSplitSibling(this)
               ->IsPrimaryFrame()) &&
          nsLayoutUtils::HasAnimationOfPropertySet(
              this, nsCSSPropertyIDSet::OpacityProperties(), aEffectSet));
}

bool nsIFrame::HasOpacityInternal(float aThreshold,
                                  const nsStyleDisplay* aStyleDisplay,
                                  const nsStyleEffects* aStyleEffects,
                                  EffectSet* aEffectSet) const {
  MOZ_ASSERT(0.0 <= aThreshold && aThreshold <= 1.0, "Invalid argument");
  if (aStyleEffects->mOpacity < aThreshold ||
      aStyleDisplay->mWillChange.bits & StyleWillChangeBits::OPACITY) {
    return true;
  }

  if (!mMayHaveOpacityAnimation) {
    return false;
  }

  return HasAnimationOfOpacity(aEffectSet);
}

bool nsIFrame::DoGetParentSVGTransforms(gfx::Matrix*) const { return false; }

bool nsIFrame::Extend3DContext(const nsStyleDisplay* aStyleDisplay,
                               const nsStyleEffects* aStyleEffects,
                               mozilla::EffectSet* aEffectSetForOpacity) const {
  if (!HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED)) {
    return false;
  }
  const nsStyleDisplay* disp = StyleDisplayWithOptionalParam(aStyleDisplay);
  if (disp->mTransformStyle != StyleTransformStyle::Preserve3d ||
      !SupportsCSSTransforms()) {
    return false;
  }

  // If we're all scroll frame, then all descendants will be clipped, so we
  // can't preserve 3d.
  if (IsScrollContainerFrame()) {
    return false;
  }

  const nsStyleEffects* effects = StyleEffectsWithOptionalParam(aStyleEffects);
  if (HasOpacity(disp, effects, aEffectSetForOpacity)) {
    return false;
  }

  return ShouldApplyOverflowClipping(disp).isEmpty() &&
         !GetClipPropClipRect(disp, effects, GetSize()) &&
         !SVGIntegrationUtils::UsingEffectsForFrame(this) &&
         !effects->HasMixBlendMode() &&
         disp->mIsolation != StyleIsolation::Isolate;
}

bool nsIFrame::Combines3DTransformWithAncestors() const {
  // Check these first as they are faster then both calls below and are we are
  // likely to hit the early return (backface hidden is uncommon and
  // GetReferenceFrame is a hot caller of this which only calls this if
  // IsCSSTransformed is false).
  if (!IsCSSTransformed() && !BackfaceIsHidden()) {
    return false;
  }
  nsIFrame* parent = GetClosestFlattenedTreeAncestorPrimaryFrame();
  return parent && parent->Extend3DContext();
}

bool nsIFrame::In3DContextAndBackfaceIsHidden() const {
  // While both tests fail most of the time, test BackfaceIsHidden()
  // first since it's likely to fail faster.
  return BackfaceIsHidden() && Combines3DTransformWithAncestors();
}

bool nsIFrame::HasPerspective() const {
  if (!IsCSSTransformed()) {
    return false;
  }
  nsIFrame* parent = GetClosestFlattenedTreeAncestorPrimaryFrame();
  if (!parent) {
    return false;
  }
  return parent->ChildrenHavePerspective();
}

nsRect nsIFrame::GetContentRectRelativeToSelf() const {
  nsMargin bp = GetUsedBorderAndPadding().ApplySkipSides(GetSkipSides());
  nsRect r(0, 0, mRect.width, mRect.height);
  r.Deflate(bp);
  return r;
}

nsRect nsIFrame::GetContentRect() const {
  return GetContentRectRelativeToSelf() + GetPosition();
}

bool nsIFrame::ComputeBorderRadii(const BorderRadius& aBorderRadius,
                                  const nsSize& aFrameSize,
                                  const nsSize& aBorderArea, Sides aSkipSides,
                                  nscoord aRadii[8]) {
  // Percentages are relative to whichever side they're on.
  for (const auto i : mozilla::AllPhysicalHalfCorners()) {
    const LengthPercentage& c = aBorderRadius.Get(i);
    nscoord axis = HalfCornerIsX(i) ? aFrameSize.width : aFrameSize.height;
    aRadii[i] = std::max(0, c.Resolve(axis));
  }

  if (aSkipSides.Top()) {
    aRadii[eCornerTopLeftX] = 0;
    aRadii[eCornerTopLeftY] = 0;
    aRadii[eCornerTopRightX] = 0;
    aRadii[eCornerTopRightY] = 0;
  }

  if (aSkipSides.Right()) {
    aRadii[eCornerTopRightX] = 0;
    aRadii[eCornerTopRightY] = 0;
    aRadii[eCornerBottomRightX] = 0;
    aRadii[eCornerBottomRightY] = 0;
  }

  if (aSkipSides.Bottom()) {
    aRadii[eCornerBottomRightX] = 0;
    aRadii[eCornerBottomRightY] = 0;
    aRadii[eCornerBottomLeftX] = 0;
    aRadii[eCornerBottomLeftY] = 0;
  }

  if (aSkipSides.Left()) {
    aRadii[eCornerBottomLeftX] = 0;
    aRadii[eCornerBottomLeftY] = 0;
    aRadii[eCornerTopLeftX] = 0;
    aRadii[eCornerTopLeftY] = 0;
  }

  // css3-background specifies this algorithm for reducing
  // corner radii when they are too big.
  bool haveRadius = false;
  double ratio = 1.0f;
  for (const auto side : mozilla::AllPhysicalSides()) {
    uint32_t hc1 = SideToHalfCorner(side, falsetrue);
    uint32_t hc2 = SideToHalfCorner(side, truetrue);
    nscoord length =
        SideIsVertical(side) ? aBorderArea.height : aBorderArea.width;
    nscoord sum = aRadii[hc1] + aRadii[hc2];
    if (sum) {
      haveRadius = true;
      // avoid floating point division in the normal case
      if (length < sum) {
        ratio = std::min(ratio, double(length) / sum);
      }
    }
  }
  if (ratio < 1.0) {
    for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
      aRadii[corner] *= ratio;
    }
  }

  return haveRadius;
}

void nsIFrame::AdjustBorderRadii(nscoord aRadii[8], const nsMargin& aOffsets) {
  auto AdjustOffset = [](const uint32_t aRadius, const nscoord aOffset) {
    // Implement the cubic formula to adjust offset when aOffset > 0 and
    // aRadius / aOffset < 1.
    // https://drafts.csswg.org/css-shapes/#valdef-shape-box-margin-box
    if (aOffset > 0) {
      const double ratio = aRadius / double(aOffset);
      if (ratio < 1.0) {
        return nscoord(aOffset * (1.0 + std::pow(ratio - 1, 3)));
      }
    }
    return aOffset;
  };

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

--> maximum size reached

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

Messung V0.5
C=91 H=95 G=92

¤ Dauer der Verarbeitung: 0.20 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.