Bug 708379 - Haptic buzz when tapping on clickable elements [r=blassey]
authorMark Finkle <mfinkle@mozilla.com>
Thu, 08 Dec 2011 15:42:55 -0500
changeset 83954 b4293d6a23ffed9ced33ccec71240bd1fee6bd72
parent 83953 411178fecf6a71b364bc1a9015dcd60a11fd45e1
child 83955 ad40be58fa5b96d0daa2b2c5b09d97c6c6ebbd12
push id519
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 00:38:35 +0000
treeherdermozilla-beta@788ea1ef610b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersblassey
bugs708379
milestone11.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 708379 - Haptic buzz when tapping on clickable elements [r=blassey]
mobile/android/base/GeckoAppShell.java
mobile/android/chrome/content/browser.js
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -952,17 +952,21 @@ public class GeckoAppShell
     }
 
     public static String showFilePicker(String aFilters) {
         return GeckoApp.mAppContext.
             showFilePicker(getMimeTypeFromExtensions(aFilters));
     }
 
     public static void performHapticFeedback(boolean aIsLongPress) {
-        // TODO
+        LayerController layerController = GeckoApp.mAppContext.getLayerController();
+        LayerView layerView = layerController.getView();
+        layerView.performHapticFeedback(aIsLongPress ?
+                                        HapticFeedbackConstants.LONG_PRESS :
+                                        HapticFeedbackConstants.VIRTUAL_KEY);
     }
 
     private static Vibrator vibrator() {
         LayerController layerController = GeckoApp.mAppContext.getLayerController();
         LayerView layerView = layerController.getView();
 
         return (Vibrator) layerView.getContext().getSystemService(Context.VIBRATOR_SERVICE);
     }
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -43,18 +43,23 @@ let Cu = Components.utils;
 let Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm")
 Cu.import("resource://gre/modules/AddonManager.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "URIFixup",
   "@mozilla.org/docshell/urifixup;1", "nsIURIFixup");
+
+XPCOMUtils.defineLazyServiceGetter(this, "Haptic",
+  "@mozilla.org/widget/hapticfeedback;1", "nsIHapticFeedback");
+
 XPCOMUtils.defineLazyServiceGetter(this, "DOMUtils",
   "@mozilla.org/inspector/dom-utils;1", "inIDOMUtils");
+
 const kStateActive = 0x00000001; // :active pseudoclass for elements
 
 // TODO: Take into account ppi in these units?
 
 // The ratio of velocity that is retained every ms.
 const kPanDeceleration = 0.999;
 
 // The number of ms to consider events over for a swipe gesture.
