RangeBoundary.h (27225B)
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/. */ 6 7 #ifndef mozilla_RangeBoundary_h 8 #define mozilla_RangeBoundary_h 9 10 #include "mozilla/Assertions.h" 11 #include "mozilla/Maybe.h" 12 #include "mozilla/StaticPrefs_dom.h" 13 #include "mozilla/dom/HTMLSlotElement.h" 14 #include "mozilla/dom/ShadowRoot.h" 15 #include "nsCOMPtr.h" 16 #include "nsIContent.h" 17 18 class nsRange; 19 20 namespace mozilla { 21 namespace dom { 22 class CrossShadowBoundaryRange; 23 } 24 25 template <typename T, typename U> 26 class EditorDOMPointBase; 27 28 // This class will maintain a reference to the child immediately 29 // before the boundary's offset. We try to avoid computing the 30 // offset as much as possible and just ensure mRef points to the 31 // correct child. 32 // 33 // mParent 34 // | 35 // [child0] [child1] [child2] 36 // / | 37 // mRef mOffset=2 38 // 39 // If mOffset == 0, mRef is null. 40 // For text nodes, mRef will always be null and the offset will 41 // be kept up-to-date. 42 // 43 // One special case is when mTreeKind is TreeKind::Flat and 44 // mParent is slot and slot has assigned nodes, it'll use 45 // the assigned nodes to determine the reference with the 46 // same idea as above. 47 // 48 // Users of RangeBoundary should be extra careful about comparing 49 // range boundaries with different kinds, as it tends to lead to 50 // unexpected results. 51 52 template <typename ParentType, typename RefType> 53 class RangeBoundaryBase; 54 55 using RangeBoundary = 56 RangeBoundaryBase<nsCOMPtr<nsINode>, nsCOMPtr<nsIContent>>; 57 using RawRangeBoundary = RangeBoundaryBase<nsINode*, nsIContent*>; 58 using ConstRawRangeBoundary = 59 RangeBoundaryBase<const nsINode*, const nsIContent*>; 60 61 /** 62 * There are two ways of ensuring that `mRef` points to the correct node. 63 * In most cases, the `RangeBoundary` is used by an object that is a 64 * `MutationObserver` (i.e. `nsRange`) and replaces its `RangeBoundary` 65 * objects when its parent chain changes. 66 * However, there are Ranges which are not `MutationObserver`s (i.e. 67 * `StaticRange`). `mRef` may become invalid when a DOM mutation happens. 68 * Therefore, it needs to be recomputed using `mOffset` before it is being 69 * accessed. 70 * Because recomputing / validating of `mRef` could be an expensive operation, 71 * it should be ensured that `Ref()` is called as few times as possible, i.e. 72 * only once per method of `RangeBoundaryBase`. 73 * 74 * Furthermore, there are special implications when the `RangeBoundary` is not 75 * used by an `MutationObserver`: 76 * After a DOM mutation, the Boundary may point to something that is not valid 77 * anymore, i.e. the `mOffset` is larger than `Container()->Length()`. In this 78 * case, `Ref()` and `Get*ChildAtOffset()` return `nullptr` as an indication 79 * that this RangeBoundary is not valid anymore. Also, `IsSetAndValid()` 80 * returns false. However, `IsSet()` will still return true. 81 * 82 */ 83 enum class RangeBoundaryIsMutationObserved { No = 0, Yes = 1 }; 84 85 // This class has two types of specializations, one using reference counting 86 // pointers and one using raw pointers (both non-const and const versions). The 87 // latter help us avoid unnecessary AddRef/Release calls. 88 template <typename ParentType, typename RefType> 89 class RangeBoundaryBase { 90 template <typename T, typename U> 91 friend class RangeBoundaryBase; 92 template <typename T, typename U> 93 friend class EditorDOMPointBase; 94 95 friend nsRange; 96 97 friend class mozilla::dom::CrossShadowBoundaryRange; 98 99 friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&, 100 RangeBoundary&, const char*, 101 uint32_t); 102 friend void ImplCycleCollectionUnlink(RangeBoundary&); 103 104 static const uint32_t kFallbackOffset = 0; 105 106 template <typename T, typename Enable = void> 107 struct GetNodeType; 108 template <typename T> 109 struct GetNodeType<T, std::enable_if_t<std::is_pointer_v<T>>> { 110 using type = std::remove_pointer_t<T>; 111 }; 112 template <typename T> 113 struct GetNodeType<T, std::enable_if_t<!std::is_pointer_v<T>>> { 114 using type = typename T::element_type; 115 }; 116 117 public: 118 using RawParentType = typename GetNodeType<ParentType>::type; 119 static_assert(std::is_same_v<RawParentType, nsINode> || 120 std::is_same_v<RawParentType, const nsINode>); 121 using RawRefType = typename GetNodeType<RefType>::type; 122 static_assert(std::is_same_v<RawRefType, nsIContent> || 123 std::is_same_v<RawRefType, const nsIContent>); 124 125 RangeBoundaryBase(RawParentType* aContainer, RawRefType* aRef, 126 TreeKind aTreeKind = TreeKind::DOM) 127 : mParent(aContainer), 128 mRef(aRef), 129 mIsMutationObserved(true), 130 mTreeKind(aTreeKind) { 131 MOZ_ASSERT( 132 aTreeKind == TreeKind::DOM || aTreeKind == TreeKind::Flat, 133 "Only TreeKind::DOM and TreeKind::Flat are valid at the moment."); 134 if (mRef) { 135 NS_WARNING_ASSERTION(IsValidParent(mParent, mRef), 136 "Initializing RangeBoundary with invalid value"); 137 } else { 138 mOffset.emplace(0); 139 } 140 } 141 142 RangeBoundaryBase(RawParentType* aContainer, uint32_t aOffset, 143 RangeBoundaryIsMutationObserved aRangeIsMutationObserver = 144 RangeBoundaryIsMutationObserved::Yes, 145 TreeKind aTreeKind = TreeKind::DOM) 146 : mParent(aContainer), 147 mRef(nullptr), 148 mOffset(mozilla::Some(aOffset)), 149 mIsMutationObserved(bool(aRangeIsMutationObserver)), 150 mTreeKind(aTreeKind) { 151 MOZ_ASSERT( 152 aTreeKind == TreeKind::DOM || aTreeKind == TreeKind::Flat, 153 "Only TreeKind::DOM and TreeKind::Flat are valid at the moment."); 154 if (mIsMutationObserved && mParent && mParent->IsContainerNode()) { 155 // Find a reference node 156 if (aOffset == GetLength(mParent)) { 157 mRef = GetLastChild(mParent); 158 } else if (aOffset > 0) { 159 mRef = GetChildAt(mParent, aOffset - 1); 160 } 161 NS_WARNING_ASSERTION(mRef || aOffset == 0, 162 "Constructing RangeBoundary with invalid value"); 163 } 164 NS_WARNING_ASSERTION(!mRef || IsValidParent(mParent, mRef), 165 "Constructing RangeBoundary with invalid value"); 166 } 167 168 [[nodiscard]] TreeKind GetTreeKind() const { return mTreeKind; } 169 170 RangeBoundaryBase AsRangeBoundaryInFlatTree() const { 171 if (mOffset) { 172 if (mTreeKind == TreeKind::Flat) { 173 MOZ_ASSERT_IF(IsSet(), IsSetAndValid()); 174 return RangeBoundaryBase( 175 mParent, mRef, *mOffset, 176 RangeBoundaryIsMutationObserved(mIsMutationObserved), 177 TreeKind::Flat); 178 } 179 // We don't assert IsSetAndValid() here because it's possible 180 // that a RangeBoundary is not valid for TreeKind::DOM but valid 181 // for TreeKind::Flat. 182 return RangeBoundaryBase( 183 mParent, *mOffset, 184 RangeBoundaryIsMutationObserved(mIsMutationObserved), TreeKind::Flat); 185 } 186 MOZ_ASSERT_IF(IsSet(), IsSetAndValid()); 187 return RangeBoundaryBase(mParent, mRef, TreeKind::Flat); 188 } 189 190 /** 191 * Special constructor to create RangeBoundaryBase which stores both mRef 192 * and mOffset. This can make the instance provide both mRef and mOffset 193 * without computation, but the creator needs to guarantee that this is 194 * valid at least at construction. 195 */ 196 RangeBoundaryBase(RawParentType* aContainer, RawRefType* aRef, 197 uint32_t aOffset, 198 RangeBoundaryIsMutationObserved aRangeIsMutationObserver = 199 RangeBoundaryIsMutationObserved::Yes, 200 TreeKind aTreeKind = TreeKind::DOM) 201 : mParent(const_cast<nsINode*>(aContainer)), 202 mRef(const_cast<nsIContent*>(aRef)), 203 mOffset(mozilla::Some(aOffset)), 204 mIsMutationObserved(bool(aRangeIsMutationObserver)), 205 mTreeKind(aTreeKind) { 206 MOZ_ASSERT(IsSetAndValid()); 207 } 208 209 explicit RangeBoundaryBase(TreeKind aTreeKind = TreeKind::DOM) 210 : mParent(nullptr), 211 mRef(nullptr), 212 mIsMutationObserved(true), 213 mTreeKind(aTreeKind) {} 214 215 // Convert from RawRangeBoundary or RangeBoundary. 216 template <typename PT, typename RT, 217 typename = std::enable_if_t<!std::is_const_v<RawParentType> || 218 std::is_const_v<PT>>> 219 RangeBoundaryBase(const RangeBoundaryBase<PT, RT>& aOther, 220 RangeBoundaryIsMutationObserved aIsMutationObserved) 221 : mParent(aOther.mParent), 222 mRef(aOther.mRef), 223 mOffset(aOther.mOffset), 224 mIsMutationObserved(bool(aIsMutationObserved)), 225 mTreeKind(aOther.mTreeKind) {} 226 227 /** 228 * This method may return `nullptr` in two cases: 229 * 1. `mIsMutationObserved` is true and the boundary points to the first 230 * child of `mParent`. 231 * 2. `mIsMutationObserved` is false and `mOffset` is out of bounds for 232 * `mParent`s child list. 233 * If `mIsMutationObserved` is false, this method may do some significant 234 * computation. Therefore it is advised to call it as seldom as possible. 235 * Code inside of this class should call this method exactly one time and 236 * afterwards refer to `mRef` directly. 237 */ 238 RawRefType* Ref() const { 239 if (mIsMutationObserved) { 240 return mRef; 241 } 242 MOZ_ASSERT(mParent); 243 MOZ_ASSERT(mOffset); 244 245 // `mRef` may have become invalid due to some DOM mutation, 246 // which is not monitored here. Therefore, we need to validate `mRef` 247 // manually. 248 const uint32_t parentLength = GetLength(mParent); 249 if (*mOffset > parentLength) { 250 // offset > child count means that the range boundary has become invalid 251 // due to a DOM mutation. 252 mRef = nullptr; 253 } else if (*mOffset == parentLength) { 254 mRef = GetLastChild(mParent); 255 } else if (*mOffset) { 256 // validate and update `mRef`. 257 // If `ComputeIndexOf()` returns `Nothing`, then `mRef` is not a child of 258 // `mParent` anymore. 259 // If the returned index for `mRef` does not match to `mOffset`, `mRef` 260 // needs to be updated. 261 auto indexOfRefObject = mTreeKind == TreeKind::DOM 262 ? mParent->ComputeIndexOf(mRef) 263 : mParent->ComputeFlatTreeIndexOf(mRef); 264 if (indexOfRefObject.isNothing() || *mOffset != *indexOfRefObject + 1) { 265 mRef = GetChildAt(mParent, *mOffset - 1); 266 } 267 } else { 268 mRef = nullptr; 269 } 270 return mRef; 271 } 272 273 RawParentType* GetContainer() const { return mParent; } 274 275 dom::Document* GetComposedDoc() const { 276 return mParent ? mParent->GetComposedDoc() : nullptr; 277 } 278 279 /** 280 * This method may return `nullptr` if `mIsMutationObserved` is false and 281 * `mOffset` is out of bounds. 282 */ 283 RawRefType* GetChildAtOffset() const { 284 if (!mParent || !mParent->IsContainerNode()) { 285 return nullptr; 286 } 287 RawRefType* const ref = Ref(); 288 if (!ref) { 289 if (!mIsMutationObserved && *mOffset != 0) { 290 // This means that this boundary is invalid. 291 // `mOffset` is out of bounds. 292 return nullptr; 293 } 294 MOZ_ASSERT(*Offset(OffsetFilter::kValidOrInvalidOffsets) == 0, 295 "invalid RangeBoundary"); 296 return GetFirstChild(mParent); 297 } 298 MOZ_ASSERT( 299 GetChildAt(mParent, *Offset(OffsetFilter::kValidOrInvalidOffsets)) == 300 GetNextSibling(ref)); 301 return GetNextSibling(ref); 302 } 303 304 /** 305 * GetNextSiblingOfChildOffset() returns next sibling of a child at offset. 306 * If this refers after the last child or the container cannot have children, 307 * this returns nullptr with warning. 308 */ 309 RawRefType* GetNextSiblingOfChildAtOffset() const { 310 if (NS_WARN_IF(!mParent) || NS_WARN_IF(!mParent->IsContainerNode())) { 311 return nullptr; 312 } 313 RawRefType* const ref = Ref(); 314 if (!ref) { 315 if (!mIsMutationObserved && *mOffset != 0) { 316 // This means that this boundary is invalid. 317 // `mOffset` is out of bounds. 318 return nullptr; 319 } 320 MOZ_ASSERT(*Offset(OffsetFilter::kValidOffsets) == 0, 321 "invalid RangeBoundary"); 322 nsIContent* firstChild = GetFirstChild(mParent); 323 if (NS_WARN_IF(!firstChild)) { 324 // Already referring the end of the container. 325 return nullptr; 326 } 327 return GetNextSibling(firstChild); 328 } 329 if (NS_WARN_IF(!GetNextSibling(ref))) { 330 // Already referring the end of the container. 331 return nullptr; 332 } 333 return GetNextSibling(GetNextSibling(ref)); 334 } 335 336 /** 337 * GetPreviousSiblingOfChildAtOffset() returns previous sibling of a child 338 * at offset. If this refers the first child or the container cannot have 339 * children, this returns nullptr with warning. 340 */ 341 RawRefType* GetPreviousSiblingOfChildAtOffset() const { 342 if (NS_WARN_IF(!mParent) || NS_WARN_IF(!mParent->IsContainerNode())) { 343 return nullptr; 344 } 345 RawRefType* const ref = Ref(); 346 if (NS_WARN_IF(!ref)) { 347 // Already referring the start of the container. 348 return nullptr; 349 } 350 return ref; 351 } 352 353 /** 354 * Return true if this has already computed/set offset. 355 */ 356 [[nodiscard]] bool HasOffset() const { return mOffset.isSome(); } 357 358 enum class OffsetFilter { kValidOffsets, kValidOrInvalidOffsets }; 359 360 /** 361 * @return maybe an offset, depending on aOffsetFilter. If it is: 362 * kValidOffsets: if the offset is valid, it, Nothing{} otherwise. 363 * kValidOrInvalidOffsets: the internally stored offset, even if 364 * invalid, or if not available, a defined 365 * default value. That is, always some value. 366 */ 367 Maybe<uint32_t> Offset(const OffsetFilter aOffsetFilter) const { 368 switch (aOffsetFilter) { 369 case OffsetFilter::kValidOffsets: { 370 if (IsSetAndValid()) { 371 MOZ_ASSERT_IF(!mIsMutationObserved, mOffset); 372 if (!mOffset && mIsMutationObserved) { 373 DetermineOffsetFromReference(); 374 } 375 } 376 return !mIsMutationObserved && *mOffset > GetLength(mParent) ? Nothing{} 377 : mOffset; 378 } 379 case OffsetFilter::kValidOrInvalidOffsets: { 380 MOZ_ASSERT_IF(!mIsMutationObserved, mOffset.isSome()); 381 if (mOffset.isSome()) { 382 return mOffset; 383 } 384 if (mParent && mIsMutationObserved) { 385 DetermineOffsetFromReference(); 386 if (mOffset.isSome()) { 387 return mOffset; 388 } 389 } 390 391 return Some(kFallbackOffset); 392 } 393 } 394 395 // Needed to calm the compiler. There was deliberately no default case added 396 // to the above switch-statement, because it would prevent build-errors when 397 // not all enumerators are handled. 398 MOZ_ASSERT_UNREACHABLE(); 399 return Some(kFallbackOffset); 400 } 401 402 friend std::ostream& operator<<( 403 std::ostream& aStream, 404 const RangeBoundaryBase<ParentType, RefType>& aRangeBoundary) { 405 aStream << "{ mParent=" << aRangeBoundary.GetContainer(); 406 if (aRangeBoundary.GetContainer()) { 407 aStream << " (" << *aRangeBoundary.GetContainer() 408 << ", Length()=" << aRangeBoundary.GetContainer()->Length() 409 << ")"; 410 } 411 if (aRangeBoundary.mIsMutationObserved) { 412 aStream << ", mRef=" << aRangeBoundary.mRef; 413 if (aRangeBoundary.mRef) { 414 aStream << " (" << *aRangeBoundary.mRef << ")"; 415 } 416 } 417 418 aStream << ", mOffset=" << aRangeBoundary.mOffset; 419 aStream << ", mIsMutationObserved=" 420 << (aRangeBoundary.mIsMutationObserved ? "true" : "false") << " }"; 421 return aStream; 422 } 423 424 private: 425 void DetermineOffsetFromReference() const { 426 MOZ_ASSERT(mParent); 427 MOZ_ASSERT(mRef); 428 MOZ_ASSERT(IsValidParent(mParent, mRef)); 429 MOZ_ASSERT(mIsMutationObserved); 430 MOZ_ASSERT(mOffset.isNothing()); 431 432 if (mRef->IsBeingRemoved()) { 433 // ComputeIndexOf would return nothing because mRef has already been 434 // removed from the child node chain of mParent. 435 return; 436 } 437 438 const Maybe<uint32_t> index = mTreeKind == TreeKind::DOM 439 ? mParent->ComputeIndexOf(mRef) 440 : mParent->ComputeFlatTreeIndexOf(mRef); 441 442 MOZ_ASSERT(*index != UINT32_MAX); 443 mOffset.emplace(MOZ_LIKELY(index.isSome()) ? *index + 1u : 0u); 444 } 445 446 RawRefType* GetNextSibling(const nsIContent* aCurrentNode) const { 447 MOZ_ASSERT(mParent); 448 MOZ_ASSERT(aCurrentNode); 449 450 if (mTreeKind == TreeKind::Flat) { 451 if (const auto* slot = dom::HTMLSlotElement::FromNode(mParent)) { 452 const Span assigned = slot->AssignedNodes(); 453 const auto index = assigned.IndexOf(aCurrentNode); 454 if (index != assigned.npos && index + 1 < assigned.Length()) { 455 if (auto* nextSibling = RawRefType::FromNode(assigned[index + 1])) { 456 return nextSibling; 457 } 458 return nullptr; 459 } 460 } 461 } 462 return aCurrentNode->GetNextSibling(); 463 } 464 465 RawRefType* GetFirstChild(const nsINode* aNode) const { 466 MOZ_ASSERT(aNode); 467 if (mTreeKind == TreeKind::Flat) { 468 if (const auto* slot = dom::HTMLSlotElement::FromNode(aNode)) { 469 const Span assigned = slot->AssignedNodes(); 470 if (!assigned.IsEmpty()) { 471 if (RawRefType* child = RawRefType::FromNode(assigned[0])) { 472 return child; 473 } 474 return nullptr; 475 } 476 } 477 478 if (const auto* shadowRoot = aNode->GetShadowRoot()) { 479 return shadowRoot->GetFirstChild(); 480 } 481 } 482 return aNode->GetFirstChild(); 483 } 484 485 bool IsValidParent(const nsINode* aParent, const nsIContent* aChild) const { 486 MOZ_ASSERT(aParent); 487 MOZ_ASSERT(aChild); 488 if (mTreeKind == TreeKind::Flat) { 489 if (const auto* slot = aChild->GetAssignedSlot()) { 490 return slot == aParent; 491 } 492 493 if (const auto* shadowRoot = 494 dom::ShadowRoot::FromNodeOrNull(aChild->GetParent())) { 495 if (shadowRoot->GetHost() == aParent) { 496 return true; 497 } 498 } 499 } 500 return aChild->GetParentNode() == aParent; 501 } 502 503 uint32_t GetLength(const nsINode* aNode) const { 504 MOZ_ASSERT(aNode); 505 if (mTreeKind == TreeKind::Flat) { 506 if (const auto* slot = dom::HTMLSlotElement::FromNode(aNode)) { 507 const Span assigned = slot->AssignedNodes(); 508 if (!assigned.IsEmpty()) { 509 return assigned.Length(); 510 } 511 } 512 513 if (const auto* shadowRoot = aNode->GetShadowRoot()) { 514 return shadowRoot->Length(); 515 } 516 } 517 return aNode->Length(); 518 } 519 520 RawRefType* GetChildAt(const nsINode* aParent, uint32_t aOffset) const { 521 MOZ_ASSERT(aParent); 522 return mTreeKind == TreeKind::Flat 523 ? RawRefType::FromNodeOrNull( 524 aParent->GetChildAtInFlatTree(aOffset)) 525 : aParent->GetChildAt_Deprecated(aOffset); 526 } 527 528 RawRefType* GetLastChild(const nsINode* aParent) const { 529 MOZ_ASSERT(aParent); 530 if (mTreeKind == TreeKind::Flat) { 531 if (const auto* slot = dom::HTMLSlotElement::FromNode(aParent)) { 532 const Span assigned = slot->AssignedNodes(); 533 if (!assigned.IsEmpty()) { 534 return RawRefType::FromNode(assigned[assigned.Length() - 1]); 535 } 536 } 537 if (const auto* shadowRoot = aParent->GetShadowRoot()) { 538 return shadowRoot->GetLastChild(); 539 } 540 } 541 return aParent->GetLastChild(); 542 } 543 544 void InvalidateOffset() { 545 MOZ_ASSERT(mParent); 546 MOZ_ASSERT(mParent->IsContainerNode(), 547 "Range is positioned on a text node!"); 548 if (!mIsMutationObserved) { 549 // RangeBoundaries that are not used in the context of a 550 // `MutationObserver` use the offset as main source of truth to compute 551 // `mRef`. Therefore, it must not be updated or invalidated. 552 return; 553 } 554 if (!mRef) { 555 MOZ_ASSERT(mOffset.isSome() && mOffset.value() == 0, 556 "Invalidating offset of invalid RangeBoundary?"); 557 return; 558 } 559 mOffset.reset(); 560 } 561 562 public: 563 void NotifyParentBecomesShadowHost() { 564 MOZ_ASSERT(mParent); 565 MOZ_ASSERT(mParent->IsContainerNode(), 566 "Range is positioned on a text node!"); 567 if (!StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) { 568 return; 569 } 570 571 if (!mIsMutationObserved) { 572 // RangeBoundaries that are not used in the context of a 573 // `MutationObserver` use the offset as main source of truth to compute 574 // `mRef`. Therefore, it must not be updated or invalidated. 575 return; 576 } 577 578 if (!mRef) { 579 MOZ_ASSERT(mOffset.isSome() && mOffset.value() == 0, 580 "Invalidating offset of invalid RangeBoundary?"); 581 return; 582 } 583 584 if (dom::ShadowRoot* shadowRoot = mParent->GetShadowRootForSelection()) { 585 mParent = shadowRoot; 586 } 587 588 mOffset = Some(0); 589 } 590 591 bool IsSet() const { return mParent && (mRef || mOffset.isSome()); } 592 593 [[nodiscard]] bool IsSetAndInComposedDoc() const { 594 return IsSet() && mParent->IsInComposedDoc(); 595 } 596 597 bool IsSetAndValid() const { 598 if (!IsSet()) { 599 return false; 600 } 601 602 if (mIsMutationObserved && Ref()) { 603 // XXX mRef refers previous sibling of pointing child. Therefore, it 604 // seems odd that this becomes invalid due to its removal. Should we 605 // change RangeBoundaryBase to refer child at offset directly? 606 return IsValidParent(GetContainer(), Ref()) && !Ref()->IsBeingRemoved(); 607 } 608 609 MOZ_ASSERT(mOffset.isSome()); 610 return *mOffset <= GetContainer()->Length(); 611 } 612 613 bool IsStartOfContainer() const { 614 // We're at the first point in the container if we don't have a reference, 615 // and our offset is 0. If we don't have a Ref, we should already have an 616 // offset, so we can just directly fetch it. 617 return mIsMutationObserved ? !Ref() && mOffset.value() == 0 618 : mOffset.value() == 0; 619 } 620 621 bool IsEndOfContainer() const { 622 // We're at the last point in the container if Ref is a pointer to the last 623 // child in GetContainer(), or our Offset() is the same as the length of our 624 // container. If we don't have a Ref, then we should already have an offset, 625 // so we can just directly fetch it. 626 return mIsMutationObserved && Ref() ? !GetNextSibling(Ref()) 627 : mOffset.value() == GetLength(mParent); 628 } 629 630 // Convenience methods for switching between the two types 631 // of RangeBoundary. 632 template <typename PT = RawParentType, 633 typename = std::enable_if_t<!std::is_const_v<PT>>> 634 RawRangeBoundary AsRaw() const { 635 return RawRangeBoundary( 636 *this, RangeBoundaryIsMutationObserved(mIsMutationObserved)); 637 } 638 ConstRawRangeBoundary AsConstRaw() const { 639 return ConstRawRangeBoundary( 640 *this, RangeBoundaryIsMutationObserved(mIsMutationObserved)); 641 } 642 643 RangeBoundaryBase& operator=(const RangeBoundaryBase& aOther) { 644 MOZ_ASSERT(mTreeKind == aOther.mTreeKind); 645 if (this != &aOther) { 646 mParent = aOther.mParent; 647 mRef = aOther.mRef; 648 mOffset = aOther.mOffset; 649 mIsMutationObserved = aOther.mIsMutationObserved; 650 } 651 return *this; 652 } 653 654 template < 655 typename PT, typename RT, typename RPT = RawParentType, 656 typename = std::enable_if_t<!std::is_const_v<PT> || std::is_const_v<RPT>>> 657 RangeBoundaryBase& CopyFrom( 658 const RangeBoundaryBase<PT, RT>& aOther, 659 RangeBoundaryIsMutationObserved aIsMutationObserved) { 660 MOZ_ASSERT(mTreeKind == aOther.mTreeKind); 661 // mParent and mRef can be strong pointers, so better to try to avoid any 662 // extra AddRef/Release calls. 663 if (mParent != aOther.mParent) { 664 mParent = aOther.mParent; 665 } 666 if (mRef != aOther.mRef) { 667 mRef = aOther.mRef; 668 } 669 670 mIsMutationObserved = bool(aIsMutationObserved); 671 if (!mIsMutationObserved && aOther.mOffset.isNothing()) { 672 // "Fix" the offset from mRef if and only if we won't be updated for 673 // further mutations and aOther has not computed the offset of its mRef. 674 // XXX What should we do if aOther is not updated for mutations and 675 // mOffset has already been invalid? 676 mOffset = aOther.Offset( 677 RangeBoundaryBase<PT, RT>::OffsetFilter::kValidOrInvalidOffsets); 678 MOZ_DIAGNOSTIC_ASSERT(mOffset.isSome()); 679 } else { 680 mOffset = aOther.mOffset; 681 } 682 // If the mutation will be observed but the other does not have proper 683 // mRef for its mOffset, we need to compute mRef like the constructor 684 // which takes aOffset. 685 if (mIsMutationObserved && !mRef && mParent && mOffset.isSome() && 686 *mOffset) { 687 if (*mOffset == mParent->GetChildCount()) { 688 mRef = GetLastChild(mParent); 689 } else { 690 mRef = GetChildAt(mParent, *mOffset - 1); 691 } 692 } 693 return *this; 694 } 695 696 bool Equals(const RawParentType* aNode, uint32_t aOffset) const { 697 if (mParent != aNode) { 698 return false; 699 } 700 701 const Maybe<uint32_t> offset = Offset(OffsetFilter::kValidOffsets); 702 return offset && (*offset == aOffset); 703 } 704 705 template <typename A, typename B> 706 [[nodiscard]] bool operator==(const RangeBoundaryBase<A, B>& aOther) const { 707 if (!mParent && !aOther.mParent) { 708 return true; 709 } 710 if (mParent != aOther.mParent) { 711 return false; 712 } 713 if (RefIsFixed() && aOther.RefIsFixed()) { 714 return mRef == aOther.mRef; 715 } 716 717 if (mTreeKind != aOther.mTreeKind) { 718 return false; 719 } 720 721 return Offset(OffsetFilter::kValidOrInvalidOffsets) == 722 aOther.Offset( 723 RangeBoundaryBase<A, B>::OffsetFilter::kValidOrInvalidOffsets); 724 } 725 726 template <typename A, typename B> 727 bool operator!=(const RangeBoundaryBase<A, B>& aOther) const { 728 return !(*this == aOther); 729 } 730 731 private: 732 [[nodiscard]] bool RefIsFixed() const { 733 return mParent && 734 ( 735 // If mutation is observed, mRef is the base of mOffset unless 736 // it's not a container node like `Text` node. 737 (mIsMutationObserved && (mRef || mParent->IsContainerNode())) || 738 // If offset is not set, we would compute mOffset from mRef. 739 // So, mRef is "fixed" for now. 740 mOffset.isNothing()); 741 } 742 743 ParentType mParent; 744 mutable RefType mRef; 745 746 mutable mozilla::Maybe<uint32_t> mOffset; 747 bool mIsMutationObserved; 748 const TreeKind mTreeKind; 749 }; 750 751 template <typename ParentType, typename RefType> 752 const uint32_t RangeBoundaryBase<ParentType, RefType>::kFallbackOffset; 753 754 inline void ImplCycleCollectionUnlink(RangeBoundary& aField) { 755 ImplCycleCollectionUnlink(aField.mParent); 756 ImplCycleCollectionUnlink(aField.mRef); 757 } 758 759 inline void ImplCycleCollectionTraverse( 760 nsCycleCollectionTraversalCallback& aCallback, RangeBoundary& aField, 761 const char* aName, uint32_t aFlags) { 762 ImplCycleCollectionTraverse(aCallback, aField.mParent, "mParent", 0); 763 ImplCycleCollectionTraverse(aCallback, aField.mRef, "mRef", 0); 764 } 765 766 } // namespace mozilla 767 768 #endif // defined(mozilla_RangeBoundary_h)