tor-browser

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

AccessibleCaretManager.cpp (56666B)


      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 #include "AccessibleCaretManager.h"
      8 
      9 #include <utility>
     10 
     11 #include "AccessibleCaret.h"
     12 #include "AccessibleCaretEventHub.h"
     13 #include "AccessibleCaretLogger.h"
     14 #include "mozilla/AsyncEventDispatcher.h"
     15 #include "mozilla/AutoRestore.h"
     16 #include "mozilla/CaretAssociationHint.h"
     17 #include "mozilla/ContentIterator.h"
     18 #include "mozilla/FocusModel.h"
     19 #include "mozilla/IMEStateManager.h"
     20 #include "mozilla/IntegerPrintfMacros.h"
     21 #include "mozilla/PresShell.h"
     22 #include "mozilla/ScrollContainerFrame.h"
     23 #include "mozilla/SelectionMovementUtils.h"
     24 #include "mozilla/StaticAnalysisFunctions.h"
     25 #include "mozilla/StaticPrefs_layout.h"
     26 #include "mozilla/dom/Element.h"
     27 #include "mozilla/dom/MouseEventBinding.h"
     28 #include "mozilla/dom/NodeFilterBinding.h"
     29 #include "mozilla/dom/Selection.h"
     30 #include "mozilla/dom/TreeWalker.h"
     31 #include "nsCaret.h"
     32 #include "nsContainerFrame.h"
     33 #include "nsContentUtils.h"
     34 #include "nsDebug.h"
     35 #include "nsFocusManager.h"
     36 #include "nsFrameSelection.h"
     37 #include "nsGenericHTMLElement.h"
     38 #include "nsIFrame.h"
     39 #include "nsIHapticFeedback.h"
     40 #include "nsLayoutUtils.h"
     41 #include "nsServiceManagerUtils.h"
     42 
     43 namespace mozilla {
     44 
     45 #undef AC_LOG
     46 #define AC_LOG(message, ...) \
     47  AC_LOG_BASE("AccessibleCaretManager (%p): " message, this, ##__VA_ARGS__);
     48 
     49 #undef AC_LOGV
     50 #define AC_LOGV(message, ...) \
     51  AC_LOGV_BASE("AccessibleCaretManager (%p): " message, this, ##__VA_ARGS__);
     52 
     53 using namespace dom;
     54 using Appearance = AccessibleCaret::Appearance;
     55 using PositionChangedResult = AccessibleCaret::PositionChangedResult;
     56 
     57 #define AC_PROCESS_ENUM_TO_STREAM(e) \
     58  case (e):                          \
     59    aStream << #e;                   \
     60    break;
     61 std::ostream& operator<<(std::ostream& aStream,
     62                         const AccessibleCaretManager::CaretMode& aCaretMode) {
     63  using CaretMode = AccessibleCaretManager::CaretMode;
     64  switch (aCaretMode) {
     65    AC_PROCESS_ENUM_TO_STREAM(CaretMode::None);
     66    AC_PROCESS_ENUM_TO_STREAM(CaretMode::Cursor);
     67    AC_PROCESS_ENUM_TO_STREAM(CaretMode::Selection);
     68  }
     69  return aStream;
     70 }
     71 
     72 std::ostream& operator<<(
     73    std::ostream& aStream,
     74    const AccessibleCaretManager::UpdateCaretsHint& aHint) {
     75  using UpdateCaretsHint = AccessibleCaretManager::UpdateCaretsHint;
     76  switch (aHint) {
     77    AC_PROCESS_ENUM_TO_STREAM(UpdateCaretsHint::Default);
     78    AC_PROCESS_ENUM_TO_STREAM(UpdateCaretsHint::RespectOldAppearance);
     79    AC_PROCESS_ENUM_TO_STREAM(UpdateCaretsHint::DispatchNoEvent);
     80  }
     81  return aStream;
     82 }
     83 #undef AC_PROCESS_ENUM_TO_STREAM
     84 
     85 AccessibleCaretManager::AccessibleCaretManager(PresShell* aPresShell)
     86    : AccessibleCaretManager{
     87          aPresShell,
     88          Carets{aPresShell ? MakeUnique<AccessibleCaret>(aPresShell) : nullptr,
     89                 aPresShell ? MakeUnique<AccessibleCaret>(aPresShell)
     90                            : nullptr}} {}
     91 
     92 AccessibleCaretManager::AccessibleCaretManager(PresShell* aPresShell,
     93                                               Carets aCarets)
     94    : mPresShell{aPresShell}, mCarets{std::move(aCarets)} {}
     95 
     96 AccessibleCaretManager::LayoutFlusher::~LayoutFlusher() {
     97  MOZ_RELEASE_ASSERT(!mFlushing, "Going away in MaybeFlush? Bad!");
     98 }
     99 
    100 void AccessibleCaretManager::Terminate() {
    101  mCarets.Terminate();
    102  mActiveCaret = nullptr;
    103  mPresShell = nullptr;
    104 }
    105 
    106 nsresult AccessibleCaretManager::OnSelectionChanged(Document* aDoc,
    107                                                    Selection* aSel,
    108                                                    int16_t aReason) {
    109  Selection* selection = GetSelection();
    110  AC_LOG("%s: aSel: %p, GetSelection(): %p, aReason: %d", __FUNCTION__, aSel,
    111         selection, aReason);
    112  if (aSel != selection) {
    113    return NS_OK;
    114  }
    115 
    116  // eSetSelection events from the Fennec widget IME can be generated
    117  // by autoSuggest / autoCorrect composition changes, or by TYPE_REPLACE_TEXT
    118  // actions, either positioning cursor for text insert, or selecting
    119  // text-to-be-replaced. None should affect AccessibleCaret visibility.
    120  if (aReason & nsISelectionListener::IME_REASON) {
    121    return NS_OK;
    122  }
    123 
    124  // Move the cursor by JavaScript or unknown internal call.
    125  if (aReason == nsISelectionListener::NO_REASON ||
    126      aReason == nsISelectionListener::JS_REASON) {
    127    auto mode = static_cast<ScriptUpdateMode>(
    128        StaticPrefs::layout_accessiblecaret_script_change_update_mode());
    129    if (mode == kScriptAlwaysShow ||
    130        (mode == kScriptUpdateVisible && mCarets.HasLogicallyVisibleCaret())) {
    131      UpdateCarets();
    132      return NS_OK;
    133    }
    134    // Default for NO_REASON is to make hidden.
    135    HideCaretsAndDispatchCaretStateChangedEvent();
    136    return NS_OK;
    137  }
    138 
    139  // Move cursor by keyboard.
    140  if (aReason & nsISelectionListener::KEYPRESS_REASON) {
    141    HideCaretsAndDispatchCaretStateChangedEvent();
    142    return NS_OK;
    143  }
    144 
    145  // OnBlur() might be called between mouse down and mouse up, so we hide carets
    146  // upon mouse down anyway, and update carets upon mouse up.
    147  if (aReason & nsISelectionListener::MOUSEDOWN_REASON) {
    148    HideCaretsAndDispatchCaretStateChangedEvent();
    149    return NS_OK;
    150  }
    151 
    152  // Range will collapse after cutting or copying text.
    153  if (aReason & (nsISelectionListener::COLLAPSETOSTART_REASON |
    154                 nsISelectionListener::COLLAPSETOEND_REASON)) {
    155    HideCaretsAndDispatchCaretStateChangedEvent();
    156    return NS_OK;
    157  }
    158 
    159  // For mouse input we don't want to show the carets.
    160  if (StaticPrefs::layout_accessiblecaret_hide_carets_for_mouse_input() &&
    161      mLastInputSource == MouseEvent_Binding::MOZ_SOURCE_MOUSE) {
    162    HideCaretsAndDispatchCaretStateChangedEvent();
    163    return NS_OK;
    164  }
    165 
    166  // When we want to hide the carets for mouse input, hide them for select
    167  // all action fired by keyboard as well.
    168  if (StaticPrefs::layout_accessiblecaret_hide_carets_for_mouse_input() &&
    169      mLastInputSource == MouseEvent_Binding::MOZ_SOURCE_KEYBOARD &&
    170      (aReason & nsISelectionListener::SELECTALL_REASON)) {
    171    HideCaretsAndDispatchCaretStateChangedEvent();
    172    return NS_OK;
    173  }
    174 
    175  UpdateCarets();
    176  return NS_OK;
    177 }
    178 
    179 void AccessibleCaretManager::HideCaretsAndDispatchCaretStateChangedEvent() {
    180  if (mCarets.HasLogicallyVisibleCaret()) {
    181    AC_LOG("%s", __FUNCTION__);
    182    mCarets.GetFirst()->SetAppearance(Appearance::None);
    183    mCarets.GetSecond()->SetAppearance(Appearance::None);
    184    mIsCaretPositionChanged = false;
    185    mDesiredAsyncPanZoomState.Update(*this);
    186    DispatchCaretStateChangedEvent(CaretChangedReason::Visibilitychange);
    187  }
    188 }
    189 
    190 auto AccessibleCaretManager::MaybeFlushLayout() -> Terminated {
    191  if (mPresShell) {
    192    // `MaybeFlush` doesn't access the PresShell after flushing, so it's OK to
    193    // mark it as live.
    194    mLayoutFlusher.MaybeFlush(MOZ_KnownLive(*mPresShell));
    195  }
    196 
    197  return IsTerminated();
    198 }
    199 
    200 void AccessibleCaretManager::UpdateCarets(const UpdateCaretsHintSet& aHint) {
    201  if (MaybeFlushLayout() == Terminated::Yes) {
    202    return;
    203  }
    204 
    205  mLastUpdateCaretMode = GetCaretMode();
    206 
    207  switch (mLastUpdateCaretMode) {
    208    case CaretMode::None:
    209      HideCaretsAndDispatchCaretStateChangedEvent();
    210      break;
    211    case CaretMode::Cursor:
    212      UpdateCaretsForCursorMode(aHint);
    213      break;
    214    case CaretMode::Selection:
    215      UpdateCaretsForSelectionMode(aHint);
    216      break;
    217  }
    218 
    219  mDesiredAsyncPanZoomState.Update(*this);
    220 }
    221 
    222 bool AccessibleCaretManager::IsCaretDisplayableInCursorMode(
    223    nsIFrame** aOutFrame, int32_t* aOutOffset) const {
    224  RefPtr<nsCaret> caret = mPresShell->GetCaret();
    225  if (!caret || !caret->IsVisible()) {
    226    return false;
    227  }
    228  auto frameData =
    229      nsCaret::GetFrameAndOffset(nsCaret::CaretPositionFor(GetSelection()));
    230  if (!GetEditingHostForFrame(frameData.mFrame)) {
    231    return false;
    232  }
    233  if (aOutFrame) {
    234    *aOutFrame = frameData.mFrame;
    235  }
    236  if (aOutOffset) {
    237    *aOutOffset = frameData.mOffsetInFrameContent;
    238  }
    239  return true;
    240 }
    241 
    242 bool AccessibleCaretManager::HasNonEmptyTextContent(nsINode* aNode) const {
    243  return nsContentUtils::HasNonEmptyTextContent(
    244      aNode, nsContentUtils::eRecurseIntoChildren);
    245 }
    246 
    247 void AccessibleCaretManager::UpdateCaretsForCursorMode(
    248    const UpdateCaretsHintSet& aHints) {
    249  AC_LOG("%s, selection: %p", __FUNCTION__, GetSelection());
    250 
    251  int32_t offset = 0;
    252  nsIFrame* frame = nullptr;
    253  if (!IsCaretDisplayableInCursorMode(&frame, &offset)) {
    254    HideCaretsAndDispatchCaretStateChangedEvent();
    255    return;
    256  }
    257 
    258  PositionChangedResult result = mCarets.GetFirst()->SetPosition(frame, offset);
    259 
    260  switch (result) {
    261    case PositionChangedResult::NotChanged:
    262    case PositionChangedResult::Position:
    263    case PositionChangedResult::Zoom:
    264      if (!aHints.contains(UpdateCaretsHint::RespectOldAppearance)) {
    265        if (HasNonEmptyTextContent(GetEditingHostForFrame(frame))) {
    266          mCarets.GetFirst()->SetAppearance(Appearance::Normal);
    267        } else if (
    268            StaticPrefs::
    269                layout_accessiblecaret_caret_shown_when_long_tapping_on_empty_content()) {
    270          if (mCarets.GetFirst()->IsLogicallyVisible()) {
    271            // Possible cases are: 1) SelectWordOrShortcut() sets the
    272            // appearance to Normal. 2) When the caret is out of viewport and
    273            // now scrolling into viewport, it has appearance NormalNotShown.
    274            mCarets.GetFirst()->SetAppearance(Appearance::Normal);
    275          } else {
    276            // Possible cases are: a) Single tap on current empty content;
    277            // OnSelectionChanged() sets the appearance to None due to
    278            // MOUSEDOWN_REASON. b) Single tap on other empty content;
    279            // OnBlur() sets the appearance to None.
    280            //
    281            // Do nothing to make the appearance remains None so that it can
    282            // be distinguished from case 2). Also do not set the appearance
    283            // to NormalNotShown here like the default update behavior.
    284          }
    285        } else {
    286          mCarets.GetFirst()->SetAppearance(Appearance::NormalNotShown);
    287        }
    288      }
    289      break;
    290 
    291    case PositionChangedResult::Invisible:
    292      mCarets.GetFirst()->SetAppearance(Appearance::NormalNotShown);
    293      break;
    294  }
    295 
    296  mCarets.GetSecond()->SetAppearance(Appearance::None);
    297 
    298  mIsCaretPositionChanged = (result == PositionChangedResult::Position);
    299 
    300  if (!aHints.contains(UpdateCaretsHint::DispatchNoEvent) && !mActiveCaret) {
    301    DispatchCaretStateChangedEvent(CaretChangedReason::Updateposition);
    302  }
    303 }
    304 
    305 void AccessibleCaretManager::UpdateCaretsForSelectionMode(
    306    const UpdateCaretsHintSet& aHints) {
    307  AC_LOG("%s: selection: %p", __FUNCTION__, GetSelection());
    308 
    309  // NOTE: Here needs to call CompareTreePosition() which is overridden by
    310  // MockAccessibleCaretManager() to make it always return true.  Therefore,
    311  // we cannot return earlier when mPresShell is nullptr or the result of
    312  // GetFrameForRangeStart() is nullptr.
    313  const FrameAndOffset startFrameAndOffset =
    314      mPresShell ? GetFirstVisibleLeafFrameOrUnselectableChildFrame(
    315                       *GetSelection()->GetFirstRange())
    316                 : FrameAndOffset{};
    317  nsCOMPtr<nsIContent> endContent;
    318  const FrameAndOffset endFrameAndOffset =
    319      mPresShell
    320          ? GetLastVisibleLeafFrameOrUnselectableChildFrame(
    321                *GetSelection()->GetLastRange(), getter_AddRefs(endContent))
    322          : FrameAndOffset{};
    323 
    324  if (!CompareTreePosition(
    325          startFrameAndOffset.mFrame,
    326          static_cast<int32_t>(startFrameAndOffset.mOffsetInFrameContent),
    327          endFrameAndOffset.mFrame,
    328          static_cast<int32_t>(endFrameAndOffset.mOffsetInFrameContent))) {
    329    // XXX: Do we really have to hide carets if this condition isn't satisfied?
    330    HideCaretsAndDispatchCaretStateChangedEvent();
    331    return;
    332  }
    333 
    334  auto updateSingleCaret = [aHints](AccessibleCaret* aCaret, nsIFrame* aFrame,
    335                                    int32_t aOffset) -> PositionChangedResult {
    336    PositionChangedResult result = aCaret->SetPosition(aFrame, aOffset);
    337 
    338    switch (result) {
    339      case PositionChangedResult::NotChanged:
    340      case PositionChangedResult::Position:
    341      case PositionChangedResult::Zoom:
    342        if (!aHints.contains(UpdateCaretsHint::RespectOldAppearance)) {
    343          aCaret->SetAppearance(Appearance::Normal);
    344        }
    345        break;
    346 
    347      case PositionChangedResult::Invisible:
    348        aCaret->SetAppearance(Appearance::NormalNotShown);
    349        break;
    350    }
    351    return result;
    352  };
    353 
    354  PositionChangedResult firstCaretResult = updateSingleCaret(
    355      mCarets.GetFirst(), startFrameAndOffset.mFrame,
    356      static_cast<int32_t>(startFrameAndOffset.mOffsetInFrameContent));
    357  // If we get a frame for a child node for the end boundary, e.g., when the
    358  // last visible content is <img> or something or unselectable container,
    359  // we want to put the second caret to next to its end edge.  Then, we use
    360  // the specific behavior of nsIFrame::GetPointFromOffset() (called by
    361  // nsCaret::GetGeometryForFrame() in AccessibleCaret::SetPosition()) which
    362  // returns the end edge if we set the length of frame content + 1.
    363  const uint32_t offsetInEndFrameContent =
    364      endFrameAndOffset.GetFrameContent() == endContent
    365          ? endFrameAndOffset.mOffsetInFrameContent
    366          : endFrameAndOffset.GetFrameContent()->Length() + 1;
    367  PositionChangedResult secondCaretResult =
    368      updateSingleCaret(mCarets.GetSecond(), endFrameAndOffset.mFrame,
    369                        static_cast<int32_t>(offsetInEndFrameContent));
    370 
    371  mIsCaretPositionChanged =
    372      firstCaretResult == PositionChangedResult::Position ||
    373      secondCaretResult == PositionChangedResult::Position;
    374 
    375  if (mIsCaretPositionChanged) {
    376    // Flush layout to make the carets intersection correct.
    377    if (MaybeFlushLayout() == Terminated::Yes) {
    378      return;
    379    }
    380  }
    381 
    382  if (!aHints.contains(UpdateCaretsHint::RespectOldAppearance)) {
    383    // Only check for tilt carets when the caller doesn't ask us to preserve
    384    // old appearance. Otherwise we might override the appearance set by the
    385    // caller.
    386    if (StaticPrefs::layout_accessiblecaret_always_tilt()) {
    387      UpdateCaretsForAlwaysTilt(startFrameAndOffset.mFrame,
    388                                endFrameAndOffset.mFrame);
    389    } else {
    390      UpdateCaretsForOverlappingTilt();
    391    }
    392  }
    393 
    394  if (!aHints.contains(UpdateCaretsHint::DispatchNoEvent) && !mActiveCaret) {
    395    DispatchCaretStateChangedEvent(CaretChangedReason::Updateposition);
    396  }
    397 }
    398 
    399 void AccessibleCaretManager::DesiredAsyncPanZoomState::Update(
    400    const AccessibleCaretManager& aAccessibleCaretManager) {
    401  if (aAccessibleCaretManager.mActiveCaret) {
    402    // No need to disable APZ when dragging the caret.
    403    mValue = Value::Enabled;
    404    return;
    405  }
    406 
    407  if (aAccessibleCaretManager.mIsScrollStarted) {
    408    // During scrolling, the caret's position is changed only if it is in a
    409    // position:fixed or a "stuck" position:sticky frame subtree.
    410    mValue = aAccessibleCaretManager.mIsCaretPositionChanged ? Value::Disabled
    411                                                             : Value::Enabled;
    412    return;
    413  }
    414 
    415  // For other cases, we can only reliably detect whether the caret is in a
    416  // position:fixed frame subtree.
    417  switch (aAccessibleCaretManager.mLastUpdateCaretMode) {
    418    case CaretMode::None:
    419      mValue = Value::Enabled;
    420      break;
    421    case CaretMode::Cursor:
    422      mValue =
    423          (aAccessibleCaretManager.mCarets.GetFirst()->IsVisuallyVisible() &&
    424           aAccessibleCaretManager.mCarets.GetFirst()
    425               ->IsInPositionFixedSubtree())
    426              ? Value::Disabled
    427              : Value::Enabled;
    428      break;
    429    case CaretMode::Selection:
    430      mValue =
    431          ((aAccessibleCaretManager.mCarets.GetFirst()->IsVisuallyVisible() &&
    432            aAccessibleCaretManager.mCarets.GetFirst()
    433                ->IsInPositionFixedSubtree()) ||
    434           (aAccessibleCaretManager.mCarets.GetSecond()->IsVisuallyVisible() &&
    435            aAccessibleCaretManager.mCarets.GetSecond()
    436                ->IsInPositionFixedSubtree()))
    437              ? Value::Disabled
    438              : Value::Enabled;
    439      break;
    440  }
    441 }
    442 
    443 bool AccessibleCaretManager::UpdateCaretsForOverlappingTilt() {
    444  if (!mCarets.GetFirst()->IsVisuallyVisible() ||
    445      !mCarets.GetSecond()->IsVisuallyVisible()) {
    446    return false;
    447  }
    448 
    449  if (!mCarets.GetFirst()->Intersects(*mCarets.GetSecond())) {
    450    mCarets.GetFirst()->SetAppearance(Appearance::Normal);
    451    mCarets.GetSecond()->SetAppearance(Appearance::Normal);
    452    return false;
    453  }
    454 
    455  if (mCarets.GetFirst()->LogicalPosition().x <=
    456      mCarets.GetSecond()->LogicalPosition().x) {
    457    mCarets.GetFirst()->SetAppearance(Appearance::Left);
    458    mCarets.GetSecond()->SetAppearance(Appearance::Right);
    459  } else {
    460    mCarets.GetFirst()->SetAppearance(Appearance::Right);
    461    mCarets.GetSecond()->SetAppearance(Appearance::Left);
    462  }
    463 
    464  return true;
    465 }
    466 
    467 void AccessibleCaretManager::UpdateCaretsForAlwaysTilt(
    468    const nsIFrame* aStartFrame, const nsIFrame* aEndFrame) {
    469  // When a short LTR word in RTL environment is selected, the two carets
    470  // tilted inward might be overlapped. Make them tilt outward.
    471  if (UpdateCaretsForOverlappingTilt()) {
    472    return;
    473  }
    474 
    475  if (mCarets.GetFirst()->IsVisuallyVisible()) {
    476    auto startFrameWritingMode = aStartFrame->GetWritingMode();
    477    mCarets.GetFirst()->SetAppearance(startFrameWritingMode.IsBidiLTR()
    478                                          ? Appearance::Left
    479                                          : Appearance::Right);
    480  }
    481  if (mCarets.GetSecond()->IsVisuallyVisible()) {
    482    auto endFrameWritingMode = aEndFrame->GetWritingMode();
    483    mCarets.GetSecond()->SetAppearance(
    484        endFrameWritingMode.IsBidiLTR() ? Appearance::Right : Appearance::Left);
    485  }
    486 }
    487 
    488 void AccessibleCaretManager::ProvideHapticFeedback() {
    489  if (StaticPrefs::layout_accessiblecaret_hapticfeedback()) {
    490    if (nsCOMPtr<nsIHapticFeedback> haptic =
    491            do_GetService("@mozilla.org/widget/hapticfeedback;1")) {
    492      haptic->PerformSimpleAction(haptic->LongPress);
    493    }
    494  }
    495 }
    496 
    497 nsresult AccessibleCaretManager::PressCaret(const nsPoint& aPoint,
    498                                            EventClassID aEventClass) {
    499  nsresult rv = NS_ERROR_FAILURE;
    500 
    501  MOZ_ASSERT(aEventClass == eMouseEventClass || aEventClass == eTouchEventClass,
    502             "Unexpected event class!");
    503 
    504  using TouchArea = AccessibleCaret::TouchArea;
    505  TouchArea touchArea =
    506      aEventClass == eMouseEventClass ? TouchArea::CaretImage : TouchArea::Full;
    507 
    508  if (mCarets.GetFirst()->Contains(aPoint, touchArea)) {
    509    mActiveCaret = mCarets.GetFirst();
    510    SetSelectionDirection(eDirPrevious);
    511  } else if (mCarets.GetSecond()->Contains(aPoint, touchArea)) {
    512    mActiveCaret = mCarets.GetSecond();
    513    SetSelectionDirection(eDirNext);
    514  }
    515 
    516  if (mActiveCaret) {
    517    mOffsetYToCaretLogicalPosition =
    518        mActiveCaret->LogicalPosition().y - aPoint.y;
    519    SetSelectionDragState(true);
    520    DispatchCaretStateChangedEvent(CaretChangedReason::Presscaret, &aPoint);
    521    rv = NS_OK;
    522  }
    523 
    524  return rv;
    525 }
    526 
    527 nsresult AccessibleCaretManager::DragCaret(const nsPoint& aPoint) {
    528  MOZ_ASSERT(mActiveCaret);
    529  MOZ_ASSERT(GetCaretMode() != CaretMode::None);
    530 
    531  if (!mPresShell || !mPresShell->GetRootFrame() || !GetSelection()) {
    532    return NS_ERROR_NULL_POINTER;
    533  }
    534 
    535  StopSelectionAutoScrollTimer();
    536  DragCaretInternal(aPoint);
    537 
    538  // We want to scroll the page even if we failed to drag the caret.
    539  StartSelectionAutoScrollTimer(aPoint);
    540  UpdateCarets();
    541 
    542  if (StaticPrefs::layout_accessiblecaret_magnifier_enabled()) {
    543    DispatchCaretStateChangedEvent(CaretChangedReason::Dragcaret, &aPoint);
    544  }
    545  return NS_OK;
    546 }
    547 
    548 nsresult AccessibleCaretManager::ReleaseCaret() {
    549  MOZ_ASSERT(mActiveCaret);
    550 
    551  mActiveCaret = nullptr;
    552  SetSelectionDragState(false);
    553  mDesiredAsyncPanZoomState.Update(*this);
    554  DispatchCaretStateChangedEvent(CaretChangedReason::Releasecaret);
    555  return NS_OK;
    556 }
    557 
    558 nsresult AccessibleCaretManager::TapCaret(const nsPoint& aPoint) {
    559  MOZ_ASSERT(GetCaretMode() != CaretMode::None);
    560 
    561  nsresult rv = NS_ERROR_FAILURE;
    562 
    563  if (GetCaretMode() == CaretMode::Cursor) {
    564    DispatchCaretStateChangedEvent(CaretChangedReason::Taponcaret, &aPoint);
    565    rv = NS_OK;
    566  }
    567 
    568  return rv;
    569 }
    570 
    571 static EnumSet<nsLayoutUtils::FrameForPointOption> GetHitTestOptions() {
    572  EnumSet<nsLayoutUtils::FrameForPointOption> options = {
    573      nsLayoutUtils::FrameForPointOption::IgnorePaintSuppression,
    574      nsLayoutUtils::FrameForPointOption::IgnoreCrossDoc};
    575  return options;
    576 }
    577 
    578 nsresult AccessibleCaretManager::SelectWordOrShortcut(const nsPoint& aPoint) {
    579  // If the long-tap is landing on a pre-existing selection, don't replace
    580  // it with a new one. Instead just return and let the context menu pop up
    581  // on the pre-existing selection.
    582  if (GetCaretMode() == CaretMode::Selection &&
    583      GetSelection()->ContainsPoint(aPoint)) {
    584    AC_LOG("%s: UpdateCarets() for current selection", __FUNCTION__);
    585    UpdateCarets();
    586    ProvideHapticFeedback();
    587    return NS_OK;
    588  }
    589 
    590  if (!mPresShell) {
    591    return NS_ERROR_UNEXPECTED;
    592  }
    593 
    594  nsIFrame* rootFrame = mPresShell->GetRootFrame();
    595  if (!rootFrame) {
    596    return NS_ERROR_NOT_AVAILABLE;
    597  }
    598 
    599  // Find the frame under point.
    600  AutoWeakFrame ptFrame = nsLayoutUtils::GetFrameForPoint(
    601      RelativeTo{rootFrame}, aPoint, GetHitTestOptions());
    602  if (!ptFrame.GetFrame()) {
    603    return NS_ERROR_FAILURE;
    604  }
    605 
    606  nsIFrame* focusableFrame = GetFocusableFrame(ptFrame);
    607 
    608 #ifdef DEBUG_FRAME_DUMP
    609  AC_LOG("%s: Found %s under (%d, %d)", __FUNCTION__, ptFrame->ListTag().get(),
    610         aPoint.x, aPoint.y);
    611  AC_LOG("%s: Found %s focusable", __FUNCTION__,
    612         focusableFrame ? focusableFrame->ListTag().get() : "no frame");
    613 #endif
    614 
    615  // Get ptInFrame here so that we don't need to check whether rootFrame is
    616  // alive later. Note that if ptFrame is being moved by
    617  // IMEStateManager::NotifyIME() or ChangeFocusToOrClearOldFocus() below,
    618  // something under the original point will be selected, which may not be the
    619  // original text the user wants to select.
    620  nsPoint ptInFrame = aPoint;
    621  nsLayoutUtils::TransformPoint(RelativeTo{rootFrame}, RelativeTo{ptFrame},
    622                                ptInFrame);
    623 
    624  // Firstly check long press on an empty editable content.
    625  Element* newFocusEditingHost = GetEditingHostForFrame(ptFrame);
    626  if (focusableFrame && newFocusEditingHost &&
    627      !HasNonEmptyTextContent(newFocusEditingHost)) {
    628    ChangeFocusToOrClearOldFocus(focusableFrame);
    629 
    630    if (StaticPrefs::
    631            layout_accessiblecaret_caret_shown_when_long_tapping_on_empty_content()) {
    632      mCarets.GetFirst()->SetAppearance(Appearance::Normal);
    633    }
    634    // We need to update carets to get correct information before dispatching
    635    // CaretStateChangedEvent.
    636    UpdateCarets();
    637    ProvideHapticFeedback();
    638    DispatchCaretStateChangedEvent(CaretChangedReason::Longpressonemptycontent);
    639    return NS_OK;
    640  }
    641 
    642  bool selectable = ptFrame->IsSelectable();
    643 
    644 #ifdef DEBUG_FRAME_DUMP
    645  AC_LOG("%s: %s %s selectable.", __FUNCTION__, ptFrame->ListTag().get(),
    646         selectable ? "is" : "is NOT");
    647 #endif
    648 
    649  if (!selectable) {
    650    return NS_ERROR_FAILURE;
    651  }
    652 
    653  // Commit the composition string of the old editable focus element (if there
    654  // is any) before changing the focus.
    655  IMEStateManager::NotifyIME(widget::REQUEST_TO_COMMIT_COMPOSITION,
    656                             mPresShell->GetPresContext());
    657  if (!ptFrame.IsAlive()) {
    658    // Cannot continue because ptFrame died.
    659    return NS_ERROR_FAILURE;
    660  }
    661 
    662  // ptFrame is selectable. Now change the focus.
    663  ChangeFocusToOrClearOldFocus(focusableFrame);
    664  if (!ptFrame.IsAlive()) {
    665    // Cannot continue because ptFrame died.
    666    return NS_ERROR_FAILURE;
    667  }
    668 
    669  // If long tap point isn't selectable frame for caret and frame selection
    670  // can find a better frame for caret, we don't select a word.
    671  // See https://webcompat.com/issues/15953
    672  nsIFrame::ContentOffsets offsets = ptFrame->GetContentOffsetsFromPoint(
    673      ptInFrame,
    674      nsIFrame::SKIP_HIDDEN | nsIFrame::IGNORE_NATIVE_ANONYMOUS_SUBTREE);
    675  if (offsets.content) {
    676    RefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
    677    if (frameSelection) {
    678      const FrameAndOffset textFrameAndOffsetContainingWordBoundary =
    679          SelectionMovementUtils::GetFrameForNodeOffset(
    680              offsets.content, offsets.offset, offsets.associate);
    681      if (textFrameAndOffsetContainingWordBoundary &&
    682          textFrameAndOffsetContainingWordBoundary != ptFrame) {
    683        SetSelectionDragState(true);
    684        frameSelection->HandleClick(
    685            MOZ_KnownLive(offsets.content) /* bug 1636889 */,
    686            offsets.StartOffset(), offsets.EndOffset(),
    687            nsFrameSelection::FocusMode::kCollapseToNewPoint,
    688            offsets.associate);
    689        SetSelectionDragState(false);
    690        ClearMaintainedSelection();
    691 
    692        if (StaticPrefs::
    693                layout_accessiblecaret_caret_shown_when_long_tapping_on_empty_content()) {
    694          mCarets.GetFirst()->SetAppearance(Appearance::Normal);
    695        }
    696 
    697        UpdateCarets();
    698        ProvideHapticFeedback();
    699        DispatchCaretStateChangedEvent(
    700            CaretChangedReason::Longpressonemptycontent);
    701 
    702        return NS_OK;
    703      }
    704    }
    705  }
    706 
    707  // Then try select a word under point.
    708  nsresult rv = SelectWord(ptFrame, ptInFrame);
    709  UpdateCarets();
    710  ProvideHapticFeedback();
    711 
    712  return rv;
    713 }
    714 
    715 void AccessibleCaretManager::OnScrollStart() {
    716  AC_LOG("%s", __FUNCTION__);
    717 
    718  nsAutoScriptBlocker scriptBlocker;
    719  AutoRestore<bool> saveAllowFlushingLayout(mLayoutFlusher.mAllowFlushing);
    720  mLayoutFlusher.mAllowFlushing = false;
    721 
    722  Maybe<PresShell::AutoAssertNoFlush> assert;
    723  if (mPresShell) {
    724    assert.emplace(*mPresShell);
    725  }
    726 
    727  mIsScrollStarted = true;
    728 
    729  if (mCarets.HasLogicallyVisibleCaret()) {
    730    // Dispatch the event only if one of the carets is logically visible like in
    731    // HideCaretsAndDispatchCaretStateChangedEvent().
    732    DispatchCaretStateChangedEvent(CaretChangedReason::Scroll);
    733  }
    734 }
    735 
    736 void AccessibleCaretManager::OnScrollEnd() {
    737  nsAutoScriptBlocker scriptBlocker;
    738  AutoRestore<bool> saveAllowFlushingLayout(mLayoutFlusher.mAllowFlushing);
    739  mLayoutFlusher.mAllowFlushing = false;
    740 
    741  Maybe<PresShell::AutoAssertNoFlush> assert;
    742  if (mPresShell) {
    743    assert.emplace(*mPresShell);
    744  }
    745 
    746  mIsScrollStarted = false;
    747 
    748  if (GetCaretMode() == CaretMode::Cursor) {
    749    if (!mCarets.GetFirst()->IsLogicallyVisible()) {
    750      // If the caret is hidden (Appearance::None) due to blur, no
    751      // need to update it.
    752      return;
    753    }
    754  }
    755 
    756  // For mouse and keyboard input, we don't want to show the carets.
    757  if (StaticPrefs::layout_accessiblecaret_hide_carets_for_mouse_input() &&
    758      (mLastInputSource == MouseEvent_Binding::MOZ_SOURCE_MOUSE ||
    759       mLastInputSource == MouseEvent_Binding::MOZ_SOURCE_KEYBOARD)) {
    760    AC_LOG("%s: HideCaretsAndDispatchCaretStateChangedEvent()", __FUNCTION__);
    761    HideCaretsAndDispatchCaretStateChangedEvent();
    762    return;
    763  }
    764 
    765  AC_LOG("%s: UpdateCarets()", __FUNCTION__);
    766  UpdateCarets();
    767 }
    768 
    769 void AccessibleCaretManager::OnScrollPositionChanged() {
    770  nsAutoScriptBlocker scriptBlocker;
    771  AutoRestore<bool> saveAllowFlushingLayout(mLayoutFlusher.mAllowFlushing);
    772  mLayoutFlusher.mAllowFlushing = false;
    773 
    774  Maybe<PresShell::AutoAssertNoFlush> assert;
    775  if (mPresShell) {
    776    assert.emplace(*mPresShell);
    777  }
    778 
    779  if (mCarets.HasLogicallyVisibleCaret()) {
    780    if (mIsScrollStarted) {
    781      // We don't want extra CaretStateChangedEvents dispatched when user is
    782      // scrolling the page.
    783      AC_LOG("%s: UpdateCarets(RespectOldAppearance | DispatchNoEvent)",
    784             __FUNCTION__);
    785      UpdateCarets({UpdateCaretsHint::RespectOldAppearance,
    786                    UpdateCaretsHint::DispatchNoEvent});
    787    } else {
    788      AC_LOG("%s: UpdateCarets(RespectOldAppearance)", __FUNCTION__);
    789      UpdateCarets(UpdateCaretsHint::RespectOldAppearance);
    790    }
    791  }
    792 }
    793 
    794 void AccessibleCaretManager::OnReflow() {
    795  nsAutoScriptBlocker scriptBlocker;
    796  AutoRestore<bool> saveAllowFlushingLayout(mLayoutFlusher.mAllowFlushing);
    797  mLayoutFlusher.mAllowFlushing = false;
    798 
    799  Maybe<PresShell::AutoAssertNoFlush> assert;
    800  if (mPresShell) {
    801    assert.emplace(*mPresShell);
    802  }
    803 
    804  if (mCarets.HasLogicallyVisibleCaret()) {
    805    AC_LOG("%s: UpdateCarets(RespectOldAppearance)", __FUNCTION__);
    806    UpdateCarets(UpdateCaretsHint::RespectOldAppearance);
    807  }
    808 }
    809 
    810 void AccessibleCaretManager::OnBlur() {
    811  AC_LOG("%s: HideCaretsAndDispatchCaretStateChangedEvent()", __FUNCTION__);
    812  HideCaretsAndDispatchCaretStateChangedEvent();
    813 }
    814 
    815 void AccessibleCaretManager::OnKeyboardEvent() {
    816  if (GetCaretMode() == CaretMode::Cursor) {
    817    AC_LOG("%s: HideCaretsAndDispatchCaretStateChangedEvent()", __FUNCTION__);
    818    HideCaretsAndDispatchCaretStateChangedEvent();
    819  }
    820 }
    821 
    822 void AccessibleCaretManager::SetLastInputSource(uint16_t aInputSource) {
    823  mLastInputSource = aInputSource;
    824 }
    825 
    826 bool AccessibleCaretManager::ShouldDisableApz() const {
    827  return mDesiredAsyncPanZoomState.ShouldDisable();
    828 }
    829 
    830 Selection* AccessibleCaretManager::GetSelection() const {
    831  RefPtr<nsFrameSelection> fs = GetFrameSelection();
    832  if (!fs) {
    833    return nullptr;
    834  }
    835  return &fs->NormalSelection();
    836 }
    837 
    838 already_AddRefed<nsFrameSelection> AccessibleCaretManager::GetFrameSelection()
    839    const {
    840  if (!mPresShell) {
    841    return nullptr;
    842  }
    843 
    844  // Prevent us from touching the nsFrameSelection associated with other
    845  // PresShell.
    846  RefPtr<nsFrameSelection> fs = mPresShell->GetLastFocusedFrameSelection();
    847  if (!fs || fs->GetPresShell() != mPresShell) {
    848    return nullptr;
    849  }
    850 
    851  return fs.forget();
    852 }
    853 
    854 nsAutoString AccessibleCaretManager::StringifiedSelection() const {
    855  nsAutoString str;
    856  RefPtr<Selection> selection = GetSelection();
    857  if (selection) {
    858    selection->Stringify(str, CallerType::System,
    859                         mLayoutFlusher.mAllowFlushing
    860                             ? Selection::FlushFrames::Yes
    861                             : Selection::FlushFrames::No);
    862  }
    863  return str;
    864 }
    865 
    866 // static
    867 Element* AccessibleCaretManager::GetEditingHostForFrame(
    868    const nsIFrame* aFrame) {
    869  if (!aFrame) {
    870    return nullptr;
    871  }
    872 
    873  auto content = aFrame->GetContent();
    874  if (!content) {
    875    return nullptr;
    876  }
    877 
    878  return content->GetEditingHost();
    879 }
    880 
    881 AccessibleCaretManager::CaretMode AccessibleCaretManager::GetCaretMode() const {
    882  const Selection* selection = GetSelection();
    883  if (!selection) {
    884    return CaretMode::None;
    885  }
    886 
    887  const uint32_t rangeCount = selection->RangeCount();
    888  if (rangeCount <= 0) {
    889    return CaretMode::None;
    890  }
    891 
    892  const nsFocusManager* fm = nsFocusManager::GetFocusManager();
    893  MOZ_ASSERT(fm);
    894  if (fm->GetFocusedWindow() != mPresShell->GetDocument()->GetWindow()) {
    895    // Hide carets if the window is not focused.
    896    return CaretMode::None;
    897  }
    898 
    899  if (selection->IsCollapsed()) {
    900    return CaretMode::Cursor;
    901  }
    902 
    903  return CaretMode::Selection;
    904 }
    905 
    906 nsIFrame* AccessibleCaretManager::GetFocusableFrame(nsIFrame* aFrame) const {
    907  // This implementation is similar to EventStateManager::PostHandleEvent().
    908  // Look for the nearest enclosing focusable frame.
    909  nsIFrame* focusableFrame = aFrame;
    910  while (focusableFrame) {
    911    if (focusableFrame->IsFocusable(IsFocusableFlags::WithMouse)) {
    912      break;
    913    }
    914    focusableFrame = focusableFrame->GetParent();
    915  }
    916  return focusableFrame;
    917 }
    918 
    919 void AccessibleCaretManager::ChangeFocusToOrClearOldFocus(
    920    nsIFrame* aFrame) const {
    921  RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
    922  MOZ_ASSERT(fm);
    923 
    924  if (aFrame) {
    925    nsIContent* focusableContent = aFrame->GetContent();
    926    MOZ_ASSERT(focusableContent, "Focusable frame must have content!");
    927    RefPtr<Element> focusableElement = Element::FromNode(focusableContent);
    928    fm->SetFocus(focusableElement, nsIFocusManager::FLAG_BYLONGPRESS);
    929  } else if (nsCOMPtr<nsPIDOMWindowOuter> win =
    930                 mPresShell->GetDocument()->GetWindow()) {
    931    fm->ClearFocus(win);
    932    fm->SetFocusedWindow(win);
    933  }
    934 }
    935 
    936 nsresult AccessibleCaretManager::SelectWord(nsIFrame* aFrame,
    937                                            const nsPoint& aPoint) const {
    938  AC_LOGV("%s", __FUNCTION__);
    939 
    940  SetSelectionDragState(true);
    941  nsresult rs =
    942      aFrame->SelectByTypeAtPoint(aPoint, eSelectWord, eSelectWord, 0);
    943 
    944  SetSelectionDragState(false);
    945  ClearMaintainedSelection();
    946 
    947  // Smart-select phone numbers if possible.
    948  if (StaticPrefs::layout_accessiblecaret_extend_selection_for_phone_number()) {
    949    SelectMoreIfPhoneNumber();
    950  }
    951 
    952  return rs;
    953 }
    954 
    955 void AccessibleCaretManager::SetSelectionDragState(bool aState) const {
    956  RefPtr<nsFrameSelection> fs = GetFrameSelection();
    957  if (fs) {
    958    fs->SetDragState(aState);
    959  }
    960 }
    961 
    962 bool AccessibleCaretManager::IsPhoneNumber(const nsAString& aCandidate) const {
    963  RefPtr<Document> doc = mPresShell->GetDocument();
    964  nsAutoString phoneNumberRegex(u"(^\\+)?[0-9 ,\\-.\\(\\)*#pw]{1,30}$"_ns);
    965  return nsContentUtils::IsPatternMatching(aCandidate,
    966                                           std::move(phoneNumberRegex), doc)
    967      .valueOr(false);
    968 }
    969 
    970 void AccessibleCaretManager::SelectMoreIfPhoneNumber() const {
    971  if (IsPhoneNumber(StringifiedSelection())) {
    972    SetSelectionDirection(eDirNext);
    973    ExtendPhoneNumberSelection(u"forward"_ns);
    974 
    975    SetSelectionDirection(eDirPrevious);
    976    ExtendPhoneNumberSelection(u"backward"_ns);
    977 
    978    SetSelectionDirection(eDirNext);
    979  }
    980 }
    981 
    982 void AccessibleCaretManager::ExtendPhoneNumberSelection(
    983    const nsAString& aDirection) const {
    984  if (!mPresShell) {
    985    return;
    986  }
    987 
    988  // Extend the phone number selection until we find a boundary.
    989  RefPtr<Selection> selection = GetSelection();
    990 
    991  while (selection) {
    992    const nsRange* anchorFocusRange = selection->GetAnchorFocusRange();
    993    if (!anchorFocusRange) {
    994      return;
    995    }
    996 
    997    // Backup the anchor focus range since both anchor node and focus node might
    998    // be changed after calling Selection::Modify().
    999    RefPtr<nsRange> oldAnchorFocusRange = anchorFocusRange->CloneRange();
   1000 
   1001    // Save current focus node, focus offset and the selected text so that
   1002    // we can compare them with the modified ones later.
   1003    nsINode* oldFocusNode = selection->GetFocusNode();
   1004    uint32_t oldFocusOffset = selection->FocusOffset();
   1005    nsAutoString oldSelectedText = StringifiedSelection();
   1006 
   1007    // Extend the selection by one char.
   1008    selection->Modify(u"extend"_ns, aDirection, u"character"_ns);
   1009    if (IsTerminated() == Terminated::Yes) {
   1010      return;
   1011    }
   1012 
   1013    // If the selection didn't change, (can't extend further), we're done.
   1014    if (selection->GetFocusNode() == oldFocusNode &&
   1015        selection->FocusOffset() == oldFocusOffset) {
   1016      return;
   1017    }
   1018 
   1019    // If the changed selection isn't a valid phone number, we're done.
   1020    // Also, if the selection was extended to a new block node, the string
   1021    // returned by stringify() won't have a new line at the beginning or the
   1022    // end of the string. Therefore, if either focus node or offset is
   1023    // changed, but selected text is not changed, we're done, too.
   1024    nsAutoString selectedText = StringifiedSelection();
   1025 
   1026    if (!IsPhoneNumber(selectedText) || oldSelectedText == selectedText) {
   1027      // Backout the undesired selection extend, restore the old anchor focus
   1028      // range before exit.
   1029      selection->SetAnchorFocusToRange(oldAnchorFocusRange);
   1030      return;
   1031    }
   1032  }
   1033 }
   1034 
   1035 void AccessibleCaretManager::SetSelectionDirection(nsDirection aDir) const {
   1036  Selection* selection = GetSelection();
   1037  if (selection) {
   1038    selection->AdjustAnchorFocusForMultiRange(aDir);
   1039  }
   1040 }
   1041 
   1042 void AccessibleCaretManager::ClearMaintainedSelection() const {
   1043  // Selection made by double-clicking for example will maintain the original
   1044  // word selection. We should clear it so that we can drag caret freely.
   1045  RefPtr<nsFrameSelection> fs = GetFrameSelection();
   1046  if (fs) {
   1047    fs->MaintainSelection(eSelectNoAmount);
   1048  }
   1049 }
   1050 
   1051 void AccessibleCaretManager::LayoutFlusher::MaybeFlush(
   1052    const PresShell& aPresShell) {
   1053  if (mAllowFlushing) {
   1054    AutoRestore<bool> flushing(mFlushing);
   1055    mFlushing = true;
   1056 
   1057    if (Document* doc = aPresShell.GetDocument()) {
   1058      doc->FlushPendingNotifications(FlushType::Layout);
   1059      // Don't access the PresShell after flushing, it could've become invalid.
   1060    }
   1061  }
   1062 }
   1063 
   1064 static nsIFrame* GetChildFrameContainingOffset(
   1065    nsIFrame* aChildFrame, uint32_t aOffsetInChildFrameContent,
   1066    CaretAssociationHint aHint) {
   1067  nsIFrame* frameAtOffset = nullptr;
   1068  int32_t unused = 0;
   1069  if (NS_WARN_IF(NS_FAILED(aChildFrame->GetChildFrameContainingOffset(
   1070          static_cast<int32_t>(aOffsetInChildFrameContent),
   1071          aHint == CaretAssociationHint::After, &unused, &frameAtOffset)))) {
   1072    frameAtOffset = aChildFrame;
   1073  }
   1074  return frameAtOffset;
   1075 }
   1076 
   1077 FrameAndOffset
   1078 AccessibleCaretManager::GetFirstVisibleLeafFrameOrUnselectableChildFrame(
   1079    nsRange& aRange, nsIContent** aOutContent /* = nullptr */,
   1080    int32_t* aOutOffsetInContent /* = nullptr */) const {
   1081  if (!mPresShell) {
   1082    return {};
   1083  }
   1084 
   1085  MOZ_ASSERT(GetCaretMode() == CaretMode::Selection);
   1086 
   1087  // FYI: aRange may be collapsed if `Selection` has multiple ranges.
   1088  if (MOZ_UNLIKELY(aRange.Collapsed())) {
   1089    return {};
   1090  }
   1091 
   1092  const RawRangeBoundary& shrunkenStart =
   1093      SelectionMovementUtils::GetFirstVisiblePointAtLeaf(aRange);
   1094  if (MOZ_UNLIKELY(!shrunkenStart.IsSet())) {
   1095    return {};
   1096  }
   1097  if (aOutContent) {
   1098    if (nsIContent* const outContent =
   1099            nsIContent::FromNode(shrunkenStart.GetContainer())) {
   1100      *aOutContent = do_AddRef(outContent).take();
   1101    }
   1102  }
   1103  if (aOutOffsetInContent) {
   1104    *aOutOffsetInContent = static_cast<int32_t>(
   1105        *shrunkenStart.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets));
   1106  }
   1107  if (nsIContent* const child = shrunkenStart.GetChildAtOffset()) {
   1108    if (nsIFrame* const childFrame = child->GetPrimaryFrame()) {
   1109      const uint32_t offsetInFrameContent = 0u;
   1110      nsIFrame* const childFrameAtOffset = GetChildFrameContainingOffset(
   1111          childFrame, offsetInFrameContent, CaretAssociationHint::After);
   1112      MOZ_ASSERT(childFrameAtOffset);
   1113      // If the child is a non-selectable container which has padding or border,
   1114      // we want to put the caret before the start edge, but returning the frame
   1115      // makes the caller will get rect in its content.  Therefore, do not
   1116      // return the position in the unselectable container frame.
   1117      if (!childFrameAtOffset->IsInlineFrame() ||
   1118          childFrameAtOffset->IsSelfEmpty()) {
   1119        return {childFrameAtOffset, offsetInFrameContent};
   1120      }
   1121    }
   1122  }
   1123  nsIContent* const container =
   1124      nsIContent::FromNode(shrunkenStart.GetContainer());
   1125  if (MOZ_UNLIKELY(!container)) {
   1126    return {};
   1127  }
   1128  nsIFrame* const frame = container->GetPrimaryFrame();
   1129  if (MOZ_UNLIKELY(!frame)) {
   1130    return {};
   1131  }
   1132  MOZ_ASSERT(frame->IsSelectable());
   1133  const uint32_t offsetInFrameContent =
   1134      *shrunkenStart.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets);
   1135  nsIFrame* const frameAtOffset = GetChildFrameContainingOffset(
   1136      frame, offsetInFrameContent, CaretAssociationHint::After);
   1137  MOZ_ASSERT(frameAtOffset);
   1138  return {frameAtOffset, offsetInFrameContent};
   1139 }
   1140 
   1141 FrameAndOffset
   1142 AccessibleCaretManager::GetLastVisibleLeafFrameOrUnselectableChildFrame(
   1143    nsRange& aRange, nsIContent** aOutContent /* = nullptr */,
   1144    int32_t* aOutOffsetInContent /* = nullptr */) const {
   1145  if (!mPresShell) {
   1146    return {};
   1147  }
   1148 
   1149  MOZ_ASSERT(GetCaretMode() == CaretMode::Selection);
   1150 
   1151  // FYI: aRange may be collapsed if `Selection` has multiple ranges.
   1152  if (MOZ_UNLIKELY(aRange.Collapsed())) {
   1153    return {};
   1154  }
   1155 
   1156  const RawRangeBoundary& shrunkenEnd =
   1157      SelectionMovementUtils::GetLastVisiblePointAtLeaf(aRange);
   1158  if (MOZ_UNLIKELY(!shrunkenEnd.IsSet())) {
   1159    return {};
   1160  }
   1161  if (aOutContent) {
   1162    if (nsIContent* const outContent =
   1163            nsIContent::FromNode(shrunkenEnd.GetContainer())) {
   1164      *aOutContent = do_AddRef(outContent).take();
   1165    }
   1166  }
   1167  if (aOutOffsetInContent) {
   1168    *aOutOffsetInContent = static_cast<int32_t>(
   1169        *shrunkenEnd.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets));
   1170  }
   1171  if (nsIContent* const previousSiblingOfChildAtOffset = shrunkenEnd.Ref()) {
   1172    if (nsIFrame* const childFrame =
   1173            previousSiblingOfChildAtOffset->GetPrimaryFrame()) {
   1174      const uint32_t offsetInChildFrameContent =
   1175          previousSiblingOfChildAtOffset->Length();
   1176      nsIFrame* const childFrameAtOffset = GetChildFrameContainingOffset(
   1177          childFrame, offsetInChildFrameContent, CaretAssociationHint::Before);
   1178      MOZ_ASSERT(childFrameAtOffset);
   1179      // If the child is a non-selectable inline container which has padding or
   1180      // border, we want to put the caret after the end edge, but returning the
   1181      // frame makes the caller will get rect in its content.  Therefore, do not
   1182      // return the position in the unselectable container frame.
   1183      if (!childFrameAtOffset->IsInlineFrame() ||
   1184          childFrameAtOffset->IsSelfEmpty()) {
   1185        return {childFrameAtOffset, offsetInChildFrameContent};
   1186      }
   1187    }
   1188  }
   1189  nsIContent* const container =
   1190      nsIContent::FromNode(shrunkenEnd.GetContainer());
   1191  if (MOZ_UNLIKELY(!container)) {
   1192    return {};
   1193  }
   1194  nsIFrame* const frame = container->GetPrimaryFrame();
   1195  if (MOZ_UNLIKELY(!frame)) {
   1196    return {};
   1197  }
   1198  MOZ_ASSERT(frame->IsSelectable());
   1199  const uint32_t offsetInFrameContent =
   1200      *shrunkenEnd.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets);
   1201  nsIFrame* const frameAtOffset = GetChildFrameContainingOffset(
   1202      frame, offsetInFrameContent, CaretAssociationHint::Before);
   1203  MOZ_ASSERT(frameAtOffset);
   1204  return {frameAtOffset, offsetInFrameContent};
   1205 }
   1206 
   1207 bool AccessibleCaretManager::RestrictCaretDraggingOffsets(
   1208    nsIFrame::ContentOffsets& aOffsets) {
   1209  if (!mPresShell) {
   1210    return false;
   1211  }
   1212 
   1213  MOZ_ASSERT(GetCaretMode() == CaretMode::Selection);
   1214 
   1215  nsDirection dir =
   1216      mActiveCaret == mCarets.GetFirst() ? eDirPrevious : eDirNext;
   1217  nsCOMPtr<nsIContent> content;
   1218  int32_t offsetInContent = 0;
   1219  const FrameAndOffset frameAndOffset =
   1220      dir == eDirNext ? GetFirstVisibleLeafFrameOrUnselectableChildFrame(
   1221                            *GetSelection()->GetFirstRange(),
   1222                            getter_AddRefs(content), &offsetInContent)
   1223                      : GetLastVisibleLeafFrameOrUnselectableChildFrame(
   1224                            *GetSelection()->GetLastRange(),
   1225                            getter_AddRefs(content), &offsetInContent);
   1226  if (!frameAndOffset) {
   1227    return false;
   1228  }
   1229 
   1230  // Compare the active caret's new position (aOffsets) to the inactive caret's
   1231  // position.
   1232  NS_ASSERTION(static_cast<int32_t>(frameAndOffset.mOffsetInFrameContent) >= 0,
   1233               "mOffsetInFrameContent should not be negative when casting to "
   1234               "signed integer");
   1235  const Maybe<int32_t> cmpToInactiveCaretPos =
   1236      nsContentUtils::ComparePoints_AllowNegativeOffsets(
   1237          aOffsets.content, aOffsets.StartOffset(),
   1238          frameAndOffset.GetFrameContent(),
   1239          static_cast<int32_t>(frameAndOffset.mOffsetInFrameContent));
   1240  if (NS_WARN_IF(!cmpToInactiveCaretPos)) {
   1241    // Potentially handle this properly when Selection across Shadow DOM
   1242    // boundary is implemented
   1243    // (https://bugzilla.mozilla.org/show_bug.cgi?id=1607497).
   1244    return false;
   1245  }
   1246 
   1247  // Move one character (in the direction of dir) from the inactive caret's
   1248  // position. This is the limit for the active caret's new position.
   1249  PeekOffsetStruct limit(
   1250      eSelectCluster, dir,
   1251      static_cast<int32_t>(frameAndOffset.mOffsetInFrameContent), nsPoint(0, 0),
   1252      {PeekOffsetOption::JumpLines, PeekOffsetOption::StopAtScroller});
   1253  nsresult rv = frameAndOffset->PeekOffset(&limit);
   1254  if (NS_FAILED(rv)) {
   1255    limit.mResultContent = content;
   1256    limit.mContentOffset = offsetInContent;
   1257  }
   1258 
   1259  // Compare the active caret's new position (aOffsets) to the limit.
   1260  NS_ASSERTION(limit.mContentOffset >= 0,
   1261               "limit.mContentOffset should not be negative");
   1262  const Maybe<int32_t> cmpToLimit =
   1263      nsContentUtils::ComparePoints_AllowNegativeOffsets(
   1264          aOffsets.content, aOffsets.StartOffset(), limit.mResultContent,
   1265          limit.mContentOffset);
   1266  if (NS_WARN_IF(!cmpToLimit)) {
   1267    // Potentially handle this properly when Selection across Shadow DOM
   1268    // boundary is implemented
   1269    // (https://bugzilla.mozilla.org/show_bug.cgi?id=1607497).
   1270    return false;
   1271  }
   1272 
   1273  auto SetOffsetsToLimit = [&aOffsets, &limit]() {
   1274    aOffsets.content = limit.mResultContent;
   1275    aOffsets.offset = limit.mContentOffset;
   1276    aOffsets.secondaryOffset = limit.mContentOffset;
   1277  };
   1278 
   1279  if (!StaticPrefs::
   1280          layout_accessiblecaret_allow_dragging_across_other_caret()) {
   1281    if ((mActiveCaret == mCarets.GetFirst() && *cmpToLimit == 1) ||
   1282        (mActiveCaret == mCarets.GetSecond() && *cmpToLimit == -1)) {
   1283      // The active caret's position is past the limit, which we don't allow
   1284      // here. So set it to the limit, resulting in one character being
   1285      // selected.
   1286      SetOffsetsToLimit();
   1287    }
   1288  } else {
   1289    switch (*cmpToInactiveCaretPos) {
   1290      case 0:
   1291        // The active caret's position is the same as the position of the
   1292        // inactive caret. So set it to the limit to prevent the selection from
   1293        // being collapsed, resulting in one character being selected.
   1294        SetOffsetsToLimit();
   1295        break;
   1296      case 1:
   1297        if (mActiveCaret == mCarets.GetFirst()) {
   1298          // First caret was moved across the second caret. After making change
   1299          // to the selection, the user will drag the second caret.
   1300          mActiveCaret = mCarets.GetSecond();
   1301        }
   1302        break;
   1303      case -1:
   1304        if (mActiveCaret == mCarets.GetSecond()) {
   1305          // Second caret was moved across the first caret. After making change
   1306          // to the selection, the user will drag the first caret.
   1307          mActiveCaret = mCarets.GetFirst();
   1308        }
   1309        break;
   1310    }
   1311  }
   1312 
   1313  return true;
   1314 }
   1315 
   1316 bool AccessibleCaretManager::CompareTreePosition(const nsIFrame* aStartFrame,
   1317                                                 int32_t aStartOffset,
   1318                                                 const nsIFrame* aEndFrame,
   1319                                                 int32_t aEndOffset) const {
   1320  if (MOZ_UNLIKELY(!aStartFrame || !aStartFrame->GetContent() || !aEndFrame ||
   1321                   !aEndFrame->GetContent())) {
   1322    return false;
   1323  }
   1324  if (aStartFrame->GetContent() == aEndFrame->GetContent()) {
   1325    return aStartOffset <= aEndOffset;
   1326  }
   1327  return nsContentUtils::ComparePoints(
   1328             ConstRawRangeBoundary(aStartFrame->GetContent(),
   1329                                   static_cast<uint32_t>(aStartOffset)),
   1330             ConstRawRangeBoundary(aEndFrame->GetContent(),
   1331                                   static_cast<uint32_t>(aEndOffset)))
   1332             .valueOr(1) <= 0;
   1333 }
   1334 
   1335 nsresult AccessibleCaretManager::DragCaretInternal(const nsPoint& aPoint) {
   1336  MOZ_ASSERT(mPresShell);
   1337 
   1338  nsIFrame* rootFrame = mPresShell->GetRootFrame();
   1339  MOZ_ASSERT(rootFrame, "We need root frame to compute caret dragging!");
   1340 
   1341  nsPoint point = AdjustDragBoundary(
   1342      nsPoint(aPoint.x, aPoint.y + mOffsetYToCaretLogicalPosition));
   1343 
   1344  // Find out which content we point to
   1345 
   1346  nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
   1347      RelativeTo{rootFrame}, point, GetHitTestOptions());
   1348  if (!ptFrame) {
   1349    return NS_ERROR_FAILURE;
   1350  }
   1351 
   1352  RefPtr<nsFrameSelection> fs = GetFrameSelection();
   1353  MOZ_ASSERT(fs);
   1354 
   1355  nsresult result;
   1356  nsIFrame* newFrame = nullptr;
   1357  nsPoint newPoint;
   1358  nsPoint ptInFrame = point;
   1359  nsLayoutUtils::TransformPoint(RelativeTo{rootFrame}, RelativeTo{ptFrame},
   1360                                ptInFrame);
   1361  result = fs->ConstrainFrameAndPointToAnchorSubtree(ptFrame, ptInFrame,
   1362                                                     &newFrame, newPoint);
   1363  if (NS_FAILED(result) || !newFrame) {
   1364    return NS_ERROR_FAILURE;
   1365  }
   1366 
   1367  if (!newFrame->IsSelectable()) {
   1368    return NS_ERROR_FAILURE;
   1369  }
   1370 
   1371  nsIFrame::ContentOffsets offsets = newFrame->GetContentOffsetsFromPoint(
   1372      newPoint, nsIFrame::IGNORE_NATIVE_ANONYMOUS_SUBTREE);
   1373  if (offsets.IsNull()) {
   1374    return NS_ERROR_FAILURE;
   1375  }
   1376 
   1377  if (GetCaretMode() == CaretMode::Selection &&
   1378      !RestrictCaretDraggingOffsets(offsets)) {
   1379    return NS_ERROR_FAILURE;
   1380  }
   1381 
   1382  ClearMaintainedSelection();
   1383 
   1384  const nsFrameSelection::FocusMode focusMode =
   1385      (GetCaretMode() == CaretMode::Selection)
   1386          ? nsFrameSelection::FocusMode::kExtendSelection
   1387          : nsFrameSelection::FocusMode::kCollapseToNewPoint;
   1388  // While dragging the active caret for collapsed selection, we should not
   1389  // extend it.  However, when crossing an unselectable node,
   1390  // GetContentOffsetsFromPoint() above may return the secondary offset.
   1391  // Therefore we need to ignore the secondary offset in that case.
   1392  int32_t startOffset, endOffset;
   1393  if (focusMode == nsFrameSelection::FocusMode::kCollapseToNewPoint) {
   1394    startOffset = endOffset = offsets.offset;
   1395  } else {
   1396    startOffset = offsets.StartOffset();
   1397    endOffset = offsets.EndOffset();
   1398  }
   1399  fs->HandleClick(MOZ_KnownLive(offsets.content) /* bug 1636889 */, startOffset,
   1400                  endOffset, focusMode, offsets.associate);
   1401  return NS_OK;
   1402 }
   1403 
   1404 // static
   1405 nsRect AccessibleCaretManager::GetAllChildFrameRectsUnion(nsIFrame* aFrame) {
   1406  nsRect unionRect;
   1407 
   1408  // Drill through scroll frames, we don't want to include scrollbar child
   1409  // frames below.
   1410  for (nsIFrame* frame = aFrame->GetContentInsertionFrame(); frame;
   1411       frame = frame->GetNextContinuation()) {
   1412    Maybe<nsRect> childrenRect;
   1413 
   1414    for (const auto& childList : frame->ChildLists()) {
   1415      // Loop all children to union their scrollable overflow rect.
   1416      for (nsIFrame* child : childList.mList) {
   1417        nsRect childRect = child->ScrollableOverflowRectRelativeToSelf();
   1418        nsLayoutUtils::TransformRect(child, frame, childRect);
   1419 
   1420        if (childrenRect) {
   1421          // Some frames (e.g. BRFrame or a TextFrame that only contains '\n')
   1422          // can have a positive block size but a zero inline size. Using
   1423          // UnionEdges ensures these dimensions are properly included in
   1424          // childrenRect.
   1425          *childrenRect = childrenRect->UnionEdges(childRect);
   1426        } else {
   1427          childrenRect.emplace(childRect);
   1428        }
   1429      }
   1430    }
   1431 
   1432    if (childrenRect) {
   1433      if (frame != aFrame) {
   1434        nsLayoutUtils::TransformRect(frame, aFrame, *childrenRect);
   1435      }
   1436      unionRect = unionRect.Union(*childrenRect);
   1437    }
   1438  }
   1439 
   1440  return unionRect;
   1441 }
   1442 
   1443 nsPoint AccessibleCaretManager::AdjustDragBoundary(
   1444    const nsPoint& aPoint) const {
   1445  nsPoint adjustedPoint = aPoint;
   1446 
   1447  auto frameData =
   1448      nsCaret::GetFrameAndOffset(nsCaret::CaretPositionFor(GetSelection()));
   1449  Element* editingHost = GetEditingHostForFrame(frameData.mFrame);
   1450 
   1451  if (editingHost) {
   1452    nsIFrame* editingHostFrame = editingHost->GetPrimaryFrame();
   1453    if (editingHostFrame) {
   1454      nsRect boundary =
   1455          AccessibleCaretManager::GetAllChildFrameRectsUnion(editingHostFrame);
   1456      nsLayoutUtils::TransformRect(editingHostFrame, mPresShell->GetRootFrame(),
   1457                                   boundary);
   1458 
   1459      // Shrink the rect to make sure we never hit the boundary.
   1460      boundary.Deflate(kBoundaryAppUnits);
   1461 
   1462      adjustedPoint = boundary.ClampPoint(adjustedPoint);
   1463    }
   1464  }
   1465 
   1466  if (GetCaretMode() == CaretMode::Selection &&
   1467      !StaticPrefs::
   1468          layout_accessiblecaret_allow_dragging_across_other_caret()) {
   1469    // Bug 1068474: Adjust the Y-coordinate so that the carets won't be in tilt
   1470    // mode when a caret is being dragged surpass the other caret.
   1471    //
   1472    // For example, when dragging the second caret, the horizontal boundary
   1473    // (lower bound) of its Y-coordinate is the logical position of the first
   1474    // caret. Likewise, when dragging the first caret, the horizontal boundary
   1475    // (upper bound) of its Y-coordinate is the logical position of the second
   1476    // caret.
   1477    if (mActiveCaret == mCarets.GetFirst()) {
   1478      nscoord dragDownBoundaryY = mCarets.GetSecond()->LogicalPosition().y;
   1479      if (dragDownBoundaryY > 0 && adjustedPoint.y > dragDownBoundaryY) {
   1480        adjustedPoint.y = dragDownBoundaryY;
   1481      }
   1482    } else {
   1483      nscoord dragUpBoundaryY = mCarets.GetFirst()->LogicalPosition().y;
   1484      if (adjustedPoint.y < dragUpBoundaryY) {
   1485        adjustedPoint.y = dragUpBoundaryY;
   1486      }
   1487    }
   1488  }
   1489 
   1490  return adjustedPoint;
   1491 }
   1492 
   1493 void AccessibleCaretManager::StartSelectionAutoScrollTimer(
   1494    const nsPoint& aPoint) const {
   1495  Selection* selection = GetSelection();
   1496  MOZ_ASSERT(selection);
   1497 
   1498  nsIFrame* anchorFrame = selection->GetPrimaryFrameForAnchorNode();
   1499  if (!anchorFrame) {
   1500    return;
   1501  }
   1502 
   1503  ScrollContainerFrame* scrollContainerFrame =
   1504      nsLayoutUtils::GetNearestScrollContainerFrame(
   1505          anchorFrame, nsLayoutUtils::SCROLLABLE_SAME_DOC |
   1506                           nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
   1507  if (!scrollContainerFrame) {
   1508    return;
   1509  }
   1510 
   1511  nsIFrame* capturingFrame = scrollContainerFrame->GetScrolledFrame();
   1512  if (!capturingFrame) {
   1513    return;
   1514  }
   1515 
   1516  nsIFrame* rootFrame = mPresShell->GetRootFrame();
   1517  MOZ_ASSERT(rootFrame);
   1518  nsPoint ptInScrolled = aPoint;
   1519  nsLayoutUtils::TransformPoint(RelativeTo{rootFrame},
   1520                                RelativeTo{capturingFrame}, ptInScrolled);
   1521 
   1522  RefPtr<nsFrameSelection> fs = GetFrameSelection();
   1523  MOZ_ASSERT(fs);
   1524  fs->StartAutoScrollTimer(capturingFrame, ptInScrolled, kAutoScrollTimerDelay);
   1525 }
   1526 
   1527 void AccessibleCaretManager::StopSelectionAutoScrollTimer() const {
   1528  RefPtr<nsFrameSelection> fs = GetFrameSelection();
   1529  MOZ_ASSERT(fs);
   1530  fs->StopAutoScrollTimer();
   1531 }
   1532 
   1533 void AccessibleCaretManager::DispatchCaretStateChangedEvent(
   1534    CaretChangedReason aReason, const nsPoint* aPoint) {
   1535  if (MaybeFlushLayout() == Terminated::Yes) {
   1536    return;
   1537  }
   1538 
   1539  const Selection* sel = GetSelection();
   1540  if (!sel) {
   1541    return;
   1542  }
   1543 
   1544  Document* doc = mPresShell->GetDocument();
   1545  MOZ_ASSERT(doc);
   1546 
   1547  CaretStateChangedEventInit init;
   1548  init.mBubbles = true;
   1549 
   1550  const nsRange* range = sel->GetAnchorFocusRange();
   1551  nsINode* commonAncestorNode = nullptr;
   1552  if (range) {
   1553    commonAncestorNode = range->GetClosestCommonInclusiveAncestor();
   1554  }
   1555 
   1556  if (!commonAncestorNode) {
   1557    commonAncestorNode = sel->GetFrameSelection()->GetAncestorLimiter();
   1558  }
   1559 
   1560  auto domRect = MakeRefPtr<DOMRect>(ToSupports(doc));
   1561  nsRect rect = nsLayoutUtils::GetSelectionBoundingRect(sel);
   1562 
   1563  nsIFrame* commonAncestorFrame = nullptr;
   1564  nsIFrame* rootFrame = mPresShell->GetRootFrame();
   1565 
   1566  if (commonAncestorNode && commonAncestorNode->IsContent()) {
   1567    commonAncestorFrame = commonAncestorNode->AsContent()->GetPrimaryFrame();
   1568  }
   1569 
   1570  if (commonAncestorFrame && rootFrame) {
   1571    nsLayoutUtils::TransformRect(rootFrame, commonAncestorFrame, rect);
   1572    nsRect clampedRect =
   1573        nsLayoutUtils::ClampRectToScrollFrames(commonAncestorFrame, rect);
   1574    nsLayoutUtils::TransformRect(commonAncestorFrame, rootFrame, clampedRect);
   1575    rect = clampedRect;
   1576    init.mSelectionVisible = !clampedRect.IsEmpty();
   1577  } else {
   1578    init.mSelectionVisible = true;
   1579  }
   1580 
   1581  domRect->SetLayoutRect(rect);
   1582 
   1583  // Send isEditable info w/ event detail. This info can help determine
   1584  // whether to show cut command on selection dialog or not.
   1585  init.mSelectionEditable = GetEditingHostForFrame(commonAncestorFrame);
   1586 
   1587  init.mBoundingClientRect = domRect;
   1588  init.mReason = aReason;
   1589  init.mCollapsed = sel->IsCollapsed();
   1590  init.mCaretVisible = mCarets.HasLogicallyVisibleCaret();
   1591  init.mCaretVisuallyVisible = mCarets.HasVisuallyVisibleCaret();
   1592  init.mSelectedTextContent = StringifiedSelection();
   1593 
   1594  if (aPoint) {
   1595    CSSIntPoint pt = CSSPixel::FromAppUnitsRounded(*aPoint);
   1596    init.mClientX = pt.x;
   1597    init.mClientY = pt.y;
   1598  }
   1599 
   1600  RefPtr<CaretStateChangedEvent> event = CaretStateChangedEvent::Constructor(
   1601      doc, u"mozcaretstatechanged"_ns, init);
   1602  event->SetTrusted(true);
   1603 
   1604  AC_LOG("%s: reason %" PRIu32 ", collapsed %d, caretVisible %" PRIu32,
   1605         __FUNCTION__, static_cast<uint32_t>(init.mReason), init.mCollapsed,
   1606         static_cast<uint32_t>(init.mCaretVisible));
   1607 
   1608  (new AsyncEventDispatcher(doc, event.forget(), ChromeOnlyDispatch::eYes))
   1609      ->PostDOMEvent();
   1610 }
   1611 
   1612 AccessibleCaretManager::Carets::Carets(UniquePtr<AccessibleCaret> aFirst,
   1613                                       UniquePtr<AccessibleCaret> aSecond)
   1614    : mFirst{std::move(aFirst)}, mSecond{std::move(aSecond)} {}
   1615 
   1616 }  // namespace mozilla