tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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)