Bug 1835710 - Cancel off-thread JIT compilation before changing nursery allocation...
[gecko.git] / dom / base / RangeBoundary.h
blob2e4ab423973164cf21bb65159d8e71e2dff0cdb9
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/Assertions.h"
13 #include "mozilla/Maybe.h"
15 class nsRange;
17 namespace mozilla {
19 template <typename T, typename U>
20 class EditorDOMPointBase;
22 // This class will maintain a reference to the child immediately
23 // before the boundary's offset. We try to avoid computing the
24 // offset as much as possible and just ensure mRef points to the
25 // correct child.
27 // mParent
28 // |
29 // [child0] [child1] [child2]
30 // / |
31 // mRef mOffset=2
33 // If mOffset == 0, mRef is null.
34 // For text nodes, mRef will always be null and the offset will
35 // be kept up-to-date.
37 template <typename ParentType, typename RefType>
38 class RangeBoundaryBase;
40 typedef RangeBoundaryBase<nsCOMPtr<nsINode>, nsCOMPtr<nsIContent>>
41 RangeBoundary;
42 typedef RangeBoundaryBase<nsINode*, nsIContent*> RawRangeBoundary;
44 /**
45 * There are two ways of ensuring that `mRef` points to the correct node.
46 * In most cases, the `RangeBoundary` is used by an object that is a
47 * `MutationObserver` (i.e. `nsRange`) and replaces its `RangeBoundary`
48 * objects when its parent chain changes.
49 * However, there are Ranges which are not `MutationObserver`s (i.e.
50 * `StaticRange`). `mRef` may become invalid when a DOM mutation happens.
51 * Therefore, it needs to be recomputed using `mOffset` before it is being
52 * accessed.
53 * Because recomputing / validating of `mRef` could be an expensive operation,
54 * it should be ensured that `Ref()` is called as few times as possible, i.e.
55 * only once per method of `RangeBoundaryBase`.
57 * Furthermore, there are special implications when the `RangeBoundary` is not
58 * used by an `MutationObserver`:
59 * After a DOM mutation, the Boundary may point to something that is not valid
60 * anymore, i.e. the `mOffset` is larger than `Container()->Length()`. In this
61 * case, `Ref()` and `Get*ChildAtOffset()` return `nullptr` as an indication
62 * that this RangeBoundary is not valid anymore. Also, `IsSetAndValid()`
63 * returns false. However, `IsSet()` will still return true.
66 enum class RangeBoundaryIsMutationObserved { No = 0, Yes = 1 };
68 // This class has two specializations, one using reference counting
69 // pointers and one using raw pointers. This helps us avoid unnecessary
70 // AddRef/Release calls.
71 template <typename ParentType, typename RefType>
72 class RangeBoundaryBase {
73 template <typename T, typename U>
74 friend class RangeBoundaryBase;
75 template <typename T, typename U>
76 friend class EditorDOMPointBase;
78 friend nsRange;
80 friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
81 RangeBoundary&, const char*,
82 uint32_t);
83 friend void ImplCycleCollectionUnlink(RangeBoundary&);
85 static const uint32_t kFallbackOffset = 0;
87 public:
88 RangeBoundaryBase(nsINode* aContainer, nsIContent* aRef)
89 : mParent(aContainer), mRef(aRef), mIsMutationObserved(true) {
90 if (mRef) {
91 NS_WARNING_ASSERTION(mRef->GetParentNode() == mParent,
92 "Initializing RangeBoundary with invalid value");
93 } else {
94 mOffset.emplace(0);
98 RangeBoundaryBase(nsINode* aContainer, uint32_t aOffset,
99 RangeBoundaryIsMutationObserved aRangeIsMutationObserver =
100 RangeBoundaryIsMutationObserved::Yes)
101 : mParent(aContainer),
102 mRef(nullptr),
103 mOffset(mozilla::Some(aOffset)),
104 mIsMutationObserved(bool(aRangeIsMutationObserver)) {
105 if (mIsMutationObserved && mParent && mParent->IsContainerNode()) {
106 // Find a reference node
107 if (aOffset == mParent->GetChildCount()) {
108 mRef = mParent->GetLastChild();
109 } else if (aOffset > 0) {
110 mRef = mParent->GetChildAt_Deprecated(aOffset - 1);
112 NS_WARNING_ASSERTION(mRef || aOffset == 0,
113 "Constructing RangeBoundary with invalid value");
115 NS_WARNING_ASSERTION(!mRef || mRef->GetParentNode() == mParent,
116 "Constructing RangeBoundary with invalid value");
119 RangeBoundaryBase()
120 : mParent(nullptr), mRef(nullptr), mIsMutationObserved(true) {}
122 // Needed for initializing RawRangeBoundary from an existing RangeBoundary.
123 template <typename PT, typename RT>
124 RangeBoundaryBase(const RangeBoundaryBase<PT, RT>& aOther,
125 RangeBoundaryIsMutationObserved aIsMutationObserved)
126 : mParent(aOther.mParent),
127 mRef(aOther.mRef),
128 mOffset(aOther.mOffset),
129 mIsMutationObserved(bool(aIsMutationObserved)) {}
132 * This method may return `nullptr` in two cases:
133 * 1. `mIsMutationObserved` is true and the boundary points to the first
134 * child of `mParent`.
135 * 2. `mIsMutationObserved` is false and `mOffset` is out of bounds for
136 * `mParent`s child list.
137 * If `mIsMutationObserved` is false, this method may do some significant
138 * computation. Therefore it is advised to call it as seldom as possible.
139 * Code inside of this class should call this method exactly one time and
140 * afterwards refer to `mRef` directly.
142 nsIContent* Ref() const {
143 if (mIsMutationObserved) {
144 return mRef;
146 MOZ_ASSERT(mParent);
147 MOZ_ASSERT(mOffset);
149 // `mRef` may have become invalid due to some DOM mutation,
150 // which is not monitored here. Therefore, we need to validate `mRef`
151 // manually.
152 if (*mOffset > Container()->Length()) {
153 // offset > child count means that the range boundary has become invalid
154 // due to a DOM mutation.
155 mRef = nullptr;
156 } else if (*mOffset == Container()->Length()) {
157 mRef = mParent->GetLastChild();
158 } else if (*mOffset) {
159 // validate and update `mRef`.
160 // If `ComputeIndexOf()` returns `Nothing`, then `mRef` is not a child of
161 // `mParent` anymore.
162 // If the returned index for `mRef` does not match to `mOffset`, `mRef`
163 // needs to be updated.
164 auto indexOfRefObject = mParent->ComputeIndexOf(mRef);
165 if (indexOfRefObject.isNothing() || *mOffset != *indexOfRefObject + 1) {
166 mRef = mParent->GetChildAt_Deprecated(*mOffset - 1);
168 } else {
169 mRef = nullptr;
171 return mRef;
174 nsINode* Container() const { return mParent; }
177 * This method may return `nullptr` if `mIsMutationObserved` is false and
178 * `mOffset` is out of bounds.
180 nsIContent* GetChildAtOffset() const {
181 if (!mParent || !mParent->IsContainerNode()) {
182 return nullptr;
184 nsIContent* const ref = Ref();
185 if (!ref) {
186 if (!mIsMutationObserved && *mOffset != 0) {
187 // This means that this boundary is invalid.
188 // `mOffset` is out of bounds.
189 return nullptr;
191 MOZ_ASSERT(*Offset(OffsetFilter::kValidOrInvalidOffsets) == 0,
192 "invalid RangeBoundary");
193 return mParent->GetFirstChild();
195 MOZ_ASSERT(mParent->GetChildAt_Deprecated(
196 *Offset(OffsetFilter::kValidOrInvalidOffsets)) ==
197 ref->GetNextSibling());
198 return ref->GetNextSibling();
202 * GetNextSiblingOfChildOffset() returns next sibling of a child at offset.
203 * If this refers after the last child or the container cannot have children,
204 * this returns nullptr with warning.
206 nsIContent* GetNextSiblingOfChildAtOffset() const {
207 if (NS_WARN_IF(!mParent) || NS_WARN_IF(!mParent->IsContainerNode())) {
208 return nullptr;
210 nsIContent* const ref = Ref();
211 if (!ref) {
212 if (!mIsMutationObserved && *mOffset != 0) {
213 // This means that this boundary is invalid.
214 // `mOffset` is out of bounds.
215 return nullptr;
217 MOZ_ASSERT(*Offset(OffsetFilter::kValidOffsets) == 0,
218 "invalid RangeBoundary");
219 nsIContent* firstChild = mParent->GetFirstChild();
220 if (NS_WARN_IF(!firstChild)) {
221 // Already referring the end of the container.
222 return nullptr;
224 return firstChild->GetNextSibling();
226 if (NS_WARN_IF(!ref->GetNextSibling())) {
227 // Already referring the end of the container.
228 return nullptr;
230 return ref->GetNextSibling()->GetNextSibling();
234 * GetPreviousSiblingOfChildAtOffset() returns previous sibling of a child
235 * at offset. If this refers the first child or the container cannot have
236 * children, this returns nullptr with warning.
238 nsIContent* GetPreviousSiblingOfChildAtOffset() const {
239 if (NS_WARN_IF(!mParent) || NS_WARN_IF(!mParent->IsContainerNode())) {
240 return nullptr;
242 nsIContent* const ref = Ref();
243 if (NS_WARN_IF(!ref)) {
244 // Already referring the start of the container.
245 return nullptr;
247 return ref;
250 enum class OffsetFilter { kValidOffsets, kValidOrInvalidOffsets };
253 * @return maybe an offset, depending on aOffsetFilter. If it is:
254 * kValidOffsets: if the offset is valid, it, Nothing{} otherwise.
255 * kValidOrInvalidOffsets: the internally stored offset, even if
256 * invalid, or if not available, a defined
257 * default value. That is, always some value.
259 Maybe<uint32_t> Offset(const OffsetFilter aOffsetFilter) const {
260 switch (aOffsetFilter) {
261 case OffsetFilter::kValidOffsets: {
262 if (IsSetAndValid()) {
263 MOZ_ASSERT_IF(!mIsMutationObserved, mOffset);
264 if (!mOffset && mIsMutationObserved) {
265 DetermineOffsetFromReference();
268 return !mIsMutationObserved && *mOffset > Container()->Length()
269 ? Nothing{}
270 : mOffset;
272 case OffsetFilter::kValidOrInvalidOffsets: {
273 MOZ_ASSERT_IF(!mIsMutationObserved, mOffset.isSome());
274 if (mOffset.isSome()) {
275 return mOffset;
277 if (mParent && mIsMutationObserved) {
278 DetermineOffsetFromReference();
279 if (mOffset.isSome()) {
280 return mOffset;
284 return Some(kFallbackOffset);
288 // Needed to calm the compiler. There was deliberately no default case added
289 // to the above switch-statement, because it would prevent build-errors when
290 // not all enumerators are handled.
291 MOZ_ASSERT_UNREACHABLE();
292 return Some(kFallbackOffset);
295 friend std::ostream& operator<<(
296 std::ostream& aStream,
297 const RangeBoundaryBase<ParentType, RefType>& aRangeBoundary) {
298 aStream << "{ mParent=" << aRangeBoundary.Container();
299 if (aRangeBoundary.Container()) {
300 aStream << " (" << *aRangeBoundary.Container()
301 << ", Length()=" << aRangeBoundary.Container()->Length() << ")";
303 if (aRangeBoundary.mIsMutationObserved) {
304 aStream << ", mRef=" << aRangeBoundary.mRef;
305 if (aRangeBoundary.mRef) {
306 aStream << " (" << *aRangeBoundary.mRef << ")";
310 aStream << ", mOffset=" << aRangeBoundary.mOffset;
311 aStream << ", mIsMutationObserved="
312 << (aRangeBoundary.mIsMutationObserved ? "true" : "false") << " }";
313 return aStream;
316 private:
317 void DetermineOffsetFromReference() const {
318 MOZ_ASSERT(mParent);
319 MOZ_ASSERT(mRef);
320 MOZ_ASSERT(mRef->GetParentNode() == mParent);
321 MOZ_ASSERT(mIsMutationObserved);
322 MOZ_ASSERT(mOffset.isNothing());
324 if (mRef->IsBeingRemoved()) {
325 // ComputeIndexOf would return nothing because mRef has already been
326 // removed from the child node chain of mParent.
327 return;
330 const Maybe<uint32_t> index = mParent->ComputeIndexOf(mRef);
331 MOZ_ASSERT(*index != UINT32_MAX);
332 mOffset.emplace(MOZ_LIKELY(index.isSome()) ? *index + 1u : 0u);
335 void InvalidateOffset() {
336 MOZ_ASSERT(mParent);
337 MOZ_ASSERT(mParent->IsContainerNode(),
338 "Range is positioned on a text node!");
339 if (!mIsMutationObserved) {
340 // RangeBoundaries that are not used in the context of a
341 // `MutationObserver` use the offset as main source of truth to compute
342 // `mRef`. Therefore, it must not be updated or invalidated.
343 return;
345 if (!mRef) {
346 MOZ_ASSERT(mOffset.isSome() && mOffset.value() == 0,
347 "Invalidating offset of invalid RangeBoundary?");
348 return;
350 mOffset.reset();
353 public:
354 bool IsSet() const { return mParent && (mRef || mOffset.isSome()); }
356 bool IsSetAndValid() const {
357 if (!IsSet()) {
358 return false;
361 if (mIsMutationObserved && Ref()) {
362 // XXX mRef refers previous sibling of pointing child. Therefore, it
363 // seems odd that this becomes invalid due to its removal. Should we
364 // change RangeBoundaryBase to refer child at offset directly?
365 return Ref()->GetParentNode() == Container() && !Ref()->IsBeingRemoved();
368 MOZ_ASSERT(mOffset.isSome());
369 return *mOffset <= Container()->Length();
372 bool IsStartOfContainer() const {
373 // We're at the first point in the container if we don't have a reference,
374 // and our offset is 0. If we don't have a Ref, we should already have an
375 // offset, so we can just directly fetch it.
376 return mIsMutationObserved ? !Ref() && mOffset.value() == 0
377 : mOffset.value() == 0;
380 bool IsEndOfContainer() const {
381 // We're at the last point in the container if Ref is a pointer to the last
382 // child in Container(), or our Offset() is the same as the length of our
383 // container. If we don't have a Ref, then we should already have an offset,
384 // so we can just directly fetch it.
385 return mIsMutationObserved && Ref()
386 ? !Ref()->GetNextSibling()
387 : mOffset.value() == Container()->Length();
390 // Convenience methods for switching between the two types
391 // of RangeBoundary.
392 RangeBoundaryBase<nsINode*, nsIContent*> AsRaw() const {
393 return RangeBoundaryBase<nsINode*, nsIContent*>(
394 *this, RangeBoundaryIsMutationObserved(mIsMutationObserved));
397 template <typename A, typename B>
398 RangeBoundaryBase& operator=(const RangeBoundaryBase<A, B>& aOther) = delete;
400 template <typename A, typename B>
401 RangeBoundaryBase& CopyFrom(
402 const RangeBoundaryBase<A, B>& aOther,
403 RangeBoundaryIsMutationObserved aIsMutationObserved) {
404 // mParent and mRef can be strong pointers, so better to try to avoid any
405 // extra AddRef/Release calls.
406 if (mParent != aOther.mParent) {
407 mParent = aOther.mParent;
409 if (mRef != aOther.mRef) {
410 mRef = aOther.mRef;
412 mOffset = aOther.mOffset;
413 mIsMutationObserved = bool(aIsMutationObserved);
414 return *this;
417 bool Equals(const nsINode* aNode, uint32_t aOffset) const {
418 if (mParent != aNode) {
419 return false;
422 const Maybe<uint32_t> offset = Offset(OffsetFilter::kValidOffsets);
423 return offset && (*offset == aOffset);
426 template <typename A, typename B>
427 bool operator==(const RangeBoundaryBase<A, B>& aOther) const {
428 return mParent == aOther.mParent &&
429 (mIsMutationObserved && aOther.mIsMutationObserved && mRef
430 ? mRef == aOther.mRef
431 : Offset(OffsetFilter::kValidOrInvalidOffsets) ==
432 aOther.Offset(
433 RangeBoundaryBase<
434 A, B>::OffsetFilter::kValidOrInvalidOffsets));
437 template <typename A, typename B>
438 bool operator!=(const RangeBoundaryBase<A, B>& aOther) const {
439 return !(*this == aOther);
442 private:
443 ParentType mParent;
444 mutable RefType mRef;
446 mutable mozilla::Maybe<uint32_t> mOffset;
447 bool mIsMutationObserved;
450 template <typename ParentType, typename RefType>
451 const uint32_t RangeBoundaryBase<ParentType, RefType>::kFallbackOffset;
453 inline void ImplCycleCollectionUnlink(RangeBoundary& aField) {
454 ImplCycleCollectionUnlink(aField.mParent);
455 ImplCycleCollectionUnlink(aField.mRef);
458 inline void ImplCycleCollectionTraverse(
459 nsCycleCollectionTraversalCallback& aCallback, RangeBoundary& aField,
460 const char* aName, uint32_t aFlags) {
461 ImplCycleCollectionTraverse(aCallback, aField.mParent, "mParent", 0);
462 ImplCycleCollectionTraverse(aCallback, aField.mRef, "mRef", 0);
465 } // namespace mozilla
467 #endif // defined(mozilla_RangeBoundary_h)