diff options
Diffstat (limited to 'dom/base/nsRange.cpp')
-rw-r--r-- | dom/base/nsRange.cpp | 406 |
1 files changed, 332 insertions, 74 deletions
diff --git a/dom/base/nsRange.cpp b/dom/base/nsRange.cpp index 3121ed86c3..cf15f239c5 100644 --- a/dom/base/nsRange.cpp +++ b/dom/base/nsRange.cpp @@ -105,16 +105,30 @@ template nsresult nsRange::SetStartAndEnd( template void nsRange::DoSetRange(const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary, - nsINode* aRootNode, bool aNotInsertedYet); + nsINode* aRootNode, bool aNotInsertedYet, + CollapsePolicy aCollapsePolicy); template void nsRange::DoSetRange(const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary, - nsINode* aRootNode, bool aNotInsertedYet); + nsINode* aRootNode, bool aNotInsertedYet, + CollapsePolicy aCollapsePolicy); template void nsRange::DoSetRange(const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary, - nsINode* aRootNode, bool aNotInsertedYet); + nsINode* aRootNode, bool aNotInsertedYet, + CollapsePolicy aCollapsePolicy); template void nsRange::DoSetRange(const RawRangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary, - nsINode* aRootNode, bool aNotInsertedYet); + nsINode* aRootNode, bool aNotInsertedYet, + CollapsePolicy aCollapsePolicy); + +template void nsRange::CreateOrUpdateCrossShadowBoundaryRangeIfNeeded( + const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary); +template void nsRange::CreateOrUpdateCrossShadowBoundaryRangeIfNeeded( + const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary); +template void nsRange::CreateOrUpdateCrossShadowBoundaryRangeIfNeeded( + const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary); +template void nsRange::CreateOrUpdateCrossShadowBoundaryRangeIfNeeded( + const RawRangeBoundary& aStartBoundary, + const RawRangeBoundary& aEndBoundary); JSObject* nsRange::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { @@ -171,7 +185,7 @@ nsRange::nsRange(nsINode* aNode) mNextEndRef(nullptr) { // printf("Size of nsRange: %zu\n", sizeof(nsRange)); - static_assert(sizeof(nsRange) <= 240, + static_assert(sizeof(nsRange) <= 248, "nsRange size shouldn't be increased as far as possible"); } @@ -201,6 +215,109 @@ already_AddRefed<nsRange> nsRange::Create( return range.forget(); } +/* + * When a new boundary is given to a nsRange, compare its position with other + * existing boundaries to see if we need to collapse the end points. + * + * aRange: The nsRange that aNewBoundary is being set to. + * aNewRoot: The shadow-including root of the container of aNewBoundary + * aNewBoundary: The new boundary + * aIsSetStart: true if ShouldCollapseBoundary is called by nsRange::SetStart, + * false otherwise + * aAllowCrossShadowBoundary: Indicates whether the boundaries allowed to cross + * shadow boundary or not + */ +static CollapsePolicy ShouldCollapseBoundary( + const nsRange* aRange, const nsINode* aNewRoot, + const RawRangeBoundary& aNewBoundary, const bool aIsSetStart, + AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) { + if (!aRange->IsPositioned()) { + return CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges; + } + + MOZ_ASSERT(aRange->GetRoot()); + if (aNewRoot != aRange->GetRoot()) { + // Boundaries are in different document (or not connected), so collapse + // the both the default range and the crossBoundaryRange range. + if (aNewRoot->GetComposedDoc() != aRange->GetRoot()->GetComposedDoc()) { + return CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges; + } + + // Always collapse both ranges if the one of the roots is an UA widget + // regardless whether the boundaries are allowed to cross shadow boundary + // or not. + if (AbstractRange::IsRootUAWidget(aNewRoot) || + AbstractRange::IsRootUAWidget(aRange->GetRoot())) { + return CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges; + } + + // Different root, but same document. So we only collapse the + // default range if boundaries are allowed to cross shadow boundary. + return aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes + ? CollapsePolicy::DefaultRange + : CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges; + } + + const RangeBoundary& otherSideExistingBoundary = + aIsSetStart ? aRange->EndRef() : aRange->StartRef(); + + // Both bondaries are in the same root, now check for their position + const Maybe<int32_t> order = + aIsSetStart ? nsContentUtils::ComparePoints(aNewBoundary, + otherSideExistingBoundary) + : nsContentUtils::ComparePoints(otherSideExistingBoundary, + aNewBoundary); + + if (order) { + if (*order != 1) { + // aNewBoundary is at a valid position. + // + // If aIsSetStart is true, this means + // aNewBoundary <= otherSideExistingBoundary which is + // good because aNewBoundary intends to be the start. + // + // If aIsSetStart is false, this means + // otherSideExistingBoundary <= aNewBoundary which is good because + // aNewBoundary intends to be the end. + // + // So no collapse for above cases. + return CollapsePolicy::No; + } + + if (!aRange->MayCrossShadowBoundary() || + aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::No) { + return CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges; + } + + const RangeBoundary& otherSideExistingCrossShadowBoundaryBoundary = + aIsSetStart ? aRange->MayCrossShadowBoundaryEndRef() + : aRange->MayCrossShadowBoundaryStartRef(); + + // Please see the comment for (*order != 1) to see what "valid" means. + // + // We reach to this line when (*order == 1), it means aNewBoundary is + // at an invalid position, so we need to collapse aNewBoundary with + // otherSideExistingBoundary. However, it's possible that aNewBoundary + // is valid with the otherSideExistingCrossShadowBoundaryBoundary. + const Maybe<int32_t> withCrossShadowBoundaryOrder = + aIsSetStart + ? nsContentUtils::ComparePoints( + aNewBoundary, otherSideExistingCrossShadowBoundaryBoundary) + : nsContentUtils::ComparePoints( + otherSideExistingCrossShadowBoundaryBoundary, aNewBoundary); + + // Valid to the cross boundary boundary. + if (withCrossShadowBoundaryOrder && *withCrossShadowBoundaryOrder != 1) { + return CollapsePolicy::DefaultRange; + } + + // Not valid to both existing boundaries. + return CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges; + } + + MOZ_ASSERT_UNREACHABLE(); + return CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges; +} /****************************************************** * nsISupports ******************************************************/ @@ -219,11 +336,13 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(nsRange) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsRange, AbstractRange) // `Reset()` unlinks `mStart`, `mEnd` and `mRoot`. + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCrossShadowBoundaryRange); tmp->Reset(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsRange, AbstractRange) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCrossShadowBoundaryRange); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsRange, AbstractRange) @@ -231,6 +350,7 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_END bool nsRange::MaybeInterruptLastRelease() { bool interrupt = AbstractRange::MaybeCacheToReuse(*this); + ResetCrossShadowBoundaryRange(); MOZ_ASSERT(!interrupt || IsCleared()); return interrupt; } @@ -573,6 +693,21 @@ void nsRange::ContentRemoved(nsIContent* aChild, nsIContent* aPreviousSibling) { nsINode* startContainer = mStart.Container(); nsINode* endContainer = mEnd.Container(); + // FIXME(sefeng): Temporary Solution for ContentRemoved + // editing/crashtests/removeformat-from-DOMNodeRemoved.html can be used to + // verify this. + if (mCrossShadowBoundaryRange) { + if (mCrossShadowBoundaryRange->GetStartContainer() == aChild || + mCrossShadowBoundaryRange->GetEndContainer() == aChild) { + ResetCrossShadowBoundaryRange(); + } else if (ShadowRoot* shadowRoot = aChild->GetShadowRoot()) { + if (mCrossShadowBoundaryRange->GetStartContainer() == shadowRoot || + mCrossShadowBoundaryRange->GetEndContainer() == shadowRoot) { + ResetCrossShadowBoundaryRange(); + } + } + } + RawRangeBoundary newStart; RawRangeBoundary newEnd; Maybe<bool> gravitateStart; @@ -881,10 +1016,12 @@ void nsRange::AssertIfMismatchRootAndRangeBoundaries( // Calling DoSetRange with either parent argument null will collapse // the range to have both endpoints point to the other node template <typename SPT, typename SRT, typename EPT, typename ERT> -void nsRange::DoSetRange(const RangeBoundaryBase<SPT, SRT>& aStartBoundary, - const RangeBoundaryBase<EPT, ERT>& aEndBoundary, - nsINode* aRootNode, - bool aNotInsertedYet /* = false */) { +void nsRange::DoSetRange( + const RangeBoundaryBase<SPT, SRT>& aStartBoundary, + const RangeBoundaryBase<EPT, ERT>& aEndBoundary, nsINode* aRootNode, + bool aNotInsertedYet /* = false */, + CollapsePolicy + aCollapsePolicy /* = DEFAULT_RANGE_AND_CROSS_BOUNDARY_RANGES */) { mIsPositioned = aStartBoundary.IsSetAndValid() && aEndBoundary.IsSetAndValid() && aRootNode; MOZ_ASSERT_IF(!mIsPositioned, !aStartBoundary.IsSet()); @@ -913,6 +1050,11 @@ void nsRange::DoSetRange(const RangeBoundaryBase<SPT, SRT>& aStartBoundary, mStart.CopyFrom(aStartBoundary, RangeBoundaryIsMutationObserved::Yes); mEnd.CopyFrom(aEndBoundary, RangeBoundaryIsMutationObserved::Yes); + if (aCollapsePolicy == + CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges) { + ResetCrossShadowBoundaryRange(); + } + if (checkCommonAncestor) { UpdateCommonAncestorIfNecessary(); } @@ -965,17 +1107,21 @@ bool nsRange::CanAccess(const nsINode& aNode) const { return nsContentUtils::CanCallerAccess(&aNode); } -void nsRange::SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv) { +void nsRange::SetStart( + nsINode& aNode, uint32_t aOffset, ErrorResult& aRv, + AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) { if (!CanAccess(aNode)) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; } AutoInvalidateSelection atEndOfBlock(this); - SetStart(RawRangeBoundary(&aNode, aOffset), aRv); + SetStart(RawRangeBoundary(&aNode, aOffset), aRv, aAllowCrossShadowBoundary); } -void nsRange::SetStart(const RawRangeBoundary& aPoint, ErrorResult& aRv) { +void nsRange::SetStart( + const RawRangeBoundary& aPoint, ErrorResult& aRv, + AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) { nsINode* newRoot = RangeUtils::ComputeRootNode(aPoint.Container()); if (!newRoot) { aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR); @@ -987,28 +1133,46 @@ void nsRange::SetStart(const RawRangeBoundary& aPoint, ErrorResult& aRv) { return; } - // Collapse if not positioned yet, if positioned in another doc or - // if the new start is after end. - const bool collapse = [&]() { - if (!mIsPositioned || (newRoot != mRoot)) { - return true; - } - - const Maybe<int32_t> order = nsContentUtils::ComparePoints(aPoint, mEnd); - if (order) { - return *order == 1; - } - - MOZ_ASSERT_UNREACHABLE(); - return true; - }(); + CollapsePolicy policy = + ShouldCollapseBoundary(this, newRoot, aPoint, true /* aIsSetStart= */, + aAllowCrossShadowBoundary); - if (collapse) { - DoSetRange(aPoint, aPoint, newRoot); - return; + switch (policy) { + case CollapsePolicy::No: + // EndRef(..) may be same as mStart or not, depends on + // the value of mCrossShadowBoundaryRange->mEnd, We need to update + // mCrossShadowBoundaryRange and the default boundaries separately + if (aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes) { + if (MayCrossShadowBoundaryEndRef() != mEnd) { + CreateOrUpdateCrossShadowBoundaryRangeIfNeeded( + aPoint, MayCrossShadowBoundaryEndRef()); + } else { + // The normal range is good enough for this case, just use that. + ResetCrossShadowBoundaryRange(); + } + } + DoSetRange(aPoint, mEnd, mRoot, false, policy); + break; + case CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges: + DoSetRange(aPoint, aPoint, newRoot, false, policy); + break; + case CollapsePolicy::DefaultRange: + MOZ_ASSERT(aAllowCrossShadowBoundary == + AllowRangeCrossShadowBoundary::Yes); + CreateOrUpdateCrossShadowBoundaryRangeIfNeeded( + aPoint, MayCrossShadowBoundaryEndRef()); + DoSetRange(aPoint, aPoint, newRoot, false, policy); + break; + default: + MOZ_ASSERT_UNREACHABLE(); } +} - DoSetRange(aPoint, mEnd, mRoot); +void nsRange::SetStartAllowCrossShadowBoundary(nsINode& aNode, uint32_t aOffset, + ErrorResult& aErr) { + AutoCalledByJSRestore calledByJSRestorer(*this); + mCalledByJS = true; + SetStart(aNode, aOffset, aErr, AllowRangeCrossShadowBoundary::Yes); } void nsRange::SetStartBeforeJS(nsINode& aNode, ErrorResult& aErr) { @@ -1017,7 +1181,9 @@ void nsRange::SetStartBeforeJS(nsINode& aNode, ErrorResult& aErr) { SetStartBefore(aNode, aErr); } -void nsRange::SetStartBefore(nsINode& aNode, ErrorResult& aRv) { +void nsRange::SetStartBefore( + nsINode& aNode, ErrorResult& aRv, + AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) { if (!CanAccess(aNode)) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; @@ -1027,7 +1193,8 @@ void nsRange::SetStartBefore(nsINode& aNode, ErrorResult& aRv) { // If the node is being removed from its parent, GetRawRangeBoundaryBefore() // returns unset instance. Then, SetStart() will throw // NS_ERROR_DOM_INVALID_NODE_TYPE_ERR. - SetStart(RangeUtils::GetRawRangeBoundaryBefore(&aNode), aRv); + SetStart(RangeUtils::GetRawRangeBoundaryBefore(&aNode), aRv, + aAllowCrossShadowBoundary); } void nsRange::SetStartAfterJS(nsINode& aNode, ErrorResult& aErr) { @@ -1055,16 +1222,18 @@ void nsRange::SetEndJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr) { SetEnd(aNode, aOffset, aErr); } -void nsRange::SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv) { +void nsRange::SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv, + AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) { if (!CanAccess(aNode)) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; } AutoInvalidateSelection atEndOfBlock(this); - SetEnd(RawRangeBoundary(&aNode, aOffset), aRv); + SetEnd(RawRangeBoundary(&aNode, aOffset), aRv, aAllowCrossShadowBoundary); } -void nsRange::SetEnd(const RawRangeBoundary& aPoint, ErrorResult& aRv) { +void nsRange::SetEnd(const RawRangeBoundary& aPoint, ErrorResult& aRv, + AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) { nsINode* newRoot = RangeUtils::ComputeRootNode(aPoint.Container()); if (!newRoot) { aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR); @@ -1076,28 +1245,47 @@ void nsRange::SetEnd(const RawRangeBoundary& aPoint, ErrorResult& aRv) { return; } - // Collapse if not positioned yet, if positioned in another doc or - // if the new end is before start. - const bool collapse = [&]() { - if (!mIsPositioned || (newRoot != mRoot)) { - return true; - } + CollapsePolicy policy = + ShouldCollapseBoundary(this, newRoot, aPoint, false /* aIsStartStart */, + aAllowCrossShadowBoundary); - const Maybe<int32_t> order = nsContentUtils::ComparePoints(mStart, aPoint); - if (order) { - return *order == 1; - } - - MOZ_ASSERT_UNREACHABLE(); - return true; - }(); - - if (collapse) { - DoSetRange(aPoint, aPoint, newRoot); - return; + switch (policy) { + case CollapsePolicy::No: + // StartRef(..) may be same as mStart or not, depends on + // the value of mCrossShadowBoundaryRange->mStart, so we need to update + // mCrossShadowBoundaryRange and the default boundaries separately + if (aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes) { + if (MayCrossShadowBoundaryStartRef() != mStart) { + CreateOrUpdateCrossShadowBoundaryRangeIfNeeded( + MayCrossShadowBoundaryStartRef(), aPoint); + } else { + // The normal range is good enough for this case, just use that. + ResetCrossShadowBoundaryRange(); + } + } + DoSetRange(mStart, aPoint, mRoot, false, policy); + break; + case CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges: + DoSetRange(aPoint, aPoint, newRoot, false, policy); + break; + case CollapsePolicy::DefaultRange: + MOZ_ASSERT(aAllowCrossShadowBoundary == + AllowRangeCrossShadowBoundary::Yes); + CreateOrUpdateCrossShadowBoundaryRangeIfNeeded( + MayCrossShadowBoundaryStartRef(), aPoint); + DoSetRange(aPoint, aPoint, newRoot, false, policy); + break; + default: + MOZ_ASSERT_UNREACHABLE(); } +} - DoSetRange(mStart, aPoint, mRoot); +void nsRange::SetEndAllowCrossShadowBoundary(nsINode& aNode, uint32_t aOffset, + ErrorResult& aErr) { + AutoCalledByJSRestore calledByJSRestorer(*this); + mCalledByJS = true; + SetEnd(aNode, aOffset, aErr, + AllowRangeCrossShadowBoundary::Yes /* aAllowCrossShadowBoundary */); } void nsRange::SelectNodesInContainer(nsINode* aContainer, @@ -1127,7 +1315,9 @@ void nsRange::SetEndBeforeJS(nsINode& aNode, ErrorResult& aErr) { SetEndBefore(aNode, aErr); } -void nsRange::SetEndBefore(nsINode& aNode, ErrorResult& aRv) { +void nsRange::SetEndBefore( + nsINode& aNode, ErrorResult& aRv, + AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) { if (!CanAccess(aNode)) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; @@ -1137,7 +1327,8 @@ void nsRange::SetEndBefore(nsINode& aNode, ErrorResult& aRv) { // If the node is being removed from its parent, GetRawRangeBoundaryBefore() // returns unset instance. Then, SetEnd() will throw // NS_ERROR_DOM_INVALID_NODE_TYPE_ERR. - SetEnd(RangeUtils::GetRawRangeBoundaryBefore(&aNode), aRv); + SetEnd(RangeUtils::GetRawRangeBoundaryBefore(&aNode), aRv, + aAllowCrossShadowBoundary); } void nsRange::SetEndAfterJS(nsINode& aNode, ErrorResult& aErr) { @@ -2201,6 +2392,11 @@ already_AddRefed<DocumentFragment> nsRange::CloneContents(ErrorResult& aRv) { already_AddRefed<nsRange> nsRange::CloneRange() const { RefPtr<nsRange> range = nsRange::Create(mOwner); range->DoSetRange(mStart, mEnd, mRoot); + if (mCrossShadowBoundaryRange) { + range->CreateOrUpdateCrossShadowBoundaryRangeIfNeeded( + mCrossShadowBoundaryRange->StartRef(), + mCrossShadowBoundaryRange->EndRef()); + } return range.forget(); } @@ -2640,13 +2836,14 @@ static nsresult GetPartialTextRect(RectCallback* aCallback, static void CollectClientRectsForSubtree( nsINode* aNode, RectCallback* aCollector, Sequence<nsString>* aTextList, nsINode* aStartContainer, uint32_t aStartOffset, nsINode* aEndContainer, - uint32_t aEndOffset, bool aClampToEdge, bool aFlushLayout) { + uint32_t aEndOffset, bool aClampToEdge, bool aFlushLayout, bool aTextOnly) { auto* content = nsIContent::FromNode(aNode); if (!content) { return; } - if (content->IsText()) { + const bool isText = content->IsText(); + if (isText) { if (aNode == aStartContainer) { int32_t offset = aStartContainer == aEndContainer ? static_cast<int32_t>(aEndOffset) @@ -2665,19 +2862,30 @@ static void CollectClientRectsForSubtree( } } - if (aNode->IsElement() && aNode->AsElement()->IsDisplayContents()) { - FlattenedChildIterator childIter(content); - - for (nsIContent* child = childIter.GetNextChild(); child; - child = childIter.GetNextChild()) { - CollectClientRectsForSubtree(child, aCollector, aTextList, - aStartContainer, aStartOffset, aEndContainer, - aEndOffset, aClampToEdge, aFlushLayout); + if (nsIFrame* frame = content->GetPrimaryFrame()) { + if (!aTextOnly || isText) { + nsLayoutUtils::GetAllInFlowRectsAndTexts( + frame, nsLayoutUtils::GetContainingBlockForClientRect(frame), + aCollector, aTextList, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS); + 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 (nsIFrame* frame = content->GetPrimaryFrame()) { - nsLayoutUtils::GetAllInFlowRectsAndTexts( - frame, nsLayoutUtils::GetContainingBlockForClientRect(frame), - aCollector, aTextList, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS); + } 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, aFlushLayout, aTextOnly); } } @@ -2745,7 +2953,7 @@ void nsRange::CollectClientRectsAndText( CollectClientRectsForSubtree(node, aCollector, aTextList, aStartContainer, aStartOffset, aEndContainer, aEndOffset, - aClampToEdge, aFlushLayout); + aClampToEdge, aFlushLayout, false); } while (!iter.IsDone()); } @@ -2994,7 +3202,7 @@ void nsRange::ExcludeNonSelectableNodes(nsTArray<RefPtr<nsRange>>* aOutRanges) { // This is the initial range and all its nodes until now are // non-selectable so just trim them from the start. IgnoredErrorResult err; - range->SetStartBefore(*node, err); + range->SetStartBefore(*node, err, AllowRangeCrossShadowBoundary::Yes); if (err.Failed()) { return; } @@ -3008,7 +3216,8 @@ void nsRange::ExcludeNonSelectableNodes(nsTArray<RefPtr<nsRange>>* aOutRanges) { // Truncate the current range before the first non-selectable node. IgnoredErrorResult err; - range->SetEndBefore(*firstNonSelectableContent, err); + range->SetEndBefore(*firstNonSelectableContent, err, + AllowRangeCrossShadowBoundary::Yes); // Store it in the result (strong ref) - do this before creating // a new range in |newRange| below so we don't drop the last ref @@ -3252,3 +3461,52 @@ void nsRange::GetInnerTextNoFlush(DOMString& aValue, ErrorResult& aError, // Do not flush trailing line breaks! Required breaks at the end of the text // are suppressed. } + +template <typename SPT, typename SRT, typename EPT, typename ERT> +void nsRange::CreateOrUpdateCrossShadowBoundaryRangeIfNeeded( + const mozilla::RangeBoundaryBase<SPT, SRT>& aStartBoundary, + const mozilla::RangeBoundaryBase<EPT, ERT>& aEndBoundary) { + if (!StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) { + return; + } + + MOZ_ASSERT(aStartBoundary.IsSetAndValid() && aEndBoundary.IsSetAndValid()); + + nsINode* startNode = aStartBoundary.Container(); + nsINode* endNode = aEndBoundary.Container(); + + if (!startNode && !endNode) { + ResetCrossShadowBoundaryRange(); + return; + } + + auto CanBecomeCrossShadowBoundaryPoint = [](nsINode* aContainer) -> bool { + if (!aContainer) { + return true; + } + + // Unlike normal ranges, shadow cross ranges don't work + // when the nodes aren't in document. + if (!aContainer->IsInComposedDoc()) { + return false; + } + + // AbstractRange::GetClosestCommonInclusiveAncestor only supports + // Document and Content nodes. + return aContainer->IsDocument() || aContainer->IsContent(); + }; + + if (!CanBecomeCrossShadowBoundaryPoint(startNode) || + !CanBecomeCrossShadowBoundaryPoint(endNode)) { + ResetCrossShadowBoundaryRange(); + return; + } + + if (!mCrossShadowBoundaryRange) { + mCrossShadowBoundaryRange = + StaticRange::Create(aStartBoundary, aEndBoundary, IgnoreErrors()); + return; + } + + mCrossShadowBoundaryRange->SetStartAndEnd(aStartBoundary, aEndBoundary); +} |