Bug 1087608 - ensuring multitap gestures do not resolve to explore. Updating gestures to classes. draft
authorYura Zenevich <yzenevich@mozilla.com>
Mon, 21 Mar 2016 17:28:56 -0400
changeset 343071 dbbb70eafb8411b0d216c008a0ba9a66408d4168
parent 342865 4037eb98974db1b1e0b5012c8a7f3a36428eaa11
child 516682 e76590b90d751d92e78ed25949960c4b293640b6
push id13519
push useryura.zenevich@gmail.com
push dateMon, 21 Mar 2016 21:30:07 +0000
bugs1087608
milestone48.0a1
Bug 1087608 - ensuring multitap gestures do not resolve to explore. Updating gestures to classes. MozReview-Commit-ID: BhaLe8NTVfe --- accessible/jsat/Gestures.jsm | 687 ++++++++++++++++++++----------------------- 1 file changed, 324 insertions(+), 363 deletions(-)
accessible/jsat/Gestures.jsm
--- a/accessible/jsat/Gestures.jsm
+++ b/accessible/jsat/Gestures.jsm
@@ -8,20 +8,18 @@
 /******************************************************************************
   All gestures have the following pathways when being resolved(v)/rejected(x):
                Tap -> DoubleTap        (x)
                    -> Dwell            (x)
                    -> Swipe            (x)
 
          DoubleTap -> TripleTap        (x)
                    -> TapHold          (x)
-                   -> Explore          (x)
 
          TripleTap -> DoubleTapHold    (x)
-                   -> Explore          (x)
 
              Dwell -> DwellEnd         (v)
 
              Swipe -> Explore          (x)
 
            TapHold -> TapHoldEnd       (v)
 
      DoubleTapHold -> DoubleTapHoldEnd (v)
@@ -34,33 +32,30 @@
 
         ExploreEnd -> Explore          (x)
 
            Explore -> ExploreEnd       (v)
 ******************************************************************************/
 
 'use strict';
 
-const Ci = Components.interfaces;
 const Cu = Components.utils;
 
