Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / base / RangeBoundary.h
blob4c5a70fb6a116c28c81b885467aace31b0236c07
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 #ifndef mozilla_RangeBoundary_h
8 #define mozilla_RangeBoundary_h
10 #include "nsCOMPtr.h"
11 #include "nsIContent.h"
12 #include "mozilla/dom/ShadowRoot.h"
13 #include "mozilla/Assertions.h"
14 #include "mozilla/Maybe.h"
16 class nsRange;
18 namespace mozilla {
20 template <typename T, typename U>
21 class EditorDOMPointBase;
23 // This class will maintain a reference to the child immediately
24 // before the boundary's offset. We try to avoid computing the
25 // offset as much as possible and just ensure mRef points to the
26 // correct child.
28 // mParent
29 // |
30 // [child0] [child1] [child2]
31 // / |
32 // mRef mOffset=2
34 // If mOffset == 0, mRef is null.
35 // For text nodes, mRef will always be null and the offset will
36 // be kept up-to-date.
38 template <typename ParentType, typename RefType>
39 class RangeBoundaryBase;
41 typedef RangeBoundaryBase<nsCOMPtr<nsINode>, nsCOMPtr<nsIContent>>
42 RangeBoundary;
43 typedef RangeBoundaryBase<nsINode*, nsIContent*> RawRangeBoundary;
45 /**
46 * There are two ways of ensuring that `mRef` points to the correct node.
47 * In most cases, the `RangeBoundary` is used by an object that is a
48 * `MutationObserver` (i.e. `nsRange`) and replaces its `RangeBoundary`
49 * objects when its parent chain changes.
50 * However, there are Ranges which are not `MutationObserver`s (i.e.
51 * `StaticRange`). `mRef` may become invalid when a DOM mutation happens.
52 * Therefore, it needs to be recomputed using `mOffset` before it is being
53 * accessed.
54 * Because recomputing / validating of `mRef` could be an expensive operation,
55 * it should be ensured that `Ref()` is called as few times as possible, i.e.
56 * only once per method of `RangeBoundaryBase`.
58 * Furthermore, there are special implications when the `RangeBoundary` is not
59 * used by an `MutationObserver`:
60 * After a DOM mutation, the Boundary may point to something that is not valid
61 * anymore, i.e. the `mOffset` is larger than `Container()->Length()`. In this
62 * case, `Ref()` and `Get*ChildAtOffset()` return `nullptr` as an indication
63 * that this RangeBoundary is not valid anymore. Also, `IsSetAndValid()`
64 * returns false. However, `IsSet()` will still return true.
67 enum class RangeBoundaryIsMutationObserved { No = 0, Yes = 1 };
69 // This class has two specializations, one using reference counting
70 // pointers and one using raw pointers. This helps us avoid unnecessary
71 // AddRef/Release calls.
72 template <typename ParentType, typename RefType>
73 class RangeBoundaryBase {
74 template <typename T, typename U>
75 friend class RangeBoundaryBase;
76 template <typename T, typename U>
77 friend class EditorDOMPointBase;
79 friend nsRange;
81 friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
82 RangeBoundary&, const char*,
83 uint32_t);
84 friend void ImplCycleCollectionUnlink(RangeBoundary&);
86 static const uint32_t kFallbackOffset = 0;
88 public:
89 RangeBoundaryBase(nsINode* aContainer, nsIContent* aRef)
90 : mParent(aContainer), mRef(aRef), mIsMutationObserved(true) {
91 if (mRef) {
92 NS_WARNING_ASSERTION(mRef->GetParentNode() == mParent,
93 "Initializing RangeBoundary with invalid value");
94 } else {
95 mOffset.emplace(0);
99 RangeBoundaryBase(nsINode* aContainer, uint32_t aOffset,
100 RangeBoundaryIsMutationObserved aRangeIsMutationObserver =
101 RangeBoundaryIsMutationObserved::Yes)
102 : mParent(aContainer),
103 mRef(nullptr),
104 mOffset(mozilla::Some(aOffset)),
105 mIsMutationObserved(bool(aRangeIsMutationObserver)) {
106 if (mIsMutationObserved && mParent && mParent->IsContainerNode()) {
107 // Find a reference node
108 if (aOffset == mParent->GetChildCount()) {
109 mRef = mParent->GetLastChild();
110 } else if (aOffset > 0) {
111 mRef = mParent->GetChildAt_Deprecated(aOffset - 1);
113 NS_WARNING_ASSERTION(mRef || aOffset == 0,
114 "Constructing RangeBoundary with invalid value");
116 NS_WARNING_ASSERTION(!mRef || mRef->GetParentNode() == mParent,
117 "Constructing RangeBoundary with invalid value");
120 RangeBoundaryBase()
121 : mParent(nullptr), mRef(nullptr), mIsMutationObserved(true) {}
123 // Needed for initializing RawRangeBoundary from an existing RangeBoundary.
124 template <typename PT, typename RT>
125 RangeBoundaryBase(const RangeBoundaryBase<PT, RT>& aOther,
126 RangeBoundaryIsMutationObserved aIsMutationObserved)
127 : mParent(aOther.mParent),
128 mRef(aOther.mRef),
129 mOffset(aOther.mOffset),
130 mIsMutationObserved(bool(aIsMutationObserved)) {}
133 * This method may return `nullptr` in two cases:
134 * 1. `mIsMutationObserved` is true and the boundary points to the first
135 * child of `mParent`.
136 * 2. `mIsMutationObserved` is false and `mOffset` is out of bounds for
137 * `mParent`s child list.
138 * If `mIsMutationObserved` is false, this method may do some significant
139 * computation. Therefore it is advised to call it as seldom as possible.
140 * Code inside of this class should call this method exactly one time and
141 * afterwards refer to `mRef` directly.
143 nsIContent* Ref() const {
144 if (mIsMutationObserved) {
145 return mRef;
147 MOZ_ASSERT(mParent);
148 MOZ_ASSERT(mOffset);
150 // `mRef` may have become invalid due to some DOM mutation,
151 // which is not monitored here. Therefore, we need to validate `mRef`
152 // manually.
153 if (*mOffset > Container()->Length()) {
154 // offset > child count means that the range boundary has become invalid
155 // due to a DOM mutation.
156 mRef = nullptr;
157 } else if (*mOffset == Container()->Length()) {
158 mRef = mParent->GetLastChild();
159 } else if (*mOffset) {
160 // validate and update `mRef`.
161 // If `ComputeIndexOf()` returns `Nothing`, then `mRef` is not a child of
162 // `mParent` anymore.
163 // If the returned index for `mRef` does not match to `mOffset`, `mRef`
164 // needs to be updated.
165 auto indexOfRefObject = mParent->ComputeIndexOf(mRef);
166 if (indexOfRefObject.isNothing() || *mOffset != *indexOfRefObject + 1) {
167 mRef = mParent->GetChildAt_Deprecated(*mOffset - 1);
169 } else {
170 mRef = nullptr;
172 return mRef;
175 nsINode* Container() const { return mParent; }
178 * This method may return `nullptr` if `mIsMutationObserved` is false and
179 * `mOffset` is out of bounds.
181 nsIContent* GetChildAtOffset() const {
182 if (!mParent || !mParent->IsContainerNode()) {
183 return nullptr;
185 nsIContent* const ref = Ref();
186 if (!ref) {
187 if (!mIsMutationObserved && *mOffset != 0) {
188 // This means that this boundary is invalid.
189 // `mOffset` is out of bounds.
190 return nullptr;
192 MOZ_ASSERT(*Offset(OffsetFilter::kValidOrInvalidOffsets) == 0,
193 "invalid RangeBoundary");
194 return mParent->GetFirstChild();
196 MOZ_ASSERT(mParent->GetChildAt_Deprecated(
197 *Offset(OffsetFilter::kValidOrInvalidOffsets)) ==
198 ref->GetNextSibling());
199 return ref->GetNextSibling();
203 * GetNextSiblingOfChildOffset() returns next sibling of a child at offset.
204 * If this refers after the last child or the container cannot have children,
205 * this returns nullptr with warning.
207 nsIContent* GetNextSiblingOfChildAtOffset() const {
208 if (NS_WARN_IF(!mParent) || NS_WARN_IF(!mParent->IsContainerNode())) {
209 return nullptr;
211 nsIContent* const ref = Ref();
212 if (!ref) {
213 if (!mIsMutationObserved && *mOffset != 0) {
214 // This means that this boundary is invalid.
215 // `mOffset` is out of bounds.
216 return nullptr;
218 MOZ_ASSERT(*Offset(OffsetFilter::kValidOffsets) == 0,
219 "invalid RangeBoundary");
220 nsIContent* firstChild = mParent->GetFirstChild();
221 if (NS_WARN_IF(!firstChild)) {
222 // Already referring the end of the container.
223 return nullptr;
225 return firstChild->GetNextSibling();
227 if (NS_WARN_IF(!ref->GetNextSibling())) {
228 // Already referring the end of the container.
229 return nullptr;
231 return ref->GetNextSibling()->GetNextSibling();
235 * GetPreviousSiblingOfChildAtOffset() returns previous sibling of a child
236 * at offset. If this refers the first child or the container cannot have
237 * children, this returns nullptr with warning.
239 nsIContent* GetPreviousSiblingOfChildAtOffset() const {
240 if (NS_WARN_IF(!mParent) || NS_WARN_IF(!mParent->IsContainerNode())) {
241 return nullptr;
243 nsIContent* const ref = Ref();
244 if (NS_WARN_IF(!ref)) {
245 // Already referring the start of the container.
246 return nullptr;
248 return ref;
251 enum class OffsetFilter { kValidOffsets, kValidOrInvalidOffsets };
254 * @return maybe an offset, depending on aOffsetFilter. If it is:
255 * kValidOffsets: if the offset is valid, it, Nothing{} otherwise.
256 * kValidOrInvalidOffsets: the internally stored offset, even if
257 * invalid, or if not available, a defined
258 * default value. That is, always some value.
260 Maybe<uint32_t> Offset(const OffsetFilter aOffsetFilter) const {
261 switch (aOffsetFilter) {
262 case OffsetFilter::kValidOffsets: {
263 if (IsSetAndValid()) {
264 MOZ_ASSERT_IF(!mIsMutationObserved, mOffset);
265 if (!mOffset && mIsMutationObserved) {
266 DetermineOffsetFromReference();
269 return !mIsMutationObserved && *mOffset > Container()->Length()
270 ? Nothing{}
271 : mOffset;
273 case OffsetFilter::kValidOrInvalidOffsets: {
274 MOZ_ASSERT_IF(!mIsMutationObserved, mOffset.isSome());
275 if (mOffset.isSome()) {
276 return mOffset;
278 if (mParent && mIsMutationObserved) {
279 DetermineOffsetFromReference();
280 if (mOffset.isSome()) {
281 return mOffset;
285 return Some(kFallbackOffset);
289 // Needed to calm the compiler. There was deliberately no default case added
290 // to the above switch-statement, because it would prevent build-errors when
291 // not all enumerators are handled.
292 MOZ_ASSERT_UNREACHABLE();
293 return Some(kFallbackOffset);
296 friend std::ostream& operator<<(
297 std::ostream& aStream,
298 const RangeBoundaryBase<ParentType, RefType>& aRangeBoundary) {
299 aStream << "{ mParent=" << aRangeBoundary.Container();
300 if (aRangeBoundary.Container()) {
301 aStream << " (" << *aRangeBoundary.Container()
302 << ", Length()=" << aRangeBoundary.Container()->Length() << ")";
304 if (aRangeBoundary.mIsMutationObserved) {
305 aStream << ", mRef=" << aRangeBoundary.mRef;
306 if (aRangeBoundary.mRef) {
307 aStream << " (" << *aRangeBoundary.mRef << ")";
311 aStream << ", mOffset=" << aRangeBoundary.mOffset;
312 aStream << ", mIsMutationObserved="
313 << (aRangeBoundary.mIsMutationObserved ? "true" : "false") << " }";
314 return aStream;
317 private:
318 void DetermineOffsetFromReference() const {
319 MOZ_ASSERT(mParent);
320 MOZ_ASSERT(mRef);
321 MOZ_ASSERT(mRef->GetParentNode() == mParent);
322 MOZ_ASSERT(mIsMutationObserved);
323 MOZ_ASSERT(mOffset.isNothing());
325 if (mRef->IsBeingRemoved()) {
326 // ComputeIndexOf would return nothing because mRef has already been
327 // removed from the child node chain of mParent.
328 return;
331 const Maybe<uint32_t> index = mParent->ComputeIndexOf(mRef);
332 MOZ_ASSERT(*index != UINT32_MAX);
333 mOffset.emplace(MOZ_LIKELY(index.isSome()) ? *index + 1u : 0u);
336 void InvalidateOffset() {
337 MOZ_ASSERT(mParent);
338 MOZ_ASSERT(mParent->IsContainerNode(),
339 "Range is positioned on a text node!");
340 if (!mIsMutationObserved) {
341 // RangeBoundaries that are not used in the context of a
342 // `MutationObserver` use the offset as main source of truth to compute
343 // `mRef`. Therefore, it must not be updated or invalidated.
344 return;
346 if (!mRef) {
347 MOZ_ASSERT(mOffset.isSome() && mOffset.value() == 0,
348 "Invalidating offset of invalid RangeBoundary?");
349 return;
351 mOffset.reset();
354 public:
355 void NotifyParentBecomesShadowHost() {
356 MOZ_ASSERT(mParent);
357 MOZ_ASSERT(mParent->IsContainerNode(),
358 "Range is positioned on a text node!");
359 if (!StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) {
360 return;
363 if (!mIsMutationObserved) {
364 // RangeBoundaries that are not used in the context of a
365 // `MutationObserver` use the offset as main source of truth to compute
366 // `mRef`. Therefore, it must not be updated or invalidated.
367 return;
370 if (!mRef) {
371 MOZ_ASSERT(mOffset.isSome() && mOffset.value() == 0,
372 "Invalidating offset of invalid RangeBoundary?");
373 return;
376 if (dom::ShadowRoot* shadowRoot = mParent->GetShadowRootForSelection()) {
377 mParent = shadowRoot;
380 mOffset = Some(0);
383 bool IsSet() const { return mParent && (mRef || mOffset.isSome()); }
385 bool IsSetAndValid() const {
386 if (!IsSet()) {
387 return false;
390 if (mIsMutationObserved && Ref()) {
391 // XXX mRef refers previous sibling of pointing child. Therefore, it
392 // seems odd that this becomes invalid due to its removal. Should we
393 // change RangeBoundaryBase to refer child at offset directly?
394 return Ref()->GetParentNode() == Container() && !Ref()->IsBeingRemoved();
397 MOZ_ASSERT(mOffset.isSome());
398 return *mOffset <= Container()->Length();
401 bool IsStartOfContainer() const {
402 // We're at the first point in the container if we don't have a reference,
403 // and our offset is 0. If we don't have a Ref, we should already have an
404 // offset, so we can just directly fetch it.
405 return mIsMutationObserved ? !Ref() && mOffset.value() == 0
406 : mOffset.value() == 0;
409 bool IsEndOfContainer() const {
410 // We're at the last point in the container if Ref is a pointer to the last
411 // child in Container(), or our Offset() is the same as the length of our
412 // container. If we don't have a Ref, then we should already have an offset,
413 // so we can just directly fetch it.
414 return mIsMutationObserved && Ref()
415 ? !Ref()->GetNextSibling()
416 : mOffset.value() == Container()->Length();
419 // Convenience methods for switching between the two types
420 // of RangeBoundary.
421 RangeBoundaryBase<nsINode*, nsIContent*> AsRaw() const {
422 return RangeBoundaryBase<nsINode*, nsIContent*>(
423 *this, RangeBoundaryIsMutationObserved(mIsMutationObserved));
426 template <typename A, typename B>
427 RangeBoundaryBase& operator=(const RangeBoundaryBase<A, B>& aOther) = delete;
429 template <typename A, typename B>
430 RangeBoundaryBase& CopyFrom(
431 const RangeBoundaryBase<A, B>& aOther,
432 RangeBoundaryIsMutationObserved aIsMutationObserved) {
433 // mParent and mRef can be strong pointers, so better to try to avoid any
434 // extra AddRef/Release calls.
435 if (mParent != aOther.mParent) {
436 mParent = aOther.mParent;
438 if (mRef != aOther.mRef) {
439 mRef = aOther.mRef;
441 mOffset = aOther.mOffset;
442 mIsMutationObserved = bool(aIsMutationObserved);
443 return *this;
446 bool Equals(const nsINode* aNode, uint32_t aOffset) const {
447 if (mParent != aNode) {
448 return false;
451 const Maybe<uint32_t> offset = Offset(OffsetFilter::kValidOffsets);
452 return offset && (*offset == aOffset);
455 template <typename A, typename B>
456 bool operator==(const RangeBoundaryBase<A, B>& aOther) const {
457 return mParent == aOther.mParent &&
458 (mIsMutationObserved && aOther.mIsMutationObserved && mRef
459 ? mRef == aOther.mRef
460 : Offset(OffsetFilter::kValidOrInvalidOffsets) ==
461 aOther.Offset(
462 RangeBoundaryBase<
463 A, B>::OffsetFilter::kValidOrInvalidOffsets));
466 template <typename A, typename B>
467 bool operator!=(const RangeBoundaryBase<A, B>& aOther) const {
468 return !(*this == aOther);
471 private:
472 ParentType mParent;
473 mutable RefType mRef;
475 mutable mozilla::Maybe<uint32_t> mOffset;
476 bool mIsMutationObserved;
479 template <typename ParentType, typename RefType>
480 const uint32_t RangeBoundaryBase<ParentType, RefType>::kFallbackOffset;
482 inline void ImplCycleCollectionUnlink(RangeBoundary& aField) {
483 ImplCycleCollectionUnlink(aField.mParent);
484 ImplCycleCollectionUnlink(aField.mRef);
487 inline void ImplCycleCollectionTraverse(
488 nsCycleCollectionTraversalCallback& aCallback, RangeBoundary& aField,
489 const char* aName, uint32_t aFlags) {
490 ImplCycleCollectionTraverse(aCallback, aField.mParent, "mParent", 0);
491 ImplCycleCollectionTraverse(aCallback, aField.mRef, "mRef", 0);
494 } // namespace mozilla
496 #endif // defined(mozilla_RangeBoundary_h)