summaryrefslogtreecommitdiffstats
path: root/dom/base/nsRange.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:35:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:35:49 +0000
commitd8bbc7858622b6d9c278469aab701ca0b609cddf (patch)
treeeff41dc61d9f714852212739e6b3738b82a2af87 /dom/base/nsRange.cpp
parentReleasing progress-linux version 125.0.3-1~progress7.99u1. (diff)
downloadfirefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz
firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/base/nsRange.cpp')
-rw-r--r--dom/base/nsRange.cpp406
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);
+}