tor-browser

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

SelectionState.h (26672B)


      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_SelectionState_h
      7 #define mozilla_SelectionState_h
      8 
      9 #include "mozilla/EditorDOMPoint.h"
     10 #include "mozilla/EditorForwards.h"
     11 #include "mozilla/Maybe.h"
     12 #include "mozilla/OwningNonNull.h"
     13 #include "mozilla/dom/Document.h"
     14 #include "nsCOMPtr.h"
     15 #include "nsDirection.h"
     16 #include "nsINode.h"
     17 #include "nsRange.h"
     18 #include "nsTArray.h"
     19 #include "nscore.h"
     20 
     21 class nsCycleCollectionTraversalCallback;
     22 class nsRange;
     23 namespace mozilla {
     24 namespace dom {
     25 class Element;
     26 class Selection;
     27 class Text;
     28 }  // namespace dom
     29 
     30 /**
     31 * A helper struct for saving/setting ranges.
     32 */
     33 struct RangeItem final {
     34  RangeItem() : mStartOffset(0), mEndOffset(0) {}
     35 
     36 private:
     37  // Private destructor, to discourage deletion outside of Release():
     38  ~RangeItem() = default;
     39 
     40 public:
     41  void StoreRange(const nsRange& aRange);
     42  void StoreRange(const EditorRawDOMPoint& aStartPoint,
     43                  const EditorRawDOMPoint& aEndPoint) {
     44    MOZ_ASSERT(aStartPoint.IsSet());
     45    MOZ_ASSERT(aEndPoint.IsSet());
     46    mStartContainer = aStartPoint.GetContainer();
     47    mStartOffset = aStartPoint.Offset();
     48    mEndContainer = aEndPoint.GetContainer();
     49    mEndOffset = aEndPoint.Offset();
     50  }
     51  void Clear() {
     52    mStartContainer = mEndContainer = nullptr;
     53    mStartOffset = mEndOffset = 0;
     54  }
     55  already_AddRefed<nsRange> GetRange() const;
     56 
     57  // Same as the API of dom::AbstractRange
     58  [[nodiscard]] nsINode* GetRoot() const;
     59  [[nodiscard]] bool Collapsed() const {
     60    return mStartContainer == mEndContainer && mStartOffset == mEndOffset;
     61  }
     62  [[nodiscard]] bool IsPositioned() const {
     63    return mStartContainer && mEndContainer;
     64  }
     65  [[nodiscard]] bool Equals(const RangeItem& aOther) const {
     66    return mStartContainer == aOther.mStartContainer &&
     67           mEndContainer == aOther.mEndContainer &&
     68           mStartOffset == aOther.mStartOffset &&
     69           mEndOffset == aOther.mEndOffset;
     70  }
     71  template <typename EditorDOMPointType = EditorDOMPoint>
     72  EditorDOMPointType StartPoint() const {
     73    return EditorDOMPointType(mStartContainer, mStartOffset);
     74  }
     75  template <typename EditorDOMPointType = EditorDOMPoint>
     76  EditorDOMPointType EndPoint() const {
     77    return EditorDOMPointType(mEndContainer, mEndOffset);
     78  }
     79 
     80  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(RangeItem)
     81  NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(RangeItem)
     82 
     83  nsCOMPtr<nsINode> mStartContainer;
     84  nsCOMPtr<nsINode> mEndContainer;
     85  uint32_t mStartOffset;
     86  uint32_t mEndOffset;
     87 };
     88 
     89 /**
     90 * mozilla::SelectionState
     91 *
     92 * Class for recording selection info.  Stores selection as collection of
     93 * { {startnode, startoffset} , {endnode, endoffset} } tuples.  Can't store
     94 * ranges since dom gravity will possibly change the ranges.
     95 */
     96 
     97 class SelectionState final {
     98 public:
     99  SelectionState() = default;
    100  explicit SelectionState(const AutoClonedSelectionRangeArray& aRanges);
    101 
    102  /**
    103   * Same as the API as dom::Selection
    104   */
    105  [[nodiscard]] bool IsCollapsed() const {
    106    if (mArray.Length() != 1) {
    107      return false;
    108    }
    109    return mArray[0]->Collapsed();
    110  }
    111 
    112  void RemoveAllRanges() {
    113    mArray.Clear();
    114    mDirection = eDirNext;
    115  }
    116 
    117  [[nodiscard]] uint32_t RangeCount() const { return mArray.Length(); }
    118 
    119  /**
    120   * Saving all ranges of aSelection.
    121   */
    122  void SaveSelection(dom::Selection& aSelection);
    123 
    124  /**
    125   * Setting aSelection to have all ranges stored by this instance.
    126   */
    127  MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
    128  RestoreSelection(dom::Selection& aSelection);
    129 
    130  /**
    131   * Setting aRanges to have all ranges stored by this instance.
    132   */
    133  void ApplyTo(AutoClonedSelectionRangeArray& aRanges);
    134 
    135  /**
    136   * HasOnlyCollapsedRange() returns true only when there is a positioned range
    137   * which is collapsed.  I.e., the selection represents a caret point.
    138   */
    139  [[nodiscard]] bool HasOnlyCollapsedRange() const {
    140    if (mArray.Length() != 1) {
    141      return false;
    142    }
    143    if (!mArray[0]->IsPositioned() || !mArray[0]->Collapsed()) {
    144      return false;
    145    }
    146    return true;
    147  }
    148 
    149  /**
    150   * Equals() returns true only when there are same number of ranges and
    151   * all their containers and offsets are exactly same.  This won't check
    152   * the validity of each range with the current DOM tree.
    153   */
    154  [[nodiscard]] bool Equals(const SelectionState& aOther) const;
    155 
    156  /**
    157   * Returns common root node of all ranges' start and end containers.
    158   * Some of them have different root nodes, this returns nullptr.
    159   */
    160  [[nodiscard]] nsINode* GetCommonRootNode() const {
    161    nsINode* rootNode = nullptr;
    162    for (const RefPtr<RangeItem>& rangeItem : mArray) {
    163      nsINode* newRootNode = rangeItem->GetRoot();
    164      if (!newRootNode || (rootNode && rootNode != newRootNode)) {
    165        return nullptr;
    166      }
    167      rootNode = newRootNode;
    168    }
    169    return rootNode;
    170  }
    171 
    172 private:
    173  CopyableAutoTArray<RefPtr<RangeItem>, 1> mArray;
    174  nsDirection mDirection = eDirNext;
    175 
    176  friend class RangeUpdater;
    177  friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
    178                                          SelectionState&, const char*,
    179                                          uint32_t);
    180  friend void ImplCycleCollectionUnlink(SelectionState&);
    181 };
    182 
    183 inline void ImplCycleCollectionTraverse(
    184    nsCycleCollectionTraversalCallback& aCallback, SelectionState& aField,
    185    const char* aName, uint32_t aFlags = 0) {
    186  ImplCycleCollectionTraverse(aCallback, aField.mArray, aName, aFlags);
    187 }
    188 
    189 inline void ImplCycleCollectionUnlink(SelectionState& aField) {
    190  ImplCycleCollectionUnlink(aField.mArray);
    191 }
    192 
    193 class MOZ_STACK_CLASS RangeUpdater final {
    194 public:
    195  RangeUpdater();
    196 
    197  void RegisterRangeItem(RangeItem& aRangeItem);
    198  void DropRangeItem(RangeItem& aRangeItem);
    199  void RegisterSelectionState(SelectionState& aSelectionState);
    200  void DropSelectionState(SelectionState& aSelectionState);
    201 
    202  // editor selection gravity routines.  Note that we can't always depend on
    203  // DOM Range gravity to do what we want to the "real" selection.  For
    204  // instance, if you move a node, that corresponds to deleting it and
    205  // reinserting it. DOM Range gravity will promote the selection out of the
    206  // node on deletion, which is not what you want if you know you are
    207  // reinserting it.
    208  template <typename PT, typename CT>
    209  nsresult SelAdjCreateNode(const EditorDOMPointBase<PT, CT>& aPoint);
    210  template <typename PT, typename CT>
    211  nsresult SelAdjInsertNode(const EditorDOMPointBase<PT, CT>& aPoint);
    212  void SelAdjDeleteNode(nsINode& aNode);
    213 
    214  /**
    215   * SelAdjSplitNode() is called immediately after spliting aOriginalNode
    216   * and inserted aNewContent into the DOM tree.
    217   *
    218   * @param aOriginalContent    The node which was split.
    219   * @param aSplitOffset        The old offset in aOriginalContent at splitting
    220   *                            it.
    221   * @param aNewContent         The new content node which was inserted into
    222   *                            the DOM tree.
    223   */
    224  nsresult SelAdjSplitNode(nsIContent& aOriginalContent, uint32_t aSplitOffset,
    225                           nsIContent& aNewContent);
    226 
    227  /**
    228   * SelAdjJoinNodes() is called immediately after joining aRemovedContent and
    229   * the container of aStartOfRightContent.
    230   *
    231   * @param aStartOfRightContent    The container is joined content node which
    232   *                                now has all children or text data which were
    233   *                                in aRemovedContent.  And this points where
    234   *                                the joined position.
    235   * @param aRemovedContent         The removed content.
    236   * @param aOldPointAtRightContent The point where the right content node was
    237   *                                before joining them.  The offset must have
    238   *                                been initialized before the joining.
    239   */
    240  nsresult SelAdjJoinNodes(const EditorRawDOMPoint& aStartOfRightContent,
    241                           const nsIContent& aRemovedContent,
    242                           const EditorDOMPoint& aOldPointAtRightContent);
    243  void SelAdjInsertText(const dom::Text& aTextNode, uint32_t aOffset,
    244                        uint32_t aInsertedLength);
    245  void SelAdjDeleteText(const dom::Text& aTextNode, uint32_t aOffset,
    246                        uint32_t aDeletedLength);
    247  void SelAdjReplaceText(const dom::Text& aTextNode, uint32_t aOffset,
    248                         uint32_t aReplacedLength, uint32_t aInsertedLength);
    249  // the following gravity routines need will/did sandwiches, because the other
    250  // gravity routines will be called inside of these sandwiches, but should be
    251  // ignored.
    252  void WillReplaceContainer() {
    253    // XXX Isn't this possible with mutation event listener?
    254    NS_WARNING_ASSERTION(!mLocked, "Has already been locked");
    255    mLocked = true;
    256  }
    257  void DidReplaceContainer(const dom::Element& aRemovedElement,
    258                           dom::Element& aInsertedElement);
    259  void WillRemoveContainer() {
    260    // XXX Isn't this possible with mutation event listener?
    261    NS_WARNING_ASSERTION(!mLocked, "Has already been locked");
    262    mLocked = true;
    263  }
    264  void DidRemoveContainer(const dom::Element& aRemovedElement,
    265                          nsINode& aRemovedElementContainerNode,
    266                          uint32_t aOldOffsetOfRemovedElement,
    267                          uint32_t aOldChildCountOfRemovedElement);
    268  void WillInsertContainer() {
    269    // XXX Isn't this possible with mutation event listener?
    270    NS_WARNING_ASSERTION(!mLocked, "Has already been locked");
    271    mLocked = true;
    272  }
    273  void DidInsertContainer() {
    274    NS_WARNING_ASSERTION(mLocked, "Not locked");
    275    mLocked = false;
    276  }
    277  template <typename PT, typename CT>
    278  struct SimpleEditorDOMPointBase {
    279    SimpleEditorDOMPointBase() = default;
    280    SimpleEditorDOMPointBase(const nsINode* aContainer,
    281                             const nsIContent* aChild, uint32_t aOffset)
    282        : mContainer(const_cast<nsINode*>(aContainer)),
    283          mChild(const_cast<nsIContent*>(aChild)),
    284          mOffset(Some(aOffset)) {}
    285    SimpleEditorDOMPointBase(const nsIContent* aChild, uint32_t aOffset)
    286        : mContainer(aChild->GetParentNode()),
    287          mChild(const_cast<nsIContent*>(aChild)),
    288          mOffset(Some(aOffset)) {}
    289    SimpleEditorDOMPointBase(const nsINode* aContainer,
    290                             const nsIContent* aChild)
    291        : mContainer(const_cast<nsINode*>(aContainer)),
    292          mChild(const_cast<nsIContent*>(aChild)) {}
    293    explicit SimpleEditorDOMPointBase(const nsIContent* aChild)
    294        : mContainer(aChild->GetParentNode()),
    295          mChild(const_cast<nsIContent*>(aChild)) {}
    296 
    297    uint32_t Offset() const {
    298      if (!mOffset && mContainer) {
    299        mOffset = mContainer->ComputeIndexOf(mChild);
    300      }
    301      return mOffset.valueOr(0);
    302    }
    303    nsIContent* GetNextSiblingOfChild() const {
    304      return mChild->GetNextSibling();
    305    }
    306    PT mContainer;
    307    CT mChild;
    308    mutable Maybe<uint32_t> mOffset;
    309  };
    310  using SimpleEditorDOMPoint =
    311      SimpleEditorDOMPointBase<nsCOMPtr<nsINode>, nsCOMPtr<nsIContent>>;
    312  using SimpleEditorRawDOMPoint =
    313      SimpleEditorDOMPointBase<nsINode*, nsIContent*>;
    314  void DidMoveNodes(const nsTArray<SimpleEditorDOMPoint>& aOldPoints,
    315                    const SimpleEditorDOMPoint& aExpectedDestination,
    316                    const nsTArray<SimpleEditorDOMPoint>& aNewPoints);
    317 
    318 private:
    319  // TODO: A lot of loop in these methods check whether each item `nullptr` or
    320  //       not. We should make it not nullable later.
    321  nsTArray<RefPtr<RangeItem>> mArray;
    322  bool mLocked;
    323 };
    324 
    325 enum class StopTracking : bool { No, Yes };
    326 
    327 /**
    328 * Helper class for using SelectionState.  Stack based class for doing
    329 * preservation of dom points across editor actions.
    330 */
    331 
    332 class MOZ_STACK_CLASS AutoTrackDOMPoint final {
    333 public:
    334  AutoTrackDOMPoint() = delete;
    335 
    336  AutoTrackDOMPoint(RangeUpdater& aRangeUpdater, CaretPoint* aCaretPoint);
    337 
    338  AutoTrackDOMPoint(RangeUpdater& aRangeUpdater, nsCOMPtr<nsINode>* aNode,
    339                    uint32_t* aOffset)
    340      : mRangeUpdater(aRangeUpdater),
    341        mNode(aNode),
    342        mOffset(aOffset),
    343        mRangeItem(do_AddRef(new RangeItem())),
    344        mWasConnected(aNode && (*aNode)->IsInComposedDoc()) {
    345    mRangeItem->mStartContainer = *mNode;
    346    mRangeItem->mEndContainer = *mNode;
    347    mRangeItem->mStartOffset = *mOffset;
    348    mRangeItem->mEndOffset = *mOffset;
    349    mDocument = (*mNode)->OwnerDoc();
    350    mRangeUpdater.RegisterRangeItem(mRangeItem);
    351  }
    352 
    353  AutoTrackDOMPoint(RangeUpdater& aRangeUpdater, EditorDOMPoint* aPoint)
    354      : mRangeUpdater(aRangeUpdater),
    355        mNode(nullptr),
    356        mOffset(nullptr),
    357        mPoint(Some(aPoint->IsSet() ? aPoint : nullptr)),
    358        mRangeItem(do_AddRef(new RangeItem())),
    359        mWasConnected(aPoint && aPoint->IsInComposedDoc()) {
    360    if (!aPoint->IsSet()) {
    361      mIsTracking = false;
    362      return;  // Nothing should be tracked.
    363    }
    364    mRangeItem->mStartContainer = aPoint->GetContainer();
    365    mRangeItem->mEndContainer = aPoint->GetContainer();
    366    mRangeItem->mStartOffset = aPoint->Offset();
    367    mRangeItem->mEndOffset = aPoint->Offset();
    368    mDocument = aPoint->GetContainer()->OwnerDoc();
    369    mRangeUpdater.RegisterRangeItem(mRangeItem);
    370  }
    371 
    372  ~AutoTrackDOMPoint() { FlushAndStopTracking(); }
    373 
    374  void Flush(StopTracking aStopTracking) {
    375    if (!mIsTracking) {
    376      return;
    377    }
    378    if (static_cast<bool>(aStopTracking)) {
    379      mIsTracking = false;
    380    }
    381    if (mPoint.isSome()) {
    382      mRangeUpdater.DropRangeItem(mRangeItem);
    383      // Setting `mPoint` with invalid DOM point causes hitting `NS_ASSERTION()`
    384      // and the number of times may be too many.  (E.g., 1533913.html hits
    385      // over 700 times!)  We should just put warning instead.
    386      if (NS_WARN_IF(!mRangeItem->mStartContainer)) {
    387        mPoint.ref()->Clear();
    388        return;
    389      }
    390      // If the node was removed from the original document, clear the instance
    391      // since the user should not keep handling the adopted or orphan node
    392      // anymore.
    393      if (NS_WARN_IF(mWasConnected &&
    394                     !mRangeItem->mStartContainer->IsInComposedDoc()) ||
    395          NS_WARN_IF(mRangeItem->mStartContainer->OwnerDoc() != mDocument)) {
    396        mPoint.ref()->Clear();
    397        return;
    398      }
    399      if (NS_WARN_IF(mRangeItem->mStartContainer->Length() <
    400                     mRangeItem->mStartOffset)) {
    401        mPoint.ref()->SetToEndOf(mRangeItem->mStartContainer);
    402        return;
    403      }
    404      mPoint.ref()->Set(mRangeItem->mStartContainer, mRangeItem->mStartOffset);
    405      return;
    406    }
    407    mRangeUpdater.DropRangeItem(mRangeItem);
    408    *mNode = mRangeItem->mStartContainer;
    409    *mOffset = mRangeItem->mStartOffset;
    410    if (!(*mNode)) {
    411      return;
    412    }
    413    // If the node was removed from the original document, clear the instances
    414    // since the user should not keep handling the adopted or orphan node
    415    // anymore.
    416    if (NS_WARN_IF(mWasConnected && !(*mNode)->IsInComposedDoc()) ||
    417        NS_WARN_IF((*mNode)->OwnerDoc() != mDocument)) {
    418      *mNode = nullptr;
    419      *mOffset = 0;
    420    }
    421  }
    422 
    423  void FlushAndStopTracking() { Flush(StopTracking::Yes); }
    424 
    425  void StopTracking() { mIsTracking = false; }
    426 
    427 private:
    428  RangeUpdater& mRangeUpdater;
    429  // Allow tracking nsINode until nsNode is gone
    430  nsCOMPtr<nsINode>* mNode;
    431  uint32_t* mOffset;
    432  Maybe<EditorDOMPoint*> mPoint;
    433  OwningNonNull<RangeItem> mRangeItem;
    434  RefPtr<dom::Document> mDocument;
    435  bool mIsTracking = true;
    436  bool mWasConnected;
    437 };
    438 
    439 class MOZ_STACK_CLASS AutoTrackDOMRange final {
    440 public:
    441  AutoTrackDOMRange() = delete;
    442  AutoTrackDOMRange(RangeUpdater& aRangeUpdater, EditorDOMPoint* aStartPoint,
    443                    EditorDOMPoint* aEndPoint)
    444      : mRangeRefPtr(nullptr), mRangeOwningNonNull(nullptr) {
    445    mStartPointTracker.emplace(aRangeUpdater, aStartPoint);
    446    mEndPointTracker.emplace(aRangeUpdater, aEndPoint);
    447  }
    448  AutoTrackDOMRange(RangeUpdater& aRangeUpdater, EditorDOMRange* aRange)
    449      : mRangeRefPtr(nullptr), mRangeOwningNonNull(nullptr) {
    450    mStartPointTracker.emplace(
    451        aRangeUpdater, const_cast<EditorDOMPoint*>(&aRange->StartRef()));
    452    mEndPointTracker.emplace(aRangeUpdater,
    453                             const_cast<EditorDOMPoint*>(&aRange->EndRef()));
    454  }
    455  AutoTrackDOMRange(RangeUpdater& aRangeUpdater, const RefPtr<nsRange>* aRange)
    456      : mStartPoint((*aRange)->StartRef()),
    457        mEndPoint((*aRange)->EndRef()),
    458        mRangeRefPtr(aRange),
    459        mRangeOwningNonNull(nullptr) {
    460    mStartPointTracker.emplace(aRangeUpdater, &mStartPoint);
    461    mEndPointTracker.emplace(aRangeUpdater, &mEndPoint);
    462  }
    463  AutoTrackDOMRange(RangeUpdater& aRangeUpdater,
    464                    const OwningNonNull<nsRange>* aRange)
    465      : mStartPoint((*aRange)->StartRef()),
    466        mEndPoint((*aRange)->EndRef()),
    467        mRangeRefPtr(nullptr),
    468        mRangeOwningNonNull(aRange) {
    469    mStartPointTracker.emplace(aRangeUpdater, &mStartPoint);
    470    mEndPointTracker.emplace(aRangeUpdater, &mEndPoint);
    471  }
    472  ~AutoTrackDOMRange() { FlushAndStopTracking(); }
    473 
    474  void FlushAndStopTracking() {
    475    if (!mStartPointTracker && !mEndPointTracker) {
    476      return;
    477    }
    478    mStartPointTracker.reset();
    479    mEndPointTracker.reset();
    480    if (!mRangeRefPtr && !mRangeOwningNonNull) {
    481      // This must be created with EditorDOMRange or EditorDOMPoints.  In the
    482      // cases, destroying mStartPointTracker and mEndPointTracker has done
    483      // everything which we need to do.
    484      return;
    485    }
    486    // Otherwise, update the DOM ranges by ourselves.
    487    if (mRangeRefPtr) {
    488      if (!mStartPoint.IsSet() || !mEndPoint.IsSet()) {
    489        (*mRangeRefPtr)->Reset();
    490        return;
    491      }
    492      (*mRangeRefPtr)
    493          ->SetStartAndEnd(mStartPoint.ToRawRangeBoundary(),
    494                           mEndPoint.ToRawRangeBoundary());
    495      return;
    496    }
    497    if (mRangeOwningNonNull) {
    498      if (!mStartPoint.IsSet() || !mEndPoint.IsSet()) {
    499        (*mRangeOwningNonNull)->Reset();
    500        return;
    501      }
    502      (*mRangeOwningNonNull)
    503          ->SetStartAndEnd(mStartPoint.ToRawRangeBoundary(),
    504                           mEndPoint.ToRawRangeBoundary());
    505      return;
    506    }
    507  }
    508 
    509  void StopTracking() {
    510    if (mStartPointTracker) {
    511      mStartPointTracker->StopTracking();
    512    }
    513    if (mEndPointTracker) {
    514      mEndPointTracker->StopTracking();
    515    }
    516  }
    517  void StopTrackingStartBoundary() {
    518    MOZ_ASSERT(!mRangeRefPtr,
    519               "StopTrackingStartBoundary() is not available when tracking "
    520               "RefPtr<nsRange>");
    521    MOZ_ASSERT(!mRangeOwningNonNull,
    522               "StopTrackingStartBoundary() is not available when tracking "
    523               "OwningNonNull<nsRange>");
    524    if (!mStartPointTracker) {
    525      return;
    526    }
    527    mStartPointTracker->StopTracking();
    528  }
    529  void StopTrackingEndBoundary() {
    530    MOZ_ASSERT(!mRangeRefPtr,
    531               "StopTrackingEndBoundary() is not available when tracking "
    532               "RefPtr<nsRange>");
    533    MOZ_ASSERT(!mRangeOwningNonNull,
    534               "StopTrackingEndBoundary() is not available when tracking "
    535               "OwningNonNull<nsRange>");
    536    if (!mEndPointTracker) {
    537      return;
    538    }
    539    mEndPointTracker->StopTracking();
    540  }
    541 
    542 private:
    543  Maybe<AutoTrackDOMPoint> mStartPointTracker;
    544  Maybe<AutoTrackDOMPoint> mEndPointTracker;
    545  EditorDOMPoint mStartPoint;
    546  EditorDOMPoint mEndPoint;
    547  const RefPtr<nsRange>* mRangeRefPtr;
    548  const OwningNonNull<nsRange>* mRangeOwningNonNull;
    549 };
    550 
    551 class MOZ_STACK_CLASS AutoTrackDOMMoveNodeResult final {
    552 public:
    553  AutoTrackDOMMoveNodeResult() = delete;
    554  AutoTrackDOMMoveNodeResult(RangeUpdater& aRangeUpdater,
    555                             MoveNodeResult* aMoveNodeResult);
    556 
    557  void FlushAndStopTracking() {
    558    mTrackCaretPoint.FlushAndStopTracking();
    559    mTrackNextInsertionPoint.FlushAndStopTracking();
    560    mTrackMovedContentRange.FlushAndStopTracking();
    561  }
    562  void StopTracking() {
    563    mTrackCaretPoint.StopTracking();
    564    mTrackNextInsertionPoint.StopTracking();
    565    mTrackMovedContentRange.StopTracking();
    566  }
    567 
    568 private:
    569  AutoTrackDOMPoint mTrackCaretPoint;
    570  AutoTrackDOMPoint mTrackNextInsertionPoint;
    571  AutoTrackDOMRange mTrackMovedContentRange;
    572 };
    573 
    574 class MOZ_STACK_CLASS AutoTrackDOMDeleteRangeResult final {
    575 public:
    576  AutoTrackDOMDeleteRangeResult() = delete;
    577  AutoTrackDOMDeleteRangeResult(RangeUpdater& aRangeUpdater,
    578                                DeleteRangeResult* aDeleteRangeResult);
    579 
    580  void FlushAndStopTracking() {
    581    mTrackCaretPoint.FlushAndStopTracking();
    582    mTrackDeleteRange.FlushAndStopTracking();
    583  }
    584  void StopTracking() {
    585    mTrackCaretPoint.StopTracking();
    586    mTrackDeleteRange.StopTracking();
    587  }
    588 
    589 private:
    590  AutoTrackDOMPoint mTrackCaretPoint;
    591  AutoTrackDOMRange mTrackDeleteRange;
    592 };
    593 
    594 class MOZ_STACK_CLASS AutoTrackLineBreak final {
    595 public:
    596  AutoTrackLineBreak() = delete;
    597  AutoTrackLineBreak(RangeUpdater& aRangeUpdater, EditorLineBreak* aLineBreak);
    598 
    599  void FlushAndStopTracking();
    600  void StopTracking() { mTracker.StopTracking(); }
    601 
    602 private:
    603  EditorLineBreak* mLineBreak;
    604  EditorDOMPoint mPoint;
    605  AutoTrackDOMPoint mTracker;
    606 };
    607 
    608 /**
    609 * Another helper class for SelectionState.  Stack based class for doing
    610 * Will/DidReplaceContainer()
    611 */
    612 
    613 class MOZ_STACK_CLASS AutoReplaceContainerSelNotify final {
    614 public:
    615  AutoReplaceContainerSelNotify() = delete;
    616  // FYI: Marked as `MOZ_CAN_RUN_SCRIPT` for avoiding to use strong pointers
    617  //      for the members.
    618  MOZ_CAN_RUN_SCRIPT
    619  AutoReplaceContainerSelNotify(RangeUpdater& aRangeUpdater,
    620                                dom::Element& aOriginalElement,
    621                                dom::Element& aNewElement)
    622      : mRangeUpdater(aRangeUpdater),
    623        mOriginalElement(aOriginalElement),
    624        mNewElement(aNewElement) {
    625    mRangeUpdater.WillReplaceContainer();
    626  }
    627 
    628  ~AutoReplaceContainerSelNotify() {
    629    mRangeUpdater.DidReplaceContainer(mOriginalElement, mNewElement);
    630  }
    631 
    632 private:
    633  RangeUpdater& mRangeUpdater;
    634  dom::Element& mOriginalElement;
    635  dom::Element& mNewElement;
    636 };
    637 
    638 /**
    639 * Another helper class for SelectionState.  Stack based class for doing
    640 * Will/DidRemoveContainer()
    641 */
    642 
    643 class MOZ_STACK_CLASS AutoRemoveContainerSelNotify final {
    644 public:
    645  AutoRemoveContainerSelNotify() = delete;
    646  AutoRemoveContainerSelNotify(RangeUpdater& aRangeUpdater,
    647                               const EditorRawDOMPoint& aAtRemovingElement)
    648      : mRangeUpdater(aRangeUpdater),
    649        mRemovingElement(*aAtRemovingElement.GetChild()->AsElement()),
    650        mParentNode(*aAtRemovingElement.GetContainer()),
    651        mOffsetInParent(aAtRemovingElement.Offset()),
    652        mChildCountOfRemovingElement(mRemovingElement->GetChildCount()) {
    653    MOZ_ASSERT(aAtRemovingElement.IsSet());
    654    mRangeUpdater.WillRemoveContainer();
    655  }
    656 
    657  ~AutoRemoveContainerSelNotify() {
    658    mRangeUpdater.DidRemoveContainer(mRemovingElement, mParentNode,
    659                                     mOffsetInParent,
    660                                     mChildCountOfRemovingElement);
    661  }
    662 
    663 private:
    664  RangeUpdater& mRangeUpdater;
    665  OwningNonNull<dom::Element> mRemovingElement;
    666  OwningNonNull<nsINode> mParentNode;
    667  uint32_t mOffsetInParent;
    668  uint32_t mChildCountOfRemovingElement;
    669 };
    670 
    671 /**
    672 * Another helper class for SelectionState.  Stack based class for doing
    673 * Will/DidInsertContainer()
    674 * XXX The lock state isn't useful if the edit action is triggered from
    675 *     a mutation event listener so that looks like that we can remove
    676 *     this class.
    677 */
    678 
    679 class MOZ_STACK_CLASS AutoInsertContainerSelNotify final {
    680 private:
    681  RangeUpdater& mRangeUpdater;
    682 
    683 public:
    684  AutoInsertContainerSelNotify() = delete;
    685  explicit AutoInsertContainerSelNotify(RangeUpdater& aRangeUpdater)
    686      : mRangeUpdater(aRangeUpdater) {
    687    mRangeUpdater.WillInsertContainer();
    688  }
    689 
    690  ~AutoInsertContainerSelNotify() { mRangeUpdater.DidInsertContainer(); }
    691 };
    692 
    693 /**
    694 * Another helper class for SelectionState.  Stack based class for doing
    695 * DidMoveNode()
    696 */
    697 
    698 class MOZ_STACK_CLASS AutoMoveNodeSelNotify final {
    699 public:
    700  using SimpleEditorDOMPoint = RangeUpdater::SimpleEditorDOMPoint;
    701 
    702  AutoMoveNodeSelNotify() = delete;
    703  explicit AutoMoveNodeSelNotify(RangeUpdater& aRangeUpdater,
    704                                 const EditorRawDOMPoint& aExpectedDestination)
    705      : mRangeUpdater(aRangeUpdater),
    706        mExpectedDestination(aExpectedDestination.GetContainer(), nullptr,
    707                             aExpectedDestination.Offset()) {}
    708  AutoMoveNodeSelNotify(RangeUpdater& aRangeUpdater, nsIContent& aContent,
    709                        const EditorRawDOMPoint& aExpectedDestination)
    710      : mRangeUpdater(aRangeUpdater),
    711        mExpectedDestination(aExpectedDestination.GetContainer(), nullptr,
    712                             aExpectedDestination.Offset()) {
    713    if (aContent.GetParentNode()) {
    714      mOldPoints.AppendElement(SimpleEditorDOMPoint(
    715          &aContent, aContent.ComputeIndexInParentNode().valueOr(0)));
    716      return;
    717    }
    718    mOldPoints.AppendElement(SimpleEditorDOMPoint(&aContent));
    719  }
    720 
    721  void AppendContentWhichWillBeMoved(nsIContent& aContent) {
    722    if (!mOldPoints.IsEmpty() &&
    723        mOldPoints.LastElement().GetNextSiblingOfChild() == &aContent) {
    724      mOldPoints.AppendElement(SimpleEditorDOMPoint(
    725          &aContent, mOldPoints.LastElement().Offset() + 1));
    726      return;
    727    }
    728    if (aContent.GetParentNode()) {
    729      mOldPoints.AppendElement(SimpleEditorDOMPoint(
    730          &aContent, aContent.ComputeIndexInParentNode().valueOr(0)));
    731      return;
    732    }
    733    mOldPoints.AppendElement(SimpleEditorDOMPoint(&aContent));
    734  }
    735 
    736  void DidMoveContent(nsIContent& aContent) {
    737    if (!mNewPoints.IsEmpty() &&
    738        mNewPoints.LastElement().GetNextSiblingOfChild() == &aContent) {
    739      mNewPoints.AppendElement(SimpleEditorDOMPoint(
    740          &aContent, mNewPoints.LastElement().Offset() + 1));
    741      return;
    742    }
    743    // Compute offset when we need it.
    744    mNewPoints.AppendElement(SimpleEditorDOMPoint(&aContent));
    745  }
    746 
    747  ~AutoMoveNodeSelNotify() {
    748    mRangeUpdater.DidMoveNodes(mOldPoints, mExpectedDestination, mNewPoints);
    749  }
    750 
    751  [[nodiscard]] size_t MovingContentCount() const {
    752    return mOldPoints.Length();
    753  }
    754  [[nodiscard]] nsIContent* GetContentAt(size_t index) const {
    755    return mOldPoints[index].mChild;
    756  }
    757 
    758 private:
    759  RangeUpdater& mRangeUpdater;
    760  SimpleEditorDOMPoint mExpectedDestination;
    761  AutoTArray<SimpleEditorDOMPoint, 12> mOldPoints;
    762  AutoTArray<SimpleEditorDOMPoint, 12> mNewPoints;
    763 };
    764 
    765 }  // namespace mozilla
    766 
    767 #endif  // #ifndef mozilla_SelectionState_h