1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/CrossShadowBoundaryRange.h"
8 #include "nsContentUtils.h"
11 #include "nsIContentInlines.h"
13 namespace mozilla::dom
{
14 template already_AddRefed
<CrossShadowBoundaryRange
>
15 CrossShadowBoundaryRange::Create(const RangeBoundary
& aStartBoundary
,
16 const RangeBoundary
& aEndBoundary
,
18 template already_AddRefed
<CrossShadowBoundaryRange
>
19 CrossShadowBoundaryRange::Create(const RangeBoundary
& aStartBoundary
,
20 const RawRangeBoundary
& aEndBoundary
,
22 template already_AddRefed
<CrossShadowBoundaryRange
>
23 CrossShadowBoundaryRange::Create(const RawRangeBoundary
& aStartBoundary
,
24 const RangeBoundary
& aEndBoundary
,
26 template already_AddRefed
<CrossShadowBoundaryRange
>
27 CrossShadowBoundaryRange::Create(const RawRangeBoundary
& aStartBoundary
,
28 const RawRangeBoundary
& aEndBoundary
,
31 template void CrossShadowBoundaryRange::DoSetRange(
32 const RangeBoundary
& aStartBoundary
, const RangeBoundary
& aEndBoundary
,
33 nsINode
* aRootNode
, nsRange
* aOwner
);
34 template void CrossShadowBoundaryRange::DoSetRange(
35 const RangeBoundary
& aStartBoundary
, const RawRangeBoundary
& aEndBoundary
,
36 nsINode
* aRootNode
, nsRange
* aOwner
);
37 template void CrossShadowBoundaryRange::DoSetRange(
38 const RawRangeBoundary
& aStartBoundary
, const RangeBoundary
& aEndBoundary
,
39 nsINode
* aRootNode
, nsRange
* aOwner
);
40 template void CrossShadowBoundaryRange::DoSetRange(
41 const RawRangeBoundary
& aStartBoundary
,
42 const RawRangeBoundary
& aEndBoundary
, nsINode
* aRootNode
, nsRange
* aOwner
);
44 template nsresult
CrossShadowBoundaryRange::SetStartAndEnd(
45 const RangeBoundary
& aStartBoundary
, const RangeBoundary
& aEndBoundary
);
46 template nsresult
CrossShadowBoundaryRange::SetStartAndEnd(
47 const RangeBoundary
& aStartBoundary
, const RawRangeBoundary
& aEndBoundary
);
48 template nsresult
CrossShadowBoundaryRange::SetStartAndEnd(
49 const RawRangeBoundary
& aStartBoundary
, const RangeBoundary
& aEndBoundary
);
50 template nsresult
CrossShadowBoundaryRange::SetStartAndEnd(
51 const RawRangeBoundary
& aStartBoundary
,
52 const RawRangeBoundary
& aEndBoundary
);
54 nsTArray
<RefPtr
<CrossShadowBoundaryRange
>>*
55 CrossShadowBoundaryRange::sCachedRanges
= nullptr;
57 NS_IMPL_CYCLE_COLLECTING_ADDREF(CrossShadowBoundaryRange
)
59 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_INTERRUPTABLE_LAST_RELEASE(
60 CrossShadowBoundaryRange
,
61 DoSetRange(RawRangeBoundary(), RawRangeBoundary(), nullptr, nullptr),
62 AbstractRange::MaybeCacheToReuse(*this))
64 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CrossShadowBoundaryRange
)
65 NS_INTERFACE_MAP_END_INHERITING(CrossShadowBoundaryRange
)
67 NS_IMPL_CYCLE_COLLECTION_CLASS(CrossShadowBoundaryRange
)
69 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CrossShadowBoundaryRange
,
71 if (tmp
->mCommonAncestor
) {
72 tmp
->mCommonAncestor
->RemoveMutationObserver(tmp
);
74 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommonAncestor
)
75 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
77 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CrossShadowBoundaryRange
,
79 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommonAncestor
)
80 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
82 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(CrossShadowBoundaryRange
,
84 NS_IMPL_CYCLE_COLLECTION_TRACE_END
87 template <typename SPT
, typename SRT
, typename EPT
, typename ERT
>
88 already_AddRefed
<CrossShadowBoundaryRange
> CrossShadowBoundaryRange::Create(
89 const RangeBoundaryBase
<SPT
, SRT
>& aStartBoundary
,
90 const RangeBoundaryBase
<EPT
, ERT
>& aEndBoundary
, nsRange
* aOwner
) {
91 RefPtr
<CrossShadowBoundaryRange
> range
;
92 if (!sCachedRanges
|| sCachedRanges
->IsEmpty()) {
93 range
= new CrossShadowBoundaryRange(aStartBoundary
.Container(), aOwner
);
95 range
= sCachedRanges
->PopLastElement().forget();
98 range
->Init(aStartBoundary
.Container());
99 range
->DoSetRange(aStartBoundary
, aEndBoundary
, nullptr, aOwner
);
100 return range
.forget();
103 template <typename SPT
, typename SRT
, typename EPT
, typename ERT
>
104 void CrossShadowBoundaryRange::DoSetRange(
105 const RangeBoundaryBase
<SPT
, SRT
>& aStartBoundary
,
106 const RangeBoundaryBase
<EPT
, ERT
>& aEndBoundary
, nsINode
* aRootNode
,
108 // aRootNode is useless to CrossShadowBoundaryRange because aStartBoundary
109 // and aEndBoundary could have different roots.
110 StaticRange::DoSetRange(aStartBoundary
, aEndBoundary
, nullptr);
112 nsINode
* startRoot
= RangeUtils::ComputeRootNode(mStart
.Container());
113 nsINode
* endRoot
= RangeUtils::ComputeRootNode(mEnd
.Container());
115 nsINode
* previousCommonAncestor
= mCommonAncestor
;
116 if (startRoot
== endRoot
) {
117 MOZ_ASSERT(!startRoot
&& !endRoot
);
119 // This should be the case when Release() is called.
120 mCommonAncestor
= startRoot
;
124 nsContentUtils::GetClosestCommonShadowIncludingInclusiveAncestor(
125 mStart
.Container(), mEnd
.Container());
126 MOZ_ASSERT_IF(mOwner
, mOwner
== aOwner
);
132 if (previousCommonAncestor
!= mCommonAncestor
) {
133 if (previousCommonAncestor
) {
134 previousCommonAncestor
->RemoveMutationObserver(this);
136 if (mCommonAncestor
) {
137 mCommonAncestor
->AddMutationObserver(this);
141 void CrossShadowBoundaryRange::ContentWillBeRemoved(nsIContent
* aChild
,
142 const BatchRemovalState
*) {
143 // It's unclear from the spec about what should the selection be after
144 // DOM mutation. See https://github.com/w3c/selection-api/issues/168
146 // For now, we just clear the selection if the removed node is related
147 // to mStart or mEnd.
148 MOZ_DIAGNOSTIC_ASSERT(mOwner
);
149 MOZ_DIAGNOSTIC_ASSERT(mOwner
->GetCrossShadowBoundaryRange() == this);
151 RefPtr
<CrossShadowBoundaryRange
> kungFuDeathGrip(this);
153 const nsINode
* startContainer
= mStart
.Container();
154 const nsINode
* endContainer
= mEnd
.Container();
156 if (startContainer
== aChild
|| endContainer
== aChild
) {
157 mOwner
->ResetCrossShadowBoundaryRange();
161 if (const auto* shadowRoot
= aChild
->GetShadowRoot()) {
162 if (startContainer
== shadowRoot
|| endContainer
== shadowRoot
) {
163 mOwner
->ResetCrossShadowBoundaryRange();
168 if (mStart
.Container()->IsShadowIncludingInclusiveDescendantOf(aChild
) ||
169 mEnd
.Container()->IsShadowIncludingInclusiveDescendantOf(aChild
)) {
170 mOwner
->ResetCrossShadowBoundaryRange();
174 nsINode
* container
= aChild
->GetParentNode();
176 auto MaybeCreateNewBoundary
=
178 const nsINode
* aContainer
,
179 const RangeBoundary
& aBoundary
) -> Maybe
<RawRangeBoundary
> {
180 if (container
== aContainer
) {
181 // We're only interested if our boundary reference was removed, otherwise
182 // we can just invalidate the offset.
183 if (aChild
== aBoundary
.Ref()) {
184 return Some
<RawRangeBoundary
>(
185 {container
, aChild
->GetPreviousSibling()});
187 RawRangeBoundary newBoundary
;
188 newBoundary
.CopyFrom(aBoundary
, RangeBoundaryIsMutationObserved::Yes
);
189 newBoundary
.InvalidateOffset();
190 return Some(newBoundary
);
195 const Maybe
<RawRangeBoundary
> newStartBoundary
=
196 MaybeCreateNewBoundary(startContainer
, mStart
);
197 const Maybe
<RawRangeBoundary
> newEndBoundary
=
198 MaybeCreateNewBoundary(endContainer
, mEnd
);
200 if (newStartBoundary
|| newEndBoundary
) {
201 SetStartAndEnd(newStartBoundary
? newStartBoundary
.ref() : mStart
.AsRaw(),
202 newEndBoundary
? newEndBoundary
.ref() : mEnd
.AsRaw());
206 // For now CrossShadowBoundaryRange::CharacterDataChanged is only meant
207 // to handle the character removal initiated by nsRange::CutContents.
208 void CrossShadowBoundaryRange::CharacterDataChanged(
209 nsIContent
* aContent
, const CharacterDataChangeInfo
& aInfo
) {
210 // When aInfo.mDetails is present, it means the character data was
211 // changed due to splitText() or normalize(), which shouldn't be the
212 // case for nsRange::CutContents, so we return early.
213 if (aInfo
.mDetails
) {
216 MOZ_ASSERT(aContent
);
217 MOZ_ASSERT(mIsPositioned
);
219 auto MaybeCreateNewBoundary
=
221 &aInfo
](const RangeBoundary
& aBoundary
) -> Maybe
<RawRangeBoundary
> {
222 // If the changed node contains our start boundary and the change starts
223 // before the boundary we'll need to adjust the offset.
224 if (aContent
== aBoundary
.Container() &&
225 // aInfo.mChangeStart is the offset where the change starts, if it's
226 // smaller than the offset of aBoundary, it means the characters
227 // before the selected content is changed (i.e, removed), so the
228 // offset of aBoundary needs to be adjusted.
231 RangeBoundary::OffsetFilter::kValidOrInvalidOffsets
)) {
232 RawRangeBoundary newStart
=
233 nsRange::ComputeNewBoundaryWhenBoundaryInsideChangedText(
234 aInfo
, aBoundary
.AsRaw());
235 return Some(newStart
);
240 const Maybe
<RawRangeBoundary
> newStartBoundary
=
241 MaybeCreateNewBoundary(mStart
);
242 const Maybe
<RawRangeBoundary
> newEndBoundary
= MaybeCreateNewBoundary(mEnd
);
244 if (newStartBoundary
|| newEndBoundary
) {
245 DoSetRange(newStartBoundary
? newStartBoundary
.ref() : mStart
.AsRaw(),
246 newEndBoundary
? newEndBoundary
.ref() : mEnd
.AsRaw(), nullptr,
251 // DOM mutation for shadow-crossing selection is not specified.
252 // Spec issue: https://github.com/w3c/selection-api/issues/168
253 void CrossShadowBoundaryRange::ParentChainChanged(nsIContent
* aContent
) {
254 MOZ_DIAGNOSTIC_ASSERT(mCommonAncestor
== aContent
,
255 "Wrong ParentChainChanged notification");
256 MOZ_DIAGNOSTIC_ASSERT(mOwner
);
257 mOwner
->ResetCrossShadowBoundaryRange();
259 } // namespace mozilla::dom