/* 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 "ContentIterator.h" #include "mozilla/Assertions.h" #include "mozilla/DebugOnly.h" #include "mozilla/RangeBoundary.h" #include "mozilla/RangeUtils.h" #include "mozilla/Result.h" #include "mozilla/dom/HTMLSlotElement.h" #include "mozilla/dom/ShadowRoot.h" #include "nsContentUtils.h" #include "nsElementTable.h" #include "nsIContent.h" #include "nsIContentInlines.h" #include "nsRange.h" namespace mozilla { using namespace dom; #define NS_INSTANTIATE_CONTENT_ITER_BASE_METHOD(aResultType, aMethodName, ...) \ template aResultType ContentIteratorBase>::aMethodName( \ __VA_ARGS__); \ template aResultType ContentIteratorBase::aMethodName(__VA_ARGS__) static bool ComparePostMode(const RawRangeBoundary& aStart, const RawRangeBoundary& aEnd, nsINode& aNode) { nsINode* parent = aNode.GetParentNode(); if (!parent) { return false; } // aNode should always be content, as we have a parent, but let's just be // extra careful and check. nsIContent* content = NS_WARN_IF(!aNode.IsContent()) ? nullptr : aNode.AsContent(); // Post mode: start < node <= end. RawRangeBoundary afterNode(parent, content); const auto isStartLessThanAfterNode = [&]() { const Maybe startComparedToAfterNode = nsContentUtils::ComparePoints(aStart, afterNode); return !NS_WARN_IF(!startComparedToAfterNode) && (*startComparedToAfterNode < 0); }; const auto isAfterNodeLessOrEqualToEnd = [&]() { const Maybe afterNodeComparedToEnd = nsContentUtils::ComparePoints(afterNode, aEnd); return !NS_WARN_IF(!afterNodeComparedToEnd) && (*afterNodeComparedToEnd <= 0); }; return isStartLessThanAfterNode() && isAfterNodeLessOrEqualToEnd(); } static bool ComparePreMode(const RawRangeBoundary& aStart, const RawRangeBoundary& aEnd, nsINode& aNode) { nsINode* parent = aNode.GetParentNode(); if (!parent) { return false; } // Pre mode: start <= node < end. RawRangeBoundary beforeNode(parent, aNode.GetPreviousSibling()); const auto isStartLessOrEqualToBeforeNode = [&]() { const Maybe startComparedToBeforeNode = nsContentUtils::ComparePoints(aStart, beforeNode); return !NS_WARN_IF(!startComparedToBeforeNode) && (*startComparedToBeforeNode <= 0); }; const auto isBeforeNodeLessThanEndNode = [&]() { const Maybe beforeNodeComparedToEnd = nsContentUtils::ComparePoints(beforeNode, aEnd); return !NS_WARN_IF(!beforeNodeComparedToEnd) && (*beforeNodeComparedToEnd < 0); }; return isStartLessOrEqualToBeforeNode() && isBeforeNodeLessThanEndNode(); } /////////////////////////////////////////////////////////////////////////// // NodeIsInTraversalRange: returns true if content is visited during // the traversal of the range in the specified mode. // static bool NodeIsInTraversalRange(nsINode* aNode, bool aIsPreMode, const RawRangeBoundary& aStart, const RawRangeBoundary& aEnd) { if (NS_WARN_IF(!aStart.IsSet()) || NS_WARN_IF(!aEnd.IsSet()) || NS_WARN_IF(!aNode)) { return false; } // If a leaf node contains an end point of the traversal range, it is // always in the traversal range. if (aNode == aStart.GetContainer() || aNode == aEnd.GetContainer()) { if (aNode->IsCharacterData()) { return true; // text node or something } if (!aNode->HasChildren()) { MOZ_ASSERT( aNode != aStart.GetContainer() || aStart.IsStartOfContainer(), "aStart.GetContainer() doesn't have children and not a data node, " "aStart should be at the beginning of its container"); MOZ_ASSERT( aNode != aEnd.GetContainer() || aEnd.IsStartOfContainer(), "aEnd.GetContainer() doesn't have children and not a data node, " "aEnd should be at the beginning of its container"); return true; } } if (aIsPreMode) { return ComparePreMode(aStart, aEnd, *aNode); } return ComparePostMode(aStart, aEnd, *aNode); } void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, PostContentIterator& aField, const char* aName, uint32_t aFlags = 0) { ImplCycleCollectionTraverse( aCallback, static_cast(aField), aName, aFlags); } void ImplCycleCollectionUnlink(PostContentIterator& aField) { ImplCycleCollectionUnlink(static_cast(aField)); } void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, PreContentIterator& aField, const char* aName, uint32_t aFlags = 0) { ImplCycleCollectionTraverse( aCallback, static_cast(aField), aName, aFlags); } void ImplCycleCollectionUnlink(PreContentIterator& aField) { ImplCycleCollectionUnlink(static_cast(aField)); } void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, ContentSubtreeIterator& aField, const char* aName, uint32_t aFlags = 0) { ImplCycleCollectionTraverse(aCallback, aField.mRange, aName, aFlags); ImplCycleCollectionTraverse( aCallback, static_cast(aField), aName, aFlags); } void ImplCycleCollectionUnlink(ContentSubtreeIterator& aField) { ImplCycleCollectionUnlink(aField.mRange); ImplCycleCollectionUnlink(static_cast(aField)); } /****************************************************** * ContentIteratorBase ******************************************************/ NS_INSTANTIATE_CONTENT_ITER_BASE_METHOD(, ContentIteratorBase, Order); template ContentIteratorBase::ContentIteratorBase(Order aOrder) : mOrder(aOrder) {} template ContentIteratorBase>::~ContentIteratorBase(); template ContentIteratorBase::~ContentIteratorBase(); template ContentIteratorBase::~ContentIteratorBase() { MOZ_DIAGNOSTIC_ASSERT_IF(mMutationGuard.isSome(), !mMutationGuard->Mutated(0)); } /****************************************************** * Init routines ******************************************************/ NS_INSTANTIATE_CONTENT_ITER_BASE_METHOD(nsresult, Init, nsINode*); template nsresult ContentIteratorBase::Init(nsINode* aRoot) { if (NS_WARN_IF(!aRoot)) { return NS_ERROR_NULL_POINTER; } if (mOrder == Order::Pre) { mFirst = aRoot; mLast = ContentIteratorBase::GetDeepLastInclusiveDescendant( aRoot); NS_WARNING_ASSERTION(mLast, "GetDeepLastInclusiveDescendant returned null"); } else { mFirst = ContentIteratorBase::GetDeepFirstInclusiveDescendant( aRoot); NS_WARNING_ASSERTION(mFirst, "GetDeepFirstInclusiveDescendant returned null"); mLast = aRoot; } mClosestCommonInclusiveAncestor = aRoot; mCurNode = mFirst; return NS_OK; } NS_INSTANTIATE_CONTENT_ITER_BASE_METHOD(nsresult, Init, AbstractRange*); template nsresult ContentIteratorBase::Init(AbstractRange* aRange) { if (NS_WARN_IF(!aRange)) { return NS_ERROR_INVALID_ARG; } if (NS_WARN_IF(!aRange->IsPositioned())) { return NS_ERROR_INVALID_ARG; } return InitInternal(aRange->StartRef().AsRaw(), aRange->EndRef().AsRaw()); } NS_INSTANTIATE_CONTENT_ITER_BASE_METHOD(nsresult, Init, nsINode*, uint32_t, nsINode*, uint32_t); template nsresult ContentIteratorBase::Init(nsINode* aStartContainer, uint32_t aStartOffset, nsINode* aEndContainer, uint32_t aEndOffset) { if (NS_WARN_IF(!RangeUtils::IsValidPoints(aStartContainer, aStartOffset, aEndContainer, aEndOffset))) { return NS_ERROR_INVALID_ARG; } return InitInternal(RawRangeBoundary(aStartContainer, aStartOffset), RawRangeBoundary(aEndContainer, aEndOffset)); } NS_INSTANTIATE_CONTENT_ITER_BASE_METHOD(nsresult, Init, const RawRangeBoundary&, const RawRangeBoundary&); template nsresult ContentIteratorBase::Init(const RawRangeBoundary& aStart, const RawRangeBoundary& aEnd) { if (NS_WARN_IF(!RangeUtils::IsValidPoints(aStart, aEnd))) { return NS_ERROR_INVALID_ARG; } return InitInternal(aStart, aEnd); } template nsresult ContentIteratorBase::InitWithoutValidatingPoints( const RawRangeBoundary& aStart, const RawRangeBoundary& aEnd) { MOZ_DIAGNOSTIC_ASSERT(RangeUtils::IsValidPoints(aStart, aEnd)); return InitInternal(aStart, aEnd); } template class MOZ_STACK_CLASS ContentIteratorBase::Initializer final { public: Initializer(ContentIteratorBase& aIterator, const RawRangeBoundary& aStart, const RawRangeBoundary& aEnd) : mIterator{aIterator}, mStart{aStart}, mEnd{aEnd}, mStartIsCharacterData{mStart.GetContainer()->IsCharacterData()} { MOZ_ASSERT(mStart.IsSetAndValid()); MOZ_ASSERT(mEnd.IsSetAndValid()); } nsresult Run(); private: /** * @return may be nullptr. */ nsINode* DetermineFirstNode() const; /** * @return may be nullptr. */ [[nodiscard]] Result DetermineLastNode() const; bool IsCollapsedNonCharacterRange() const; bool IsSingleNodeCharacterRange() const; ContentIteratorBase& mIterator; const RawRangeBoundary& mStart; const RawRangeBoundary& mEnd; const bool mStartIsCharacterData; }; template <> nsresult ContentIteratorBase>::InitInternal( const RawRangeBoundary& aStart, const RawRangeBoundary& aEnd) { Initializer initializer{*this, aStart, aEnd}; return initializer.Run(); } template <> nsresult ContentIteratorBase::InitInternal( const RawRangeBoundary& aStart, const RawRangeBoundary& aEnd) { Initializer initializer{*this, aStart, aEnd}; nsresult rv = initializer.Run(); if (NS_FAILED(rv)) { return rv; } mMutationGuard.emplace(); mAssertNoGC.emplace(); return NS_OK; } template bool ContentIteratorBase::Initializer::IsCollapsedNonCharacterRange() const { return !mStartIsCharacterData && mStart == mEnd; } template bool ContentIteratorBase::Initializer::IsSingleNodeCharacterRange() const { return mStartIsCharacterData && mStart.GetContainer() == mEnd.GetContainer(); } template nsresult ContentIteratorBase::Initializer::Run() { // get common content parent mIterator.mClosestCommonInclusiveAncestor = nsContentUtils::GetClosestCommonInclusiveAncestor(mStart.GetContainer(), mEnd.GetContainer()); if (NS_WARN_IF(!mIterator.mClosestCommonInclusiveAncestor)) { return NS_ERROR_FAILURE; } // Check to see if we have a collapsed range, if so, there is nothing to // iterate over. // // XXX: CharacterDataNodes (text nodes) are currently an exception, since // we always want to be able to iterate text nodes at the end points // of a range. if (IsCollapsedNonCharacterRange()) { mIterator.SetEmpty(); return NS_OK; } if (IsSingleNodeCharacterRange()) { mIterator.mFirst = mStart.GetContainer()->AsContent(); mIterator.mLast = mIterator.mFirst; mIterator.mCurNode = mIterator.mFirst; return NS_OK; } mIterator.mFirst = DetermineFirstNode(); if (Result lastNode = DetermineLastNode(); NS_WARN_IF(lastNode.isErr())) { return lastNode.unwrapErr(); } else { mIterator.mLast = lastNode.unwrap(); } // If either first or last is null, they both have to be null! if (!mIterator.mFirst || !mIterator.mLast) { mIterator.SetEmpty(); } mIterator.mCurNode = mIterator.mFirst; return NS_OK; } template nsINode* ContentIteratorBase::Initializer::DetermineFirstNode() const { nsIContent* cChild = nullptr; // Try to get the child at our starting point. This might return null if // mStart is immediately after the last node in mStart.GetContainer(). if (!mStartIsCharacterData) { cChild = mStart.GetChildAtOffset(); } if (!cChild) { // No children (possibly a
or text node), or index is after last child. if (mIterator.mOrder == Order::Pre) { // XXX: In the future, if start offset is after the last // character in the cdata node, should we set mFirst to // the next sibling? // Normally we would skip the start node because the start node is outside // of the range in pre mode. However, if aStartOffset == 0, and the node // is a non-container node (e.g.
), we don't skip the node in this // case in order to address bug 1215798. bool startIsContainer = true; if (mStart.GetContainer()->IsHTMLElement()) { nsAtom* name = mStart.GetContainer()->NodeInfo()->NameAtom(); startIsContainer = nsHTMLElement::IsContainer(nsHTMLTags::AtomTagToId(name)); } if (!mStartIsCharacterData && (startIsContainer || !mStart.IsStartOfContainer())) { nsINode* const result = ContentIteratorBase::GetNextSibling( mStart.GetContainer()); NS_WARNING_ASSERTION(result, "GetNextSibling returned null"); // Does mFirst node really intersect the range? The range could be // 'degenerate', i.e., not collapsed but still contain no content. if (result && NS_WARN_IF(!NodeIsInTraversalRange( result, mIterator.mOrder == Order::Pre, mStart, mEnd))) { return nullptr; } return result; } return mStart.GetContainer()->AsContent(); } // post-order if (NS_WARN_IF(!mStart.GetContainer()->IsContent())) { // What else can we do? return nullptr; } return mStart.GetContainer()->AsContent(); } if (mIterator.mOrder == Order::Pre) { return cChild; } // post-order nsINode* const result = ContentIteratorBase::GetDeepFirstInclusiveDescendant( cChild); NS_WARNING_ASSERTION(result, "GetDeepFirstInclusiveDescendant returned null"); // Does mFirst node really intersect the range? The range could be // 'degenerate', i.e., not collapsed but still contain no content. if (result && !NodeIsInTraversalRange(result, mIterator.mOrder == Order::Pre, mStart, mEnd)) { return nullptr; } return result; } template Result ContentIteratorBase::Initializer::DetermineLastNode() const { const bool endIsCharacterData = mEnd.GetContainer()->IsCharacterData(); if (endIsCharacterData || !mEnd.GetContainer()->HasChildren() || mEnd.IsStartOfContainer()) { if (mIterator.mOrder == Order::Pre) { if (NS_WARN_IF(!mEnd.GetContainer()->IsContent())) { // Not much else to do here... return nullptr; } // If the end node is a non-container element and the end offset is 0, // the last element should be the previous node (i.e., shouldn't // include the end node in the range). bool endIsContainer = true; if (mEnd.GetContainer()->IsHTMLElement()) { nsAtom* name = mEnd.GetContainer()->NodeInfo()->NameAtom(); endIsContainer = nsHTMLElement::IsContainer(nsHTMLTags::AtomTagToId(name)); } if (!endIsCharacterData && !endIsContainer && mEnd.IsStartOfContainer()) { nsINode* const result = mIterator.PrevNode(mEnd.GetContainer()); NS_WARNING_ASSERTION(result, "PrevNode returned null"); if (result && result != mIterator.mFirst && NS_WARN_IF(!NodeIsInTraversalRange( result, mIterator.mOrder == Order::Pre, RawRangeBoundary::StartOfParent(*mIterator.mFirst), mEnd))) { return nullptr; } return result; } return mEnd.GetContainer()->AsContent(); } // post-order // // XXX: In the future, if end offset is before the first character in the // cdata node, should we set mLast to the prev sibling? if (!endIsCharacterData) { nsINode* const result = ContentIteratorBase::GetPrevSibling( mEnd.GetContainer()); NS_WARNING_ASSERTION(result, "GetPrevSibling returned null"); if (!NodeIsInTraversalRange(result, mIterator.mOrder == Order::Pre, mStart, mEnd)) { return nullptr; } return result; } return mEnd.GetContainer()->AsContent(); } nsIContent* cChild = mEnd.Ref(); if (NS_WARN_IF(!cChild)) { // No child at offset! MOZ_ASSERT_UNREACHABLE("ContentIterator::ContentIterator"); return Err(NS_ERROR_FAILURE); } if (mIterator.mOrder == Order::Pre) { nsINode* const result = ContentIteratorBase::GetDeepLastInclusiveDescendant( cChild); NS_WARNING_ASSERTION(result, "GetDeepLastInclusiveDescendant returned null"); if (NS_WARN_IF(!NodeIsInTraversalRange( result, mIterator.mOrder == Order::Pre, mStart, mEnd))) { return nullptr; } return result; } // post-order return cChild; } NS_INSTANTIATE_CONTENT_ITER_BASE_METHOD(void, SetEmpty); template void ContentIteratorBase::SetEmpty() { mCurNode = nullptr; mFirst = nullptr; mLast = nullptr; mClosestCommonInclusiveAncestor = nullptr; } // static template template nsINode* ContentIteratorBase::GetDeepFirstInclusiveDescendant( nsINode* aNode) { if (NS_WARN_IF(!aNode)) { return aNode; } nsIContent* const firstChild = aNode->GetFirstChild(); if (!firstChild) { return aNode; } return ContentIteratorBase::GetDeepFirstInclusiveDescendant( firstChild); } // static template template nsIContent* ContentIteratorBase::GetDeepFirstInclusiveDescendant( nsIContent* aContent) { if (NS_WARN_IF(!aContent)) { return nullptr; } nsIContent* lastContent = aContent; while (nsIContent* const firstChild = lastContent->GetFirstChild()) { lastContent = firstChild; } return lastContent; } // static template template nsINode* ContentIteratorBase::GetDeepLastInclusiveDescendant( nsINode* aNode) { if (NS_WARN_IF(!aNode)) { return aNode; } nsIContent* const lastChild = aNode->GetLastChild(); if (!lastChild) { return aNode; } return ContentIteratorBase::GetDeepLastInclusiveDescendant(lastChild); } // static template template nsIContent* ContentIteratorBase::GetDeepLastInclusiveDescendant( nsIContent* aContent) { if (NS_WARN_IF(!aContent)) { return nullptr; } nsIContent* lastContent = aContent; while (nsIContent* const lastChild = lastContent->GetLastChild()) { lastContent = lastChild; } return lastContent; } // Get the next sibling, or parent's next sibling, or shadow host's next // sibling (when aAllowCrossShadowBoundary is true), or grandpa's next // sibling... // // static // template template nsIContent* ContentIteratorBase::GetNextSibling( nsINode* aNode, nsTArray* aInclusiveAncestorsOfEndContainer) { if (NS_WARN_IF(!aNode)) { return nullptr; } if constexpr (ShouldHandleAssignedNodesOnSlot()) { if (aNode->IsContent()) { // Could have nested slots while (HTMLSlotElement* slot = aNode->AsContent()->GetAssignedSlot()) { // Next sibling of a slotted node should be the next slotted node auto assigned = slot->AssignedNodes(); auto cur = assigned.IndexOf(aNode); if (cur != assigned.npos && cur + 1 < assigned.Length()) { return assigned[cur + 1]->AsContent(); } // Move on to assigned slot's next sibling aNode = slot; } if (nsIContent* const next = ChildIteratorBase::GetNextChild(aNode->AsContent())) { return next; } } } else { if (nsIContent* const next = aNode->GetNextSibling()) { return next; } } nsINode* parent = ShadowDOMSelectionHelpers::GetParentNodeInSameSelection( *aNode, aKind == TreeKind::DOM ? AllowRangeCrossShadowBoundary::No : AllowRangeCrossShadowBoundary::Yes); if (NS_WARN_IF(!parent)) { return nullptr; } // We now have finished iterating descendants within this shadow root, and // reached to the shadow host. if constexpr (ShouldHandleAssignedNodesOnSlot()) { if (aInclusiveAncestorsOfEndContainer && parent->GetShadowRoot() == aNode) { const int32_t i = aInclusiveAncestorsOfEndContainer->IndexOf( parent, 0, InclusiveAncestorComparator()); // If parent is an ancestor of the end container, we return the parent so // that the caller (ContentSubtreeIterator::Next) can stop the iteration. // // This is only a special case for ShadowDOM selection where // the end container is in light DOM and we have to iterate // shadow DOM nodes first. We would have reached to mLast // already if this isn't the case. if (i != -1) { MOZ_ASSERT(!aInclusiveAncestorsOfEndContainer->ElementAt(i) .mIsDescendantInShadowTree); return parent->AsContent(); } } } return ContentIteratorBase::GetNextSibling( parent, aInclusiveAncestorsOfEndContainer); } // Get the prev sibling, or parent's prev sibling, or shadow host's prev sibling // (when aAllowCrossShadowBoundary is true), or grandpa's prev sibling... static template template nsIContent* ContentIteratorBase::GetPrevSibling(nsINode* aNode) { if (NS_WARN_IF(!aNode)) { return nullptr; } if constexpr (ShouldHandleAssignedNodesOnSlot()) { if (aNode->IsContent()) { // Could have nested slots. while (HTMLSlotElement* slot = aNode->AsContent()->GetAssignedSlot()) { // prev sibling of a slotted node should be the prev slotted node auto assigned = slot->AssignedNodes(); auto cur = assigned.IndexOf(aNode); if (cur != assigned.npos && cur != 0) { return assigned[cur - 1]->AsContent(); } aNode = slot; } if (nsIContent* const prev = ChildIteratorBase::GetPreviousChild(aNode->AsContent())) { return prev; } } } else { if (nsIContent* const prev = aNode->GetPreviousSibling()) { return prev; } } nsINode* parent = ShadowDOMSelectionHelpers::GetParentNodeInSameSelection( *aNode, aKind == TreeKind::DOM ? AllowRangeCrossShadowBoundary::No : AllowRangeCrossShadowBoundary::Yes); if (NS_WARN_IF(!parent)) { return nullptr; } return ContentIteratorBase::GetPrevSibling(parent); } template template nsINode* ContentIteratorBase::NextNode(nsINode* aNode) { nsINode* node = aNode; // if we are a Pre-order iterator, use pre-order if (mOrder == Order::Pre) { // if it has children then next node is first child if (nsIContent* const firstChild = node->GetFirstChild()) { return firstChild; } // else next sibling is next return ContentIteratorBase::GetNextSibling(node); } // post-order nsINode* parent = node->GetParentNode(); if (NS_WARN_IF(!parent)) { MOZ_ASSERT(parent, "The node is the root node but not the last node"); mCurNode = nullptr; return node; } if constexpr (aKind == TreeKind::DOM) { if (nsIContent* const sibling = node->GetNextSibling()) { // next node is sibling's "deep left" child return ContentIteratorBase::GetDeepFirstInclusiveDescendant( sibling); } } else if (node->IsContent()) { if (nsIContent* const sibling = ChildIteratorBase::GetNextChild(node->AsContent())) { return ContentIteratorBase::GetDeepFirstInclusiveDescendant( sibling); } } return parent; } template template nsINode* ContentIteratorBase::PrevNode(nsINode* aNode) { nsINode* node = aNode; // if we are a Pre-order iterator, use pre-order if (mOrder == Order::Pre) { nsINode* parent = node->GetParentNode(); if (NS_WARN_IF(!parent)) { MOZ_ASSERT(parent, "The node is the root node but not the first node"); mCurNode = nullptr; return aNode; } if constexpr (aKind == TreeKind::DOM) { if (nsIContent* const sibling = node->GetPreviousSibling()) { return ContentIteratorBase::GetDeepLastInclusiveDescendant( sibling); } } else if (node->IsContent()) { if (nsIContent* const sibling = ChildIteratorBase::GetPreviousChild(node->AsContent())) { return ContentIteratorBase::GetDeepLastInclusiveDescendant( sibling); } } return parent; } // post-order if (nsIContent* const lastChild = node->GetLastChild()) { return lastChild; } // else prev sibling is previous return ContentIteratorBase::GetPrevSibling(node); } /****************************************************** * ContentIteratorBase routines ******************************************************/ NS_INSTANTIATE_CONTENT_ITER_BASE_METHOD(void, First); template void ContentIteratorBase::First() { if (!mFirst) { MOZ_ASSERT(IsDone()); mCurNode = nullptr; return; } mozilla::DebugOnly rv = PositionAt(mFirst); NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to position iterator!"); } NS_INSTANTIATE_CONTENT_ITER_BASE_METHOD(void, Last); template void ContentIteratorBase::Last() { // Note that mLast can be nullptr if SetEmpty() is called in Init() // since at that time, Init() returns NS_OK. if (!mLast) { MOZ_ASSERT(IsDone()); mCurNode = nullptr; return; } mozilla::DebugOnly rv = PositionAt(mLast); NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to position iterator!"); } NS_INSTANTIATE_CONTENT_ITER_BASE_METHOD(void, Next); template void ContentIteratorBase::Next() { if (IsDone()) { return; } if (mCurNode == mLast) { mCurNode = nullptr; return; } mCurNode = NextNode(mCurNode); } NS_INSTANTIATE_CONTENT_ITER_BASE_METHOD(void, Prev); template void ContentIteratorBase::Prev() { if (IsDone()) { return; } if (mCurNode == mFirst) { mCurNode = nullptr; return; } mCurNode = PrevNode(mCurNode); } // Keeping arrays of indexes for the stack of nodes makes PositionAt // interesting... NS_INSTANTIATE_CONTENT_ITER_BASE_METHOD(nsresult, PositionAt, nsINode*); template nsresult ContentIteratorBase::PositionAt(nsINode* aCurNode) { if (NS_WARN_IF(!aCurNode)) { return NS_ERROR_NULL_POINTER; } // take an early out if this doesn't actually change the position if (mCurNode == aCurNode) { return NS_OK; } mCurNode = aCurNode; // Check to see if the node falls within the traversal range. RawRangeBoundary first(mFirst, 0u); RawRangeBoundary last(mLast, 0u); if (mFirst && mLast) { if (mOrder == Order::Pre) { // In pre we want to record the point immediately before mFirst, which is // the point immediately after mFirst's previous sibling. first = {mFirst->GetParentNode(), mFirst->GetPreviousSibling()}; // If mLast has no children, then we want to make sure to include it. if (!mLast->HasChildren()) { last = {mLast->GetParentNode(), mLast->AsContent()}; } } else { // If the first node has any children, we want to be immediately after the // last. Otherwise we want to be immediately before mFirst. if (mFirst->HasChildren()) { first = {mFirst, mFirst->GetLastChild()}; } else { first = {mFirst->GetParentNode(), mFirst->GetPreviousSibling()}; } // Set the last point immediately after the final node. last = {mLast->GetParentNode(), mLast->AsContent()}; } } NS_WARNING_ASSERTION(first.IsSetAndValid(), "first is not valid"); NS_WARNING_ASSERTION(last.IsSetAndValid(), "last is not valid"); // The end positions are always in the range even if it has no parent. We // need to allow that or 'iter->Init(root)' would assert in Last() or First() // for example, bug 327694. if (mFirst != mCurNode && mLast != mCurNode && (NS_WARN_IF(!first.IsSet()) || NS_WARN_IF(!last.IsSet()) || NS_WARN_IF(!NodeIsInTraversalRange(mCurNode, mOrder == Order::Pre, first, last)))) { mCurNode = nullptr; return NS_ERROR_FAILURE; } return NS_OK; } /****************************************************** * ContentSubtreeIterator init routines ******************************************************/ nsresult ContentSubtreeIterator::Init(nsINode* aRoot) { return NS_ERROR_NOT_IMPLEMENTED; } nsresult ContentSubtreeIterator::Init(AbstractRange* aRange) { MOZ_ASSERT(aRange); if (NS_WARN_IF(!aRange->IsPositioned())) { return NS_ERROR_INVALID_ARG; } mRange = aRange; return InitWithRange(); } nsresult ContentSubtreeIterator::Init(nsINode* aStartContainer, uint32_t aStartOffset, nsINode* aEndContainer, uint32_t aEndOffset) { return Init(RawRangeBoundary(aStartContainer, aStartOffset), RawRangeBoundary(aEndContainer, aEndOffset)); } nsresult ContentSubtreeIterator::Init(const RawRangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary) { RefPtr range = nsRange::Create(aStartBoundary, aEndBoundary, IgnoreErrors()); if (NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned())) { return NS_ERROR_INVALID_ARG; } if (NS_WARN_IF(range->MayCrossShadowBoundaryStartRef() != aStartBoundary) || NS_WARN_IF(range->MayCrossShadowBoundaryEndRef() != aEndBoundary)) { // Could happen if the above nsRange::Create decides to collapse // the range, like aStartBoundary is "after" aEndBoundary. return NS_ERROR_UNEXPECTED; } mRange = std::move(range); return InitWithRange(); } nsresult ContentSubtreeIterator::InitWithAllowCrossShadowBoundary( AbstractRange* aRange) { MOZ_ASSERT(aRange); if (NS_WARN_IF(!aRange->IsPositioned())) { return NS_ERROR_INVALID_ARG; } if (aRange->IsDynamicRange()) { // When we handle flattened tree, we should ignore unformatted nodes. // Therefore, we need to adjust the range into the formatted flattened tree. mRange = aRange->AsDynamicRange()->GetRangeInFlatTree(); } else { mRange = aRange; } mAllowCrossShadowBoundary = AllowRangeCrossShadowBoundary::Yes; return InitWithRange(); } void ContentSubtreeIterator::CacheInclusiveAncestorsOfEndContainer() { mInclusiveAncestorsOfEndContainer.Clear(); nsINode* const endContainer = ShadowDOMSelectionHelpers::GetEndContainer( mRange, mAllowCrossShadowBoundary); nsIContent* endNode = endContainer->IsContent() ? endContainer->AsContent() : nullptr; AncestorInfo info{endNode, false}; while (info.mAncestor) { const nsINode* child = info.mAncestor; mInclusiveAncestorsOfEndContainer.AppendElement(info); // Cross the boundary for contents in shadow tree. nsINode* parent = ShadowDOMSelectionHelpers::GetParentNodeInSameSelection( *child, mAllowCrossShadowBoundary); if (!parent || !parent->IsContent()) { break; } // `ShadowDOMSelectionHelpers::GetShadowRoot` would return non-null shadow // root if parent is a shadow host that we support cross boundary selection. const bool isChildAShadowRootForSelection = ShadowDOMSelectionHelpers::GetShadowRoot( parent, mAllowCrossShadowBoundary) == child; info.mAncestor = parent->AsContent(); // mIsDescendantInShadowTree indicates that whether child is in the // shadow tree of parent or in the regular light DOM tree of parent. // So that later, when info.mAncestor is reached, we can decide whether // we should dive into the shadow tree. info.mIsDescendantInShadowTree = IterAllowCrossShadowBoundary() && isChildAShadowRootForSelection; } } nsIContent* ContentSubtreeIterator::DetermineCandidateForFirstContent() const { nsINode* startContainer = ShadowDOMSelectionHelpers::GetStartContainer( mRange, mAllowCrossShadowBoundary); nsIContent* firstCandidate = nullptr; // find first node in range nsINode* node = nullptr; if (IterAllowCrossShadowBoundary() ? !startContainer->HasChildren() : !startContainer->HasChildren()) { // no children, start at the node itself node = startContainer; } else { nsIContent* child = IterAllowCrossShadowBoundary() ? mRange->GetMayCrossShadowBoundaryChildAtStartOffset() : mRange->GetChildAtStartOffset(); #ifdef DEBUG const auto& startRef = IterAllowCrossShadowBoundary() ? mRange->MayCrossShadowBoundaryStartRef() : mRange->StartRef(); MOZ_ASSERT(startRef.IsSetAndValid()); #endif if (!child) { // offset after last child node = startContainer; } else { firstCandidate = child; } } if (!firstCandidate) { // then firstCandidate is next node after node firstCandidate = IterAllowCrossShadowBoundary() ? ContentIteratorBase::GetNextSibling( node) : ContentIteratorBase::GetNextSibling(node); } if (firstCandidate) { firstCandidate = IterAllowCrossShadowBoundary() ? ContentIteratorBase::GetDeepFirstInclusiveDescendant< TreeKind::FlatForSelection>(firstCandidate) : ContentIteratorBase::GetDeepFirstInclusiveDescendant< TreeKind::DOM>(firstCandidate); } return firstCandidate; } nsIContent* ContentSubtreeIterator::DetermineFirstContent() const { nsIContent* firstCandidate = DetermineCandidateForFirstContent(); if (!firstCandidate) { return nullptr; } // confirm that this first possible contained node is indeed contained. Else // we have a range that does not fully contain any node. const Maybe isNodeContainedInRange = IterAllowCrossShadowBoundary() ? RangeUtils::IsNodeContainedInRange( *firstCandidate, mRange) : RangeUtils::IsNodeContainedInRange( *firstCandidate, mRange); MOZ_ALWAYS_TRUE(isNodeContainedInRange); if (!isNodeContainedInRange.value()) { return nullptr; } // cool, we have the first node in the range. Now we walk up its ancestors // to find the most senior that is still in the range. That's the real first // node. return GetTopAncestorInRange(firstCandidate); } nsIContent* ContentSubtreeIterator::DetermineCandidateForLastContent() const { nsIContent* lastCandidate{nullptr}; nsINode* endContainer = ShadowDOMSelectionHelpers::GetEndContainer( mRange, mAllowCrossShadowBoundary); // now to find the last node int32_t offset = ShadowDOMSelectionHelpers::EndOffset(mRange, mAllowCrossShadowBoundary); const int32_t numChildren = IterAllowCrossShadowBoundary() ? endContainer->GetFlatTreeForSelectionChildCount() : endContainer->GetChildCount(); nsINode* node = nullptr; if (offset > numChildren) { // Can happen for text nodes offset = numChildren; } if (!offset || !numChildren) { node = endContainer; } else { lastCandidate = IterAllowCrossShadowBoundary() ? mRange->MayCrossShadowBoundaryEndRef().Ref() : mRange->EndRef().Ref(); #ifdef DEBUG const auto& endRef = IterAllowCrossShadowBoundary() ? mRange->MayCrossShadowBoundaryEndRef() : mRange->EndRef(); MOZ_ASSERT(endRef.IsSetAndValid()); #endif NS_ASSERTION(lastCandidate, "tree traversal trouble in ContentSubtreeIterator::Init"); } if (!lastCandidate) { // then lastCandidate is prev node before node lastCandidate = IterAllowCrossShadowBoundary() ? ContentIteratorBase::GetPrevSibling( node) : ContentIteratorBase::GetPrevSibling(node); } if (lastCandidate) { lastCandidate = IterAllowCrossShadowBoundary() ? ContentIteratorBase::GetDeepLastInclusiveDescendant< TreeKind::FlatForSelection>(lastCandidate) : ContentIteratorBase::GetDeepLastInclusiveDescendant< TreeKind::DOM>(lastCandidate); } return lastCandidate; } nsresult ContentSubtreeIterator::InitWithRange() { MOZ_ASSERT(mRange); MOZ_ASSERT(mRange->IsPositioned()); // get the start node and offset, convert to nsINode mClosestCommonInclusiveAncestor = mRange->GetClosestCommonInclusiveAncestor(mAllowCrossShadowBoundary); const RawRangeBoundary startRef = ShadowDOMSelectionHelpers::StartRef(mRange, mAllowCrossShadowBoundary); const RawRangeBoundary endRef = ShadowDOMSelectionHelpers::EndRef(mRange, mAllowCrossShadowBoundary); if (!mClosestCommonInclusiveAncestor) [[unlikely]] { NS_WARNING(fmt::format("startRef:{}", startRef).c_str()); NS_WARNING(fmt::format("endRef: {}", endRef).c_str()); MOZ_ASSERT_UNREACHABLE("mRange boundaries must be connected"); return NS_ERROR_FAILURE; } MOZ_ASSERT(startRef.IsSet()); MOZ_ASSERT(endRef.IsSet()); // short circuit when start node == end node if (startRef.GetContainer() == endRef.GetContainer()) { nsIContent* const child = IterAllowCrossShadowBoundary() ? startRef.GetContainer()->GetFlattenedTreeFirstChildForSelection() : startRef.GetContainer()->GetFirstChild(); if (!child || startRef == endRef) { // Text node, empty container, or collapsed SetEmpty(); return NS_OK; } } CacheInclusiveAncestorsOfEndContainer(); mFirst = DetermineFirstContent(); if (!mFirst) { SetEmpty(); return NS_OK; } mLast = DetermineLastContent(); if (!mLast) { SetEmpty(); return NS_OK; } mCurNode = mFirst; return NS_OK; } nsIContent* ContentSubtreeIterator::DetermineLastContent() const { nsIContent* lastCandidate = DetermineCandidateForLastContent(); if (!lastCandidate) { return nullptr; } // confirm that this last possible contained node is indeed contained. Else // we have a range that does not fully contain any node. const Maybe isNodeContainedInRange = IterAllowCrossShadowBoundary() ? RangeUtils::IsNodeContainedInRange( *lastCandidate, mRange) : RangeUtils::IsNodeContainedInRange( *lastCandidate, mRange); MOZ_ALWAYS_TRUE(isNodeContainedInRange); if (!isNodeContainedInRange.value()) { return nullptr; } // cool, we have the last node in the range. Now we walk up its ancestors to // find the most senior that is still in the range. That's the real first // node. return GetTopAncestorInRange(lastCandidate); } /**************************************************************** * ContentSubtreeIterator overrides of ContentIterator routines ****************************************************************/ // we can't call PositionAt in a subtree iterator... void ContentSubtreeIterator::First() { mCurNode = mFirst; } // we can't call PositionAt in a subtree iterator... void ContentSubtreeIterator::Last() { mCurNode = mLast; } void ContentSubtreeIterator::Next() { if (IsDone()) { return; } if (mCurNode == mLast) { mCurNode = nullptr; return; } nsINode* nextNode = IterAllowCrossShadowBoundary() ? ContentIteratorBase::GetNextSibling( mCurNode, &mInclusiveAncestorsOfEndContainer) : ContentIteratorBase::GetNextSibling( mCurNode, &mInclusiveAncestorsOfEndContainer); NS_ASSERTION(nextNode, "No next sibling!?! This could mean deadlock!"); int32_t i = mInclusiveAncestorsOfEndContainer.IndexOf( nextNode, 0, InclusiveAncestorComparator()); while (i != -1) { // as long as we are finding ancestors of the endpoint of the range, // dive down into their children ShadowRoot* root = ShadowDOMSelectionHelpers::GetShadowRoot( nextNode, mAllowCrossShadowBoundary); if (mInclusiveAncestorsOfEndContainer[i].mIsDescendantInShadowTree) { MOZ_ASSERT(root); nextNode = IterAllowCrossShadowBoundary() ? root->GetFlattenedTreeFirstChildForSelection() : root->GetFirstChild(); } else if (HTMLSlotElement* const slot = nextNode->GetAsHTMLSlotElementIfFilledForSelection(); slot && IterAllowCrossShadowBoundary()) { // Ancestor is a slot, we start from the first assigned node within this // slot nextNode = slot->AssignedNodes()[0]; } else { if (root) { // nextNode is a shadow host but the descendant in the light DOM // of it. There's no need to iterate light DOM elements for a // shadow tree. Stop here. mCurNode = nullptr; return; } nextNode = IterAllowCrossShadowBoundary() ? nextNode->GetFlattenedTreeFirstChildForSelection() : nextNode->GetFirstChild(); } NS_ASSERTION(nextNode, "Iterator error, expected a child node!"); // should be impossible to get a null pointer. If we went all the way // down the child chain to the bottom without finding an interior node, // then the previous node should have been the last, which was // was tested at top of routine. i = mInclusiveAncestorsOfEndContainer.IndexOf( nextNode, 0, InclusiveAncestorComparator()); } mCurNode = nextNode; } void ContentSubtreeIterator::Prev() { // Prev should be optimized to use the mStartNodes, just as Next // uses mInclusiveAncestorsOfEndContainer. if (IsDone()) { return; } if (mCurNode == mFirst) { mCurNode = nullptr; return; } // If any of these function calls return null, so will all succeeding ones, // so mCurNode will wind up set to null. nsINode* prevNode = IterAllowCrossShadowBoundary() ? ContentIteratorBase::GetDeepFirstInclusiveDescendant< TreeKind::FlatForSelection>(mCurNode) : ContentIteratorBase::GetDeepFirstInclusiveDescendant( mCurNode); prevNode = IterAllowCrossShadowBoundary() ? PrevNode(prevNode) : PrevNode(prevNode); prevNode = IterAllowCrossShadowBoundary() ? ContentIteratorBase::GetDeepLastInclusiveDescendant< TreeKind::FlatForSelection>(prevNode) : ContentIteratorBase::GetDeepLastInclusiveDescendant( prevNode); mCurNode = GetTopAncestorInRange(prevNode); } nsresult ContentSubtreeIterator::PositionAt(nsINode* aCurNode) { NS_ERROR("Not implemented!"); return NS_ERROR_NOT_IMPLEMENTED; } /**************************************************************** * ContentSubtreeIterator helper routines ****************************************************************/ nsIContent* ContentSubtreeIterator::GetTopAncestorInRange( nsINode* aNode) const { if (!aNode || !ShadowDOMSelectionHelpers::GetParentNodeInSameSelection( *aNode, mAllowCrossShadowBoundary)) { return nullptr; } // aNode has a parent, so it must be content. nsIContent* content = aNode->AsContent(); // sanity check: aNode is itself in the range Maybe isNodeContainedInRange = IterAllowCrossShadowBoundary() ? RangeUtils::IsNodeContainedInRange( *aNode, mRange) : RangeUtils::IsNodeContainedInRange( *aNode, mRange); NS_ASSERTION(isNodeContainedInRange && isNodeContainedInRange.value(), "aNode isn't in mRange, or something else weird happened"); if (!isNodeContainedInRange || !isNodeContainedInRange.value()) { return nullptr; } nsIContent* lastContentInShadowTree = nullptr; while (content) { nsINode* parent = ShadowDOMSelectionHelpers::GetParentNodeInSameSelection( *content, mAllowCrossShadowBoundary); // content always has a parent. If its parent is the root, however -- // i.e., either it's not content, or it is content but its own parent is // null -- then we're finished, since we don't go up to the root. // // Caveat: If iteration crossing shadow boundary is allowed // and the root is a shadow root, we keep going up to the // shadow host and continue. // // We have to special-case this because CompareNodeToRange treats the root // node differently -- see bug 765205. if (!parent || !ShadowDOMSelectionHelpers::GetParentNodeInSameSelection( *parent, mAllowCrossShadowBoundary)) { return content; } isNodeContainedInRange = IterAllowCrossShadowBoundary() ? RangeUtils::IsNodeContainedInRange( *parent, mRange) : RangeUtils::IsNodeContainedInRange( *parent, mRange); MOZ_ALWAYS_TRUE(isNodeContainedInRange); if (!isNodeContainedInRange.value()) { if (IterAllowCrossShadowBoundary() && content->IsShadowRoot()) { MOZ_ASSERT(parent->GetShadowRoot() == content); // host element is not in range, the last content in tree // should be the ancestor. MOZ_ASSERT(lastContentInShadowTree); return lastContentInShadowTree; } return content; } // When we cross the boundary, we keep a reference to the // last content that is in tree, because if we later // find the shadow host element is not in the range, that means // the last content in the tree should be top ancestor in range. // // Using shadow root doesn't make sense here because it doesn't // represent a actual content. if (IterAllowCrossShadowBoundary() && parent->IsShadowRoot()) { lastContentInShadowTree = content; } content = parent->AsContent(); } MOZ_CRASH("This should only be possible if aNode was null"); } nsresult RangeSubtreeIterator::Init( AbstractRange* aRange, dom::AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) { mIterState = eDone; if (aRange->AreNormalRangeAndCrossShadowBoundaryRangeCollapsed()) { return NS_OK; } // Grab the start point of the range and QI it to // a CharacterData pointer. If it is CharacterData store // a pointer to the node. if (!aRange->IsPositioned()) { return NS_ERROR_FAILURE; } // XXX It's odd to me that why does here may use the may cross shadow boundary // range even if aAllowCrossShadowBoundary?? nsINode* node = aRange->GetMayCrossShadowBoundaryStartContainer(); if (NS_WARN_IF(!node)) { return NS_ERROR_FAILURE; } if (node->IsCharacterData() || (node->IsElement() && aRange->MayCrossShadowBoundaryStartRef().IsEndOfContainer())) { mStart = node; } // Grab the end point of the range and QI it to // a CharacterData pointer. If it is CharacterData store // a pointer to the node. node = aRange->GetMayCrossShadowBoundaryEndContainer(); if (NS_WARN_IF(!node)) { return NS_ERROR_FAILURE; } if (node->IsCharacterData() || (node->IsElement() && aRange->MayCrossShadowBoundaryEndRef().IsStartOfContainer())) { mEnd = node; } if (mStart && mStart == mEnd) { // The range starts and stops in the same CharacterData // node. Null out the end pointer so we only visit the // node once! mEnd = nullptr; } else { // Now create a Content Subtree Iterator to be used // for the subtrees between the end points! mSubtreeIter.emplace(); nsresult res = aAllowCrossShadowBoundary == dom::AllowRangeCrossShadowBoundary::Yes ? mSubtreeIter->InitWithAllowCrossShadowBoundary(aRange) : mSubtreeIter->Init(aRange); if (NS_FAILED(res)) return res; if (mSubtreeIter->IsDone()) { // The subtree iterator thinks there's nothing // to iterate over, so just free it up so we // don't accidentally call into it. mSubtreeIter.reset(); } } // Initialize the iterator by calling First(). // Note that we are ignoring the return value on purpose! First(); return NS_OK; } already_AddRefed RangeSubtreeIterator::GetCurrentNode() { nsCOMPtr node; if (mIterState == eUseStart && mStart) { node = mStart; } else if (mIterState == eUseEnd && mEnd) { node = mEnd; } else if (mIterState == eUseIterator && mSubtreeIter) { node = mSubtreeIter->GetCurrentNode(); } return node.forget(); } void RangeSubtreeIterator::First() { if (mStart) { mIterState = eUseStart; } else if (mSubtreeIter) { mSubtreeIter->First(); mIterState = eUseIterator; } else if (mEnd) { mIterState = eUseEnd; } else { mIterState = eDone; } } void RangeSubtreeIterator::Last() { if (mEnd) { mIterState = eUseEnd; } else if (mSubtreeIter) { mSubtreeIter->Last(); mIterState = eUseIterator; } else if (mStart) { mIterState = eUseStart; } else { mIterState = eDone; } } void RangeSubtreeIterator::Next() { if (mIterState == eUseStart) { if (mSubtreeIter) { mSubtreeIter->First(); mIterState = eUseIterator; } else if (mEnd) { mIterState = eUseEnd; } else { mIterState = eDone; } } else if (mIterState == eUseIterator) { mSubtreeIter->Next(); if (mSubtreeIter->IsDone()) { if (mEnd) { mIterState = eUseEnd; } else { mIterState = eDone; } } } else { mIterState = eDone; } } void RangeSubtreeIterator::Prev() { if (mIterState == eUseEnd) { if (mSubtreeIter) { mSubtreeIter->Last(); mIterState = eUseIterator; } else if (mStart) { mIterState = eUseStart; } else { mIterState = eDone; } } else if (mIterState == eUseIterator) { mSubtreeIter->Prev(); if (mSubtreeIter->IsDone()) { if (mStart) { mIterState = eUseStart; } else { mIterState = eDone; } } } else { mIterState = eDone; } } #undef NS_INSTANTIATE_CONTENT_ITER_BASE_METHOD } // namespace mozilla