@@ -1571,55 +1576,50 @@ var BrowserEventHandler = {
       if (this._scrollableElement == null)
         return;
 
       // If this is the first scroll event and we can't scroll in the direction
       // the user wanted, and neither can any non-root sub-frame, cancel the
       // override so that Java can handle panning the main document.
       let data = JSON.parse(aData);
       if (this._firstScrollEvent) {
-        while (this._scrollableElement != null &&
-               !this._elementCanScroll(this._scrollableElement, data.x, data.y))
+        while (this._scrollableElement != null && !this._elementCanScroll(this._scrollableElement, data.x, data.y))
           this._scrollableElement = this._findScrollableElement(this._scrollableElement, false);
 
         let doc = BrowserApp.selectedBrowser.contentDocument;
-        if (this._scrollableElement == doc.body ||
-            this._scrollableElement == doc.documentElement) {
+        if (this._scrollableElement == doc.body || this._scrollableElement == doc.documentElement) {
           sendMessageToJava({ gecko: { type: "Panning:CancelOverride" } });
           return;
         }
 
         this._firstScrollEvent = false;
       }
 
       // Scroll the scrollable element
       this._scrollElementBy(this._scrollableElement, data.x, data.y);
       sendMessageToJava({ gecko: { type: "Gesture:ScrollAck" } });
     } else if (aTopic == "Gesture:CancelTouch") {
       this._cancelTapHighlight();
     } else if (aTopic == "Gesture:ShowPress") {
       let data = JSON.parse(aData);
-      let closest = ElementTouchHelper.elementFromPoint(BrowserApp.selectedBrowser.contentWindow,
-                                                        data.x, data.y);
+      let closest = ElementTouchHelper.elementFromPoint(BrowserApp.selectedBrowser.contentWindow, data.x, data.y);
       if (!closest)
-        closest = ElementTouchHelper.anyElementFromPoint(BrowserApp.selectedBrowser.contentWindow,
-                                                        data.x, data.y);
+        closest = ElementTouchHelper.anyElementFromPoint(BrowserApp.selectedBrowser.contentWindow, data.x, data.y);
       if (closest) {
         this._doTapHighlight(closest);
 
         // If we've pressed a scrollable element, let Java know that we may
         // want to override the scroll behaviour (for document sub-frames)
         this._scrollableElement = this._findScrollableElement(closest, true);
         this._firstScrollEvent = true;
 
         if (this._scrollableElement != null) {
           // Discard if it's the top-level scrollable, we let Java handle this
           let doc = BrowserApp.selectedBrowser.contentDocument;
-          if (this._scrollableElement != doc.body &&
-              this._scrollableElement != doc.documentElement)
+          if (this._scrollableElement != doc.body && this._scrollableElement != doc.documentElement)
             sendMessageToJava({ gecko: { type: "Panning:Override" } });
         }
       }
     } else if (aTopic == "Gesture:SingleTap") {
       let element = this._highlightElement;
       if (element && !FormAssistant.handleClick(element)) {
         let data = JSON.parse(aData);
         [data.x, data.y] = ElementTouchHelper.toScreenCoords(element.ownerDocument.defaultView, data.x, data.y);
@@ -1683,16 +1683,19 @@ var BrowserEventHandler = {
 
   _firstScrollEvent: false,
 
   _scrollableElement: null,
 
   _highlightElement: null,
 
   _doTapHighlight: function _doTapHighlight(aElement) {
+    if (ElementTouchHelper.isElementClickable(aElement))
+      Haptic.performSimpleAction(Haptic.LongPress);
+
     DOMUtils.setContentState(aElement, kStateActive);
     this._highlightElement = aElement;
   },
 
   _cancelTapHighlight: function _cancelTapHighlight() {
     DOMUtils.setContentState(BrowserApp.selectedBrowser.contentWindow.document.documentElement, kStateActive);
     this._highlightElement = null;
   },
@@ -1761,18 +1764,17 @@ var BrowserEventHandler = {
           scrollable = true;
           break;
         }
       } else {
         checkElem = true;
       }
 
       // Propagate up iFrames
-      if (!elem.parentNode && elem.documentElement &&
-          elem.documentElement.ownerDocument)
+      if (!elem.parentNode && elem.documentElement && elem.documentElement.ownerDocument)
         elem = elem.documentElement.ownerDocument.defaultView.frameElement;
       else
         elem = elem.parentNode;
     }
 
     if (!scrollable)
       return null;
 
@@ -1915,29 +1917,29 @@ const ElementTouchHelper = {
 
     let dpiRatio = this.dpiRatio;
 
     let target = aWindowUtils.elementFromPoint(aX, aY,
                                                true,   /* ignore root scroll frame*/
                                                false); /* don't flush layout */
 
     // if this element is clickable we return quickly
-    if (this._isElementClickable(target))
+    if (this.isElementClickable(target))
       return target;
 
     let target = null;
     let nodes = aWindowUtils.nodesFromRect(aX, aY, this.radius.top * dpiRatio,
                                                    this.radius.right * dpiRatio,
                                                    this.radius.bottom * dpiRatio,
                                                    this.radius.left * dpiRatio, true, false);
 
     let threshold = Number.POSITIVE_INFINITY;
     for (let i = 0; i < nodes.length; i++) {
       let current = nodes[i];
-      if (!current.mozMatchesSelector || !this._isElementClickable(current))
+      if (!current.mozMatchesSelector || !this.isElementClickable(current))
         continue;
 
       let rect = current.getBoundingClientRect();
       let distance = this._computeDistanceFromRect(aX, aY, rect);
 
       // increase a little bit the weight for already visited items
       if (current && current.mozMatchesSelector("*:visited"))
         distance *= (this.weight.visited / 100);
@@ -1946,17 +1948,17 @@ const ElementTouchHelper = {
         target = current;
         threshold = distance;
       }
     }
 
     return target;
   },
 
-  _isElementClickable: function _isElementClickable(aElement) {
+  isElementClickable: function isElementClickable(aElement) {
     const selector = "a,:link,:visited,[role=button],button,input,select,textarea,label";
     for (let elem = aElement; elem; elem = elem.parentNode) {
       if (this._hasMouseListener(elem))
         return true;
       if (elem.mozMatchesSelector && elem.mozMatchesSelector(selector))
         return true;
     }
     return false;