Bug 1075253 - Consecutive taps should only emit one gesture. r=yzen
authorEitan Isaacson <eitan@monotonous.org>
Mon, 20 Oct 2014 09:54:01 -0700
changeset 211342 30f4aa3c67903205f4a39d018d399b95f0ed7d56
parent 211341 8f34e100ffeae9f08d07a5e293533822c0656d52
child 211343 0970007a8468ab055abad0586855f3c95f684594
push id27673
push userkwierso@gmail.com
push dateTue, 21 Oct 2014 01:57:45 +0000
treeherdermozilla-central@29fbfc1b31aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersyzen
bugs1075253
milestone36.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 1075253 - Consecutive taps should only emit one gesture. r=yzen
accessible/jsat/Gestures.jsm
accessible/tests/mochitest/jsat/dom_helper.js
accessible/tests/mochitest/jsat/gestures.json
accessible/tests/mochitest/jsat/test_gesture_tracker.html
--- a/accessible/jsat/Gestures.jsm
+++ b/accessible/jsat/Gestures.jsm
@@ -2,25 +2,25 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* global Components, GestureSettings, XPCOMUtils, Utils, Promise, Logger */
 /* exported GestureSettings, GestureTracker */
 
 /******************************************************************************
   All gestures have the following pathways when being resolved(v)/rejected(x):
-               Tap -> DoubleTap        (v)
+               Tap -> DoubleTap        (x)
                    -> Dwell            (x)
                    -> Swipe            (x)
 
-        AndroidTap -> TripleTap        (v)
+        AndroidTap -> TripleTap        (x)
                    -> TapHold          (x)
                    -> Swipe            (x)
 
-         DoubleTap -> TripleTap        (v)
+         DoubleTap -> TripleTap        (x)
                    -> TapHold          (x)
                    -> Explore          (x)
 
          TripleTap -> DoubleTapHold    (x)
                    -> Explore          (x)
 
              Dwell -> DwellEnd         (v)
 
@@ -355,29 +355,32 @@ Gesture.prototype = {
     // reject this gesture promise.
     return GestureSettings.maxConsecutiveGestureDelay;
   },
 
   /**
    * Clear the existing timer.
    */
   clearTimer: function Gesture_clearTimer() {
+    Logger.gesture('clearTimeout', this.type);
     clearTimeout(this._timer);
     delete this._timer;
   },
 
   /**
    * Start the timer for gesture timeout.
    * @param {Number} aTimeStamp An original pointer event's timeStamp that
    * started the gesture resolution sequence.
    */
   startTimer: function Gesture_startTimer(aTimeStamp) {
+    Logger.gesture('startTimer', this.type);
     this.clearTimer();
     let delay = this._getDelay(aTimeStamp);
     let handler = () => {
+      Logger.gesture('timer handler');
       delete this._timer;
       if (!this._inProgress) {
         this._deferred.reject();
       } else if (this._rejectToOnWait) {
         this._deferred.reject(this._rejectToOnWait);
       }
     };
     if (delay <= 0) {
@@ -667,69 +670,99 @@ DoubleTapHoldEnd.prototype = Object.crea
 DoubleTapHoldEnd.prototype.type = 'doubletapholdend';
 
 /**
  * A common tap gesture object.
  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
  * the gesture resolution sequence.
  * @param {Object} aPoints An existing set of points (from previous events).
  * @param {?String} aLastEvent Last pointer event type.
- * @param {Function} aRejectTo A constructor for the next gesture to reject to
- * in case no pointermove or pointerup happens within the
+ * @param {Function} aRejectToOnWait A constructor for the next gesture to
+ * reject to in case no pointermove or pointerup happens within the
  * GestureSettings.dwellThreshold.
+ * @param {Function} aRejectToOnPointerDown A constructor for the gesture to
+ * reject to if a finger comes down immediately after the tap.
  * @param {Function} aTravelTo An optional constuctor for the next gesture to
  * reject to in case the the TravelGesture test fails.
  */
-function TapGesture(aTimeStamp, aPoints, aLastEvent, aRejectTo, aTravelTo) {
-  this._rejectToOnWait = aRejectTo;
+function TapGesture(aTimeStamp, aPoints, aLastEvent, aRejectToOnWait, aTravelTo, aRejectToOnPointerDown) {
+  this._rejectToOnWait = aRejectToOnWait;
+  this._rejectToOnPointerDown = aRejectToOnPointerDown;
   // If the pointer travels, reject to aTravelTo.
   TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent, aTravelTo,
     TAP_MAX_RADIUS);
 }
 
 TapGesture.prototype = Object.create(TravelGesture.prototype);
 TapGesture.prototype._getDelay = function TapGesture__getDelay() {
   // If, for TapGesture, no pointermove or pointerup happens within the
   // GestureSettings.dwellThreshold, reject.
   // Note: the original pointer event's timeStamp is irrelevant here.
   return GestureSettings.dwellThreshold;
 };
 
+TapGesture.prototype.pointerup = function TapGesture_pointerup(aPoints) {
+    if (this._rejectToOnPointerDown) {
+      let complete = this._update(aPoints, 'pointerup', false, true);
+      if (complete) {
+        this.clearTimer();
+        if (GestureSettings.maxConsecutiveGestureDelay) {
+          this._pointerUpTimer = setTimeout(() => {
+            delete this._pointerUpTimer;
+            this._deferred.resolve();
+          }, GestureSettings.maxConsecutiveGestureDelay);
+        } else {
+          this._deferred.resolve();
+        }
+      }
+    } else {
+      TravelGesture.prototype.pointerup.call(this, aPoints);
+    }
+};
+
+TapGesture.prototype.pointerdown = function TapGesture_pointerdown(aPoints, aTimeStamp) {
+  TravelGesture.prototype.pointerdown.call(this, aPoints, aTimeStamp);
+  if (this._pointerUpTimer) {
+    clearTimeout(this._pointerUpTimer);
+    delete this._pointerUpTimer;
+    this._deferred.reject(this._rejectToOnPointerDown);
+  }
+};
+
+
 /**
  * Tap gesture.
  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
  * the gesture resolution sequence.
  * @param {Object} aPoints An existing set of points (from previous events).
  * @param {?String} aLastEvent Last pointer event type.
  */
 function Tap(aTimeStamp, aPoints, aLastEvent) {
   // If the pointer travels, reject to Swipe.
-  TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, Dwell, Swipe);
+  TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, Dwell, Swipe, DoubleTap);
 }
 
 Tap.prototype = Object.create(TapGesture.prototype);
 Tap.prototype.type = 'tap';