-this.EXPORTED_SYMBOLS = ['GestureSettings', 'GestureTracker']; // jshint ignore:line
+this.EXPORTED_SYMBOLS = ['GestureSettings', 'GestureTracker'];
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 
-XPCOMUtils.defineLazyModuleGetter(this, 'Utils', // jshint ignore:line
+XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
   'resource://gre/modules/accessibility/Utils.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'Logger', // jshint ignore:line
+XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
   'resource://gre/modules/accessibility/Utils.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'setTimeout', // jshint ignore:line
+XPCOMUtils.defineLazyModuleGetter(this, 'setTimeout',
   'resource://gre/modules/Timer.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'clearTimeout', // jshint ignore:line
+XPCOMUtils.defineLazyModuleGetter(this, 'clearTimeout',
   'resource://gre/modules/Timer.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'Promise', // jshint ignore:line
-  'resource://gre/modules/Promise.jsm');
 
 // Default maximum duration of swipe
 const SWIPE_MAX_DURATION = 200;
 // Default maximum amount of time allowed for a gesture to be considered a
 // multitouch
 const MAX_MULTITOUCH = 125;
 // Default maximum consecutive pointer event timeout
 const MAX_CONSECUTIVE_GESTURE_DELAY = 200;
@@ -68,84 +63,81 @@ const MAX_CONSECUTIVE_GESTURE_DELAY = 20
 const DWELL_THRESHOLD = 250;
 // Minimal swipe distance in inches
 const SWIPE_MIN_DISTANCE = 0.4;
 // Maximum distance the pointer could move during a tap in inches
 const TAP_MAX_RADIUS = 0.2;
 // Directness coefficient. It is based on the maximum 15 degree angle between
 // consequent pointer move lines.
 const DIRECTNESS_COEFF = 1.44;
-// The virtual touch ID generated by a mouse event.
-const MOUSE_ID = 'mouse';
 // Amount in inches from the edges of the screen for it to be an edge swipe
 const EDGE = 0.1;
 // Multiply timeouts by this constant, x2 works great too for slower users.
 const TIMEOUT_MULTIPLIER = 1;
 // A single pointer down/up sequence periodically precedes the tripple swipe
 // gesture on Android. This delay acounts for that.
 const IS_ANDROID = Utils.MozBuildApp === 'mobile/android' &&
   Utils.AndroidSdkVersion >= 14;
 
 /**
- * A point object containing distance travelled data.
+ * A point class containing distance travelled data.
  * @param {Object} aPoint A point object that looks like: {
  *   x: x coordinate in pixels,
  *   y: y coordinate in pixels
  * }
  */
-function Point(aPoint) {
-  this.startX = this.x = aPoint.x;
-  this.startY = this.y = aPoint.y;
-  this.distanceTraveled = 0;
-  this.totalDistanceTraveled = 0;
-}
-
-Point.prototype = {
+class Point {
+  constructor(aPoint) {
+    this.startX = this.x = aPoint.x;
+    this.startY = this.y = aPoint.y;
+    this.distanceTraveled = 0;
+    this.totalDistanceTraveled = 0;
+  }
   /**
    * Update the current point coordiates.
    * @param  {Object} aPoint A new point coordinates.
    */
-  update: function Point_update(aPoint) {
+  update(aPoint) {
     let lastX = this.x;
     let lastY = this.y;
     this.x = aPoint.x;
     this.y = aPoint.y;
     this.distanceTraveled = this.getDistanceToCoord(lastX, lastY);
     this.totalDistanceTraveled += this.distanceTraveled;
-  },
+  }
 
-  reset: function Point_reset() {
+  reset() {
     this.distanceTraveled = 0;
     this.totalDistanceTraveled = 0;
-  },
+  }
 
   /**
    * Get distance between the current point coordinates and the given ones.
    * @param  {Number} aX A pixel value for the x coordinate.
    * @param  {Number} aY A pixel value for the y coordinate.
    * @return {Number} A distance between point's current and the given
    * coordinates.
    */
-  getDistanceToCoord: function Point_getDistanceToCoord(aX, aY) {
+  getDistanceToCoord(aX, aY) {
     return Math.hypot(this.x - aX, this.y - aY);
-  },
+  }
 
   /**
    * Get the direct distance travelled by the point so far.
    */
   get directDistanceTraveled() {
     return this.getDistanceToCoord(this.startX, this.startY);
   }
-};
+}
 
 /**
  * An externally accessible collection of settings used in gesture resolition.
  * @type {Object}
  */
-this.GestureSettings = { // jshint ignore:line
+this.GestureSettings = {
   /**
    * Maximum duration of swipe
    * @type {Number}
    */
   swipeMaxDuration: SWIPE_MAX_DURATION * TIMEOUT_MULTIPLIER,
 
   /**
    * Maximum amount of time allowed for a gesture to be considered a multitouch.
@@ -182,17 +174,17 @@ this.GestureSettings = { // jshint ignor
   travelThreshold: 0.025
 };
 
 /**
  * An interface that handles the pointer events and calculates the appropriate
  * gestures.
  * @type {Object}
  */
-this.GestureTracker = { // jshint ignore:line
+this.GestureTracker = {
   /**
    * Reset GestureTracker to its initial state.
    * @return {[type]} [description]
    */
   reset: function GestureTracker_reset() {
     if (this.current) {
       this.current.clearTimer();
     }
@@ -206,45 +198,43 @@ this.GestureTracker = { // jshint ignore
    * @param  {Number} aTimeStamp A new pointer event timeStamp.
    * @param  {Function} aGesture A gesture constructor (default: Tap).
    */
   _init: function GestureTracker__init(aDetail, aTimeStamp, aGesture) {
     // Only create a new gesture on |pointerdown| event.
     if (aDetail.type !== 'pointerdown') {
       return;
     }
-    let points = aDetail.points;
     let GestureConstructor = aGesture || (IS_ANDROID ? DoubleTap : Tap);
     this._create(GestureConstructor);
     this._update(aDetail, aTimeStamp);
   },
 
   /**
    * Handle the incoming pointer event with the existing gesture object(if
    * present) or with the newly created one.
    * @param  {Object} aDetail A new pointer event detail.
    * @param  {Number} aTimeStamp A new pointer event timeStamp.
    */
   handle: function GestureTracker_handle(aDetail, aTimeStamp) {
-    Logger.gesture(() => {
-      return ['Pointer event', Utils.dpi, 'at:', aTimeStamp, JSON.stringify(aDetail)];
-    });
+    Logger.gesture(() =>
+      ['Pointer event', Utils.dpi, 'at:', aTimeStamp, JSON.stringify(aDetail)]);
     this[this.current ? '_update' : '_init'](aDetail, aTimeStamp);
   },
 
   /**
    * Create a new gesture object and attach resolution handler to it.
    * @param  {Function} aGesture A gesture constructor.
    * @param  {Number} aTimeStamp An original pointer event timeStamp.
    * @param  {Array} aPoints All changed points associated with the new pointer
    * event.
    * @param {?String} aLastEvent Last pointer event type.
    */
   _create: function GestureTracker__create(aGesture, aTimeStamp, aPoints, aLastEvent) {
-    this.current = new aGesture(aTimeStamp, aPoints, aLastEvent); /* A constructor name should start with an uppercase letter. */ // jshint ignore:line
+    this.current = new aGesture(aTimeStamp, aPoints, aLastEvent); /* A constructor name should start with an uppercase letter. */
     this.current.then(this._onFulfill.bind(this));
   },
 
   /**
    * Handle the incoming pointer event with the existing gesture object.
    * @param  {Object} aDetail A new pointer event detail.
    * @param  {Number} aTimeStamp A new pointer event timeStamp.
    */
@@ -265,16 +255,17 @@ this.GestureTracker = { // jshint ignore
     if (!current || current.id !== id) {
       return;
     }
     // Only create a gesture if we got a constructor.
     if (gestureType) {
       this._create(gestureType, current.startTime, current.points,
         current.lastEvent);
     } else {
+      this.current.clearTimer();
       delete this.current;
     }
   }
 };
 
 /**
  * Compile a mozAccessFuGesture detail structure.
  * @param  {String} aType A gesture type.
@@ -313,645 +304,615 @@ function compileDetail(aType, aPoints, k
     type: aType,
     touches: touches,
     deltaX: maxDeltaX,
     deltaY: maxDeltaY
   };
 }
 
 /**
- * A general gesture object.
+ * A general gesture class.
  * @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).
  * Default is an empty object.
  * @param {?String} aLastEvent Last pointer event type.
  */
-function Gesture(aTimeStamp, aPoints = {}, aLastEvent = undefined) {
-  this.startTime = Date.now();
-  Logger.gesture('Creating', this.id, 'gesture.');
-  this.points = aPoints;
-  this.lastEvent = aLastEvent;
-  this._deferred = Promise.defer();
-  // Call this._handleResolve or this._handleReject when the promise is
-  // fulfilled with either resolve or reject.
-  this.promise = this._deferred.promise.then(this._handleResolve.bind(this),
-    this._handleReject.bind(this));
-  this.startTimer(aTimeStamp);
-}
-
-Gesture.prototype = {
+class Gesture {
+  constructor(aTimeStamp, aPoints = {}, aLastEvent = undefined) {
+    this.startTime = Date.now();
+    Logger.gesture('Creating', this.id, 'gesture.');
+    this.points = aPoints;
+    this.lastEvent = aLastEvent;
+    let promise = new Promise((resolve, reject) => {
+      this.resolve = resolve;
+      this.reject = reject;
+    });
+    // Call this._handleResolve or this._handleReject when the promise is
+    // fulfilled with either resolve or reject.
+    this.promise = promise.then(this._handleResolve.bind(this),
+      this._handleReject.bind(this));
+    this.startTimer(aTimeStamp);
+  }
   /**
    * Get the gesture timeout delay.
    * @return {Number}
    */
-  _getDelay: function Gesture__getDelay() {
+  _getDelay() {
     // If nothing happens withing the
     // GestureSettings.maxConsecutiveGestureDelay, we should not wait for any
     // more pointer events and consider them the part of the same gesture -
     // reject this gesture promise.
     return GestureSettings.maxConsecutiveGestureDelay;
-  },
+  }
 
+  get _inProgress() {
+    return this.__inProgress;
+  }
+  set _inProgress(aVal) {
+    this.__inProgress = aVal;
+  }
   /**
    * Clear the existing timer.
    */
-  clearTimer: function Gesture_clearTimer() {
+  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) {
+  startTimer(aTimeStamp) {
     Logger.gesture('startTimer', this.type);
     this.clearTimer();
     let delay = this._getDelay(aTimeStamp);
     let handler = () => {
       Logger.gesture('timer handler');
       this.clearTimer();
       if (!this._inProgress) {
-        this._deferred.reject();
+        this.reject();
       } else if (this._rejectToOnWait) {
-        this._deferred.reject(this._rejectToOnWait);
+        this.reject(this._rejectToOnWait);
       }
     };
     if (delay <= 0) {
       handler();
     } else {
       this._timer = setTimeout(handler, delay);
     }
-  },
-
+  }
   /**
    * Add a gesture promise resolution callback.
    * @param  {Function} aCallback
    */
-  then: function Gesture_then(aCallback) {
+  then(aCallback) {
     this.promise.then(aCallback);
-  },
-
+  }
   /**
    * Update gesture's points. Test the points set with the optional gesture test
    * function.
    * @param  {Array} aPoints An array with the changed points from the new
    * pointer event.
    * @param {String} aType Pointer event type.
    * @param  {Boolean} aCanCreate A flag that enables including the new points.
    * Default is false.
    * @param  {Boolean} aNeedComplete A flag that indicates that the gesture is
    * completing. Default is false.
    * @return {Boolean} Indicates whether the gesture can be complete (it is
    * set to true iff the aNeedComplete is true and there was a change to at
    * least one point that belongs to the gesture).
    */
-  _update: function Gesture__update(aPoints, aType, aCanCreate = false, aNeedComplete = false) {
-    let complete;
+  _update(aPoints, aType, aCanCreate = false, aNeedComplete = false) {
     let lastEvent;
     for (let point of aPoints) {
       let identifier = point.identifier;
       let gesturePoint = this.points[identifier];
       if (gesturePoint) {
         if (aType === 'pointerdown' && aCanCreate) {
           // scratch the previous pointer with that id.
           this.points[identifier] = new Point(point);
         } else {
           gesturePoint.update(point);
         }
-        if (aNeedComplete) {
-          // Since the gesture is completing and at least one of the gesture
-          // points is updated, set the return value to true.
-          complete = true;
-        }
         lastEvent = lastEvent || aType;
       } else if (aCanCreate) {
         // Only create a new point if aCanCreate is true.
-        this.points[identifier] =
-          new Point(point);
+        this.points[identifier] = new Point(point);
         lastEvent = lastEvent || aType;
       }
     }
     this.lastEvent = lastEvent || this.lastEvent;
     // If test function is defined test the points.
     if (this.test) {
-      this.test(complete);
+      this.test(aNeedComplete);
     }
-    return complete;
-  },
-
+  }
   /**
    * Emit a mozAccessFuGesture (when the gesture is resolved).
    * @param  {Object} aDetail a compiled mozAccessFuGesture detail structure.
    */
-  _emit: function Gesture__emit(aDetail) {
+  _emit(aDetail) {
     let evt = new Utils.win.CustomEvent('mozAccessFuGesture', {
       bubbles: true,
       cancelable: true,
       detail: aDetail
     });
     Utils.win.dispatchEvent(evt);
-  },
-
+  }
   /**
    * Handle the pointer down event.
    * @param  {Array} aPoints A new pointer down points.
    * @param  {Number} aTimeStamp A new pointer down timeStamp.
    */
-  pointerdown: function Gesture_pointerdown(aPoints, aTimeStamp) {
+  pointerdown(aPoints, aTimeStamp) {
     this._inProgress = true;
     this._update(aPoints, 'pointerdown',
       aTimeStamp - this.startTime < GestureSettings.maxMultitouch);
-  },
-
+  }
   /**
    * Handle the pointer move event.
    * @param  {Array} aPoints A new pointer move points.
    */
-  pointermove: function Gesture_pointermove(aPoints) {
+  pointermove(aPoints) {
     this._update(aPoints, 'pointermove');
-  },
-
+  }
   /**
    * Handle the pointer up event.
    * @param  {Array} aPoints A new pointer up points.
    */
-  pointerup: function Gesture_pointerup(aPoints) {
-    let complete = this._update(aPoints, 'pointerup', false, true);
-    if (complete) {
-      this._deferred.resolve();
-    }
-  },
-
+  pointerup(aPoints) {
+    this._update(aPoints, 'pointerup', false, true);
+    this.resolve();
+  }
   /**
    * A subsequent gesture constructor to resolve the current one to. E.g.
    * tap->doubletap, dwell->dwellend, etc.
    * @type {Function}
    */
-  resolveTo: null,
-
+  get resolveTo() {
+    return null;
+  }
   /**
    * A unique id for the gesture. Composed of the type + timeStamp.
    */
   get id() {
     delete this._id;
     this._id = this.type + this.startTime;
     return this._id;
-  },
-
+  }
   /**
    * A gesture promise resolve callback. Compile and emit the gesture.
    * @return {Object} Returns a structure to the gesture handler that looks like
    * this: {
    *   id: current gesture id,
    *   gestureType: an optional subsequent gesture constructor.
    * }
    */
-  _handleResolve: function Gesture__handleResolve() {
+  _handleResolve() {
     if (this.isComplete) {
       return;
     }
     Logger.gesture('Resolving', this.id, 'gesture.');
     this.isComplete = true;
     this.clearTimer();
     let detail = this.compile();
     if (detail) {
       this._emit(detail);
     }
     return {
       id: this.id,
       gestureType: this.resolveTo
     };
-  },
-
+  }
   /**
    * A gesture promise reject callback.
    * @return {Object} Returns a structure to the gesture handler that looks like
    * this: {
    *   id: current gesture id,
    *   gestureType: an optional subsequent gesture constructor.
    * }
    */
-  _handleReject: function Gesture__handleReject(aRejectTo) {
+  _handleReject(aRejectTo) {
     if (this.isComplete) {
       return;
     }
     Logger.gesture('Rejecting', this.id, 'gesture.');
     this.isComplete = true;
     this.clearTimer();
     return {
       id: this.id,
       gestureType: aRejectTo
     };
-  },
-
+  }
   /**
    * A default compilation function used to build the mozAccessFuGesture event
    * detail. The detail always includes the type and the touches associated
    * with the gesture.
    * @return {Object} Gesture event detail.
    */
-  compile: function Gesture_compile() {
+  compile() {
     return compileDetail(this.type, this.points);
   }
-};
+}
 
 /**
  * A mixin for an explore related object.
  */
-function ExploreGesture() {
-  this.compile = () => {
+let ExploreGesture = Base => class extends Base {
+  compile() {
     // Unlike most of other gestures explore based gestures compile using the
     // current point position and not the start one.
     return compileDetail(this.type, this.points, {x: 'x', y: 'y'});
-  };
-}
+  }
+};
 
 /**
  * Check the in progress gesture for completion.
  */
 function checkProgressGesture(aGesture) {
-  aGesture._inProgress = true;
   if (aGesture.lastEvent === 'pointerup') {
     if (aGesture.test) {
       aGesture.test(true);
     }
-    aGesture._deferred.resolve();
+    aGesture.resolve();
   }
 }
 
 /**
+ * Common base object for gestures that are created as resolved.
+ * @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.
+ */
+class ResolvedGesture extends Gesture {
+  constructor(aTimeStamp, aPoints, aLastEvent) {
+    super(aTimeStamp, aPoints, aLastEvent);
+    // Resolve the guesture right away.
+    this.resolve();
+  }
+}
+
+/**
+ * Explore 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.
+ */
+class Explore extends ExploreGesture(ResolvedGesture) {
+  get type() { return 'explore'; }
+  get resolveTo() { return ExploreEnd; }
+}
+
+/**
  * A common travel gesture. When the travel gesture is created, all subsequent
  * pointer events' points are tested for their total distance traveled. If that
  * distance exceeds the _threshold distance, the gesture will be rejected to a
  * _travelTo 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.
  * @param {Function} aTravelTo A contructor for the gesture to reject to when
  * travelling (default: Explore).
  * @param {Number} aThreshold Travel threshold (default:
  * GestureSettings.travelThreshold).
  */
-function TravelGesture(aTimeStamp, aPoints, aLastEvent, aTravelTo = Explore, aThreshold = GestureSettings.travelThreshold) {
-  Gesture.call(this, aTimeStamp, aPoints, aLastEvent);
-  this._travelTo = aTravelTo;
-  this._threshold = aThreshold;
+class TravelGesture extends Gesture {
+  constructor(aTimeStamp, aPoints, aLastEvent, aTravelTo = Explore, aThreshold = GestureSettings.travelThreshold) {
+    super(aTimeStamp, aPoints, aLastEvent);
+    this._travelTo = aTravelTo;
+    this._threshold = aThreshold;
+  }
+  /**
+   * Test the gesture points for travel. The gesture will be rejected to
+   * this._travelTo gesture iff at least one point crosses this._threshold.
+   */
+  test() {
+    if (!this._travelTo) {
+      return;
+    }
+    for (let identifier in this.points) {
+      let point = this.points[identifier];
+      if (point.totalDistanceTraveled / Utils.dpi > this._threshold) {
+        this.reject(this._travelTo);
+        return;
+      }
+    }
+  }
 }
 
-TravelGesture.prototype = Object.create(Gesture.prototype);
-
 /**
- * Test the gesture points for travel. The gesture will be rejected to
- * this._travelTo gesture iff at least one point crosses this._threshold.
+ * ExploreEnd 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.
  */
-TravelGesture.prototype.test = function TravelGesture_test() {
-  for (let identifier in this.points) {
-    let point = this.points[identifier];
-    if (point.totalDistanceTraveled / Utils.dpi > this._threshold) {
-      this._deferred.reject(this._travelTo);
-      return;
-    }
+class ExploreEnd extends ExploreGesture(TravelGesture) {
+  constructor(aTimeStamp, aPoints, aLastEvent) {
+    super(aTimeStamp, aPoints, aLastEvent);
+    checkProgressGesture(this);
   }
-};
+  get type() { return 'exploreend'; }
+  get _inProgress() { return true; }
+  set _inProgress(aVal) { return; }
+}
 
 /**
  * DwellEnd 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 DwellEnd(aTimeStamp, aPoints, aLastEvent) {
-  this._inProgress = true;
-  // If the pointer travels, reject to Explore.
-  TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
-  checkProgressGesture(this);
+class DwellEnd extends TravelGesture {
+  constructor(aTimeStamp, aPoints, aLastEvent) {
+    // If the pointer travels, reject to Explore.
+    super(aTimeStamp, aPoints, aLastEvent);
+    checkProgressGesture(this);
+  }
+  get _inProgress() { return true; }
+  set _inProgress(aVal) { return; }
+  get type() { return 'dwellend'; }
 }
 
-DwellEnd.prototype = Object.create(TravelGesture.prototype);
-DwellEnd.prototype.type = 'dwellend';
-
 /**
  * TapHoldEnd gesture. This gesture can be represented as the following diagram:
  * pointerdown-pointerup-pointerdown-*wait*-pointerup.
  * @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 TapHoldEnd(aTimeStamp, aPoints, aLastEvent) {
-  this._inProgress = true;
-  // If the pointer travels, reject to Explore.
-  TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
-  checkProgressGesture(this);
+class TapHoldEnd extends TravelGesture {
+  constructor(aTimeStamp, aPoints, aLastEvent) {
+    // If the pointer travels, reject to Explore.
+    super(aTimeStamp, aPoints, aLastEvent);
+    checkProgressGesture(this);
+  }
+  get _inProgress() { return true; }
+  set _inProgress(aVal) { return; }
+  get type() { return 'tapholdend'; }
 }
 
-TapHoldEnd.prototype = Object.create(TravelGesture.prototype);
-TapHoldEnd.prototype.type = 'tapholdend';
-
 /**
  * DoubleTapHoldEnd gesture. This gesture can be represented as the following
  * diagram:
  * pointerdown-pointerup-pointerdown-pointerup-pointerdown-*wait*-pointerup.
  * @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 DoubleTapHoldEnd(aTimeStamp, aPoints, aLastEvent) {
-  this._inProgress = true;
-  // If the pointer travels, reject to Explore.
-  TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
-  checkProgressGesture(this);
+class DoubleTapHoldEnd extends TravelGesture {
+  constructor(aTimeStamp, aPoints, aLastEvent) {
+    // If the pointer travels, reject to Explore.
+    super(aTimeStamp, aPoints, aLastEvent);
+    checkProgressGesture(this);
+  }
+  get _inProgress() { return true; }
+  set _inProgress(aVal) { return; }
+  get type() { return 'doubletapholdend'; }
 }
 
-DoubleTapHoldEnd.prototype = Object.create(TravelGesture.prototype);
-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} 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, 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);
+class TapGesture extends TravelGesture {
+  constructor(aTimeStamp, aPoints, aLastEvent, aTravelTo) {
+    // If the pointer travels, reject to aTravelTo.
+    super(aTimeStamp, aPoints, aLastEvent, aTravelTo, TAP_MAX_RADIUS);
+  }
+  _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;
+  }
+
+  pointerup(aPoints) {
+    if (this._rejectToOnPointerDown) {
+      this._update(aPoints, 'pointerup', false, true);
+      this.clearTimer();
+      if (GestureSettings.maxGestureResolveTimeout) {
+        this._pointerUpTimer = setTimeout(() => {
+          clearTimeout(this._pointerUpTimer);
+          delete this._pointerUpTimer;
+          this.resolve();
+        }, GestureSettings.maxGestureResolveTimeout);
+      } else {
+        this.resolve();
+      }
+    } else {
+      super.pointerup(aPoints);
+    }
+  }
+
+  pointerdown(aPoints, aTimeStamp) {
+    super.pointerdown(aPoints, aTimeStamp);
+    if (this._pointerUpTimer) {
+      clearTimeout(this._pointerUpTimer);
+      delete this._pointerUpTimer;
+      this.reject(this._rejectToOnPointerDown);
+    }
+  }
 }
 
-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;
-};
+/**
+ * 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.
+ */
+class TripleTap extends TapGesture {
+  constructor(aTimeStamp, aPoints, aLastEvent) {
+    // If the pointer travels, do not reject.
+    super(aTimeStamp, aPoints, aLastEvent, null);
+  }
+  get _rejectToOnWait() { return DoubleTapHold; }
+  get _inProgress() { return true; }
+  set _inProgress(aVal) { return; }
+  get type() { return 'tripletap'; }
+}
 
-TapGesture.prototype.pointerup = function TapGesture_pointerup(aPoints) {
-    if (this._rejectToOnPointerDown) {
-      let complete = this._update(aPoints, 'pointerup', false, true);
-      if (complete) {
-        this.clearTimer();
-        if (GestureSettings.maxGestureResolveTimeout) {
-          this._pointerUpTimer = setTimeout(() => {
-            clearTimeout(this._pointerUpTimer);
-            delete this._pointerUpTimer;
-            this._deferred.resolve();
-          }, GestureSettings.maxGestureResolveTimeout);
-        } 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);
+/**
+ * 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.
+ */
+class DoubleTap extends TapGesture {
+  constructor(aTimeStamp, aPoints, aLastEvent) {
+    // If the pointer travels, do not reject.
+    super(aTimeStamp, aPoints, aLastEvent, null);
   }
-};
-
+  get _rejectToOnWait() { return TapHold; }
+  get _rejectToOnPointerDown() { return TripleTap; }
+  get _inProgress() { return true; }
+  set _inProgress(aVal) { return; }
+  get type() { return 'doubletap'; }
+}
 
 /**
  * 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, DoubleTap);
-}
-
-Tap.prototype = Object.create(TapGesture.prototype);
-Tap.prototype.type = 'tap';
-
-
-/**
- * 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) {
-  this._inProgress = true;
-  TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, TapHold, null, TripleTap);
+class Tap extends TapGesture {
+  constructor(aTimeStamp, aPoints, aLastEvent) {
+    // If the pointer travels, reject to Swipe.
+    super(aTimeStamp, aPoints, aLastEvent, Swipe);
+  }
+  get _rejectToOnWait() { return Dwell; }
+  get _rejectToOnPointerDown() { return DoubleTap; }
+  get type() { return 'tap'; }
 }
 
-DoubleTap.prototype = Object.create(TapGesture.prototype);
-DoubleTap.prototype.type = 'doubletap';
-
-/**
- * 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.
- * @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 ResolvedGesture(aTimeStamp, aPoints, aLastEvent) {
-  Gesture.call(this, aTimeStamp, aPoints, aLastEvent);
-  // Resolve the guesture right away.
-  this._deferred.resolve();
-}
-
-ResolvedGesture.prototype = Object.create(Gesture.prototype);
-
 /**
  * Dwell 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 Dwell(aTimeStamp, aPoints, aLastEvent) {
-  ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
+class Dwell extends ResolvedGesture {
+  get type() { return 'dwell'; }
+  get resolveTo() { return DwellEnd; }
 }
 
-Dwell.prototype = Object.create(ResolvedGesture.prototype);
-Dwell.prototype.type = 'dwell';
-Dwell.prototype.resolveTo = DwellEnd;
-
 /**
  * TapHold 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 TapHold(aTimeStamp, aPoints, aLastEvent) {
-  ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
+class TapHold extends ResolvedGesture {
+  get type() { return 'taphold'; }
+  get resolveTo() { return TapHoldEnd; }
 }
 
-TapHold.prototype = Object.create(ResolvedGesture.prototype);
-TapHold.prototype.type = 'taphold';
-TapHold.prototype.resolveTo = TapHoldEnd;
-
 /**
  * DoubleTapHold 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 DoubleTapHold(aTimeStamp, aPoints, aLastEvent) {
-  ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
-}
-
-DoubleTapHold.prototype = Object.create(ResolvedGesture.prototype);
-DoubleTapHold.prototype.type = 'doubletaphold';
-DoubleTapHold.prototype.resolveTo = DoubleTapHoldEnd;
-
-/**
- * Explore 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 Explore(aTimeStamp, aPoints, aLastEvent) {
-  ExploreGesture.call(this);
-  ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
+class DoubleTapHold extends ResolvedGesture {
+  get type() { return 'doubletaphold'; }
+  get resolveTo() { return DoubleTapHoldEnd; }
 }
 
-Explore.prototype = Object.create(ResolvedGesture.prototype);
-Explore.prototype.type = 'explore';
-Explore.prototype.resolveTo = ExploreEnd;
-
-/**
- * ExploreEnd 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 ExploreEnd(aTimeStamp, aPoints, aLastEvent) {
-  this._inProgress = true;
-  ExploreGesture.call(this);
-  // If the pointer travels, reject to Explore.
-  TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
-  checkProgressGesture(this);
-}
-
-ExploreEnd.prototype = Object.create(TravelGesture.prototype);
-ExploreEnd.prototype.type = 'exploreend';
-
 /**
  * Swipe 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 Swipe(aTimeStamp, aPoints, aLastEvent) {
-  this._inProgress = true;
-  this._rejectToOnWait = Explore;
-  Gesture.call(this, aTimeStamp, aPoints, aLastEvent);
-  checkProgressGesture(this);
-}
-
-Swipe.prototype = Object.create(Gesture.prototype);
-Swipe.prototype.type = 'swipe';
-Swipe.prototype._getDelay = function Swipe__getDelay(aTimeStamp) {
-  // Swipe should be completed within the GestureSettings.swipeMaxDuration from
-  // the initial pointer down event.
-  return GestureSettings.swipeMaxDuration - this.startTime + aTimeStamp;
-};
+class Swipe extends Gesture {
+  constructor(aTimeStamp, aPoints, aLastEvent) {
+    super(aTimeStamp, aPoints, aLastEvent);
+    checkProgressGesture(this);
+  }
+  get type() { return 'swipe'; }
+  get _inProgress() { return true; }
+  set _inProgress(aVal) { return; }
+  get _rejectToOnWait() { return Explore; }
+  _getDelay(aTimeStamp) {
+    // Swipe should be completed within the GestureSettings.swipeMaxDuration
+    // from the initial pointer down event.
+    return GestureSettings.swipeMaxDuration - this.startTime + aTimeStamp;
+  }
 
-/**
- * Determine wither the gesture was Swipe or Explore.
- * @param  {Booler} aComplete A flag that indicates whether the gesture is and
- * will be complete after the test.
- */
-Swipe.prototype.test = function Swipe_test(aComplete) {
-  if (!aComplete) {
-    // No need to test if the gesture is not completing or can't be complete.
-    return;
-  }
-  let reject = true;
-  // If at least one point travelled for more than SWIPE_MIN_DISTANCE and it was
-  // direct enough, consider it a Swipe.
-  for (let identifier in this.points) {
-    let point = this.points[identifier];
-    let directDistance = point.directDistanceTraveled;
-    if (directDistance / Utils.dpi >= SWIPE_MIN_DISTANCE ||
-      directDistance * DIRECTNESS_COEFF >= point.totalDistanceTraveled) {
-      reject = false;
+  test(aComplete) {
+    if (!aComplete) {
+      // No need to test if the gesture is not completing or can't be complete.
+      return;
+    }
+    let reject = true;
+    // If at least one point travelled for more than SWIPE_MIN_DISTANCE and it was
+    // direct enough, consider it a Swipe.
+    for (let identifier in this.points) {
+      let point = this.points[identifier];
+      let directDistance = point.directDistanceTraveled;
+      if (directDistance / Utils.dpi >= SWIPE_MIN_DISTANCE ||
+        directDistance * DIRECTNESS_COEFF >= point.totalDistanceTraveled) {
+        reject = false;
+      }
+    }
+    if (reject) {
+      this.reject(Explore);
     }
   }
-  if (reject) {
-    this._deferred.reject(Explore);
+  /**
+   * Compile a swipe related mozAccessFuGesture event detail.
+   * @return {Object} A mozAccessFuGesture detail object.
+   */
+  compile() {
+    let type = this.type;
+    let detail = compileDetail(type, this.points,
+      {x1: 'startX', y1: 'startY', x2: 'x', y2: 'y'});
+    let deltaX = detail.deltaX;
+    let deltaY = detail.deltaY;
+    let edge = EDGE * Utils.dpi;
+    if (Math.abs(deltaX) > Math.abs(deltaY)) {
+      // Horizontal swipe.
+      let startPoints = detail.touches.map(touch => touch.x1);
+      if (deltaX > 0) {
+        detail.type = type + 'right';
+        detail.edge = Math.min.apply(null, startPoints) <= edge;
+      } else {
+        detail.type = type + 'left';
+        detail.edge =
+          Utils.win.screen.width - Math.max.apply(null, startPoints) <= edge;
+      }
+    } else {
+      // Vertical swipe.
+      let startPoints = detail.touches.map(touch => touch.y1);
+      if (deltaY > 0) {
+        detail.type = type + 'down';
+        detail.edge = Math.min.apply(null, startPoints) <= edge;
+      } else {
+        detail.type = type + 'up';
+        detail.edge =
+          Utils.win.screen.height - Math.max.apply(null, startPoints) <= edge;
+      }
+    }
+    return detail;
   }
-};
-
-/**
- * Compile a swipe related mozAccessFuGesture event detail.
- * @return {Object} A mozAccessFuGesture detail object.
- */
-Swipe.prototype.compile = function Swipe_compile() {
-  let type = this.type;
-  let detail = compileDetail(type, this.points,
-    {x1: 'startX', y1: 'startY', x2: 'x', y2: 'y'});
-  let deltaX = detail.deltaX;
-  let deltaY = detail.deltaY;
-  let edge = EDGE * Utils.dpi;
-  if (Math.abs(deltaX) > Math.abs(deltaY)) {
-    // Horizontal swipe.
-    let startPoints = detail.touches.map(touch => touch.x1);
-    if (deltaX > 0) {
-      detail.type = type + 'right';
-      detail.edge = Math.min.apply(null, startPoints) <= edge;
-    } else {
-      detail.type = type + 'left';
-      detail.edge =
-        Utils.win.screen.width - Math.max.apply(null, startPoints) <= edge;
-    }
-  } else {
-    // Vertical swipe.
-    let startPoints = detail.touches.map(touch => touch.y1);
-    if (deltaY > 0) {
-      detail.type = type + 'down';
-      detail.edge = Math.min.apply(null, startPoints) <= edge;
-    } else {
-      detail.type = type + 'up';
-      detail.edge =
-        Utils.win.screen.height - Math.max.apply(null, startPoints) <= edge;
-    }
-  }
-  return detail;
-};
+}