Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/base/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 36 kB image not shown  

Quelle  AbstractRange.cpp

  Sprache: C
 

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


#include "mozilla/dom/AbstractRange.h"

#include "mozilla/Assertions.h"
#include "mozilla/ContentIterator.h"
#include "mozilla/PresShell.h"
#include "mozilla/RangeUtils.h"
#include "mozilla/SelectionMovementUtils.h"
#include "mozilla/dom/AbstractRangeBinding.h"
#include "mozilla/dom/ChildIterator.h"
#include "mozilla/dom/CrossShadowBoundaryRange.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/ShadowIncludingTreeIterator.h"
#include "mozilla/dom/StaticRange.h"
#include "mozilla/dom/TreeIterator.h"
#include "nsContentUtils.h"
#include "nsCycleCollectionParticipant.h"
#include "nsFmtString.h"
#include "nsINode.h"
#include "nsLayoutUtils.h"
#include "nsRange.h"
#include "nsTArray.h"
#include "nsTextFrame.h"

namespace mozilla::dom {

template nsresult AbstractRange::SetStartAndEndInternal(
    const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
    nsRange* aRange, AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary);
template nsresult AbstractRange::SetStartAndEndInternal(
    const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary,
    nsRange* aRange, AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary);
template nsresult AbstractRange::SetStartAndEndInternal(
    const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
    nsRange* aRange, AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary);
template nsresult AbstractRange::SetStartAndEndInternal(
    const RawRangeBoundary& aStartBoundary,
    const RawRangeBoundary& aEndBoundary, nsRange* aRange,
    AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary);
template nsresult AbstractRange::SetStartAndEndInternal(
    const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
    StaticRange* aRange,
    AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary);
template nsresult AbstractRange::SetStartAndEndInternal(
    const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary,
    StaticRange* aRange,
    AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary);
template nsresult AbstractRange::SetStartAndEndInternal(
    const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
    StaticRange* aRange,
    AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary);
template nsresult AbstractRange::SetStartAndEndInternal(
    const RawRangeBoundary& aStartBoundary,
    const RawRangeBoundary& aEndBoundary, StaticRange* aRange,
    AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary);
template bool AbstractRange::MaybeCacheToReuse(nsRange& aInstance);
template bool AbstractRange::MaybeCacheToReuse(StaticRange& aInstance);
template bool AbstractRange::MaybeCacheToReuse(
    CrossShadowBoundaryRange& aInstance);

bool AbstractRange::sHasShutDown = false;

NS_IMPL_CYCLE_COLLECTING_ADDREF(AbstractRange)
NS_IMPL_CYCLE_COLLECTING_RELEASE(AbstractRange)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbstractRange)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(AbstractRange)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AbstractRange)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner);
  // mStart and mEnd may depend on or be depended on some other members in
  // concrete classes so that they should be unlinked in sub classes.
  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
  tmp->mSelections.Clear();
  // Unregistering of the common inclusive ancestors would by design
  // also happen when the actual implementations unlink `mStart`/`mEnd`.
  // This may introduce additional overhead which is not needed when unlinking,
  // therefore this is done here beforehand.
  if (tmp->mRegisteredClosestCommonInclusiveAncestor) {
    tmp->UnregisterClosestCommonInclusiveAncestor(IsUnlinking::Yes);
  }
  MOZ_DIAGNOSTIC_ASSERT(!tmp->isInList(),
                        "Shouldn't be registered now that we're unlinking");

NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AbstractRange)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStart)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEnd)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRegisteredClosestCommonInclusiveAncestor)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

void AbstractRange::UpdateDescendantsInFlattenedTree(nsINode& aNode,
                                                     bool aMarkDescendants) {
  auto UpdateDescendant = [aMarkDescendants](nsINode* node) {
    if (aMarkDescendants) {
      node->SetDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
    } else {
      node->ClearDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
    }
  };

  nsINode* target = &aNode;

  if (target->IsDocument()) {
    if (auto* rootElement = aNode.AsDocument()->GetRootElement()) {
      target = rootElement;
      UpdateDescendant(target);
    }
  }

  if (!target || !target->IsContent()) {
    return;
  }

  TreeIterator<FlattenedChildIterator> iter(*target->AsContent());
  iter.GetNext();  // Skip aNode itself.
  while (nsIContent* curNode = iter.GetCurrent()) {
    UpdateDescendant(curNode);
    if (curNode->IsClosestCommonInclusiveAncestorForRangeInSelection()) {
      iter.GetNextSkippingChildren();
    } else {
      iter.GetNext();
    }
  }
}

void AbstractRange::MarkDescendants(nsINode& aNode) {
  // Set NodeIsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection on
  // aNode's descendants unless aNode is already marked as a range common
  // ancestor or a descendant of one, in which case all of our descendants have
  // the bit set already.
  if (!aNode.IsMaybeSelected()) {
    // If aNode has a web-exposed shadow root, use this shadow tree and ignore
    // the children of aNode.

    UpdateDescendantsInFlattenedTree(aNode, true /* aMarkDescendants */);
  }
}

