tor-browser

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

nsFrameSelection.cpp (118983B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 /*
      8 * Implementation of nsFrameSelection
      9 */
     10 
     11 #include "nsFrameSelection.h"
     12 
     13 #include <algorithm>
     14 
     15 #include "ErrorList.h"
     16 #include "mozilla/Attributes.h"
     17 #include "mozilla/AutoRestore.h"
     18 #include "mozilla/BasePrincipal.h"
     19 #include "mozilla/HTMLEditor.h"
     20 #include "mozilla/IntegerRange.h"
     21 #include "mozilla/Logging.h"
     22 #include "mozilla/MouseEvents.h"
     23 #include "mozilla/Preferences.h"
     24 #include "mozilla/PresShell.h"
     25 #include "mozilla/PseudoStyleType.h"
     26 #include "mozilla/ScrollContainerFrame.h"
     27 #include "mozilla/ScrollTypes.h"
     28 #include "mozilla/StaticAnalysisFunctions.h"
     29 #include "mozilla/StaticPrefs_bidi.h"
     30 #include "mozilla/StaticPrefs_dom.h"
     31 #include "mozilla/StaticPrefs_layout.h"
     32 #include "mozilla/TextEvents.h"
     33 #include "mozilla/intl/BidiEmbeddingLevel.h"
     34 #include "nsBidiPresUtils.h"
     35 #include "nsCCUncollectableMarker.h"
     36 #include "nsCOMPtr.h"
     37 #include "nsCSSFrameConstructor.h"
     38 #include "nsCaret.h"
     39 #include "nsContentUtils.h"
     40 #include "nsDebug.h"
     41 #include "nsDeviceContext.h"
     42 #include "nsFrameTraversal.h"
     43 #include "nsGkAtoms.h"
     44 #include "nsIContent.h"
     45 #include "nsISelectionListener.h"
     46 #include "nsITableCellLayout.h"
     47 #include "nsLayoutUtils.h"
     48 #include "nsPresContext.h"
     49 #include "nsRange.h"
     50 #include "nsString.h"
     51 #include "nsTArray.h"
     52 #include "nsTableCellFrame.h"
     53 #include "nsTableWrapperFrame.h"
     54 #include "nsTextFrame.h"
     55 #include "nsThreadUtils.h"
     56 
     57 // notifications
     58 #include "SelectionMovementUtils.h"
     59 #include "mozilla/AsyncEventDispatcher.h"
     60 #include "mozilla/AutoCopyListener.h"
     61 #include "mozilla/ErrorResult.h"
     62 #include "mozilla/dom/AncestorIterator.h"
     63 #include "mozilla/dom/Document.h"
     64 #include "mozilla/dom/Element.h"
     65 #include "mozilla/dom/Highlight.h"
     66 #include "mozilla/dom/Selection.h"
     67 #include "mozilla/dom/SelectionBinding.h"
     68 #include "mozilla/dom/ShadowRoot.h"
     69 #include "mozilla/dom/StaticRange.h"
     70 #include "mozilla/dom/Text.h"
     71 #include "nsCopySupport.h"
     72 #include "nsError.h"
     73 #include "nsFocusManager.h"
     74 #include "nsIClipboard.h"
     75 #include "nsIFrameInlines.h"
     76 #include "nsISelectionController.h"  //for the enums
     77 #include "nsPIDOMWindow.h"
     78 
     79 using namespace mozilla;
     80 using namespace mozilla::dom;
     81 
     82 static LazyLogModule sFrameSelectionLog("FrameSelection");
     83 
     84 std::ostream& operator<<(std::ostream& aStream,
     85                         const nsFrameSelection& aFrameSelection) {
     86  return aStream << "{ mPresShell=" << aFrameSelection.mPresShell
     87                 << ", mLimiters={ mIndependentSelectionRootElement="
     88                 << aFrameSelection.mLimiters.mIndependentSelectionRootElement
     89                 << ", mAncestorLimiter="
     90                 << aFrameSelection.mLimiters.mAncestorLimiter
     91                 << "}, IsBatching()=" << std::boolalpha
     92                 << aFrameSelection.IsBatching()
     93                 << ", IsInTableSelectionMode()=" << std::boolalpha
     94                 << aFrameSelection.IsInTableSelectionMode()
     95                 << ", GetDragState()=" << std::boolalpha
     96                 << aFrameSelection.GetDragState()
     97                 << ", HighlightSelectionCount()="
     98                 << aFrameSelection.HighlightSelectionCount() << " }";
     99 }
    100 
    101 namespace mozilla {
    102 extern LazyLogModule sSelectionAPILog;
    103 extern void LogStackForSelectionAPI();
    104 
    105 static void LogSelectionAPI(const dom::Selection* aSelection,
    106                            const char* aFuncName, const char* aArgName,
    107                            const nsIContent* aContent) {
    108  MOZ_LOG(sSelectionAPILog, LogLevel::Info,
    109          ("%p nsFrameSelection::%s(%s=%s)", aSelection, aFuncName, aArgName,
    110           aContent ? ToString(*aContent).c_str() : "<nullptr>"));
    111 }
    112 }  // namespace mozilla
    113 
    114 // #define DEBUG_TABLE 1
    115 
    116 /**
    117 * Add cells to the selection inside of the given cells range.
    118 *
    119 * @param  aTable             [in] HTML table element
    120 * @param  aStartRowIndex     [in] row index where the cells range starts
    121 * @param  aStartColumnIndex  [in] column index where the cells range starts
    122 * @param  aEndRowIndex       [in] row index where the cells range ends
    123 * @param  aEndColumnIndex    [in] column index where the cells range ends
    124 */
    125 static nsresult AddCellsToSelection(const nsIContent* aTableContent,
    126                                    int32_t aStartRowIndex,
    127                                    int32_t aStartColumnIndex,
    128                                    int32_t aEndRowIndex,
    129                                    int32_t aEndColumnIndex,
    130                                    Selection& aNormalSelection);
    131 
    132 static nsAtom* GetTag(nsINode* aNode);
    133 
    134 static nsINode* GetClosestInclusiveTableCellAncestor(nsINode* aDomNode);
    135 MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult CreateAndAddRange(
    136    nsINode* aContainer, int32_t aOffset, Selection& aNormalSelection);
    137 static nsresult SelectCellElement(nsIContent* aCellElement,
    138                                  Selection& aNormalSelection);
    139 
    140 #ifdef XP_MACOSX
    141 static nsresult UpdateSelectionCacheOnRepaintSelection(Selection* aSel);
    142 #endif  // XP_MACOSX
    143 
    144 #ifdef PRINT_RANGE
    145 static void printRange(nsRange* aDomRange);
    146 #  define DEBUG_OUT_RANGE(x) printRange(x)
    147 #else
    148 #  define DEBUG_OUT_RANGE(x)
    149 #endif  // PRINT_RANGE
    150 
    151 /******************************************************************************
    152 * mozilla::PeekOffsetStruct
    153 ******************************************************************************/
    154 
    155 // #define DEBUG_SELECTION // uncomment for printf describing every collapse and
    156 //  extend. #define DEBUG_NAVIGATION
    157 
    158 // #define DEBUG_TABLE_SELECTION 1
    159 
    160 namespace mozilla {
    161 
    162 PeekOffsetStruct::PeekOffsetStruct(
    163    nsSelectionAmount aAmount, nsDirection aDirection, int32_t aStartOffset,
    164    nsPoint aDesiredCaretPos, const PeekOffsetOptions aOptions,
    165    EWordMovementType aWordMovementType /* = eDefaultBehavior */,
    166    const Element* aAncestorLimiter /* = nullptr */)
    167    : mAmount(aAmount),
    168      mDirection(aDirection),
    169      mStartOffset(aStartOffset),
    170      mDesiredCaretPos(aDesiredCaretPos),
    171      mWordMovementType(aWordMovementType),
    172      mOptions(aOptions),
    173      mAncestorLimiter(aAncestorLimiter),
    174      mResultFrame(nullptr),
    175      mContentOffset(0),
    176      mAttach(CaretAssociationHint::Before) {}
    177 
    178 }  // namespace mozilla
    179 
    180 // Array which contains index of each SelectionType in
    181 // Selection::mDOMSelections. For avoiding using if nor switch to retrieve the
    182 // index, this needs to have -1 for SelectionTypes which won't be created its
    183 // Selection instance.
    184 static const int8_t kIndexOfSelections[] = {
    185    -1,  // SelectionType::eInvalid
    186    -1,  // SelectionType::eNone
    187    0,   // SelectionType::eNormal
    188    1,   // SelectionType::eSpellCheck
    189    2,   // SelectionType::eIMERawClause
    190    3,   // SelectionType::eIMESelectedRawClause
    191    4,   // SelectionType::eIMEConvertedClause
    192    5,   // SelectionType::eIMESelectedClause
    193    6,   // SelectionType::eAccessibility
    194    7,   // SelectionType::eFind
    195    8,   // SelectionType::eURLSecondary
    196    9,   // SelectionType::eURLStrikeout
    197    10,  // SelectionType::eTargetText
    198    -1,  // SelectionType::eHighlight
    199 };
    200 
    201 inline int8_t GetIndexFromSelectionType(SelectionType aSelectionType) {
    202  // The enum value of eInvalid is -1 and the others are sequential value
    203  // starting from 0.  Therefore, |SelectionType + 1| is the index of
    204  // kIndexOfSelections.
    205  return kIndexOfSelections[static_cast<int8_t>(aSelectionType) + 1];
    206 }
    207 
    208 /*
    209 The limiter is used specifically for the text areas and textfields
    210 In that case it is the DIV tag that is anonymously created for the text
    211 areas/fields.  Text nodes and BR nodes fall beneath it.  In the case of a
    212 BR node the limiter will be the parent and the offset will point before or
    213 after the BR node.  In the case of the text node the parent content is
    214 the text node itself and the offset will be the exact character position.
    215 The offset is not important to check for validity.  Simply look at the
    216 passed in content.  If it equals the limiter then the selection point is valid.
    217 If its parent it the limiter then the point is also valid.  In the case of
    218 NO limiter all points are valid since you are in a topmost iframe. (browser
    219 or composer)
    220 */
    221 bool nsFrameSelection::NodeIsInLimiters(const nsINode* aContainerNode) const {
    222  return NodeIsInLimiters(aContainerNode, GetIndependentSelectionRootElement(),
    223                          GetAncestorLimiter());
    224 }
    225 
    226 // static
    227 bool nsFrameSelection::NodeIsInLimiters(
    228    const nsINode* aContainerNode,
    229    const Element* aIndependentSelectionLimiterElement,
    230    const Element* aSelectionAncestorLimiter) {
    231  if (!aContainerNode) {
    232    return false;
    233  }
    234 
    235  // If there is a selection limiter, it must be the anonymous <div> of a text
    236  // control.  The <div> should have only one Text and/or a <br>.  Therefore,
    237  // when it's non-nullptr, selection range containers must be the container or
    238  // the Text in it.
    239  if (aIndependentSelectionLimiterElement) {
    240    MOZ_ASSERT(aIndependentSelectionLimiterElement->GetPseudoElementType() ==
    241               PseudoStyleType::mozTextControlEditingRoot);
    242    MOZ_ASSERT(
    243        aIndependentSelectionLimiterElement->IsHTMLElement(nsGkAtoms::div));
    244    if (aIndependentSelectionLimiterElement == aContainerNode) {
    245      return true;
    246    }
    247    if (aIndependentSelectionLimiterElement == aContainerNode->GetParent()) {
    248      NS_WARNING_ASSERTION(aContainerNode->IsText(),
    249                           ToString(*aContainerNode).c_str());
    250      MOZ_ASSERT(aContainerNode->IsText());
    251      return true;
    252    }
    253    return false;
    254  }
    255 
    256  // XXX We might need to return `false` if aContainerNode is in a native
    257  // anonymous subtree, but doing it will make it impossible to select the
    258  // anonymous subtree text in <details>.
    259  return !aSelectionAncestorLimiter ||
    260         aContainerNode->IsInclusiveDescendantOf(aSelectionAncestorLimiter);
    261 }
    262 
    263 namespace mozilla {
    264 struct MOZ_RAII AutoPrepareFocusRange {
    265  AutoPrepareFocusRange(Selection* aSelection,
    266                        const bool aMultiRangeSelection) {
    267    MOZ_ASSERT(aSelection);
    268    MOZ_ASSERT(aSelection->GetType() == SelectionType::eNormal);
    269 
    270    if (aSelection->mStyledRanges.mRanges.Length() <= 1) {
    271      return;
    272    }
    273 
    274    if (aSelection->mFrameSelection->IsUserSelectionReason()) {
    275      mUserSelect.emplace(aSelection);
    276    }
    277 
    278    nsTArray<StyledRange>& ranges = aSelection->mStyledRanges.mRanges;
    279    if (!aSelection->mUserInitiated || aMultiRangeSelection) {
    280      // Scripted command or the user is starting a new explicit multi-range
    281      // selection.
    282      for (StyledRange& entry : ranges) {
    283        MOZ_ASSERT(entry.mRange->IsDynamicRange());
    284        entry.mRange->AsDynamicRange()->SetIsGenerated(false);
    285      }
    286      return;
    287    }
    288 
    289    if (!IsAnchorRelativeOperation(
    290            aSelection->mFrameSelection->mSelectionChangeReasons)) {
    291      return;
    292    }
    293 
    294    // This operation is against the anchor but our current mAnchorFocusRange
    295    // represents the focus in a multi-range selection.  The anchor from a user
    296    // perspective is the most distant generated range on the opposite side.
    297    // Find that range and make it the mAnchorFocusRange.
    298    nsRange* const newAnchorFocusRange =
    299        FindGeneratedRangeMostDistantFromAnchor(*aSelection);
    300 
    301    if (!newAnchorFocusRange) {
    302      // There are no generated ranges - that's fine.
    303      return;
    304    }
    305 
    306    // Setup the new mAnchorFocusRange and mark the old one as generated.
    307    if (aSelection->mAnchorFocusRange) {
    308      aSelection->mAnchorFocusRange->SetIsGenerated(true);
    309    }
    310 
    311    newAnchorFocusRange->SetIsGenerated(false);
    312    aSelection->mAnchorFocusRange = newAnchorFocusRange;
    313 
    314    RemoveGeneratedRanges(*aSelection);
    315 
    316    if (aSelection->mFrameSelection) {
    317      aSelection->mFrameSelection->InvalidateDesiredCaretPos();
    318    }
    319  }
    320 
    321 private:
    322  static nsRange* FindGeneratedRangeMostDistantFromAnchor(
    323      const Selection& aSelection) {
    324    const nsTArray<StyledRange>& ranges = aSelection.mStyledRanges.mRanges;
    325    const size_t len = ranges.Length();
    326    nsRange* result{nullptr};
    327    if (aSelection.GetDirection() == eDirNext) {
    328      for (size_t i = 0; i < len; ++i) {
    329        // This function is only called for selections with type == eNormal.
    330        // (see MOZ_ASSERT in constructor).
    331        // Therefore, all ranges must be dynamic.
    332        if (ranges[i].mRange->AsDynamicRange()->IsGenerated()) {
    333          result = ranges[i].mRange->AsDynamicRange();
    334          break;
    335        }
    336      }
    337    } else {
    338      size_t i = len;
    339      while (i--) {
    340        if (ranges[i].mRange->AsDynamicRange()->IsGenerated()) {
    341          result = ranges[i].mRange->AsDynamicRange();
    342          break;
    343        }
    344      }
    345    }
    346 
    347    return result;
    348  }
    349 
    350  static void RemoveGeneratedRanges(Selection& aSelection) {
    351    RefPtr<nsPresContext> presContext = aSelection.GetPresContext();
    352    nsTArray<StyledRange>& ranges = aSelection.mStyledRanges.mRanges;
    353    size_t i = ranges.Length();
    354    while (i--) {
    355      // This function is only called for selections with type == eNormal.
    356      // (see MOZ_ASSERT in constructor).
    357      // Therefore, all ranges must be dynamic.
    358      if (!ranges[i].mRange->IsDynamicRange()) {
    359        continue;
    360      }
    361      nsRange* range = ranges[i].mRange->AsDynamicRange();
    362      if (range->IsGenerated()) {
    363        range->UnregisterSelection(aSelection);
    364        aSelection.SelectFrames(presContext, *range, false);
    365        ranges.RemoveElementAt(i);
    366      }
    367    }
    368  }
    369 
    370  /**
    371   * @aParam aSelectionChangeReasons can be multiple of the reasons defined in
    372             nsISelectionListener.idl.
    373   */
    374  static bool IsAnchorRelativeOperation(const int16_t aSelectionChangeReasons) {
    375    return aSelectionChangeReasons &
    376           (nsISelectionListener::DRAG_REASON |
    377            nsISelectionListener::MOUSEDOWN_REASON |
    378            nsISelectionListener::MOUSEUP_REASON |
    379            nsISelectionListener::COLLAPSETOSTART_REASON);
    380  }
    381 
    382  Maybe<Selection::AutoUserInitiated> mUserSelect;
    383 };
    384 
    385 }  // namespace mozilla
    386 
    387 ////////////BEGIN nsFrameSelection methods
    388 
    389 template Result<RefPtr<nsRange>, nsresult>
    390 nsFrameSelection::CreateRangeExtendedToSomewhere(
    391    PresShell& aPresShell,
    392    const mozilla::LimitersAndCaretData& aLimitersAndCaretData,
    393    const AbstractRange& aRange, nsDirection aRangeDirection,
    394    nsDirection aExtendDirection, nsSelectionAmount aAmount,
    395    CaretMovementStyle aMovementStyle);
    396 template Result<RefPtr<StaticRange>, nsresult>
    397 nsFrameSelection::CreateRangeExtendedToSomewhere(
    398    PresShell& aPresShell,
    399    const mozilla::LimitersAndCaretData& aLimitersAndCaretData,
    400    const AbstractRange& aRange, nsDirection aRangeDirection,
    401    nsDirection aExtendDirection, nsSelectionAmount aAmount,
    402    CaretMovementStyle aMovementStyle);
    403 
    404 nsFrameSelection::nsFrameSelection(
    405    PresShell* aPresShell, const bool aAccessibleCaretEnabled,
    406    Element* aEditorRootAnonymousDiv /* = nullptr */) {
    407  for (size_t i = 0; i < std::size(mDomSelections); i++) {
    408    mDomSelections[i] = new Selection(kPresentSelectionTypes[i], this);
    409  }
    410 
    411  Selection& sel = NormalSelection();
    412  if (AutoCopyListener::IsEnabled()) {
    413    sel.NotifyAutoCopy();
    414  }
    415 
    416  mPresShell = aPresShell;
    417  mDragState = false;
    418 
    419  MOZ_ASSERT_IF(aEditorRootAnonymousDiv,
    420                aEditorRootAnonymousDiv->GetPseudoElementType() ==
    421                    PseudoStyleType::mozTextControlEditingRoot);
    422  MOZ_ASSERT_IF(aEditorRootAnonymousDiv,
    423                aEditorRootAnonymousDiv->IsHTMLElement(nsGkAtoms::div));
    424  mLimiters.mIndependentSelectionRootElement = aEditorRootAnonymousDiv;
    425 
    426  // This should only ever be initialized on the main thread, so we are OK here.
    427  MOZ_ASSERT(NS_IsMainThread());
    428 
    429  mAccessibleCaretEnabled = aAccessibleCaretEnabled;
    430  if (mAccessibleCaretEnabled) {
    431    sel.MaybeNotifyAccessibleCaretEventHub(aPresShell);
    432  }
    433 
    434  sel.EnableSelectionChangeEvent();
    435 }
    436 
    437 nsFrameSelection::~nsFrameSelection() = default;
    438 
    439 NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameSelection)
    440 
    441 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameSelection)
    442  for (size_t i = 0; i < std::size(tmp->mDomSelections); ++i) {
    443    tmp->mDomSelections[i] = nullptr;
    444  }
    445  tmp->mHighlightSelections.Clear();
    446 
    447  NS_IMPL_CYCLE_COLLECTION_UNLINK(
    448      mTableSelection.mClosestInclusiveTableCellAncestor)
    449  tmp->mTableSelection.mMode = TableSelectionMode::None;
    450  tmp->mTableSelection.mDragSelectingCells = false;
    451  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection.mStartSelectedCell)
    452  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection.mEndSelectedCell)
    453  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection.mAppendStartSelectedCell)
    454  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection.mUnselectCellOnMouseUp)
    455  NS_IMPL_CYCLE_COLLECTION_UNLINK(mMaintainedRange.mRange)
    456  NS_IMPL_CYCLE_COLLECTION_UNLINK(mLimiters.mIndependentSelectionRootElement)
    457  NS_IMPL_CYCLE_COLLECTION_UNLINK(mLimiters.mAncestorLimiter)
    458 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
    459 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameSelection)
    460  if (tmp->mPresShell && tmp->mPresShell->GetDocument() &&
    461      nsCCUncollectableMarker::InGeneration(
    462          cb, tmp->mPresShell->GetDocument()->GetMarkedCCGeneration())) {
    463    return NS_SUCCESS_INTERRUPTED_TRAVERSE;
    464  }
    465  for (size_t i = 0; i < std::size(tmp->mDomSelections); ++i) {
    466    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDomSelections[i])
    467  }
    468 
    469  for (const auto& value : tmp->mHighlightSelections) {
    470    CycleCollectionNoteChild(cb, value.second().get(),
    471                             "mHighlightSelections[]");
    472  }
    473 
    474  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
    475      mTableSelection.mClosestInclusiveTableCellAncestor)
    476  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection.mStartSelectedCell)
    477  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection.mEndSelectedCell)
    478  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection.mAppendStartSelectedCell)
    479  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection.mUnselectCellOnMouseUp)
    480  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMaintainedRange.mRange)
    481  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLimiters.mIndependentSelectionRootElement)
    482  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLimiters.mAncestorLimiter)
    483 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
    484 
    485 // static
    486 bool nsFrameSelection::Caret::IsVisualMovement(
    487    ExtendSelection aExtendSelection, CaretMovementStyle aMovementStyle) {
    488  int32_t movementFlag = StaticPrefs::bidi_edit_caret_movement_style();
    489  return aMovementStyle == eVisual ||
    490         (aMovementStyle == eUsePrefStyle &&
    491          (movementFlag == 1 ||
    492           (movementFlag == 2 && aExtendSelection == ExtendSelection::No)));
    493 }
    494 
    495 // Get the x (or y, in vertical writing mode) position requested
    496 // by the Key Handling for line-up/down
    497 nsresult nsFrameSelection::DesiredCaretPos::FetchPos(
    498    nsPoint& aDesiredCaretPos, const PresShell& aPresShell,
    499    Selection& aNormalSelection) const {
    500  MOZ_ASSERT(aNormalSelection.GetType() == SelectionType::eNormal);
    501 
    502  if (mIsSet) {
    503    aDesiredCaretPos = mValue;
    504    return NS_OK;
    505  }
    506 
    507  RefPtr<nsCaret> caret = aPresShell.GetCaret();
    508  if (!caret) {
    509    return NS_ERROR_NULL_POINTER;
    510  }
    511 
    512  caret->SetSelection(&aNormalSelection);
    513 
    514  nsRect coord;
    515  nsIFrame* caretFrame = caret->GetGeometry(&coord);
    516  if (!caretFrame) {
    517    return NS_ERROR_FAILURE;
    518  }
    519  coord += caretFrame->GetOffsetToRootFrame();
    520  aDesiredCaretPos = coord.TopLeft();
    521  return NS_OK;
    522 }
    523 
    524 void nsFrameSelection::InvalidateDesiredCaretPos()  // do not listen to
    525                                                    // mDesiredCaretPos.mValue;
    526                                                    // you must get another.
    527 {
    528  mDesiredCaretPos.Invalidate();
    529 }
    530 
    531 void nsFrameSelection::DesiredCaretPos::Invalidate() { mIsSet = false; }
    532 
    533 void nsFrameSelection::DesiredCaretPos::Set(const nsPoint& aPos) {
    534  mValue = aPos;
    535  mIsSet = true;
    536 }
    537 
    538 nsresult nsFrameSelection::ConstrainFrameAndPointToAnchorSubtree(
    539    nsIFrame* aFrame, const nsPoint& aPoint, nsIFrame** aRetFrame,
    540    nsPoint& aRetPoint) const {
    541  //
    542  // The whole point of this method is to return a frame and point that
    543  // that lie within the same valid subtree as the anchor node's frame,
    544  // for use with the method GetContentAndOffsetsFromPoint().
    545  //
    546  // A valid subtree is defined to be one where all the content nodes in
    547  // the tree have a valid parent-child relationship.
    548  //
    549  // If the anchor frame and aFrame are in the same subtree, aFrame will
    550  // be returned in aRetFrame. If they are in different subtrees, we
    551  // return the frame for the root of the subtree.
    552  //
    553 
    554  if (!aFrame || !aRetFrame) {
    555    return NS_ERROR_NULL_POINTER;
    556  }
    557 
    558  *aRetFrame = aFrame;
    559  aRetPoint = aPoint;
    560 
    561  //
    562  // Get the frame and content for the selection's anchor point!
    563  //
    564 
    565  const Selection& sel = NormalSelection();
    566 
    567  nsCOMPtr<nsIContent> anchorContent =
    568      nsIContent::FromNodeOrNull(sel.GetMayCrossShadowBoundaryAnchorNode());
    569  if (!anchorContent) {
    570    return NS_ERROR_FAILURE;
    571  }
    572 
    573  //
    574  // Now find the root of the subtree containing the anchor's content.
    575  //
    576 
    577  NS_ENSURE_STATE(mPresShell);
    578  RefPtr<PresShell> presShell = mPresShell;
    579  nsIContent* anchorRoot = anchorContent->GetSelectionRootContent(
    580      presShell, nsINode::IgnoreOwnIndependentSelection::Yes,
    581      static_cast<nsINode::AllowCrossShadowBoundary>(
    582          StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()));
    583  NS_ENSURE_TRUE(anchorRoot, NS_ERROR_UNEXPECTED);
    584 
    585  //
    586  // Now find the root of the subtree containing aFrame's content.
    587  //
    588 
    589  nsCOMPtr<nsIContent> content = aFrame->GetContent();
    590 
    591  if (content) {
    592    nsIContent* contentRoot = content->GetSelectionRootContent(
    593        presShell, nsINode::IgnoreOwnIndependentSelection::Yes,
    594        static_cast<nsINode::AllowCrossShadowBoundary>(
    595            StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()));
    596    NS_ENSURE_TRUE(contentRoot, NS_ERROR_UNEXPECTED);
    597 
    598    if (anchorRoot == contentRoot) {
    599      // If the aFrame's content isn't the capturing content, it should be
    600      // a descendant.  At this time, we can return simply.
    601      nsIContent* capturedContent = PresShell::GetCapturingContent();
    602      if (capturedContent != content) {
    603        return NS_OK;
    604      }
    605 
    606      // Find the frame under the mouse cursor with the root frame.
    607      // At this time, don't use the anchor's frame because it may not have
    608      // fixed positioned frames.
    609      nsIFrame* rootFrame = presShell->GetRootFrame();
    610      nsPoint ptInRoot = aPoint + aFrame->GetOffsetTo(rootFrame);
    611      nsIFrame* cursorFrame =
    612          nsLayoutUtils::GetFrameForPoint(RelativeTo{rootFrame}, ptInRoot);
    613 
    614      // If the mouse cursor in on a frame which is descendant of same
    615      // selection root, we can expand the selection to the frame.
    616      if (cursorFrame && cursorFrame->PresShell() == presShell) {
    617        nsCOMPtr<nsIContent> cursorContent = cursorFrame->GetContent();
    618        NS_ENSURE_TRUE(cursorContent, NS_ERROR_FAILURE);
    619        nsIContent* cursorContentRoot = cursorContent->GetSelectionRootContent(
    620            presShell, nsINode::IgnoreOwnIndependentSelection::Yes,
    621            static_cast<nsINode::AllowCrossShadowBoundary>(
    622                StaticPrefs::
    623                    dom_shadowdom_selection_across_boundary_enabled()));
    624        NS_ENSURE_TRUE(cursorContentRoot, NS_ERROR_UNEXPECTED);
    625        if (cursorContentRoot == anchorRoot) {
    626          *aRetFrame = cursorFrame;
    627          aRetPoint = aPoint + aFrame->GetOffsetTo(cursorFrame);
    628          return NS_OK;
    629        }
    630      }
    631      // Otherwise, e.g., the cursor isn't on any frames (e.g., the mouse
    632      // cursor is out of the window), we should use the frame of the anchor
    633      // root.
    634    }
    635  }
    636 
    637  //
    638  // When we can't find a frame which is under the mouse cursor and has a same
    639  // selection root as the anchor node's, we should return the selection root
    640  // frame.
    641  //
    642 
    643  *aRetFrame = anchorRoot->GetPrimaryFrame();
    644 
    645  if (!*aRetFrame) {
    646    return NS_ERROR_FAILURE;
    647  }
    648 
    649  //
    650  // Now make sure that aRetPoint is converted to the same coordinate
    651  // system used by aRetFrame.
    652  //
    653 
    654  aRetPoint = aPoint + aFrame->GetOffsetTo(*aRetFrame);
    655 
    656  return NS_OK;
    657 }
    658 
    659 void nsFrameSelection::SetCaretBidiLevelAndMaybeSchedulePaint(
    660    mozilla::intl::BidiEmbeddingLevel aLevel) {
    661  // If the current level is undefined, we have just inserted new text.
    662  // In this case, we don't want to reset the keyboard language
    663  mCaret.mBidiLevel = aLevel;
    664 
    665  RefPtr<nsCaret> caret;
    666  if (mPresShell && (caret = mPresShell->GetCaret())) {
    667    caret->SchedulePaint();
    668  }
    669 }
    670 
    671 mozilla::intl::BidiEmbeddingLevel nsFrameSelection::GetCaretBidiLevel() const {
    672  return mCaret.mBidiLevel;
    673 }
    674 
    675 void nsFrameSelection::UndefineCaretBidiLevel() {
    676  mCaret.mBidiLevel = mozilla::intl::BidiEmbeddingLevel(mCaret.mBidiLevel |
    677                                                        BIDI_LEVEL_UNDEFINED);
    678 }
    679 
    680 #ifdef PRINT_RANGE
    681 void printRange(nsRange* aDomRange) {
    682  if (!aDomRange) {
    683    printf("NULL Range\n");
    684  }
    685  nsINode* startNode = aDomRange->GetStartContainer();
    686  nsINode* endNode = aDomRange->GetEndContainer();
    687  int32_t startOffset = aDomRange->StartOffset();
    688  int32_t endOffset = aDomRange->EndOffset();
    689 
    690  printf("range: 0x%lx\t start: 0x%lx %ld, \t end: 0x%lx,%ld\n",
    691         (unsigned long)aDomRange, (unsigned long)startNode, (long)startOffset,
    692         (unsigned long)endNode, (long)endOffset);
    693 }
    694 #endif /* PRINT_RANGE */
    695 
    696 static nsAtom* GetTag(nsINode* aNode) {
    697  nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
    698  if (!content) {
    699    MOZ_ASSERT_UNREACHABLE("bad node passed to GetTag()");
    700    return nullptr;
    701  }
    702 
    703  return content->NodeInfo()->NameAtom();
    704 }
    705 
    706 /**
    707 * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor.
    708 */
    709 static nsINode* GetClosestInclusiveTableCellAncestor(nsINode* aDomNode) {
    710  if (!aDomNode) {
    711    return nullptr;
    712  }
    713  nsINode* current = aDomNode;
    714  // Start with current node and look for a table cell
    715  while (current) {
    716    nsAtom* tag = GetTag(current);
    717    if (tag == nsGkAtoms::td || tag == nsGkAtoms::th) {
    718      return current;
    719    }
    720    current = current->GetParent();
    721  }
    722  return nullptr;
    723 }
    724 
    725 static nsDirection GetCaretDirection(const nsIFrame& aFrame,
    726                                     nsDirection aDirection,
    727                                     bool aVisualMovement) {
    728  const mozilla::intl::BidiDirection paragraphDirection =
    729      nsBidiPresUtils::ParagraphDirection(&aFrame);
    730  return (aVisualMovement &&
    731          paragraphDirection == mozilla::intl::BidiDirection::RTL)
    732             ? nsDirection(1 - aDirection)
    733             : aDirection;
    734 }
    735 
    736 nsresult nsFrameSelection::MoveCaret(nsDirection aDirection,
    737                                     ExtendSelection aExtendSelection,
    738                                     const nsSelectionAmount aAmount,
    739                                     CaretMovementStyle aMovementStyle) {
    740  NS_ENSURE_STATE(mPresShell);
    741  // Flush out layout, since we need it to be up to date to do caret
    742  // positioning.
    743  OwningNonNull<PresShell> presShell(*mPresShell);
    744  presShell->FlushPendingNotifications(FlushType::Layout);
    745 
    746  if (!mPresShell) {
    747    return NS_OK;
    748  }
    749 
    750  nsPresContext* context = mPresShell->GetPresContext();
    751  if (!context) {
    752    return NS_ERROR_FAILURE;
    753  }
    754 
    755  const RefPtr<Selection> sel = &NormalSelection();
    756 
    757  auto scrollFlags = ScrollFlags::None;
    758  if (sel->IsEditorSelection()) {
    759    // If caret moves in editor, it should cause scrolling even if it's in
    760    // overflow: hidden;.
    761    scrollFlags |= ScrollFlags::ScrollOverflowHidden;
    762  }
    763 
    764  const bool doCollapse = [&] {
    765    if (sel->IsCollapsed() || aExtendSelection == ExtendSelection::Yes) {
    766      return false;
    767    }
    768    if (aAmount > eSelectLine) {
    769      return false;
    770    }
    771    int32_t caretStyle = StaticPrefs::layout_selection_caret_style();
    772    return caretStyle == 2 || (caretStyle == 0 && aAmount != eSelectLine);
    773  }();
    774 
    775  if (doCollapse) {
    776    if (aDirection == eDirPrevious) {
    777      SetChangeReasons(nsISelectionListener::COLLAPSETOSTART_REASON);
    778      mCaret.mHint = CaretAssociationHint::After;
    779    } else {
    780      SetChangeReasons(nsISelectionListener::COLLAPSETOEND_REASON);
    781      mCaret.mHint = CaretAssociationHint::Before;
    782    }
    783  } else {
    784    SetChangeReasons(nsISelectionListener::KEYPRESS_REASON);
    785  }
    786 
    787  mCaretMoveAmount = aAmount;
    788 
    789  AutoPrepareFocusRange prep(sel, false);
    790 
    791  // we must keep this around and revalidate it when its just UP/DOWN
    792  nsPoint desiredPos(0, 0);
    793 
    794  if (aAmount == eSelectLine) {
    795    nsresult result = mDesiredCaretPos.FetchPos(desiredPos, *mPresShell, *sel);
    796    if (NS_FAILED(result)) {
    797      return result;
    798    }
    799    mDesiredCaretPos.Set(desiredPos);
    800  }
    801 
    802  bool visualMovement =
    803      Caret::IsVisualMovement(aExtendSelection, aMovementStyle);
    804  const PrimaryFrameData frameForFocus =
    805      sel->GetPrimaryFrameForCaretAtFocusNode(visualMovement);
    806  if (!frameForFocus) {
    807    return NS_ERROR_FAILURE;
    808  }
    809  if (visualMovement) {
    810    // FYI: This was done during a call of GetPrimaryFrameForCaretAtFocusNode.
    811    // Therefore, this may not be intended by the original author.
    812    SetHint(frameForFocus.mHint);
    813  }
    814 
    815  Result<bool, nsresult> isIntraLineCaretMove =
    816      SelectionMovementUtils::IsIntraLineCaretMove(aAmount);
    817  nsDirection direction{aDirection};
    818  if (isIntraLineCaretMove.isErr()) {
    819    return isIntraLineCaretMove.unwrapErr();
    820  }
    821  if (isIntraLineCaretMove.inspect()) {
    822    // Forget old caret position for moving caret to different line since
    823    // caret position may be changed.
    824    mDesiredCaretPos.Invalidate();
    825    direction =
    826        GetCaretDirection(*frameForFocus.mFrame, aDirection, visualMovement);
    827  }
    828 
    829  if (doCollapse) {
    830    const nsRange* anchorFocusRange = sel->GetAnchorFocusRange();
    831    if (anchorFocusRange) {
    832      RefPtr<nsINode> node;
    833      uint32_t offset;
    834      if (visualMovement &&
    835          nsBidiPresUtils::IsReversedDirectionFrame(frameForFocus.mFrame)) {
    836        direction = nsDirection(1 - direction);
    837      }
    838      if (direction == eDirPrevious) {
    839        node = anchorFocusRange->GetStartContainer();
    840        offset = anchorFocusRange->StartOffset();
    841      } else {
    842        node = anchorFocusRange->GetEndContainer();
    843        offset = anchorFocusRange->EndOffset();
    844      }
    845      sel->CollapseInLimiter(node, offset);
    846    }
    847    sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
    848                        ScrollAxis(), ScrollAxis(), scrollFlags);
    849    return NS_OK;
    850  }
    851 
    852  CaretAssociationHint tHint(
    853      mCaret.mHint);  // temporary variable so we dont set
    854                      // mCaret.mHint until it is necessary
    855 
    856  Result<PeekOffsetOptions, nsresult> options =
    857      CreatePeekOffsetOptionsForCaretMove(sel, aExtendSelection,
    858                                          aMovementStyle);
    859  if (options.isErr()) {
    860    return options.propagateErr();
    861  }
    862  Result<const dom::Element*, nsresult> ancestorLimiter =
    863      GetAncestorLimiterForCaretMove(sel);
    864  if (ancestorLimiter.isErr()) {
    865    return ancestorLimiter.propagateErr();
    866  }
    867  nsIContent* content = nsIContent::FromNodeOrNull(sel->GetFocusNode());
    868 
    869  Result<PeekOffsetStruct, nsresult> result =
    870      SelectionMovementUtils::PeekOffsetForCaretMove(
    871          content, sel->FocusOffset(), direction, GetHint(),
    872          GetCaretBidiLevel(), aAmount, desiredPos, options.unwrap(),
    873          ancestorLimiter.unwrap());
    874  nsresult rv;
    875  if (result.isOk() && result.inspect().mResultContent) {
    876    const PeekOffsetStruct& pos = result.inspect();
    877    nsIFrame* theFrame;
    878    int32_t frameStart, frameEnd;
    879 
    880    if (aAmount <= eSelectWordNoSpace) {
    881      // For left/right, PeekOffset() sets pos.mResultFrame correctly, but does
    882      // not set pos.mAttachForward, so determine the hint here based on the
    883      // result frame and offset: If we're at the end of a text frame, set the
    884      // hint to ASSOCIATE_BEFORE to indicate that we want the caret displayed
    885      // at the end of this frame, not at the beginning of the next one.
    886      theFrame = pos.mResultFrame;
    887      std::tie(frameStart, frameEnd) = theFrame->GetOffsets();
    888      if (frameEnd == pos.mContentOffset &&
    889          !(frameStart == 0 && frameEnd == 0)) {
    890        tHint = CaretAssociationHint::Before;
    891      } else {
    892        tHint = CaretAssociationHint::After;
    893      }
    894    } else {
    895      // For up/down and home/end, pos.mResultFrame might not be set correctly,
    896      // or not at all. In these cases, get the frame based on the content and
    897      // hint returned by PeekOffset().
    898      tHint = pos.mAttach;
    899      theFrame = SelectionMovementUtils::GetFrameForNodeOffset(
    900          pos.mResultContent, pos.mContentOffset, tHint);
    901      if (!theFrame) {
    902        return NS_ERROR_FAILURE;
    903      }
    904 
    905      std::tie(frameStart, frameEnd) = theFrame->GetOffsets();
    906    }
    907 
    908    if (context->BidiEnabled()) {
    909      switch (aAmount) {
    910        case eSelectBeginLine:
    911        case eSelectEndLine: {
    912          // In Bidi contexts, PeekOffset calculates pos.mContentOffset
    913          // differently depending on whether the movement is visual or logical.
    914          // For visual movement, pos.mContentOffset depends on the direction-
    915          // ality of the first/last frame on the line (theFrame), and the caret
    916          // directionality must correspond.
    917          FrameBidiData bidiData = theFrame->GetBidiData();
    918          SetCaretBidiLevelAndMaybeSchedulePaint(
    919              visualMovement ? bidiData.embeddingLevel : bidiData.baseLevel);
    920          break;
    921        }
    922        default:
    923          // If the current position is not a frame boundary, it's enough just
    924          // to take the Bidi level of the current frame
    925          if ((pos.mContentOffset != frameStart &&
    926               pos.mContentOffset != frameEnd) ||
    927              eSelectLine == aAmount) {
    928            SetCaretBidiLevelAndMaybeSchedulePaint(
    929                theFrame->GetEmbeddingLevel());
    930          } else {
    931            BidiLevelFromMove(mPresShell, pos.mResultContent,
    932                              pos.mContentOffset, aAmount, tHint);
    933          }
    934      }
    935    }
    936    // "pos" is on the stack, so pos.mResultContent has stack lifetime, so using
    937    // MOZ_KnownLive is ok.
    938    const FocusMode focusMode = aExtendSelection == ExtendSelection::Yes
    939                                    ? FocusMode::kExtendSelection
    940                                    : FocusMode::kCollapseToNewPoint;
    941    rv = TakeFocus(MOZ_KnownLive(*pos.mResultContent), pos.mContentOffset,
    942                   pos.mContentOffset, tHint, focusMode);
    943  } else if (aAmount <= eSelectWordNoSpace && direction == eDirNext &&
    944             aExtendSelection == ExtendSelection::No) {
    945    // Collapse selection if PeekOffset failed, we either
    946    //  1. bumped into the BRFrame, bug 207623
    947    //  2. had select-all in a text input (DIV range), bug 352759.
    948    bool isBRFrame = frameForFocus.mFrame->IsBrFrame();
    949    RefPtr<nsINode> node = sel->GetFocusNode();
    950    sel->CollapseInLimiter(node, sel->FocusOffset());
    951    // Note: 'frameForFocus.mFrame' might be dead here.
    952    if (!isBRFrame) {
    953      mCaret.mHint = CaretAssociationHint::Before;  // We're now at the end of
    954                                                    // the frame to the left.
    955    }
    956    rv = NS_OK;
    957  } else {
    958    rv = result.isErr() ? result.unwrapErr() : NS_OK;
    959  }
    960  if (NS_SUCCEEDED(rv)) {
    961    rv = sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
    962                             ScrollAxis(), ScrollAxis(), scrollFlags);
    963  }
    964 
    965  return rv;
    966 }
    967 
    968 // static
    969 Result<PeekOffsetOptions, nsresult>
    970 nsFrameSelection::CreatePeekOffsetOptionsForCaretMove(
    971    const Element* aSelectionLimiter, ForceEditableRegion aForceEditableRegion,
    972    ExtendSelection aExtendSelection, CaretMovementStyle aMovementStyle) {
    973  PeekOffsetOptions options;
    974  // set data using aSelectionLimiter to stop on scroll views.  If we have a
    975  // limiter then we stop peeking when we hit scrollable views.  If no limiter
    976  // then just let it go ahead
    977  if (aSelectionLimiter) {
    978    options += PeekOffsetOption::StopAtScroller;
    979  }
    980  const bool visualMovement =
    981      Caret::IsVisualMovement(aExtendSelection, aMovementStyle);
    982  if (visualMovement) {
    983    options += PeekOffsetOption::Visual;
    984  }
    985  if (aExtendSelection == ExtendSelection::Yes) {
    986    options += PeekOffsetOption::Extend;
    987  }
    988  if (static_cast<bool>(aForceEditableRegion)) {
    989    options += PeekOffsetOption::ForceEditableRegion;
    990  }
    991  return options;
    992 }
    993 
    994 Result<Element*, nsresult> nsFrameSelection::GetAncestorLimiterForCaretMove(
    995    dom::Selection* aSelection) const {
    996  if (!mPresShell) {
    997    return Err(NS_ERROR_NULL_POINTER);
    998  }
    999 
   1000  MOZ_ASSERT(aSelection);
   1001  nsIContent* content = nsIContent::FromNodeOrNull(aSelection->GetFocusNode());
   1002  if (!content) {
   1003    return Err(NS_ERROR_FAILURE);
   1004  }
   1005 
   1006  MOZ_ASSERT(mPresShell->GetDocument() == content->GetComposedDoc());
   1007 
   1008  Element* ancestorLimiter = GetAncestorLimiter();
   1009  if (aSelection->IsEditorSelection()) {
   1010    // If the editor has not receive `focus` event, it may have not set ancestor
   1011    // limiter.  Then, we need to compute it here for the caret move.
   1012    if (!ancestorLimiter) {
   1013      // Editing hosts can be nested.  Therefore, computing selection root from
   1014      // selection range may be different from the focused editing host.
   1015      // Therefore, we may need to use a non-closest inclusive ancestor editing
   1016      // host of selection range container.  On the other hand, selection ranges
   1017      // may be outside of focused editing host.  In such case, we should use
   1018      // the closest editing host as the ancestor limiter instead.
   1019      PresShell* const presShell = aSelection->GetPresShell();
   1020      const Document* const doc =
   1021          presShell ? presShell->GetDocument() : nullptr;
   1022      if (const nsPIDOMWindowInner* const win =
   1023              doc ? doc->GetInnerWindow() : nullptr) {
   1024        Element* const focusedElement = win->GetFocusedElement();
   1025        Element* closestEditingHost = nullptr;
   1026        for (Element* element : content->InclusiveAncestorsOfType<Element>()) {
   1027          if (element->IsEditingHost()) {
   1028            if (!closestEditingHost) {
   1029              closestEditingHost = element;
   1030            }
   1031            if (focusedElement == element) {
   1032              ancestorLimiter = focusedElement;
   1033              break;
   1034            }
   1035          }
   1036        }
   1037        if (!ancestorLimiter) {
   1038          ancestorLimiter = closestEditingHost;
   1039        }
   1040      }
   1041      // If it's the root element, we don't need to limit the new caret
   1042      // position.
   1043      if (ancestorLimiter && !ancestorLimiter->GetParent()) {
   1044        ancestorLimiter = nullptr;
   1045      }
   1046    }
   1047  }
   1048  return ancestorLimiter;
   1049 }
   1050 
   1051 nsPrevNextBidiLevels nsFrameSelection::GetPrevNextBidiLevels(
   1052    nsIContent* aNode, uint32_t aContentOffset, bool aJumpLines) const {
   1053  return SelectionMovementUtils::GetPrevNextBidiLevels(
   1054      aNode, aContentOffset, mCaret.mHint, aJumpLines,
   1055      GetAncestorLimiterOrIndependentSelectionRootElement());
   1056 }
   1057 
   1058 nsresult nsFrameSelection::MaintainSelection(nsSelectionAmount aAmount) {
   1059  const Selection& sel = NormalSelection();
   1060 
   1061  mMaintainedRange.MaintainAnchorFocusRange(sel, aAmount);
   1062 
   1063  return NS_OK;
   1064 }
   1065 
   1066 void nsFrameSelection::BidiLevelFromMove(PresShell* aPresShell,
   1067                                         nsIContent* aNode,
   1068                                         uint32_t aContentOffset,
   1069                                         nsSelectionAmount aAmount,
   1070                                         CaretAssociationHint aHint) {
   1071  switch (aAmount) {
   1072    // Movement within the line: the new cursor Bidi level is the level of the
   1073    // last character moved over
   1074    case eSelectCharacter:
   1075    case eSelectCluster:
   1076    case eSelectWord:
   1077    case eSelectWordNoSpace:
   1078    case eSelectBeginLine:
   1079    case eSelectEndLine:
   1080    case eSelectNoAmount: {
   1081      nsPrevNextBidiLevels levels =
   1082          SelectionMovementUtils::GetPrevNextBidiLevels(
   1083              aNode, aContentOffset, aHint, false,
   1084              GetAncestorLimiterOrIndependentSelectionRootElement());
   1085 
   1086      SetCaretBidiLevelAndMaybeSchedulePaint(
   1087          aHint == CaretAssociationHint::Before ? levels.mLevelBefore
   1088                                                : levels.mLevelAfter);
   1089      break;
   1090    }
   1091      /*
   1092    // Up and Down: the new cursor Bidi level is the smaller of the two
   1093    surrounding characters case eSelectLine: case eSelectParagraph:
   1094      GetPrevNextBidiLevels(aContext, aNode, aContentOffset, &firstFrame,
   1095    &secondFrame, &firstLevel, &secondLevel);
   1096      aPresShell->SetCaretBidiLevelAndMaybeSchedulePaint(std::min(firstLevel,
   1097    secondLevel)); break;
   1098      */
   1099 
   1100    default:
   1101      UndefineCaretBidiLevel();
   1102  }
   1103 }
   1104 
   1105 void nsFrameSelection::BidiLevelFromClick(nsIContent* aNode,
   1106                                          uint32_t aContentOffset) {
   1107  nsIFrame* clickInFrame = SelectionMovementUtils::GetFrameForNodeOffset(
   1108      aNode, aContentOffset, mCaret.mHint);
   1109  if (!clickInFrame) {
   1110    return;
   1111  }
   1112 
   1113  SetCaretBidiLevelAndMaybeSchedulePaint(clickInFrame->GetEmbeddingLevel());
   1114 }
   1115 
   1116 void nsFrameSelection::MaintainedRange::AdjustNormalSelection(
   1117    const nsIContent* aContent, const int32_t aOffset,
   1118    Selection& aNormalSelection) const {
   1119  MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
   1120 
   1121  if (!mRange || !aContent) {
   1122    return;
   1123  }
   1124 
   1125  nsINode* rangeStartNode = mRange->GetStartContainer();
   1126  nsINode* rangeEndNode = mRange->GetEndContainer();
   1127  const uint32_t rangeStartOffset = mRange->StartOffset();
   1128  const uint32_t rangeEndOffset = mRange->EndOffset();
   1129 
   1130  NS_ASSERTION(aOffset >= 0, "aOffset should not be negative");
   1131  const Maybe<int32_t> relToStart =
   1132      nsContentUtils::ComparePoints_AllowNegativeOffsets(
   1133          rangeStartNode, rangeStartOffset, aContent, aOffset);
   1134  if (NS_WARN_IF(!relToStart)) {
   1135    // Potentially handle this properly when Selection across Shadow DOM
   1136    // boundary is implemented
   1137    // (https://bugzilla.mozilla.org/show_bug.cgi?id=1607497).
   1138    return;
   1139  }
   1140 
   1141  const Maybe<int32_t> relToEnd =
   1142      nsContentUtils::ComparePoints_AllowNegativeOffsets(
   1143          rangeEndNode, rangeEndOffset, aContent, aOffset);
   1144  if (NS_WARN_IF(!relToEnd)) {
   1145    // Potentially handle this properly when Selection across Shadow DOM
   1146    // boundary is implemented
   1147    // (https://bugzilla.mozilla.org/show_bug.cgi?id=1607497).
   1148    return;
   1149  }
   1150 
   1151  // If aContent/aOffset is inside (or at the edge of) the maintained
   1152  // selection, or if it is on the "anchor" side of the maintained selection,
   1153  // we need to do something.
   1154  if ((*relToStart <= 0 && *relToEnd >= 0) ||
   1155      (*relToStart > 0 && aNormalSelection.GetDirection() == eDirNext) ||
   1156      (*relToEnd < 0 && aNormalSelection.GetDirection() == eDirPrevious)) {
   1157    // Set the current range to the maintained range.
   1158    aNormalSelection.ReplaceAnchorFocusRange(mRange);
   1159    // Set the direction of the selection so that the anchor will be on the
   1160    // far side of the maintained selection, relative to aContent/aOffset.
   1161    aNormalSelection.SetDirection(*relToStart > 0 ? eDirPrevious : eDirNext);
   1162  }
   1163 }
   1164 
   1165 void nsFrameSelection::MaintainedRange::AdjustContentOffsets(
   1166    nsIFrame::ContentOffsets& aOffsets, StopAtScroller aStopAtScroller) const {
   1167  // Adjust offsets according to maintained amount
   1168  if (mRange && mAmount != eSelectNoAmount) {
   1169    const Maybe<int32_t> relativePosition = nsContentUtils::ComparePoints(
   1170        mRange->StartRef(),
   1171        RawRangeBoundary(aOffsets.content, aOffsets.offset,
   1172                         RangeBoundaryIsMutationObserved::No));
   1173    if (NS_WARN_IF(!relativePosition)) {
   1174      // Potentially handle this properly when Selection across Shadow DOM
   1175      // boundary is implemented
   1176      // (https://bugzilla.mozilla.org/show_bug.cgi?id=1607497).
   1177      return;
   1178    }
   1179 
   1180    nsDirection direction = *relativePosition > 0 ? eDirPrevious : eDirNext;
   1181    nsSelectionAmount amount = mAmount;
   1182    if (amount == eSelectBeginLine && direction == eDirNext) {
   1183      amount = eSelectEndLine;
   1184    }
   1185 
   1186    FrameAndOffset frameAndOffset =
   1187        SelectionMovementUtils::GetFrameForNodeOffset(
   1188            aOffsets.content, aOffsets.offset, CaretAssociationHint::After);
   1189 
   1190    PeekOffsetOptions peekOffsetOptions{};
   1191    if (aStopAtScroller == StopAtScroller::Yes) {
   1192      peekOffsetOptions += PeekOffsetOption::StopAtScroller;
   1193    }
   1194    if (frameAndOffset && amount == eSelectWord && direction == eDirPrevious) {
   1195      // To avoid selecting the previous word when at start of word,
   1196      // first move one character forward.
   1197      PeekOffsetStruct charPos(
   1198          eSelectCharacter, eDirNext,
   1199          static_cast<int32_t>(frameAndOffset.mOffsetInFrameContent),
   1200          nsPoint(0, 0), peekOffsetOptions);
   1201      if (NS_SUCCEEDED(frameAndOffset->PeekOffset(&charPos))) {
   1202        frameAndOffset = {charPos.mResultFrame,
   1203                          static_cast<uint32_t>(charPos.mContentOffset)};
   1204      }
   1205    }
   1206 
   1207    PeekOffsetStruct pos(
   1208        amount, direction,
   1209        static_cast<int32_t>(frameAndOffset.mOffsetInFrameContent),
   1210        nsPoint(0, 0), peekOffsetOptions);
   1211    if (frameAndOffset && NS_SUCCEEDED(frameAndOffset->PeekOffset(&pos)) &&
   1212        pos.mResultContent) {
   1213      aOffsets.content = pos.mResultContent;
   1214      aOffsets.offset = pos.mContentOffset;
   1215    }
   1216  }
   1217 }
   1218 
   1219 void nsFrameSelection::MaintainedRange::MaintainAnchorFocusRange(
   1220    const Selection& aNormalSelection, const nsSelectionAmount aAmount) {
   1221  MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
   1222 
   1223  mAmount = aAmount;
   1224 
   1225  const nsRange* anchorFocusRange = aNormalSelection.GetAnchorFocusRange();
   1226  if (anchorFocusRange && aAmount != eSelectNoAmount) {
   1227    mRange = anchorFocusRange->CloneRange();
   1228    return;
   1229  }
   1230 
   1231  mRange = nullptr;
   1232 }
   1233 
   1234 nsresult nsFrameSelection::HandleClick(nsIContent* aNewFocus,
   1235                                       uint32_t aContentOffset,
   1236                                       uint32_t aContentEndOffset,
   1237                                       const FocusMode aFocusMode,
   1238                                       CaretAssociationHint aHint) {
   1239  if (!aNewFocus) {
   1240    return NS_ERROR_INVALID_ARG;
   1241  }
   1242 
   1243  if (MOZ_LOG_TEST(sFrameSelectionLog, LogLevel::Debug)) {
   1244    const Selection& sel = NormalSelection();
   1245    MOZ_LOG(sFrameSelectionLog, LogLevel::Debug,
   1246            ("%s: selection=%p, new focus=%p, offsets=(%u,%u), focus mode=%i",
   1247             __FUNCTION__, &sel, aNewFocus, aContentOffset, aContentEndOffset,
   1248             static_cast<int>(aFocusMode)));
   1249  }
   1250 
   1251  mDesiredCaretPos.Invalidate();
   1252 
   1253  if (aFocusMode != FocusMode::kExtendSelection) {
   1254    mMaintainedRange.mRange = nullptr;
   1255    if (!NodeIsInLimiters(aNewFocus)) {
   1256      mLimiters.mAncestorLimiter = nullptr;
   1257    }
   1258  }
   1259 
   1260  // Don't take focus when dragging off of a table
   1261  if (!mTableSelection.mDragSelectingCells) {
   1262    BidiLevelFromClick(aNewFocus, aContentOffset);
   1263    SetChangeReasons(nsISelectionListener::MOUSEDOWN_REASON +
   1264                     nsISelectionListener::DRAG_REASON);
   1265 
   1266    RefPtr<Selection> selection = &NormalSelection();
   1267 
   1268    if (aFocusMode == FocusMode::kExtendSelection) {
   1269      mMaintainedRange.AdjustNormalSelection(aNewFocus, aContentOffset,
   1270                                             *selection);
   1271    }
   1272 
   1273    AutoPrepareFocusRange prep(selection,
   1274                               aFocusMode == FocusMode::kMultiRangeSelection);
   1275    return TakeFocus(*aNewFocus, aContentOffset, aContentEndOffset, aHint,
   1276                     aFocusMode);
   1277  }
   1278 
   1279  return NS_OK;
   1280 }
   1281 
   1282 void nsFrameSelection::HandleDrag(nsIFrame* aFrame, const nsPoint& aPoint) {
   1283  if (!aFrame || !mPresShell) {
   1284    return;
   1285  }
   1286 
   1287  nsresult result;
   1288  nsIFrame* newFrame = 0;
   1289  nsPoint newPoint;
   1290 
   1291  result = ConstrainFrameAndPointToAnchorSubtree(aFrame, aPoint, &newFrame,
   1292                                                 newPoint);
   1293  if (NS_FAILED(result)) {
   1294    return;
   1295  }
   1296  if (!newFrame) {
   1297    return;
   1298  }
   1299 
   1300  nsIFrame::ContentOffsets offsets =
   1301      newFrame->GetContentOffsetsFromPoint(newPoint);
   1302  if (!offsets.content) {
   1303    return;
   1304  }
   1305 
   1306  RefPtr<Selection> selection = &NormalSelection();
   1307  if (newFrame->IsSelected()) {
   1308    // `MOZ_KnownLive` required because of
   1309    // https://bugzilla.mozilla.org/show_bug.cgi?id=1636889.
   1310    mMaintainedRange.AdjustNormalSelection(MOZ_KnownLive(offsets.content),
   1311                                           offsets.offset, *selection);
   1312  }
   1313 
   1314  mMaintainedRange.AdjustContentOffsets(
   1315      offsets, mLimiters.mIndependentSelectionRootElement
   1316                   ? MaintainedRange::StopAtScroller::Yes
   1317                   : MaintainedRange::StopAtScroller::No);
   1318 
   1319  // TODO: no click has happened, rename `HandleClick`.
   1320  HandleClick(MOZ_KnownLive(offsets.content) /* bug 1636889 */, offsets.offset,
   1321              offsets.offset, FocusMode::kExtendSelection, offsets.associate);
   1322 }
   1323 
   1324 nsresult nsFrameSelection::StartAutoScrollTimer(nsIFrame* aFrame,
   1325                                                const nsPoint& aPoint,
   1326                                                uint32_t aDelay) {
   1327  RefPtr<Selection> selection = &NormalSelection();
   1328  return selection->StartAutoScrollTimer(aFrame, aPoint, aDelay);
   1329 }
   1330 
   1331 void nsFrameSelection::StopAutoScrollTimer() {
   1332  Selection& sel = NormalSelection();
   1333  sel.StopAutoScrollTimer();
   1334 }
   1335 
   1336 // static
   1337 nsINode* nsFrameSelection::TableSelection::IsContentInActivelyEditableTableCell(
   1338    nsPresContext* aContext, nsIContent* aContent) {
   1339  if (!aContext) {
   1340    return nullptr;
   1341  }
   1342 
   1343  RefPtr<HTMLEditor> htmlEditor = nsContentUtils::GetHTMLEditor(aContext);
   1344  if (!htmlEditor) {
   1345    return nullptr;
   1346  }
   1347 
   1348  nsINode* inclusiveTableCellAncestor =
   1349      GetClosestInclusiveTableCellAncestor(aContent);
   1350  if (!inclusiveTableCellAncestor) {
   1351    return nullptr;
   1352  }
   1353 
   1354  const Element* editingHost = htmlEditor->ComputeEditingHost();
   1355  if (!editingHost) {
   1356    return nullptr;
   1357  }
   1358 
   1359  const bool editableCell =
   1360      inclusiveTableCellAncestor->IsInclusiveDescendantOf(editingHost);
   1361  return editableCell ? inclusiveTableCellAncestor : nullptr;
   1362 }
   1363 
   1364 namespace {
   1365 struct ParentAndOffset {
   1366  explicit ParentAndOffset(const nsINode& aNode)
   1367      : mParent{aNode.GetParent()},
   1368        mOffset{mParent ? mParent->ComputeIndexOf_Deprecated(&aNode) : 0} {}
   1369 
   1370  nsINode* mParent;
   1371 
   1372  // 0, if there's no parent.
   1373  int32_t mOffset;
   1374 };
   1375 
   1376 }  // namespace
   1377 /**
   1378 hard to go from nodes to frames, easy the other way!
   1379 */
   1380 nsresult nsFrameSelection::TakeFocus(nsIContent& aNewFocus,
   1381                                     uint32_t aContentOffset,
   1382                                     uint32_t aContentEndOffset,
   1383                                     CaretAssociationHint aHint,
   1384                                     const FocusMode aFocusMode) {
   1385  NS_ENSURE_STATE(mPresShell);
   1386 
   1387  if (!NodeIsInLimiters(&aNewFocus)) {
   1388    return NS_ERROR_FAILURE;
   1389  }
   1390 
   1391  MOZ_LOG(sFrameSelectionLog, LogLevel::Verbose,
   1392          ("%s: new focus=%p, offsets=(%u, %u), hint=%i, focusMode=%i",
   1393           __FUNCTION__, &aNewFocus, aContentOffset, aContentEndOffset,
   1394           static_cast<int>(aHint), static_cast<int>(aFocusMode)));
   1395 
   1396  mPresShell->FrameSelectionWillTakeFocus(
   1397      *this, aNewFocus.CanStartSelectionAsWebCompatHack()
   1398                 ? PresShell::CanMoveLastSelectionForToString::Yes
   1399                 : PresShell::CanMoveLastSelectionForToString::No);
   1400 
   1401  // Clear all table selection data
   1402  mTableSelection.mMode = TableSelectionMode::None;
   1403  mTableSelection.mDragSelectingCells = false;
   1404  mTableSelection.mStartSelectedCell = nullptr;
   1405  mTableSelection.mEndSelectedCell = nullptr;
   1406  mTableSelection.mAppendStartSelectedCell = nullptr;
   1407  mCaret.mHint = aHint;
   1408 
   1409  RefPtr<Selection> selection = &NormalSelection();
   1410 
   1411  Maybe<Selection::AutoUserInitiated> userSelect;
   1412  if (IsUserSelectionReason()) {
   1413    userSelect.emplace(selection);
   1414  }
   1415 
   1416  // traverse through document and unselect crap here
   1417  switch (aFocusMode) {
   1418    case FocusMode::kCollapseToNewPoint:
   1419      [[fallthrough]];
   1420    case FocusMode::kMultiRangeSelection: {
   1421      // single click? setting cursor down
   1422      const Batching saveBatching =
   1423          mBatching;  // hack to use the collapse code.
   1424      mBatching.mCounter = 1;
   1425 
   1426      if (aFocusMode == FocusMode::kMultiRangeSelection) {
   1427        // Remove existing collapsed ranges as there's no point in having
   1428        // non-anchor/focus collapsed ranges.
   1429        selection->RemoveCollapsedRanges();
   1430 
   1431        ErrorResult error;
   1432        RefPtr<nsRange> newRange = nsRange::Create(
   1433            &aNewFocus, aContentOffset, &aNewFocus, aContentOffset, error);
   1434        if (NS_WARN_IF(error.Failed())) {
   1435          return error.StealNSResult();
   1436        }
   1437        MOZ_ASSERT(newRange);
   1438        selection->AddRangeAndSelectFramesAndNotifyListeners(*newRange,
   1439                                                             IgnoreErrors());
   1440      } else {
   1441        bool oldDesiredPosSet =
   1442            mDesiredCaretPos.mIsSet;  // need to keep old desired
   1443                                      // position if it was set.
   1444        selection->CollapseInLimiter(&aNewFocus, aContentOffset);
   1445        mDesiredCaretPos.mIsSet =
   1446            oldDesiredPosSet;  // now reset desired pos back.
   1447      }
   1448 
   1449      mBatching = saveBatching;
   1450 
   1451      if (aContentEndOffset != aContentOffset) {
   1452        selection->Extend(&aNewFocus, aContentEndOffset);
   1453      }
   1454 
   1455      // find out if we are inside a table. if so, find out which one and which
   1456      // cell once we do that, the next time we get a takefocus, check the
   1457      // parent tree. if we are no longer inside same table ,cell then switch to
   1458      // table selection mode. BUT only do this in an editor
   1459 
   1460      NS_ENSURE_STATE(mPresShell);
   1461      RefPtr<nsPresContext> context = mPresShell->GetPresContext();
   1462      mTableSelection.mClosestInclusiveTableCellAncestor = nullptr;
   1463      if (nsINode* inclusiveTableCellAncestor =
   1464              TableSelection::IsContentInActivelyEditableTableCell(
   1465                  context, &aNewFocus)) {
   1466        mTableSelection.mClosestInclusiveTableCellAncestor =
   1467            inclusiveTableCellAncestor;
   1468        MOZ_LOG(sFrameSelectionLog, LogLevel::Debug,
   1469                ("%s: Collapsing into new cell", __FUNCTION__));
   1470      }
   1471 
   1472      break;
   1473    }
   1474    case FocusMode::kExtendSelection: {
   1475      // Now update the range list:
   1476      nsINode* inclusiveTableCellAncestor =
   1477          GetClosestInclusiveTableCellAncestor(&aNewFocus);
   1478      if (mTableSelection.mClosestInclusiveTableCellAncestor &&
   1479          inclusiveTableCellAncestor &&
   1480          inclusiveTableCellAncestor !=
   1481              mTableSelection
   1482                  .mClosestInclusiveTableCellAncestor)  // switch to cell
   1483                                                        // selection mode
   1484      {
   1485        MOZ_LOG(sFrameSelectionLog, LogLevel::Debug,
   1486                ("%s: moving into new cell", __FUNCTION__));
   1487 
   1488        WidgetMouseEvent event(false, eVoidEvent, nullptr,
   1489                               WidgetMouseEvent::eReal);
   1490 
   1491        // Start selecting in the cell we were in before
   1492        ParentAndOffset parentAndOffset{
   1493            *mTableSelection.mClosestInclusiveTableCellAncestor};
   1494        if (const nsCOMPtr<nsINode> previousParent = parentAndOffset.mParent) {
   1495          const nsresult result =
   1496              HandleTableSelection(previousParent, parentAndOffset.mOffset,
   1497                                   TableSelectionMode::Cell, &event);
   1498          if (NS_WARN_IF(NS_FAILED(result))) {
   1499            return result;
   1500          }
   1501        }
   1502 
   1503        // Find the parent of this new cell and extend selection to it
   1504        parentAndOffset = ParentAndOffset{*inclusiveTableCellAncestor};
   1505 
   1506        // XXXX We need to REALLY get the current key shift state
   1507        //  (we'd need to add event listener -- let's not bother for now)
   1508        event.mModifiers &= ~MODIFIER_SHIFT;  // aExtendSelection;
   1509        if (const nsCOMPtr<nsINode> newParent = parentAndOffset.mParent) {
   1510          mTableSelection.mClosestInclusiveTableCellAncestor =
   1511              inclusiveTableCellAncestor;
   1512          // Continue selection into next cell
   1513          const nsresult result =
   1514              HandleTableSelection(newParent, parentAndOffset.mOffset,
   1515                                   TableSelectionMode::Cell, &event);
   1516          if (NS_WARN_IF(NS_FAILED(result))) {
   1517            return result;
   1518          }
   1519        }
   1520      } else {
   1521        // XXXX Problem: Shift+click in browser is appending text selection to
   1522        // selected table!!!
   1523        //   is this the place to erase selected cells ?????
   1524        uint32_t offset =
   1525            (selection->GetDirection() == eDirNext &&
   1526             aContentEndOffset > aContentOffset)  // didn't go far enough
   1527                ? aContentEndOffset  // this will only redraw the diff
   1528                : aContentOffset;
   1529        selection->Extend(&aNewFocus, offset);
   1530      }
   1531      break;
   1532    }
   1533  }
   1534 
   1535  // Be aware, the Selection instance may be destroyed after this call.
   1536  return NotifySelectionListeners(SelectionType::eNormal);
   1537 }
   1538 
   1539 UniquePtr<SelectionDetails> nsFrameSelection::LookUpSelection(
   1540    nsIContent* aContent, int32_t aContentOffset, int32_t aContentLength,
   1541    IgnoreNormalSelection aIgnoreNormalSelection) const {
   1542  if (!aContent || !mPresShell) {
   1543    return nullptr;
   1544  }
   1545 
   1546  // TODO: Layout should use `uint32_t` for handling offset in DOM nodes
   1547  //       (for example: bug 1735262)
   1548  MOZ_ASSERT(aContentOffset >= 0);
   1549  MOZ_ASSERT(aContentLength >= 0);
   1550  if (MOZ_UNLIKELY(aContentOffset < 0) || MOZ_UNLIKELY(aContentLength < 0)) {
   1551    return nullptr;
   1552  }
   1553 
   1554  UniquePtr<SelectionDetails> details;
   1555  for (size_t j = aIgnoreNormalSelection == IgnoreNormalSelection::Yes ? 1 : 0;
   1556       j < std::size(mDomSelections); j++) {
   1557    MOZ_ASSERT(mDomSelections[j]);
   1558    details = mDomSelections[j]->LookUpSelection(
   1559        aContent, static_cast<uint32_t>(aContentOffset),
   1560        static_cast<uint32_t>(aContentLength), std::move(details),
   1561        kPresentSelectionTypes[j]);
   1562  }
   1563 
   1564  // This may seem counter intuitive at first. Highlight selections need to be
   1565  // iterated from back to front:
   1566  //
   1567  //  - `mHighlightSelections` is ordered by insertion, i.e. if two or more
   1568  //  highlights overlap, the latest must take precedence.
   1569  //  - however, the `LookupSelection()` algorithm reverses the order by setting
   1570  //    the current `details` as `mNext`.
   1571  for (const auto& iter : Reversed(mHighlightSelections)) {
   1572    details = iter.second()->LookUpSelection(
   1573        aContent, static_cast<uint32_t>(aContentOffset),
   1574        static_cast<uint32_t>(aContentLength), std::move(details),
   1575        SelectionType::eHighlight);
   1576  }
   1577 
   1578  return details;
   1579 }
   1580 
   1581 void nsFrameSelection::SetDragState(bool aState) {
   1582  if (mDragState == aState) {
   1583    return;
   1584  }
   1585 
   1586  mDragState = aState;
   1587 
   1588  if (!mDragState) {
   1589    mTableSelection.mDragSelectingCells = false;
   1590    // Notify that reason is mouse up.
   1591    SetChangeReasons(nsISelectionListener::MOUSEUP_REASON);
   1592 
   1593    // flag is set to NotApplicable in `Selection::NotifySelectionListeners`.
   1594    // since this function call is part of click event, this would immediately
   1595    // reset the flag, rendering it useless.
   1596    AutoRestore<ClickSelectionType> restoreClickSelectionType(
   1597        mClickSelectionType);
   1598    // Be aware, the Selection instance may be destroyed after this call.
   1599    NotifySelectionListeners(SelectionType::eNormal);
   1600  }
   1601 }
   1602 
   1603 Selection* nsFrameSelection::GetSelection(SelectionType aSelectionType) const {
   1604  int8_t index = GetIndexFromSelectionType(aSelectionType);
   1605  if (index < 0) {
   1606    return nullptr;
   1607  }
   1608  MOZ_ASSERT(mDomSelections[index]);
   1609  return mDomSelections[index];
   1610 }
   1611 
   1612 void nsFrameSelection::AddHighlightSelection(
   1613    nsAtom* aHighlightName, mozilla::dom::Highlight& aHighlight) {
   1614  RefPtr<Selection> selection =
   1615      aHighlight.CreateHighlightSelection(aHighlightName, this);
   1616  if (auto iter =
   1617          std::find_if(mHighlightSelections.begin(), mHighlightSelections.end(),
   1618                       [&aHighlightName](auto const& aElm) {
   1619                         return aElm.first() == aHighlightName;
   1620                       });
   1621      iter != mHighlightSelections.end()) {
   1622    iter->second() = std::move(selection);
   1623  } else {
   1624    mHighlightSelections.AppendElement(
   1625        CompactPair<RefPtr<nsAtom>, RefPtr<Selection>>(aHighlightName,
   1626                                                       std::move(selection)));
   1627  }
   1628 }
   1629 
   1630 void nsFrameSelection::RepaintHighlightSelection(nsAtom* aHighlightName) {
   1631  if (auto iter =
   1632          std::find_if(mHighlightSelections.begin(), mHighlightSelections.end(),
   1633                       [&aHighlightName](auto const& aElm) {
   1634                         return aElm.first() == aHighlightName;
   1635                       });
   1636      iter != mHighlightSelections.end()) {
   1637    RefPtr selection = iter->second();
   1638    selection->Repaint(mPresShell->GetPresContext());
   1639  }
   1640 }
   1641 
   1642 void nsFrameSelection::RemoveHighlightSelection(nsAtom* aHighlightName) {
   1643  if (auto iter =
   1644          std::find_if(mHighlightSelections.begin(), mHighlightSelections.end(),
   1645                       [&aHighlightName](auto const& aElm) {
   1646                         return aElm.first() == aHighlightName;
   1647                       });
   1648      iter != mHighlightSelections.end()) {
   1649    RefPtr<Selection> selection = iter->second();
   1650    selection->RemoveAllRanges(IgnoreErrors());
   1651    mHighlightSelections.RemoveElementAt(iter);
   1652  }
   1653 }
   1654 
   1655 void nsFrameSelection::AddHighlightSelectionRange(
   1656    nsAtom* aHighlightName, mozilla::dom::Highlight& aHighlight,
   1657    mozilla::dom::AbstractRange& aRange) {
   1658  if (auto iter =
   1659          std::find_if(mHighlightSelections.begin(), mHighlightSelections.end(),
   1660                       [&aHighlightName](auto const& aElm) {
   1661                         return aElm.first() == aHighlightName;
   1662                       });
   1663      iter != mHighlightSelections.end()) {
   1664    RefPtr<Selection> selection = iter->second();
   1665    selection->AddHighlightRangeAndSelectFramesAndNotifyListeners(aRange);
   1666  } else {
   1667    // if the selection does not exist yet, add all of its ranges and exit.
   1668    RefPtr<Selection> selection =
   1669        aHighlight.CreateHighlightSelection(aHighlightName, this);
   1670    mHighlightSelections.AppendElement(
   1671        CompactPair<RefPtr<nsAtom>, RefPtr<Selection>>(aHighlightName,
   1672                                                       std::move(selection)));
   1673  }
   1674 }
   1675 
   1676 void nsFrameSelection::RemoveHighlightSelectionRange(
   1677    nsAtom* aHighlightName, mozilla::dom::AbstractRange& aRange) {
   1678  if (auto iter =
   1679          std::find_if(mHighlightSelections.begin(), mHighlightSelections.end(),
   1680                       [&aHighlightName](auto const& aElm) {
   1681                         return aElm.first() == aHighlightName;
   1682                       });
   1683      iter != mHighlightSelections.end()) {
   1684    // NOLINTNEXTLINE(performance-unnecessary-copy-initialization)
   1685    RefPtr<Selection> selection = iter->second();
   1686    selection->RemoveRangeAndUnselectFramesAndNotifyListeners(aRange,
   1687                                                              IgnoreErrors());
   1688  }
   1689 }
   1690 
   1691 nsresult nsFrameSelection::ScrollSelectionIntoView(SelectionType aSelectionType,
   1692                                                   SelectionRegion aRegion,
   1693                                                   int16_t aFlags) const {
   1694  RefPtr<Selection> sel = GetSelection(aSelectionType);
   1695  if (!sel) {
   1696    return NS_ERROR_INVALID_ARG;
   1697  }
   1698 
   1699  const auto vScroll = [&]() -> WhereToScroll {
   1700    if (aFlags & nsISelectionController::SCROLL_VERTICAL_START) {
   1701      return WhereToScroll::Start;
   1702    }
   1703    if (aFlags & nsISelectionController::SCROLL_VERTICAL_END) {
   1704      return WhereToScroll::End;
   1705    }
   1706    if (aFlags & nsISelectionController::SCROLL_VERTICAL_CENTER) {
   1707      return WhereToScroll::Center;
   1708    }
   1709    return WhereToScroll::Nearest;
   1710  }();
   1711 
   1712  auto mode = aFlags & nsISelectionController::SCROLL_SYNCHRONOUS
   1713                  ? SelectionScrollMode::SyncFlush
   1714                  : SelectionScrollMode::Async;
   1715 
   1716  auto scrollFlags = ScrollFlags::None;
   1717  if (aFlags & nsISelectionController::SCROLL_OVERFLOW_HIDDEN) {
   1718    scrollFlags |= ScrollFlags::ScrollOverflowHidden;
   1719  }
   1720 
   1721  // After ScrollSelectionIntoView(), the pending notifications might be
   1722  // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
   1723  return sel->ScrollIntoView(aRegion, ScrollAxis(vScroll), ScrollAxis(),
   1724                             scrollFlags, mode);
   1725 }
   1726 
   1727 nsresult nsFrameSelection::RepaintSelection(SelectionType aSelectionType) {
   1728  RefPtr<Selection> sel = GetSelection(aSelectionType);
   1729  if (!sel) {
   1730    return NS_ERROR_INVALID_ARG;
   1731  }
   1732  if (!mPresShell) {
   1733    return NS_ERROR_UNEXPECTED;
   1734  }
   1735 
   1736 // On macOS, update the selection cache to the new active selection
   1737 // aka the current selection.
   1738 #ifdef XP_MACOSX
   1739  // Check that we're in the an active window and, if this is Web content,
   1740  // in the frontmost tab.
   1741  Document* doc = mPresShell->GetDocument();
   1742  if (doc && IsInActiveTab(doc) && aSelectionType == SelectionType::eNormal) {
   1743    UpdateSelectionCacheOnRepaintSelection(sel);
   1744  }
   1745 #endif
   1746  return sel->Repaint(mPresShell->GetPresContext());
   1747 }
   1748 
   1749 nsIFrame* nsFrameSelection::GetFrameToPageSelect() const {
   1750  if (NS_WARN_IF(!mPresShell)) {
   1751    return nullptr;
   1752  }
   1753 
   1754  nsIFrame* rootFrameToSelect;
   1755  if (mLimiters.mIndependentSelectionRootElement) {
   1756    rootFrameToSelect =
   1757        mLimiters.mIndependentSelectionRootElement->GetPrimaryFrame();
   1758    if (NS_WARN_IF(!rootFrameToSelect)) {
   1759      return nullptr;
   1760    }
   1761  } else if (mLimiters.mAncestorLimiter) {
   1762    rootFrameToSelect = mLimiters.mAncestorLimiter->GetPrimaryFrame();
   1763    if (NS_WARN_IF(!rootFrameToSelect)) {
   1764      return nullptr;
   1765    }
   1766  } else {
   1767    rootFrameToSelect = mPresShell->GetRootScrollContainerFrame();
   1768    if (NS_WARN_IF(!rootFrameToSelect)) {
   1769      return nullptr;
   1770    }
   1771  }
   1772 
   1773  nsCOMPtr<nsIContent> contentToSelect = mPresShell->GetContentForScrolling();
   1774  if (contentToSelect) {
   1775    // If there is selected content, look for nearest and vertical scrollable
   1776    // parent under the root frame.
   1777    for (nsIFrame* frame = contentToSelect->GetPrimaryFrame();
   1778         frame && frame != rootFrameToSelect; frame = frame->GetParent()) {
   1779      ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(frame);
   1780      if (!scrollContainerFrame) {
   1781        continue;
   1782      }
   1783      ScrollStyles scrollStyles = scrollContainerFrame->GetScrollStyles();
   1784      if (scrollStyles.mVertical == StyleOverflow::Hidden) {
   1785        continue;
   1786      }
   1787      layers::ScrollDirections directions =
   1788          scrollContainerFrame->GetAvailableScrollingDirections();
   1789      if (directions.contains(layers::ScrollDirection::eVertical)) {
   1790        // If there is sub scrollable frame, let's use its page size to select.
   1791        return frame;
   1792      }
   1793    }
   1794  }
   1795  // Otherwise, i.e., there is no scrollable frame or only the root frame is
   1796  // scrollable, let's return the root frame because Shift + PageUp/PageDown
   1797  // should expand the selection in the root content even if it's not
   1798  // scrollable.
   1799  return rootFrameToSelect;
   1800 }
   1801 
   1802 nsresult nsFrameSelection::PageMove(bool aForward, bool aExtend,
   1803                                    nsIFrame* aFrame,
   1804                                    SelectionIntoView aSelectionIntoView) {
   1805  MOZ_ASSERT(aFrame);
   1806 
   1807  // expected behavior for PageMove is to scroll AND move the caret
   1808  // and remain relative position of the caret in view. see Bug 4302.
   1809 
   1810  // Get the scroll container frame.  If aFrame is not scrollable, this is
   1811  // nullptr.
   1812  ScrollContainerFrame* scrollContainerFrame = aFrame->GetScrollTargetFrame();
   1813  // Get the scrolled frame.  If aFrame is not scrollable, this is aFrame
   1814  // itself.
   1815  nsIFrame* scrolledFrame =
   1816      scrollContainerFrame ? scrollContainerFrame->GetScrolledFrame() : aFrame;
   1817  if (!scrolledFrame) {
   1818    return NS_OK;
   1819  }
   1820 
   1821  // find out where the caret is.
   1822  // we should know mDesiredCaretPos.mValue value of nsFrameSelection, but I
   1823  // havent seen that behavior in other windows applications yet.
   1824  RefPtr<Selection> selection = &NormalSelection();
   1825  if (!selection) {
   1826    return NS_OK;
   1827  }
   1828 
   1829  nsRect caretPos;
   1830  nsIFrame* caretFrame = nsCaret::GetGeometry(selection, &caretPos);
   1831  if (!caretFrame) {
   1832    return NS_OK;
   1833  }
   1834 
   1835  // If the scrolled frame is outside of current selection limiter,
   1836  // we need to scroll the frame but keep moving selection in the limiter.
   1837  nsIFrame* frameToClick = scrolledFrame;
   1838  if (!NodeIsInLimiters(scrolledFrame->GetContent())) {
   1839    frameToClick = GetFrameToPageSelect();
   1840    if (NS_WARN_IF(!frameToClick)) {
   1841      return NS_OK;
   1842    }
   1843  }
   1844 
   1845  if (scrollContainerFrame) {
   1846    // If there is a scrollable frame, adjust pseudo-click position with page
   1847    // scroll amount.
   1848    // XXX This may scroll more than one page if ScrollSelectionIntoView is
   1849    //     called later because caret may not fully visible.  E.g., if
   1850    //     clicking line will be visible only half height with scrolling
   1851    //     the frame, ScrollSelectionIntoView additionally scrolls to show
   1852    //     the caret entirely.
   1853    if (aForward) {
   1854      caretPos.y += scrollContainerFrame->GetPageScrollAmount().height;
   1855    } else {
   1856      caretPos.y -= scrollContainerFrame->GetPageScrollAmount().height;
   1857    }
   1858  } else {
   1859    // Otherwise, adjust pseudo-click position with the frame size.
   1860    if (aForward) {
   1861      caretPos.y += frameToClick->GetSize().height;
   1862    } else {
   1863      caretPos.y -= frameToClick->GetSize().height;
   1864    }
   1865  }
   1866 
   1867  caretPos += caretFrame->GetOffsetTo(frameToClick);
   1868 
   1869  // get a content at desired location
   1870  nsPoint desiredPoint;
   1871  desiredPoint.x = caretPos.x;
   1872  desiredPoint.y = caretPos.y + caretPos.height / 2;
   1873  nsIFrame::ContentOffsets offsets =
   1874      frameToClick->GetContentOffsetsFromPoint(desiredPoint);
   1875 
   1876  if (!offsets.content) {
   1877    // XXX Do we need to handle ScrollSelectionIntoView in this case?
   1878    return NS_OK;
   1879  }
   1880 
   1881  // First, place the caret.
   1882  bool selectionChanged;
   1883  {
   1884    // We don't want any script to run until we check whether selection is
   1885    // modified by HandleClick.
   1886    SelectionBatcher ensureNoSelectionChangeNotifications(selection,
   1887                                                          __FUNCTION__);
   1888 
   1889    RangeBoundary oldAnchor = selection->AnchorRef();
   1890    RangeBoundary oldFocus = selection->FocusRef();
   1891 
   1892    const FocusMode focusMode =
   1893        aExtend ? FocusMode::kExtendSelection : FocusMode::kCollapseToNewPoint;
   1894    HandleClick(MOZ_KnownLive(offsets.content) /* bug 1636889 */,
   1895                offsets.offset, offsets.offset, focusMode,
   1896                CaretAssociationHint::After);
   1897 
   1898    selectionChanged = selection->AnchorRef() != oldAnchor ||
   1899                       selection->FocusRef() != oldFocus;
   1900  }
   1901 
   1902  bool doScrollSelectionIntoView = !(
   1903      aSelectionIntoView == SelectionIntoView::IfChanged && !selectionChanged);
   1904 
   1905  // Then, scroll the given frame one page.
   1906  if (scrollContainerFrame) {
   1907    // If we'll call ScrollSelectionIntoView later and selection wasn't
   1908    // changed and we scroll outside of selection limiter, we shouldn't use
   1909    // smooth scroll here because ScrollContainerFrame uses normal runnable,
   1910    // but ScrollSelectionIntoView uses early runner and it cancels the
   1911    // pending smooth scroll.  Therefore, if we used smooth scroll in such
   1912    // case, ScrollSelectionIntoView would scroll to show caret instead of
   1913    // page scroll of an element outside selection limiter.
   1914    ScrollMode scrollMode = doScrollSelectionIntoView && !selectionChanged &&
   1915                                    scrolledFrame != frameToClick
   1916                                ? ScrollMode::Instant
   1917                                : ScrollMode::Smooth;
   1918    scrollContainerFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
   1919                                   ScrollUnit::PAGES, scrollMode);
   1920  }
   1921 
   1922  // Finally, scroll selection into view if requested.
   1923  if (!doScrollSelectionIntoView) {
   1924    return NS_OK;
   1925  }
   1926  return ScrollSelectionIntoView(SelectionType::eNormal,
   1927                                 nsISelectionController::SELECTION_FOCUS_REGION,
   1928                                 nsISelectionController::SCROLL_SYNCHRONOUS);
   1929 }
   1930 
   1931 nsresult nsFrameSelection::PhysicalMove(int16_t aDirection, int16_t aAmount,
   1932                                        bool aExtend) {
   1933  NS_ENSURE_STATE(mPresShell);
   1934  // Flush out layout, since we need it to be up to date to do caret
   1935  // positioning.
   1936  OwningNonNull<PresShell> presShell(*mPresShell);
   1937  presShell->FlushPendingNotifications(FlushType::Layout);
   1938 
   1939  if (!mPresShell) {
   1940    return NS_OK;
   1941  }
   1942 
   1943  // Check that parameters are safe
   1944  if (aDirection < 0 || aDirection > 3 || aAmount < 0 || aAmount > 1) {
   1945    return NS_ERROR_FAILURE;
   1946  }
   1947 
   1948  nsPresContext* context = mPresShell->GetPresContext();
   1949  if (!context) {
   1950    return NS_ERROR_FAILURE;
   1951  }
   1952 
   1953  RefPtr<Selection> sel = &NormalSelection();
   1954 
   1955  // Map the abstract movement amounts (0-1) to direction-specific
   1956  // selection units.
   1957  static const nsSelectionAmount inlineAmount[] = {eSelectCluster, eSelectWord};
   1958  static const nsSelectionAmount blockPrevAmount[] = {eSelectLine,
   1959                                                      eSelectBeginLine};
   1960  static const nsSelectionAmount blockNextAmount[] = {eSelectLine,
   1961                                                      eSelectEndLine};
   1962 
   1963  struct PhysicalToLogicalMapping {
   1964    nsDirection direction;
   1965    const nsSelectionAmount* amounts;
   1966  };
   1967  static const PhysicalToLogicalMapping verticalLR[4] = {
   1968      {eDirPrevious, blockPrevAmount},  // left
   1969      {eDirNext, blockNextAmount},      // right
   1970      {eDirPrevious, inlineAmount},     // up
   1971      {eDirNext, inlineAmount}          // down
   1972  };
   1973  static const PhysicalToLogicalMapping verticalRL[4] = {
   1974      {eDirNext, blockNextAmount},
   1975      {eDirPrevious, blockPrevAmount},
   1976      {eDirPrevious, inlineAmount},
   1977      {eDirNext, inlineAmount}};
   1978  static const PhysicalToLogicalMapping horizontal[4] = {
   1979      {eDirPrevious, inlineAmount},
   1980      {eDirNext, inlineAmount},
   1981      {eDirPrevious, blockPrevAmount},
   1982      {eDirNext, blockNextAmount}};
   1983 
   1984  WritingMode wm;
   1985  const PrimaryFrameData frameForFocus =
   1986      sel->GetPrimaryFrameForCaretAtFocusNode(true);
   1987  if (frameForFocus) {
   1988    // FYI: Setting the caret association hint was done during a call of
   1989    // GetPrimaryFrameForCaretAtFocusNode.  Therefore, this may not be intended
   1990    // by the original author.
   1991    sel->GetFrameSelection()->SetHint(frameForFocus.mHint);
   1992 
   1993    if (!frameForFocus->Style()->IsTextCombined()) {
   1994      wm = frameForFocus->GetWritingMode();
   1995    } else {
   1996      // Using different direction for horizontal-in-vertical would
   1997      // make it hard to navigate via keyboard. Inherit the moving
   1998      // direction from its parent.
   1999      MOZ_ASSERT(frameForFocus->IsTextFrame());
   2000      wm = frameForFocus->GetParent()->GetWritingMode();
   2001      MOZ_ASSERT(wm.IsVertical(),
   2002                 "Text combined "
   2003                 "can only appear in vertical text");
   2004    }
   2005  }
   2006 
   2007  const PhysicalToLogicalMapping& mapping =
   2008      wm.IsVertical()
   2009          ? wm.IsVerticalLR() ? verticalLR[aDirection] : verticalRL[aDirection]
   2010          : horizontal[aDirection];
   2011 
   2012  nsresult rv = MoveCaret(mapping.direction, ExtendSelection(aExtend),
   2013                          mapping.amounts[aAmount], eVisual);
   2014  if (NS_FAILED(rv)) {
   2015    // If we tried to do a line move, but couldn't move in the given direction,
   2016    // then we'll "promote" this to a line-edge move instead.
   2017    if (mapping.amounts[aAmount] == eSelectLine) {
   2018      rv = MoveCaret(mapping.direction, ExtendSelection(aExtend),
   2019                     mapping.amounts[aAmount + 1], eVisual);
   2020    }
   2021    // And if it was a next-word move that failed (which can happen when
   2022    // eat_space_to_next_word is true, see bug 1153237), then just move forward
   2023    // to the line-edge.
   2024    else if (mapping.amounts[aAmount] == eSelectWord &&
   2025             mapping.direction == eDirNext) {
   2026      rv = MoveCaret(eDirNext, ExtendSelection(aExtend), eSelectEndLine,
   2027                     eVisual);
   2028    }
   2029  }
   2030 
   2031  return rv;
   2032 }
   2033 
   2034 nsresult nsFrameSelection::CharacterMove(bool aForward, bool aExtend) {
   2035  return MoveCaret(aForward ? eDirNext : eDirPrevious, ExtendSelection(aExtend),
   2036                   eSelectCluster, eUsePrefStyle);
   2037 }
   2038 
   2039 nsresult nsFrameSelection::WordMove(bool aForward, bool aExtend) {
   2040  return MoveCaret(aForward ? eDirNext : eDirPrevious, ExtendSelection(aExtend),
   2041                   eSelectWord, eUsePrefStyle);
   2042 }
   2043 
   2044 nsresult nsFrameSelection::LineMove(bool aForward, bool aExtend) {
   2045  return MoveCaret(aForward ? eDirNext : eDirPrevious, ExtendSelection(aExtend),
   2046                   eSelectLine, eUsePrefStyle);
   2047 }
   2048 
   2049 nsresult nsFrameSelection::IntraLineMove(bool aForward, bool aExtend) {
   2050  if (aForward) {
   2051    return MoveCaret(eDirNext, ExtendSelection(aExtend), eSelectEndLine,
   2052                     eLogical);
   2053  }
   2054  return MoveCaret(eDirPrevious, ExtendSelection(aExtend), eSelectBeginLine,
   2055                   eLogical);
   2056 }
   2057 
   2058 // static
   2059 template <typename RangeType>
   2060 Result<RefPtr<RangeType>, nsresult>
   2061 nsFrameSelection::CreateRangeExtendedToSomewhere(
   2062    PresShell& aPresShell,
   2063    const mozilla::LimitersAndCaretData& aLimitersAndCaretData,
   2064    const AbstractRange& aRange, nsDirection aRangeDirection,
   2065    nsDirection aExtendDirection, nsSelectionAmount aAmount,
   2066    CaretMovementStyle aMovementStyle) {
   2067  MOZ_ASSERT(aRangeDirection == eDirNext || aRangeDirection == eDirPrevious);
   2068  MOZ_ASSERT(aExtendDirection == eDirNext || aExtendDirection == eDirPrevious);
   2069  MOZ_ASSERT(aAmount == eSelectCharacter || aAmount == eSelectCluster ||
   2070             aAmount == eSelectWord || aAmount == eSelectBeginLine ||
   2071             aAmount == eSelectEndLine);
   2072  MOZ_ASSERT(aMovementStyle == eLogical || aMovementStyle == eVisual ||
   2073             aMovementStyle == eUsePrefStyle);
   2074 
   2075  aPresShell.FlushPendingNotifications(FlushType::Layout);
   2076  if (aPresShell.IsDestroying()) {
   2077    return Err(NS_ERROR_FAILURE);
   2078  }
   2079  if (!aRange.IsPositioned()) {
   2080    return Err(NS_ERROR_FAILURE);
   2081  }
   2082  const ForceEditableRegion forceEditableRegion = [&]() {
   2083    if (aRange.GetStartContainer()->IsEditable()) {
   2084      return ForceEditableRegion::Yes;
   2085    }
   2086    const auto* const element = Element::FromNode(aRange.GetStartContainer());
   2087    return element && element->State().HasState(ElementState::READWRITE)
   2088               ? ForceEditableRegion::Yes
   2089               : ForceEditableRegion::No;
   2090  }();
   2091  Result<PeekOffsetOptions, nsresult> options =
   2092      CreatePeekOffsetOptionsForCaretMove(
   2093          aLimitersAndCaretData.mIndependentSelectionRootElement,
   2094          forceEditableRegion, ExtendSelection::Yes, aMovementStyle);
   2095  if (MOZ_UNLIKELY(options.isErr())) {
   2096    return options.propagateErr();
   2097  }
   2098  Result<RawRangeBoundary, nsresult> result =
   2099      SelectionMovementUtils::MoveRangeBoundaryToSomewhere(
   2100          aRangeDirection == eDirNext ? aRange.StartRef().AsRaw()
   2101                                      : aRange.EndRef().AsRaw(),
   2102          aExtendDirection, aLimitersAndCaretData.mCaretAssociationHint,
   2103          aLimitersAndCaretData.mCaretBidiLevel, aAmount, options.unwrap(),
   2104          aLimitersAndCaretData.mAncestorLimiter);
   2105  if (result.isErr()) {
   2106    return result.propagateErr();
   2107  }
   2108  RefPtr<RangeType> range;
   2109  RawRangeBoundary rangeBoundary = result.unwrap();
   2110  if (!rangeBoundary.IsSetAndValid()) {
   2111    return range;
   2112  }
   2113  if (aExtendDirection == eDirPrevious) {
   2114    range = RangeType::Create(rangeBoundary, aRange.EndRef(), IgnoreErrors());
   2115  } else {
   2116    range = RangeType::Create(aRange.StartRef(), rangeBoundary, IgnoreErrors());
   2117  }
   2118  return range;
   2119 }
   2120 
   2121 //////////END FRAMESELECTION
   2122 
   2123 LazyLogModule gBatchLog("SelectionBatch");
   2124 
   2125 void nsFrameSelection::StartBatchChanges(const char* aRequesterFuncName) {
   2126  MOZ_LOG(gBatchLog, LogLevel::Info,
   2127          ("%p%snsFrameSelection::StartBatchChanges(%s)", this,
   2128           std::string((mBatching.mCounter + 1) * 2, ' ').c_str(),
   2129           aRequesterFuncName));
   2130  mBatching.mCounter++;
   2131 }
   2132 
   2133 void nsFrameSelection::EndBatchChanges(const char* aRequesterFuncName,
   2134                                       int16_t aReasons) {
   2135  MOZ_LOG(gBatchLog, LogLevel::Info,
   2136          ("%p%snsFrameSelection::EndBatchChanges  (%s, %s)", this,
   2137           std::string(mBatching.mCounter * 2, ' ').c_str(), aRequesterFuncName,
   2138           SelectionChangeReasonsToCString(aReasons).get()));
   2139  MOZ_ASSERT(mBatching.mCounter > 0, "Bad mBatching.mCounter");
   2140  mBatching.mCounter--;
   2141 
   2142  if (mBatching.mCounter == 0) {
   2143    AddChangeReasons(aReasons);
   2144    mCaretMoveAmount = eSelectNoAmount;
   2145    // Be aware, the Selection instance may be destroyed after this call,
   2146    // hence make sure that this instance remains until the end of this call.
   2147    RefPtr frameSelection = this;
   2148    for (auto selectionType : kPresentSelectionTypes) {
   2149      // This returns NS_ERROR_FAILURE if being called for a selection that is
   2150      // not present. We don't care about that here, so we silently ignore it
   2151      // and continue.
   2152      (void)NotifySelectionListeners(selectionType, IsBatchingEnd::Yes);
   2153    }
   2154  }
   2155 }
   2156 
   2157 nsresult nsFrameSelection::NotifySelectionListeners(
   2158    SelectionType aSelectionType, IsBatchingEnd aEndBatching) {
   2159  if (RefPtr<Selection> selection = GetSelection(aSelectionType)) {
   2160    if (aEndBatching == IsBatchingEnd::Yes &&
   2161        !selection->ChangesDuringBatching()) {
   2162      return NS_OK;
   2163    }
   2164    selection->NotifySelectionListeners();
   2165    mCaretMoveAmount = eSelectNoAmount;
   2166    return NS_OK;
   2167  }
   2168  return NS_ERROR_FAILURE;
   2169 }
   2170 
   2171 // Start of Table Selection methods
   2172 
   2173 static bool IsCell(nsIContent* aContent) {
   2174  return aContent->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th);
   2175 }
   2176 
   2177 // static
   2178 nsITableCellLayout* nsFrameSelection::GetCellLayout(
   2179    const nsIContent* aCellContent) {
   2180  nsITableCellLayout* cellLayoutObject =
   2181      do_QueryFrame(aCellContent->GetPrimaryFrame());
   2182  return cellLayoutObject;
   2183 }
   2184 
   2185 nsresult nsFrameSelection::ClearNormalSelection() {
   2186  RefPtr<Selection> selection = &NormalSelection();
   2187  ErrorResult err;
   2188  selection->RemoveAllRanges(err);
   2189  return err.StealNSResult();
   2190 }
   2191 
   2192 static nsIContent* GetFirstSelectedContent(const nsRange* aRange) {
   2193  if (!aRange) {
   2194    return nullptr;
   2195  }
   2196 
   2197  MOZ_ASSERT(aRange->GetStartContainer(), "Must have start parent!");
   2198  MOZ_ASSERT(aRange->GetStartContainer()->IsElement(), "Unexpected parent");
   2199 
   2200  return aRange->GetChildAtStartOffset();
   2201 }
   2202 
   2203 // Table selection support.
   2204 // TODO: Separate table methods into a separate nsITableSelection interface
   2205 nsresult nsFrameSelection::HandleTableSelection(nsINode* aParentContent,
   2206                                                int32_t aContentOffset,
   2207                                                TableSelectionMode aTarget,
   2208                                                WidgetMouseEvent* aMouseEvent) {
   2209  RefPtr<Selection> selection = &NormalSelection();
   2210  return mTableSelection.HandleSelection(aParentContent, aContentOffset,
   2211                                         aTarget, aMouseEvent, mDragState,
   2212                                         *selection);
   2213 }
   2214 
   2215 nsresult nsFrameSelection::TableSelection::HandleSelection(
   2216    nsINode* aParentContent, int32_t aContentOffset, TableSelectionMode aTarget,
   2217    WidgetMouseEvent* aMouseEvent, bool aDragState,
   2218    Selection& aNormalSelection) {
   2219  MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
   2220 
   2221  NS_ENSURE_TRUE(aParentContent, NS_ERROR_NULL_POINTER);
   2222  NS_ENSURE_TRUE(aMouseEvent, NS_ERROR_NULL_POINTER);
   2223 
   2224  if (aDragState && mDragSelectingCells &&
   2225      aTarget == TableSelectionMode::Table) {
   2226    // We were selecting cells and user drags mouse in table border or inbetween
   2227    // cells,
   2228    //  just do nothing
   2229    return NS_OK;
   2230  }
   2231 
   2232  RefPtr<nsIContent> childContent =
   2233      aParentContent->GetChildAt_Deprecated(aContentOffset);
   2234 
   2235  // When doing table selection, always set the direction to next so
   2236  // we can be sure that anchorNode's offset always points to the
   2237  // selected cell
   2238  aNormalSelection.SetDirection(eDirNext);
   2239 
   2240  // Stack-class to wrap all table selection changes in
   2241  //  BeginBatchChanges() / EndBatchChanges()
   2242  SelectionBatcher selectionBatcher(&aNormalSelection, __FUNCTION__);
   2243 
   2244  if (aDragState && mDragSelectingCells) {
   2245    return HandleDragSelecting(aTarget, childContent, aMouseEvent,
   2246                               aNormalSelection);
   2247  }
   2248 
   2249  return HandleMouseUpOrDown(aTarget, aDragState, childContent, aParentContent,
   2250                             aContentOffset, aMouseEvent, aNormalSelection);
   2251 }
   2252 
   2253 class nsFrameSelection::TableSelection::RowAndColumnRelation {
   2254 public:
   2255  static Result<RowAndColumnRelation, nsresult> Create(
   2256      const nsIContent* aFirst, const nsIContent* aSecond) {
   2257    RowAndColumnRelation result;
   2258 
   2259    nsresult errorResult =
   2260        GetCellIndexes(aFirst, result.mFirst.mRow, result.mFirst.mColumn);
   2261    if (NS_FAILED(errorResult)) {
   2262      return Err(errorResult);
   2263    }
   2264 
   2265    errorResult =
   2266        GetCellIndexes(aSecond, result.mSecond.mRow, result.mSecond.mColumn);
   2267    if (NS_FAILED(errorResult)) {
   2268      return Err(errorResult);
   2269    }
   2270 
   2271    return result;
   2272  }
   2273 
   2274  bool IsSameColumn() const { return mFirst.mColumn == mSecond.mColumn; }
   2275 
   2276  bool IsSameRow() const { return mFirst.mRow == mSecond.mRow; }
   2277 
   2278 private:
   2279  RowAndColumnRelation() = default;
   2280 
   2281  struct RowAndColumn {
   2282    int32_t mRow = 0;
   2283    int32_t mColumn = 0;
   2284  };
   2285 
   2286  RowAndColumn mFirst;
   2287  RowAndColumn mSecond;
   2288 };
   2289 
   2290 nsresult nsFrameSelection::TableSelection::HandleDragSelecting(
   2291    TableSelectionMode aTarget, nsIContent* aChildContent,
   2292    const WidgetMouseEvent* aMouseEvent, Selection& aNormalSelection) {
   2293  // We are drag-selecting
   2294  if (aTarget != TableSelectionMode::Table) {
   2295    // If dragging in the same cell as last event, do nothing
   2296    if (mEndSelectedCell == aChildContent) {
   2297      return NS_OK;
   2298    }
   2299 
   2300 #ifdef DEBUG_TABLE_SELECTION
   2301    printf(
   2302        " mStartSelectedCell = %p, "
   2303        "mEndSelectedCell = %p, aChildContent = %p "
   2304        "\n",
   2305        mStartSelectedCell.get(), mEndSelectedCell.get(), aChildContent);
   2306 #endif
   2307    // aTarget can be any "cell mode",
   2308    //  so we can easily drag-select rows and columns
   2309    // Once we are in row or column mode,
   2310    //  we can drift into any cell to stay in that mode
   2311    //  even if aTarget = TableSelectionMode::Cell
   2312 
   2313    if (mMode == TableSelectionMode::Row ||
   2314        mMode == TableSelectionMode::Column) {
   2315      if (mEndSelectedCell) {
   2316        Result<RowAndColumnRelation, nsresult> rowAndColumnRelation =
   2317            RowAndColumnRelation::Create(mEndSelectedCell, aChildContent);
   2318 
   2319        if (rowAndColumnRelation.isErr()) {
   2320          return rowAndColumnRelation.unwrapErr();
   2321        }
   2322 
   2323        if ((mMode == TableSelectionMode::Row &&
   2324             rowAndColumnRelation.inspect().IsSameRow()) ||
   2325            (mMode == TableSelectionMode::Column &&
   2326             rowAndColumnRelation.inspect().IsSameColumn())) {
   2327          return NS_OK;
   2328        }
   2329      }
   2330 #ifdef DEBUG_TABLE_SELECTION
   2331      printf(" Dragged into a new column or row\n");
   2332 #endif
   2333      // Continue dragging row or column selection
   2334 
   2335      return SelectRowOrColumn(aChildContent, aNormalSelection);
   2336    }
   2337    if (mMode == TableSelectionMode::Cell) {
   2338 #ifdef DEBUG_TABLE_SELECTION
   2339      printf("HandleTableSelection: Dragged into a new cell\n");
   2340 #endif
   2341      // Trick for quick selection of rows and columns
   2342      // Hold down shift, then start selecting in one direction
   2343      // If next cell dragged into is in same row, select entire row,
   2344      //   if next cell is in same column, select entire column
   2345      if (mStartSelectedCell && aMouseEvent->IsShift()) {
   2346        Result<RowAndColumnRelation, nsresult> rowAndColumnRelation =
   2347            RowAndColumnRelation::Create(mStartSelectedCell, aChildContent);
   2348        if (rowAndColumnRelation.isErr()) {
   2349          return rowAndColumnRelation.unwrapErr();
   2350        }
   2351 
   2352        if (rowAndColumnRelation.inspect().IsSameRow() ||
   2353            rowAndColumnRelation.inspect().IsSameColumn()) {
   2354          // Force new selection block
   2355          mStartSelectedCell = nullptr;
   2356          aNormalSelection.RemoveAllRanges(IgnoreErrors());
   2357 
   2358          if (rowAndColumnRelation.inspect().IsSameRow()) {
   2359            mMode = TableSelectionMode::Row;
   2360          } else {
   2361            mMode = TableSelectionMode::Column;
   2362          }
   2363 
   2364          return SelectRowOrColumn(aChildContent, aNormalSelection);
   2365        }
   2366      }
   2367 
   2368      // Reselect block of cells to new end location
   2369      const nsCOMPtr<nsIContent> startSelectedCell = mStartSelectedCell;
   2370      return SelectBlockOfCells(startSelectedCell, aChildContent,
   2371                                aNormalSelection);
   2372    }
   2373  }
   2374  // Do nothing if dragging in table, but outside a cell
   2375  return NS_OK;
   2376 }
   2377 
   2378 nsresult nsFrameSelection::TableSelection::HandleMouseUpOrDown(
   2379    TableSelectionMode aTarget, bool aDragState, nsIContent* aChildContent,
   2380    nsINode* aParentContent, int32_t aContentOffset,
   2381    const WidgetMouseEvent* aMouseEvent, Selection& aNormalSelection) {
   2382  nsresult result = NS_OK;
   2383  // Not dragging  -- mouse event is down or up
   2384  if (aDragState) {
   2385 #ifdef DEBUG_TABLE_SELECTION
   2386    printf("HandleTableSelection: Mouse down event\n");
   2387 #endif
   2388    // Clear cell we stored in mouse-down
   2389    mUnselectCellOnMouseUp = nullptr;
   2390 
   2391    if (aTarget == TableSelectionMode::Cell) {
   2392      bool isSelected = false;
   2393 
   2394      // Check if we have other selected cells
   2395      nsIContent* previousCellNode =
   2396          GetFirstSelectedContent(GetFirstCellRange(aNormalSelection));
   2397      if (previousCellNode) {
   2398        // We have at least 1 other selected cell
   2399 
   2400        // Check if new cell is already selected
   2401        nsIFrame* cellFrame = aChildContent->GetPrimaryFrame();
   2402        if (!cellFrame) {
   2403          return NS_ERROR_NULL_POINTER;
   2404        }
   2405        isSelected = cellFrame->IsSelected();
   2406      } else {
   2407        // No cells selected -- remove non-cell selection
   2408        aNormalSelection.RemoveAllRanges(IgnoreErrors());
   2409      }
   2410      mDragSelectingCells = true;  // Signal to start drag-cell-selection
   2411      mMode = aTarget;
   2412      // Set start for new drag-selection block (not appended)
   2413      mStartSelectedCell = aChildContent;
   2414      // The initial block end is same as the start
   2415      mEndSelectedCell = aChildContent;
   2416 
   2417      if (isSelected) {
   2418        // Remember this cell to (possibly) unselect it on mouseup
   2419        mUnselectCellOnMouseUp = aChildContent;
   2420 #ifdef DEBUG_TABLE_SELECTION
   2421        printf(
   2422            "HandleTableSelection: Saving "
   2423            "mUnselectCellOnMouseUp\n");
   2424 #endif
   2425      } else {
   2426        // Select an unselected cell
   2427        // but first remove existing selection if not in same table
   2428        if (previousCellNode &&
   2429            !IsInSameTable(previousCellNode, aChildContent)) {
   2430          aNormalSelection.RemoveAllRanges(IgnoreErrors());
   2431          // Reset selection mode that is cleared in RemoveAllRanges
   2432          mMode = aTarget;
   2433        }
   2434 
   2435        return ::SelectCellElement(aChildContent, aNormalSelection);
   2436      }
   2437 
   2438      return NS_OK;
   2439    }
   2440    if (aTarget == TableSelectionMode::Table) {
   2441      // TODO: We currently select entire table when clicked between cells,
   2442      //  should we restrict to only around border?
   2443      //  *** How do we get location data for cell and click?
   2444      mDragSelectingCells = false;
   2445      mStartSelectedCell = nullptr;
   2446      mEndSelectedCell = nullptr;
   2447 
   2448      // Remove existing selection and select the table
   2449      aNormalSelection.RemoveAllRanges(IgnoreErrors());
   2450      return CreateAndAddRange(aParentContent, aContentOffset,
   2451                               aNormalSelection);
   2452    }
   2453    if (aTarget == TableSelectionMode::Row ||
   2454        aTarget == TableSelectionMode::Column) {
   2455 #ifdef DEBUG_TABLE_SELECTION
   2456      printf("aTarget == %d\n", aTarget);
   2457 #endif
   2458 
   2459      // Start drag-selecting mode so multiple rows/cols can be selected
   2460      // Note: Currently, nsIFrame::GetDataForTableSelection
   2461      //       will never call us for row or column selection on mouse down
   2462      mDragSelectingCells = true;
   2463 
   2464      // Force new selection block
   2465      mStartSelectedCell = nullptr;
   2466      aNormalSelection.RemoveAllRanges(IgnoreErrors());
   2467      // Always do this AFTER RemoveAllRanges
   2468      mMode = aTarget;
   2469 
   2470      return SelectRowOrColumn(aChildContent, aNormalSelection);
   2471    }
   2472  } else {
   2473 #ifdef DEBUG_TABLE_SELECTION
   2474    printf(
   2475        "HandleTableSelection: Mouse UP event. "
   2476        "mDragSelectingCells=%d, "
   2477        "mStartSelectedCell=%p\n",
   2478        mDragSelectingCells, mStartSelectedCell.get());
   2479 #endif
   2480    // First check if we are extending a block selection
   2481    const uint32_t rangeCount = aNormalSelection.RangeCount();
   2482 
   2483    if (rangeCount > 0 && aMouseEvent->IsShift() && mAppendStartSelectedCell &&
   2484        mAppendStartSelectedCell != aChildContent) {
   2485      // Shift key is down: append a block selection
   2486      mDragSelectingCells = false;
   2487 
   2488      const OwningNonNull<nsIContent> appendStartSelectedCell =
   2489          *mAppendStartSelectedCell;
   2490      return SelectBlockOfCells(appendStartSelectedCell, aChildContent,
   2491                                aNormalSelection);
   2492    }
   2493 
   2494    if (mDragSelectingCells) {
   2495      mAppendStartSelectedCell = mStartSelectedCell;
   2496    }
   2497 
   2498    mDragSelectingCells = false;
   2499    mStartSelectedCell = nullptr;
   2500    mEndSelectedCell = nullptr;
   2501 
   2502    // Any other mouseup actions require that Ctrl or Cmd key is pressed
   2503    //  else stop table selection mode
   2504    bool doMouseUpAction = false;
   2505 #ifdef XP_MACOSX
   2506    doMouseUpAction = aMouseEvent->IsMeta();
   2507 #else
   2508    doMouseUpAction = aMouseEvent->IsControl();
   2509 #endif
   2510    if (!doMouseUpAction) {
   2511 #ifdef DEBUG_TABLE_SELECTION
   2512      printf(
   2513          "HandleTableSelection: Ending cell selection on mouseup: "
   2514          "mAppendStartSelectedCell=%p\n",
   2515          mAppendStartSelectedCell.get());
   2516 #endif
   2517      return NS_OK;
   2518    }
   2519    // Unselect a cell only if it wasn't
   2520    //  just selected on mousedown
   2521    if (aChildContent == mUnselectCellOnMouseUp) {
   2522      // Scan ranges to find the cell to unselect (the selection range to
   2523      // remove)
   2524      // XXXbz it's really weird that this lives outside the loop, so once we
   2525      // find one we keep looking at it even if we find no more cells...
   2526      nsINode* previousCellParent = nullptr;
   2527 #ifdef DEBUG_TABLE_SELECTION
   2528      printf(
   2529          "HandleTableSelection: Unselecting "
   2530          "mUnselectCellOnMouseUp; "
   2531          "rangeCount=%d\n",
   2532          rangeCount);
   2533 #endif
   2534      for (const uint32_t i : IntegerRange(rangeCount)) {
   2535        MOZ_ASSERT(aNormalSelection.RangeCount() == rangeCount);
   2536        // Strong reference, because sometimes we want to remove
   2537        // this range, and then we might be the only owner.
   2538        RefPtr<nsRange> range = aNormalSelection.GetRangeAt(i);
   2539        if (MOZ_UNLIKELY(!range)) {
   2540          return NS_ERROR_NULL_POINTER;
   2541        }
   2542 
   2543        nsINode* container = range->GetStartContainer();
   2544        if (!container) {
   2545          return NS_ERROR_NULL_POINTER;
   2546        }
   2547 
   2548        int32_t offset = range->StartOffset();
   2549        // Be sure previous selection is a table cell
   2550        nsIContent* child = range->GetChildAtStartOffset();
   2551        if (child && IsCell(child)) {
   2552          previousCellParent = container;
   2553        }
   2554 
   2555        // We're done if we didn't find parent of a previously-selected cell
   2556        if (!previousCellParent) {
   2557          break;
   2558        }
   2559 
   2560        if (previousCellParent == aParentContent && offset == aContentOffset) {
   2561          // Cell is already selected
   2562          if (rangeCount == 1) {
   2563 #ifdef DEBUG_TABLE_SELECTION
   2564            printf("HandleTableSelection: Unselecting single selected cell\n");
   2565 #endif
   2566            // This was the only cell selected.
   2567            // Collapse to "normal" selection inside the cell
   2568            mStartSelectedCell = nullptr;
   2569            mEndSelectedCell = nullptr;
   2570            mAppendStartSelectedCell = nullptr;
   2571            // TODO: We need a "Collapse to just before deepest child" routine
   2572            // Even better, should we collapse to just after the LAST deepest
   2573            // child
   2574            //  (i.e., at the end of the cell's contents)?
   2575            return aNormalSelection.CollapseInLimiter(aChildContent, 0);
   2576          }
   2577 #ifdef DEBUG_TABLE_SELECTION
   2578          printf(
   2579              "HandleTableSelection: Removing cell from multi-cell "
   2580              "selection\n");
   2581 #endif
   2582          // Unselecting the start of previous block
   2583          // XXX What do we use now!
   2584          if (aChildContent == mAppendStartSelectedCell) {
   2585            mAppendStartSelectedCell = nullptr;
   2586          }
   2587 
   2588          // Deselect cell by removing its range from selection
   2589          ErrorResult err;
   2590          aNormalSelection.RemoveRangeAndUnselectFramesAndNotifyListeners(
   2591              *range, err);
   2592          return err.StealNSResult();
   2593        }
   2594      }
   2595      mUnselectCellOnMouseUp = nullptr;
   2596    }
   2597  }
   2598  return result;
   2599 }
   2600 
   2601 nsresult nsFrameSelection::TableSelection::SelectBlockOfCells(
   2602    nsIContent* aStartCell, nsIContent* aEndCell, Selection& aNormalSelection) {
   2603  NS_ENSURE_TRUE(aStartCell, NS_ERROR_NULL_POINTER);
   2604  NS_ENSURE_TRUE(aEndCell, NS_ERROR_NULL_POINTER);
   2605  mEndSelectedCell = aEndCell;
   2606 
   2607  nsresult result = NS_OK;
   2608 
   2609  // If new end cell is in a different table, do nothing
   2610  const RefPtr<const nsIContent> table = IsInSameTable(aStartCell, aEndCell);
   2611  if (!table) {
   2612    return NS_OK;
   2613  }
   2614 
   2615  // Get starting and ending cells' location in the cellmap
   2616  int32_t startRowIndex, startColIndex, endRowIndex, endColIndex;
   2617  result = GetCellIndexes(aStartCell, startRowIndex, startColIndex);
   2618  if (NS_FAILED(result)) {
   2619    return result;
   2620  }
   2621  result = GetCellIndexes(aEndCell, endRowIndex, endColIndex);
   2622  if (NS_FAILED(result)) {
   2623    return result;
   2624  }
   2625 
   2626  if (mDragSelectingCells) {
   2627    // Drag selecting: remove selected cells outside of new block limits
   2628    // TODO: `UnselectCells`'s return value shouldn't be ignored.
   2629    UnselectCells(table, startRowIndex, startColIndex, endRowIndex, endColIndex,
   2630                  true, aNormalSelection);
   2631  }
   2632 
   2633  // Note that we select block in the direction of user's mouse dragging,
   2634  //  which means start cell may be after the end cell in either row or column
   2635  return AddCellsToSelection(table, startRowIndex, startColIndex, endRowIndex,
   2636                             endColIndex, aNormalSelection);
   2637 }
   2638 
   2639 nsresult nsFrameSelection::TableSelection::UnselectCells(
   2640    const nsIContent* aTableContent, int32_t aStartRowIndex,
   2641    int32_t aStartColumnIndex, int32_t aEndRowIndex, int32_t aEndColumnIndex,
   2642    bool aRemoveOutsideOfCellRange, mozilla::dom::Selection& aNormalSelection) {
   2643  MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
   2644 
   2645  nsTableWrapperFrame* tableFrame =
   2646      do_QueryFrame(aTableContent->GetPrimaryFrame());
   2647  if (!tableFrame) {
   2648    return NS_ERROR_FAILURE;
   2649  }
   2650 
   2651  int32_t minRowIndex = std::min(aStartRowIndex, aEndRowIndex);
   2652  int32_t maxRowIndex = std::max(aStartRowIndex, aEndRowIndex);
   2653  int32_t minColIndex = std::min(aStartColumnIndex, aEndColumnIndex);
   2654  int32_t maxColIndex = std::max(aStartColumnIndex, aEndColumnIndex);
   2655 
   2656  // Strong reference because we sometimes remove the range
   2657  RefPtr<nsRange> range = GetFirstCellRange(aNormalSelection);
   2658  nsIContent* cellNode = GetFirstSelectedContent(range);
   2659  MOZ_ASSERT(!range || cellNode, "Must have cellNode if had a range");
   2660 
   2661  int32_t curRowIndex, curColIndex;
   2662  while (cellNode) {
   2663    nsresult result = GetCellIndexes(cellNode, curRowIndex, curColIndex);
   2664    if (NS_FAILED(result)) {
   2665      return result;
   2666    }
   2667 
   2668 #ifdef DEBUG_TABLE_SELECTION
   2669    if (!range) printf("RemoveCellsToSelection -- range is null\n");
   2670 #endif
   2671 
   2672    if (range) {
   2673      if (aRemoveOutsideOfCellRange) {
   2674        if (curRowIndex < minRowIndex || curRowIndex > maxRowIndex ||
   2675            curColIndex < minColIndex || curColIndex > maxColIndex) {
   2676          aNormalSelection.RemoveRangeAndUnselectFramesAndNotifyListeners(
   2677              *range, IgnoreErrors());
   2678          // Since we've removed the range, decrement pointer to next range
   2679          mSelectedCellIndex--;
   2680        }
   2681 
   2682      } else {
   2683        // Remove cell from selection if it belongs to the given cells range or
   2684        // it is spanned onto the cells range.
   2685        nsTableCellFrame* cellFrame =
   2686            tableFrame->GetCellFrameAt(curRowIndex, curColIndex);
   2687 
   2688        uint32_t origRowIndex = cellFrame->RowIndex();
   2689        uint32_t origColIndex = cellFrame->ColIndex();
   2690        uint32_t actualRowSpan =
   2691            tableFrame->GetEffectiveRowSpanAt(origRowIndex, origColIndex);
   2692        uint32_t actualColSpan =
   2693            tableFrame->GetEffectiveColSpanAt(curRowIndex, curColIndex);
   2694        if (origRowIndex <= static_cast<uint32_t>(maxRowIndex) &&
   2695            maxRowIndex >= 0 &&
   2696            origRowIndex + actualRowSpan - 1 >=
   2697                static_cast<uint32_t>(minRowIndex) &&
   2698            origColIndex <= static_cast<uint32_t>(maxColIndex) &&
   2699            maxColIndex >= 0 &&
   2700            origColIndex + actualColSpan - 1 >=
   2701                static_cast<uint32_t>(minColIndex)) {
   2702          aNormalSelection.RemoveRangeAndUnselectFramesAndNotifyListeners(
   2703              *range, IgnoreErrors());
   2704          // Since we've removed the range, decrement pointer to next range
   2705          mSelectedCellIndex--;
   2706        }
   2707      }
   2708    }
   2709 
   2710    range = GetNextCellRange(aNormalSelection);
   2711    cellNode = GetFirstSelectedContent(range);
   2712    MOZ_ASSERT(!range || cellNode, "Must have cellNode if had a range");
   2713  }
   2714 
   2715  return NS_OK;
   2716 }
   2717 
   2718 nsresult SelectCellElement(nsIContent* aCellElement,
   2719                           Selection& aNormalSelection) {
   2720  MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
   2721 
   2722  nsIContent* parent = aCellElement->GetParent();
   2723 
   2724  // Get child offset
   2725  const int32_t offset = parent->ComputeIndexOf_Deprecated(aCellElement);
   2726 
   2727  return CreateAndAddRange(parent, offset, aNormalSelection);
   2728 }
   2729 
   2730 static nsresult AddCellsToSelection(const nsIContent* aTableContent,
   2731                                    int32_t aStartRowIndex,
   2732                                    int32_t aStartColumnIndex,
   2733                                    int32_t aEndRowIndex,
   2734                                    int32_t aEndColumnIndex,
   2735                                    Selection& aNormalSelection) {
   2736  MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
   2737 
   2738  nsTableWrapperFrame* tableFrame =
   2739      do_QueryFrame(aTableContent->GetPrimaryFrame());
   2740  if (!tableFrame) {  // Check that |table| is a table.
   2741    return NS_ERROR_FAILURE;
   2742  }
   2743 
   2744  nsresult result = NS_OK;
   2745  uint32_t row = aStartRowIndex;
   2746  while (true) {
   2747    uint32_t col = aStartColumnIndex;
   2748    while (true) {
   2749      nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(row, col);
   2750 
   2751      // Skip cells that are spanned from previous locations or are already
   2752      // selected
   2753      if (cellFrame) {
   2754        uint32_t origRow = cellFrame->RowIndex();
   2755        uint32_t origCol = cellFrame->ColIndex();
   2756        if (origRow == row && origCol == col && !cellFrame->IsSelected()) {
   2757          result = SelectCellElement(cellFrame->GetContent(), aNormalSelection);
   2758          if (NS_FAILED(result)) {
   2759            return result;
   2760          }
   2761        }
   2762      }
   2763      // Done when we reach end column
   2764      if (col == static_cast<uint32_t>(aEndColumnIndex)) {
   2765        break;
   2766      }
   2767 
   2768      if (aStartColumnIndex < aEndColumnIndex) {
   2769        col++;
   2770      } else {
   2771        col--;
   2772      }
   2773    }
   2774    if (row == static_cast<uint32_t>(aEndRowIndex)) {
   2775      break;
   2776    }
   2777 
   2778    if (aStartRowIndex < aEndRowIndex) {
   2779      row++;
   2780    } else {
   2781      row--;
   2782    }
   2783  }
   2784  return result;
   2785 }
   2786 
   2787 nsresult nsFrameSelection::RemoveCellsFromSelection(nsIContent* aTable,
   2788                                                    int32_t aStartRowIndex,
   2789                                                    int32_t aStartColumnIndex,
   2790                                                    int32_t aEndRowIndex,
   2791                                                    int32_t aEndColumnIndex) {
   2792  const RefPtr<Selection> selection = &NormalSelection();
   2793  return mTableSelection.UnselectCells(aTable, aStartRowIndex,
   2794                                       aStartColumnIndex, aEndRowIndex,
   2795                                       aEndColumnIndex, false, *selection);
   2796 }
   2797 
   2798 nsresult nsFrameSelection::RestrictCellsToSelection(nsIContent* aTable,
   2799                                                    int32_t aStartRowIndex,
   2800                                                    int32_t aStartColumnIndex,
   2801                                                    int32_t aEndRowIndex,
   2802                                                    int32_t aEndColumnIndex) {
   2803  const RefPtr<Selection> selection = &NormalSelection();
   2804  return mTableSelection.UnselectCells(aTable, aStartRowIndex,
   2805                                       aStartColumnIndex, aEndRowIndex,
   2806                                       aEndColumnIndex, true, *selection);
   2807 }
   2808 
   2809 Result<nsFrameSelection::TableSelection::FirstAndLastCell, nsresult>
   2810 nsFrameSelection::TableSelection::FindFirstAndLastCellOfRowOrColumn(
   2811    const nsIContent& aCellContent) const {
   2812  const nsIContent* table = GetParentTable(&aCellContent);
   2813  if (!table) {
   2814    return Err(NS_ERROR_NULL_POINTER);
   2815  }
   2816 
   2817  // Get table and cell layout interfaces to access
   2818  // cell data based on cellmap location
   2819  // Frames are not ref counted, so don't use an nsCOMPtr
   2820  nsTableWrapperFrame* tableFrame = do_QueryFrame(table->GetPrimaryFrame());
   2821  if (!tableFrame) {
   2822    return Err(NS_ERROR_FAILURE);
   2823  }
   2824  nsITableCellLayout* cellLayout = GetCellLayout(&aCellContent);
   2825  if (!cellLayout) {
   2826    return Err(NS_ERROR_FAILURE);
   2827  }
   2828 
   2829  // Get location of target cell:
   2830  int32_t rowIndex, colIndex;
   2831  nsresult result = cellLayout->GetCellIndexes(rowIndex, colIndex);
   2832  if (NS_FAILED(result)) {
   2833    return Err(result);
   2834  }
   2835 
   2836  // Be sure we start at proper beginning
   2837  // (This allows us to select row or col given ANY cell!)
   2838  if (mMode == TableSelectionMode::Row) {
   2839    colIndex = 0;
   2840  }
   2841  if (mMode == TableSelectionMode::Column) {
   2842    rowIndex = 0;
   2843  }
   2844 
   2845  FirstAndLastCell firstAndLastCell;
   2846  while (true) {
   2847    // Loop through all cells in column or row to find first and last
   2848    nsCOMPtr<nsIContent> curCellContent =
   2849        tableFrame->GetCellAt(rowIndex, colIndex);
   2850    if (!curCellContent) {
   2851      break;
   2852    }
   2853 
   2854    if (!firstAndLastCell.mFirst) {
   2855      firstAndLastCell.mFirst = curCellContent;
   2856    }
   2857 
   2858    firstAndLastCell.mLast = std::move(curCellContent);
   2859 
   2860    // Move to next cell in cellmap, skipping spanned locations
   2861    if (mMode == TableSelectionMode::Row) {
   2862      colIndex += tableFrame->GetEffectiveRowSpanAt(rowIndex, colIndex);
   2863    } else {
   2864      rowIndex += tableFrame->GetEffectiveRowSpanAt(rowIndex, colIndex);
   2865    }
   2866  }
   2867  return firstAndLastCell;
   2868 }
   2869 
   2870 nsresult nsFrameSelection::TableSelection::SelectRowOrColumn(
   2871    nsIContent* aCellContent, Selection& aNormalSelection) {
   2872  MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
   2873 
   2874  if (!aCellContent) {
   2875    return NS_ERROR_NULL_POINTER;
   2876  }
   2877 
   2878  Result<FirstAndLastCell, nsresult> firstAndLastCell =
   2879      FindFirstAndLastCellOfRowOrColumn(*aCellContent);
   2880  if (firstAndLastCell.isErr()) {
   2881    return firstAndLastCell.unwrapErr();
   2882  }
   2883 
   2884  // Use SelectBlockOfCells:
   2885  // This will replace existing selection,
   2886  //  but allow unselecting by dragging out of selected region
   2887  if (firstAndLastCell.inspect().mFirst && firstAndLastCell.inspect().mLast) {
   2888    nsresult rv{NS_OK};
   2889 
   2890    if (!mStartSelectedCell) {
   2891      // We are starting a new block, so select the first cell
   2892      rv = ::SelectCellElement(firstAndLastCell.inspect().mFirst,
   2893                               aNormalSelection);
   2894      if (NS_FAILED(rv)) {
   2895        return rv;
   2896      }
   2897      mStartSelectedCell = firstAndLastCell.inspect().mFirst;
   2898    }
   2899 
   2900    const nsCOMPtr<nsIContent> startSelectedCell = mStartSelectedCell;
   2901    rv = SelectBlockOfCells(startSelectedCell,
   2902                            MOZ_KnownLive(firstAndLastCell.inspect().mLast),
   2903                            aNormalSelection);
   2904 
   2905    // This gets set to the cell at end of row/col,
   2906    //   but we need it to be the cell under cursor
   2907    mEndSelectedCell = aCellContent;
   2908    return rv;
   2909  }
   2910 
   2911 #if 0
   2912 // This is a more efficient strategy that appends row to current selection,
   2913 //  but doesn't allow dragging OFF of an existing selection to unselect!
   2914  do {
   2915    // Loop through all cells in column or row
   2916    result = tableLayout->GetCellDataAt(rowIndex, colIndex,
   2917                                        getter_AddRefs(cellElement),
   2918                                        curRowIndex, curColIndex,
   2919                                        rowSpan, colSpan,
   2920                                        actualRowSpan, actualColSpan,
   2921                                        isSelected);
   2922    if (NS_FAILED(result)) return result;
   2923    // We're done when cell is not found
   2924    if (!cellElement) break;
   2925 
   2926 
   2927    // Check spans else we infinitely loop
   2928    NS_ASSERTION(actualColSpan, "actualColSpan is 0!");
   2929    NS_ASSERTION(actualRowSpan, "actualRowSpan is 0!");
   2930 
   2931    // Skip cells that are already selected or span from outside our region
   2932    if (!isSelected && rowIndex == curRowIndex && colIndex == curColIndex)
   2933    {
   2934      result = SelectCellElement(cellElement);
   2935      if (NS_FAILED(result)) return result;
   2936    }
   2937    // Move to next row or column in cellmap, skipping spanned locations
   2938    if (mMode == TableSelectionMode::Row)
   2939      colIndex += actualColSpan;
   2940    else
   2941      rowIndex += actualRowSpan;
   2942  }
   2943  while (cellElement);
   2944 #endif
   2945 
   2946  return NS_OK;
   2947 }
   2948 
   2949 // static
   2950 nsIContent* nsFrameSelection::GetFirstCellNodeInRange(const nsRange* aRange) {
   2951  if (!aRange) {
   2952    return nullptr;
   2953  }
   2954 
   2955  nsIContent* childContent = aRange->GetChildAtStartOffset();
   2956  if (!childContent) {
   2957    return nullptr;
   2958  }
   2959  // Don't return node if not a cell
   2960  if (!IsCell(childContent)) {
   2961    return nullptr;
   2962  }
   2963 
   2964  return childContent;
   2965 }
   2966 
   2967 nsRange* nsFrameSelection::TableSelection::GetFirstCellRange(
   2968    const mozilla::dom::Selection& aNormalSelection) {
   2969  MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
   2970 
   2971  nsRange* firstRange = aNormalSelection.GetRangeAt(0);
   2972  if (!GetFirstCellNodeInRange(firstRange)) {
   2973    return nullptr;
   2974  }
   2975 
   2976  // Setup for next cell
   2977  mSelectedCellIndex = 1;
   2978 
   2979  return firstRange;
   2980 }
   2981 
   2982 nsRange* nsFrameSelection::TableSelection::GetNextCellRange(
   2983    const mozilla::dom::Selection& aNormalSelection) {
   2984  MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
   2985 
   2986  nsRange* range =
   2987      aNormalSelection.GetRangeAt(AssertedCast<uint32_t>(mSelectedCellIndex));
   2988 
   2989  // Get first node in next range of selection - test if it's a cell
   2990  if (!GetFirstCellNodeInRange(range)) {
   2991    return nullptr;
   2992  }
   2993 
   2994  // Setup for next cell
   2995  mSelectedCellIndex++;
   2996 
   2997  return range;
   2998 }
   2999 
   3000 // static
   3001 nsresult nsFrameSelection::GetCellIndexes(const nsIContent* aCell,
   3002                                          int32_t& aRowIndex,
   3003                                          int32_t& aColIndex) {
   3004  if (!aCell) {
   3005    return NS_ERROR_NULL_POINTER;
   3006  }
   3007 
   3008  aColIndex = 0;  // initialize out params
   3009  aRowIndex = 0;
   3010 
   3011  nsITableCellLayout* cellLayoutObject = GetCellLayout(aCell);
   3012  if (!cellLayoutObject) {
   3013    return NS_ERROR_FAILURE;
   3014  }
   3015  return cellLayoutObject->GetCellIndexes(aRowIndex, aColIndex);
   3016 }
   3017 
   3018 // static
   3019 nsIContent* nsFrameSelection::IsInSameTable(const nsIContent* aContent1,
   3020                                            const nsIContent* aContent2) {
   3021  if (!aContent1 || !aContent2) {
   3022    return nullptr;
   3023  }
   3024 
   3025  nsIContent* tableNode1 = GetParentTable(aContent1);
   3026  nsIContent* tableNode2 = GetParentTable(aContent2);
   3027 
   3028  // Must be in the same table.  Note that we want to return false for
   3029  // the test if both tables are null.
   3030  return (tableNode1 == tableNode2) ? tableNode1 : nullptr;
   3031 }
   3032 
   3033 // static
   3034 nsIContent* nsFrameSelection::GetParentTable(const nsIContent* aCell) {
   3035  if (!aCell) {
   3036    return nullptr;
   3037  }
   3038 
   3039  for (nsIContent* parent = aCell->GetParent(); parent;
   3040       parent = parent->GetParent()) {
   3041    if (parent->IsHTMLElement(nsGkAtoms::table)) {
   3042      return parent;
   3043    }
   3044  }
   3045 
   3046  return nullptr;
   3047 }
   3048 
   3049 nsresult nsFrameSelection::SelectCellElement(nsIContent* aCellElement) {
   3050  const RefPtr<Selection> selection = &NormalSelection();
   3051  return ::SelectCellElement(aCellElement, *selection);
   3052 }
   3053 
   3054 nsresult CreateAndAddRange(nsINode* aContainer, int32_t aOffset,
   3055                           Selection& aNormalSelection) {
   3056  MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
   3057 
   3058  if (!aContainer) {
   3059    return NS_ERROR_NULL_POINTER;
   3060  }
   3061 
   3062  // Set range around child at given offset
   3063  ErrorResult error;
   3064  RefPtr<nsRange> range =
   3065      nsRange::Create(aContainer, aOffset, aContainer, aOffset + 1, error);
   3066  if (NS_WARN_IF(error.Failed())) {
   3067    return error.StealNSResult();
   3068  }
   3069  MOZ_ASSERT(range);
   3070 
   3071  ErrorResult err;
   3072  aNormalSelection.AddRangeAndSelectFramesAndNotifyListeners(*range, err);
   3073  return err.StealNSResult();
   3074 }
   3075 
   3076 // End of Table Selection
   3077 
   3078 void nsFrameSelection::SetAncestorLimiter(Element* aLimiter) {
   3079  if (mLimiters.mAncestorLimiter != aLimiter) {
   3080    mLimiters.mAncestorLimiter = aLimiter;
   3081    const Selection& sel = NormalSelection();
   3082    LogSelectionAPI(&sel, __FUNCTION__, "aLimiter", aLimiter);
   3083 
   3084    if (!NodeIsInLimiters(sel.GetFocusNode())) {
   3085      ClearNormalSelection();
   3086      if (mLimiters.mAncestorLimiter) {
   3087        SetChangeReasons(nsISelectionListener::NO_REASON);
   3088        nsCOMPtr<nsIContent> limiter(mLimiters.mAncestorLimiter);
   3089        const nsresult rv =
   3090            TakeFocus(*limiter, 0, 0, CaretAssociationHint::Before,
   3091                      FocusMode::kCollapseToNewPoint);
   3092        (void)NS_WARN_IF(NS_FAILED(rv));
   3093        // TODO: in case of failure, propagate it to the callers.
   3094      }
   3095    }
   3096  }
   3097 }
   3098 
   3099 void nsFrameSelection::SetDelayedCaretData(WidgetMouseEvent* aMouseEvent) {
   3100  if (aMouseEvent) {
   3101    mDelayedMouseEvent.mIsValid = true;
   3102    mDelayedMouseEvent.mIsShift = aMouseEvent->IsShift();
   3103    mDelayedMouseEvent.mClickCount = aMouseEvent->mClickCount;
   3104  } else {
   3105    mDelayedMouseEvent.mIsValid = false;
   3106  }
   3107 }
   3108 
   3109 void nsFrameSelection::DisconnectFromPresShell() {
   3110  if (mAccessibleCaretEnabled) {
   3111    Selection& sel = NormalSelection();
   3112    sel.StopNotifyingAccessibleCaretEventHub();
   3113  }
   3114 
   3115  StopAutoScrollTimer();
   3116  for (size_t i = 0; i < std::size(mDomSelections); i++) {
   3117    MOZ_ASSERT(mDomSelections[i]);
   3118    mDomSelections[i]->Clear(nullptr);
   3119  }
   3120 
   3121  if (auto* presshell = mPresShell) {
   3122    if (const nsFrameSelection* sel = presshell->GetLastSelectionForToString();
   3123        sel == this) {
   3124      presshell->UpdateLastSelectionForToString(nullptr);
   3125    }
   3126    mPresShell = nullptr;
   3127  }
   3128 }
   3129 
   3130 #ifdef XP_MACOSX
   3131 /**
   3132 * See Bug 1288453.
   3133 *
   3134 * Update the selection cache on repaint to handle when a pre-existing
   3135 * selection becomes active aka the current selection.
   3136 *
   3137 * 1. Change the current selection by click n dragging another selection.
   3138 *   - Make a selection on content page. Make a selection in a text editor.
   3139 *   - You can click n drag the content selection to make it active again.
   3140 * 2. Change the current selection when switching to a tab with a selection.
   3141 *   - Make selection in tab.
   3142 *   - Switching tabs will make its respective selection active.
   3143 *
   3144 * Therefore, we only update the selection cache on a repaint
   3145 * if the current selection being repainted is not an empty selection.
   3146 *
   3147 * If the current selection is empty. The current selection cache
   3148 * would be cleared by AutoCopyListener::OnSelectionChange().
   3149 */
   3150 static nsresult UpdateSelectionCacheOnRepaintSelection(Selection* aSel) {
   3151  PresShell* presShell = aSel->GetPresShell();
   3152  if (!presShell) {
   3153    return NS_OK;
   3154  }
   3155  nsCOMPtr<Document> aDoc = presShell->GetDocument();
   3156 
   3157  if (aDoc && aSel && !aSel->IsCollapsed()) {
   3158    return nsCopySupport::EncodeDocumentWithContextAndPutToClipboard(
   3159        aSel, aDoc, nsIClipboard::kSelectionCache, false);
   3160  }
   3161 
   3162  return NS_OK;
   3163 }
   3164 #endif  // XP_MACOSX
   3165 
   3166 // mozilla::AutoCopyListener
   3167 
   3168 /*
   3169 * What we do now:
   3170 * On every selection change, we copy to the clipboard anew, creating a
   3171 * HTML buffer, a transferable, an nsISupportsString and
   3172 * a huge mess every time.  This is basically what
   3173 * nsCopySupport::EncodeDocumentWithContextAndPutToClipboard() does to move the
   3174 * selection into the clipboard for Edit->Copy.
   3175 *
   3176 * What we should do, to make our end of the deal faster:
   3177 * Create a singleton transferable with our own magic converter.  When selection
   3178 * changes (use a quick cache to detect ``real'' changes), we put the new
   3179 * Selection in the transferable.  Our magic converter will take care of
   3180 * transferable->whatever-other-format when the time comes to actually
   3181 * hand over the clipboard contents.
   3182 *
   3183 * Other issues:
   3184 * - which X clipboard should we populate?
   3185 * - should we use a different one than Edit->Copy, so that inadvertant
   3186 *   selections (or simple clicks, which currently cause a selection
   3187 *   notification, regardless of if they're in the document which currently has
   3188 *   selection!) don't lose the contents of the ``application''?  Or should we
   3189 *   just put some intelligence in the ``is this a real selection?'' code to
   3190 *   protect our selection against clicks in other documents that don't create
   3191 *   selections?
   3192 * - maybe we should just never clear the X clipboard?  That would make this
   3193 *   problem just go away, which is very tempting.
   3194 *
   3195 * On macOS,
   3196 * nsIClipboard::kSelectionCache is the flag for current selection cache.
   3197 * Set the current selection cache on the parent process in
   3198 * widget cocoa nsClipboard whenever selection changes.
   3199 */
   3200 
   3201 // static
   3202 void AutoCopyListener::OnSelectionChange(Document* aDocument,
   3203                                         Selection& aSelection,
   3204                                         int16_t aReason) {
   3205  MOZ_ASSERT(IsEnabled());
   3206 
   3207  // For now, we should prevent any updates caused by a call of Selection API.
   3208  // We should allow this in some cases later, though. See the valid usage in
   3209  // bug 1567160.
   3210  if (aReason & nsISelectionListener::JS_REASON) {
   3211    return;
   3212  }
   3213 
   3214  if (sClipboardID == nsIClipboard::kSelectionCache) {
   3215    // Do nothing if this isn't in the active window and,
   3216    // in the case of Web content, in the frontmost tab.
   3217    if (!aDocument || !IsInActiveTab(aDocument)) {
   3218      return;
   3219    }
   3220  }
   3221 
   3222  static const int16_t kResasonsToHandle =
   3223      nsISelectionListener::MOUSEUP_REASON |
   3224      nsISelectionListener::SELECTALL_REASON |
   3225      nsISelectionListener::KEYPRESS_REASON;
   3226  if (!(aReason & kResasonsToHandle)) {
   3227    return;  // Don't care if we are still dragging.
   3228  }
   3229 
   3230  if (!aDocument ||
   3231      aSelection.AreNormalAndCrossShadowBoundaryRangesCollapsed()) {
   3232 #ifdef DEBUG_CLIPBOARD
   3233    fprintf(stderr, "CLIPBOARD: no selection/collapsed selection\n");
   3234 #endif
   3235    if (sClipboardID != nsIClipboard::kSelectionCache) {
   3236      // XXX Should we clear X clipboard?
   3237      return;
   3238    }
   3239 
   3240    // If on macOS, clear the current selection transferable cached
   3241    // on the parent process (nsClipboard) when the selection is empty.
   3242    DebugOnly<nsresult> rv = nsCopySupport::ClearSelectionCache();
   3243    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3244                         "nsCopySupport::ClearSelectionCache() failed");
   3245    return;
   3246  }
   3247 
   3248  DebugOnly<nsresult> rv =
   3249      nsCopySupport::EncodeDocumentWithContextAndPutToClipboard(
   3250          &aSelection, aDocument, sClipboardID, false);
   3251  NS_WARNING_ASSERTION(
   3252      NS_SUCCEEDED(rv),
   3253      "nsCopySupport::EncodeDocumentWithContextAndPutToClipboard() failed");
   3254 }