Bug 1293483 - Keep the carets hidden when the user is using a mouse to modify the selection. r?TYLin draft
authorKartikaya Gupta <kgupta@mozilla.com>
Fri, 30 Sep 2016 16:13:28 -0400
changeset 419730 9219a53aa751f021babf455cd8b147348e1aebdc
parent 419507 5ffed033557e5b6f9694123f1948f867f913ede3
child 532640 09cfc5f94d4a3cc217469ef8fa26bf4a591ade2d
push id31002
push userkgupta@mozilla.com
push dateFri, 30 Sep 2016 20:13:59 +0000
reviewersTYLin
bugs1293483
milestone52.0a1
Bug 1293483 - Keep the carets hidden when the user is using a mouse to modify the selection. r?TYLin MozReview-Commit-ID: zOIORfhYO
layout/base/AccessibleCaretEventHub.cpp
layout/base/AccessibleCaretManager.cpp
layout/base/AccessibleCaretManager.h
layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py
modules/libpref/init/all.js
--- a/layout/base/AccessibleCaretEventHub.cpp
+++ b/layout/base/AccessibleCaretEventHub.cpp
@@ -509,16 +509,26 @@ AccessibleCaretEventHub::HandleMouseEven
   if (aEvent->button != WidgetMouseEvent::eLeftButton) {
     return rv;
   }
 
   int32_t id =
     (mActiveTouchId == kInvalidTouchId ? kDefaultTouchId : mActiveTouchId);
   nsPoint point = GetMouseEventPosition(aEvent);
 