void AbstractRange::UnmarkDescendants(nsINode& aNode) {
  // Unset NodeIsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection
  // on aNode's descendants unless aNode is a descendant of another range common
  // ancestor. Also, exclude descendants of range common ancestors (but not the
  // common ancestor itself).
  if (!aNode
           .IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection()) {
    UpdateDescendantsInFlattenedTree(aNode, false /* aMarkDescendants */);
  }
}

// NOTE: If you need to change default value of members of AbstractRange,
//       update nsRange::Create(nsINode* aNode) and ClearForReuse() too.
AbstractRange::AbstractRange(nsINode* aNode, bool aIsDynamicRange,
                             TreeKind aBoundaryTreeKind)
    : mStart(aBoundaryTreeKind),
      mEnd(aBoundaryTreeKind),
      mRegisteredClosestCommonInclusiveAncestor(nullptr),
      mIsPositioned(false),
      mIsGenerated(false),
      mCalledByJS(false),
      mIsDynamicRange(aIsDynamicRange) {
  mRefCnt.SetIsOnMainThread();
  Init(aNode);
}

AbstractRange::~AbstractRange() = default;

void AbstractRange::Init(nsINode* aNode) {
  MOZ_ASSERT(aNode, "range isn't in a document!");
  mOwner = aNode->OwnerDoc();
}

// static
void AbstractRange::Shutdown() {
  sHasShutDown = true;
  if (nsTArray<RefPtr<nsRange>>* cachedRanges = nsRange::sCachedRanges) {
    nsRange::sCachedRanges = nullptr;
    cachedRanges->Clear();
    delete cachedRanges;
  }
  if (nsTArray<RefPtr<StaticRange>>* cachedRanges =
          StaticRange::sCachedRanges) {
    StaticRange::sCachedRanges = nullptr;
    cachedRanges->Clear();
    delete cachedRanges;
  }
  if (nsTArray<RefPtr<CrossShadowBoundaryRange>>* cachedRanges =
          CrossShadowBoundaryRange::sCachedRanges) {
    CrossShadowBoundaryRange::sCachedRanges = nullptr;
    cachedRanges->Clear();
    delete cachedRanges;
  }
}

// static
template <class RangeType>
bool AbstractRange::MaybeCacheToReuse(RangeType& aInstance) {
  static const size_t kMaxRangeCache = 64;

  // If the instance is not used by JS and the cache is not yet full, we
  // should reuse it.  Otherwise, delete it.
  if (sHasShutDown || aInstance.GetWrapperMaybeDead() || aInstance.GetFlags() ||
      (RangeType::sCachedRanges &&
       RangeType::sCachedRanges->Length() == kMaxRangeCache)) {
    return false;
  }

  aInstance.ClearForReuse();

  if (!RangeType::sCachedRanges) {
    RangeType::sCachedRanges = new nsTArray<RefPtr<RangeType>>(16);
  }
  RangeType::sCachedRanges->AppendElement(&aInstance);
  return true;
}

nsINode* AbstractRange::GetClosestCommonInclusiveAncestor(
    AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) const {
  if (!mIsPositioned) {
    return nullptr;
  }
  nsINode* startContainer = ShadowDOMSelectionHelpers::GetStartContainer(
      this, aAllowCrossShadowBoundary);
  nsINode* endContainer = ShadowDOMSelectionHelpers::GetEndContainer(
      this, aAllowCrossShadowBoundary);

  if (aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes) {
    if (startContainer == endContainer) {
      return startContainer;
    }
    // Since both the start container and the end container are
    // guaranteed to be in the same composed document.
    // If one of the boundary is a document, use that document
    // as the common ancestor since both nodes.
    const bool oneBoundaryIsDocument =
        (startContainer && startContainer->IsDocument()) ||
        (endContainer && endContainer->IsDocument());
    if (oneBoundaryIsDocument) {
      MOZ_ASSERT_IF(
          startContainer && startContainer->IsDocument(),
          !endContainer || endContainer->GetComposedDoc() == startContainer);
      MOZ_ASSERT_IF(
          endContainer && endContainer->IsDocument(),
          !startContainer || startContainer->GetComposedDoc() == endContainer);

      return startContainer ? startContainer->GetComposedDoc()
                            : endContainer->GetComposedDoc();
    }

    const auto rescope = [](nsINode*& aContainer) {
      if (!aContainer) {
        return;
      }
      // RangeBoundary allows the container to be shadow roots; When
      // this happens, we should use the shadow host here.
      if (auto* shadowRoot = ShadowRoot::FromNode(aContainer)) {
        aContainer = shadowRoot->GetHost();
        return;
      }
    };

    rescope(startContainer);
    rescope(endContainer);

    return nsContentUtils::GetCommonFlattenedTreeAncestorForSelection(
        startContainer ? startContainer->AsContent() : nullptr,
        endContainer ? endContainer->AsContent() : nullptr);
  }
  return nsContentUtils::GetClosestCommonInclusiveAncestor(startContainer,
                                                           endContainer);
}

