Bug 988143 - Enable Gecko Touch in Fennec, TouchCaret mods, r=ehsan
authorMark Capella <markcapella@twcny.rr.com>
Thu, 14 May 2015 22:06:13 -0400
changeset 243976 0f3dc51af81675c0cc43a54671a19d497d933bb2
parent 243975 952cc0d3db08a103dd41f1399b64b4e43c742c84
child 243977 35cab4c3e81d05d783d1b0623f63b1c0b207b63e
push id28761
push usercbook@mozilla.com
push dateFri, 15 May 2015 14:50:10 +0000
treeherdermozilla-central@c0e709a5baca [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs988143
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 988143 - Enable Gecko Touch in Fennec, TouchCaret mods, r=ehsan * * * Bug 988143 - Enable Gecko Touch in Fennec, TouchCaret mods, r=ehsan
layout/base/TouchCaret.cpp
layout/base/TouchCaret.h
mobile/android/app/mobile.js
modules/libpref/init/all.js
--- a/layout/base/TouchCaret.cpp
+++ b/layout/base/TouchCaret.cpp
@@ -55,38 +55,47 @@ static const char* kTouchCaretLogModuleN
 // front/end of the content. To advoid this, we need to deflate the content
 // boundary by 61 app units (1 pixel + 1 app unit).
 static const int32_t kBoundaryAppUnits = 61;
 
 NS_IMPL_ISUPPORTS(TouchCaret, nsISelectionListener)
 
 /*static*/ int32_t TouchCaret::sTouchCaretInflateSize = 0;
 /*static*/ int32_t TouchCaret::sTouchCaretExpirationTime = 0;
+/*static*/ bool TouchCaret::sCaretManagesAndroidActionbar = false;
+/*static*/ bool TouchCaret::sTouchcaretExtendedvisibility = false;
+
+/*static*/ uint32_t TouchCaret::sActionBarViewCount = 0;
 
 TouchCaret::TouchCaret(nsIPresShell* aPresShell)
   : mState(TOUCHCARET_NONE),
     mActiveTouchId(-1),
     mCaretCenterToDownPointOffsetY(0),
     mVisible(false),
-    mIsValidTap(false)
+    mIsValidTap(false),
+    mActionBarViewID(0)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!gTouchCaretLog) {
     gTouchCaretLog = PR_NewLogModule(kTouchCaretLogModuleName);
   }
 
   TOUCHCARET_LOG("Constructor, PresShell=%p", aPresShell);
 
   static bool addedTouchCaretPref = false;
   if (!addedTouchCaretPref) {
     Preferences::AddIntVarCache(&sTouchCaretInflateSize,
                                 "touchcaret.inflatesize.threshold");
     Preferences::AddIntVarCache(&sTouchCaretExpirationTime,
                                 "touchcaret.expiration.time");
+    Preferences::AddBoolVarCache(&sCaretManagesAndroidActionbar,
+                                 "caret.manages-android-actionbar");
+    Preferences::AddBoolVarCache(&sTouchcaretExtendedvisibility,
+                                 "touchcaret.extendedvisibility");
     addedTouchCaretPref = true;
   }
 
   // The presshell owns us, so no addref.
   mPresShell = do_GetWeakReference(aPresShell);
   MOZ_ASSERT(mPresShell, "Hey, pres shell should support weak refs");
 }
 
@@ -169,16 +178,48 @@ TouchCaret::SetVisibility(bool aVisible)
   ErrorResult err;
   touchCaretElement->ClassList()->Toggle(NS_LITERAL_STRING("hidden"),
                                          dom::Optional<bool>(!mVisible),
                                          err);
   TOUCHCARET_LOG("Set visibility %s", (mVisible ? "shown" : "hidden"));
 
   // Set touch caret expiration time.
   mVisible ? LaunchExpirationTimer() : CancelExpirationTimer();
