tor-browser

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

AutoClonedRangeArray.h (24884B)


      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 AutoClonedRangeArray_h
      7 #define AutoClonedRangeArray_h
      8 
      9 #include "EditAction.h"      // for EditSubAction
     10 #include "EditorBase.h"      // for EditorBase
     11 #include "EditorDOMPoint.h"  // for EditorDOMPoint, EditorDOMRange, etc
     12 #include "EditorForwards.h"
     13 #include "HTMLEditHelpers.h"  // for BlockInlineCheck
     14 #include "HTMLEditor.h"       // for HTMLEditor
     15 #include "SelectionState.h"   // for SelectionState
     16 
     17 #include "mozilla/ErrorResult.h"              // for ErrorResult
     18 #include "mozilla/IntegerRange.h"             // for IntegerRange
     19 #include "mozilla/Maybe.h"                    // for Maybe
     20 #include "mozilla/RangeBoundary.h"            // for RangeBoundary
     21 #include "mozilla/Result.h"                   // for Result<>
     22 #include "mozilla/dom/Element.h"              // for dom::Element
     23 #include "mozilla/dom/HTMLBRElement.h"        // for dom::HTMLBRElement
     24 #include "mozilla/dom/Selection.h"            // for dom::Selection
     25 #include "mozilla/dom/Text.h"                 // for dom::Text
     26 #include "mozilla/intl/BidiEmbeddingLevel.h"  // for BidiEmbeddingLevel
     27 
     28 #include "nsDebug.h"           // for NS_WARNING, etc
     29 #include "nsDirection.h"       // for nsDirection
     30 #include "nsError.h"           // for NS_SUCCESS_* and NS_ERROR_*
     31 #include "nsFrameSelection.h"  // for nsFrameSelection
     32 #include "nsRange.h"           // for nsRange
     33 
     34 namespace mozilla {
     35 
     36 enum class CaretAssociationHint;
     37 
     38 /******************************************************************************
     39 * AutoClonedRangeArray stores closed ranges and has similar API to Selection.
     40 *****************************************************************************/
     41 
     42 class MOZ_STACK_CLASS AutoClonedRangeArray {
     43 public:
     44  template <typename PointType>
     45  explicit AutoClonedRangeArray(const EditorDOMRangeBase<PointType>& aRange);
     46  template <typename PT, typename CT>
     47  explicit AutoClonedRangeArray(const EditorDOMPointBase<PT, CT>& aPoint);
     48  explicit AutoClonedRangeArray(const nsRange& aRange);
     49  // The copy constructor copies everything except saved ranges.
     50  explicit AutoClonedRangeArray(const AutoClonedRangeArray& aOther);
     51 
     52  virtual ~AutoClonedRangeArray() = default;
     53 
     54  /**
     55   * Check whether all ranges in content nodes or not.  If the ranges is empty,
     56   * this returns false.
     57   */
     58  [[nodiscard]] bool IsInContent() const {
     59    if (mRanges.IsEmpty()) {
     60      return false;
     61    }
     62    for (const OwningNonNull<nsRange>& range : mRanges) {
     63      if (MOZ_UNLIKELY(!range->IsPositioned() || !range->GetStartContainer() ||
     64                       !range->GetStartContainer()->IsContent() ||
     65                       !range->GetEndContainer() ||
     66                       !range->GetEndContainer()->IsContent())) {
     67        return false;
     68      }
     69    }
     70    return true;
     71  }
     72 
     73  /**
     74   * EnsureOnlyEditableRanges() removes ranges which cannot modify.
     75   * Note that this is designed only for `HTMLEditor` because this must not
     76   * be required by `TextEditor`.
     77   */
     78  void EnsureOnlyEditableRanges(const dom::Element& aEditingHost);
     79 
     80  enum class RangeInReplacedOrVoidElement : bool {
     81    // Each range in a replaced or a void element should be collapsed before the
     82    // element.
     83    Collapse,
     84    // Each range in a replaced or a void element should be deleted.
     85    Delete,
     86  };
     87 
     88  /**
     89   * Adjust ranges if each boundary is in a replaced element or a void element.
     90   * If the adjusted range is not at proper position to edit, this will remove
     91   * the range.
     92   *
     93   * @return true if some ranges are modified.
     94   */
     95  bool AdjustRangesNotInReplacedNorVoidElements(
     96      RangeInReplacedOrVoidElement aRangeInReplacedOrVoidElement,
     97      const dom::Element& aEditingHost);
     98 
     99  /**
    100   * EnsureRangesInTextNode() is designed for TextEditor to guarantee that
    101   * all ranges are in its text node which is first child of the anonymous <div>
    102   * element and is first child.
    103   */
    104  void EnsureRangesInTextNode(const dom::Text& aTextNode);
    105 
    106  /**
    107   * Extend ranges to make each range select starting from a line start edge and
    108   * ending after a line end edge to handle per line edit sub-actions.
    109   */
    110  void ExtendRangesToWrapLines(EditSubAction aEditSubAction,
    111                               BlockInlineCheck aBlockInlineCheck,
    112                               const dom::Element& aAncestorLimiter);
    113 
    114  /**
    115   * Check whether the range is in aEditingHost and both containers of start and
    116   * end boundaries of the range are editable.
    117   */
    118  [[nodiscard]] static bool IsEditableRange(const dom::AbstractRange& aRange,
    119                                            const dom::Element& aEditingHost);
    120 
    121  /**
    122   * Check whether the first range is in aEditingHost and both containers of
    123   * start and end boundaries of the first range are editable.
    124   */
    125  [[nodiscard]] bool IsFirstRangeEditable(
    126      const dom::Element& aEditingHost) const {
    127    return IsEditableRange(FirstRangeRef(), aEditingHost);
    128  }
    129 
    130  /**
    131   * IsAtLeastOneContainerOfRangeBoundariesInclusiveDescendantOf() returns true
    132   * if at least one of the containers of the range boundaries is an inclusive
    133   * descendant of aContent.
    134   */
    135  [[nodiscard]] bool
    136  IsAtLeastOneContainerOfRangeBoundariesInclusiveDescendantOf(
    137      const nsIContent& aContent) const {
    138    for (const OwningNonNull<nsRange>& range : mRanges) {
    139      nsINode* startContainer = range->GetStartContainer();
    140      if (startContainer &&
    141          startContainer->IsInclusiveDescendantOf(&aContent)) {
    142        return true;
    143      }
    144      nsINode* endContainer = range->GetEndContainer();
    145      if (startContainer == endContainer) {
    146        continue;
    147      }
    148      if (endContainer && endContainer->IsInclusiveDescendantOf(&aContent)) {
    149        return true;
    150      }
    151    }
    152    return false;
    153  }
    154 
    155  [[nodiscard]] auto& Ranges() { return mRanges; }
    156  [[nodiscard]] const auto& Ranges() const { return mRanges; }
    157  [[nodiscard]] OwningNonNull<nsRange>& FirstRangeRef() { return mRanges[0]; }
    158  [[nodiscard]] const OwningNonNull<nsRange>& FirstRangeRef() const {
    159    return mRanges[0];
    160  }
    161 
    162  template <template <typename> typename StrongPtrType>
    163  [[nodiscard]] AutoTArray<StrongPtrType<nsRange>, 8> CloneRanges() const {
    164    AutoTArray<StrongPtrType<nsRange>, 8> ranges;
    165    for (const auto& range : mRanges) {
    166      ranges.AppendElement(range->CloneRange());
    167    }
    168    return ranges;
    169  }
    170 
    171  template <typename EditorDOMPointType>
    172  [[nodiscard]] EditorDOMPointType GetFirstRangeStartPoint() const {
    173    if (mRanges.IsEmpty() || !mRanges[0]->IsPositioned()) {
    174      return EditorDOMPointType();
    175    }
    176    return EditorDOMPointType(mRanges[0]->StartRef());
    177  }
    178  template <typename EditorDOMPointType>
    179  [[nodiscard]] EditorDOMPointType GetFirstRangeEndPoint() const {
    180    if (mRanges.IsEmpty() || !mRanges[0]->IsPositioned()) {
    181      return EditorDOMPointType();
    182    }
    183    return EditorDOMPointType(mRanges[0]->EndRef());
    184  }
    185 
    186  nsresult SelectNode(nsINode& aNode) {
    187    mRanges.Clear();
    188    if (!mAnchorFocusRange) {
    189      mAnchorFocusRange = nsRange::Create(&aNode);
    190      if (!mAnchorFocusRange) {
    191        return NS_ERROR_FAILURE;
    192      }
    193    }
    194    ErrorResult error;
    195    mAnchorFocusRange->SelectNode(aNode, error);
    196    if (error.Failed()) {
    197      mAnchorFocusRange = nullptr;
    198      return error.StealNSResult();
    199    }
    200    mRanges.AppendElement(*mAnchorFocusRange);
    201    return NS_OK;
    202  }
    203 
    204  /**
    205   * For compatiblity with the other browsers, we should shrink ranges to
    206   * start from an atomic content and/or end after one instead of start
    207   * from end of a preceding text node and end by start of a follwing text
    208   * node.  Returns true if this modifies a range.
    209   */
    210  enum class IfSelectingOnlyOneAtomicContent {
    211    Collapse,  // Collapse to the range selecting only one atomic content to
    212               // start or after of it.  Whether to collapse start or after
    213               // it depends on aDirectionAndAmount.  This is ignored if
    214               // there are multiple ranges.
    215    KeepSelecting,  // Won't collapse the range.
    216  };
    217  Result<bool, nsresult> ShrinkRangesIfStartFromOrEndAfterAtomicContent(
    218      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
    219      IfSelectingOnlyOneAtomicContent aIfSelectingOnlyOneAtomicContent);
    220 
    221  /**
    222   * The following methods are same as `Selection`'s methods.
    223   */
    224  [[nodiscard]] bool IsCollapsed() const {
    225    return mRanges.IsEmpty() ||
    226           (mRanges.Length() == 1 && mRanges[0]->Collapsed());
    227  }
    228  template <typename PT, typename CT>
    229  nsresult Collapse(const EditorDOMPointBase<PT, CT>& aPoint) {
    230    mRanges.Clear();
    231    if (!mAnchorFocusRange) {
    232      ErrorResult error;
    233      mAnchorFocusRange = nsRange::Create(aPoint.ToRawRangeBoundary(),
    234                                          aPoint.ToRawRangeBoundary(), error);
    235      if (error.Failed()) {
    236        mAnchorFocusRange = nullptr;
    237        return error.StealNSResult();
    238      }
    239    } else {
    240      nsresult rv = mAnchorFocusRange->CollapseTo(aPoint.ToRawRangeBoundary());
    241      if (NS_FAILED(rv)) {
    242        mAnchorFocusRange = nullptr;
    243        return rv;
    244      }
    245    }
    246    mRanges.AppendElement(*mAnchorFocusRange);
    247    SetNewCaretAssociationHint(aPoint.ToRawRangeBoundary(),
    248                               aPoint.GetInterlinePosition());
    249    return NS_OK;
    250  }
    251  template <typename SPT, typename SCT, typename EPT, typename ECT>
    252  nsresult SetStartAndEnd(const EditorDOMPointBase<SPT, SCT>& aStart,
    253                          const EditorDOMPointBase<EPT, ECT>& aEnd) {
    254    mRanges.Clear();
    255    if (!mAnchorFocusRange) {
    256      ErrorResult error;
    257      mAnchorFocusRange = nsRange::Create(aStart.ToRawRangeBoundary(),
    258                                          aEnd.ToRawRangeBoundary(), error);
    259      if (error.Failed()) {
    260        mAnchorFocusRange = nullptr;
    261        return error.StealNSResult();
    262      }
    263    } else {
    264      nsresult rv = mAnchorFocusRange->SetStartAndEnd(
    265          aStart.ToRawRangeBoundary(), aEnd.ToRawRangeBoundary());
    266      if (NS_FAILED(rv)) {
    267        mAnchorFocusRange = nullptr;
    268        return rv;
    269      }
    270    }
    271    mRanges.AppendElement(*mAnchorFocusRange);
    272    return NS_OK;
    273  }
    274  template <typename SPT, typename SCT, typename EPT, typename ECT>
    275  nsresult SetBaseAndExtent(const EditorDOMPointBase<SPT, SCT>& aAnchor,
    276                            const EditorDOMPointBase<EPT, ECT>& aFocus) {
    277    if (MOZ_UNLIKELY(!aAnchor.IsSet()) || MOZ_UNLIKELY(!aFocus.IsSet())) {
    278      mRanges.Clear();
    279      mAnchorFocusRange = nullptr;
    280      return NS_ERROR_INVALID_ARG;
    281    }
    282    return aAnchor.EqualsOrIsBefore(aFocus) ? SetStartAndEnd(aAnchor, aFocus)
    283                                            : SetStartAndEnd(aFocus, aAnchor);
    284  }
    285  [[nodiscard]] const nsRange* GetAnchorFocusRange() const {
    286    return mAnchorFocusRange;
    287  }
    288  [[nodiscard]] nsDirection GetDirection() const { return mDirection; }
    289 
    290  void SetDirection(nsDirection aDirection) { mDirection = aDirection; }
    291 
    292  [[nodiscard]] const RangeBoundary& AnchorRef() const {
    293    if (!mAnchorFocusRange) {
    294      static RangeBoundary sEmptyRangeBoundary;
    295      return sEmptyRangeBoundary;
    296    }
    297    return mDirection == nsDirection::eDirNext ? mAnchorFocusRange->StartRef()
    298                                               : mAnchorFocusRange->EndRef();
    299  }
    300  [[nodiscard]] nsINode* GetAnchorNode() const {
    301    return AnchorRef().IsSet() ? AnchorRef().GetContainer() : nullptr;
    302  }
    303  [[nodiscard]] uint32_t GetAnchorOffset() const {
    304    return AnchorRef().IsSet()
    305               ? AnchorRef()
    306                     .Offset(RangeBoundary::OffsetFilter::kValidOffsets)
    307                     .valueOr(0)
    308               : 0;
    309  }
    310  [[nodiscard]] nsIContent* GetChildAtAnchorOffset() const {
    311    return AnchorRef().IsSet() ? AnchorRef().GetChildAtOffset() : nullptr;
    312  }
    313 
    314  [[nodiscard]] const RangeBoundary& FocusRef() const {
    315    if (!mAnchorFocusRange) {
    316      static RangeBoundary sEmptyRangeBoundary;
    317      return sEmptyRangeBoundary;
    318    }
    319    return mDirection == nsDirection::eDirNext ? mAnchorFocusRange->EndRef()
    320                                               : mAnchorFocusRange->StartRef();
    321  }
    322  [[nodiscard]] nsINode* GetFocusNode() const {
    323    return FocusRef().IsSet() ? FocusRef().GetContainer() : nullptr;
    324  }
    325  [[nodiscard]] uint32_t FocusOffset() const {
    326    return FocusRef().IsSet()
    327               ? FocusRef()
    328                     .Offset(RangeBoundary::OffsetFilter::kValidOffsets)
    329                     .valueOr(0)
    330               : 0;
    331  }
    332  [[nodiscard]] nsIContent* GetChildAtFocusOffset() const {
    333    return FocusRef().IsSet() ? FocusRef().GetChildAtOffset() : nullptr;
    334  }
    335 
    336  void RemoveAllRanges() {
    337    mRanges.Clear();
    338    mAnchorFocusRange = nullptr;
    339    mDirection = nsDirection::eDirNext;
    340  }
    341 
    342  void RemoveCollapsedRanges();
    343 
    344  /**
    345   * If the points are same (i.e., mean a collapsed range) and in an empty block
    346   * element except the padding <br> element, this makes aStartPoint and
    347   * aEndPoint contain the padding <br> element.
    348   */
    349  static void UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement(
    350      EditorDOMPoint& aStartPoint, EditorDOMPoint& aEndPoint,
    351      const dom::Element& aEditingHost);
    352 
    353  /**
    354   * CreateRangeExtendedToHardLineStartAndEnd() creates an nsRange instance
    355   * which may be expanded to start/end of hard line at both edges of the given
    356   * range.  If this fails handling something, returns nullptr.
    357   */
    358  static already_AddRefed<nsRange>
    359  CreateRangeWrappingStartAndEndLinesContainingBoundaries(
    360      const EditorDOMRange& aRange, EditSubAction aEditSubAction,
    361      BlockInlineCheck aBlockInlineCheck, const dom::Element& aEditingHost) {
    362    if (!aRange.IsPositioned()) {
    363      return nullptr;
    364    }
    365    return CreateRangeWrappingStartAndEndLinesContainingBoundaries(
    366        aRange.StartRef(), aRange.EndRef(), aEditSubAction, aBlockInlineCheck,
    367        aEditingHost);
    368  }
    369  static already_AddRefed<nsRange>
    370  CreateRangeWrappingStartAndEndLinesContainingBoundaries(
    371      const EditorDOMPoint& aStartPoint, const EditorDOMPoint& aEndPoint,
    372      EditSubAction aEditSubAction, BlockInlineCheck aBlockInlineCheck,
    373      const dom::Element& aEditingHost) {
    374    RefPtr<nsRange> range =
    375        nsRange::Create(aStartPoint.ToRawRangeBoundary(),
    376                        aEndPoint.ToRawRangeBoundary(), IgnoreErrors());
    377    if (MOZ_UNLIKELY(!range)) {
    378      return nullptr;
    379    }
    380    if (NS_FAILED(ExtendRangeToWrapStartAndEndLinesContainingBoundaries(
    381            *range, aEditSubAction, aBlockInlineCheck, aEditingHost)) ||
    382        MOZ_UNLIKELY(!range->IsPositioned())) {
    383      return nullptr;
    384    }
    385    return range.forget();
    386  }
    387 
    388  /**
    389   * Splits text nodes if each range end is in middle of a text node, then,
    390   * calls HTMLEditor::SplitParentInlineElementsAtRangeBoundaries() for each
    391   * range.  Finally, updates ranges to keep edit target ranges as expected.
    392   *
    393   * @param aHTMLEditor         The HTMLEditor which will handle the splittings.
    394   * @param aBlockInlineCheck   Considering block vs inline with whether the
    395   *                            computed style or the HTML default style.
    396   * @param aElement            The editing host.
    397   * @param aAncestorLimiter    A content node which you don't want this to
    398   *                            split it.
    399   * @return                    A suggest point to put caret if succeeded, but
    400   *                            it may be unset.
    401   */
    402  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult>
    403  SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
    404      HTMLEditor& aHTMLEditor, BlockInlineCheck aBlockInlineCheck,
    405      const dom::Element& aEditingHost,
    406      const nsIContent* aAncestorLimiter = nullptr);
    407 
    408  /**
    409   * CollectEditTargetNodes() collects edit target nodes the ranges.
    410   * First, this collects all nodes in given ranges, then, modifies the
    411   * result for specific edit sub-actions.
    412   */
    413  enum class CollectNonEditableNodes { No, Yes };
    414  nsresult CollectEditTargetNodes(
    415      const HTMLEditor& aHTMLEditor,
    416      nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents,
    417      EditSubAction aEditSubAction,
    418      CollectNonEditableNodes aCollectNonEditableNodes) const;
    419 
    420  /**
    421   * Retrieve a closest ancestor list element of a common ancestor of _A_ range
    422   * of the ranges.  This tries to retrieve it from the first range to the last
    423   * range.
    424   */
    425  dom::Element* GetClosestAncestorAnyListElementOfRange() const;
    426 
    427  [[nodiscard]] virtual bool HasSavedRanges() const { return false; }
    428 
    429  /**
    430   * Extend all ranges to contain surrounding invisible white-spaces if there
    431   * are.
    432   *
    433   * @param aStripWrappers      nsIEditor::eStrip if the caller wants to delete
    434   *                            inline ancestors too.
    435   */
    436  void ExtendRangeToContainSurroundingInvisibleWhiteSpaces(
    437      nsIEditor::EStripWrappers aStripWrappers);
    438 
    439 protected:
    440  AutoClonedRangeArray() = default;
    441 
    442  static nsresult ExtendRangeToWrapStartAndEndLinesContainingBoundaries(
    443      nsRange& aRange, EditSubAction aEditSubAction,
    444      BlockInlineCheck aBlockInlineCheck, const dom::Element& aEditingHost);
    445 
    446  using InterlinePosition = dom::Selection::InterlinePosition;
    447  virtual void SetNewCaretAssociationHint(
    448      const RawRangeBoundary& aPoint, InterlinePosition aInterlinePosition) {}
    449 
    450  AutoTArray<mozilla::OwningNonNull<nsRange>, 8> mRanges;
    451  RefPtr<nsRange> mAnchorFocusRange;
    452  nsDirection mDirection = nsDirection::eDirNext;
    453 };
    454 
    455 /******************************************************************************
    456 * AutoClonedSelectionRangeArray stores closed ranges and have similar API of
    457 * Selection.  So, different from `AutoSelectionRangeArray`, this can be used
    458 * for ranges which may need to be modified before touching the DOM tree, but
    459 * does not want to modify `Selection` for the performance.
    460 *****************************************************************************/
    461 
    462 class MOZ_STACK_CLASS AutoClonedSelectionRangeArray final
    463    : public AutoClonedRangeArray {
    464  using Selection = dom::Selection;
    465 
    466 public:
    467  AutoClonedSelectionRangeArray() = delete;
    468  explicit AutoClonedSelectionRangeArray(const Selection& aSelection);
    469  template <typename PointType>
    470  AutoClonedSelectionRangeArray(
    471      const EditorDOMRangeBase<PointType>& aRange,
    472      const LimitersAndCaretData& aLimitersAndCaretData);
    473  template <typename PT, typename CT>
    474  AutoClonedSelectionRangeArray(
    475      const EditorDOMPointBase<PT, CT>& aPoint,
    476      const LimitersAndCaretData& aLimitersAndCaretData);
    477  AutoClonedSelectionRangeArray(
    478      const nsRange& aRange, const LimitersAndCaretData& aLimitersAndCaretData);
    479  // The copy constructor copies everything except saved ranges.
    480  explicit AutoClonedSelectionRangeArray(
    481      const AutoClonedSelectionRangeArray& aOther);
    482 
    483  ~AutoClonedSelectionRangeArray() override {
    484    if (HasSavedRanges()) {
    485      ClearSavedRanges();
    486    }
    487  }
    488 
    489  void Initialize(const Selection& aSelection) {
    490    ClearSavedRanges();
    491    mDirection = aSelection.GetDirection();
    492    mRanges.Clear();
    493    if (nsFrameSelection* frameSelection = aSelection.GetFrameSelection()) {
    494      mLimitersAndCaretData = LimitersAndCaretData(*frameSelection);
    495    }
    496    for (const uint32_t i : IntegerRange(aSelection.RangeCount())) {
    497      MOZ_ASSERT(aSelection.GetRangeAt(i));
    498      const nsRange* const range = aSelection.GetRangeAt(i);
    499      if (!RangeIsInLimiters(*range)) {
    500        continue;
    501      }
    502      mRanges.AppendElement(range->CloneRange());
    503      if (range == aSelection.GetAnchorFocusRange()) {
    504        mAnchorFocusRange = mRanges.LastElement();
    505      }
    506    }
    507  }
    508 
    509  /**
    510   * APIs to store ranges with only container node and offset in it, and track
    511   * them with RangeUpdater.
    512   */
    513  [[nodiscard]] bool SaveAndTrackRanges(HTMLEditor& aHTMLEditor);
    514  [[nodiscard]] bool HasSavedRanges() const override {
    515    return mSavedRanges.isSome();
    516  }
    517  void ClearSavedRanges();
    518  void RestoreFromSavedRanges() {
    519    MOZ_DIAGNOSTIC_ASSERT(mSavedRanges.isSome());
    520    if (MOZ_UNLIKELY(mSavedRanges.isNothing())) {
    521      return;
    522    }
    523    mSavedRanges->ApplyTo(*this);
    524    ClearSavedRanges();
    525  }
    526 
    527  /**
    528   * Apply mRanges and mDirection to aSelection.
    529   */
    530  MOZ_CAN_RUN_SCRIPT nsresult ApplyTo(dom::Selection& aSelection) {
    531    dom::SelectionBatcher selectionBatcher(aSelection, __FUNCTION__);
    532    aSelection.RemoveAllRanges(IgnoreErrors());
    533    MOZ_ASSERT(!aSelection.RangeCount());
    534    aSelection.SetDirection(mDirection);
    535    IgnoredErrorResult error;
    536    for (const OwningNonNull<nsRange>& range : mRanges) {
    537      // MOZ_KnownLive(range) due to bug 1622253
    538      aSelection.AddRangeAndSelectFramesAndNotifyListeners(MOZ_KnownLive(range),
    539                                                           error);
    540      if (MOZ_UNLIKELY(error.Failed())) {
    541        return error.StealNSResult();
    542      }
    543    }
    544    // FIXME: If and only if the interline position is set explicitly, we need
    545    // to call `Selection::SetInterlinePosition` here with the specified value.
    546    // However, currently nobody does it.
    547    return NS_OK;
    548  }
    549 
    550  [[nodiscard]] const LimitersAndCaretData& LimitersAndCaretDataRef() const {
    551    return mLimitersAndCaretData;
    552  }
    553 
    554  /**
    555   * Equivalent to nsFrameSelection::GetIndependentSelectionRootElement().
    556   * NOTE: This should be called only when IsForSelection() returns true.
    557   */
    558  [[nodiscard]] dom::Element* GetIndependentSelectionRootElement() const {
    559    return mLimitersAndCaretData.mIndependentSelectionRootElement;
    560  }
    561  /**
    562   * Equivalent to nsFrameSelection::GetAncestorLimiter()
    563   * NOTE: This should be called only when IsForSelection() returns true.
    564   */
    565  [[nodiscard]] dom::Element* GetAncestorLimiter() const {
    566    return mLimitersAndCaretData.mAncestorLimiter;
    567  }
    568  /**
    569   * Equivalent to nsFrameSelection::GetHint(), this may be updated when
    570   * Collapse() is called.  Otherwise, this value may mismatch with what
    571   * nsFrameSelection returns if you worked with nsFrameSelection and
    572   * Selection directly.
    573   */
    574  [[nodiscard]] CaretAssociationHint GetHint() const {
    575    return mLimitersAndCaretData.mCaretAssociationHint;
    576  }
    577  /**
    578   * Equivalent to nsFrameSelection::GetCaretBidiLevel(), this returns the value
    579   * when this is initialized with nsFrameSelection or inherits another
    580   * instance.  Therefore, the value may be invalid once you already modified
    581   * the ranges.
    582   */
    583  [[nodiscard]] intl::BidiEmbeddingLevel GetCaretBidiLevel() const {
    584    return mLimitersAndCaretData.mCaretBidiLevel;
    585  }
    586 
    587  void SetAncestorLimiter(const dom::Element* aSelectionAncestorLimiter) {
    588    if (mLimitersAndCaretData.mAncestorLimiter == aSelectionAncestorLimiter) {
    589      return;
    590    }
    591    mLimitersAndCaretData.mAncestorLimiter =
    592        const_cast<dom::Element*>(aSelectionAncestorLimiter);
    593    if (NodeIsInLimiters(GetFocusNode())) {
    594      return;
    595    }
    596    RemoveAllRanges();
    597  }
    598 
    599  void SetInterlinePosition(Selection::InterlinePosition aInterlinePosition) {
    600    switch (aInterlinePosition) {
    601      case Selection::InterlinePosition::EndOfLine:
    602        mLimitersAndCaretData.mCaretAssociationHint =
    603            CaretAssociationHint::Before;
    604        break;
    605      case Selection::InterlinePosition::StartOfNextLine:
    606        mLimitersAndCaretData.mCaretAssociationHint =
    607            CaretAssociationHint::After;
    608        break;
    609      case Selection::InterlinePosition::Undefined:
    610        break;
    611    }
    612  }
    613 
    614  void SetCaretBidiLevel(intl::BidiEmbeddingLevel aBidiLevel) {
    615    mLimitersAndCaretData.mCaretBidiLevel = aBidiLevel;
    616  }
    617 
    618  [[nodiscard]] bool NodeIsInLimiters(const nsINode* aContainerNode) const {
    619    return mLimitersAndCaretData.NodeIsInLimiters(aContainerNode);
    620  }
    621  [[nodiscard]] bool RangeIsInLimiters(const dom::AbstractRange& aRange) const {
    622    return mLimitersAndCaretData.RangeInLimiters(aRange);
    623  }
    624 
    625  /**
    626   * ExtendAnchorFocusRangeFor() extends the anchor-focus range for deleting
    627   * content for aDirectionAndAmount.  The range won't be extended to outer of
    628   * selection limiter.  Note that if a range is extened, the range is
    629   * recreated.  Therefore, caller cannot cache pointer of any ranges before
    630   * calling this.
    631   */
    632  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<nsIEditor::EDirection, nsresult>
    633  ExtendAnchorFocusRangeFor(const EditorBase& aEditorBase,
    634                            nsIEditor::EDirection aDirectionAndAmount);
    635 
    636 private:
    637  void SetNewCaretAssociationHint(
    638      const RawRangeBoundary& aRawRangeBoundary,
    639      InterlinePosition aInternlinePosition) override;
    640 
    641  Maybe<SelectionState> mSavedRanges;
    642  RefPtr<HTMLEditor> mTrackingHTMLEditor;
    643  LimitersAndCaretData mLimitersAndCaretData;
    644 };
    645 
    646 }  // namespace mozilla
    647 
    648 #endif  // #ifndef AutoClonedRangeArray_h