// static
template <typename SPT, typename SRT, typename EPT, typename ERT,
          typename RangeType>
nsresult AbstractRange::SetStartAndEndInternal(
    const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
    const RangeBoundaryBase<EPT, ERT>& aEndBoundary, RangeType* aRange,
    AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) {
  if (NS_WARN_IF(!aStartBoundary.IsSet()) ||
      NS_WARN_IF(!aEndBoundary.IsSet())) {
    return NS_ERROR_INVALID_ARG;
  }

  nsINode* newStartRoot =
      RangeUtils::ComputeRootNode(aStartBoundary.GetContainer());
  if (!newStartRoot) {
    return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
  }
  if (!aStartBoundary.IsSetAndValid()) {
    return NS_ERROR_DOM_INDEX_SIZE_ERR;
  }

  if (aStartBoundary.GetContainer() == aEndBoundary.GetContainer()) {
    if (!aEndBoundary.IsSetAndValid()) {
      return NS_ERROR_DOM_INDEX_SIZE_ERR;
    }
    // XXX: Offsets - handle this more efficiently.
    // If the end offset is less than the start offset, this should be
    // collapsed at the end offset.
    if (*aStartBoundary.Offset(
            RangeBoundaryBase<SPT, SRT>::OffsetFilter::kValidOffsets) >
        *aEndBoundary.Offset(
            RangeBoundaryBase<EPT, ERT>::OffsetFilter::kValidOffsets)) {
      aRange->DoSetRange(aEndBoundary, aEndBoundary, newStartRoot);
    } else {
      aRange->DoSetRange(aStartBoundary, aEndBoundary, newStartRoot);
    }
    return NS_OK;
  }

  nsINode* newEndRoot =
      RangeUtils::ComputeRootNode(aEndBoundary.GetContainer());
  if (!newEndRoot) {
    return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
  }
  if (!aEndBoundary.IsSetAndValid()) {
    return NS_ERROR_DOM_INDEX_SIZE_ERR;
  }

  // Different root
  if (newStartRoot != newEndRoot) {
    if (aRange->IsStaticRange()) {
      // StaticRange allows nodes in different trees, so set start and end
      // accordingly
      aRange->DoSetRange(aStartBoundary, aEndBoundary, newEndRoot);
    } else {
      MOZ_ASSERT(aRange->IsDynamicRange());
      // In contrast, nsRange keeps both. It has a pair of start and end
      // which they have been collapsed to one end, and it also may have a pair
      // of start and end which are the original value.
      aRange->DoSetRange(aEndBoundary, aEndBoundary, newEndRoot);

      // Don't create the cross shadow bounday range if the one of the roots is
      // an UA widget regardless whether the boundaries are allowed to cross
      // shadow boundary or not.
      if (aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes &&
          !IsRootUAWidget(newStartRoot) && !IsRootUAWidget(newEndRoot)) {
        const auto startInFlat =
            aStartBoundary.AsRangeBoundaryInFlatTree(RangeBoundaryFor::Start);
        const auto endInFlat =
            aEndBoundary.AsRangeBoundaryInFlatTree(RangeBoundaryFor::End);
        if (MOZ_UNLIKELY(!startInFlat.IsSet() || !endInFlat.IsSet())) {
          NS_WARNING_ASSERTION(
              !startInFlat.IsSet(),
              nsFmtCString(
                  FMT_STRING("aStartBoundary={} could not convert to a "
                             "point in the flat tree"),
                  aStartBoundary)
                  .get());
          NS_WARNING_ASSERTION(
              !endInFlat.IsSet(),
              nsFmtCString(FMT_STRING("aEndBoundary={} could not convert to a "
                                      "point in the flat tree"),
                           aEndBoundary)
                  .get());
          return NS_ERROR_FAILURE;
        }
        aRange->AsDynamicRange()
            ->CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(startInFlat,
                                                             endInFlat);
      }
    }
    return NS_OK;
  }

  const bool useFlatTree =
      aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes;
  const Maybe<int32_t> pointOrder =
      useFlatTree ? nsContentUtils::ComparePoints<TreeKind::Flat>(
                        aStartBoundary, aEndBoundary)
                  : nsContentUtils::ComparePoints<TreeKind::ShadowIncludingDOM>(
                        aStartBoundary, aEndBoundary);
  if (!pointOrder) {
    // Safely return a value but also detected this in debug builds.
    MOZ_ASSERT_UNREACHABLE("The boundaries are not connected");
    return NS_ERROR_INVALID_ARG;
  }

  // If the end point is before the start point, this should be collapsed at
  // the end point.
  if (*pointOrder == 1) {
    aRange->DoSetRange(aEndBoundary, aEndBoundary, newEndRoot);
    return NS_OK;
  }

  // Otherwise, set the range as specified.  However, the order may be opposite
  // in the same tree if the given range is for the flat tree.  Thus, we need to
  // recompute the order within the same tree if we computed the order in the
  // flat tree.
  if (!useFlatTree) {
    aRange->DoSetRange(aStartBoundary, aEndBoundary, newStartRoot);
  } else {
    const Maybe<int32_t> pointOrderInSameTree =
        nsContentUtils::ComparePoints<TreeKind::DOM>(aStartBoundary,
                                                     aEndBoundary);
    if (MOZ_UNLIKELY(pointOrderInSameTree.isNothing())) {
      MOZ_ASSERT_UNREACHABLE(
          "The boundaries are not connected in the same DOM tree");
      aRange->DoSetRange(aEndBoundary, aEndBoundary, newStartRoot);
    } else if (*pointOrderInSameTree != 1) {
      aRange->DoSetRange(aStartBoundary, aEndBoundary, newStartRoot);
    } else {
      aRange->DoSetRange(aEndBoundary, aStartBoundary, newStartRoot);
    }
  }

  if (aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes &&
      aRange->IsDynamicRange()) {
    const bool isCollapsing = aStartBoundary == aEndBoundary;
    const auto startInFlat = aStartBoundary
                                 .AsRangeBoundaryInFlatTree(
                                     isCollapsing ? RangeBoundaryFor::Collapsed
                                                  : RangeBoundaryFor::Start)
                                 .AsRaw();
    const auto endInFlat =
        isCollapsing
            ? startInFlat
            : aEndBoundary.AsRangeBoundaryInFlatTree(RangeBoundaryFor::End)
                  .AsRaw();
    if (MOZ_UNLIKELY(!startInFlat.IsSet() || !endInFlat.IsSet())) {
      NS_WARNING_ASSERTION(
          !startInFlat.IsSet(),
          nsFmtCString(FMT_STRING("aStartBoundary={} could not convert to a "
                                  "point in the flat tree"),
                       aStartBoundary)
              .get());
      NS_WARNING_ASSERTION(
          !endInFlat.IsSet(),
          nsFmtCString(FMT_STRING("aEndBoundary={} could not convert to a "
                                  "point in the flat tree"),
                       aEndBoundary)
              .get());
      return NS_ERROR_FAILURE;
    }

    aRange->AsDynamicRange()->CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
        startInFlat, endInFlat);
  }

  return NS_OK;
}