-Tap.prototype.resolveTo = DoubleTap;
 
 /**
  * Tap (multi) gesture on Android.
  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
  * the gesture resolution sequence.
  * @param {Object} aPoints An existing set of points (from previous events).
  * @param {?String} aLastEvent Last pointer event type.
  */
 function AndroidTap(aTimeStamp, aPoints, aLastEvent) {
   // If the pointer travels, reject to Swipe. On dwell threshold reject to
   // TapHold.
-  TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, TapHold, Swipe);
+  TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, TapHold, Swipe, TripleTap);
 }
 AndroidTap.prototype = Object.create(TapGesture.prototype);
 // Android double taps are translated to single taps.
 AndroidTap.prototype.type = 'doubletap';
-AndroidTap.prototype.resolveTo = TripleTap;
 
 /**
  * Clear the pointerup handler timer in case of the 3 pointer swipe.
  */
 AndroidTap.prototype.clearThreeFingerSwipeTimer = function AndroidTap_clearThreeFingerSwipeTimer() {
   clearTimeout(this._threeFingerSwipeTimer);
   delete this._threeFingerSwipeTimer;
 };
@@ -762,31 +795,32 @@ AndroidTap.prototype.pointerup = functio
 /**
  * Double Tap gesture.
  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
  * the gesture resolution sequence.
  * @param {Object} aPoints An existing set of points (from previous events).
  * @param {?String} aLastEvent Last pointer event type.
  */
 function DoubleTap(aTimeStamp, aPoints, aLastEvent) {
-  TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, TapHold);
+  this._inProgress = true;
+  TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, TapHold, null, TripleTap);
 }
 
 DoubleTap.prototype = Object.create(TapGesture.prototype);
 DoubleTap.prototype.type = 'doubletap';
