From a8fbcd54a1976d409ec1ef2aaaba8980a7ad982f Mon Sep 17 00:00:00 2001 From: Sean Feng Date: Fri, 9 May 2025 17:16:36 +0000 Subject: [PATCH] Bug 1932150 - Allow using flat tree order for point comparing in selection r=jjaschke,smaug,dom-core Differential Revision: https://phabricator.services.mozilla.com/D231588 --- dom/base/RangeUtils.cpp | 112 +++++++++++---- dom/base/RangeUtils.h | 14 +- dom/base/Selection.cpp | 47 +++++-- dom/base/nsContentUtils.cpp | 212 ++++++++++++++++++++++------- dom/base/nsContentUtils.h | 25 +++- dom/base/nsIContentInlines.h | 5 +- dom/base/nsINode.cpp | 32 +++-- dom/base/nsINode.h | 2 +- dom/base/nsRange.cpp | 31 +++-- editor/libeditor/HTMLEditUtils.cpp | 7 +- layout/base/PresShell.cpp | 3 +- 11 files changed, 363 insertions(+), 127 deletions(-) diff --git a/dom/base/RangeUtils.cpp b/dom/base/RangeUtils.cpp index 34e7545cbe4e..898f1e5d90e3 100644 --- a/dom/base/RangeUtils.cpp +++ b/dom/base/RangeUtils.cpp @@ -26,25 +26,58 @@ template bool RangeUtils::IsValidPoints(const RawRangeBoundary& aStartBoundary, template bool RangeUtils::IsValidPoints(const RawRangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary); -template nsresult RangeUtils::CompareNodeToRangeBoundaries( +template nsresult +RangeUtils::CompareNodeToRangeBoundaries( + nsINode* aNode, const RangeBoundary& aStartBoundary, + const RangeBoundary& aEndBoundary, bool* aNodeIsBeforeRange, + bool* aNodeIsAfterRange); +template nsresult RangeUtils::CompareNodeToRangeBoundaries( nsINode* aNode, const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary, bool* aNodeIsBeforeRange, bool* aNodeIsAfterRange); -template nsresult RangeUtils::CompareNodeToRangeBoundaries( +template nsresult +RangeUtils::CompareNodeToRangeBoundaries( + nsINode* aNode, const RangeBoundary& aStartBoundary, + const RawRangeBoundary& aEndBoundary, bool* aNodeIsBeforeRange, + bool* aNodeIsAfterRange); +template nsresult RangeUtils::CompareNodeToRangeBoundaries( nsINode* aNode, const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary, bool* aNodeIsBeforeRange, bool* aNodeIsAfterRange); -template nsresult RangeUtils::CompareNodeToRangeBoundaries( +template nsresult +RangeUtils::CompareNodeToRangeBoundaries( + nsINode* aNode, const RawRangeBoundary& aStartBoundary, + const RangeBoundary& aEndBoundary, bool* aNodeIsBeforeRange, + bool* aNodeIsAfterRange); +template nsresult RangeUtils::CompareNodeToRangeBoundaries( nsINode* aNode, const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary, bool* aNodeIsBeforeRange, bool* aNodeIsAfterRange); -template nsresult RangeUtils::CompareNodeToRangeBoundaries( +template nsresult +RangeUtils::CompareNodeToRangeBoundaries( nsINode* aNode, const RawRangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary, bool* aNodeIsBeforeRange, bool* aNodeIsAfterRange); +template nsresult RangeUtils::CompareNodeToRangeBoundaries( + nsINode* aNode, const RawRangeBoundary& aStartBoundary, + const RawRangeBoundary& aEndBoundary, bool* aNodeIsBeforeRange, + bool* aNodeIsAfterRange); + +template nsresult RangeUtils::CompareNodeToRange( + nsINode* aNode, AbstractRange* aAbstractRange, bool* aNodeIsBeforeRange, + bool* aNodeIsAfterRange); +template nsresult RangeUtils::CompareNodeToRange( + nsINode* aNode, AbstractRange* aAbstractRange, bool* aNodeIsBeforeRange, + bool* aNodeIsAfterRange); + +template Maybe +RangeUtils::IsNodeContainedInRange( + nsINode& aNode, AbstractRange* aAbstractRange); +template Maybe RangeUtils::IsNodeContainedInRange( + nsINode& aNode, AbstractRange* aAbstractRange); [[nodiscard]] static inline bool ParentNodeIsInSameSelection( const nsINode& aNode) { @@ -67,17 +100,6 @@ template nsresult RangeUtils::CompareNodeToRangeBoundaries( return true; } -// static -nsINode* RangeUtils::GetParentNodeInSameSelection(const nsINode* aNode) { - if (MOZ_UNLIKELY(!aNode)) { - return nullptr; - } - if (!ParentNodeIsInSameSelection(*aNode)) { - return nullptr; - } - return aNode->GetParentNode(); -} - // static nsINode* RangeUtils::ComputeRootNode(nsINode* aNode) { if (!aNode) { @@ -150,13 +172,14 @@ bool RangeUtils::IsValidPoints( } // static +template Maybe RangeUtils::IsNodeContainedInRange(nsINode& aNode, AbstractRange* aAbstractRange) { bool nodeIsBeforeRange{false}; bool nodeIsAfterRange{false}; - const nsresult rv = CompareNodeToRange(&aNode, aAbstractRange, - &nodeIsBeforeRange, &nodeIsAfterRange); + const nsresult rv = CompareNodeToRange( + &aNode, aAbstractRange, &nodeIsBeforeRange, &nodeIsAfterRange); if (NS_FAILED(rv)) { return Nothing(); } @@ -172,6 +195,7 @@ Maybe RangeUtils::IsNodeContainedInRange(nsINode& aNode, // XXX - callers responsibility to ensure node in same doc as range! // static +template nsresult RangeUtils::CompareNodeToRange(nsINode* aNode, AbstractRange* aAbstractRange, bool* aNodeIsBeforeRange, @@ -180,12 +204,13 @@ nsresult RangeUtils::CompareNodeToRange(nsINode* aNode, NS_WARN_IF(!aAbstractRange->IsPositioned())) { return NS_ERROR_INVALID_ARG; } - return CompareNodeToRangeBoundaries( + return CompareNodeToRangeBoundaries( aNode, aAbstractRange->MayCrossShadowBoundaryStartRef(), aAbstractRange->MayCrossShadowBoundaryEndRef(), aNodeIsBeforeRange, aNodeIsAfterRange); } -template +template nsresult RangeUtils::CompareNodeToRangeBoundaries( nsINode* aNode, const RangeBoundaryBase& aStartBoundary, const RangeBoundaryBase& aEndBoundary, bool* aNodeIsBeforeRange, @@ -208,7 +233,17 @@ nsresult RangeUtils::CompareNodeToRangeBoundaries( // gather up the dom point info int32_t nodeStart; uint32_t nodeEnd; - const nsINode* parent = GetParentNodeInSameSelection(aNode); + const nsINode* parent = nullptr; + + MOZ_ASSERT_IF(aKind == TreeKind::Flat, + StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()); + // ShadowRoot has no parent, nor can be represented by parent/offset pair. + if (!aNode->IsShadowRoot()) { + parent = ShadowDOMSelectionHelpers::GetParentNodeInSameSelection( + *aNode, aKind == TreeKind::Flat ? AllowRangeCrossShadowBoundary::Yes + : AllowRangeCrossShadowBoundary::No); + } + if (!parent) { // can't make a parent/offset pair to represent start or // end of the root node, because it has no parent. @@ -216,6 +251,14 @@ nsresult RangeUtils::CompareNodeToRangeBoundaries( parent = aNode; nodeStart = 0; nodeEnd = aNode->GetChildCount(); + } else if (const HTMLSlotElement* slotAsParent = + HTMLSlotElement::FromNode(parent); + slotAsParent && aKind == TreeKind::Flat) { + // aNode is a slotted content, use the index in the assigned nodes + // to represent this node. + auto index = slotAsParent->AssignedNodes().IndexOf(aNode); + nodeStart = index; + nodeEnd = nodeStart + 1; } else { nodeStart = parent->ComputeIndexOf_Deprecated(aNode); NS_WARNING_ASSERTION( @@ -240,17 +283,19 @@ nsresult RangeUtils::CompareNodeToRangeBoundaries( // silence the warning. (Bug 1438996) // is RANGE(start) <= NODE(start) ? - Maybe order = nsContentUtils::ComparePoints_AllowNegativeOffsets( - aStartBoundary.GetContainer(), - *aStartBoundary.Offset( - RangeBoundaryBase::OffsetFilter::kValidOrInvalidOffsets), - parent, nodeStart); + Maybe order = + nsContentUtils::ComparePoints_AllowNegativeOffsets( + aStartBoundary.GetContainer(), + *aStartBoundary.Offset( + RangeBoundaryBase::OffsetFilter::kValidOrInvalidOffsets), + parent, nodeStart); if (NS_WARN_IF(!order)) { return NS_ERROR_DOM_WRONG_DOCUMENT_ERR; } *aNodeIsBeforeRange = *order > 0; // is RANGE(end) >= NODE(end) ? - order = nsContentUtils::ComparePointsWithIndices( + order = nsContentUtils::ComparePointsWithIndices( aEndBoundary.GetContainer(), *aEndBoundary.Offset( RangeBoundaryBase::OffsetFilter::kValidOrInvalidOffsets), @@ -315,10 +360,17 @@ nsINode* ShadowDOMSelectionHelpers::GetParentNodeInSameSelection( if (!ParentNodeIsInSameSelection(aNode)) { return nullptr; } - return (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() && - aAllowCrossShadowBoundary) - ? aNode.GetParentOrShadowHostNode() - : aNode.GetParentNode(); + + if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() && + aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes) { + if (aNode.IsContent()) { + if (HTMLSlotElement* slot = aNode.AsContent()->GetAssignedSlot()) { + return slot; + } + } + return aNode.GetParentOrShadowHostNode(); + } + return aNode.GetParentNode(); } // static diff --git a/dom/base/RangeUtils.h b/dom/base/RangeUtils.h index a7f8c6388af1..6111f2830f85 100644 --- a/dom/base/RangeUtils.h +++ b/dom/base/RangeUtils.h @@ -11,6 +11,7 @@ #include "mozilla/RangeBoundary.h" #include "nsIContent.h" #include "nsINode.h" +#include "nsContentUtils.h" namespace mozilla { @@ -55,8 +56,6 @@ class RangeUtils final { using AbstractRange = dom::AbstractRange; public: - static nsINode* GetParentNodeInSameSelection(const nsINode* aNode); - /** * GetRawRangeBoundaryBefore() and GetRawRangeBoundaryAfter() retrieve * RawRangeBoundary which points before or after aNode. @@ -137,6 +136,9 @@ class RangeUtils final { /** * The caller needs to ensure aNode is in the same doc like aAbstractRange. */ + template > static Maybe IsNodeContainedInRange(nsINode& aNode, AbstractRange* aAbstractRange); @@ -145,12 +147,18 @@ class RangeUtils final { * ends after a range. If neither it is contained inside the range. * Note that callers responsibility to ensure node in same doc as range. */ + template > static nsresult CompareNodeToRange(nsINode* aNode, AbstractRange* aAbstractRange, bool* aNodeIsBeforeRange, bool* aNodeIsAfterRange); - template + template > static nsresult CompareNodeToRangeBoundaries( nsINode* aNode, const RangeBoundaryBase& aStartBoundary, const RangeBoundaryBase& aEndBoundary, bool* aNodeIsBeforeRange, diff --git a/dom/base/Selection.cpp b/dom/base/Selection.cpp index 379d97c926b7..2f5c94164835 100644 --- a/dom/base/Selection.cpp +++ b/dom/base/Selection.cpp @@ -928,9 +928,15 @@ static int32_t CompareToRangeStart( return 1; } - // The points are in the same subtree, hence there has to be an order. - return *nsContentUtils::ComparePoints( - aCompareBoundary, aRange.MayCrossShadowBoundaryStartRef(), aCache); + nsINode* start = aRange.GetMayCrossShadowBoundaryStartContainer(); + uint32_t startOffset = aRange.MayCrossShadowBoundaryStartOffset(); + if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) { + return *nsContentUtils::ComparePoints( + aCompareBoundary, ConstRawRangeBoundary{start, startOffset}, aCache); + } + + return *nsContentUtils::ComparePoints( + aCompareBoundary, ConstRawRangeBoundary{start, startOffset}, aCache); } template @@ -956,9 +962,14 @@ static int32_t CompareToRangeEnd( return 1; } - // The points are in the same subtree, hence there has to be an order. - return *nsContentUtils::ComparePoints(aCompareBoundary, - aRange.MayCrossShadowBoundaryEndRef()); + nsINode* end = aRange.GetMayCrossShadowBoundaryEndContainer(); + uint32_t endOffset = aRange.MayCrossShadowBoundaryEndOffset(); + if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) { + return *nsContentUtils::ComparePoints( + aCompareBoundary, ConstRawRangeBoundary{end, endOffset}); + } + return *nsContentUtils::ComparePoints( + aCompareBoundary, ConstRawRangeBoundary{end, endOffset}); } // static @@ -3123,17 +3134,24 @@ void Selection::Extend(nsINode& aContainer, uint32_t aOffset, const uint32_t endOffset = range->MayCrossShadowBoundaryEndOffset(); bool shouldClearRange = false; + + auto ComparePoints = [](const nsINode* aNode1, const uint32_t aOffset1, + const nsINode* aNode2, const uint32_t aOffset2) { + if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) { + return nsContentUtils::ComparePointsWithIndices( + aNode1, aOffset1, aNode2, aOffset2); + } + return nsContentUtils::ComparePointsWithIndices< + TreeKind::ShadowIncludingDOM>(aNode1, aOffset1, aNode2, aOffset2); + }; const Maybe anchorOldFocusOrder = - nsContentUtils::ComparePointsWithIndices(anchorNode, anchorOffset, - focusNode, focusOffset); + ComparePoints(anchorNode, anchorOffset, focusNode, focusOffset); shouldClearRange |= !anchorOldFocusOrder; const Maybe oldFocusNewFocusOrder = - nsContentUtils::ComparePointsWithIndices(focusNode, focusOffset, - &aContainer, aOffset); + ComparePoints(focusNode, focusOffset, &aContainer, aOffset); shouldClearRange |= !oldFocusNewFocusOrder; const Maybe anchorNewFocusOrder = - nsContentUtils::ComparePointsWithIndices(anchorNode, anchorOffset, - &aContainer, aOffset); + ComparePoints(anchorNode, anchorOffset, &aContainer, aOffset); shouldClearRange |= !anchorNewFocusOrder; // If the points are disconnected, the range will be collapsed below, @@ -4231,7 +4249,10 @@ void Selection::SetBaseAndExtentInternal(InLimiter aInLimiter, // new nsRange instance? SelectionBatcher batch(this, __FUNCTION__); const Maybe order = - nsContentUtils::ComparePoints(aAnchorRef, aFocusRef); + StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() + ? nsContentUtils::ComparePoints(aAnchorRef, aFocusRef) + : nsContentUtils::ComparePoints( + aAnchorRef, aFocusRef); if (order && (*order <= 0)) { SetStartAndEndInternal(aInLimiter, aAnchorRef, aFocusRef, eDirNext, aRv); return; diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index bbb928982f54..99516207e891 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -487,31 +487,61 @@ mozilla::LazyLogModule nsContentUtils::sDOMDumpLog("Dump"); int32_t nsContentUtils::sInnerOrOuterWindowCount = 0; uint32_t nsContentUtils::sInnerOrOuterWindowSerialCounter = 0; -template Maybe nsContentUtils::ComparePoints( +template Maybe +nsContentUtils::ComparePoints( const RangeBoundary& aFirstBoundary, const RangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache); -template Maybe nsContentUtils::ComparePoints( +template Maybe nsContentUtils::ComparePoints( + const RangeBoundary& aFirstBoundary, const RangeBoundary& aSecondBoundary, + NodeIndexCache* aIndexCache); + +template Maybe +nsContentUtils::ComparePoints( const RangeBoundary& aFirstBoundary, const RawRangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache); -template Maybe nsContentUtils::ComparePoints( +template Maybe nsContentUtils::ComparePoints( + const RangeBoundary& aFirstBoundary, + const RawRangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache); + +template Maybe +nsContentUtils::ComparePoints( const RawRangeBoundary& aFirstBoundary, const RangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache); -template Maybe nsContentUtils::ComparePoints( +template Maybe nsContentUtils::ComparePoints( + const RawRangeBoundary& aFirstBoundary, + const RangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache); + +template Maybe +nsContentUtils::ComparePoints( const RawRangeBoundary& aFirstBoundary, const RawRangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache); -template Maybe nsContentUtils::ComparePoints( +template Maybe nsContentUtils::ComparePoints( + const RawRangeBoundary& aFirstBoundary, + const RawRangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache); + +template Maybe +nsContentUtils::ComparePoints( const RangeBoundary& aFirstBoundary, const ConstRawRangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache); -template Maybe nsContentUtils::ComparePoints( +template Maybe nsContentUtils::ComparePoints( + const RangeBoundary& aFirstBoundary, + const ConstRawRangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache); + +template Maybe +nsContentUtils::ComparePoints( const ConstRawRangeBoundary& aFirstBoundary, const RangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache); -template Maybe nsContentUtils::ComparePoints( - const RawRangeBoundary& aFirstBoundary, - const ConstRawRangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache); -template Maybe nsContentUtils::ComparePoints( + +template Maybe +nsContentUtils::ComparePoints( const ConstRawRangeBoundary& aFirstBoundary, const RawRangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache); -template Maybe nsContentUtils::ComparePoints( + +template Maybe +nsContentUtils::ComparePoints( + const ConstRawRangeBoundary& aFirstBoundary, + const ConstRawRangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache); +template Maybe nsContentUtils::ComparePoints( const ConstRawRangeBoundary& aFirstBoundary, const ConstRawRangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache); @@ -757,9 +787,10 @@ static auto* GetFlattenedTreeParent(const nsIContent* aContent) { return aContent->GetFlattenedTreeParent(); } -static auto* GetFlattenedTreeParentNodeForSelection( - const nsIContent* aContent) { - return aContent->GetFlattenedTreeParentNodeForSelection(); +static nsIContent* GetFlattenedTreeParentNodeForSelection( + const nsIContent* aNode) { + nsINode* parent = aNode->GetFlattenedTreeParentNodeForSelection(); + return parent && parent->IsContent() ? parent->AsContent() : nullptr; } static auto* GetFlattenedTreeParentElementForStyle(const Element* aElement) { @@ -772,6 +803,17 @@ static auto* GetParentBrowserParent(const BrowserParent* aBrowserParent) { : nullptr; } +static bool AreNodesInSameSlot(const nsINode* aNode1, const nsINode* aNode2) { + if (auto* content1 = nsIContent::FromNodeOrNull(aNode1)) { + if (auto* slot = content1->GetAssignedSlot()) { + if (auto* content2 = nsIContent::FromNodeOrNull(aNode2)) { + return slot == content2->GetAssignedSlot(); + } + } + } + return false; +} + template class MOZ_STACK_CLASS CommonAncestors final { public: @@ -826,9 +868,12 @@ class MOZ_STACK_CLASS CommonAncestors final { return GetClosestCommonAncestorChild(mInclusiveAncestors2); } + template void WarnIfClosestCommonAncestorChildrenAreNotInChildList() const { - WarnIfClosestCommonAncestorChildIsNotInChildList(mInclusiveAncestors1); - WarnIfClosestCommonAncestorChildIsNotInChildList(mInclusiveAncestors2); + WarnIfClosestCommonAncestorChildIsNotInChildList( + mInclusiveAncestors1); + WarnIfClosestCommonAncestorChildIsNotInChildList( + mInclusiveAncestors2); } private: @@ -870,7 +915,9 @@ class MOZ_STACK_CLASS CommonAncestors final { return child; } - template + template > void WarnIfClosestCommonAncestorChildIsNotInChildList( const nsTArray& aInclusiveAncestors) const { #ifdef DEBUG @@ -879,8 +926,24 @@ class MOZ_STACK_CLASS CommonAncestors final { if (!child) { return; } - const Maybe childIndex = - mClosestCommonAncestor->ComputeIndexOf(child); + + if (mClosestCommonAncestor->GetShadowRoot() == child) { + return; + } + + Maybe childIndex; + if constexpr (aKind == TreeKind::Flat) { + if (auto* slot = HTMLSlotElement::FromNode(mClosestCommonAncestor)) { + auto index = slot->AssignedNodes().IndexOf(child); + if (index != nsTArray>::NoIndex) { + childIndex = Some(index); + } + } + } + + if (childIndex.isNothing()) { + childIndex = mClosestCommonAncestor->ComputeIndexOf(child); + } if (MOZ_LIKELY(childIndex.isSome())) { return; } @@ -3164,6 +3227,7 @@ Element* nsContentUtils::GetCommonFlattenedTreeAncestorForStyle( } /* static */ +template Maybe nsContentUtils::CompareChildNodes( const nsINode* aChild1, const nsINode* aChild2, NodeIndexCache* aIndexCache /* = nullptr */) { @@ -3186,6 +3250,23 @@ Maybe nsContentUtils::CompareChildNodes( MOZ_ASSERT(aChild1->GetParentOrShadowHostNode()); return Some(-1); } + + if constexpr (aKind == TreeKind::Flat) { + if (AreNodesInSameSlot(aChild1, aChild2)) { + // They differ at slot, so use their position in slot + const auto* slot = aChild1->AsContent()->GetAssignedSlot(); + MOZ_ASSERT(slot); + + auto child1Index = slot->AssignedNodes().IndexOf(aChild1); + auto child2Index = slot->AssignedNodes().IndexOf(aChild2); + + MOZ_ASSERT(child1Index != nsTArray>::NoIndex); + MOZ_ASSERT(child2Index != nsTArray>::NoIndex); + + return Some(child1Index < child2Index ? -1 : 1); + } + } + MOZ_ASSERT(aChild1->GetParentOrShadowHostNode()); const nsINode& commonParentNode = *aChild1->GetParentOrShadowHostNode(); MOZ_ASSERT(aChild2->GetParentOrShadowHostNode() == &commonParentNode); @@ -3254,9 +3335,10 @@ Maybe nsContentUtils::CompareChildNodes( } /* static */ +template Maybe nsContentUtils::CompareClosestCommonAncestorChildren( const nsINode& aParent, const nsINode* aChild1, const nsINode* aChild2, - nsContentUtils::NodeIndexCache* aIndexCache = nullptr) { + nsContentUtils::NodeIndexCache* aIndexCache) { MOZ_ASSERT_IF(aChild1, GetParentOrShadowHostNode(aChild1)); MOZ_ASSERT_IF(aChild2, GetParentOrShadowHostNode(aChild2)); @@ -3281,7 +3363,7 @@ Maybe nsContentUtils::CompareClosestCommonAncestorChildren( return Some(1); } const Maybe comp = - nsContentUtils::CompareChildNodes(aChild1, aChild2, aIndexCache); + nsContentUtils::CompareChildNodes(aChild1, aChild2, aIndexCache); if (MOZ_UNLIKELY(comp.isNothing())) { NS_ASSERTION(comp.isSome(), "nsContentUtils::CompareChildNodes() must return Some here. " @@ -3291,14 +3373,16 @@ Maybe nsContentUtils::CompareClosestCommonAncestorChildren( return Some(1); } MOZ_ASSERT_IF(!*comp, aChild1 == aChild2); - MOZ_ASSERT_IF(*comp < 0, (aChild1 ? *aChild1->ComputeIndexInParentNode() - : aParent.GetChildCount()) < - (aChild2 ? *aChild2->ComputeIndexInParentNode() - : aParent.GetChildCount())); - MOZ_ASSERT_IF(*comp > 0, (aChild2 ? *aChild2->ComputeIndexInParentNode() - : aParent.GetChildCount()) < - (aChild1 ? *aChild1->ComputeIndexInParentNode() - : aParent.GetChildCount())); + MOZ_ASSERT_IF(*comp < 0 && !AreNodesInSameSlot(aChild1, aChild2), + (aChild1 ? *aChild1->ComputeIndexInParentNode() + : aParent.GetChildCount()) < + (aChild2 ? *aChild2->ComputeIndexInParentNode() + : aParent.GetChildCount())); + MOZ_ASSERT_IF(*comp > 0 && !AreNodesInSameSlot(aChild1, aChild2), + (aChild2 ? *aChild2->ComputeIndexInParentNode() + : aParent.GetChildCount()) < + (aChild1 ? *aChild1->ComputeIndexInParentNode() + : aParent.GetChildCount())); return comp; } @@ -3350,6 +3434,7 @@ Maybe nsContentUtils::CompareChildNodeAndChildOffset( } /* static */ +template Maybe nsContentUtils::ComparePointsWithIndices( const nsINode* aParent1, uint32_t aOffset1, const nsINode* aParent2, uint32_t aOffset2, NodeIndexCache* aIndexCache) { @@ -3360,8 +3445,17 @@ Maybe nsContentUtils::ComparePointsWithIndices( return Some(aOffset1 < aOffset2 ? -1 : (aOffset1 > aOffset2 ? 1 : 0)); } - const CommonAncestors commonAncestors(*aParent1, *aParent2, - GetParentOrShadowHostNode); + auto GetParentFunc = [](const nsINode* aNode) -> nsINode* { + MOZ_ASSERT(aNode); + if constexpr (aKind == TreeKind::Flat) { + if (aNode->IsContent() && aNode->AsContent()->GetAssignedSlot()) { + return aNode->GetFlattenedTreeParentNodeForSelection(); + } + } + return aNode->GetParentOrShadowHostNode(); + }; + + const CommonAncestors commonAncestors(*aParent1, *aParent2, GetParentFunc); if (MOZ_UNLIKELY(!commonAncestors.GetClosestCommonAncestor())) { return Nothing(); @@ -3372,16 +3466,22 @@ Maybe nsContentUtils::ComparePointsWithIndices( const nsINode* closestCommonAncestorChild2 = commonAncestors.GetClosestCommonAncestorChild2(); MOZ_ASSERT(closestCommonAncestorChild1 != closestCommonAncestorChild2); - commonAncestors.WarnIfClosestCommonAncestorChildrenAreNotInChildList(); + commonAncestors + .template WarnIfClosestCommonAncestorChildrenAreNotInChildList(); if (closestCommonAncestorChild1 && closestCommonAncestorChild2) { - return CompareClosestCommonAncestorChildren( + return CompareClosestCommonAncestorChildren( *commonAncestors.GetClosestCommonAncestor(), closestCommonAncestorChild1, closestCommonAncestorChild2, aIndexCache); } if (closestCommonAncestorChild2) { - MOZ_ASSERT(closestCommonAncestorChild2->GetParentOrShadowHostNode() == - aParent1); + MOZ_ASSERT(GetParentFunc(closestCommonAncestorChild2) == aParent1); + if (aParent1->GetShadowRoot() == closestCommonAncestorChild2) { + // Comparing a shadow host with its shadow root. + // We consider: [host, 0] < anything in shadow root < [host, 1] + return aOffset1 > 0 ? Some(-1) : Some(1); + } + // FIXME: bug 1946001, bug 1946003 and bug 1946008. if (MOZ_UNLIKELY( closestCommonAncestorChild2->IsRootOfNativeAnonymousSubtree() || @@ -3408,9 +3508,14 @@ Maybe nsContentUtils::ComparePointsWithIndices( return comp; } + if (aParent2->GetShadowRoot() == closestCommonAncestorChild1) { + // Comparing a shadow host with its shadow root. + // We consider: [host, 0] < anything in shadow root < [host, 1] + return aOffset2 > 0 ? Some(-1) : Some(1); + } + MOZ_ASSERT(closestCommonAncestorChild1); - MOZ_ASSERT(closestCommonAncestorChild1->GetParentOrShadowHostNode() == - aParent2); + MOZ_ASSERT(GetParentFunc(closestCommonAncestorChild1) == aParent2); // FIXME: bug 1946001, bug 1946003 and bug 1946008. if (MOZ_UNLIKELY( closestCommonAncestorChild1->IsRootOfNativeAnonymousSubtree() || @@ -3500,7 +3605,8 @@ Element* nsContentUtils::GetTargetElement(Document* aDocument, } /* static */ -template +template Maybe nsContentUtils::ComparePoints( const RangeBoundaryBase& aBoundary1, const RangeBoundaryBase& aBoundary2, @@ -3517,7 +3623,7 @@ Maybe nsContentUtils::ComparePoints( // offset in the container. If both instances have computed offset, we can // use ComparePointsWithIndices() which works with offsets. if (aBoundary1.HasOffset() && aBoundary2.HasOffset()) { - return ComparePointsWithIndices( + return ComparePointsWithIndices( aBoundary1.GetContainer(), *aBoundary1.Offset(kValidOrInvalidOffsets1), aBoundary2.GetContainer(), *aBoundary2.Offset(kValidOrInvalidOffsets2), aIndexCache); @@ -3535,18 +3641,27 @@ Maybe nsContentUtils::ComparePoints( if (aBoundary1.GetContainer() == aBoundary2.GetContainer()) { const nsIContent* const child1 = aBoundary1.GetChildAtOffset(); const nsIContent* const child2 = aBoundary2.GetChildAtOffset(); - return CompareClosestCommonAncestorChildren(*aBoundary1.GetContainer(), - child1, child2, aIndexCache); + return CompareClosestCommonAncestorChildren( + *aBoundary1.GetContainer(), child1, child2, aIndexCache); } + auto GetParentFunc = [](const nsINode* aNode) -> nsINode* { + MOZ_ASSERT(aNode); + if constexpr (aKind == TreeKind::Flat) { + if (aNode->IsContent() && aNode->AsContent()->GetAssignedSlot()) { + return aNode->GetFlattenedTreeParentNodeForSelection(); + } + } + return aNode->GetParentOrShadowHostNode(); + }; + // Otherwise, we need to compare the common ancestor children which is the // most distant different inclusive ancestors of the containers. So, the // following implementation is similar to ComparePointsWithIndices(), but we // don't have offset, so, we cannot use offset when we compare the boundaries // whose one is a descendant of the other. - const CommonAncestors commonAncestors(*aBoundary1.GetContainer(), - *aBoundary2.GetContainer(), - GetParentOrShadowHostNode); + const CommonAncestors commonAncestors( + *aBoundary1.GetContainer(), *aBoundary2.GetContainer(), GetParentFunc); if (MOZ_UNLIKELY(!commonAncestors.GetClosestCommonAncestor())) { return Nothing(); @@ -3557,10 +3672,11 @@ Maybe nsContentUtils::ComparePoints( commonAncestors.GetClosestCommonAncestorChild1(); const nsINode* closestCommonAncestorChild2 = commonAncestors.GetClosestCommonAncestorChild2(); - commonAncestors.WarnIfClosestCommonAncestorChildrenAreNotInChildList(); + commonAncestors + .template WarnIfClosestCommonAncestorChildrenAreNotInChildList(); MOZ_ASSERT(closestCommonAncestorChild1 != closestCommonAncestorChild2); if (closestCommonAncestorChild1 && closestCommonAncestorChild2) { - return CompareClosestCommonAncestorChildren( + return CompareClosestCommonAncestorChildren( *commonAncestors.GetClosestCommonAncestor(), closestCommonAncestorChild1, closestCommonAncestorChild2, aIndexCache); } @@ -3575,7 +3691,7 @@ Maybe nsContentUtils::ComparePoints( // XXX Keep the odd traditional behavior for now. return Some(1); } - const Maybe comp = nsContentUtils::CompareChildNodes( + const Maybe comp = nsContentUtils::CompareChildNodes( aBoundary1.GetChildAtOffset(), closestCommonAncestorChild2, aIndexCache); if (NS_WARN_IF(comp.isNothing())) { @@ -3613,7 +3729,7 @@ Maybe nsContentUtils::ComparePoints( // XXX Keep the odd traditional behavior for now. return Some(-1); } - const Maybe comp = nsContentUtils::CompareChildNodes( + const Maybe comp = nsContentUtils::CompareChildNodes( closestCommonAncestorChild1, aBoundary2.GetChildAtOffset(), aIndexCache); if (NS_WARN_IF(comp.isNothing())) { NS_ASSERTION(comp.isSome(), diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 2963ea3f061f..b518b72d0b52 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -716,6 +716,9 @@ class nsContentUtils { * 0 if point1 == point2. * `Nothing` if the two nodes aren't in the same connected subtree. */ + template > static mozilla::Maybe ComparePointsWithIndices( const nsINode* aParent1, uint32_t aOffset1, const nsINode* aParent2, uint32_t aOffset2, NodeIndexCache* aIndexCache = nullptr); @@ -730,7 +733,10 @@ class nsContentUtils { * 0 if point1 == point2. * `Nothing` if the two nodes aren't in the same connected subtree. */ - template + template > static mozilla::Maybe ComparePoints( const mozilla::RangeBoundaryBase& aBoundary1, const mozilla::RangeBoundaryBase& aBoundary2, @@ -746,6 +752,9 @@ class nsContentUtils { * traditional behavior. If you want to use this in new code, it means that * you **should** check the offset values and call `ComparePoints` instead. */ + template > static mozilla::Maybe ComparePoints_AllowNegativeOffsets( const nsINode* aParent1, int64_t aOffset1, const nsINode* aParent2, int64_t aOffset2) { @@ -768,7 +777,7 @@ class nsContentUtils { } // Otherwise, aOffset1 nor aOffset2 is referred so that any value is fine // if negative. - return ComparePointsWithIndices( + return ComparePointsWithIndices( aParent1, // Avoid warnings. aOffset1 < 0 ? aParent1->GetChildCount() @@ -780,7 +789,8 @@ class nsContentUtils { : std::min(static_cast(aOffset2), aParent2->GetChildCount())); } - return ComparePointsWithIndices(aParent1, aOffset1, aParent2, aOffset2); + return ComparePointsWithIndices(aParent1, aOffset1, aParent2, + aOffset2); } /** @@ -3642,6 +3652,9 @@ class nsContentUtils { * node. * Return Nothing if aChild1 is a root of the native anonymous subtree. */ + template > static mozilla::Maybe CompareChildNodes( const nsINode* aChild1, const nsINode* aChild2, NodeIndexCache* aIndexCache = nullptr); @@ -3671,8 +3684,12 @@ class nsContentUtils { * includes odd traditional behavior. Therefore, do not use this method as a * utility method. */ + template > static mozilla::Maybe CompareClosestCommonAncestorChildren( - const nsINode&, const nsINode*, const nsINode*, NodeIndexCache*); + const nsINode&, const nsINode*, const nsINode*, + NodeIndexCache* = nullptr); static nsIXPConnect* sXPConnect; diff --git a/dom/base/nsIContentInlines.h b/dom/base/nsIContentInlines.h index a931626f7870..2cd48062db5b 100644 --- a/dom/base/nsIContentInlines.h +++ b/dom/base/nsIContentInlines.h @@ -144,9 +144,8 @@ inline nsINode* nsINode::GetFlattenedTreeParentNodeForStyle() const { return ::GetFlattenedTreeParentNode(this); } -inline nsIContent* nsINode::GetFlattenedTreeParentNodeForSelection() const { - nsINode* parent = ::GetFlattenedTreeParentNode(this); - return (parent && parent->IsContent()) ? parent->AsContent() : nullptr; +inline nsINode* nsINode::GetFlattenedTreeParentNodeForSelection() const { + return ::GetFlattenedTreeParentNode(this); } inline bool nsINode::NodeOrAncestorHasDirAuto() const { diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp index e74af55d0aab..2d4d95806bd6 100644 --- a/dom/base/nsINode.cpp +++ b/dom/base/nsINode.cpp @@ -343,16 +343,28 @@ class IsItemInRangeComparator { } int operator()(const AbstractRange* const aRange) const { - Maybe cmp = nsContentUtils::ComparePoints( - ConstRawRangeBoundary(&mNode, mEndOffset, - RangeBoundaryIsMutationObserved::No), - aRange->MayCrossShadowBoundaryStartRef(), mCache); - if (cmp.valueOr(1) == 1) { - cmp = nsContentUtils::ComparePoints( - ConstRawRangeBoundary(&mNode, mStartOffset, - RangeBoundaryIsMutationObserved::No), - aRange->MayCrossShadowBoundaryEndRef(), mCache); - if (cmp.valueOr(1) == -1) { + auto ComparePoints = [](const nsINode* aNode1, const uint32_t aOffset1, + const nsINode* aNode2, const uint32_t aOffset2, + nsContentUtils::NodeIndexCache* aCache) { + if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) { + return nsContentUtils::ComparePointsWithIndices( + aNode1, aOffset1, aNode2, aOffset2, aCache); + } + return nsContentUtils::ComparePointsWithIndices< + TreeKind::ShadowIncludingDOM>(aNode1, aOffset1, aNode2, aOffset2, + aCache); + }; + + Maybe cmp = ComparePoints( + &mNode, mEndOffset, aRange->GetMayCrossShadowBoundaryStartContainer(), + aRange->MayCrossShadowBoundaryStartOffset(), mCache); + MOZ_ASSERT(cmp.isSome()); // Should always be connected at this point. + if (cmp.value() == 1) { + cmp = ComparePoints(&mNode, mStartOffset, + aRange->GetMayCrossShadowBoundaryEndContainer(), + aRange->MayCrossShadowBoundaryEndOffset(), mCache); + MOZ_ASSERT(cmp.isSome()); + if (cmp.value() == -1) { return 0; } return 1; diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h index 2bdf92b941ce..6a982b3f278b 100644 --- a/dom/base/nsINode.h +++ b/dom/base/nsINode.h @@ -1190,7 +1190,7 @@ class nsINode : public mozilla::dom::EventTarget { * 2. For contents that are slotted into a UA shadow tree, use its * parent rather than the slot element. */ - inline nsIContent* GetFlattenedTreeParentNodeForSelection() const; + inline nsINode* GetFlattenedTreeParentNodeForSelection() const; inline mozilla::dom::Element* GetFlattenedTreeParentElement() const; inline mozilla::dom::Element* GetFlattenedTreeParentElementForStyle() const; diff --git a/dom/base/nsRange.cpp b/dom/base/nsRange.cpp index a9d2d151e169..65c0518f9d60 100644 --- a/dom/base/nsRange.cpp +++ b/dom/base/nsRange.cpp @@ -285,12 +285,21 @@ static RangeBehaviour GetRangeBehaviour( const RangeBoundary& otherSideExistingBoundary = aIsSetStart ? aRange->EndRef() : aRange->StartRef(); + auto ComparePoints = [aAllowCrossShadowBoundary]( + const RawRangeBoundary& aBoundary1, + const RawRangeBoundary& aBoundary2) { + if (aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes) { + return nsContentUtils::ComparePoints(aBoundary1, + aBoundary2); + } + return nsContentUtils::ComparePoints( + aBoundary1, aBoundary2); + }; // Both bondaries are in the same root, now check for their position const Maybe order = - aIsSetStart ? nsContentUtils::ComparePoints(aNewBoundary, - otherSideExistingBoundary) - : nsContentUtils::ComparePoints(otherSideExistingBoundary, - aNewBoundary); + aIsSetStart + ? ComparePoints(aNewBoundary, otherSideExistingBoundary.AsRaw()) + : ComparePoints(otherSideExistingBoundary.AsRaw(), aNewBoundary); if (order) { if (*order != 1) { @@ -324,11 +333,12 @@ static RangeBehaviour GetRangeBehaviour( // otherSideExistingBoundary. However, it's possible that aNewBoundary // is valid with the otherSideExistingCrossShadowBoundaryBoundary. const Maybe withCrossShadowBoundaryOrder = - aIsSetStart - ? nsContentUtils::ComparePoints( - aNewBoundary, otherSideExistingCrossShadowBoundaryBoundary) - : nsContentUtils::ComparePoints( - otherSideExistingCrossShadowBoundaryBoundary, aNewBoundary); + aIsSetStart ? ComparePoints( + aNewBoundary, + otherSideExistingCrossShadowBoundaryBoundary.AsRaw()) + : ComparePoints( + otherSideExistingCrossShadowBoundaryBoundary.AsRaw(), + aNewBoundary); // Valid to the cross boundary boundary. if (withCrossShadowBoundaryOrder && *withCrossShadowBoundaryOrder != 1) { @@ -3246,8 +3256,7 @@ void nsRange::ExcludeNonSelectableNodes(nsTArray>* aOutRanges) { nsINode* node = preOrderIter.GetCurrentNode(); preOrderIter.Next(); bool selectable = true; - nsIContent* content = - node && node->IsContent() ? node->AsContent() : nullptr; + nsIContent* content = nsIContent::FromNodeOrNull(node); if (content) { if (firstNonSelectableContent && ExcludeIfNextToNonSelectable(content)) { diff --git a/editor/libeditor/HTMLEditUtils.cpp b/editor/libeditor/HTMLEditUtils.cpp index 238adb827b53..c60e57ea6659 100644 --- a/editor/libeditor/HTMLEditUtils.cpp +++ b/editor/libeditor/HTMLEditUtils.cpp @@ -2829,9 +2829,10 @@ HTMLEditUtils::ComputePointToPutCaretInElementIfOutside( // Use range boundaries and RangeUtils::CompareNodeToRange() to compare // selection start to new block. bool nodeBefore, nodeAfter; - nsresult rv = RangeUtils::CompareNodeToRangeBoundaries( - const_cast(&aElement), aCurrentPoint.ToRawRangeBoundary(), - aCurrentPoint.ToRawRangeBoundary(), &nodeBefore, &nodeAfter); + nsresult rv = + RangeUtils::CompareNodeToRangeBoundaries( + const_cast(&aElement), aCurrentPoint.ToRawRangeBoundary(), + aCurrentPoint.ToRawRangeBoundary(), &nodeBefore, &nodeAfter); if (NS_FAILED(rv)) { NS_WARNING("RangeUtils::CompareNodeToRange() failed"); return Err(rv); diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp index 7a585018123a..0141759ca584 100644 --- a/layout/base/PresShell.cpp +++ b/layout/base/PresShell.cpp @@ -5063,7 +5063,8 @@ nsRect PresShell::ClipListToRange(nsDisplayListBuilder* aBuilder, // if the node is within the range, append it to the temporary list bool before, after; nsresult rv = - RangeUtils::CompareNodeToRange(content, aRange, &before, &after); + RangeUtils::CompareNodeToRange( + content, aRange, &before, &after); if (NS_SUCCEEDED(rv) && !before && !after) { itemToInsert = i; bool snap;