bool AbstractRange::IsInSelection(const Selection& aSelection) const {
  return mSelections.Contains(&aSelection);
}

void AbstractRange::RegisterSelection(Selection& aSelection) {
  if (IsInSelection(aSelection)) {
    return;
  }
  bool isFirstSelection = mSelections.IsEmpty();
  mSelections.AppendElement(&aSelection);
  const bool isValidRange = !IsStaticRange() || AsStaticRange()->IsValid();
  if (isFirstSelection && !mRegisteredClosestCommonInclusiveAncestor &&
      isValidRange) {
    nsINode* commonAncestor =
        GetClosestCommonInclusiveAncestor(AllowRangeCrossShadowBoundary::Yes);
    MOZ_ASSERT(commonAncestor, "unexpected disconnected nodes");
    RegisterClosestCommonInclusiveAncestor(commonAncestor);
  }
}

const nsTArray<WeakPtr<Selection>>& AbstractRange::GetSelections() const {
  return mSelections;
}

void AbstractRange::UnregisterSelection(const Selection& aSelection,
                                        IsUnlinking aIsUnlinking) {
  mSelections.RemoveElement(&aSelection);
  if (mSelections.IsEmpty() && mRegisteredClosestCommonInclusiveAncestor) {
    UnregisterClosestCommonInclusiveAncestor(aIsUnlinking);
    MOZ_DIAGNOSTIC_ASSERT(
        !mRegisteredClosestCommonInclusiveAncestor,
        "How can we have a registered common ancestor when we "
        "just unregistered?");
    MOZ_DIAGNOSTIC_ASSERT(
        !isInList(),
        "Shouldn't be registered if we have no "
        "mRegisteredClosestCommonInclusiveAncestor after unregistering");
  }
}

void AbstractRange::RegisterClosestCommonInclusiveAncestor(nsINode* aNode) {
  MOZ_ASSERT(aNode, "bad arg");

  MOZ_DIAGNOSTIC_ASSERT(IsInAnySelection(),
                        "registering range not in selection");

  mRegisteredClosestCommonInclusiveAncestor = aNode;

  MarkDescendants(*aNode);

  UniquePtr<LinkedList<AbstractRange>>& ranges =
      aNode->GetClosestCommonInclusiveAncestorRangesPtr();
  if (!ranges) {
    ranges = MakeUnique<LinkedList<AbstractRange>>();
  }

  MOZ_DIAGNOSTIC_ASSERT(!isInList());
  ranges->insertBack(this);
  aNode->SetClosestCommonInclusiveAncestorForRangeInSelection();
}

