tor-browser

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

EditorDOMPoint.h (73944B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #ifndef mozilla_EditorDOMPoint_h
      7 #define mozilla_EditorDOMPoint_h
      8 
      9 #include "mozilla/Assertions.h"
     10 #include "mozilla/Attributes.h"
     11 #include "mozilla/EditorForwards.h"
     12 #include "mozilla/Maybe.h"
     13 #include "mozilla/RangeBoundary.h"
     14 #include "mozilla/dom/AbstractRange.h"
     15 #include "mozilla/dom/Element.h"
     16 #include "mozilla/dom/Selection.h"  // for Selection::InterlinePosition
     17 #include "mozilla/dom/Text.h"
     18 #include "nsAtom.h"
     19 #include "nsCOMPtr.h"
     20 #include "nsContentUtils.h"
     21 #include "nsCRT.h"
     22 #include "nsGkAtoms.h"
     23 #include "nsIContent.h"
     24 #include "nsINode.h"
     25 #include "nsString.h"
     26 #include "nsStyledElement.h"
     27 
     28 #include <algorithm>
     29 #include <type_traits>
     30 
     31 namespace mozilla {
     32 
     33 /**
     34 * EditorDOMPoint and EditorRawDOMPoint are simple classes which refers
     35 * a point in the DOM tree at creating the instance or initializing the
     36 * instance with calling Set().
     37 *
     38 * EditorDOMPoint refers container node (and child node if it's already set)
     39 * with nsCOMPtr.  EditorRawDOMPoint refers them with raw pointer.
     40 * So, EditorRawDOMPoint is useful when you access the nodes only before
     41 * changing DOM tree since increasing refcount may appear in micro benchmark
     42 * if it's in a hot path.  On the other hand, if you need to refer them even
     43 * after changing DOM tree, you must use EditorDOMPoint.
     44 *
     45 * When initializing an instance only with child node or offset,  the instance
     46 * starts to refer the child node or offset in the container.  In this case,
     47 * the other information hasn't been initialized due to performance reason.
     48 * When you retrieve the other information with calling Offset() or
     49 * GetChild(), the other information is computed with the current DOM tree.
     50 * Therefore, e.g., in the following case, the other information may be
     51 * different:
     52 *
     53 * EditorDOMPoint pointA(container1, childNode1);
     54 * EditorDOMPoint pointB(container1, childNode1);
     55 * (void)pointA.Offset(); // The offset is computed now.
     56 * container1->RemoveChild(childNode1->GetPreviousSibling());
     57 * (void)pointB.Offset(); // Now, pointB.Offset() equals pointA.Offset() - 1
     58 *
     59 * similarly:
     60 *
     61 * EditorDOMPoint pointA(container1, 5);
     62 * EditorDOMPoint pointB(container1, 5);
     63 * (void)pointA.GetChild(); // The child is computed now.
     64 * container1->RemoveChild(childNode1->GetFirstChild());
     65 * (void)pointB.GetChild(); // Now, pointB.GetChild() equals
     66 *                          // pointA.GetChild()->GetPreviousSibling().
     67 *
     68 * So, when you initialize an instance only with one information, you need to
     69 * be careful when you access the other information after changing the DOM tree.
     70 * When you need to lock the child node or offset and recompute the other
     71 * information with new DOM tree, you can use
     72 * AutoEditorDOMPointOffsetInvalidator and AutoEditorDOMPointChildInvalidator.
     73 */
     74 
     75 // FYI: Don't make the following instantiating macros end with `;` because
     76 //      using them without `;`, VSCode may be confused and cause wrong red-
     77 //      wavy underlines in the following code of the macro.
     78 #define NS_INSTANTIATE_EDITOR_DOM_POINT_METHOD(aResultType, aMethodName, ...) \
     79  template aResultType EditorDOMPoint::aMethodName(__VA_ARGS__);              \
     80  template aResultType EditorRawDOMPoint::aMethodName(__VA_ARGS__);           \
     81  template aResultType EditorDOMPointInText::aMethodName(__VA_ARGS__);        \
     82  template aResultType EditorRawDOMPointInText::aMethodName(__VA_ARGS__)
     83 
     84 #define NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(aResultType, aMethodName, \
     85                                                     ...)                      \
     86  template aResultType EditorDOMPoint::aMethodName(__VA_ARGS__) const;         \
     87  template aResultType EditorRawDOMPoint::aMethodName(__VA_ARGS__) const;      \
     88  template aResultType EditorDOMPointInText::aMethodName(__VA_ARGS__) const;   \
     89  template aResultType EditorRawDOMPointInText::aMethodName(__VA_ARGS__) const
     90 
     91 #define NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(aMethodName, ...) \
     92  template EditorDOMPoint aMethodName(__VA_ARGS__);                            \
     93  template EditorRawDOMPoint aMethodName(__VA_ARGS__);                         \
     94  template EditorDOMPointInText aMethodName(__VA_ARGS__);                      \
     95  template EditorRawDOMPointInText aMethodName(__VA_ARGS__)
     96 
     97 #define NS_INSTANTIATE_CONST_METHOD_RETURNING_ANY_EDITOR_DOM_POINT( \
     98    aMethodName, ...)                                               \
     99  template EditorDOMPoint aMethodName(__VA_ARGS__) const;           \
    100  template EditorRawDOMPoint aMethodName(__VA_ARGS__) const;        \
    101  template EditorDOMPointInText aMethodName(__VA_ARGS__) const;     \
    102  template EditorRawDOMPointInText aMethodName(__VA_ARGS__) const
    103 
    104 template <typename ParentType, typename ChildType>
    105 class EditorDOMPointBase final {
    106  using SelfType = EditorDOMPointBase<ParentType, ChildType>;
    107 
    108 public:
    109  using InterlinePosition = dom::Selection::InterlinePosition;
    110 
    111  EditorDOMPointBase() = default;
    112 
    113  template <typename ContainerType>
    114  EditorDOMPointBase(
    115      const ContainerType* aContainer, uint32_t aOffset,
    116      InterlinePosition aInterlinePosition = InterlinePosition::Undefined)
    117      : mParent(const_cast<ContainerType*>(aContainer)),
    118        mChild(nullptr),
    119        mOffset(Some(aOffset)),
    120        mInterlinePosition(aInterlinePosition) {
    121    NS_WARNING_ASSERTION(
    122        !mParent || mOffset.value() <= mParent->Length(),
    123        "The offset is larger than the length of aContainer or negative");
    124    if (!mParent) {
    125      mOffset.reset();
    126    }
    127  }
    128 
    129  template <typename PT, template <typename> typename StrongPtr>
    130  EditorDOMPointBase(
    131      StrongPtr<PT>&& aContainer, uint32_t aOffset,
    132      InterlinePosition aInterlinePosition = InterlinePosition::Undefined)
    133      : mParent(std::forward<StrongPtr<PT>>(aContainer)),
    134        mChild(nullptr),
    135        mOffset(Some(aOffset)),
    136        mInterlinePosition(aInterlinePosition) {
    137    NS_WARNING_ASSERTION(
    138        !mParent || mOffset.value() <= mParent->Length(),
    139        "The offset is larger than the length of aContainer or negative");
    140    if (!mParent) {
    141      mOffset.reset();
    142    }
    143  }
    144 
    145  template <typename ContainerType, template <typename> typename StrongPtr>
    146  EditorDOMPointBase(
    147      const StrongPtr<ContainerType>& aContainer, uint32_t aOffset,
    148      InterlinePosition aInterlinePosition = InterlinePosition::Undefined)
    149      : EditorDOMPointBase(aContainer.get(), aOffset, aInterlinePosition) {}
    150 
    151  template <typename ContainerType, template <typename> typename StrongPtr>
    152  EditorDOMPointBase(
    153      const StrongPtr<const ContainerType>& aContainer, uint32_t aOffset,
    154      InterlinePosition aInterlinePosition = InterlinePosition::Undefined)
    155      : EditorDOMPointBase(aContainer.get(), aOffset, aInterlinePosition) {}
    156 
    157  /**
    158   * Different from RangeBoundary, aPointedNode should be a child node
    159   * which you want to refer.
    160   */
    161  explicit EditorDOMPointBase(
    162      const nsINode* aPointedNode,  // FIXME: This should const nsIContent&
    163      InterlinePosition aInterlinePosition = InterlinePosition::Undefined)
    164      : mParent(aPointedNode && aPointedNode->IsContent()
    165                    ? aPointedNode->GetParentNode()
    166                    : nullptr),
    167        mChild(aPointedNode && aPointedNode->IsContent()
    168                   ? const_cast<nsIContent*>(aPointedNode->AsContent())
    169                   : nullptr),
    170        mInterlinePosition(aInterlinePosition) {
    171    mIsChildInitialized = mChild;
    172    NS_WARNING_ASSERTION(IsSet(),
    173                         "The child is nullptr or doesn't have its parent");
    174    NS_WARNING_ASSERTION(mChild && mChild->GetParentNode() == mParent,
    175                         "Initializing RangeBoundary with invalid value");
    176  }
    177 
    178  template <typename CT, template <typename> typename StrongPtr>
    179  explicit EditorDOMPointBase(
    180      StrongPtr<CT>&& aChild,
    181      InterlinePosition aInterlinePosition = InterlinePosition::Undefined)
    182      : mParent(aChild ? aChild->GetParentNode() : nullptr),
    183        mChild(std::forward<StrongPtr<CT>>(aChild)),
    184        mInterlinePosition(aInterlinePosition) {
    185    mIsChildInitialized = !!mChild;
    186    NS_WARNING_ASSERTION(IsSet(),
    187                         "The child is nullptr or doesn't have its parent");
    188    NS_WARNING_ASSERTION(mChild && mChild->GetParentNode() == mParent,
    189                         "Initializing RangeBoundary with invalid value");
    190  }
    191 
    192  EditorDOMPointBase(
    193      nsINode* aContainer, nsIContent* aPointedNode, uint32_t aOffset,
    194      InterlinePosition aInterlinePosition = InterlinePosition::Undefined)
    195      : mParent(aContainer),
    196        mChild(aPointedNode),
    197        mOffset(mozilla::Some(aOffset)),
    198        mInterlinePosition(aInterlinePosition),
    199        mIsChildInitialized(true) {
    200    MOZ_DIAGNOSTIC_ASSERT(
    201        aContainer, "This constructor shouldn't be used when pointing nowhere");
    202    MOZ_ASSERT(mOffset.value() <= mParent->Length());
    203    MOZ_ASSERT(mChild || mParent->Length() == mOffset.value() ||
    204               !mParent->IsContainerNode());
    205    MOZ_ASSERT(!mChild || mParent == mChild->GetParentNode());
    206    MOZ_ASSERT(mParent->GetChildAt_Deprecated(mOffset.value()) == mChild);
    207  }
    208 
    209  template <typename PT, typename CT>
    210  explicit EditorDOMPointBase(const RangeBoundaryBase<PT, CT>& aOther)
    211      : mParent(aOther.mParent),
    212        mChild(aOther.mRef ? aOther.mRef->GetNextSibling()
    213                           : (aOther.mParent ? aOther.mParent->GetFirstChild()
    214                                             : nullptr)),
    215        mOffset(aOther.mOffset),
    216        mIsChildInitialized(aOther.mRef || (aOther.mOffset.isSome() &&
    217                                            !aOther.mOffset.value())) {}
    218 
    219  void SetInterlinePosition(InterlinePosition aInterlinePosition) {
    220    MOZ_ASSERT(IsSet());
    221    mInterlinePosition = aInterlinePosition;
    222  }
    223  InterlinePosition GetInterlinePosition() const {
    224    return IsSet() ? mInterlinePosition : InterlinePosition::Undefined;
    225  }
    226 
    227  /**
    228   * GetContainer() returns the container node at the point.
    229   * GetContainerAs() returns the container node as specific type.
    230   */
    231  nsINode* GetContainer() const { return mParent; }
    232  template <typename ContentNodeType>
    233  ContentNodeType* GetContainerAs() const {
    234    return ContentNodeType::FromNodeOrNull(mParent);
    235  }
    236 
    237  /**
    238   * ContainerAs() returns the container node with just casting to the specific
    239   * type.  Therefore, callers need to guarantee that the result is not nullptr
    240   * nor wrong cast.
    241   */
    242  template <typename ContentNodeType>
    243  ContentNodeType* ContainerAs() const {
    244    MOZ_ASSERT(mParent);
    245    MOZ_DIAGNOSTIC_ASSERT(
    246        ContentNodeType::FromNode(static_cast<const nsINode*>(mParent)));
    247    return static_cast<ContentNodeType*>(GetContainer());
    248  }
    249 
    250  /**
    251   * GetContainerParent() returns parent of the container node at the point.
    252   */
    253  nsINode* GetContainerParent() const {
    254    return mParent ? mParent->GetParent() : nullptr;
    255  }
    256  template <typename ContentNodeType>
    257  ContentNodeType* GetContainerParentAs() const {
    258    return ContentNodeType::FromNodeOrNull(GetContainerParent());
    259  }
    260  template <typename ContentNodeType>
    261  ContentNodeType* ContainerParentAs() const {
    262    MOZ_DIAGNOSTIC_ASSERT(GetContainerParentAs<ContentNodeType>());
    263    return static_cast<ContentNodeType*>(GetContainerParent());
    264  }
    265 
    266  dom::Element* GetContainerOrContainerParentElement() const {
    267    if (MOZ_UNLIKELY(!mParent)) {
    268      return nullptr;
    269    }
    270    return mParent->IsElement() ? ContainerAs<dom::Element>()
    271                                : GetContainerParentAs<dom::Element>();
    272  }
    273 
    274  /**
    275   * CanContainerHaveChildren() returns true if the container node can have
    276   * child nodes.  Otherwise, e.g., when the container is a text node, returns
    277   * false.
    278   */
    279  bool CanContainerHaveChildren() const {
    280    return mParent && mParent->IsContainerNode();
    281  }
    282 
    283  /**
    284   * IsContainerEmpty() returns true if it has no children or its text is empty.
    285   */
    286  bool IsContainerEmpty() const { return mParent && !mParent->Length(); }
    287 
    288  /**
    289   * IsInContentNode() returns true if the container is a subclass of
    290   * nsIContent.
    291   */
    292  bool IsInContentNode() const { return mParent && mParent->IsContent(); }
    293 
    294  /**
    295   * IsInDataNode() returns true if the container node is a data node including
    296   * text node.
    297   */
    298  bool IsInDataNode() const { return mParent && mParent->IsCharacterData(); }
    299 
    300  /**
    301   * IsInTextNode() returns true if the container node is a text node.
    302   */
    303  bool IsInTextNode() const { return mParent && mParent->IsText(); }
    304 
    305  /**
    306   * IsInNativeAnonymousSubtree() returns true if the container is in
    307   * native anonymous subtree.
    308   */
    309  bool IsInNativeAnonymousSubtree() const {
    310    return mParent && mParent->IsInNativeAnonymousSubtree();
    311  }
    312 
    313  /**
    314   * Returns true if the container node is an element node.
    315   */
    316  bool IsContainerElement() const { return mParent && mParent->IsElement(); }
    317 
    318  /**
    319   * Returns true if the container node is an editing host.
    320   */
    321  [[nodiscard]] bool IsContainerEditableRoot() const;
    322 
    323  /**
    324   * IsContainerHTMLElement() returns true if the container node is an HTML
    325   * element node and its node name is aTag.
    326   */
    327  bool IsContainerHTMLElement(nsAtom* aTag) const {
    328    return mParent && mParent->IsHTMLElement(aTag);
    329  }
    330 
    331  /**
    332   * IsContainerAnyOfHTMLElements() returns true if the container node is an
    333   * HTML element node and its node name is one of the arguments.
    334   */
    335  template <typename First, typename... Args>
    336  bool IsContainerAnyOfHTMLElements(First aFirst, Args... aArgs) const {
    337    return mParent && mParent->IsAnyOfHTMLElements(aFirst, aArgs...);
    338  }
    339 
    340  /**
    341   * GetChild() returns a child node which is pointed by the instance.
    342   * If mChild hasn't been initialized yet, this computes the child node
    343   * from mParent and mOffset with *current* DOM tree.
    344   */
    345  nsIContent* GetChild() const {
    346    if (!mParent || !mParent->IsContainerNode()) {
    347      return nullptr;
    348    }
    349    if (mIsChildInitialized) {
    350      return mChild;
    351    }
    352    // Fix child node now.
    353    const_cast<SelfType*>(this)->EnsureChild();
    354    return mChild;
    355  }
    356 
    357  template <typename ContentNodeType>
    358  ContentNodeType* GetChildAs() const {
    359    return ContentNodeType::FromNodeOrNull(GetChild());
    360  }
    361  template <typename ContentNodeType>
    362  ContentNodeType* ChildAs() const {
    363    MOZ_DIAGNOSTIC_ASSERT(GetChildAs<ContentNodeType>());
    364    return static_cast<ContentNodeType*>(GetChild());
    365  }
    366 
    367  /**
    368   * GetCurrentChildAtOffset() returns current child at mOffset.
    369   * I.e., mOffset needs to be fixed before calling this.
    370   */
    371  nsIContent* GetCurrentChildAtOffset() const {
    372    MOZ_ASSERT(mOffset.isSome());
    373    if (mOffset.isNothing()) {
    374      return GetChild();
    375    }
    376    return mParent ? mParent->GetChildAt_Deprecated(*mOffset) : nullptr;
    377  }
    378 
    379  /**
    380   * GetChildOrContainerIfDataNode() returns the child content node,
    381   * or container content node if the container is a data node.
    382   */
    383  nsIContent* GetChildOrContainerIfDataNode() const {
    384    if (IsInDataNode()) {
    385      return ContainerAs<nsIContent>();
    386    }
    387    return GetChild();
    388  }
    389 
    390  /**
    391   * GetNextSiblingOfChild() returns next sibling of the child node.
    392   * If this refers after the last child or the container cannot have children,
    393   * this returns nullptr with warning.
    394   * If mChild hasn't been initialized yet, this computes the child node
    395   * from mParent and mOffset with *current* DOM tree.
    396   */
    397  nsIContent* GetNextSiblingOfChild() const {
    398    if (NS_WARN_IF(!mParent) || !mParent->IsContainerNode()) {
    399      return nullptr;
    400    }
    401    if (mIsChildInitialized) {
    402      return mChild ? mChild->GetNextSibling() : nullptr;
    403    }
    404    MOZ_ASSERT(mOffset.isSome());
    405    if (NS_WARN_IF(mOffset.value() > mParent->Length())) {
    406      // If this has been set only offset and now the offset is invalid,
    407      // let's just return nullptr.
    408      return nullptr;
    409    }
    410    // Fix child node now.
    411    const_cast<SelfType*>(this)->EnsureChild();
    412    return mChild ? mChild->GetNextSibling() : nullptr;
    413  }
    414  template <typename ContentNodeType>
    415  ContentNodeType* GetNextSiblingOfChildAs() const {
    416    return ContentNodeType::FromNodeOrNull(GetNextSiblingOfChild());
    417  }
    418  template <typename ContentNodeType>
    419  ContentNodeType* NextSiblingOfChildAs() const {
    420    MOZ_ASSERT(IsSet());
    421    MOZ_DIAGNOSTIC_ASSERT(GetNextSiblingOfChildAs<ContentNodeType>());
    422    return static_cast<ContentNodeType*>(GetNextSiblingOfChild());
    423  }
    424 
    425  /**
    426   * GetPreviousSiblingOfChild() returns previous sibling of a child
    427   * at offset.  If this refers the first child or the container cannot have
    428   * children, this returns nullptr with warning.
    429   * If mChild hasn't been initialized yet, this computes the child node
    430   * from mParent and mOffset with *current* DOM tree.
    431   */
    432  nsIContent* GetPreviousSiblingOfChild() const {
    433    if (NS_WARN_IF(!mParent) || !mParent->IsContainerNode()) {
    434      return nullptr;
    435    }
    436    if (mIsChildInitialized) {
    437      return mChild ? mChild->GetPreviousSibling() : mParent->GetLastChild();
    438    }
    439    MOZ_ASSERT(mOffset.isSome());
    440    if (NS_WARN_IF(mOffset.value() > mParent->Length())) {
    441      // If this has been set only offset and now the offset is invalid,
    442      // let's just return nullptr.
    443      return nullptr;
    444    }
    445    // Fix child node now.
    446    const_cast<SelfType*>(this)->EnsureChild();
    447    return mChild ? mChild->GetPreviousSibling() : mParent->GetLastChild();
    448  }
    449  template <typename ContentNodeType>
    450  ContentNodeType* GetPreviousSiblingOfChildAs() const {
    451    return ContentNodeType::FromNodeOrNull(GetPreviousSiblingOfChild());
    452  }
    453  template <typename ContentNodeType>
    454  ContentNodeType* PreviousSiblingOfChildAs() const {
    455    MOZ_ASSERT(IsSet());
    456    MOZ_DIAGNOSTIC_ASSERT(GetPreviousSiblingOfChildAs<ContentNodeType>());
    457    return static_cast<ContentNodeType*>(GetPreviousSiblingOfChild());
    458  }
    459 
    460  /**
    461   * Simple accessors of the character in dom::Text so that when you call
    462   * these methods, you need to guarantee that the container is a dom::Text.
    463   */
    464  MOZ_NEVER_INLINE_DEBUG char16_t Char() const {
    465    MOZ_ASSERT(IsSetAndValid());
    466    MOZ_ASSERT(!IsEndOfContainer());
    467    return ContainerAs<dom::Text>()->DataBuffer().CharAt(mOffset.value());
    468  }
    469  MOZ_NEVER_INLINE_DEBUG bool IsCharASCIISpace() const {
    470    return nsCRT::IsAsciiSpace(Char());
    471  }
    472  MOZ_NEVER_INLINE_DEBUG bool IsCharNBSP() const { return Char() == 0x00A0; }
    473  MOZ_NEVER_INLINE_DEBUG bool IsCharASCIISpaceOrNBSP() const {
    474    char16_t ch = Char();
    475    return nsCRT::IsAsciiSpace(ch) || ch == 0x00A0;
    476  }
    477  MOZ_NEVER_INLINE_DEBUG bool IsCharNewLine() const { return Char() == '\n'; }
    478 
    479  /**
    480   * Return true if pointing "\n" and it should be rendered as a line break.
    481   * Be aware that even if this returns false, the "\n" may be rendered as a
    482   * non-collapsible white-space.
    483   */
    484  MOZ_NEVER_INLINE_DEBUG bool IsCharPreformattedNewLine() const;
    485 
    486  MOZ_NEVER_INLINE_DEBUG bool
    487  IsCharPreformattedNewLineCollapsedWithWhiteSpaces() const;
    488 
    489  /**
    490   * IsCharCollapsibleASCIISpace(), IsCharCollapsibleNBSP() and
    491   * IsCharCollapsibleASCIISpaceOrNBSP() checks whether the white-space is
    492   * preformatted or collapsible with the style of the container text node
    493   * without flushing pending notifications.
    494   */
    495  bool IsCharCollapsibleASCIISpace() const;
    496  bool IsCharCollapsibleNBSP() const;
    497  bool IsCharCollapsibleASCIISpaceOrNBSP() const;
    498 
    499  MOZ_NEVER_INLINE_DEBUG bool IsCharHighSurrogateFollowedByLowSurrogate()
    500      const {
    501    MOZ_ASSERT(IsSetAndValid());
    502    MOZ_ASSERT(!IsEndOfContainer());
    503    return ContainerAs<dom::Text>()
    504        ->DataBuffer()
    505        .IsHighSurrogateFollowedByLowSurrogateAt(mOffset.value());
    506  }
    507  MOZ_NEVER_INLINE_DEBUG bool IsCharLowSurrogateFollowingHighSurrogate() const {
    508    MOZ_ASSERT(IsSetAndValid());
    509    MOZ_ASSERT(!IsEndOfContainer());
    510    return ContainerAs<dom::Text>()
    511        ->DataBuffer()
    512        .IsLowSurrogateFollowingHighSurrogateAt(mOffset.value());
    513  }
    514 
    515  MOZ_NEVER_INLINE_DEBUG char16_t PreviousChar() const {
    516    MOZ_ASSERT(IsSetAndValid());
    517    MOZ_ASSERT(!IsStartOfContainer());
    518    return ContainerAs<dom::Text>()->DataBuffer().CharAt(mOffset.value() - 1);
    519  }
    520  MOZ_NEVER_INLINE_DEBUG bool IsPreviousCharASCIISpace() const {
    521    return nsCRT::IsAsciiSpace(PreviousChar());
    522  }
    523  MOZ_NEVER_INLINE_DEBUG bool IsPreviousCharNBSP() const {
    524    return PreviousChar() == 0x00A0;
    525  }
    526  MOZ_NEVER_INLINE_DEBUG bool IsPreviousCharASCIISpaceOrNBSP() const {
    527    char16_t ch = PreviousChar();
    528    return nsCRT::IsAsciiSpace(ch) || ch == 0x00A0;
    529  }
    530  MOZ_NEVER_INLINE_DEBUG bool IsPreviousCharNewLine() const {
    531    return PreviousChar() == '\n';
    532  }
    533 
    534  /**
    535   * Return true if pointing next character of "\n" and it should be rendered as
    536   * a line break.  Be aware that even if this returns false, the "\n" may be
    537   * rendered as a non-collapsible white-space.
    538   */
    539  MOZ_NEVER_INLINE_DEBUG bool IsPreviousCharPreformattedNewLine() const;
    540 
    541  MOZ_NEVER_INLINE_DEBUG bool
    542  IsPreviousCharPreformattedNewLineCollapsedWithWhiteSpaces() const;
    543 
    544  /**
    545   * IsPreviousCharCollapsibleASCIISpace(), IsPreviousCharCollapsibleNBSP() and
    546   * IsPreviousCharCollapsibleASCIISpaceOrNBSP() checks whether the white-space
    547   * is preformatted or collapsible with the style of the container text node
    548   * without flushing pending notifications.
    549   */
    550  bool IsPreviousCharCollapsibleASCIISpace() const;
    551  bool IsPreviousCharCollapsibleNBSP() const;
    552  bool IsPreviousCharCollapsibleASCIISpaceOrNBSP() const;
    553 
    554  MOZ_NEVER_INLINE_DEBUG char16_t NextChar() const {
    555    MOZ_ASSERT(IsSetAndValid());
    556    MOZ_ASSERT(!IsAtLastContent() && !IsEndOfContainer());
    557    return ContainerAs<dom::Text>()->DataBuffer().CharAt(mOffset.value() + 1);
    558  }
    559  MOZ_NEVER_INLINE_DEBUG bool IsNextCharASCIISpace() const {
    560    return nsCRT::IsAsciiSpace(NextChar());
    561  }
    562  MOZ_NEVER_INLINE_DEBUG bool IsNextCharNBSP() const {
    563    return NextChar() == 0x00A0;
    564  }
    565  MOZ_NEVER_INLINE_DEBUG bool IsNextCharASCIISpaceOrNBSP() const {
    566    char16_t ch = NextChar();
    567    return nsCRT::IsAsciiSpace(ch) || ch == 0x00A0;
    568  }
    569  MOZ_NEVER_INLINE_DEBUG bool IsNextCharNewLine() const {
    570    return NextChar() == '\n';
    571  }
    572 
    573  /**
    574   * Return true if pointing previous character of "\n" and it should be
    575   * rendered as a line break.  Be aware that even if this returns false, the
    576   * "\n" may be rendered as a non-collapsible white-space.
    577   */
    578  MOZ_NEVER_INLINE_DEBUG bool IsNextCharPreformattedNewLine() const;
    579 
    580  MOZ_NEVER_INLINE_DEBUG bool
    581  IsNextCharPreformattedNewLineCollapsedWithWhiteSpaces() const;
    582 
    583  /**
    584   * IsNextCharCollapsibleASCIISpace(), IsNextCharCollapsibleNBSP() and
    585   * IsNextCharCollapsibleASCIISpaceOrNBSP() checks whether the white-space is
    586   * preformatted or collapsible with the style of the container text node
    587   * without flushing pending notifications.
    588   */
    589  bool IsNextCharCollapsibleASCIISpace() const;
    590  bool IsNextCharCollapsibleNBSP() const;
    591  bool IsNextCharCollapsibleASCIISpaceOrNBSP() const;
    592 
    593  [[nodiscard]] bool HasOffset() const { return mOffset.isSome(); }
    594  uint32_t Offset() const {
    595    if (mOffset.isSome()) {
    596      MOZ_ASSERT(mOffset.isSome());
    597      return mOffset.value();
    598    }
    599    if (MOZ_UNLIKELY(!mParent)) {
    600      MOZ_ASSERT(!mChild);
    601      return 0u;
    602    }
    603    MOZ_ASSERT(mParent->IsContainerNode(),
    604               "If the container cannot have children, mOffset.isSome() should "
    605               "be true");
    606    if (!mChild) {
    607      // We're referring after the last child.  Fix offset now.
    608      const_cast<SelfType*>(this)->mOffset = mozilla::Some(mParent->Length());
    609      return mOffset.value();
    610    }
    611    MOZ_ASSERT(mChild->GetParentNode() == mParent);
    612    // Fix offset now.
    613    if (mChild == mParent->GetFirstChild()) {
    614      const_cast<SelfType*>(this)->mOffset = mozilla::Some(0u);
    615      return 0u;
    616    }
    617    const_cast<SelfType*>(this)->mOffset = mParent->ComputeIndexOf(mChild);
    618    MOZ_DIAGNOSTIC_ASSERT(mOffset.isSome());
    619    return mOffset.valueOr(0u);  // Avoid crash in Release/Beta
    620  }
    621 
    622  /**
    623   * Set() sets a point to aOffset or aChild.
    624   * If it's set with aOffset, mChild is invalidated.  If it's set with aChild,
    625   * mOffset may be invalidated.
    626   */
    627  template <typename ContainerType>
    628  void Set(ContainerType* aContainer, uint32_t aOffset) {
    629    mParent = aContainer;
    630    mChild = nullptr;
    631    mOffset = mozilla::Some(aOffset);
    632    mIsChildInitialized = false;
    633    mInterlinePosition = InterlinePosition::Undefined;
    634    NS_ASSERTION(!mParent || mOffset.value() <= mParent->Length(),
    635                 "The offset is out of bounds");
    636  }
    637  template <typename ContainerType, template <typename> typename StrongPtr>
    638  void Set(const StrongPtr<ContainerType>& aContainer, uint32_t aOffset) {
    639    Set(aContainer.get(), aOffset);
    640  }
    641  template <typename ContainerType, template <typename> typename StrongPtr>
    642  void Set(StrongPtr<ContainerType>&& aContainer, uint32_t aOffset) {
    643    mParent = std::forward<StrongPtr<ContainerType>>(aContainer);
    644    mChild = nullptr;
    645    mOffset = mozilla::Some(aOffset);
    646    mIsChildInitialized = false;
    647    mInterlinePosition = InterlinePosition::Undefined;
    648    NS_ASSERTION(!mParent || mOffset.value() <= mParent->Length(),
    649                 "The offset is out of bounds");
    650  }
    651  void Set(const nsINode* aChild) {
    652    MOZ_ASSERT(aChild);
    653    if (NS_WARN_IF(!aChild->IsContent())) {
    654      Clear();
    655      return;
    656    }
    657    mParent = aChild->GetParentNode();
    658    mChild = const_cast<nsIContent*>(aChild->AsContent());
    659    mOffset.reset();
    660    mIsChildInitialized = true;
    661    mInterlinePosition = InterlinePosition::Undefined;
    662  }
    663  template <typename CT, template <typename> typename StrongPtr>
    664  void Set(StrongPtr<CT>&& aChild) {
    665    MOZ_ASSERT(aChild);
    666    if (NS_WARN_IF(!aChild->IsContent())) {
    667      Clear();
    668      return;
    669    }
    670    mParent = aChild->GetParentNode();
    671    mChild = std::forward<StrongPtr<CT>>(aChild);
    672    mOffset.reset();
    673    mIsChildInitialized = true;
    674    mInterlinePosition = InterlinePosition::Undefined;
    675  }
    676 
    677  /**
    678   * SetToEndOf() sets this to the end of aContainer.  Then, mChild is always
    679   * nullptr but marked as initialized and mOffset is always set.
    680   */
    681  template <typename ContainerType>
    682  void SetToEndOf(const ContainerType* aContainer) {
    683    MOZ_ASSERT(aContainer);
    684    mParent = const_cast<ContainerType*>(aContainer);
    685    mChild = nullptr;
    686    mOffset = mozilla::Some(mParent->Length());
    687    mIsChildInitialized = true;
    688    mInterlinePosition = InterlinePosition::Undefined;
    689  }
    690  template <typename ContainerType, template <typename> typename StrongPtr>
    691  void SetToEndOf(const StrongPtr<ContainerType>& aContainer) {
    692    SetToEndOf(aContainer.get());
    693  }
    694  template <typename ContainerType, template <typename> typename StrongPtr>
    695  void SetToEndOf(StrongPtr<ContainerType>&& aContainer) {
    696    mParent = std::forward<StrongPtr<ContainerType>>(aContainer);
    697    mChild = nullptr;
    698    mOffset = mozilla::Some(mParent->Length());
    699    mIsChildInitialized = true;
    700    mInterlinePosition = InterlinePosition::Undefined;
    701  }
    702  template <typename ContainerType>
    703  [[nodiscard]] static SelfType AtEndOf(
    704      const ContainerType& aContainer,
    705      InterlinePosition aInterlinePosition = InterlinePosition::Undefined) {
    706    SelfType point;
    707    point.SetToEndOf(&aContainer);
    708    point.mInterlinePosition = aInterlinePosition;
    709    return point;
    710  }
    711  template <typename ContainerType, template <typename> typename StrongPtr>
    712  [[nodiscard]] static SelfType AtEndOf(
    713      const StrongPtr<ContainerType>& aContainer,
    714      InterlinePosition aInterlinePosition = InterlinePosition::Undefined) {
    715    MOZ_ASSERT(aContainer.get());
    716    return AtEndOf(*aContainer.get(), aInterlinePosition);
    717  }
    718  template <typename ContainerType, template <typename> typename StrongPtr>
    719  [[nodiscard]] static SelfType AtEndOf(
    720      StrongPtr<ContainerType>&& aContainer,
    721      InterlinePosition aInterlinePosition = InterlinePosition::Undefined) {
    722    MOZ_ASSERT(aContainer.get());
    723    SelfType result;
    724    result.SetToEndOf(std::forward<StrongPtr<ContainerType>>(aContainer));
    725    result.mInterlinePosition = aInterlinePosition;
    726    return result;
    727  }
    728 
    729  /**
    730   * SetToLastContentOf() sets this to the last child of aContainer or the last
    731   * character of aContainer.
    732   */
    733  template <typename ContainerType>
    734  void SetToLastContentOf(const ContainerType* aContainer) {
    735    MOZ_ASSERT(aContainer);
    736    mParent = const_cast<ContainerType*>(aContainer);
    737    if (aContainer->IsContainerNode()) {
    738      MOZ_ASSERT(aContainer->GetChildCount());
    739      mChild = aContainer->GetLastChild();
    740      mOffset = mozilla::Some(aContainer->GetChildCount() - 1u);
    741    } else {
    742      MOZ_ASSERT(aContainer->Length());
    743      mChild = nullptr;
    744      mOffset = mozilla::Some(aContainer->Length() - 1u);
    745    }
    746    mIsChildInitialized = true;
    747    mInterlinePosition = InterlinePosition::Undefined;
    748  }
    749  template <typename ContainerType, template <typename> typename StrongPtr>
    750  MOZ_NEVER_INLINE_DEBUG void SetToLastContentOf(
    751      const StrongPtr<ContainerType>& aContainer) {
    752    SetToLastContentOf(aContainer.get());
    753  }
    754  template <typename ContainerType>
    755  MOZ_NEVER_INLINE_DEBUG static SelfType AtLastContentOf(
    756      const ContainerType& aContainer,
    757      InterlinePosition aInterlinePosition = InterlinePosition::Undefined) {
    758    SelfType point;
    759    point.SetToLastContentOf(&aContainer);
    760    point.mInterlinePosition = aInterlinePosition;
    761    return point;
    762  }
    763  template <typename ContainerType, template <typename> typename StrongPtr>
    764  MOZ_NEVER_INLINE_DEBUG static SelfType AtLastContentOf(
    765      const StrongPtr<ContainerType>& aContainer,
    766      InterlinePosition aInterlinePosition = InterlinePosition::Undefined) {
    767    MOZ_ASSERT(aContainer.get());
    768    return AtLastContentOf(*aContainer.get(), aInterlinePosition);
    769  }
    770 
    771  /**
    772   * SetAfter() sets mChild to next sibling of aChild.
    773   */
    774  void SetAfter(const nsINode* aChild) {
    775    MOZ_ASSERT(aChild);
    776    nsIContent* nextSibling = aChild->GetNextSibling();
    777    if (nextSibling) {
    778      Set(nextSibling);
    779      return;
    780    }
    781    nsINode* parentNode = aChild->GetParentNode();
    782    if (NS_WARN_IF(!parentNode)) {
    783      Clear();
    784      return;
    785    }
    786    SetToEndOf(parentNode);
    787  }
    788  void SetAfterContainer() {
    789    MOZ_ASSERT(mParent);
    790    SetAfter(mParent);
    791  }
    792  template <typename ContainerType>
    793  static SelfType After(
    794      const ContainerType& aContainer,
    795      InterlinePosition aInterlinePosition = InterlinePosition::Undefined) {
    796    SelfType point;
    797    point.SetAfter(&aContainer);
    798    point.mInterlinePosition = aInterlinePosition;
    799    return point;
    800  }
    801  template <typename ContainerType, template <typename> typename StrongPtr>
    802  MOZ_NEVER_INLINE_DEBUG static SelfType After(
    803      const StrongPtr<ContainerType>& aContainer,
    804      InterlinePosition aInterlinePosition = InterlinePosition::Undefined) {
    805    MOZ_ASSERT(aContainer.get());
    806    return After(*aContainer.get(), aInterlinePosition);
    807  }
    808  template <typename PT, typename CT>
    809  MOZ_NEVER_INLINE_DEBUG static SelfType After(
    810      const EditorDOMPointBase<PT, CT>& aPoint,
    811      InterlinePosition aInterlinePosition = InterlinePosition::Undefined) {
    812    MOZ_ASSERT(aPoint.IsSet());
    813    if (aPoint.mChild) {
    814      return After(*aPoint.mChild, aInterlinePosition);
    815    }
    816    if (NS_WARN_IF(aPoint.IsEndOfContainer())) {
    817      return SelfType();
    818    }
    819    auto point = aPoint.NextPoint().template To<SelfType>();
    820    point.mInterlinePosition = aInterlinePosition;
    821    return point;
    822  }
    823 
    824  /**
    825   * ParentPoint() returns a point whose child is the container.
    826   */
    827  template <typename EditorDOMPointType = SelfType>
    828  EditorDOMPointType ParentPoint() const {
    829    MOZ_ASSERT(mParent);
    830    if (MOZ_UNLIKELY(!mParent) || !mParent->IsContent()) {
    831      return EditorDOMPointType();
    832    }
    833    return EditorDOMPointType(ContainerAs<nsIContent>());
    834  }
    835 
    836  /**
    837   * NextPoint() and PreviousPoint() returns next/previous DOM point in
    838   * the container.
    839   */
    840  template <typename EditorDOMPointType = SelfType>
    841  EditorDOMPointType NextPoint() const {
    842    NS_ASSERTION(!IsEndOfContainer(), "Should not be at end of the container");
    843    auto result = this->template To<EditorDOMPointType>();
    844    result.AdvanceOffset();
    845    return result;
    846  }
    847  template <typename EditorDOMPointType = SelfType>
    848  EditorDOMPointType NextPointOrAfterContainer() const {
    849    MOZ_ASSERT(IsInContentNode());
    850    if (!IsEndOfContainer()) {
    851      return NextPoint<EditorDOMPointType>();
    852    }
    853    return AfterContainer<EditorDOMPointType>();
    854  }
    855  template <typename EditorDOMPointType = SelfType>
    856  EditorDOMPointType AfterContainer() const {
    857    MOZ_ASSERT(IsInContentNode());
    858    return EditorDOMPointType::After(*ContainerAs<nsIContent>());
    859  }
    860  template <typename EditorDOMPointType = SelfType>
    861  EditorDOMPointType PreviousPoint() const {
    862    NS_ASSERTION(!IsStartOfContainer(),
    863                 "Should not be at start of the container");
    864    auto result = this->template To<EditorDOMPointType>();
    865    result.RewindOffset();
    866    return result;
    867  }
    868  template <typename EditorDOMPointType = SelfType>
    869  EditorDOMPointType PreviousPointOrParentPoint() const {
    870    if (IsStartOfContainer()) {
    871      return ParentPoint<EditorDOMPointType>();
    872    }
    873    return PreviousPoint<EditorDOMPointType>();
    874  }
    875 
    876  /**
    877   * Clear() makes the instance not point anywhere.
    878   */
    879  void Clear() {
    880    mParent = nullptr;
    881    mChild = nullptr;
    882    mOffset.reset();
    883    mIsChildInitialized = false;
    884    mInterlinePosition = InterlinePosition::Undefined;
    885  }
    886 
    887  /**
    888   * AdvanceOffset() tries to refer next sibling of mChild and/of next offset.
    889   * If the container can have children and there is no next sibling or the
    890   * offset reached the length of the container, this outputs warning and does
    891   * nothing.  So, callers need to check if there is next sibling which you
    892   * need to refer.
    893   *
    894   * @return            true if there is a next DOM point to refer.
    895   */
    896  bool AdvanceOffset() {
    897    if (NS_WARN_IF(!mParent)) {
    898      return false;
    899    }
    900    // If only mOffset is available, just compute the offset.
    901    if ((mOffset.isSome() && !mIsChildInitialized) ||
    902        !mParent->IsContainerNode()) {
    903      MOZ_ASSERT(mOffset.isSome());
    904      MOZ_ASSERT(!mChild);
    905      if (NS_WARN_IF(mOffset.value() >= mParent->Length())) {
    906        // We're already referring the start of the container.
    907        return false;
    908      }
    909      mOffset = mozilla::Some(mOffset.value() + 1);
    910      mInterlinePosition = InterlinePosition::Undefined;
    911      return true;
    912    }
    913 
    914    MOZ_ASSERT(mIsChildInitialized);
    915    MOZ_ASSERT(!mOffset.isSome() || mOffset.isSome());
    916    if (NS_WARN_IF(!mParent->HasChildren()) || NS_WARN_IF(!mChild) ||
    917        NS_WARN_IF(mOffset.isSome() && mOffset.value() >= mParent->Length())) {
    918      // We're already referring the end of the container (or outside).
    919      return false;
    920    }
    921 
    922    if (mOffset.isSome()) {
    923      MOZ_ASSERT(mOffset.isSome());
    924      mOffset = mozilla::Some(mOffset.value() + 1);
    925    }
    926    mChild = mChild->GetNextSibling();
    927    mInterlinePosition = InterlinePosition::Undefined;
    928    return true;
    929  }
    930 
    931  /**
    932   * RewindOffset() tries to refer previous sibling of mChild and/or previous
    933   * offset.  If the container can have children and there is no next previous
    934   * or the offset is 0, this outputs warning and does nothing.  So, callers
    935   * need to check if there is previous sibling which you need to refer.
    936   *
    937   * @return            true if there is a previous DOM point to refer.
    938   */
    939  bool RewindOffset() {
    940    if (NS_WARN_IF(!mParent)) {
    941      return false;
    942    }
    943    // If only mOffset is available, just compute the offset.
    944    if ((mOffset.isSome() && !mIsChildInitialized) ||
    945        !mParent->IsContainerNode()) {
    946      MOZ_ASSERT(mOffset.isSome());
    947      MOZ_ASSERT(!mChild);
    948      if (NS_WARN_IF(!mOffset.value()) ||
    949          NS_WARN_IF(mOffset.value() > mParent->Length())) {
    950        // We're already referring the start of the container or
    951        // the offset is invalid since perhaps, the offset was set before
    952        // the last DOM tree change.
    953        NS_ASSERTION(false, "Failed to rewind offset");
    954        return false;
    955      }
    956      mOffset = mozilla::Some(mOffset.value() - 1);
    957      mInterlinePosition = InterlinePosition::Undefined;
    958      return true;
    959    }
    960 
    961    MOZ_ASSERT(mIsChildInitialized);
    962    MOZ_ASSERT(!mOffset.isSome() || mOffset.isSome());
    963    if (NS_WARN_IF(!mParent->HasChildren()) ||
    964        NS_WARN_IF(mChild && !mChild->GetPreviousSibling()) ||
    965        NS_WARN_IF(mOffset.isSome() && !mOffset.value())) {
    966      // We're already referring the start of the container (or the child has
    967      // been moved from the container?).
    968      return false;
    969    }
    970 
    971    nsIContent* previousSibling =
    972        mChild ? mChild->GetPreviousSibling() : mParent->GetLastChild();
    973    if (NS_WARN_IF(!previousSibling)) {
    974      // We're already referring the first child of the container.
    975      return false;
    976    }
    977 
    978    if (mOffset.isSome()) {
    979      mOffset = mozilla::Some(mOffset.value() - 1);
    980    }
    981    mChild = previousSibling;
    982    mInterlinePosition = InterlinePosition::Undefined;
    983    return true;
    984  }
    985 
    986  /**
    987   * GetNonAnonymousSubtreePoint() returns a DOM point which is NOT in
    988   * native-anonymous subtree.  If the instance isn't in native-anonymous
    989   * subtree, this returns same point.  Otherwise, climbs up until finding
    990   * non-native-anonymous parent and returns the point of it.  I.e.,
    991   * container is parent of the found non-anonymous-native node.
    992   */
    993  template <typename EditorDOMPointType>
    994  EditorDOMPointType GetNonAnonymousSubtreePoint() const {
    995    if (NS_WARN_IF(!IsSet())) {
    996      return EditorDOMPointType();
    997    }
    998    if (!IsInNativeAnonymousSubtree()) {
    999      return this->template To<EditorDOMPointType>();
   1000    }
   1001    nsINode* parent;
   1002    for (parent = mParent->GetParentNode();
   1003         parent && parent->IsInNativeAnonymousSubtree();
   1004         parent = parent->GetParentNode()) {
   1005    }
   1006    if (!parent) {
   1007      return EditorDOMPointType();
   1008    }
   1009    return EditorDOMPointType(parent);
   1010  }
   1011 
   1012  [[nodiscard]] bool IsSet() const {
   1013    return mParent && (mIsChildInitialized || mOffset.isSome());
   1014  }
   1015 
   1016  [[nodiscard]] bool IsSetAndValid() const {
   1017    if (!IsSet()) {
   1018      return false;
   1019    }
   1020 
   1021    if (mChild &&
   1022        (mChild->GetParentNode() != mParent || mChild->IsBeingRemoved())) {
   1023      return false;
   1024    }
   1025    if (mOffset.isSome() && mOffset.value() > mParent->Length()) {
   1026      return false;
   1027    }
   1028    return true;
   1029  }
   1030 
   1031  [[nodiscard]] bool IsInContentNodeAndValid() const {
   1032    return IsInContentNode() && IsSetAndValid();
   1033  }
   1034 
   1035  [[nodiscard]] bool IsInComposedDoc() const {
   1036    return IsSet() && mParent->IsInComposedDoc();
   1037  }
   1038 
   1039  [[nodiscard]] bool IsSetAndValidInComposedDoc() const {
   1040    return IsInComposedDoc() && IsSetAndValid();
   1041  }
   1042 
   1043  [[nodiscard]] bool IsInContentNodeAndValidInComposedDoc() const {
   1044    return IsInContentNode() && IsSetAndValidInComposedDoc();
   1045  }
   1046 
   1047  [[nodiscard]] bool IsInNativeAnonymousSubtreeInTextControl() const {
   1048    if (!mParent || !mParent->IsInNativeAnonymousSubtree()) {
   1049      return false;
   1050    }
   1051    nsIContent* maybeTextControl =
   1052        mParent->GetClosestNativeAnonymousSubtreeRootParentOrHost();
   1053    return !!maybeTextControl;
   1054  }
   1055 
   1056  [[nodiscard]] bool IsStartOfContainer() const {
   1057    // If we're referring the first point in the container:
   1058    //   If mParent is not a container like a text node, mOffset is 0.
   1059    //   If mChild is initialized and it's first child of mParent.
   1060    //   If mChild isn't initialized and the offset is 0.
   1061    if (NS_WARN_IF(!mParent)) {
   1062      return false;
   1063    }
   1064    if (!mParent->IsContainerNode()) {
   1065      return !mOffset.value();
   1066    }
   1067    if (mIsChildInitialized) {
   1068      if (mParent->GetFirstChild() == mChild) {
   1069        NS_WARNING_ASSERTION(!mOffset.isSome() || !mOffset.value(),
   1070                             "If mOffset was initialized, it should be 0");
   1071        return true;
   1072      }
   1073      NS_WARNING_ASSERTION(!mOffset.isSome() || mParent->GetChildAt_Deprecated(
   1074                                                    mOffset.value()) == mChild,
   1075                           "mOffset and mChild are mismatched");
   1076      return false;
   1077    }
   1078    MOZ_ASSERT(mOffset.isSome());
   1079    return !mOffset.value();
   1080  }
   1081 
   1082  [[nodiscard]] bool IsMiddleOfContainer() const {
   1083    if (NS_WARN_IF(!mParent)) {
   1084      return false;
   1085    }
   1086    if (mParent->IsText()) {
   1087      return *mOffset && *mOffset < mParent->Length();
   1088    }
   1089    if (!mParent->HasChildren()) {
   1090      return false;
   1091    }
   1092    if (mIsChildInitialized) {
   1093      NS_WARNING_ASSERTION(
   1094          mOffset.isNothing() ||
   1095              (!mChild && *mOffset == mParent->GetChildCount()) ||
   1096              (mChild && mOffset == mParent->ComputeIndexOf(mChild)),
   1097          "mOffset does not match with current offset of mChild");
   1098      return mChild && mChild != mParent->GetFirstChild();
   1099    }
   1100    MOZ_ASSERT(mOffset.isSome());
   1101    return *mOffset && *mOffset < mParent->Length();
   1102  }
   1103 
   1104  [[nodiscard]] bool IsEndOfContainer() const {
   1105    // If we're referring after the last point of the container:
   1106    //   If mParent is not a container like text node, mOffset is same as the
   1107    //   length of the container.
   1108    //   If mChild is initialized and it's nullptr.
   1109    //   If mChild isn't initialized and mOffset is same as the length of the
   1110    //   container.
   1111    if (NS_WARN_IF(!mParent)) {
   1112      return false;
   1113    }
   1114    if (!mParent->IsContainerNode()) {
   1115      return mOffset.value() == mParent->Length();
   1116    }
   1117    if (mIsChildInitialized) {
   1118      if (!mChild) {
   1119        NS_WARNING_ASSERTION(
   1120            !mOffset.isSome() || mOffset.value() == mParent->Length(),
   1121            "If mOffset was initialized, it should be length of the container");
   1122        return true;
   1123      }
   1124      NS_WARNING_ASSERTION(!mOffset.isSome() || mParent->GetChildAt_Deprecated(
   1125                                                    mOffset.value()) == mChild,
   1126                           "mOffset and mChild are mismatched");
   1127      return false;
   1128    }
   1129    MOZ_ASSERT(mOffset.isSome());
   1130    return mOffset.value() == mParent->Length();
   1131  }
   1132 
   1133  /**
   1134   * IsAtLastContent() returns true when it refers last child of the container
   1135   * or last character offset of text node.
   1136   */
   1137  bool IsAtLastContent() const {
   1138    if (NS_WARN_IF(!mParent)) {
   1139      return false;
   1140    }
   1141    if (mParent->IsContainerNode() && mOffset.isSome()) {
   1142      return mOffset.value() == mParent->Length() - 1;
   1143    }
   1144    if (mIsChildInitialized) {
   1145      if (mChild && mChild == mParent->GetLastChild()) {
   1146        NS_WARNING_ASSERTION(
   1147            !mOffset.isSome() || mOffset.value() == mParent->Length() - 1,
   1148            "If mOffset was initialized, it should be length - 1 of the "
   1149            "container");
   1150        return true;
   1151      }
   1152      NS_WARNING_ASSERTION(!mOffset.isSome() || mParent->GetChildAt_Deprecated(
   1153                                                    mOffset.value()) == mChild,
   1154                           "mOffset and mChild are mismatched");
   1155      return false;
   1156    }
   1157    MOZ_ASSERT(mOffset.isSome());
   1158    return mOffset.value() == mParent->Length() - 1;
   1159  }
   1160 
   1161  /**
   1162   * Return a point in text node if "this" points around a text node.
   1163   * EditorDOMPointType can always be EditorDOMPoint or EditorRawDOMPoint,
   1164   * but EditorDOMPointInText or EditorRawDOMPointInText is also available
   1165   * only when "this type" is one of them.
   1166   * If the point is in the anonymous <div> of a TextEditor, use
   1167   * TextEditor::FindBetterInsertionPoint() instead.
   1168   */
   1169  template <typename EditorDOMPointType>
   1170  EditorDOMPointType GetPointInTextNodeIfPointingAroundTextNode() const {
   1171    if (NS_WARN_IF(!IsSet()) || !mParent->HasChildren()) {
   1172      return To<EditorDOMPointType>();
   1173    }
   1174    if (IsStartOfContainer()) {
   1175      if (auto* firstTextChild =
   1176              dom::Text::FromNode(mParent->GetFirstChild())) {
   1177        return EditorDOMPointType(firstTextChild, 0u);
   1178      }
   1179      return To<EditorDOMPointType>();
   1180    }
   1181    if (auto* previousSiblingChild = dom::Text::FromNodeOrNull(
   1182            GetPreviousSiblingOfChildAs<dom::Text>())) {
   1183      return EditorDOMPointType::AtEndOf(*previousSiblingChild);
   1184    }
   1185    if (auto* child = dom::Text::FromNodeOrNull(GetChildAs<dom::Text>())) {
   1186      return EditorDOMPointType(child, 0u);
   1187    }
   1188    return To<EditorDOMPointType>();
   1189  }
   1190 
   1191  template <typename A, typename B>
   1192  EditorDOMPointBase& operator=(const RangeBoundaryBase<A, B>& aOther) {
   1193    mParent = aOther.mParent;
   1194    mChild = aOther.mRef ? aOther.mRef->GetNextSibling()
   1195                         : (aOther.mParent && aOther.mParent->IsContainerNode()
   1196                                ? aOther.mParent->GetFirstChild()
   1197                                : nullptr);
   1198    mOffset = aOther.mOffset;
   1199    mIsChildInitialized =
   1200        aOther.mRef || (aOther.mParent && !aOther.mParent->IsContainerNode()) ||
   1201        (aOther.mOffset.isSome() && !aOther.mOffset.value());
   1202    mInterlinePosition = InterlinePosition::Undefined;
   1203    return *this;
   1204  }
   1205 
   1206  /**
   1207   * If EditorDOMPointType is same as SelfType, return the reference of `this`.
   1208   * Otherwise, returns new instance of EditorDOMPointType.
   1209   *
   1210   * So, don't do this:
   1211   * auto newPoint = oldPoint.RefOrTo();
   1212   * newPoint.AdvanceOffset();
   1213   * Then, oldPoint is also modified.  Change the `auto` to the type of
   1214   * `oldPoint` in this case.
   1215   *
   1216   * If you always want a new instance, you should use To() instead.
   1217   */
   1218  template <typename EditorDOMPointType>
   1219  constexpr EditorDOMPointType RefOrTo() const {
   1220    if constexpr (std::is_same_v<SelfType, EditorDOMPointType>) {
   1221      return *this;
   1222    } else {
   1223      EditorDOMPointType result;
   1224      result.mParent = mParent;
   1225      result.mChild = mChild;
   1226      result.mOffset = mOffset;
   1227      result.mIsChildInitialized = mIsChildInitialized;
   1228      result.mInterlinePosition = mInterlinePosition;
   1229      return result;
   1230    }
   1231  }
   1232 
   1233  /**
   1234   * Even if EditorDOMPointType is same as SelfType, return a copy of `this`.
   1235   */
   1236  template <typename EditorDOMPointType>
   1237  constexpr EditorDOMPointType To() const {
   1238    EditorDOMPointType result;
   1239    result.mParent = mParent;
   1240    result.mChild = mChild;
   1241    result.mOffset = mOffset;
   1242    result.mIsChildInitialized = mIsChildInitialized;
   1243    result.mInterlinePosition = mInterlinePosition;
   1244    return result;
   1245  }
   1246 
   1247  /**
   1248   * Don't compare mInterlinePosition.  If it's required to check, perhaps,
   1249   * another compare operator like `===` should be created.
   1250   */
   1251  template <typename A, typename B>
   1252  bool operator==(const EditorDOMPointBase<A, B>& aOther) const {
   1253    if (mParent != aOther.mParent) {
   1254      return false;
   1255    }
   1256 
   1257    if (mOffset.isSome() && aOther.mOffset.isSome()) {
   1258      // If both mOffset are set, we need to compare both mRef too because
   1259      // the relation of mRef and mOffset have already broken by DOM tree
   1260      // changes.
   1261      if (mOffset != aOther.mOffset) {
   1262        return false;
   1263      }
   1264      if (mChild == aOther.mChild) {
   1265        return true;
   1266      }
   1267      if (NS_WARN_IF(mIsChildInitialized && aOther.mIsChildInitialized)) {
   1268        // In this case, relation between mChild and mOffset of one of or both
   1269        // of them doesn't match with current DOM tree since the DOM tree might
   1270        // have been changed after computing mChild or mOffset.
   1271        return false;
   1272      }
   1273      // If one of mChild hasn't been computed yet, we should compare them only
   1274      // with mOffset.  Perhaps, we shouldn't copy mChild from non-nullptr one
   1275      // to the other since if we copy it here, it may be unexpected behavior
   1276      // for some callers.
   1277      return true;
   1278    }
   1279 
   1280    MOZ_ASSERT(mIsChildInitialized || aOther.mIsChildInitialized);
   1281 
   1282    if (mOffset.isSome() && !mIsChildInitialized && !aOther.mOffset.isSome() &&
   1283        aOther.mIsChildInitialized) {
   1284      // If this has only mOffset and the other has only mChild, this needs to
   1285      // compute mChild now.
   1286      const_cast<SelfType*>(this)->EnsureChild();
   1287      return mChild == aOther.mChild;
   1288    }
   1289 
   1290    if (!mOffset.isSome() && mIsChildInitialized && aOther.mOffset.isSome() &&
   1291        !aOther.mIsChildInitialized) {
   1292      // If this has only mChild and the other has only mOffset, the other needs
   1293      // to compute mChild now.
   1294      const_cast<EditorDOMPointBase<A, B>&>(aOther).EnsureChild();
   1295      return mChild == aOther.mChild;
   1296    }
   1297 
   1298    // If mOffset of one of them hasn't been computed from mChild yet, we should
   1299    // compare only with mChild.  Perhaps, we shouldn't copy mOffset from being
   1300    // some one to not being some one since if we copy it here, it may be
   1301    // unexpected behavior for some callers.
   1302    return mChild == aOther.mChild;
   1303  }
   1304 
   1305  template <typename A, typename B>
   1306  bool operator==(const RangeBoundaryBase<A, B>& aOther) const {
   1307    // TODO: Optimize this with directly comparing with RangeBoundaryBase
   1308    //       members.
   1309    return *this == SelfType(aOther);
   1310  }
   1311 
   1312  template <typename A, typename B>
   1313  bool operator!=(const EditorDOMPointBase<A, B>& aOther) const {
   1314    return !(*this == aOther);
   1315  }
   1316 
   1317  template <typename A, typename B>
   1318  bool operator!=(const RangeBoundaryBase<A, B>& aOther) const {
   1319    return !(*this == aOther);
   1320  }
   1321 
   1322  /**
   1323   * This operator should be used if API of other modules take RawRangeBoundary,
   1324   * e.g., methods of Selection and nsRange.
   1325   */
   1326  operator const RawRangeBoundary() const { return ToRawRangeBoundary(); }
   1327  const RawRangeBoundary ToRawRangeBoundary() const {
   1328    if (!IsSet() || NS_WARN_IF(!mIsChildInitialized && !mOffset.isSome())) {
   1329      return RawRangeBoundary();
   1330    }
   1331    if (!mParent->IsContainerNode()) {
   1332      MOZ_ASSERT(mOffset.value() <= mParent->Length());
   1333      // If the container is a data node like a text node, we need to create
   1334      // RangeBoundaryBase instance only with mOffset because mChild is always
   1335      // nullptr.
   1336      return RawRangeBoundary(mParent, mOffset.value(),
   1337                              // Avoid immediately to compute the child node.
   1338                              RangeBoundaryIsMutationObserved::No);
   1339    }
   1340    if (mIsChildInitialized && mOffset.isSome()) {
   1341      // If we've already set both child and offset, we should create
   1342      // RangeBoundary with offset after validation.
   1343 #ifdef DEBUG
   1344      if (mChild) {
   1345        MOZ_ASSERT(mParent == mChild->GetParentNode());
   1346        MOZ_ASSERT(mParent->GetChildAt_Deprecated(mOffset.value()) == mChild);
   1347      } else {
   1348        MOZ_ASSERT(mParent->Length() == mOffset.value());
   1349      }
   1350 #endif  // #ifdef DEBUG
   1351      return RawRangeBoundary(mParent, mOffset.value(),
   1352                              // Avoid immediately to compute the child node.
   1353                              RangeBoundaryIsMutationObserved::No);
   1354    }
   1355    // Otherwise, we should create RangeBoundaryBase only with available
   1356    // information.
   1357    if (mOffset.isSome()) {
   1358      return RawRangeBoundary(mParent, mOffset.value(),
   1359                              // Avoid immediately to compute the child node.
   1360                              RangeBoundaryIsMutationObserved::No);
   1361    }
   1362    if (mChild) {
   1363      return RawRangeBoundary(mParent, mChild->GetPreviousSibling());
   1364    }
   1365    return RawRangeBoundary(mParent, mParent->GetLastChild());
   1366  }
   1367 
   1368  already_AddRefed<nsRange> CreateCollapsedRange(ErrorResult& aRv) const {
   1369    const RawRangeBoundary boundary = ToRawRangeBoundary();
   1370    RefPtr<nsRange> range = nsRange::Create(boundary, boundary, aRv);
   1371    if (MOZ_UNLIKELY(aRv.Failed() || !range)) {
   1372      return nullptr;
   1373    }
   1374    return range.forget();
   1375  }
   1376 
   1377  [[nodiscard]] EditorDOMPointInText GetAsInText() const {
   1378    return IsInTextNode() ? EditorDOMPointInText(ContainerAs<dom::Text>(),
   1379                                                 Offset(), mInterlinePosition)
   1380                          : EditorDOMPointInText();
   1381  }
   1382  [[nodiscard]] EditorDOMPointInText AsInText() const {
   1383    MOZ_ASSERT(IsInTextNode());
   1384    return EditorDOMPointInText(ContainerAs<dom::Text>(), Offset(),
   1385                                mInterlinePosition);
   1386  }
   1387  [[nodiscard]] EditorRawDOMPointInText GetAsRawInText() const {
   1388    return IsInTextNode()
   1389               ? EditorRawDOMPointInText(ContainerAs<dom::Text>(), Offset(),
   1390                                         mInterlinePosition)
   1391               : EditorRawDOMPointInText();
   1392  }
   1393  [[nodiscard]] EditorRawDOMPointInText AsRawInText() const {
   1394    MOZ_ASSERT(IsInTextNode());
   1395    return EditorRawDOMPointInText(ContainerAs<dom::Text>(), Offset(),
   1396                                   mInterlinePosition);
   1397  }
   1398 
   1399  template <typename A, typename B>
   1400  bool IsBefore(const EditorDOMPointBase<A, B>& aOther) const {
   1401    if (!IsSetAndValid() || !aOther.IsSetAndValid()) {
   1402      return false;
   1403    }
   1404    Maybe<int32_t> comp = nsContentUtils::ComparePoints(
   1405        ToRawRangeBoundary(), aOther.ToRawRangeBoundary());
   1406    return comp.isSome() && comp.value() == -1;
   1407  }
   1408 
   1409  template <typename A, typename B>
   1410  bool EqualsOrIsBefore(const EditorDOMPointBase<A, B>& aOther) const {
   1411    if (!IsSetAndValid() || !aOther.IsSetAndValid()) {
   1412      return false;
   1413    }
   1414    Maybe<int32_t> comp = nsContentUtils::ComparePoints(
   1415        ToRawRangeBoundary(), aOther.ToRawRangeBoundary());
   1416    return comp.isSome() && comp.value() <= 0;
   1417  }
   1418 
   1419  friend std::ostream& operator<<(std::ostream& aStream,
   1420                                  const SelfType& aDOMPoint) {
   1421    aStream << "{ mParent=" << aDOMPoint.GetContainer();
   1422    if (aDOMPoint.mParent) {
   1423      const auto* parentAsText = dom::Text::FromNode(aDOMPoint.mParent);
   1424      if (parentAsText && parentAsText->TextDataLength()) {
   1425        nsAutoString data;
   1426        parentAsText->AppendTextTo(data);
   1427        if (data.Length() > 10) {
   1428          data.Truncate(10);
   1429        }
   1430        data.ReplaceSubstring(u"\n", u"\\n");
   1431        data.ReplaceSubstring(u"\r", u"\\r");
   1432        data.ReplaceSubstring(u"\t", u"\\t");
   1433        data.ReplaceSubstring(u"\f", u"\\f");
   1434        data.ReplaceSubstring(u"\u00A0", u"&nbsp;");
   1435        aStream << " (" << *parentAsText << ", (begins with=\""
   1436                << NS_ConvertUTF16toUTF8(data).get()
   1437                << "\"), Length()=" << parentAsText->TextDataLength() << ")";
   1438      } else {
   1439        aStream << " (" << *aDOMPoint.mParent
   1440                << ", Length()=" << aDOMPoint.mParent->Length() << ")";
   1441      }
   1442    }
   1443    aStream << ", mChild=" << static_cast<nsIContent*>(aDOMPoint.mChild);
   1444    if (aDOMPoint.mChild) {
   1445      aStream << " (" << *aDOMPoint.mChild << ")";
   1446    }
   1447    aStream << ", mOffset=" << aDOMPoint.mOffset << ", mIsChildInitialized="
   1448            << (aDOMPoint.mIsChildInitialized ? "true" : "false")
   1449            << ", mInterlinePosition=" << aDOMPoint.mInterlinePosition << " }";
   1450    return aStream;
   1451  }
   1452 
   1453 private:
   1454  void EnsureChild() {
   1455    if (mIsChildInitialized) {
   1456      return;
   1457    }
   1458    if (!mParent) {
   1459      MOZ_ASSERT(!mOffset.isSome());
   1460      return;
   1461    }
   1462    MOZ_ASSERT(mOffset.isSome());
   1463    MOZ_ASSERT(mOffset.value() <= mParent->Length());
   1464    mIsChildInitialized = true;
   1465    if (!mParent->IsContainerNode()) {
   1466      return;
   1467    }
   1468    mChild = mParent->GetChildAt_Deprecated(mOffset.value());
   1469    MOZ_ASSERT(mChild || mOffset.value() == mParent->Length());
   1470  }
   1471 
   1472  ParentType mParent = nullptr;
   1473  ChildType mChild = nullptr;
   1474 
   1475  Maybe<uint32_t> mOffset;
   1476  InterlinePosition mInterlinePosition = InterlinePosition::Undefined;
   1477  bool mIsChildInitialized = false;
   1478 
   1479  template <typename PT, typename CT>
   1480  friend class EditorDOMPointBase;
   1481 
   1482  friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
   1483                                          EditorDOMPoint&, const char*,
   1484                                          uint32_t);
   1485  friend void ImplCycleCollectionUnlink(EditorDOMPoint&);
   1486 };
   1487 
   1488 inline void ImplCycleCollectionUnlink(EditorDOMPoint& aField) {
   1489  ImplCycleCollectionUnlink(aField.mParent);
   1490  ImplCycleCollectionUnlink(aField.mChild);
   1491 }
   1492 
   1493 inline void ImplCycleCollectionTraverse(
   1494    nsCycleCollectionTraversalCallback& aCallback, EditorDOMPoint& aField,
   1495    const char* aName, uint32_t aFlags) {
   1496  ImplCycleCollectionTraverse(aCallback, aField.mParent, "mParent", 0);
   1497  ImplCycleCollectionTraverse(aCallback, aField.mChild, "mChild", 0);
   1498 }
   1499 
   1500 /**
   1501 * EditorDOMRangeBase class stores a pair of same EditorDOMPointBase type.
   1502 * The instance must be created with valid DOM points and start must be
   1503 * before or same as end.
   1504 */
   1505 #define NS_INSTANTIATE_EDITOR_DOM_RANGE_METHOD(aResultType, aMethodName, ...) \
   1506  template aResultType EditorDOMRange::aMethodName(__VA_ARGS__);              \
   1507  template aResultType EditorRawDOMRange::aMethodName(__VA_ARGS__);           \
   1508  template aResultType EditorDOMRangeInTexts::aMethodName(__VA_ARGS__);       \
   1509  template aResultType EditorRawDOMRangeInTexts::aMethodName(__VA_ARGS__)
   1510 
   1511 #define NS_INSTANTIATE_EDITOR_DOM_RANGE_CONST_METHOD(aResultType, aMethodName, \
   1512                                                     ...)                      \
   1513  template aResultType EditorDOMRange::aMethodName(__VA_ARGS__) const;         \
   1514  template aResultType EditorRawDOMRange::aMethodName(__VA_ARGS__) const;      \
   1515  template aResultType EditorDOMRangeInTexts::aMethodName(__VA_ARGS__) const;  \
   1516  template aResultType EditorRawDOMRangeInTexts::aMethodName(__VA_ARGS__) const
   1517 template <typename EditorDOMPointType>
   1518 class EditorDOMRangeBase final {
   1519  using SelfType = EditorDOMRangeBase<EditorDOMPointType>;
   1520 
   1521 public:
   1522  using PointType = EditorDOMPointType;
   1523 
   1524  EditorDOMRangeBase() = default;
   1525  template <typename PT, typename CT>
   1526  explicit EditorDOMRangeBase(const EditorDOMPointBase<PT, CT>& aStart)
   1527      : mStart(aStart), mEnd(aStart) {
   1528    MOZ_ASSERT(!mStart.IsSet() || mStart.IsSetAndValid());
   1529  }
   1530  template <typename StartPointType, typename EndPointType>
   1531  explicit EditorDOMRangeBase(const StartPointType& aStart,
   1532                              const EndPointType& aEnd)
   1533      : mStart(aStart.template RefOrTo<PointType>()),
   1534        mEnd(aEnd.template RefOrTo<PointType>()) {
   1535    MOZ_ASSERT_IF(mStart.IsSet(), mStart.IsSetAndValid());
   1536    MOZ_ASSERT_IF(mEnd.IsSet(), mEnd.IsSetAndValid());
   1537    MOZ_ASSERT_IF(mStart.IsSet() && mEnd.IsSet(),
   1538                  mStart.EqualsOrIsBefore(mEnd));
   1539  }
   1540  template <typename EndPointType>
   1541  explicit EditorDOMRangeBase(PointType&& aStart, EndPointType& aEnd)
   1542      : mStart(std::forward<PointType>(aStart)),
   1543        mEnd(aEnd.template RefOrTo<PointType>()) {
   1544    MOZ_ASSERT_IF(mStart.IsSet(), mStart.IsSetAndValid());
   1545    MOZ_ASSERT_IF(mEnd.IsSet(), mEnd.IsSetAndValid());
   1546    MOZ_ASSERT_IF(mStart.IsSet() && mEnd.IsSet(),
   1547                  mStart.EqualsOrIsBefore(mEnd));
   1548  }
   1549  template <typename StartPointType>
   1550  explicit EditorDOMRangeBase(StartPointType& aStart, PointType&& aEnd)
   1551      : mStart(aStart.template RefOrTo<PointType>()),
   1552        mEnd(std::forward<PointType>(aEnd)) {
   1553    MOZ_ASSERT_IF(mStart.IsSet(), mStart.IsSetAndValid());
   1554    MOZ_ASSERT_IF(mEnd.IsSet(), mEnd.IsSetAndValid());
   1555    MOZ_ASSERT_IF(mStart.IsSet() && mEnd.IsSet(),
   1556                  mStart.EqualsOrIsBefore(mEnd));
   1557  }
   1558  explicit EditorDOMRangeBase(PointType&& aStart, PointType&& aEnd)
   1559      : mStart(std::forward<PointType>(aStart)),
   1560        mEnd(std::forward<PointType>(aEnd)) {
   1561    MOZ_ASSERT_IF(mStart.IsSet(), mStart.IsSetAndValid());
   1562    MOZ_ASSERT_IF(mEnd.IsSet(), mEnd.IsSetAndValid());
   1563    MOZ_ASSERT_IF(mStart.IsSet() && mEnd.IsSet(),
   1564                  mStart.EqualsOrIsBefore(mEnd));
   1565  }
   1566  template <typename OtherPointType>
   1567  explicit EditorDOMRangeBase(const EditorDOMRangeBase<OtherPointType>& aOther)
   1568      : mStart(aOther.StartRef().template RefOrTo<PointType>()),
   1569        mEnd(aOther.EndRef().template RefOrTo<PointType>()) {
   1570    MOZ_ASSERT_IF(mStart.IsSet(), mStart.IsSetAndValid());
   1571    MOZ_ASSERT_IF(mEnd.IsSet(), mEnd.IsSetAndValid());
   1572    MOZ_ASSERT(mStart.IsSet() == mEnd.IsSet());
   1573  }
   1574  explicit EditorDOMRangeBase(const dom::AbstractRange& aRange)
   1575      : mStart(aRange.StartRef()), mEnd(aRange.EndRef()) {
   1576    MOZ_ASSERT_IF(mStart.IsSet(), mStart.IsSetAndValid());
   1577    MOZ_ASSERT_IF(mEnd.IsSet(), mEnd.IsSetAndValid());
   1578    MOZ_ASSERT_IF(mStart.IsSet() && mEnd.IsSet(),
   1579                  mStart.EqualsOrIsBefore(mEnd));
   1580  }
   1581 
   1582  template <typename MaybeOtherPointType>
   1583  void SetStart(const MaybeOtherPointType& aStart) {
   1584    mStart = aStart.template RefOrTo<PointType>();
   1585  }
   1586  void SetStart(PointType&& aStart) { mStart = std::move(aStart); }
   1587  template <typename MaybeOtherPointType>
   1588  void SetEnd(const MaybeOtherPointType& aEnd) {
   1589    mEnd = aEnd.template RefOrTo<PointType>();
   1590  }
   1591  void SetEnd(PointType&& aEnd) { mEnd = std::move(aEnd); }
   1592  template <typename StartPointType, typename EndPointType>
   1593  void SetStartAndEnd(const StartPointType& aStart, const EndPointType& aEnd) {
   1594    MOZ_ASSERT_IF(aStart.IsSet() && aEnd.IsSet(),
   1595                  aStart.EqualsOrIsBefore(aEnd));
   1596    mStart = aStart.template RefOrTo<PointType>();
   1597    mEnd = aEnd.template RefOrTo<PointType>();
   1598  }
   1599  template <typename StartPointType>
   1600  void SetStartAndEnd(const StartPointType& aStart, PointType&& aEnd) {
   1601    MOZ_ASSERT_IF(aStart.IsSet() && aEnd.IsSet(),
   1602                  aStart.EqualsOrIsBefore(aEnd));
   1603    mStart = aStart.template RefOrTo<PointType>();
   1604    mEnd = std::move(aEnd);
   1605  }
   1606  template <typename EndPointType>
   1607  void SetStartAndEnd(PointType&& aStart, const EndPointType& aEnd) {
   1608    MOZ_ASSERT_IF(aStart.IsSet() && aEnd.IsSet(),
   1609                  aStart.EqualsOrIsBefore(aEnd));
   1610    mStart = std::move(aStart);
   1611    mEnd = aEnd.template RefOrTo<PointType>();
   1612  }
   1613  void SetStartAndEnd(PointType&& aStart, PointType&& aEnd) {
   1614    MOZ_ASSERT_IF(aStart.IsSet() && aEnd.IsSet(),
   1615                  aStart.EqualsOrIsBefore(aEnd));
   1616    mStart = std::move(aStart);
   1617    mEnd = std::move(aEnd);
   1618  }
   1619  template <typename PT, typename CT>
   1620  void MergeWith(const EditorDOMPointBase<PT, CT>& aPoint) {
   1621    MOZ_ASSERT(aPoint.IsSet());
   1622    if (!IsPositioned()) {
   1623      SetStartAndEnd(aPoint, aPoint);
   1624      return;
   1625    }
   1626    MOZ_ASSERT(nsContentUtils::GetClosestCommonInclusiveAncestor(
   1627        GetClosestCommonInclusiveAncestor(), aPoint.GetContainer()));
   1628    if (mEnd.EqualsOrIsBefore(aPoint)) {
   1629      SetEnd(aPoint);
   1630      return;
   1631    }
   1632    if (aPoint.IsBefore(mStart)) {
   1633      SetStart(aPoint);
   1634      return;
   1635    }
   1636  }
   1637  void MergeWith(PointType&& aPoint) {
   1638    MOZ_ASSERT(aPoint.IsSet());
   1639    if (!IsPositioned()) {
   1640      SetStartAndEnd(aPoint, aPoint);
   1641      return;
   1642    }
   1643    MOZ_ASSERT(GetClosestCommonInclusiveAncestor());
   1644    MOZ_ASSERT(nsContentUtils::GetClosestCommonInclusiveAncestor(
   1645        GetClosestCommonInclusiveAncestor(), aPoint.GetContainer()));
   1646    if (mEnd.EqualsOrIsBefore(aPoint)) {
   1647      SetEnd(std::move(aPoint));
   1648      return;
   1649    }
   1650    if (aPoint.IsBefore(mStart)) {
   1651      SetStart(std::move(aPoint));
   1652      return;
   1653    }
   1654  }
   1655  template <typename PT, typename CT>
   1656  void MergeWith(const EditorDOMRangeBase<EditorDOMPointBase<PT, CT>>& aRange) {
   1657    MOZ_ASSERT(aRange.IsPositioned());
   1658    MOZ_ASSERT(aRange.GetClosestCommonInclusiveAncestor());
   1659    if (!IsPositioned()) {
   1660      SetStartAndEnd(aRange.mStart, aRange.mEnd);
   1661      return;
   1662    }
   1663    MOZ_ASSERT(GetClosestCommonInclusiveAncestor());
   1664    MOZ_ASSERT(nsContentUtils::GetClosestCommonInclusiveAncestor(
   1665        GetClosestCommonInclusiveAncestor(),
   1666        aRange.GetClosestCommonInclusiveAncestor()));
   1667    if (mEnd.IsBefore(aRange.mEnd)) {
   1668      SetEnd(aRange.mEnd);
   1669    }
   1670    if (aRange.mStart.IsBefore(mStart)) {
   1671      SetStart(aRange.mStart);
   1672    }
   1673  }
   1674  void MergeWith(SelfType&& aRange) {
   1675    MOZ_ASSERT(aRange.IsPositioned());
   1676    MOZ_ASSERT(aRange.GetClosestCommonInclusiveAncestor());
   1677    if (!IsPositioned()) {
   1678      SetStartAndEnd(std::move(aRange.mStart), std::move(aRange.mEnd));
   1679      return;
   1680    }
   1681    MOZ_ASSERT(GetClosestCommonInclusiveAncestor());
   1682    MOZ_ASSERT(nsContentUtils::GetClosestCommonInclusiveAncestor(
   1683        GetClosestCommonInclusiveAncestor(),
   1684        aRange.GetClosestCommonInclusiveAncestor()));
   1685    if (mEnd.IsBefore(aRange.mEnd)) {
   1686      SetEnd(std::move(aRange.mEnd));
   1687    }
   1688    if (aRange.mStart.IsBefore(mStart)) {
   1689      SetStart(std::move(aRange.mStart));
   1690    }
   1691    aRange.Clear();
   1692  }
   1693  void Clear() {
   1694    mStart.Clear();
   1695    mEnd.Clear();
   1696  }
   1697 
   1698  const PointType& StartRef() const { return mStart; }
   1699  const PointType& EndRef() const { return mEnd; }
   1700 
   1701  bool Collapsed() const {
   1702    MOZ_ASSERT(IsPositioned());
   1703    return mStart == mEnd;
   1704  }
   1705  bool IsPositioned() const { return mStart.IsSet() && mEnd.IsSet(); }
   1706  bool IsPositionedAndValid() const {
   1707    return mStart.IsSetAndValid() && mEnd.IsSetAndValid() &&
   1708           mStart.EqualsOrIsBefore(mEnd);
   1709  }
   1710  bool IsPositionedAndValidInComposedDoc() const {
   1711    return IsPositionedAndValid() && mStart.GetContainer()->IsInComposedDoc();
   1712  }
   1713  template <typename OtherPointType>
   1714  MOZ_NEVER_INLINE_DEBUG bool Contains(const OtherPointType& aPoint) const {
   1715    MOZ_ASSERT(aPoint.IsSetAndValid());
   1716    return IsPositioned() && aPoint.IsSet() &&
   1717           mStart.EqualsOrIsBefore(aPoint) && aPoint.IsBefore(mEnd);
   1718  }
   1719  [[nodiscard]] nsINode* GetClosestCommonInclusiveAncestor() const;
   1720  bool InSameContainer() const {
   1721    MOZ_ASSERT(IsPositioned());
   1722    return IsPositioned() && mStart.GetContainer() == mEnd.GetContainer();
   1723  }
   1724  bool InAdjacentSiblings() const {
   1725    MOZ_ASSERT(IsPositioned());
   1726    return IsPositioned() &&
   1727           mStart.GetContainer()->GetNextSibling() == mEnd.GetContainer();
   1728  }
   1729  bool IsInContentNodes() const {
   1730    MOZ_ASSERT(IsPositioned());
   1731    return IsPositioned() && mStart.IsInContentNode() && mEnd.IsInContentNode();
   1732  }
   1733  bool IsInTextNodes() const {
   1734    MOZ_ASSERT(IsPositioned());
   1735    return IsPositioned() && mStart.IsInTextNode() && mEnd.IsInTextNode();
   1736  }
   1737  template <typename OtherRangeType>
   1738  bool operator==(const OtherRangeType& aOther) const {
   1739    return (!IsPositioned() && !aOther.IsPositioned()) ||
   1740           (mStart == aOther.StartRef() && mEnd == aOther.EndRef());
   1741  }
   1742  template <typename OtherRangeType>
   1743  bool operator!=(const OtherRangeType& aOther) const {
   1744    return !(*this == aOther);
   1745  }
   1746 
   1747  EditorDOMRangeInTexts GetAsInTexts() const {
   1748    return IsInTextNodes()
   1749               ? EditorDOMRangeInTexts(mStart.AsInText(), mEnd.AsInText())
   1750               : EditorDOMRangeInTexts();
   1751  }
   1752  MOZ_NEVER_INLINE_DEBUG EditorDOMRangeInTexts AsInTexts() const {
   1753    MOZ_ASSERT(IsInTextNodes());
   1754    return EditorDOMRangeInTexts(mStart.AsInText(), mEnd.AsInText());
   1755  }
   1756 
   1757  bool EnsureNotInNativeAnonymousSubtree() {
   1758    if (mStart.IsInNativeAnonymousSubtree()) {
   1759      nsIContent* parent = nullptr;
   1760      for (parent = mStart.template ContainerAs<nsIContent>()
   1761                        ->GetClosestNativeAnonymousSubtreeRootParentOrHost();
   1762           parent && parent->IsInNativeAnonymousSubtree();
   1763           parent =
   1764               parent->GetClosestNativeAnonymousSubtreeRootParentOrHost()) {
   1765      }
   1766      if (MOZ_UNLIKELY(!parent)) {
   1767        return false;
   1768      }
   1769      mStart.Set(parent);
   1770    }
   1771    if (mEnd.IsInNativeAnonymousSubtree()) {
   1772      nsIContent* parent = nullptr;
   1773      for (parent = mEnd.template ContainerAs<nsIContent>()
   1774                        ->GetClosestNativeAnonymousSubtreeRootParentOrHost();
   1775           parent && parent->IsInNativeAnonymousSubtree();
   1776           parent =
   1777               parent->GetClosestNativeAnonymousSubtreeRootParentOrHost()) {
   1778      }
   1779      if (MOZ_UNLIKELY(!parent)) {
   1780        return false;
   1781      }
   1782      mEnd.SetAfter(parent);
   1783    }
   1784    return true;
   1785  }
   1786 
   1787  already_AddRefed<nsRange> CreateRange(ErrorResult& aRv) const {
   1788    RefPtr<nsRange> range = nsRange::Create(mStart.ToRawRangeBoundary(),
   1789                                            mEnd.ToRawRangeBoundary(), aRv);
   1790    if (MOZ_UNLIKELY(aRv.Failed() || !range)) {
   1791      return nullptr;
   1792    }
   1793    return range.forget();
   1794  }
   1795  nsresult SetToRange(nsRange& aRange) const {
   1796    return aRange.SetStartAndEnd(mStart.ToRawRangeBoundary(),
   1797                                 mEnd.ToRawRangeBoundary());
   1798  }
   1799 
   1800  friend std::ostream& operator<<(std::ostream& aStream,
   1801                                  const SelfType& aRange) {
   1802    if (aRange.Collapsed()) {
   1803      aStream << "{ mStart=mEnd=" << aRange.mStart << " }";
   1804    } else {
   1805      aStream << "{ mStart=" << aRange.mStart << ", mEnd=" << aRange.mEnd
   1806              << " }";
   1807    }
   1808    return aStream;
   1809  }
   1810 
   1811 private:
   1812  EditorDOMPointType mStart;
   1813  EditorDOMPointType mEnd;
   1814 
   1815  friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
   1816                                          EditorDOMRange&, const char*,
   1817                                          uint32_t);
   1818  friend void ImplCycleCollectionUnlink(EditorDOMRange&);
   1819 };
   1820 
   1821 inline void ImplCycleCollectionUnlink(EditorDOMRange& aField) {
   1822  ImplCycleCollectionUnlink(aField.mStart);
   1823  ImplCycleCollectionUnlink(aField.mEnd);
   1824 }
   1825 
   1826 inline void ImplCycleCollectionTraverse(
   1827    nsCycleCollectionTraversalCallback& aCallback, EditorDOMRange& aField,
   1828    const char* aName, uint32_t aFlags) {
   1829  ImplCycleCollectionTraverse(aCallback, aField.mStart, "mStart", 0);
   1830  ImplCycleCollectionTraverse(aCallback, aField.mEnd, "mEnd", 0);
   1831 }
   1832 
   1833 /**
   1834 * AutoEditorDOMPointOffsetInvalidator is useful if DOM tree will be changed
   1835 * when EditorDOMPoint instance is available and keeps referring same child
   1836 * node.
   1837 *
   1838 * This class automatically guarantees that given EditorDOMPoint instance
   1839 * stores the child node and invalidates its offset when the instance is
   1840 * destroyed.  Additionally, users of this class can invalidate the offset
   1841 * manually when they need.
   1842 */
   1843 class MOZ_STACK_CLASS AutoEditorDOMPointOffsetInvalidator final {
   1844 public:
   1845  AutoEditorDOMPointOffsetInvalidator() = delete;
   1846  AutoEditorDOMPointOffsetInvalidator(
   1847      const AutoEditorDOMPointOffsetInvalidator&) = delete;
   1848  AutoEditorDOMPointOffsetInvalidator(AutoEditorDOMPointOffsetInvalidator&&) =
   1849      delete;
   1850  const AutoEditorDOMPointOffsetInvalidator& operator=(
   1851      const AutoEditorDOMPointOffsetInvalidator&) = delete;
   1852  explicit AutoEditorDOMPointOffsetInvalidator(EditorDOMPoint& aPoint)
   1853      : mPoint(aPoint), mCanceled(false) {
   1854    MOZ_ASSERT(aPoint.IsSetAndValid());
   1855    MOZ_ASSERT(mPoint.CanContainerHaveChildren());
   1856    mChild = mPoint.GetChild();
   1857  }
   1858 
   1859  ~AutoEditorDOMPointOffsetInvalidator() {
   1860    if (!mCanceled) {
   1861      InvalidateOffset();
   1862    }
   1863  }
   1864 
   1865  /**
   1866   * Manually, invalidate offset of the given point.
   1867   */
   1868  void InvalidateOffset() {
   1869    if (mChild) {
   1870      mPoint.Set(mChild);
   1871    } else {
   1872      // If the point referred after the last child, let's keep referring
   1873      // after current last node of the old container.
   1874      mPoint.SetToEndOf(mPoint.GetContainer());
   1875    }
   1876  }
   1877 
   1878  /**
   1879   * After calling Cancel(), mPoint won't be modified by the destructor.
   1880   */
   1881  void Cancel() { mCanceled = true; }
   1882 
   1883 private:
   1884  EditorDOMPoint& mPoint;
   1885  // Needs to store child node by ourselves because EditorDOMPoint stores
   1886  // child node with mRef which is previous sibling of current child node.
   1887  // Therefore, we cannot keep referring it if it's first child.
   1888  nsCOMPtr<nsIContent> mChild;
   1889 
   1890  bool mCanceled;
   1891 };
   1892 
   1893 class MOZ_STACK_CLASS AutoEditorDOMRangeOffsetsInvalidator final {
   1894 public:
   1895  explicit AutoEditorDOMRangeOffsetsInvalidator(EditorDOMRange& aRange)
   1896      : mStartInvalidator(const_cast<EditorDOMPoint&>(aRange.StartRef())),
   1897        mEndInvalidator(const_cast<EditorDOMPoint&>(aRange.EndRef())) {}
   1898 
   1899  void InvalidateOffsets() {
   1900    mStartInvalidator.InvalidateOffset();
   1901    mEndInvalidator.InvalidateOffset();
   1902  }
   1903 
   1904  void Cancel() {
   1905    mStartInvalidator.Cancel();
   1906    mEndInvalidator.Cancel();
   1907  }
   1908 
   1909 private:
   1910  AutoEditorDOMPointOffsetInvalidator mStartInvalidator;
   1911  AutoEditorDOMPointOffsetInvalidator mEndInvalidator;
   1912 };
   1913 
   1914 /**
   1915 * AutoEditorDOMPointChildInvalidator is useful if DOM tree will be changed
   1916 * when EditorDOMPoint instance is available and keeps referring same container
   1917 * and offset in it.
   1918 *
   1919 * This class automatically guarantees that given EditorDOMPoint instance
   1920 * stores offset and invalidates its child node when the instance is destroyed.
   1921 * Additionally, users of this class can invalidate the child manually when
   1922 * they need.
   1923 */
   1924 class MOZ_STACK_CLASS AutoEditorDOMPointChildInvalidator final {
   1925 public:
   1926  AutoEditorDOMPointChildInvalidator() = delete;
   1927  AutoEditorDOMPointChildInvalidator(
   1928      const AutoEditorDOMPointChildInvalidator&) = delete;
   1929  AutoEditorDOMPointChildInvalidator(AutoEditorDOMPointChildInvalidator&&) =
   1930      delete;
   1931  const AutoEditorDOMPointChildInvalidator& operator=(
   1932      const AutoEditorDOMPointChildInvalidator&) = delete;
   1933  explicit AutoEditorDOMPointChildInvalidator(EditorDOMPoint& aPoint)
   1934      : mPoint(aPoint), mCanceled(false) {
   1935    MOZ_ASSERT(aPoint.IsSetAndValid());
   1936    (void)mPoint.Offset();
   1937  }
   1938 
   1939  ~AutoEditorDOMPointChildInvalidator() {
   1940    if (!mCanceled) {
   1941      InvalidateChild();
   1942    }
   1943  }
   1944 
   1945  /**
   1946   * Manually, invalidate child of the given point.
   1947   */
   1948  void InvalidateChild() { mPoint.Set(mPoint.GetContainer(), mPoint.Offset()); }
   1949 
   1950  /**
   1951   * After calling Cancel(), mPoint won't be modified by the destructor.
   1952   */
   1953  void Cancel() { mCanceled = true; }
   1954 
   1955 private:
   1956  EditorDOMPoint& mPoint;
   1957 
   1958  bool mCanceled;
   1959 };
   1960 
   1961 class MOZ_STACK_CLASS AutoEditorDOMRangeChildrenInvalidator final {
   1962 public:
   1963  explicit AutoEditorDOMRangeChildrenInvalidator(EditorDOMRange& aRange)
   1964      : mStartInvalidator(const_cast<EditorDOMPoint&>(aRange.StartRef())),
   1965        mEndInvalidator(const_cast<EditorDOMPoint&>(aRange.EndRef())) {}
   1966 
   1967  void InvalidateChildren() {
   1968    mStartInvalidator.InvalidateChild();
   1969    mEndInvalidator.InvalidateChild();
   1970  }
   1971 
   1972  void Cancel() {
   1973    mStartInvalidator.Cancel();
   1974    mEndInvalidator.Cancel();
   1975  }
   1976 
   1977 private:
   1978  AutoEditorDOMPointChildInvalidator mStartInvalidator;
   1979  AutoEditorDOMPointChildInvalidator mEndInvalidator;
   1980 };
   1981 
   1982 }  // namespace mozilla
   1983 
   1984 #endif  // #ifndef mozilla_EditorDOMPoint_h