-DoubleTap.prototype.resolveTo = TripleTap;
 
 /**
  * Triple Tap gesture.
  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
  * the gesture resolution sequence.
  * @param {Object} aPoints An existing set of points (from previous events).
  * @param {?String} aLastEvent Last pointer event type.
  */
 function TripleTap(aTimeStamp, aPoints, aLastEvent) {
+  this._inProgress = true;
   TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, DoubleTapHold);
 }
 
 TripleTap.prototype = Object.create(TapGesture.prototype);
 TripleTap.prototype.type = 'tripletap';
 
 /**
  * Common base object for gestures that are created as resolved.
--- a/accessible/tests/mochitest/jsat/dom_helper.js
+++ b/accessible/tests/mochitest/jsat/dom_helper.js
@@ -97,32 +97,36 @@ sendTouchEvent.touchList = null;
 var eventMap = {
   touchstart: sendTouchEvent,
   touchend: sendTouchEvent,
   touchmove: sendTouchEvent
 };
 
 var originalDwellThreshold = GestureSettings.dwellThreshold;
 var originalSwipeMaxDuration = GestureSettings.swipeMaxDuration;
+var originalConsecutiveGestureDelay =
+  GestureSettings.maxConsecutiveGestureDelay;
 
 /**
  * Attach a listener for the mozAccessFuGesture event that tests its
  * type.
  * @param  {Array} aExpectedGestures A stack of expected event types.
  * Note: the listener is removed once the stack reaches 0.
  */
 function testMozAccessFuGesture(aExpectedGestures) {
   var types = aExpectedGestures;
   function handleGesture(aEvent) {
     if (aEvent.detail.type !== types[0].type) {
+      info('Got ' + aEvent.detail.type + ' waiting for ' + types[0].type);
       // The is not the event of interest.
       return;
     }
     is(!!aEvent.detail.edge, !!types[0].edge);
-    ok(true, 'Received correct mozAccessFuGesture: ' + types.shift() + '.');
+    ok(true, 'Received correct mozAccessFuGesture: ' +
+      JSON.stringify(types.shift()) + '.');
     if (types.length === 0) {
       win.removeEventListener('mozAccessFuGesture', handleGesture);
       if (AccessFuTest.sequenceCleanup) {
         AccessFuTest.sequenceCleanup();
       }
       AccessFuTest.nextTest();
     }
   }
@@ -168,16 +172,19 @@ AccessFuTest.addSequence = function Acce
     var events = aSequence.events;
     function fireEvent(aEvent) {
       var event = {
         points: convertPointCoordinates(aEvent.points),
         type: aEvent.type
       };
       var timeStamp = Date.now();
       resetTimers();
+      GestureSettings.maxConsecutiveGestureDelay =
+        aEvent.removeConsecutiveGestureDelay ?
+        0 : originalConsecutiveGestureDelay;
       GestureTracker.handle(event, timeStamp);
       setTimers(timeStamp, aEvent.removeDwellThreshold,
         aEvent.removeSwipeMaxDuration);
       processEvents();
     }
     function processEvents() {
       if (events.length === 0) {
         return;
--- a/accessible/tests/mochitest/jsat/gestures.json
+++ b/accessible/tests/mochitest/jsat/gestures.json
@@ -1,22 +1,24 @@
 [
   {
     "events": [
       {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
-      {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
+      {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}],
+       "removeConsecutiveGestureDelay": true }
     ],
     "expectedGestures": [{ "type": "tap" }]
   },
   {
     "events": [
       {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
       {"type": "pointermove",
         "points": [{"x": 1.03, "y": 1.03, "identifier": 1}]},
-      {"type": "pointerup", "points": [{"x": 1.03, "y": 1.03, "identifier": 1}]}
+      {"type": "pointerup", "points": [{"x": 1.03, "y": 1.03, "identifier": 1}],
+       "removeConsecutiveGestureDelay": true }
     ],
     "expectedGestures": [{ "type": "tap" }]
   },
   {
     "events": [
       {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}],
         "removeDwellThreshold": true},
       {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
@@ -29,54 +31,55 @@
       {"type": "pointermove",
         "points": [{"x": 1.03, "y": 1.02, "identifier": 1}]},
       {"type": "pointerup",
         "points": [{"x": 1.03, "y": 1.02, "identifier": 1}]},
       {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
       {"type": "pointermove",
         "points": [{"x": 0.97, "y": 1.01, "identifier": 1}]},
       {"type": "pointerup",
-        "points": [{"x": 0.97, "y": 1.01, "identifier": 1}]}
+        "points": [{"x": 0.97, "y": 1.01, "identifier": 1}],
+        "removeConsecutiveGestureDelay": true }
     ],
-    "expectedGestures": [{ "type": "tap" }, { "type": "doubletap" }]
+    "expectedGestures": [{ "type": "doubletap" }]
   },
   {
     "events": [
       {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
       {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]},
       {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
       {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]},
       {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
-      {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
+      {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}],
+       "removeConsecutiveGestureDelay": true }
     ],
-    "expectedGestures": [{ "type": "tap" }, { "type": "doubletap" }, { "type": "tripletap" }]
+    "expectedGestures": [{ "type": "tripletap" }]
   },
   {
     "events": [
       {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
       {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]},
       {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
       {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]},
       {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}],
         "removeDwellThreshold": true},
       {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
     ],
-    "expectedGestures": [{ "type": "tap" }, { "type": "doubletap" },
-      { "type": "doubletaphold" }, { "type": "doubletapholdend" }]
+    "expectedGestures": [{ "type": "doubletaphold" },
+      { "type": "doubletapholdend" }]
   },
   {
     "events": [
       {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
       {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]},
       {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}],
         "removeDwellThreshold": true},
       {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
     ],
-    "expectedGestures": [{ "type": "tap" }, { "type": "taphold" },
-      { "type": "tapholdend" }]
+    "expectedGestures": [{ "type": "taphold" }, { "type": "tapholdend" }]
   },
   {
     "events": [
       {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
       {"type": "pointermove", "points": [{"x": 1.5, "y": 1, "identifier": 1}]},
       {"type": "pointerup", "points": [{"x": 1.5, "y": 1, "identifier": 1}]}
     ],
     "expectedGestures": [{ "type": "swiperight" }]
--- a/accessible/tests/mochitest/jsat/test_gesture_tracker.html
+++ b/accessible/tests/mochitest/jsat/test_gesture_tracker.html
@@ -26,17 +26,17 @@
     function doTest() {
       loadJSON("./gestures.json", function onSuccess(gestures) {
         AccessFuTest.addFunc(startGestureTracker);
         AccessFuTest.sequenceCleanup = GestureTracker.reset.bind(
           GestureTracker);
         gestures.forEach(AccessFuTest.addSequence);
         AccessFuTest.addFunc(stopGestureTracker);
         AccessFuTest.waitForExplicitFinish();
-        Logger.logLevel = Logger.DEBUG;
+        Logger.logLevel = Logger.GESTURE;
         AccessFuTest.runTests();
       });
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>