void AbstractRange::UnregisterClosestCommonInclusiveAncestor(
    IsUnlinking aIsUnlinking) {
  if (!mRegisteredClosestCommonInclusiveAncestor) {
    return;
  }
  nsCOMPtr oldClosestCommonInclusiveAncestor =
      mRegisteredClosestCommonInclusiveAncestor;
  mRegisteredClosestCommonInclusiveAncestor = nullptr;
  LinkedList<AbstractRange>* ranges =
      oldClosestCommonInclusiveAncestor
          ->GetExistingClosestCommonInclusiveAncestorRanges();
  MOZ_ASSERT(ranges);

#ifdef DEBUG
  bool found = false;
  for (AbstractRange* range : *ranges) {
    if (range == this) {
      found = true;
      break;
    }
  }
  MOZ_ASSERT(found,
             "We should be in the list on our registered common ancestor");
#endif  // DEBUG

  remove();

  // We don't want to waste time unmarking flags on nodes that are
  // being unlinked anyway.
  if (aIsUnlinking == IsUnlinking::No && ranges->isEmpty()) {
    oldClosestCommonInclusiveAncestor
        ->ClearClosestCommonInclusiveAncestorForRangeInSelection();
    UnmarkDescendants(*oldClosestCommonInclusiveAncestor);
  }
  oldClosestCommonInclusiveAncestor = nullptr;
}

void AbstractRange::UpdateCommonAncestorIfNecessary() {
  nsINode* oldCommonAncestor = mRegisteredClosestCommonInclusiveAncestor;
  nsINode* newCommonAncestor =
      GetClosestCommonInclusiveAncestor(AllowRangeCrossShadowBoundary::Yes);
  if (newCommonAncestor != oldCommonAncestor) {
    UnregisterClosestCommonInclusiveAncestor();

    if (newCommonAncestor) {
      RegisterClosestCommonInclusiveAncestor(newCommonAncestor);
    } else {
      MOZ_DIAGNOSTIC_ASSERT(!mIsPositioned, "unexpected disconnected nodes");
      mSelections.Clear();
      MOZ_DIAGNOSTIC_ASSERT(
          !mRegisteredClosestCommonInclusiveAncestor,
          "How can we have a registered common ancestor when we "
          "didn't register ourselves?");
      MOZ_DIAGNOSTIC_ASSERT(!isInList(),
                            "Shouldn't be registered if we have no "
                            "mRegisteredClosestCommonInclusiveAncestor");
    }
  }
}

const RangeBoundary& AbstractRange::MayCrossShadowBoundaryStartRef() const {
  return IsDynamicRange() ? AsDynamicRange()->MayCrossShadowBoundaryStartRef()
                          : mStart;
}

const RangeBoundary& AbstractRange::MayCrossShadowBoundaryEndRef() const {
  return IsDynamicRange() ? AsDynamicRange()->MayCrossShadowBoundaryEndRef()
                          : mEnd;
}

nsIContent* AbstractRange::GetMayCrossShadowBoundaryChildAtStartOffset() const {
  return IsDynamicRange()
             ? AsDynamicRange()->GetMayCrossShadowBoundaryChildAtStartOffset()
             : mStart.GetChildAtOffset();
}

nsIContent* AbstractRange::GetMayCrossShadowBoundaryChildAtEndOffset() const {
  return IsDynamicRange()
             ? AsDynamicRange()->GetMayCrossShadowBoundaryChildAtEndOffset()
             : mEnd.GetChildAtOffset();
}

nsINode* AbstractRange::GetMayCrossShadowBoundaryStartContainer() const {
  return IsDynamicRange()
             ? AsDynamicRange()->GetMayCrossShadowBoundaryStartContainer()
             : mStart.GetContainer();
}

nsINode* AbstractRange::GetMayCrossShadowBoundaryEndContainer() const {
  return IsDynamicRange()
             ? AsDynamicRange()->GetMayCrossShadowBoundaryEndContainer()
             : mEnd.GetContainer();
}

bool AbstractRange::MayCrossShadowBoundary() const {
  return IsDynamicRange() ? !!AsDynamicRange()->GetCrossShadowBoundaryRange()
                          : false;
}

uint32_t AbstractRange::MayCrossShadowBoundaryStartOffset() const {
  return IsDynamicRange()
             ? AsDynamicRange()->MayCrossShadowBoundaryStartOffset()
             : static_cast<uint32_t>(*mStart.Offset(
                   RangeBoundary::OffsetFilter::kValidOrInvalidOffsets));
}

uint32_t AbstractRange::MayCrossShadowBoundaryEndOffset() const {
  return IsDynamicRange()
             ? AsDynamicRange()->MayCrossShadowBoundaryEndOffset()
             : static_cast<uint32_t>(*mEnd.Offset(
                   RangeBoundary::OffsetFilter::kValidOrInvalidOffsets));
}

nsINode* AbstractRange::GetParentObject() const { return mOwner; }

JSObject* AbstractRange::WrapObject(JSContext* aCx,
                                    JS::Handle<JSObject*> aGivenProto) {
  MOZ_CRASH("Must be overridden");
}

