Bug 540008 - stop kinetic scrolling when user taps screen [r=vingtetun]
authorMatt Brubeck <mbrubeck@mozilla.com>
Mon, 16 Aug 2010 06:38:28 -0700
changeset 66442 3d135a627fa4fd02700d45207868de9004cbb91f
parent 66441 d5a1eae38759f457f1bdc0e8f41907965d7f1859
child 66443 fbf4a8bf612cd8c4ba42f8c3f990a17f74139917
child 66454 60f6c44808ea5ebf3b376d73621548e3b4d03e29
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvingtetun
bugs540008
Bug 540008 - stop kinetic scrolling when user taps screen [r=vingtetun]
mobile/chrome/content/InputHandler.js
--- a/mobile/chrome/content/InputHandler.js
+++ b/mobile/chrome/content/InputHandler.js
@@ -22,16 +22,17 @@
  *
  * Contributor(s):
  *   Stuart Parmenter <stuart@mozilla.com>
  *   Brad Lassey <blassey@mozilla.com>
  *   Mark Finkle <mfinkle@mozilla.com>
  *   Gavin Sharp <gavin.sharp@gmail.com>
  *   Ben Combee <combee@mozilla.com>
  *   Roy Frostig <rfrostig@mozilla.com>
+ *   Matt Brubeck <mbrubeck@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -52,16 +53,19 @@ const kDoubleClickThreshold = 200;
 const kTapRadius = 25;
 
 // maximum drag distance in pixels while axis locking can still be reverted
 const kAxisLockRevertThreshold = 200;
 
 // Same as NS_EVENT_STATE_ACTIVE from nsIEventStateManager.h
 const kStateActive = 0x00000001;
 
+// threshold in ms for touch and hold to stop kinetic scrolling
+const kKineticBrakesDelay = 50;
+
 /**
  * InputHandler
  *
  * The input handler is an arbiter between the Fennec chrome window inputs and any
  * registered input modules.  It keeps an array of input module objects.  Incoming
  * input events are wrapped in an EventInfo object and passed down to the input modules
  * in the order of the modules array.  Every registed module thus gets called with
  * an EventInfo for each event that the InputHandler is registered to listen for.
@@ -424,20 +428,23 @@ MouseModule.prototype = {
     }
     dragData.reset();
 
     // walk up the DOM tree in search of nearest scrollable ancestor.  nulls are
     // returned if none found.
     let [targetScrollbox, targetScrollInterface]
       = this.getScrollboxFromElement(aEvent.target);
 
-    // stop kinetic panning if targetScrollbox has changed
-    let oldInterface = this._targetScrollInterface;
-    if (this._kinetic.isActive() && targetScrollInterface != oldInterface)
-      this._kinetic.end();
+    if (this._kinetic.isActive()) {
+      let oldInterface = this._targetScrollInterface;
+      if (targetScrollInterface != oldInterface)
+        this._kinetic.end(); // stop right away if targetScrollbox has changed
+      else
+        this._kinetic.brakesApplied(); // otherwise, stop soon
+    }
 
     let targetClicker = this.getClickerFromElement(aEvent.target);
 
     this._targetScrollInterface = targetScrollInterface;
     this._dragger = (targetScrollInterface) ? (targetScrollbox.customDragger || this._defaultDragger)
                                             : null;
     this._clicker = (targetClicker) ? targetClicker.customClicker : null;
 
@@ -981,16 +988,21 @@ function KineticController(aPanBy, aEndC
 
 KineticController.prototype = {
   _reset: function _reset() {
     if (this._timer != null) {
       this._timer.cancel();
       this._timer = null;
     }
 
+    if (this._brakesTimeout) {
+      clearTimeout(this._brakesTimeout);
+      delete this._brakesTimeout;
+    }
+
     this.momentumBuffer = [];
     this._velocity.set(0, 0);
   },
 
   isActive: function isActive() {
     return (this._timer != null);
   },
 
@@ -1130,16 +1142,30 @@ KineticController.prototype = {
 
     if (this.isActive()) {
       // Stop active movement when dragging in other direction.
       if (dx * this._velocity.x < 0 || dy * this._velocity.y < 0)
         this.end();
     }
 
     this.momentumBuffer.push({'t': now, 'dx' : dx, 'dy' : dy});
+
+    if (dx > 0 && dy > 0 && this._brakesTimeout) {
+      clearTimeout(this._brakesTimeout);
+      delete this._brakesTimeout;
+    }
+  },
+
+  /** Stop panning very soon if no more movement is added. */
+  brakesApplied: function brakesApplied() {
+    let self = this;
+    this._brakesTimeout = setTimeout(function() {
+      self.end();
+      delete self._brakesTimeout;
+    }, kKineticBrakesDelay);
   }
 };
 
 
 /**
  * Input module for basic key input.
  */
 function KeyModule(owner, browserViewContainer) {