LCOV - code coverage report
Current view: top level - layout/base - AccessibleCaretManager.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 696 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 54 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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 "AccessibleCaret.h"
      10             : #include "AccessibleCaretEventHub.h"
      11             : #include "AccessibleCaretLogger.h"
      12             : #include "mozilla/AsyncEventDispatcher.h"
      13             : #include "mozilla/dom/Element.h"
      14             : #include "mozilla/dom/Selection.h"
      15             : #include "mozilla/dom/TreeWalker.h"
      16             : #include "mozilla/IMEStateManager.h"
      17             : #include "mozilla/IntegerPrintfMacros.h"
      18             : #include "mozilla/Preferences.h"
      19             : #include "nsCaret.h"
      20             : #include "nsContainerFrame.h"
      21             : #include "nsContentUtils.h"
      22             : #include "nsFocusManager.h"
      23             : #include "nsFrame.h"
      24             : #include "nsFrameSelection.h"
      25             : #include "nsGenericHTMLElement.h"
      26             : #include "nsIHapticFeedback.h"
      27             : #ifdef MOZ_WIDGET_ANDROID
      28             : #include "nsWindow.h"
      29             : #endif
      30             : 
      31             : namespace mozilla {
      32             : 
      33             : #undef AC_LOG
      34             : #define AC_LOG(message, ...)                                                   \
      35             :   AC_LOG_BASE("AccessibleCaretManager (%p): " message, this, ##__VA_ARGS__);
      36             : 
      37             : #undef AC_LOGV
      38             : #define AC_LOGV(message, ...)                                                  \
      39             :   AC_LOGV_BASE("AccessibleCaretManager (%p): " message, this, ##__VA_ARGS__);
      40             : 
      41             : using namespace dom;
      42             : using Appearance = AccessibleCaret::Appearance;
      43             : using PositionChangedResult = AccessibleCaret::PositionChangedResult;
      44             : 
      45             : #define AC_PROCESS_ENUM_TO_STREAM(e) case(e): aStream << #e; break;
      46             : std::ostream&
      47           0 : operator<<(std::ostream& aStream,
      48             :            const AccessibleCaretManager::CaretMode& aCaretMode)
      49             : {
      50             :   using CaretMode = AccessibleCaretManager::CaretMode;
      51           0 :   switch (aCaretMode) {
      52           0 :     AC_PROCESS_ENUM_TO_STREAM(CaretMode::None);
      53           0 :     AC_PROCESS_ENUM_TO_STREAM(CaretMode::Cursor);
      54           0 :     AC_PROCESS_ENUM_TO_STREAM(CaretMode::Selection);
      55             :   }
      56           0 :   return aStream;
      57             : }
      58             : 
      59           0 : std::ostream& operator<<(std::ostream& aStream,
      60             :                          const AccessibleCaretManager::UpdateCaretsHint& aHint)
      61             : {
      62             :   using UpdateCaretsHint = AccessibleCaretManager::UpdateCaretsHint;
      63           0 :   switch (aHint) {
      64           0 :     AC_PROCESS_ENUM_TO_STREAM(UpdateCaretsHint::Default);
      65           0 :     AC_PROCESS_ENUM_TO_STREAM(UpdateCaretsHint::RespectOldAppearance);
      66           0 :     AC_PROCESS_ENUM_TO_STREAM(UpdateCaretsHint::DispatchNoEvent);
      67             :   }
      68           0 :   return aStream;
      69             : }
      70             : #undef AC_PROCESS_ENUM_TO_STREAM
      71             : 
      72             : /* static */ bool
      73             : AccessibleCaretManager::sSelectionBarEnabled = false;
      74             : /* static */ bool
      75             : AccessibleCaretManager::sCaretShownWhenLongTappingOnEmptyContent = false;
      76             : /* static */ bool
      77             : AccessibleCaretManager::sCaretsAlwaysTilt = false;
      78             : /* static */ bool
      79             : AccessibleCaretManager::sCaretsAlwaysShowWhenScrolling = true;
      80             : /* static */ bool
      81             : AccessibleCaretManager::sCaretsScriptUpdates = false;
      82             : /* static */ bool
      83             : AccessibleCaretManager::sCaretsAllowDraggingAcrossOtherCaret = true;
      84             : /* static */ bool
      85             : AccessibleCaretManager::sHapticFeedback = false;
      86             : /* static */ bool
      87             : AccessibleCaretManager::sExtendSelectionForPhoneNumber = false;
      88             : /* static */ bool
      89             : AccessibleCaretManager::sHideCaretsForMouseInput = true;
      90             : 
      91           0 : AccessibleCaretManager::AccessibleCaretManager(nsIPresShell* aPresShell)
      92           0 :   : mPresShell(aPresShell)
      93             : {
      94           0 :   if (!mPresShell) {
      95           0 :     return;
      96             :   }
      97             : 
      98           0 :   mFirstCaret = MakeUnique<AccessibleCaret>(mPresShell);
      99           0 :   mSecondCaret = MakeUnique<AccessibleCaret>(mPresShell);
     100             : 
     101             :   static bool addedPrefs = false;
     102           0 :   if (!addedPrefs) {
     103             :     Preferences::AddBoolVarCache(&sSelectionBarEnabled,
     104           0 :                                  "layout.accessiblecaret.bar.enabled");
     105             :     Preferences::AddBoolVarCache(&sCaretShownWhenLongTappingOnEmptyContent,
     106           0 :       "layout.accessiblecaret.caret_shown_when_long_tapping_on_empty_content");
     107             :     Preferences::AddBoolVarCache(&sCaretsAlwaysTilt,
     108           0 :                                  "layout.accessiblecaret.always_tilt");
     109             :     Preferences::AddBoolVarCache(&sCaretsAlwaysShowWhenScrolling,
     110           0 :       "layout.accessiblecaret.always_show_when_scrolling", true);
     111             :     Preferences::AddBoolVarCache(&sCaretsScriptUpdates,
     112           0 :       "layout.accessiblecaret.allow_script_change_updates");
     113             :     Preferences::AddBoolVarCache(&sCaretsAllowDraggingAcrossOtherCaret,
     114           0 :       "layout.accessiblecaret.allow_dragging_across_other_caret", true);
     115             :     Preferences::AddBoolVarCache(&sHapticFeedback,
     116           0 :                                  "layout.accessiblecaret.hapticfeedback");
     117             :     Preferences::AddBoolVarCache(&sExtendSelectionForPhoneNumber,
     118           0 :       "layout.accessiblecaret.extend_selection_for_phone_number");
     119             :     Preferences::AddBoolVarCache(&sHideCaretsForMouseInput,
     120           0 :       "layout.accessiblecaret.hide_carets_for_mouse_input");
     121           0 :     addedPrefs = true;
     122             :   }
     123             : }
     124             : 
     125           0 : AccessibleCaretManager::~AccessibleCaretManager()
     126             : {
     127           0 : }
     128             : 
     129             : void
     130           0 : AccessibleCaretManager::Terminate()
     131             : {
     132           0 :   mFirstCaret = nullptr;
     133           0 :   mSecondCaret = nullptr;
     134           0 :   mActiveCaret = nullptr;
     135           0 :   mPresShell = nullptr;
     136           0 : }
     137             : 
     138             : nsresult
     139           0 : AccessibleCaretManager::OnSelectionChanged(nsIDOMDocument* aDoc,
     140             :                                            nsISelection* aSel, int16_t aReason)
     141             : {
     142           0 :   Selection* selection = GetSelection();
     143           0 :   AC_LOG("%s: aSel: %p, GetSelection(): %p, aReason: %d", __FUNCTION__,
     144             :          aSel, selection, aReason);
     145           0 :   if (aSel != selection) {
     146           0 :     return NS_OK;
     147             :   }
     148             : 
     149             :   // eSetSelection events from the Fennec widget IME can be generated
     150             :   // by autoSuggest / autoCorrect composition changes, or by TYPE_REPLACE_TEXT
     151             :   // actions, either positioning cursor for text insert, or selecting
     152             :   // text-to-be-replaced. None should affect AccessibleCaret visibility.
     153           0 :   if (aReason & nsISelectionListener::IME_REASON) {
     154           0 :     return NS_OK;
     155             :   }
     156             : 
     157             :   // Move the cursor by Javascript / or unknown internal.
     158           0 :   if (aReason == nsISelectionListener::NO_REASON) {
     159             :     // Update visible carets, if javascript changes are allowed.
     160           0 :     if (sCaretsScriptUpdates &&
     161           0 :         (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible())) {
     162           0 :         UpdateCarets();
     163           0 :         return NS_OK;
     164             :     }
     165             :     // Default for NO_REASON is to make hidden.
     166           0 :     HideCarets();
     167           0 :     return NS_OK;
     168             :   }
     169             : 
     170             :   // Move cursor by keyboard.
     171           0 :   if (aReason & nsISelectionListener::KEYPRESS_REASON) {
     172           0 :     HideCarets();
     173           0 :     return NS_OK;
     174             :   }
     175             : 
     176             :   // OnBlur() might be called between mouse down and mouse up, so we hide carets
     177             :   // upon mouse down anyway, and update carets upon mouse up.
     178           0 :   if (aReason & nsISelectionListener::MOUSEDOWN_REASON) {
     179           0 :     HideCarets();
     180           0 :     return NS_OK;
     181             :   }
     182             : 
     183             :   // Range will collapse after cutting or copying text.
     184           0 :   if (aReason & (nsISelectionListener::COLLAPSETOSTART_REASON |
     185             :                  nsISelectionListener::COLLAPSETOEND_REASON)) {
     186           0 :     HideCarets();
     187           0 :     return NS_OK;
     188             :   }
     189             : 
     190             :   // For mouse input we don't want to show the carets.
     191           0 :   if (sHideCaretsForMouseInput &&
     192           0 :       mLastInputSource == nsIDOMMouseEvent::MOZ_SOURCE_MOUSE) {
     193           0 :     HideCarets();
     194           0 :     return NS_OK;
     195             :   }
     196             : 
     197             :   // When we want to hide the carets for mouse input, hide them for select
     198             :   // all action fired by keyboard as well.
     199           0 :   if (sHideCaretsForMouseInput &&
     200           0 :       mLastInputSource == nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD &&
     201           0 :       (aReason & nsISelectionListener::SELECTALL_REASON)) {
     202           0 :     HideCarets();
     203           0 :     return NS_OK;
     204             :   }
     205             : 
     206           0 :   UpdateCarets();
     207           0 :   return NS_OK;
     208             : }
     209             : 
     210             : void
     211           0 : AccessibleCaretManager::HideCarets()
     212             : {
     213           0 :   if (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible()) {
     214           0 :     AC_LOG("%s", __FUNCTION__);
     215           0 :     mFirstCaret->SetAppearance(Appearance::None);
     216           0 :     mSecondCaret->SetAppearance(Appearance::None);
     217           0 :     DispatchCaretStateChangedEvent(CaretChangedReason::Visibilitychange);
     218             :   }
     219           0 : }
     220             : 
     221             : void
     222           0 : AccessibleCaretManager::UpdateCarets(UpdateCaretsHintSet aHint)
     223             : {
     224           0 :   FlushLayout();
     225           0 :   if (IsTerminated()) {
     226           0 :     return;
     227             :   }
     228             : 
     229           0 :   mLastUpdateCaretMode = GetCaretMode();
     230             : 
     231           0 :   switch (mLastUpdateCaretMode) {
     232             :   case CaretMode::None:
     233           0 :     HideCarets();
     234           0 :     break;
     235             :   case CaretMode::Cursor:
     236           0 :     UpdateCaretsForCursorMode(aHint);
     237           0 :     break;
     238             :   case CaretMode::Selection:
     239           0 :     UpdateCaretsForSelectionMode(aHint);
     240           0 :     break;
     241             :   }
     242             : }
     243             : 
     244             : bool
     245           0 : AccessibleCaretManager::IsCaretDisplayableInCursorMode(nsIFrame** aOutFrame,
     246             :                                                        int32_t* aOutOffset) const
     247             : {
     248           0 :   RefPtr<nsCaret> caret = mPresShell->GetCaret();
     249           0 :   if (!caret || !caret->IsVisible()) {
     250           0 :     return false;
     251             :   }
     252             : 
     253           0 :   int32_t offset = 0;
     254           0 :   nsIFrame* frame = nsCaret::GetFrameAndOffset(GetSelection(), nullptr, 0, &offset);
     255             : 
     256           0 :   if (!frame) {
     257           0 :     return false;
     258             :   }
     259             : 
     260           0 :   if (!GetEditingHostForFrame(frame)) {
     261           0 :     return false;
     262             :   }
     263             : 
     264           0 :   if (aOutFrame) {
     265           0 :     *aOutFrame = frame;
     266             :   }
     267             : 
     268           0 :   if (aOutOffset) {
     269           0 :     *aOutOffset = offset;
     270             :   }
     271             : 
     272           0 :   return true;
     273             : }
     274             : 
     275             : bool
     276           0 : AccessibleCaretManager::HasNonEmptyTextContent(nsINode* aNode) const
     277             : {
     278             :   return nsContentUtils::HasNonEmptyTextContent(
     279           0 :            aNode, nsContentUtils::eRecurseIntoChildren);
     280             : }
     281             : 
     282             : void
     283           0 : AccessibleCaretManager::UpdateCaretsForCursorMode(UpdateCaretsHintSet aHints)
     284             : {
     285           0 :   AC_LOG("%s, selection: %p", __FUNCTION__, GetSelection());
     286             : 
     287           0 :   int32_t offset = 0;
     288           0 :   nsIFrame* frame = nullptr;
     289           0 :   if (!IsCaretDisplayableInCursorMode(&frame, &offset)) {
     290           0 :     HideCarets();
     291           0 :     return;
     292             :   }
     293             : 
     294           0 :   PositionChangedResult result = mFirstCaret->SetPosition(frame, offset);
     295             : 
     296           0 :   switch (result) {
     297             :     case PositionChangedResult::NotChanged:
     298             :     case PositionChangedResult::Changed:
     299           0 :       if (aHints == UpdateCaretsHint::Default) {
     300           0 :         if (HasNonEmptyTextContent(GetEditingHostForFrame(frame))) {
     301           0 :           mFirstCaret->SetAppearance(Appearance::Normal);
     302           0 :         } else if (sCaretShownWhenLongTappingOnEmptyContent) {
     303           0 :           if (mFirstCaret->IsLogicallyVisible()) {
     304             :             // Possible cases are: 1) SelectWordOrShortcut() sets the
     305             :             // appearance to Normal. 2) When the caret is out of viewport and
     306             :             // now scrolling into viewport, it has appearance NormalNotShown.
     307           0 :             mFirstCaret->SetAppearance(Appearance::Normal);
     308             :           } else {
     309             :             // Possible cases are: a) Single tap on current empty content;
     310             :             // OnSelectionChanged() sets the appearance to None due to
     311             :             // MOUSEDOWN_REASON. b) Single tap on other empty content;
     312             :             // OnBlur() sets the appearance to None.
     313             :             //
     314             :             // Do nothing to make the appearance remains None so that it can
     315             :             // be distinguished from case 2). Also do not set the appearance
     316             :             // to NormalNotShown here like the default update behavior.
     317             :           }
     318             :         } else {
     319           0 :           mFirstCaret->SetAppearance(Appearance::NormalNotShown);
     320             :         }
     321           0 :       } else if (aHints.contains(UpdateCaretsHint::RespectOldAppearance)) {
     322             :         // Do nothing to preserve the appearance of the caret set by the
     323             :         // caller.
     324             :       }
     325           0 :       break;
     326             : 
     327             :     case PositionChangedResult::Invisible:
     328           0 :       mFirstCaret->SetAppearance(Appearance::NormalNotShown);
     329           0 :       break;
     330             :   }
     331             : 
     332           0 :   mFirstCaret->SetSelectionBarEnabled(false);
     333           0 :   mSecondCaret->SetAppearance(Appearance::None);
     334             : 
     335           0 :   if (!aHints.contains(UpdateCaretsHint::DispatchNoEvent) &&
     336           0 :       !mActiveCaret) {
     337           0 :     DispatchCaretStateChangedEvent(CaretChangedReason::Updateposition);
     338             :   }
     339             : }
     340             : 
     341             : void
     342           0 : AccessibleCaretManager::UpdateCaretsForSelectionMode(UpdateCaretsHintSet aHints)
     343             : {
     344           0 :   AC_LOG("%s: selection: %p", __FUNCTION__, GetSelection());
     345             : 
     346           0 :   int32_t startOffset = 0;
     347             :   nsIFrame* startFrame =
     348           0 :     GetFrameForFirstRangeStartOrLastRangeEnd(eDirNext, &startOffset);
     349             : 
     350           0 :   int32_t endOffset = 0;
     351             :   nsIFrame* endFrame =
     352           0 :     GetFrameForFirstRangeStartOrLastRangeEnd(eDirPrevious, &endOffset);
     353             : 
     354           0 :   if (!CompareTreePosition(startFrame, endFrame)) {
     355             :     // XXX: Do we really have to hide carets if this condition isn't satisfied?
     356           0 :     HideCarets();
     357           0 :     return;
     358             :   }
     359             : 
     360             :   auto updateSingleCaret = [aHints](AccessibleCaret* aCaret, nsIFrame* aFrame,
     361           0 :                                     int32_t aOffset) -> PositionChangedResult
     362             :   {
     363           0 :     PositionChangedResult result = aCaret->SetPosition(aFrame, aOffset);
     364           0 :     aCaret->SetSelectionBarEnabled(sSelectionBarEnabled);
     365             : 
     366           0 :     switch (result) {
     367             :       case PositionChangedResult::NotChanged:
     368             :       case PositionChangedResult::Changed:
     369           0 :         if (aHints == UpdateCaretsHint::Default) {
     370           0 :           aCaret->SetAppearance(Appearance::Normal);
     371           0 :         } else if (aHints.contains(UpdateCaretsHint::RespectOldAppearance)) {
     372             :           // Do nothing to preserve the appearance of the caret set by the
     373             :           // caller.
     374             :         }
     375           0 :         break;
     376             : 
     377             :       case PositionChangedResult::Invisible:
     378           0 :         aCaret->SetAppearance(Appearance::NormalNotShown);
     379           0 :         break;
     380             :     }
     381           0 :     return result;
     382           0 :   };
     383             : 
     384             :   PositionChangedResult firstCaretResult =
     385           0 :     updateSingleCaret(mFirstCaret.get(), startFrame, startOffset);
     386             :   PositionChangedResult secondCaretResult =
     387           0 :     updateSingleCaret(mSecondCaret.get(), endFrame, endOffset);
     388             : 
     389           0 :   if (firstCaretResult == PositionChangedResult::Changed ||
     390             :       secondCaretResult == PositionChangedResult::Changed) {
     391             :     // Flush layout to make the carets intersection correct.
     392           0 :     FlushLayout();
     393           0 :     if (IsTerminated()) {
     394           0 :       return;
     395             :     }
     396             :   }
     397             : 
     398           0 :   if (aHints == UpdateCaretsHint::Default) {
     399             :     // Only check for tilt carets with default update hint. Otherwise we might
     400             :     // override the appearance set by the caller.
     401           0 :     if (sCaretsAlwaysTilt) {
     402           0 :       UpdateCaretsForAlwaysTilt(startFrame, endFrame);
     403             :     } else {
     404           0 :       UpdateCaretsForOverlappingTilt();
     405             :     }
     406             :   }
     407             : 
     408           0 :   if (!aHints.contains(UpdateCaretsHint::DispatchNoEvent) &&
     409           0 :       !mActiveCaret) {
     410           0 :     DispatchCaretStateChangedEvent(CaretChangedReason::Updateposition);
     411             :   }
     412             : }
     413             : 
     414             : bool
     415           0 : AccessibleCaretManager::UpdateCaretsForOverlappingTilt()
     416             : {
     417           0 :   if (!mFirstCaret->IsVisuallyVisible() || !mSecondCaret->IsVisuallyVisible()) {
     418           0 :     return false;
     419             :   }
     420             : 
     421           0 :   if (!mFirstCaret->Intersects(*mSecondCaret)) {
     422           0 :     mFirstCaret->SetAppearance(Appearance::Normal);
     423           0 :     mSecondCaret->SetAppearance(Appearance::Normal);
     424           0 :     return false;
     425             :   }
     426             : 
     427           0 :   if (mFirstCaret->LogicalPosition().x <=
     428           0 :       mSecondCaret->LogicalPosition().x) {
     429           0 :     mFirstCaret->SetAppearance(Appearance::Left);
     430           0 :     mSecondCaret->SetAppearance(Appearance::Right);
     431             :   } else {
     432           0 :     mFirstCaret->SetAppearance(Appearance::Right);
     433           0 :     mSecondCaret->SetAppearance(Appearance::Left);
     434             :   }
     435             : 
     436           0 :   return true;
     437             : }
     438             : 
     439             : void
     440           0 : AccessibleCaretManager::UpdateCaretsForAlwaysTilt(nsIFrame* aStartFrame,
     441             :                                                   nsIFrame* aEndFrame)
     442             : {
     443             :   // When a short LTR word in RTL environment is selected, the two carets
     444             :   // tilted inward might be overlapped. Make them tilt outward.
     445           0 :   if (UpdateCaretsForOverlappingTilt()) {
     446           0 :     return;
     447             :   }
     448             : 
     449           0 :   if (mFirstCaret->IsVisuallyVisible()) {
     450           0 :     auto startFrameWritingMode = aStartFrame->GetWritingMode();
     451           0 :     mFirstCaret->SetAppearance(startFrameWritingMode.IsBidiLTR() ?
     452           0 :                                Appearance::Left : Appearance::Right);
     453             :   }
     454           0 :   if (mSecondCaret->IsVisuallyVisible()) {
     455           0 :     auto endFrameWritingMode = aEndFrame->GetWritingMode();
     456           0 :     mSecondCaret->SetAppearance(endFrameWritingMode.IsBidiLTR() ?
     457           0 :                                 Appearance::Right : Appearance::Left);
     458             :   }
     459             : }
     460             : 
     461             : void
     462           0 : AccessibleCaretManager::ProvideHapticFeedback()
     463             : {
     464           0 :   if (sHapticFeedback) {
     465             :     nsCOMPtr<nsIHapticFeedback> haptic =
     466           0 :       do_GetService("@mozilla.org/widget/hapticfeedback;1");
     467           0 :     haptic->PerformSimpleAction(haptic->LongPress);
     468             :   }
     469           0 : }
     470             : 
     471             : nsresult
     472           0 : AccessibleCaretManager::PressCaret(const nsPoint& aPoint,
     473             :                                    EventClassID aEventClass)
     474             : {
     475           0 :   nsresult rv = NS_ERROR_FAILURE;
     476             : 
     477           0 :   MOZ_ASSERT(aEventClass == eMouseEventClass || aEventClass == eTouchEventClass,
     478             :              "Unexpected event class!");
     479             : 
     480             :   using TouchArea = AccessibleCaret::TouchArea;
     481             :   TouchArea touchArea =
     482           0 :     aEventClass == eMouseEventClass ? TouchArea::CaretImage : TouchArea::Full;
     483             : 
     484           0 :   if (mFirstCaret->Contains(aPoint, touchArea)) {
     485           0 :     mActiveCaret = mFirstCaret.get();
     486           0 :     SetSelectionDirection(eDirPrevious);
     487           0 :   } else if (mSecondCaret->Contains(aPoint, touchArea)) {
     488           0 :     mActiveCaret = mSecondCaret.get();
     489           0 :     SetSelectionDirection(eDirNext);
     490             :   }
     491             : 
     492           0 :   if (mActiveCaret) {
     493           0 :     mOffsetYToCaretLogicalPosition =
     494           0 :       mActiveCaret->LogicalPosition().y - aPoint.y;
     495           0 :     SetSelectionDragState(true);
     496           0 :     DispatchCaretStateChangedEvent(CaretChangedReason::Presscaret);
     497           0 :     rv = NS_OK;
     498             :   }
     499             : 
     500           0 :   return rv;
     501             : }
     502             : 
     503             : nsresult
     504           0 : AccessibleCaretManager::DragCaret(const nsPoint& aPoint)
     505             : {
     506           0 :   MOZ_ASSERT(mActiveCaret);
     507           0 :   MOZ_ASSERT(GetCaretMode() != CaretMode::None);
     508             : 
     509           0 :   nsPoint point(aPoint.x, aPoint.y + mOffsetYToCaretLogicalPosition);
     510           0 :   DragCaretInternal(point);
     511           0 :   UpdateCarets();
     512           0 :   return NS_OK;
     513             : }
     514             : 
     515             : nsresult
     516           0 : AccessibleCaretManager::ReleaseCaret()
     517             : {
     518           0 :   MOZ_ASSERT(mActiveCaret);
     519             : 
     520           0 :   mActiveCaret = nullptr;
     521           0 :   SetSelectionDragState(false);
     522           0 :   DispatchCaretStateChangedEvent(CaretChangedReason::Releasecaret);
     523           0 :   return NS_OK;
     524             : }
     525             : 
     526             : nsresult
     527           0 : AccessibleCaretManager::TapCaret(const nsPoint& aPoint)
     528             : {
     529           0 :   MOZ_ASSERT(GetCaretMode() != CaretMode::None);
     530             : 
     531           0 :   nsresult rv = NS_ERROR_FAILURE;
     532             : 
     533           0 :   if (GetCaretMode() == CaretMode::Cursor) {
     534           0 :     DispatchCaretStateChangedEvent(CaretChangedReason::Taponcaret);
     535           0 :     rv = NS_OK;
     536             :   }
     537             : 
     538           0 :   return rv;
     539             : }
     540             : 
     541             : nsresult
     542           0 : AccessibleCaretManager::SelectWordOrShortcut(const nsPoint& aPoint)
     543             : {
     544           0 :   auto UpdateCaretsWithHapticFeedback = [this] {
     545           0 :     UpdateCarets();
     546           0 :     ProvideHapticFeedback();
     547           0 :   };
     548             : 
     549             :   // If the long-tap is landing on a pre-existing selection, don't replace
     550             :   // it with a new one. Instead just return and let the context menu pop up
     551             :   // on the pre-existing selection.
     552           0 :   if (GetCaretMode() == CaretMode::Selection &&
     553           0 :       GetSelection()->ContainsPoint(aPoint)) {
     554           0 :     AC_LOG("%s: UpdateCarets() for current selection", __FUNCTION__);
     555           0 :     UpdateCaretsWithHapticFeedback();
     556           0 :     return NS_OK;
     557             :   }
     558             : 
     559           0 :   if (!mPresShell) {
     560           0 :     return NS_ERROR_UNEXPECTED;
     561             :   }
     562             : 
     563           0 :   nsIFrame* rootFrame = mPresShell->GetRootFrame();
     564           0 :   if (!rootFrame) {
     565           0 :     return NS_ERROR_NOT_AVAILABLE;
     566             :   }
     567             : 
     568             :   // Find the frame under point.
     569             :   AutoWeakFrame ptFrame = nsLayoutUtils::GetFrameForPoint(rootFrame, aPoint,
     570           0 :     nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC);
     571           0 :   if (!ptFrame.IsAlive()) {
     572           0 :     return NS_ERROR_FAILURE;
     573             :   }
     574             : 
     575           0 :   nsIFrame* focusableFrame = GetFocusableFrame(ptFrame);
     576             : 
     577             : #ifdef DEBUG_FRAME_DUMP
     578           0 :   AC_LOG("%s: Found %s under (%d, %d)", __FUNCTION__, ptFrame->ListTag().get(),
     579             :          aPoint.x, aPoint.y);
     580           0 :   AC_LOG("%s: Found %s focusable", __FUNCTION__,
     581             :          focusableFrame ? focusableFrame->ListTag().get() : "no frame");
     582             : #endif
     583             : 
     584             :   // Get ptInFrame here so that we don't need to check whether rootFrame is
     585             :   // alive later. Note that if ptFrame is being moved by
     586             :   // IMEStateManager::NotifyIME() or ChangeFocusToOrClearOldFocus() below,
     587             :   // something under the original point will be selected, which may not be the
     588             :   // original text the user wants to select.
     589           0 :   nsPoint ptInFrame = aPoint;
     590           0 :   nsLayoutUtils::TransformPoint(rootFrame, ptFrame, ptInFrame);
     591             : 
     592             :   // Firstly check long press on an empty editable content.
     593           0 :   Element* newFocusEditingHost = GetEditingHostForFrame(ptFrame);
     594           0 :   if (focusableFrame && newFocusEditingHost &&
     595           0 :       !HasNonEmptyTextContent(newFocusEditingHost)) {
     596           0 :     ChangeFocusToOrClearOldFocus(focusableFrame);
     597             : 
     598           0 :     if (sCaretShownWhenLongTappingOnEmptyContent) {
     599           0 :       mFirstCaret->SetAppearance(Appearance::Normal);
     600             :     }
     601             :     // We need to update carets to get correct information before dispatching
     602             :     // CaretStateChangedEvent.
     603           0 :     UpdateCaretsWithHapticFeedback();
     604           0 :     DispatchCaretStateChangedEvent(CaretChangedReason::Longpressonemptycontent);
     605           0 :     return NS_OK;
     606             :   }
     607             : 
     608           0 :   bool selectable = ptFrame->IsSelectable(nullptr);
     609             : 
     610             : #ifdef DEBUG_FRAME_DUMP
     611           0 :   AC_LOG("%s: %s %s selectable.", __FUNCTION__, ptFrame->ListTag().get(),
     612             :          selectable ? "is" : "is NOT");
     613             : #endif
     614             : 
     615           0 :   if (!selectable) {
     616           0 :     return NS_ERROR_FAILURE;
     617             :   }
     618             : 
     619             :   // Commit the composition string of the old editable focus element (if there
     620             :   // is any) before changing the focus.
     621           0 :   IMEStateManager::NotifyIME(widget::REQUEST_TO_COMMIT_COMPOSITION,
     622           0 :                              mPresShell->GetPresContext());
     623           0 :   if (!ptFrame.IsAlive()) {
     624             :     // Cannot continue because ptFrame died.
     625           0 :     return NS_ERROR_FAILURE;
     626             :   }
     627             : 
     628             :   // ptFrame is selectable. Now change the focus.
     629           0 :   ChangeFocusToOrClearOldFocus(focusableFrame);
     630           0 :   if (!ptFrame.IsAlive()) {
     631             :     // Cannot continue because ptFrame died.
     632           0 :     return NS_ERROR_FAILURE;
     633             :   }
     634             : 
     635             :   // Then try select a word under point.
     636           0 :   nsresult rv = SelectWord(ptFrame, ptInFrame);
     637           0 :   UpdateCaretsWithHapticFeedback();
     638             : 
     639           0 :   return rv;
     640             : }
     641             : 
     642             : void
     643           0 : AccessibleCaretManager::OnScrollStart()
     644             : {
     645           0 :   AC_LOG("%s", __FUNCTION__);
     646             : 
     647           0 :   mIsScrollStarted = true;
     648             : 
     649           0 :   if (!sCaretsAlwaysShowWhenScrolling) {
     650             :     // Backup the appearance so that we can restore them after the scrolling
     651             :     // ends.
     652           0 :     mFirstCaretAppearanceOnScrollStart = mFirstCaret->GetAppearance();
     653           0 :     mSecondCaretAppearanceOnScrollStart = mSecondCaret->GetAppearance();
     654           0 :     HideCarets();
     655           0 :     return;
     656             :   }
     657             : 
     658           0 :   if (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible()) {
     659             :     // Dispatch the event only if one of the carets is logically visible like in
     660             :     // HideCarets().
     661           0 :     DispatchCaretStateChangedEvent(CaretChangedReason::Scroll);
     662             :   }
     663             : }
     664             : 
     665             : void
     666           0 : AccessibleCaretManager::OnScrollEnd()
     667             : {
     668           0 :   if (mLastUpdateCaretMode != GetCaretMode()) {
     669           0 :     return;
     670             :   }
     671             : 
     672           0 :   mIsScrollStarted = false;
     673             : 
     674           0 :   if (!sCaretsAlwaysShowWhenScrolling) {
     675             :     // Restore the appearance which is saved before the scrolling is started.
     676           0 :     mFirstCaret->SetAppearance(mFirstCaretAppearanceOnScrollStart);
     677           0 :     mSecondCaret->SetAppearance(mSecondCaretAppearanceOnScrollStart);
     678             :   }
     679             : 
     680           0 :   if (GetCaretMode() == CaretMode::Cursor) {
     681           0 :     if (!mFirstCaret->IsLogicallyVisible()) {
     682             :       // If the caret is hidden (Appearance::None) due to blur, no
     683             :       // need to update it.
     684           0 :       return;
     685             :     }
     686             :   }
     687             : 
     688             :   // For mouse input we don't want to show the carets.
     689           0 :   if (sHideCaretsForMouseInput &&
     690           0 :       mLastInputSource == nsIDOMMouseEvent::MOZ_SOURCE_MOUSE) {
     691           0 :     AC_LOG("%s: HideCarets()", __FUNCTION__);
     692           0 :     HideCarets();
     693           0 :     return;
     694             :   }
     695             : 
     696           0 :   AC_LOG("%s: UpdateCarets()", __FUNCTION__);
     697           0 :   UpdateCarets();
     698             : }
     699             : 
     700             : void
     701           0 : AccessibleCaretManager::OnScrollPositionChanged()
     702             : {
     703           0 :   if (mLastUpdateCaretMode != GetCaretMode()) {
     704           0 :     return;
     705             :   }
     706             : 
     707           0 :   if (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible()) {
     708           0 :     if (mIsScrollStarted) {
     709             :       // We don't want extra CaretStateChangedEvents dispatched when user is
     710             :       // scrolling the page.
     711           0 :       AC_LOG("%s: UpdateCarets(RespectOldAppearance | DispatchNoEvent)",
     712             :              __FUNCTION__);
     713           0 :       UpdateCarets({ UpdateCaretsHint::RespectOldAppearance,
     714           0 :                      UpdateCaretsHint::DispatchNoEvent });
     715             :     } else {
     716           0 :       AC_LOG("%s: UpdateCarets(RespectOldAppearance)", __FUNCTION__);
     717           0 :       UpdateCarets(UpdateCaretsHint::RespectOldAppearance);
     718             :     }
     719             :   }
     720             : }
     721             : 
     722             : void
     723           0 : AccessibleCaretManager::OnReflow()
     724             : {
     725           0 :   if (mLastUpdateCaretMode != GetCaretMode()) {
     726           0 :     return;
     727             :   }
     728             : 
     729           0 :   if (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible()) {
     730           0 :     AC_LOG("%s: UpdateCarets(RespectOldAppearance)", __FUNCTION__);
     731           0 :     UpdateCarets(UpdateCaretsHint::RespectOldAppearance);
     732             :   }
     733             : }
     734             : 
     735             : void
     736           0 : AccessibleCaretManager::OnBlur()
     737             : {
     738           0 :   AC_LOG("%s: HideCarets()", __FUNCTION__);
     739           0 :   HideCarets();
     740           0 : }
     741             : 
     742             : void
     743           0 : AccessibleCaretManager::OnKeyboardEvent()
     744             : {
     745           0 :   if (GetCaretMode() == CaretMode::Cursor) {
     746           0 :     AC_LOG("%s: HideCarets()", __FUNCTION__);
     747           0 :     HideCarets();
     748             :   }
     749           0 : }
     750             : 
     751             : void
     752           0 : AccessibleCaretManager::OnFrameReconstruction()
     753             : {
     754           0 :   mFirstCaret->EnsureApzAware();
     755           0 :   mSecondCaret->EnsureApzAware();
     756           0 : }
     757             : 
     758             : void
     759           0 : AccessibleCaretManager::SetLastInputSource(uint16_t aInputSource)
     760             : {
     761           0 :   mLastInputSource = aInputSource;
     762           0 : }
     763             : 
     764             : Selection*
     765           0 : AccessibleCaretManager::GetSelection() const
     766             : {
     767           0 :   RefPtr<nsFrameSelection> fs = GetFrameSelection();
     768           0 :   if (!fs) {
     769           0 :     return nullptr;
     770             :   }
     771           0 :   return fs->GetSelection(SelectionType::eNormal);
     772             : }
     773             : 
     774             : already_AddRefed<nsFrameSelection>
     775           0 : AccessibleCaretManager::GetFrameSelection() const
     776             : {
     777           0 :   if (!mPresShell) {
     778           0 :     return nullptr;
     779             :   }
     780             : 
     781           0 :   nsFocusManager* fm = nsFocusManager::GetFocusManager();
     782           0 :   MOZ_ASSERT(fm);
     783             : 
     784           0 :   nsIContent* focusedContent = fm->GetFocusedContent();
     785           0 :   if (focusedContent) {
     786           0 :     nsIFrame* focusFrame = focusedContent->GetPrimaryFrame();
     787           0 :     if (!focusFrame) {
     788           0 :       return nullptr;
     789             :     }
     790             : 
     791             :     // Prevent us from touching the nsFrameSelection associated with other
     792             :     // PresShell.
     793           0 :     RefPtr<nsFrameSelection> fs = focusFrame->GetFrameSelection();
     794           0 :     if (!fs || fs->GetShell() != mPresShell) {
     795           0 :       return nullptr;
     796             :     }
     797             : 
     798           0 :     return fs.forget();
     799             :   } else {
     800             :     // For non-editable content
     801           0 :     return mPresShell->FrameSelection();
     802             :   }
     803             : }
     804             : 
     805             : nsAutoString
     806           0 : AccessibleCaretManager::StringifiedSelection() const
     807             : {
     808           0 :   nsAutoString str;
     809           0 :   Selection* selection = GetSelection();
     810           0 :   if (selection) {
     811           0 :     selection->Stringify(str);
     812             :   }
     813           0 :   return str;
     814             : }
     815             : 
     816             : Element*
     817           0 : AccessibleCaretManager::GetEditingHostForFrame(nsIFrame* aFrame) const
     818             : {
     819           0 :   if (!aFrame) {
     820           0 :     return nullptr;
     821             :   }
     822             : 
     823           0 :   auto content = aFrame->GetContent();
     824           0 :   if (!content) {
     825           0 :     return nullptr;
     826             :   }
     827             : 
     828           0 :   return content->GetEditingHost();
     829             : }
     830             : 
     831             : 
     832             : AccessibleCaretManager::CaretMode
     833           0 : AccessibleCaretManager::GetCaretMode() const
     834             : {
     835           0 :   Selection* selection = GetSelection();
     836           0 :   if (!selection) {
     837           0 :     return CaretMode::None;
     838             :   }
     839             : 
     840           0 :   uint32_t rangeCount = selection->RangeCount();
     841           0 :   if (rangeCount <= 0) {
     842           0 :     return CaretMode::None;
     843             :   }
     844             : 
     845           0 :   if (selection->IsCollapsed()) {
     846           0 :     return CaretMode::Cursor;
     847             :   }
     848             : 
     849           0 :   return CaretMode::Selection;
     850             : }
     851             : 
     852             : nsIFrame*
     853           0 : AccessibleCaretManager::GetFocusableFrame(nsIFrame* aFrame) const
     854             : {
     855             :   // This implementation is similar to EventStateManager::PostHandleEvent().
     856             :   // Look for the nearest enclosing focusable frame.
     857           0 :   nsIFrame* focusableFrame = aFrame;
     858           0 :   while (focusableFrame) {
     859           0 :     if (focusableFrame->IsFocusable(nullptr, true)) {
     860           0 :       break;
     861             :     }
     862           0 :     focusableFrame = focusableFrame->GetParent();
     863             :   }
     864           0 :   return focusableFrame;
     865             : }
     866             : 
     867             : void
     868           0 : AccessibleCaretManager::ChangeFocusToOrClearOldFocus(nsIFrame* aFrame) const
     869             : {
     870           0 :   nsFocusManager* fm = nsFocusManager::GetFocusManager();
     871           0 :   MOZ_ASSERT(fm);
     872             : 
     873           0 :   if (aFrame) {
     874           0 :     nsIContent* focusableContent = aFrame->GetContent();
     875           0 :     MOZ_ASSERT(focusableContent, "Focusable frame must have content!");
     876           0 :     nsCOMPtr<nsIDOMElement> focusableElement = do_QueryInterface(focusableContent);
     877           0 :     fm->SetFocus(focusableElement, nsIFocusManager::FLAG_BYMOUSE);
     878             :   } else {
     879           0 :     nsPIDOMWindowOuter* win = mPresShell->GetDocument()->GetWindow();
     880           0 :     if (win) {
     881           0 :       fm->ClearFocus(win);
     882           0 :       fm->SetFocusedWindow(win);
     883             :     }
     884             :   }
     885           0 : }
     886             : 
     887             : nsresult
     888           0 : AccessibleCaretManager::SelectWord(nsIFrame* aFrame, const nsPoint& aPoint) const
     889             : {
     890           0 :   SetSelectionDragState(true);
     891           0 :   nsFrame* frame = static_cast<nsFrame*>(aFrame);
     892           0 :   nsresult rs = frame->SelectByTypeAtPoint(mPresShell->GetPresContext(), aPoint,
     893           0 :                                            eSelectWord, eSelectWord, 0);
     894             : 
     895           0 :   SetSelectionDragState(false);
     896           0 :   ClearMaintainedSelection();
     897             : 
     898             :   // Smart-select phone numbers if possible.
     899           0 :   if (sExtendSelectionForPhoneNumber) {
     900           0 :     SelectMoreIfPhoneNumber();
     901             :   }
     902             : 
     903           0 :   return rs;
     904             : }
     905             : 
     906             : void
     907           0 : AccessibleCaretManager::SetSelectionDragState(bool aState) const
     908             : {
     909           0 :   RefPtr<nsFrameSelection> fs = GetFrameSelection();
     910           0 :   if (fs) {
     911           0 :     fs->SetDragState(aState);
     912             :   }
     913             : 
     914             :   // Pin Fennecs DynamicToolbarAnimator in place before/after dragging,
     915             :   // to avoid co-incident screen scrolling.
     916             :   #ifdef MOZ_WIDGET_ANDROID
     917             :     nsIDocument* doc = mPresShell->GetDocument();
     918             :     MOZ_ASSERT(doc);
     919             :     nsIWidget* widget = nsContentUtils::WidgetForDocument(doc);
     920             :     static_cast<nsWindow*>(widget)->SetSelectionDragState(aState);
     921             :   #endif
     922           0 : }
     923             : 
     924             : bool
     925           0 : AccessibleCaretManager::IsPhoneNumber(nsAString& aCandidate) const
     926             : {
     927           0 :   RefPtr<nsIDocument> doc = mPresShell->GetDocument();
     928             :   nsAutoString phoneNumberRegex(
     929           0 :     NS_LITERAL_STRING("(^\\+)?[0-9 ,\\-.()*#pw]{1,30}$"));
     930           0 :   return nsContentUtils::IsPatternMatching(aCandidate, phoneNumberRegex, doc);
     931             : }
     932             : 
     933             : void
     934           0 : AccessibleCaretManager::SelectMoreIfPhoneNumber() const
     935             : {
     936           0 :   nsAutoString selectedText = StringifiedSelection();
     937             : 
     938           0 :   if (IsPhoneNumber(selectedText)) {
     939           0 :     SetSelectionDirection(eDirNext);
     940           0 :     ExtendPhoneNumberSelection(NS_LITERAL_STRING("forward"));
     941             : 
     942           0 :     SetSelectionDirection(eDirPrevious);
     943           0 :     ExtendPhoneNumberSelection(NS_LITERAL_STRING("backward"));
     944             : 
     945           0 :     SetSelectionDirection(eDirNext);
     946             :   }
     947           0 : }
     948             : 
     949             : void
     950           0 : AccessibleCaretManager::ExtendPhoneNumberSelection(const nsAString& aDirection) const
     951             : {
     952           0 :   if (!mPresShell) {
     953           0 :     return;
     954             :   }
     955             : 
     956             :   // Extend the phone number selection until we find a boundary.
     957           0 :   RefPtr<Selection> selection = GetSelection();
     958             : 
     959           0 :   while (selection) {
     960           0 :     const nsRange* anchorFocusRange = selection->GetAnchorFocusRange();
     961           0 :     if (!anchorFocusRange) {
     962           0 :       return;
     963             :     }
     964             : 
     965             :     // Backup the anchor focus range since both anchor node and focus node might
     966             :     // be changed after calling Selection::Modify().
     967           0 :     RefPtr<nsRange> oldAnchorFocusRange = anchorFocusRange->CloneRange();
     968             : 
     969             :     // Save current focus node, focus offset and the selected text so that
     970             :     // we can compare them with the modified ones later.
     971           0 :     nsINode* oldFocusNode = selection->GetFocusNode();
     972           0 :     uint32_t oldFocusOffset = selection->FocusOffset();
     973           0 :     nsAutoString oldSelectedText = StringifiedSelection();
     974             : 
     975             :     // Extend the selection by one char.
     976           0 :     selection->Modify(NS_LITERAL_STRING("extend"),
     977             :                       aDirection,
     978           0 :                       NS_LITERAL_STRING("character"));
     979           0 :     if (IsTerminated()) {
     980           0 :       return;
     981             :     }
     982             : 
     983             :     // If the selection didn't change, (can't extend further), we're done.
     984           0 :     if (selection->GetFocusNode() == oldFocusNode &&
     985           0 :         selection->FocusOffset() == oldFocusOffset) {
     986           0 :       return;
     987             :     }
     988             : 
     989             :     // If the changed selection isn't a valid phone number, we're done.
     990             :     // Also, if the selection was extended to a new block node, the string
     991             :     // returned by stringify() won't have a new line at the beginning or the
     992             :     // end of the string. Therefore, if either focus node or offset is
     993             :     // changed, but selected text is not changed, we're done, too.
     994           0 :     nsAutoString selectedText = StringifiedSelection();
     995             : 
     996           0 :     if (!IsPhoneNumber(selectedText) || oldSelectedText == selectedText) {
     997             :       // Backout the undesired selection extend, restore the old anchor focus
     998             :       // range before exit.
     999           0 :       selection->SetAnchorFocusToRange(oldAnchorFocusRange);
    1000           0 :       return;
    1001             :     }
    1002             :   }
    1003             : }
    1004             : 
    1005             : void
    1006           0 : AccessibleCaretManager::SetSelectionDirection(nsDirection aDir) const
    1007             : {
    1008           0 :   Selection* selection = GetSelection();
    1009           0 :   if (selection) {
    1010           0 :     selection->AdjustAnchorFocusForMultiRange(aDir);
    1011             :   }
    1012           0 : }
    1013             : 
    1014             : void
    1015           0 : AccessibleCaretManager::ClearMaintainedSelection() const
    1016             : {
    1017             :   // Selection made by double-clicking for example will maintain the original
    1018             :   // word selection. We should clear it so that we can drag caret freely.
    1019           0 :   RefPtr<nsFrameSelection> fs = GetFrameSelection();
    1020           0 :   if (fs) {
    1021           0 :     fs->MaintainSelection(eSelectNoAmount);
    1022             :   }
    1023           0 : }
    1024             : 
    1025             : void
    1026           0 : AccessibleCaretManager::FlushLayout() const
    1027             : {
    1028           0 :   if (mPresShell) {
    1029           0 :     mPresShell->FlushPendingNotifications(FlushType::Layout);
    1030             :   }
    1031           0 : }
    1032             : 
    1033             : nsIFrame*
    1034           0 : AccessibleCaretManager::GetFrameForFirstRangeStartOrLastRangeEnd(
    1035             :   nsDirection aDirection,
    1036             :   int32_t* aOutOffset,
    1037             :   nsIContent** aOutContent,
    1038             :   int32_t* aOutContentOffset) const
    1039             : {
    1040           0 :   if (!mPresShell) {
    1041           0 :     return nullptr;
    1042             :   }
    1043             : 
    1044           0 :   MOZ_ASSERT(GetCaretMode() == CaretMode::Selection);
    1045           0 :   MOZ_ASSERT(aOutOffset, "aOutOffset shouldn't be nullptr!");
    1046             : 
    1047           0 :   nsRange* range = nullptr;
    1048           0 :   RefPtr<nsINode> startNode;
    1049           0 :   RefPtr<nsINode> endNode;
    1050           0 :   int32_t nodeOffset = 0;
    1051             :   CaretAssociationHint hint;
    1052             : 
    1053           0 :   RefPtr<Selection> selection = GetSelection();
    1054           0 :   bool findInFirstRangeStart = aDirection == eDirNext;
    1055             : 
    1056           0 :   if (findInFirstRangeStart) {
    1057           0 :     range = selection->GetRangeAt(0);
    1058           0 :     startNode = range->GetStartContainer();
    1059           0 :     endNode = range->GetEndContainer();
    1060           0 :     nodeOffset = range->StartOffset();
    1061           0 :     hint = CARET_ASSOCIATE_AFTER;
    1062             :   } else {
    1063           0 :     range = selection->GetRangeAt(selection->RangeCount() - 1);
    1064           0 :     startNode = range->GetEndContainer();
    1065           0 :     endNode = range->GetStartContainer();
    1066           0 :     nodeOffset = range->EndOffset();
    1067           0 :     hint = CARET_ASSOCIATE_BEFORE;
    1068             :   }
    1069             : 
    1070           0 :   nsCOMPtr<nsIContent> startContent = do_QueryInterface(startNode);
    1071           0 :   RefPtr<nsFrameSelection> fs = GetFrameSelection();
    1072             :   nsIFrame* startFrame =
    1073           0 :     fs->GetFrameForNodeOffset(startContent, nodeOffset, hint, aOutOffset);
    1074             : 
    1075           0 :   if (!startFrame) {
    1076           0 :     ErrorResult err;
    1077           0 :     RefPtr<TreeWalker> walker = mPresShell->GetDocument()->CreateTreeWalker(
    1078           0 :       *startNode, nsIDOMNodeFilter::SHOW_ALL, nullptr, err);
    1079             : 
    1080           0 :     if (!walker) {
    1081           0 :       return nullptr;
    1082             :     }
    1083             : 
    1084           0 :     startFrame = startContent ? startContent->GetPrimaryFrame() : nullptr;
    1085           0 :     while (!startFrame && startNode != endNode) {
    1086           0 :       startNode = findInFirstRangeStart ? walker->NextNode(err)
    1087           0 :                                         : walker->PreviousNode(err);
    1088             : 
    1089           0 :       if (!startNode) {
    1090           0 :         break;
    1091             :       }
    1092             : 
    1093           0 :       startContent = startNode->AsContent();
    1094           0 :       startFrame = startContent ? startContent->GetPrimaryFrame() : nullptr;
    1095             :     }
    1096             : 
    1097             :     // We are walking among the nodes in the content tree, so the node offset
    1098             :     // relative to startNode should be set to 0.
    1099           0 :     nodeOffset = 0;
    1100           0 :     *aOutOffset = 0;
    1101             :   }
    1102             : 
    1103           0 :   if (startFrame) {
    1104           0 :     if (aOutContent) {
    1105           0 :       startContent.forget(aOutContent);
    1106             :     }
    1107           0 :     if (aOutContentOffset) {
    1108           0 :       *aOutContentOffset = nodeOffset;
    1109             :     }
    1110             :   }
    1111             : 
    1112           0 :   return startFrame;
    1113             : }
    1114             : 
    1115             : bool
    1116           0 : AccessibleCaretManager::RestrictCaretDraggingOffsets(
    1117             :   nsIFrame::ContentOffsets& aOffsets)
    1118             : {
    1119           0 :   if (!mPresShell) {
    1120           0 :     return false;
    1121             :   }
    1122             : 
    1123           0 :   MOZ_ASSERT(GetCaretMode() == CaretMode::Selection);
    1124             : 
    1125           0 :   nsDirection dir = mActiveCaret == mFirstCaret.get() ? eDirPrevious : eDirNext;
    1126           0 :   int32_t offset = 0;
    1127           0 :   nsCOMPtr<nsIContent> content;
    1128           0 :   int32_t contentOffset = 0;
    1129             :   nsIFrame* frame =
    1130           0 :     GetFrameForFirstRangeStartOrLastRangeEnd(dir, &offset,
    1131           0 :                                              getter_AddRefs(content),
    1132           0 :                                              &contentOffset);
    1133             : 
    1134           0 :   if (!frame) {
    1135           0 :     return false;
    1136             :   }
    1137             : 
    1138             : 
    1139             :   // Compare the active caret's new position (aOffsets) to the inactive caret's
    1140             :   // position.
    1141             :   int32_t cmpToInactiveCaretPos =
    1142           0 :     nsContentUtils::ComparePoints(aOffsets.content, aOffsets.StartOffset(),
    1143           0 :                                   content, contentOffset);
    1144             : 
    1145             :   // Move one character (in the direction of dir) from the inactive caret's
    1146             :   // position. This is the limit for the active caret's new position.
    1147           0 :   nsPeekOffsetStruct limit(eSelectCluster, dir, offset, nsPoint(0, 0), true, true,
    1148           0 :                            false, false, false);
    1149           0 :   nsresult rv = frame->PeekOffset(&limit);
    1150           0 :   if (NS_FAILED(rv)) {
    1151           0 :     limit.mResultContent = content;
    1152           0 :     limit.mContentOffset = contentOffset;
    1153             :   }
    1154             : 
    1155             :   // Compare the active caret's new position (aOffsets) to the limit.
    1156             :   int32_t cmpToLimit =
    1157           0 :     nsContentUtils::ComparePoints(aOffsets.content, aOffsets.StartOffset(),
    1158           0 :                                   limit.mResultContent, limit.mContentOffset);
    1159             : 
    1160           0 :   auto SetOffsetsToLimit = [&aOffsets, &limit] () {
    1161           0 :     aOffsets.content = limit.mResultContent;
    1162           0 :     aOffsets.offset = limit.mContentOffset;
    1163           0 :     aOffsets.secondaryOffset = limit.mContentOffset;
    1164           0 :   };
    1165             : 
    1166           0 :   if (!sCaretsAllowDraggingAcrossOtherCaret) {
    1167           0 :     if ((mActiveCaret == mFirstCaret.get() && cmpToLimit == 1) ||
    1168           0 :         (mActiveCaret == mSecondCaret.get() && cmpToLimit == -1)) {
    1169             :       // The active caret's position is past the limit, which we don't allow
    1170             :       // here. So set it to the limit, resulting in one character being
    1171             :       // selected.
    1172           0 :       SetOffsetsToLimit();
    1173             :     }
    1174             :   } else {
    1175           0 :     switch (cmpToInactiveCaretPos) {
    1176             :       case 0:
    1177             :         // The active caret's position is the same as the position of the
    1178             :         // inactive caret. So set it to the limit to prevent the selection from
    1179             :         // being collapsed, resulting in one character being selected.
    1180           0 :         SetOffsetsToLimit();
    1181           0 :         break;
    1182             :       case 1:
    1183           0 :         if (mActiveCaret == mFirstCaret.get()) {
    1184             :           // First caret was moved across the second caret. After making change
    1185             :           // to the selection, the user will drag the second caret.
    1186           0 :           mActiveCaret = mSecondCaret.get();
    1187             :         }
    1188           0 :         break;
    1189             :       case -1:
    1190           0 :         if (mActiveCaret == mSecondCaret.get()) {
    1191             :           // Second caret was moved across the first caret. After making change
    1192             :           // to the selection, the user will drag the first caret.
    1193           0 :           mActiveCaret = mFirstCaret.get();
    1194             :         }
    1195           0 :         break;
    1196             :     }
    1197             :   }
    1198             : 
    1199           0 :   return true;
    1200             : }
    1201             : 
    1202             : bool
    1203           0 : AccessibleCaretManager::CompareTreePosition(nsIFrame* aStartFrame,
    1204             :                                             nsIFrame* aEndFrame) const
    1205             : {
    1206           0 :   return (aStartFrame && aEndFrame &&
    1207           0 :           nsLayoutUtils::CompareTreePosition(aStartFrame, aEndFrame) <= 0);
    1208             : }
    1209             : 
    1210             : nsresult
    1211           0 : AccessibleCaretManager::DragCaretInternal(const nsPoint& aPoint)
    1212             : {
    1213           0 :   if (!mPresShell) {
    1214           0 :     return NS_ERROR_NULL_POINTER;
    1215             :   }
    1216             : 
    1217           0 :   nsIFrame* rootFrame = mPresShell->GetRootFrame();
    1218           0 :   if (!rootFrame) {
    1219           0 :     return NS_ERROR_NULL_POINTER;
    1220             :   }
    1221             : 
    1222           0 :   nsPoint point = AdjustDragBoundary(aPoint);
    1223             : 
    1224             :   // Find out which content we point to
    1225           0 :   nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
    1226             :     rootFrame, point,
    1227           0 :     nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC);
    1228           0 :   if (!ptFrame) {
    1229           0 :     return NS_ERROR_FAILURE;
    1230             :   }
    1231             : 
    1232           0 :   RefPtr<nsFrameSelection> fs = GetFrameSelection();
    1233           0 :   if (!fs) {
    1234           0 :     return NS_ERROR_NULL_POINTER;
    1235             :   }
    1236             : 
    1237             :   nsresult result;
    1238           0 :   nsIFrame* newFrame = nullptr;
    1239           0 :   nsPoint newPoint;
    1240           0 :   nsPoint ptInFrame = point;
    1241           0 :   nsLayoutUtils::TransformPoint(rootFrame, ptFrame, ptInFrame);
    1242           0 :   result = fs->ConstrainFrameAndPointToAnchorSubtree(ptFrame, ptInFrame,
    1243           0 :                                                      &newFrame, newPoint);
    1244           0 :   if (NS_FAILED(result) || !newFrame) {
    1245           0 :     return NS_ERROR_FAILURE;
    1246             :   }
    1247             : 
    1248           0 :   if (!newFrame->IsSelectable(nullptr)) {
    1249           0 :     return NS_ERROR_FAILURE;
    1250             :   }
    1251             : 
    1252             :   nsIFrame::ContentOffsets offsets =
    1253           0 :     newFrame->GetContentOffsetsFromPoint(newPoint);
    1254           0 :   if (offsets.IsNull()) {
    1255           0 :     return NS_ERROR_FAILURE;
    1256             :   }
    1257             : 
    1258           0 :   Selection* selection = GetSelection();
    1259           0 :   if (!selection) {
    1260           0 :     return NS_ERROR_NULL_POINTER;
    1261             :   }
    1262             : 
    1263           0 :   if (GetCaretMode() == CaretMode::Selection &&
    1264           0 :       !RestrictCaretDraggingOffsets(offsets)) {
    1265           0 :     return NS_ERROR_FAILURE;
    1266             :   }
    1267             : 
    1268           0 :   ClearMaintainedSelection();
    1269             : 
    1270           0 :   nsIFrame* anchorFrame = nullptr;
    1271           0 :   selection->GetPrimaryFrameForAnchorNode(&anchorFrame);
    1272             : 
    1273             :   nsIFrame* scrollable =
    1274           0 :     nsLayoutUtils::GetClosestFrameOfType(anchorFrame, LayoutFrameType::Scroll);
    1275           0 :   AutoWeakFrame weakScrollable = scrollable;
    1276           0 :   fs->HandleClick(offsets.content, offsets.StartOffset(), offsets.EndOffset(),
    1277           0 :                   GetCaretMode() == CaretMode::Selection, false,
    1278           0 :                   offsets.associate);
    1279           0 :   if (!weakScrollable.IsAlive()) {
    1280           0 :     return NS_OK;
    1281             :   }
    1282             : 
    1283             :   // Scroll scrolled frame.
    1284           0 :   nsIScrollableFrame* saf = do_QueryFrame(scrollable);
    1285           0 :   nsIFrame* capturingFrame = saf->GetScrolledFrame();
    1286           0 :   nsPoint ptInScrolled = point;
    1287           0 :   nsLayoutUtils::TransformPoint(rootFrame, capturingFrame, ptInScrolled);
    1288           0 :   fs->StartAutoScrollTimer(capturingFrame, ptInScrolled, kAutoScrollTimerDelay);
    1289           0 :   return NS_OK;
    1290             : }
    1291             : 
    1292             : nsRect
    1293           0 : AccessibleCaretManager::GetAllChildFrameRectsUnion(nsIFrame* aFrame) const
    1294             : {
    1295           0 :   nsRect unionRect;
    1296             : 
    1297             :   // Drill through scroll frames, we don't want to include scrollbar child
    1298             :   // frames below.
    1299           0 :   for (nsIFrame* frame = aFrame->GetContentInsertionFrame();
    1300           0 :        frame;
    1301           0 :        frame = frame->GetNextContinuation()) {
    1302           0 :     nsRect frameRect;
    1303             : 
    1304           0 :     for (nsIFrame::ChildListIterator lists(frame); !lists.IsDone(); lists.Next()) {
    1305             :       // Loop all children to union their scrollable overflow rect.
    1306           0 :       for (nsIFrame* child : lists.CurrentList()) {
    1307           0 :         nsRect childRect = child->GetScrollableOverflowRectRelativeToSelf();
    1308           0 :         nsLayoutUtils::TransformRect(child, frame, childRect);
    1309             : 
    1310             :         // A TextFrame containing only '\n' has positive height and width 0, or
    1311             :         // positive width and height 0 if it's vertical. Need to use UnionEdges
    1312             :         // to add its rect. BRFrame rect should be non-empty.
    1313           0 :         if (childRect.IsEmpty()) {
    1314           0 :           frameRect = frameRect.UnionEdges(childRect);
    1315             :         } else {
    1316           0 :           frameRect = frameRect.Union(childRect);
    1317             :         }
    1318             :       }
    1319             :     }
    1320             : 
    1321           0 :     MOZ_ASSERT(!frameRect.IsEmpty(),
    1322             :                "Editable frames should have at least one BRFrame child to make "
    1323             :                "frameRect non-empty!");
    1324           0 :     if (frame != aFrame) {
    1325           0 :       nsLayoutUtils::TransformRect(frame, aFrame, frameRect);
    1326             :     }
    1327           0 :     unionRect = unionRect.Union(frameRect);
    1328             :   }
    1329             : 
    1330           0 :   return unionRect;
    1331             : }
    1332             : 
    1333             : nsPoint
    1334           0 : AccessibleCaretManager::AdjustDragBoundary(const nsPoint& aPoint) const
    1335             : {
    1336           0 :   nsPoint adjustedPoint = aPoint;
    1337             : 
    1338           0 :   int32_t focusOffset = 0;
    1339             :   nsIFrame* focusFrame =
    1340           0 :     nsCaret::GetFrameAndOffset(GetSelection(), nullptr, 0, &focusOffset);
    1341           0 :   Element* editingHost = GetEditingHostForFrame(focusFrame);
    1342             : 
    1343           0 :   if (editingHost) {
    1344           0 :     nsIFrame* editingHostFrame = editingHost->GetPrimaryFrame();
    1345           0 :     if (editingHostFrame) {
    1346           0 :       nsRect boundary = GetAllChildFrameRectsUnion(editingHostFrame);
    1347           0 :       nsLayoutUtils::TransformRect(editingHostFrame, mPresShell->GetRootFrame(),
    1348           0 :                                    boundary);
    1349             : 
    1350             :       // Shrink the rect to make sure we never hit the boundary.
    1351           0 :       boundary.Deflate(kBoundaryAppUnits);
    1352             : 
    1353           0 :       adjustedPoint = boundary.ClampPoint(adjustedPoint);
    1354             :     }
    1355             :   }
    1356             : 
    1357           0 :   if (GetCaretMode() == CaretMode::Selection &&
    1358           0 :       !sCaretsAllowDraggingAcrossOtherCaret) {
    1359             :     // Bug 1068474: Adjust the Y-coordinate so that the carets won't be in tilt
    1360             :     // mode when a caret is being dragged surpass the other caret.
    1361             :     //
    1362             :     // For example, when dragging the second caret, the horizontal boundary (lower
    1363             :     // bound) of its Y-coordinate is the logical position of the first caret.
    1364             :     // Likewise, when dragging the first caret, the horizontal boundary (upper
    1365             :     // bound) of its Y-coordinate is the logical position of the second caret.
    1366           0 :     if (mActiveCaret == mFirstCaret.get()) {
    1367           0 :       nscoord dragDownBoundaryY = mSecondCaret->LogicalPosition().y;
    1368           0 :       if (dragDownBoundaryY > 0 && adjustedPoint.y > dragDownBoundaryY) {
    1369           0 :         adjustedPoint.y = dragDownBoundaryY;
    1370             :       }
    1371             :     } else {
    1372           0 :       nscoord dragUpBoundaryY = mFirstCaret->LogicalPosition().y;
    1373           0 :       if (adjustedPoint.y < dragUpBoundaryY) {
    1374           0 :         adjustedPoint.y = dragUpBoundaryY;
    1375             :       }
    1376             :     }
    1377             :   }
    1378             : 
    1379           0 :   return adjustedPoint;
    1380             : }
    1381             : 
    1382             : void
    1383           0 : AccessibleCaretManager::DispatchCaretStateChangedEvent(CaretChangedReason aReason) const
    1384             : {
    1385           0 :   if (!mPresShell) {
    1386           0 :     return;
    1387             :   }
    1388             : 
    1389           0 :   FlushLayout();
    1390           0 :   if (IsTerminated()) {
    1391           0 :     return;
    1392             :   }
    1393             : 
    1394           0 :   Selection* sel = GetSelection();
    1395           0 :   if (!sel) {
    1396           0 :     return;
    1397             :   }
    1398             : 
    1399           0 :   nsIDocument* doc = mPresShell->GetDocument();
    1400           0 :   MOZ_ASSERT(doc);
    1401             : 
    1402           0 :   CaretStateChangedEventInit init;
    1403           0 :   init.mBubbles = true;
    1404             : 
    1405           0 :   const nsRange* range = sel->GetAnchorFocusRange();
    1406           0 :   nsINode* commonAncestorNode = nullptr;
    1407           0 :   if (range) {
    1408           0 :     commonAncestorNode = range->GetCommonAncestor();
    1409             :   }
    1410             : 
    1411           0 :   if (!commonAncestorNode) {
    1412           0 :     commonAncestorNode = sel->GetFrameSelection()->GetAncestorLimiter();
    1413             :   }
    1414             : 
    1415           0 :   RefPtr<DOMRect> domRect = new DOMRect(ToSupports(doc));
    1416           0 :   nsRect rect = nsLayoutUtils::GetSelectionBoundingRect(sel);
    1417             : 
    1418           0 :   nsIFrame* commonAncestorFrame = nullptr;
    1419           0 :   nsIFrame* rootFrame = mPresShell->GetRootFrame();
    1420             : 
    1421           0 :   if (commonAncestorNode && commonAncestorNode->IsContent()) {
    1422           0 :     commonAncestorFrame = commonAncestorNode->AsContent()->GetPrimaryFrame();
    1423             :   }
    1424             : 
    1425           0 :   if (commonAncestorFrame && rootFrame) {
    1426           0 :     nsLayoutUtils::TransformRect(rootFrame, commonAncestorFrame, rect);
    1427             :     nsRect clampedRect = nsLayoutUtils::ClampRectToScrollFrames(commonAncestorFrame,
    1428           0 :                                                                 rect);
    1429           0 :     nsLayoutUtils::TransformRect(commonAncestorFrame, rootFrame, clampedRect);
    1430           0 :     domRect->SetLayoutRect(clampedRect);
    1431           0 :     init.mSelectionVisible = !clampedRect.IsEmpty();
    1432             :   } else {
    1433           0 :     domRect->SetLayoutRect(rect);
    1434           0 :     init.mSelectionVisible = true;
    1435             :   }
    1436             : 
    1437             :   // Send isEditable info w/ event detail. This info can help determine
    1438             :   // whether to show cut command on selection dialog or not.
    1439           0 :   init.mSelectionEditable = commonAncestorFrame &&
    1440           0 :     GetEditingHostForFrame(commonAncestorFrame);
    1441             : 
    1442           0 :   init.mBoundingClientRect = domRect;
    1443           0 :   init.mReason = aReason;
    1444           0 :   init.mCollapsed = sel->IsCollapsed();
    1445           0 :   init.mCaretVisible = mFirstCaret->IsLogicallyVisible() ||
    1446           0 :                        mSecondCaret->IsLogicallyVisible();
    1447           0 :   init.mCaretVisuallyVisible = mFirstCaret->IsVisuallyVisible() ||
    1448           0 :                                 mSecondCaret->IsVisuallyVisible();
    1449           0 :   sel->Stringify(init.mSelectedTextContent);
    1450             : 
    1451             :   RefPtr<CaretStateChangedEvent> event =
    1452           0 :     CaretStateChangedEvent::Constructor(doc, NS_LITERAL_STRING("mozcaretstatechanged"), init);
    1453             : 
    1454           0 :   event->SetTrusted(true);
    1455           0 :   event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
    1456             : 
    1457           0 :   AC_LOG("%s: reason %" PRIu32 ", collapsed %d, caretVisible %" PRIu32, __FUNCTION__,
    1458             :          static_cast<uint32_t>(init.mReason), init.mCollapsed,
    1459             :          static_cast<uint32_t>(init.mCaretVisible));
    1460             : 
    1461           0 :   (new AsyncEventDispatcher(doc, event))->RunDOMEventWhenSafe();
    1462             : }
    1463             : 
    1464             : } // namespace mozilla

Generated by: LCOV version 1.13