bool AbstractRange::AreNormalRangeAndCrossShadowBoundaryRangeCollapsed() const {
  if (!Collapsed()) {
    return false;
  }

  // We know normal range is collapsed at this point
  if (IsStaticRange()) {
    return true;
  }

  if (const CrossShadowBoundaryRange* crossShadowBoundaryRange =
          AsDynamicRange()->GetCrossShadowBoundaryRange()) {
    return crossShadowBoundaryRange->Collapsed();
  }

  return true;
}

void AbstractRange::ClearForReuse() {
  mOwner = nullptr;
  mStart = RangeBoundary(mStart.GetTreeKind());
  mEnd = RangeBoundary(mEnd.GetTreeKind());
  mIsPositioned = false;
  mIsGenerated = false;
  mCalledByJS = false;
}

/*static*/
bool AbstractRange::IsRootUAWidget(const nsINode* aRoot) {
  MOZ_ASSERT(aRoot);
  if (const ShadowRoot* shadowRoot = ShadowRoot::FromNode(aRoot)) {
    return shadowRoot->IsUAWidget();
  }
  return false;
}

already_AddRefed<StaticRange> AbstractRange::GetShrunkenRangeToVisibleLeaves()
    const {
  if (NS_WARN_IF(!IsPositioned()) || NS_WARN_IF(Collapsed()) ||
      NS_WARN_IF(IsStaticRange() && !AsStaticRange()->IsValid())) {
    return nullptr;
  }

  const RawRangeBoundary startBoundary =
      SelectionMovementUtils::GetFirstVisiblePointAtLeaf(*this);
  if (MOZ_UNLIKELY(!startBoundary.IsSet())) {
    return nullptr;
  }
  const RawRangeBoundary endBoundary =
      SelectionMovementUtils::GetLastVisiblePointAtLeaf(*this);
  if (MOZ_UNLIKELY(!endBoundary.IsSet())) {
    return nullptr;
  }
  IgnoredErrorResult error;
  RefPtr<StaticRange> range =
      StaticRange::Create(startBoundary, endBoundary, error);
  if (NS_WARN_IF(error.Failed())) {
    error.SuppressException();
    return nullptr;
  }
  return range.forget();
}

static void ExtractRectFromOffset(nsIFrame* aFrame, const int32_t aOffset,
                                  nsRect* aR, bool aFlushToOriginEdge,
                                  bool aClampToEdge) {
  MOZ_ASSERT(aFrame);
  MOZ_ASSERT(aR);

  nsPoint point;
  aFrame->GetPointFromOffset(aOffset, &point);

  // Determine if aFrame has a vertical writing mode, which will change our math
  // on the output rect.
  bool isVertical = aFrame->GetWritingMode().IsVertical();

  if (!aClampToEdge && !aR->Contains(point)) {
    // If point is outside aR, and we aren't clamping, output an empty rect
    // with origin at the point.
    if (isVertical) {
      aR->SetHeight(0);
      aR->y = point.y;
    } else {
      aR->SetWidth(0);
      aR->x = point.x;
    }
    return;
  }

  if (aClampToEdge) {
    point = aR->ClampPoint(point);
  }

  // point is within aR, and now we'll modify aR to output a rect that has point
  // on one edge. But which edge?
  if (aFlushToOriginEdge) {
    // The output rect should be flush to the edge of aR that contains the
    // origin.
    if (isVertical) {
      aR->SetHeight(point.y - aR->y);
    } else {
      aR->SetWidth(point.x - aR->x);
    }
  } else {
    // The output rect should be flush to the edge of aR opposite the origin.
    if (isVertical) {
      aR->SetHeight(aR->YMost() - point.y);
      aR->y = point.y;
    } else {
      aR->SetWidth(aR->XMost() - point.x);
      aR->x = point.x;
    }
  }
}

static nsTextFrame* GetTextFrameForContent(nsIContent* aContent) {
  return do_QueryFrame(aContent->GetPrimaryFrame());
}