+
+  // If after a TouchCaret visibility change we become hidden, ensure
+  // the Android ActionBar handler is notified to close the current view.
+  if (!mVisible && sCaretManagesAndroidActionbar) {
+    UpdateAndroidActionBarVisibility(false, mActionBarViewID);
+  }
+}
+
+/**
+ * Open or close the Android TextSelection ActionBar, based on visibility.
+ * Each time we're called to open the actionbar, we increment / assign a
+ * unique view ID and return it to the caller. The ID is returned on calls
+ * to close the actionbar to ensure we don't close the shared view if it
+ * was already force closed by a subsequent callers open request.
+ */
+/* static */void
+TouchCaret::UpdateAndroidActionBarVisibility(bool aVisibility, uint32_t& aViewID)
+{
+  // Are we openning a new view?
+  if (aVisibility) {
+    // Assign a new view ID.
+    aViewID = ++sActionBarViewCount;
+  }
+
+  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+  if (os) {
+    nsString topic = (aVisibility) ?
+      NS_LITERAL_STRING("ActionBar:OpenNew") : NS_LITERAL_STRING("ActionBar:Close");
+    nsAutoString viewCount;
+    viewCount.AppendInt(aViewID);
+    os->NotifyObservers(nullptr, NS_ConvertUTF16toUTF8(topic).get(), viewCount.get());
+  }
 }
 
 nsRect
 TouchCaret::GetTouchFrameRect()
 {
   nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
   if (!presShell) {
     return nsRect();
@@ -357,16 +398,35 @@ TouchCaret::NotifySelectionChanged(nsIDO
   // Also hide touch caret when gecko or javascript collapse the selection.
   if (aReason & nsISelectionListener::KEYPRESS_REASON ||
       aReason & nsISelectionListener::COLLAPSETOSTART_REASON ||
       aReason & nsISelectionListener::COLLAPSETOEND_REASON) {
     TOUCHCARET_LOG("KEYPRESS_REASON");
     SetVisibility(false);
   } else {
     SyncVisibilityWithCaret();
+
+    // Is the TouchCaret visible and we're showing/hiding the actionbar?
+    if (mVisible && sCaretManagesAndroidActionbar) {
+      // A selection change due to touch tap opens the actionbar.
+      if (aReason & nsISelectionListener::MOUSEUP_REASON) {
+        UpdateAndroidActionBarVisibility(true, mActionBarViewID);
+      } else {
+        // Update the ActionBar state for caret-specific selection changes.
+        // Ignore transient selection composition changes that occur while
+        // the TouchCaret is also visible.
+        bool isCollapsed;
+        if (NS_SUCCEEDED(aSel->GetIsCollapsed(&isCollapsed)) && isCollapsed) {
+          nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+          if (os) {
+            os->NotifyObservers(nullptr, "ActionBar:UpdateState", nullptr);
+          }
+        }
+      }
+    }
   }
 
   return NS_OK;
 }
 
 void
 TouchCaret::SyncVisibilityWithCaret()
 {
@@ -442,27 +502,33 @@ TouchCaret::IsDisplayable()
   }
 
   nsRect focusRect;
   nsIFrame* focusFrame = caret->GetGeometry(&focusRect);
   if (!focusFrame) {
     TOUCHCARET_LOG("Focus frame is not valid!");
     return false;
   }
-  if (focusRect.IsEmpty()) {
-    TOUCHCARET_LOG("Focus rect is empty!");
-    return false;
-  }
 
   dom::Element* editingHost = focusFrame->GetContent()->GetEditingHost();
   if (!editingHost) {
     TOUCHCARET_LOG("Cannot get editing host!");
     return false;
   }
 
+  // No further checks required if extended TouchCaret visibility.
+  if (sTouchcaretExtendedvisibility) {
+    return true;
+  }
+
+  if (focusRect.IsEmpty()) {
+    TOUCHCARET_LOG("Focus rect is empty!");
+    return false;
+  }
+
   if (!nsContentUtils::HasNonEmptyTextContent(
          editingHost, nsContentUtils::eRecurseIntoChildren)) {
     TOUCHCARET_LOG("The content is empty!");
     return false;
   }
 
   if (mState != TOUCHCARET_TOUCHDRAG_ACTIVE &&
         !nsLayoutUtils::IsRectVisibleInScrollFrames(focusFrame, focusRect)) {
@@ -832,16 +898,22 @@ TouchCaret::HandleMouseDownEvent(WidgetM
           SetSelectionDragState(true);
           // Cache distence of the event point to the center of touch caret.
           mCaretCenterToDownPointOffsetY = GetCaretYCenterPosition() - point.y;
           // Enter TOUCHCARET_MOUSEDRAG_ACTIVE state and cancel the timer.
           SetState(TOUCHCARET_MOUSEDRAG_ACTIVE);
           CancelExpirationTimer();
           status = nsEventStatus_eConsumeNoDefault;
         } else {
+          // Mousedown events that miss HitTest can be caused by soft-keyboard
+          // auto-suggestions. If extended visibility, update the caret position.
+          if (sTouchcaretExtendedvisibility) {
+            UpdatePositionIfNeeded();
+            break;
+          }
           // Set touch caret invisible if HisTest fails. Bypass event.
           SetVisibility(false);
           status = nsEventStatus_eIgnore;
         }
       } else {
         // Set touch caret invisible if not left button down event.
         SetVisibility(false);
         status = nsEventStatus_eIgnore;
@@ -892,18 +964,24 @@ TouchCaret::HandleTouchDownEvent(WidgetT
             CancelExpirationTimer();
             status = nsEventStatus_eConsumeNoDefault;
             break;
           }
         }
         // No touch is on the touch caret. Set touch caret invisible, and bypass
         // the event.
         if (mActiveTouchId == -1) {
-          SetVisibility(false);
-          status = nsEventStatus_eIgnore;
+          // Check touch caret visibility style.
+          if (sTouchcaretExtendedvisibility) {
+            // Update position on events associated with scroll and pan-zoom.
+            UpdatePositionIfNeeded();
+          } else {
+            SetVisibility(false);
+            status = nsEventStatus_eIgnore;
+          }
         }
       }
       break;
 
     case TOUCHCARET_MOUSEDRAG_ACTIVE:
     case TOUCHCARET_TOUCHDRAG_ACTIVE:
     case TOUCHCARET_TOUCHDRAG_INACTIVE:
       // Consume NS_TOUCH_START event.
--- a/layout/base/TouchCaret.h
+++ b/layout/base/TouchCaret.h
@@ -55,16 +55,21 @@ public:
   /**
    * GetVisibility will get the visibility of the touch caret.
    */
   bool GetVisibility() const
   {
     return mVisible;
   }
 
+  /**
+   * Open or close the Android TextSelection ActionBar based on visibility.
+   */
+  static void UpdateAndroidActionBarVisibility(bool aVisibility, uint32_t& aViewID);
+
 private:
   // Hide default constructor.
   TouchCaret() = delete;
 
   ~TouchCaret();
 
   bool IsDisplayable();
 
@@ -275,15 +280,21 @@ private:
   // Use for detecting single tap on touch caret.
   bool mIsValidTap;
   // Touch caret timer
   nsCOMPtr<nsITimer> mTouchCaretExpirationTimer;
 
   // Preference
   static int32_t sTouchCaretInflateSize;
   static int32_t sTouchCaretExpirationTime;
+  static bool sCaretManagesAndroidActionbar;
+  static bool sTouchcaretExtendedvisibility;
 
   // The auto scroll timer's interval in miliseconds.
   friend class SelectionCarets;
   static const int32_t sAutoScrollTimerDelay = 30;
+
+  // Unique ID of current Mobile ActionBar view.
+  static uint32_t sActionBarViewCount;
+  uint32_t mActionBarViewID;
 };
 } //namespace mozilla
 #endif //mozilla_TouchCaret_h__
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -877,8 +877,23 @@ pref("browser.readinglist.enabled", true
 // Whether to use the unified telemetry behavior, requires a restart.
 pref("toolkit.telemetry.unified", false);
 
 // Selection carets never fall-back to internal LongTap detector.
 pref("selectioncaret.detects.longtap", false);
 
 // Selection carets override caret visibility.
 pref("selectioncaret.visibility.affectscaret", true);
+
+// Turn off touch caret by default.
+pref("touchcaret.enabled", false);
+
+// TouchCaret never auto-hides.
+pref("touchcaret.expiration.time", 0);
+
+// Touch caret stays visible under a wider range of conditions
+// than the default b2g. We can display the caret in empty editables
+// for example, and do not auto-hide until loss of focus.
+pref("touchcaret.extendedvisibility", true);
+
+// The TouchCaret and the SelectionCarets will indicate when the
+// TextSelection actionbar is to be openned or closed.
+pref("caret.manages-android-actionbar", true);
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4558,16 +4558,25 @@ pref("selectioncaret.enabled", false);
 pref("selectioncaret.inflatesize.threshold", 40);
 
 // Selection carets will fall-back to internal LongTap detector.
 pref("selectioncaret.detects.longtap", true);
 
 // Selection carets do not affect caret visibility.
 pref("selectioncaret.visibility.affectscaret", false);
 
+// The Touch caret by default observes the b2g visibility rules, and
+// not the extended Android visibility rules that allow for touchcaret
+// display in empty editable fields, for example.
+pref("touchcaret.extendedvisibility", false);
+
+// Desktop and b2g don't need to open or close the Android
+// TextSelection (Actionbar) utility.
+pref("caret.manages-android-actionbar", false);
+
 // New implementation to unify touch-caret and selection-carets.
 pref("layout.accessiblecaret.enabled", false);
 
 // Timeout in milliseconds to hide the accessiblecaret under cursor mode while
 // no one touches it. Set the value to 0 to disable this feature.
 pref("layout.accessiblecaret.timeout_ms", 3000);
 
 // Wakelock is disabled by default.