+  if (aEvent->mMessage == eMouseDown ||
+      aEvent->mMessage == eMouseUp ||
+      aEvent->mMessage == eMouseClick ||
+      aEvent->mMessage == eMouseDoubleClick ||
+      aEvent->mMessage == eMouseLongTap) {
+    // Don't reset the source on mouse movement since that can
+    // happen anytime, even randomly during a touch sequence
+    mManager->SetLastInputSource(aEvent->inputSource);
+  }
+
   switch (aEvent->mMessage) {
     case eMouseDown:
       AC_LOGV("Before eMouseDown, state: %s", mState->Name());
       rv = mState->OnPress(this, point, id, eMouseEventClass);
       AC_LOGV("After eMouseDown, state: %s, consume: %d", mState->Name(), rv);
       break;
 
     case eMouseMove:
@@ -557,16 +567,18 @@ AccessibleCaretEventHub::HandleTouchEven
 
   nsEventStatus rv = nsEventStatus_eIgnore;
 
   int32_t id =
     (mActiveTouchId == kInvalidTouchId ? aEvent->mTouches[0]->Identifier()
                                        : mActiveTouchId);
   nsPoint point = GetTouchEventPosition(aEvent, id);
 
+  mManager->SetLastInputSource(nsIDOMMouseEvent::MOZ_SOURCE_TOUCH);
+
   switch (aEvent->mMessage) {
     case eTouchStart:
       AC_LOGV("Before eTouchStart, state: %s", mState->Name());
       rv = mState->OnPress(this, point, id, eTouchEventClass);
       AC_LOGV("After eTouchStart, state: %s, consume: %d", mState->Name(), rv);
       break;
 
     case eTouchMove:
@@ -591,16 +603,18 @@ AccessibleCaretEventHub::HandleTouchEven
   }
 
   return rv;
 }
 
 nsEventStatus
 AccessibleCaretEventHub::HandleKeyboardEvent(WidgetKeyboardEvent* aEvent)
 {
+  mManager->SetLastInputSource(nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD);
+
   switch (aEvent->mMessage) {
     case eKeyUp:
       AC_LOGV("eKeyUp, state: %s", mState->Name());
       mManager->OnKeyboardEvent();
       break;
 
     case eKeyDown:
       AC_LOGV("eKeyDown, state: %s", mState->Name());
--- a/layout/base/AccessibleCaretManager.cpp
+++ b/layout/base/AccessibleCaretManager.cpp
@@ -78,16 +78,18 @@ AccessibleCaretManager::sCaretsAlwaysSho
 /*static*/ bool
 AccessibleCaretManager::sCaretsScriptUpdates = false;
 /*static*/ bool
 AccessibleCaretManager::sCaretsAllowDraggingAcrossOtherCaret = true;
 /*static*/ bool
 AccessibleCaretManager::sHapticFeedback = false;
 /*static*/ bool
 AccessibleCaretManager::sExtendSelectionForPhoneNumber = false;
+/*static*/ bool
+AccessibleCaretManager::sHideCaretsForMouseInput = true;
 
 AccessibleCaretManager::AccessibleCaretManager(nsIPresShell* aPresShell)
   : mPresShell(aPresShell)
 {
   if (!mPresShell) {
     return;
   }
 
@@ -109,16 +111,18 @@ AccessibleCaretManager::AccessibleCaretM
     Preferences::AddBoolVarCache(&sCaretsScriptUpdates,
       "layout.accessiblecaret.allow_script_change_updates");
     Preferences::AddBoolVarCache(&sCaretsAllowDraggingAcrossOtherCaret,
       "layout.accessiblecaret.allow_dragging_across_other_caret", true);
     Preferences::AddBoolVarCache(&sHapticFeedback,
                                  "layout.accessiblecaret.hapticfeedback");
     Preferences::AddBoolVarCache(&sExtendSelectionForPhoneNumber,
       "layout.accessiblecaret.extend_selection_for_phone_number");
+    Preferences::AddBoolVarCache(&sHideCaretsForMouseInput,
+      "layout.accessiblecaret.hide_carets_for_mouse_input");
     addedPrefs = true;
   }
 }
 
 AccessibleCaretManager::~AccessibleCaretManager()
 {
 }
 
@@ -180,16 +184,23 @@ AccessibleCaretManager::OnSelectionChang
 
   // Range will collapse after cutting or copying text.
   if (aReason & (nsISelectionListener::COLLAPSETOSTART_REASON |
                  nsISelectionListener::COLLAPSETOEND_REASON)) {
     HideCarets();
     return NS_OK;
   }
 
+  // For mouse input we don't want to show the carets.
+  if (sHideCaretsForMouseInput &&
+      mLastInputSource == nsIDOMMouseEvent::MOZ_SOURCE_MOUSE) {
+    HideCarets();
+    return NS_OK;
+  }
+
   UpdateCarets();
   return NS_OK;
 }
 
 void
 AccessibleCaretManager::HideCarets()
 {
   if (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible()) {
@@ -667,16 +678,24 @@ AccessibleCaretManager::OnScrollEnd()
   if (GetCaretMode() == CaretMode::Cursor) {
     if (!mFirstCaret->IsLogicallyVisible()) {
       // If the caret is hidden (Appearance::None) due to timeout or blur, no
       // need to update it.
       return;
     }
   }
 
+  // For mouse input we don't want to show the carets.
+  if (sHideCaretsForMouseInput &&
+      mLastInputSource == nsIDOMMouseEvent::MOZ_SOURCE_MOUSE) {
+    AC_LOG("%s: HideCarets()", __FUNCTION__);
+    HideCarets();
+    return;
+  }
+
   AC_LOG("%s: UpdateCarets()", __FUNCTION__);
   UpdateCarets();
 }
 
 void
 AccessibleCaretManager::OnScrollPositionChanged()
 {
   if (mLastUpdateCaretMode != GetCaretMode()) {
@@ -720,16 +739,22 @@ AccessibleCaretManager::OnKeyboardEvent(
 
 void
 AccessibleCaretManager::OnFrameReconstruction()
 {
   mFirstCaret->EnsureApzAware();
   mSecondCaret->EnsureApzAware();
 }
 
+void
+AccessibleCaretManager::SetLastInputSource(uint16_t aInputSource)
+{
+  mLastInputSource = aInputSource;
+}
+
 Selection*
 AccessibleCaretManager::GetSelection() const
 {
   RefPtr<nsFrameSelection> fs = GetFrameSelection();
   if (!fs) {
     return nullptr;
   }
   return fs->GetSelection(SelectionType::eNormal);
--- a/layout/base/AccessibleCaretManager.h
+++ b/layout/base/AccessibleCaretManager.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef AccessibleCaretManager_h
 #define AccessibleCaretManager_h
 
 #include "AccessibleCaret.h"
 #include "nsCOMPtr.h"
 #include "nsCoord.h"
+#include "nsIDOMMouseEvent.h"
 #include "nsIFrame.h"
 #include "nsISelectionListener.h"
 #include "mozilla/RefPtr.h"
 #include "nsWeakReference.h"
 #include "mozilla/dom/CaretStateChangedEvent.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
@@ -97,16 +98,20 @@ public:
                                       int16_t aReason);
   // Handle key event.
   virtual void OnKeyboardEvent();
 
   // The canvas frame holding the accessible caret anonymous content elements
   // was reconstructed, resulting in the content elements getting cloned.
   virtual void OnFrameReconstruction();
 
+  // Update the manager with the last input source that was observed. This
+  // is used in part to determine if the carets should be shown or hidden.
+  void SetLastInputSource(uint16_t aInputSource);
+
 protected:
   // This enum representing the number of AccessibleCarets on the screen.
   enum class CaretMode : uint8_t {
     // No caret on the screen.
     None,
 
     // One caret, i.e. the selection is collapsed.
     Cursor,
@@ -271,16 +276,22 @@ protected:
 
   // Store the appearance of the carets when calling OnScrollStart() so that it
   // can be restored in OnScrollEnd().
   AccessibleCaret::Appearance mFirstCaretAppearanceOnScrollStart =
                                  AccessibleCaret::Appearance::None;
   AccessibleCaret::Appearance mSecondCaretAppearanceOnScrollStart =
                                  AccessibleCaret::Appearance::None;
 
+  // The last input source that the event hub saw. We use this to decide whether
+  // or not show the carets when the selection is updated, as we want to hide
+  // the carets for mouse-triggered selection changes but show them for other
+  // input types such as touch.
+  uint16_t mLastInputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
+
   static const int32_t kAutoScrollTimerDelay = 30;
 
   // Clicking on the boundary of input or textarea will move the caret to the
   // front or end of the content. To avoid this, we need to deflate the content
   // boundary by 61 app units, which is 1 pixel + 1 app unit as defined in
   // AppUnit.h.
   static const int32_t kBoundaryAppUnits = 61;
 
@@ -314,16 +325,20 @@ protected:
 
   // Preference to allow one caret to be dragged across the other caret without
   // any limitation. When set to false, one caret cannot be dragged across the
   // other one.
   static bool sCaretsAllowDraggingAcrossOtherCaret;
 
   // AccessibleCaret pref for haptic feedback behaviour on longPress.
   static bool sHapticFeedback;
+
+  // Preference to keep carets hidden when the selection is being manipulated
+  // by mouse input (as opposed to touch/pen/etc.).
+  static bool sHideCaretsForMouseInput;
 };
 
 std::ostream& operator<<(std::ostream& aStream,
                          const AccessibleCaretManager::CaretMode& aCaretMode);
 
 std::ostream& operator<<(std::ostream& aStream,
                          const AccessibleCaretManager::UpdateCaretsHint& aResult);
 
--- a/layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py
+++ b/layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py
@@ -30,19 +30,21 @@ class AccessibleCaretCursorModeTestCase(
     # Test html files.
     _cursor_html = 'test_carets_cursor.html'
 
     def setUp(self):
         # Code to execute before every test is running.
         super(AccessibleCaretCursorModeTestCase, self).setUp()
         self.caret_tested_pref = 'layout.accessiblecaret.enabled'
         self.caret_timeout_ms_pref = 'layout.accessiblecaret.timeout_ms'
+        self.hide_carets_for_mouse = 'layout.accessiblecaret.hide_carets_for_mouse_input'
         self.prefs = {
             self.caret_tested_pref: True,
             self.caret_timeout_ms_pref: 0,
+            self.hide_carets_for_mouse: False,
         }
         self.marionette.set_prefs(self.prefs)
         self.actions = Actions(self.marionette)
 
     def open_test_html(self, test_html):
         self.marionette.navigate(self.marionette.absolute_url(test_html))
 
     @parameterized(_input_id, el_id=_input_id)
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5248,16 +5248,19 @@ pref("layout.accessiblecaret.allow_scrip
 pref("layout.accessiblecaret.allow_dragging_across_other_caret", true);
 
 // Optionally provide haptic feedback on longPress selection events.
 pref("layout.accessiblecaret.hapticfeedback", false);
 
 // Smart phone-number selection on long-press is not enabled by default.
 pref("layout.accessiblecaret.extend_selection_for_phone_number", false);
 
+// Keep the accessible carets hidden when the user is using mouse input.
+pref("layout.accessiblecaret.hide_carets_for_mouse_input", true);
+
 // Wakelock is disabled by default.
 pref("dom.wakelock.enabled", false);
 
 // The URL of the Firefox Accounts auth server backend
 pref("identity.fxaccounts.auth.uri", "https://api.accounts.firefox.com/v1");
 
 // disable mozsample size for now
 pref("image.mozsamplesize.enabled", false);