static void GetPartialTextRect(RectCallback* aCallback,
                               Sequence<nsString>* aTextList,
                               nsIContent* aContent, int32_t aStartOffset,
                               int32_t aEndOffset, bool aClampToEdge) {
  nsTextFrame* textFrame = GetTextFrameForContent(aContent);
  if (!textFrame) {
    return;
  }
  nsIFrame* relativeTo =
      nsLayoutUtils::GetContainingBlockForClientRect(textFrame);

  for (nsTextFrame* f = textFrame->FindContinuationForOffset(aStartOffset); f;
       f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
    int32_t fstart = f->GetContentOffset(), fend = f->GetContentEnd();
    if (fend <= aStartOffset) {
      continue;
    }
    if (fstart >= aEndOffset) {
      break;
    }

    // Calculate the text content offsets we'll need if text is requested.
    int32_t textContentStart = fstart;
    int32_t textContentEnd = fend;

    // overlapping with the offset we want
    f->EnsureTextRun(nsTextFrame::eInflated);
    gfxTextRun* run = f->GetTextRun(nsTextFrame::eInflated);
    if (NS_WARN_IF(!run)) {
      continue;
    }
    bool topLeftToBottomRight = !run->IsInlineReversed();
    nsRect r = f->GetRectRelativeToSelf();
    if (fstart < aStartOffset) {
      // aStartOffset is within this frame
      ExtractRectFromOffset(f, aStartOffset, &r, !topLeftToBottomRight,
                            aClampToEdge);
      textContentStart = aStartOffset;
    }
    if (fend > aEndOffset) {
      // aEndOffset is in the middle of this frame
      ExtractRectFromOffset(f, aEndOffset, &r, topLeftToBottomRight,
                            aClampToEdge);
      textContentEnd = aEndOffset;
    }
    r = nsLayoutUtils::TransformFrameRectToAncestor(f, r, relativeTo);
    aCallback->AddRect(r);

    // Finally capture the text, if requested.
    if (aTextList) {
      nsIFrame::RenderedText renderedText =
          f->GetRenderedText(textContentStart, textContentEnd,
                             nsIFrame::TextOffsetType::OffsetsInContentText,
                             nsIFrame::TrailingWhitespace::DontTrim);

      if (!aTextList->AppendElement(renderedText.mString, fallible)) {
        return;
      }
    }
  }
}

static void CollectClientRectsForSubtree(
    nsINode* aNode, RectCallback* aCollector, Sequence<nsString>* aTextList,
    nsINode* aStartContainer, uint32_t aStartOffset, nsINode* aEndContainer,
    uint32_t aEndOffset, bool aClampToEdge, bool aTextOnly) {
  auto* content = nsIContent::FromNode(aNode);
  if (!content) {
    return;
  }

  const bool isText = content->IsText();
  if (isText) {
    if (aNode == aStartContainer) {
      int32_t offset = aStartContainer == aEndContainer
                           ? static_cast<int32_t>(aEndOffset)
                           : content->AsText()->TextDataLength();
      GetPartialTextRect(aCollector, aTextList, content,
                         static_cast<int32_t>(aStartOffset), offset,
                         aClampToEdge);
      return;
    }

    if (aNode == aEndContainer) {
      GetPartialTextRect(aCollector, aTextList, content, 0,
                         static_cast<int32_t>(aEndOffset), aClampToEdge);
      return;
    }
  }

  if (nsIFrame* frame = content->GetPrimaryFrame()) {
    if (!aTextOnly || isText) {
      nsLayoutUtils::GetAllInFlowRectsAndTexts(
          frame, nsLayoutUtils::GetContainingBlockForClientRect(frame),
          aCollector, aTextList,
          nsLayoutUtils::GetAllInFlowRectsFlag::AccountForTransforms);
      if (isText) {
        return;
      }
      aTextOnly = true;
      // We just get the text when calling GetAllInFlowRectsAndTexts, so we
      // don't need to call it again when visiting the children.
      aTextList = nullptr;
    }
  } else if (!content->IsElement() ||
             !content->AsElement()->IsDisplayContents()) {
    return;
  }

  FlattenedChildIterator childIter(content);
  for (nsIContent* child = childIter.GetNextChild(); child;
       child = childIter.GetNextChild()) {
    CollectClientRectsForSubtree(child, aCollector, aTextList, aStartContainer,
                                 aStartOffset, aEndContainer, aEndOffset,
                                 aClampToEdge, aTextOnly);
  }
}

/* static */
void AbstractRange::CollectClientRectsAndText(
    RectCallback* aCollector, Sequence<nsString>* aTextList,
    AbstractRange* aRange, nsINode* aStartContainer, uint32_t aStartOffset,
    nsINode* aEndContainer, uint32_t aEndOffset, bool aClampToEdge,
    bool aFlushLayout) {
  // Currently, this method is called with start of end offset of AbstractRange.
  // So, they must be between 0 - INT32_MAX.
  MOZ_ASSERT(RangeUtils::IsValidOffset(aStartOffset));
  MOZ_ASSERT(RangeUtils::IsValidOffset(aEndOffset));

  // Hold strong pointers across the flush
  nsCOMPtr<nsINode> startContainer = aStartContainer;
  nsCOMPtr<nsINode> endContainer = aEndContainer;

  // Flush out layout so our frames are up to date.
  if (!aStartContainer->IsInComposedDoc()) {
    return;
  }

  if (aFlushLayout) {
    if (auto* content = nsIContent::FromNode(aStartContainer)) {
      content->GetPrimaryFrame(FlushType::Layout);
    } else {
      aStartContainer->OwnerDoc()->FlushPendingNotifications(FlushType::Layout);
    }
    // Recheck whether we're still in the document
    if (!aStartContainer->IsInComposedDoc()) {
      return;
    }
  }

  RangeSubtreeIterator iter;

  if (NS_FAILED(iter.Init(aRange))) {
    return;
  }

  if (iter.IsDone()) {
    // the range is collapsed, only continue if the cursor is in a text node
    if (!aStartContainer->IsText()) {
      return;
    }
    nsTextFrame* textFrame = GetTextFrameForContent(aStartContainer->AsText());
    if (!textFrame) {
      return;
    }
    int32_t outOffset = 0;
    nsIFrame* outFrame = nullptr;
    textFrame->GetChildFrameContainingOffset(static_cast<int32_t>(aStartOffset),
                                             false, &outOffset, &outFrame);
    if (!outFrame) {
      return;
    }
    nsIFrame* relativeTo =
        nsLayoutUtils::GetContainingBlockForClientRect(outFrame);
    nsRect r = outFrame->GetRectRelativeToSelf();
    ExtractRectFromOffset(outFrame, static_cast<int32_t>(aStartOffset), &r,
                          false, aClampToEdge);
    r.SetWidth(0);
    r = nsLayoutUtils::TransformFrameRectToAncestor(outFrame, r, relativeTo);
    aCollector->AddRect(r);
    return;
  }

  do {
    nsCOMPtr<nsINode> node = iter.GetCurrentNode();
    iter.Next();

    CollectClientRectsForSubtree(node, aCollector, aTextList, aStartContainer,
                                 aStartOffset, aEndContainer, aEndOffset,
                                 aClampToEdge, false);
  } while (!iter.IsDone());
}

already_AddRefed<DOMRect> AbstractRange::GetBoundingClientRect(
    bool aClampToEdge, bool aFlushLayout) {
  RefPtr<DOMRect> rect = new DOMRect(ToSupports(mOwner));
  if (!mIsPositioned) {
    return rect.forget();
  }

  nsLayoutUtils::RectAccumulator accumulator;
  CollectClientRectsAndText(
      &accumulator, nullptr, this, mStart.GetContainer(),
      *mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
      mEnd.GetContainer(),
      *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets), aClampToEdge,
      aFlushLayout);

  nsRect r = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect
                                               : accumulator.mResultRect;
  rect->SetLayoutRect(r);
  return rect.forget();
}

already_AddRefed<DOMRectList> AbstractRange::GetClientRects(bool aClampToEdge,
                                                            bool aFlushLayout) {
  return GetClientRectsInner(AllowRangeCrossShadowBoundary::No, aClampToEdge,
                             aFlushLayout);
}

already_AddRefed<DOMRectList>
AbstractRange::GetAllowCrossShadowBoundaryClientRects(bool aClampToEdge,
                                                      bool aFlushLayout) {
  return GetClientRectsInner(AllowRangeCrossShadowBoundary::Yes, aClampToEdge,
                             aFlushLayout);
}

void AbstractRange::CollectClientRects(RectCallback& aCallback,
                                       bool aClampToEdge) const {
  if (!mIsPositioned) {
    return;
  }
  CollectClientRectsAndText(
      &aCallback, nullptr, const_cast<AbstractRange*>(this),
      mStart.GetContainer(),
      *mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
      mEnd.GetContainer(),
      *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets), aClampToEdge,
      /* aFlushLayout */ false);
}

already_AddRefed<DOMRectList> AbstractRange::GetClientRectsInner(
    AllowRangeCrossShadowBoundary aAllowCrossShadowBoundaryRange,
    bool aClampToEdge, bool aFlushLayout) {
  if (!mIsPositioned) {
    return nullptr;
  }

  RefPtr<DOMRectList> rectList = new DOMRectList(ToSupports(mOwner));

  nsLayoutUtils::RectListBuilder builder(rectList);

  const auto& startRef =
      aAllowCrossShadowBoundaryRange == AllowRangeCrossShadowBoundary::Yes
          ? MayCrossShadowBoundaryStartRef()
          : mStart;
  const auto& endRef =
      aAllowCrossShadowBoundaryRange == AllowRangeCrossShadowBoundary::Yes
          ? MayCrossShadowBoundaryEndRef()
          : mEnd;

  CollectClientRectsAndText(
      &builder, nullptr, this, startRef.GetContainer(),
      *startRef.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
      endRef.GetContainer(),
      *endRef.Offset(RangeBoundary::OffsetFilter::kValidOffsets), aClampToEdge,
      aFlushLayout);
  return rectList.forget();
}

void AbstractRange::GetClientRectsAndTexts(
    mozilla::dom::ClientRectsAndTexts& aResult, ErrorResult& aErr) {
  if (!mIsPositioned) {
    return;
  }

  aResult.mRectList = new DOMRectList(ToSupports(mOwner));

  nsLayoutUtils::RectListBuilder builder(aResult.mRectList);

  CollectClientRectsAndText(
      &builder, &aResult.mTextList, this, mStart.GetContainer(),
      *mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
      mEnd.GetContainer(),
      *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets), true, true);
}

}  // namespace mozilla::dom

Messung V0.5 in Prozent
C=90 H=97 G=93

¤ Dauer der Verarbeitung: 0.17 Sekunden  (vorverarbeitet am  2026-08-25) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

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.