accessible/src/jsat/Gestures.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 /* global Components, GestureSettings, XPCOMUtils, Utils, Promise, Logger */
     6 /* exported GestureSettings, GestureTracker */
     8 /******************************************************************************
     9   All gestures have the following pathways when being resolved(v)/rejected(x):
    10                Tap -> DoubleTap        (v)
    11                    -> Dwell            (x)
    12                    -> Swipe            (x)
    14         AndroidTap -> TripleTap        (v)
    15                    -> TapHold          (x)
    16                    -> Swipe            (x)
    18          DoubleTap -> TripleTap        (v)
    19                    -> TapHold          (x)
    20                    -> Explore          (x)
    22          TripleTap -> DoubleTapHold    (x)
    23                    -> Explore          (x)
    25              Dwell -> DwellEnd         (v)
    27              Swipe -> Explore          (x)
    29            TapHold -> TapHoldEnd       (v)
    31      DoubleTapHold -> DoubleTapHoldEnd (v)
    33           DwellEnd -> Explore          (x)
    35         TapHoldEnd -> Explore          (x)
    37   DoubleTapHoldEnd -> Explore          (x)
    39         ExploreEnd -> Explore          (x)
    41            Explore -> ExploreEnd       (v)
    42 ******************************************************************************/
    44 'use strict';
    46 const Ci = Components.interfaces;
    47 const Cu = Components.utils;
    49 this.EXPORTED_SYMBOLS = ['GestureSettings', 'GestureTracker']; // jshint ignore:line
    51 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
    53 XPCOMUtils.defineLazyModuleGetter(this, 'Utils', // jshint ignore:line
    54   'resource://gre/modules/accessibility/Utils.jsm');
    55 XPCOMUtils.defineLazyModuleGetter(this, 'Logger', // jshint ignore:line
    56   'resource://gre/modules/accessibility/Utils.jsm');
    57 XPCOMUtils.defineLazyModuleGetter(this, 'setTimeout', // jshint ignore:line
    58   'resource://gre/modules/Timer.jsm');
    59 XPCOMUtils.defineLazyModuleGetter(this, 'clearTimeout', // jshint ignore:line
    60   'resource://gre/modules/Timer.jsm');
    61 XPCOMUtils.defineLazyModuleGetter(this, 'Promise', // jshint ignore:line
    62   'resource://gre/modules/Promise.jsm');
    64 // Maximum amount of time allowed for a gesture to be considered a multitouch.
    65 const MAX_MULTITOUCH = 250;
    66 // Minimal swipe distance in inches
    67 const SWIPE_MIN_DISTANCE = 0.4;
    68 // Maximum distance the pointer could move during a tap in inches
    69 const TAP_MAX_RADIUS = 0.2;
    70 // Directness coefficient. It is based on the maximum 15 degree angle between
    71 // consequent pointer move lines.
    72 const DIRECTNESS_COEFF = 1.44;
    73 // An android flag.
    74 const IS_ANDROID = Utils.MozBuildApp === 'mobile/android' &&
    75   Utils.AndroidSdkVersion >= 14;
    76 // A single pointer down/up sequence periodically precedes the tripple swipe
    77 // gesture on Android. This delay acounts for that.
    78 const ANDROID_TRIPLE_SWIPE_DELAY = 50;
    79 // The virtual touch ID generated by a mouse event.
    80 const MOUSE_ID = 'mouse';
    82 /**
    83  * A point object containing distance travelled data.
    84  * @param {Object} aPoint A point object that looks like: {
    85  *   x: x coordinate in pixels,
    86  *   y: y coordinate in pixels
    87  * }
    88  */
    89 function Point(aPoint) {
    90   this.startX = this.x = aPoint.x;
    91   this.startY = this.y = aPoint.y;
    92   this.distanceTraveled = 0;
    93   this.totalDistanceTraveled = 0;
    94 }
    96 Point.prototype = {
    97   /**
    98    * Update the current point coordiates.
    99    * @param  {Object} aPoint A new point coordinates.
   100    */
   101   update: function Point_update(aPoint) {
   102     let lastX = this.x;
   103     let lastY = this.y;
   104     this.x = aPoint.x;
   105     this.y = aPoint.y;
   106     this.distanceTraveled = this.getDistanceToCoord(lastX, lastY);
   107     this.totalDistanceTraveled += this.distanceTraveled;
   108   },
   110   reset: function Point_reset() {
   111     this.distanceTraveled = 0;
   112     this.totalDistanceTraveled = 0;
   113   },
   115   /**
   116    * Get distance between the current point coordinates and the given ones.
   117    * @param  {Number} aX A pixel value for the x coordinate.
   118    * @param  {Number} aY A pixel value for the y coordinate.
   119    * @return {Number} A distance between point's current and the given
   120    * coordinates.
   121    */
   122   getDistanceToCoord: function Point_getDistanceToCoord(aX, aY) {
   123     return Math.hypot(this.x - aX, this.y - aY);
   124   },
   126   /**
   127    * Get the direct distance travelled by the point so far.
   128    */
   129   get directDistanceTraveled() {
   130     return this.getDistanceToCoord(this.startX, this.startY);
   131   }
   132 };
   134 /**
   135  * An externally accessible collection of settings used in gesture resolition.
   136  * @type {Object}
   137  */
   138 this.GestureSettings = { // jshint ignore:line
   139   /**
   140    * Maximum duration of swipe
   141    * @type {Number}
   142    */
   143   swipeMaxDuration: 400,
   145   /**
   146    * Maximum consecutive pointer event timeout.
   147    * @type {Number}
   148    */
   149   maxConsecutiveGestureDelay: 400,
   151   /**
   152    * Delay before tap turns into dwell
   153    * @type {Number}
   154    */
   155   dwellThreshold: 500,
   157   /**
   158    * Minimum distance that needs to be travelled for the pointer move to be
   159    * fired.
   160    * @type {Number}
   161    */
   162   travelThreshold: 0.025
   163 };
   165 /**
   166  * An interface that handles the pointer events and calculates the appropriate
   167  * gestures.
   168  * @type {Object}
   169  */
   170 this.GestureTracker = { // jshint ignore:line
   171   /**
   172    * Reset GestureTracker to its initial state.
   173    * @return {[type]} [description]
   174    */
   175   reset: function GestureTracker_reset() {
   176     if (this.current) {
   177       this.current.clearTimer();
   178     }
   179     delete this.current;
   180   },
   182   /**
   183    * Create a new gesture object and attach resolution handler to it as well as
   184    * handle the incoming pointer event.
   185    * @param  {Object} aDetail A new pointer event detail.
   186    * @param  {Number} aTimeStamp A new pointer event timeStamp.
   187    * @param  {Function} aGesture A gesture constructor (default: Tap).
   188    */
   189   _init: function GestureTracker__init(aDetail, aTimeStamp, aGesture = Tap) {
   190     // Only create a new gesture on |pointerdown| event.
   191     if (aDetail.type !== 'pointerdown') {
   192       return;
   193     }
   194     let points = aDetail.points;
   195     let GestureConstructor = aGesture;
   196     if (IS_ANDROID && GestureConstructor === Tap && points.length === 1 &&
   197       points[0].identifier !== MOUSE_ID) {
   198       // Handle Android events when EBT is enabled. Two finger gestures are
   199       // translated to one.
   200       GestureConstructor = AndroidTap;
   201     }
   202     this._create(GestureConstructor);
   203     this._update(aDetail, aTimeStamp);
   204   },
   206   /**
   207    * Handle the incoming pointer event with the existing gesture object(if
   208    * present) or with the newly created one.
   209    * @param  {Object} aDetail A new pointer event detail.
   210    * @param  {Number} aTimeStamp A new pointer event timeStamp.
   211    */
   212   handle: function GestureTracker_handle(aDetail, aTimeStamp) {
   213     Logger.debug(() => {
   214       return ['Pointer event', aDetail.type, 'at:', aTimeStamp,
   215         JSON.stringify(aDetail.points)];
   216     });
   217     this[this.current ? '_update' : '_init'](aDetail, aTimeStamp);
   218   },
   220   /**
   221    * Create a new gesture object and attach resolution handler to it.
   222    * @param  {Function} aGesture A gesture constructor.
   223    * @param  {Number} aTimeStamp An original pointer event timeStamp.
   224    * @param  {Array} aPoints All changed points associated with the new pointer
   225    * event.
   226    * @param {?String} aLastEvent Last pointer event type.
   227    */
   228   _create: function GestureTracker__create(aGesture, aTimeStamp, aPoints, aLastEvent) {
   229     this.current = new aGesture(aTimeStamp, aPoints, aLastEvent); /* A constructor name should start with an uppercase letter. */ // jshint ignore:line
   230     this.current.then(this._onFulfill.bind(this));
   231   },
   233   /**
   234    * Handle the incoming pointer event with the existing gesture object.
   235    * @param  {Object} aDetail A new pointer event detail.
   236    * @param  {Number} aTimeStamp A new pointer event timeStamp.
   237    */
   238   _update: function GestureTracker_update(aDetail, aTimeStamp) {
   239     this.current[aDetail.type](aDetail.points, aTimeStamp);
   240   },
   242   /**
   243    * A resolution handler function for the current gesture promise.
   244    * @param  {Object} aResult A resolution payload with the relevant gesture id
   245    * and an optional new gesture contructor.
   246    */
   247   _onFulfill: function GestureTracker__onFulfill(aResult) {
   248     let {id, gestureType} = aResult;
   249     let current = this.current;
   250     // Do nothing if there's no existing gesture or there's already a newer
   251     // gesture.
   252     if (!current || current.id !== id) {
   253       return;
   254     }
   255     // Only create a gesture if we got a constructor.
   256     if (gestureType) {
   257       this._create(gestureType, current.startTime, current.points,
   258         current.lastEvent);
   259     } else {
   260       delete this.current;
   261     }
   262   }
   263 };
   265 /**
   266  * Compile a mozAccessFuGesture detail structure.
   267  * @param  {String} aType A gesture type.
   268  * @param  {Object} aPoints Gesture's points.
   269  * @param  {String} xKey A default key for the x coordinate. Default is
   270  * 'startX'.
   271  * @param  {String} yKey A default key for the y coordinate. Default is
   272  * 'startY'.
   273  * @return {Object} a mozAccessFuGesture detail structure.
   274  */
   275 function compileDetail(aType, aPoints, keyMap = {x: 'startX', y: 'startY'}) {
   276   let touches = [];
   277   let maxDeltaX = 0;
   278   let maxDeltaY = 0;
   279   for (let identifier in aPoints) {
   280     let point = aPoints[identifier];
   281     let touch = {};
   282     for (let key in keyMap) {
   283       touch[key] = point[keyMap[key]];
   284     }
   285     touches.push(touch);
   286     let deltaX = point.x - point.startX;
   287     let deltaY = point.y - point.startY;
   288     // Determine the maximum x and y travel intervals.
   289     if (Math.abs(maxDeltaX) < Math.abs(deltaX)) {
   290       maxDeltaX = deltaX;
   291     }
   292     if (Math.abs(maxDeltaY) < Math.abs(deltaY)) {
   293       maxDeltaY = deltaY;
   294     }
   295     // Since the gesture is resolving, reset the points' distance information
   296     // since they are passed to the next potential gesture.
   297     point.reset();
   298   }
   299   return {
   300     type: aType,
   301     touches: touches,
   302     deltaX: maxDeltaX,
   303     deltaY: maxDeltaY
   304   };
   305 }
   307 /**
   308  * A general gesture object.
   309  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
   310  * the gesture resolution sequence.
   311  * @param {Object} aPoints An existing set of points (from previous events).
   312  * Default is an empty object.
   313  * @param {?String} aLastEvent Last pointer event type.
   314  */
   315 function Gesture(aTimeStamp, aPoints = {}, aLastEvent = undefined) {
   316   this.startTime = Date.now();
   317   Logger.debug('Creating', this.id, 'gesture.');
   318   this.points = aPoints;
   319   this.lastEvent = aLastEvent;
   320   this._deferred = Promise.defer();
   321   // Call this._handleResolve or this._handleReject when the promise is
   322   // fulfilled with either resolve or reject.
   323   this.promise = this._deferred.promise.then(this._handleResolve.bind(this),
   324     this._handleReject.bind(this));
   325   this.startTimer(aTimeStamp);
   326 }
   328 Gesture.prototype = {
   329   /**
   330    * Get the gesture timeout delay.
   331    * @return {Number}
   332    */
   333   _getDelay: function Gesture__getDelay() {
   334     // If nothing happens withing the
   335     // GestureSettings.maxConsecutiveGestureDelay, we should not wait for any
   336     // more pointer events and consider them the part of the same gesture -
   337     // reject this gesture promise.
   338     return GestureSettings.maxConsecutiveGestureDelay;
   339   },
   341   /**
   342    * Clear the existing timer.
   343    */
   344   clearTimer: function Gesture_clearTimer() {
   345     clearTimeout(this._timer);
   346     delete this._timer;
   347   },
   349   /**
   350    * Start the timer for gesture timeout.
   351    * @param {Number} aTimeStamp An original pointer event's timeStamp that
   352    * started the gesture resolution sequence.
   353    */
   354   startTimer: function Gesture_startTimer(aTimeStamp) {
   355     this.clearTimer();
   356     let delay = this._getDelay(aTimeStamp);
   357     let handler = () => {
   358       delete this._timer;
   359       if (!this._inProgress) {
   360         this._deferred.reject();
   361       } else if (this._rejectToOnWait) {
   362         this._deferred.reject(this._rejectToOnWait);
   363       }
   364     };
   365     if (delay <= 0) {
   366       handler();
   367     } else {
   368       this._timer = setTimeout(handler, delay);
   369     }
   370   },
   372   /**
   373    * Add a gesture promise resolution callback.
   374    * @param  {Function} aCallback
   375    */
   376   then: function Gesture_then(aCallback) {
   377     this.promise.then(aCallback);
   378   },
   380   /**
   381    * Update gesture's points. Test the points set with the optional gesture test
   382    * function.
   383    * @param  {Array} aPoints An array with the changed points from the new
   384    * pointer event.
   385    * @param {String} aType Pointer event type.
   386    * @param  {Boolean} aCanCreate A flag that enables including the new points.
   387    * Default is false.
   388    * @param  {Boolean} aNeedComplete A flag that indicates that the gesture is
   389    * completing. Default is false.
   390    * @return {Boolean} Indicates whether the gesture can be complete (it is
   391    * set to true iff the aNeedComplete is true and there was a change to at
   392    * least one point that belongs to the gesture).
   393    */
   394   _update: function Gesture__update(aPoints, aType, aCanCreate = false, aNeedComplete = false) {
   395     let complete;
   396     let lastEvent;
   397     for (let point of aPoints) {
   398       let identifier = point.identifier;
   399       let gesturePoint = this.points[identifier];
   400       if (gesturePoint) {
   401         gesturePoint.update(point);
   402         if (aNeedComplete) {
   403           // Since the gesture is completing and at least one of the gesture
   404           // points is updated, set the return value to true.
   405           complete = true;
   406         }
   407         lastEvent = lastEvent || aType;
   408       } else if (aCanCreate) {
   409         // Only create a new point if aCanCreate is true.
   410         this.points[identifier] =
   411           new Point(point);
   412         lastEvent = lastEvent || aType;
   413       }
   414     }
   415     this.lastEvent = lastEvent || this.lastEvent;
   416     // If test function is defined test the points.
   417     if (this.test) {
   418       this.test(complete);
   419     }
   420     return complete;
   421   },
   423   /**
   424    * Emit a mozAccessFuGesture (when the gesture is resolved).
   425    * @param  {Object} aDetail a compiled mozAccessFuGesture detail structure.
   426    */
   427   _emit: function Gesture__emit(aDetail) {
   428     let evt = new Utils.win.CustomEvent('mozAccessFuGesture', {
   429       bubbles: true,
   430       cancelable: true,
   431       detail: aDetail
   432     });
   433     Utils.win.dispatchEvent(evt);
   434   },
   436   /**
   437    * Handle the pointer down event.
   438    * @param  {Array} aPoints A new pointer down points.
   439    * @param  {Number} aTimeStamp A new pointer down timeStamp.
   440    */
   441   pointerdown: function Gesture_pointerdown(aPoints, aTimeStamp) {
   442     this._inProgress = true;
   443     this._update(aPoints, 'pointerdown',
   444       aTimeStamp - this.startTime < MAX_MULTITOUCH);
   445   },
   447   /**
   448    * Handle the pointer move event.
   449    * @param  {Array} aPoints A new pointer move points.
   450    */
   451   pointermove: function Gesture_pointermove(aPoints) {
   452     this._update(aPoints, 'pointermove');
   453   },
   455   /**
   456    * Handle the pointer up event.
   457    * @param  {Array} aPoints A new pointer up points.
   458    */
   459   pointerup: function Gesture_pointerup(aPoints) {
   460     let complete = this._update(aPoints, 'pointerup', false, true);
   461     if (complete) {
   462       this._deferred.resolve();
   463     }
   464   },
   466   /**
   467    * A subsequent gesture constructor to resolve the current one to. E.g.
   468    * tap->doubletap, dwell->dwellend, etc.
   469    * @type {Function}
   470    */
   471   resolveTo: null,
   473   /**
   474    * A unique id for the gesture. Composed of the type + timeStamp.
   475    */
   476   get id() {
   477     delete this._id;
   478     this._id = this.type + this.startTime;
   479     return this._id;
   480   },
   482   /**
   483    * A gesture promise resolve callback. Compile and emit the gesture.
   484    * @return {Object} Returns a structure to the gesture handler that looks like
   485    * this: {
   486    *   id: current gesture id,
   487    *   gestureType: an optional subsequent gesture constructor.
   488    * }
   489    */
   490   _handleResolve: function Gesture__handleResolve() {
   491     if (this.isComplete) {
   492       return;
   493     }
   494     Logger.debug('Resolving', this.id, 'gesture.');
   495     this.isComplete = true;
   496     let detail = this.compile();
   497     if (detail) {
   498       this._emit(detail);
   499     }
   500     return {
   501       id: this.id,
   502       gestureType: this.resolveTo
   503     };
   504   },
   506   /**
   507    * A gesture promise reject callback.
   508    * @return {Object} Returns a structure to the gesture handler that looks like
   509    * this: {
   510    *   id: current gesture id,
   511    *   gestureType: an optional subsequent gesture constructor.
   512    * }
   513    */
   514   _handleReject: function Gesture__handleReject(aRejectTo) {
   515     if (this.isComplete) {
   516       return;
   517     }
   518     Logger.debug('Rejecting', this.id, 'gesture.');
   519     this.isComplete = true;
   520     return {
   521       id: this.id,
   522       gestureType: aRejectTo
   523     };
   524   },
   526   /**
   527    * A default compilation function used to build the mozAccessFuGesture event
   528    * detail. The detail always includes the type and the touches associated
   529    * with the gesture.
   530    * @return {Object} Gesture event detail.
   531    */
   532   compile: function Gesture_compile() {
   533     return compileDetail(this.type, this.points);
   534   }
   535 };
   537 /**
   538  * A mixin for an explore related object.
   539  */
   540 function ExploreGesture() {
   541   this.compile = () => {
   542     // Unlike most of other gestures explore based gestures compile using the
   543     // current point position and not the start one.
   544     return compileDetail(this.type, this.points, {x: 'x', y: 'y'});
   545   };
   546 }
   548 /**
   549  * Check the in progress gesture for completion.
   550  */
   551 function checkProgressGesture(aGesture) {
   552   aGesture._inProgress = true;
   553   if (aGesture.lastEvent === 'pointerup') {
   554     if (aGesture.test) {
   555       aGesture.test(true);
   556     }
   557     aGesture._deferred.resolve();
   558   }
   559 }
   561 /**
   562  * A common travel gesture. When the travel gesture is created, all subsequent
   563  * pointer events' points are tested for their total distance traveled. If that
   564  * distance exceeds the _threshold distance, the gesture will be rejected to a
   565  * _travelTo gesture.
   566  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
   567  * the gesture resolution sequence.
   568  * @param {Object} aPoints An existing set of points (from previous events).
   569  * @param {?String} aLastEvent Last pointer event type.
   570  * @param {Function} aTravelTo A contructor for the gesture to reject to when
   571  * travelling (default: Explore).
   572  * @param {Number} aThreshold Travel threshold (default:
   573  * GestureSettings.travelThreshold).
   574  */
   575 function TravelGesture(aTimeStamp, aPoints, aLastEvent, aTravelTo = Explore, aThreshold = GestureSettings.travelThreshold) {
   576   Gesture.call(this, aTimeStamp, aPoints, aLastEvent);
   577   this._travelTo = aTravelTo;
   578   this._threshold = aThreshold;
   579 }
   581 TravelGesture.prototype = Object.create(Gesture.prototype);
   583 /**
   584  * Test the gesture points for travel. The gesture will be rejected to
   585  * this._travelTo gesture iff at least one point crosses this._threshold.
   586  */
   587 TravelGesture.prototype.test = function TravelGesture_test() {
   588   for (let identifier in this.points) {
   589     let point = this.points[identifier];
   590     if (point.totalDistanceTraveled / Utils.dpi > this._threshold) {
   591       this._deferred.reject(this._travelTo);
   592       return;
   593     }
   594   }
   595 };
   597 /**
   598  * DwellEnd gesture.
   599  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
   600  * the gesture resolution sequence.
   601  * @param {Object} aPoints An existing set of points (from previous events).
   602  * @param {?String} aLastEvent Last pointer event type.
   603  */
   604 function DwellEnd(aTimeStamp, aPoints, aLastEvent) {
   605   this._inProgress = true;
   606   // If the pointer travels, reject to Explore.
   607   TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
   608   checkProgressGesture(this);
   609 }
   611 DwellEnd.prototype = Object.create(TravelGesture.prototype);
   612 DwellEnd.prototype.type = 'dwellend';
   614 /**
   615  * TapHoldEnd gesture. This gesture can be represented as the following diagram:
   616  * pointerdown-pointerup-pointerdown-*wait*-pointerup.
   617  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
   618  * the gesture resolution sequence.
   619  * @param {Object} aPoints An existing set of points (from previous events).
   620  * @param {?String} aLastEvent Last pointer event type.
   621  */
   622 function TapHoldEnd(aTimeStamp, aPoints, aLastEvent) {
   623   this._inProgress = true;
   624   // If the pointer travels, reject to Explore.
   625   TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
   626   checkProgressGesture(this);
   627 }
   629 TapHoldEnd.prototype = Object.create(TravelGesture.prototype);
   630 TapHoldEnd.prototype.type = 'tapholdend';
   632 /**
   633  * DoubleTapHoldEnd gesture. This gesture can be represented as the following
   634  * diagram:
   635  * pointerdown-pointerup-pointerdown-pointerup-pointerdown-*wait*-pointerup.
   636  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
   637  * the gesture resolution sequence.
   638  * @param {Object} aPoints An existing set of points (from previous events).
   639  * @param {?String} aLastEvent Last pointer event type.
   640  */
   641 function DoubleTapHoldEnd(aTimeStamp, aPoints, aLastEvent) {
   642   this._inProgress = true;
   643   // If the pointer travels, reject to Explore.
   644   TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
   645   checkProgressGesture(this);
   646 }
   648 DoubleTapHoldEnd.prototype = Object.create(TravelGesture.prototype);
   649 DoubleTapHoldEnd.prototype.type = 'doubletapholdend';
   651 /**
   652  * A common tap gesture object.
   653  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
   654  * the gesture resolution sequence.
   655  * @param {Object} aPoints An existing set of points (from previous events).
   656  * @param {?String} aLastEvent Last pointer event type.
   657  * @param {Function} aRejectTo A constructor for the next gesture to reject to
   658  * in case no pointermove or pointerup happens within the
   659  * GestureSettings.dwellThreshold.
   660  * @param {Function} aTravelTo An optional constuctor for the next gesture to
   661  * reject to in case the the TravelGesture test fails.
   662  */
   663 function TapGesture(aTimeStamp, aPoints, aLastEvent, aRejectTo, aTravelTo) {
   664   this._rejectToOnWait = aRejectTo;
   665   // If the pointer travels, reject to aTravelTo.
   666   TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent, aTravelTo,
   667     TAP_MAX_RADIUS);
   668 }
   670 TapGesture.prototype = Object.create(TravelGesture.prototype);
   671 TapGesture.prototype._getDelay = function TapGesture__getDelay() {
   672   // If, for TapGesture, no pointermove or pointerup happens within the
   673   // GestureSettings.dwellThreshold, reject.
   674   // Note: the original pointer event's timeStamp is irrelevant here.
   675   return GestureSettings.dwellThreshold;
   676 };
   678 /**
   679  * Tap gesture.
   680  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
   681  * the gesture resolution sequence.
   682  * @param {Object} aPoints An existing set of points (from previous events).
   683  * @param {?String} aLastEvent Last pointer event type.
   684  */
   685 function Tap(aTimeStamp, aPoints, aLastEvent) {
   686   // If the pointer travels, reject to Swipe.
   687   TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, Dwell, Swipe);
   688 }
   690 Tap.prototype = Object.create(TapGesture.prototype);
   691 Tap.prototype.type = 'tap';
   692 Tap.prototype.resolveTo = DoubleTap;
   694 /**
   695  * Tap (multi) gesture on Android.
   696  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
   697  * the gesture resolution sequence.
   698  * @param {Object} aPoints An existing set of points (from previous events).
   699  * @param {?String} aLastEvent Last pointer event type.
   700  */
   701 function AndroidTap(aTimeStamp, aPoints, aLastEvent) {
   702   // If the pointer travels, reject to Swipe. On dwell threshold reject to
   703   // TapHold.
   704   TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, TapHold, Swipe);
   705 }
   706 AndroidTap.prototype = Object.create(TapGesture.prototype);
   707 // Android double taps are translated to single taps.
   708 AndroidTap.prototype.type = 'doubletap';
   709 AndroidTap.prototype.resolveTo = TripleTap;
   711 /**
   712  * Clear the pointerup handler timer in case of the 3 pointer swipe.
   713  */
   714 AndroidTap.prototype.clearThreeFingerSwipeTimer = function AndroidTap_clearThreeFingerSwipeTimer() {
   715   clearTimeout(this._threeFingerSwipeTimer);
   716   delete this._threeFingerSwipeTimer;
   717 };
   719 AndroidTap.prototype.pointerdown = function AndroidTap_pointerdown(aPoints, aTimeStamp) {
   720   this.clearThreeFingerSwipeTimer();
   721   TapGesture.prototype.pointerdown.call(this, aPoints, aTimeStamp);
   722 };
   724 AndroidTap.prototype.pointermove = function AndroidTap_pointermove(aPoints) {
   725   this.clearThreeFingerSwipeTimer();
   726   this._moved = true;
   727   TapGesture.prototype.pointermove.call(this, aPoints);
   728 };
   730 AndroidTap.prototype.pointerup = function AndroidTap_pointerup(aPoints) {
   731   if (this._moved) {
   732     // If there was a pointer move - handle the real gesture.
   733     TapGesture.prototype.pointerup.call(this, aPoints);
   734   } else {
   735     // Primptively delay the multi pointer gesture resolution, because Android
   736     // sometimes fires a pointerdown/poitnerup sequence before the real events.
   737     this._threeFingerSwipeTimer = setTimeout(() => {
   738       delete this._threeFingerSwipeTimer;
   739       TapGesture.prototype.pointerup.call(this, aPoints);
   740     }, ANDROID_TRIPLE_SWIPE_DELAY);
   741   }
   742 };
   744 /**
   745  * Reject an android tap gesture.
   746  * @param  {?Function} aRejectTo An optional next gesture constructor.
   747  * @return {Object} structure that looks like {
   748  *   id: gesture_id, // Current AndroidTap gesture id.
   749  *   gestureType: next_gesture // Optional
   750  * }
   751  */
   752 AndroidTap.prototype._handleReject = function AndroidTap__handleReject(aRejectTo) {
   753   let keys = Object.keys(this.points);
   754   if (aRejectTo === Swipe && keys.length === 1) {
   755     let key = keys[0];
   756     let point = this.points[key];
   757     // Two finger swipe is translated into single swipe.
   758     this.points[key + '-copy'] = point;
   759   }
   760   return TapGesture.prototype._handleReject.call(this, aRejectTo);
   761 };
   763 /**
   764  * Double Tap gesture.
   765  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
   766  * the gesture resolution sequence.
   767  * @param {Object} aPoints An existing set of points (from previous events).
   768  * @param {?String} aLastEvent Last pointer event type.
   769  */
   770 function DoubleTap(aTimeStamp, aPoints, aLastEvent) {
   771   TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, TapHold);
   772 }
   774 DoubleTap.prototype = Object.create(TapGesture.prototype);
   775 DoubleTap.prototype.type = 'doubletap';
   776 DoubleTap.prototype.resolveTo = TripleTap;
   778 /**
   779  * Triple Tap gesture.
   780  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
   781  * the gesture resolution sequence.
   782  * @param {Object} aPoints An existing set of points (from previous events).
   783  * @param {?String} aLastEvent Last pointer event type.
   784  */
   785 function TripleTap(aTimeStamp, aPoints, aLastEvent) {
   786   TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, DoubleTapHold);
   787 }
   789 TripleTap.prototype = Object.create(TapGesture.prototype);
   790 TripleTap.prototype.type = 'tripletap';
   792 /**
   793  * Common base object for gestures that are created as resolved.
   794  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
   795  * the gesture resolution sequence.
   796  * @param {Object} aPoints An existing set of points (from previous events).
   797  * @param {?String} aLastEvent Last pointer event type.
   798  */
   799 function ResolvedGesture(aTimeStamp, aPoints, aLastEvent) {
   800   Gesture.call(this, aTimeStamp, aPoints, aLastEvent);
   801   // Resolve the guesture right away.
   802   this._deferred.resolve();
   803 }
   805 ResolvedGesture.prototype = Object.create(Gesture.prototype);
   807 /**
   808  * Dwell gesture
   809  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
   810  * the gesture resolution sequence.
   811  * @param {Object} aPoints An existing set of points (from previous events).
   812  * @param {?String} aLastEvent Last pointer event type.
   813  */
   814 function Dwell(aTimeStamp, aPoints, aLastEvent) {
   815   ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
   816 }
   818 Dwell.prototype = Object.create(ResolvedGesture.prototype);
   819 Dwell.prototype.type = 'dwell';
   820 Dwell.prototype.resolveTo = DwellEnd;
   822 /**
   823  * TapHold gesture
   824  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
   825  * the gesture resolution sequence.
   826  * @param {Object} aPoints An existing set of points (from previous events).
   827  * @param {?String} aLastEvent Last pointer event type.
   828  */
   829 function TapHold(aTimeStamp, aPoints, aLastEvent) {
   830   ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
   831 }
   833 TapHold.prototype = Object.create(ResolvedGesture.prototype);
   834 TapHold.prototype.type = 'taphold';
   835 TapHold.prototype.resolveTo = TapHoldEnd;
   837 /**
   838  * DoubleTapHold gesture
   839  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
   840  * the gesture resolution sequence.
   841  * @param {Object} aPoints An existing set of points (from previous events).
   842  * @param {?String} aLastEvent Last pointer event type.
   843  */
   844 function DoubleTapHold(aTimeStamp, aPoints, aLastEvent) {
   845   ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
   846 }
   848 DoubleTapHold.prototype = Object.create(ResolvedGesture.prototype);
   849 DoubleTapHold.prototype.type = 'doubletaphold';
   850 DoubleTapHold.prototype.resolveTo = DoubleTapHoldEnd;
   852 /**
   853  * Explore gesture
   854  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
   855  * the gesture resolution sequence.
   856  * @param {Object} aPoints An existing set of points (from previous events).
   857  * @param {?String} aLastEvent Last pointer event type.
   858  */
   859 function Explore(aTimeStamp, aPoints, aLastEvent) {
   860   ExploreGesture.call(this);
   861   ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
   862 }
   864 Explore.prototype = Object.create(ResolvedGesture.prototype);
   865 Explore.prototype.type = 'explore';
   866 Explore.prototype.resolveTo = ExploreEnd;
   868 /**
   869  * ExploreEnd gesture.
   870  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
   871  * the gesture resolution sequence.
   872  * @param {Object} aPoints An existing set of points (from previous events).
   873  * @param {?String} aLastEvent Last pointer event type.
   874  */
   875 function ExploreEnd(aTimeStamp, aPoints, aLastEvent) {
   876   this._inProgress = true;
   877   ExploreGesture.call(this);
   878   // If the pointer travels, reject to Explore.
   879   TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
   880   checkProgressGesture(this);
   881 }
   883 ExploreEnd.prototype = Object.create(TravelGesture.prototype);
   884 ExploreEnd.prototype.type = 'exploreend';
   886 /**
   887  * Swipe gesture.
   888  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
   889  * the gesture resolution sequence.
   890  * @param {Object} aPoints An existing set of points (from previous events).
   891  * @param {?String} aLastEvent Last pointer event type.
   892  */
   893 function Swipe(aTimeStamp, aPoints, aLastEvent) {
   894   this._inProgress = true;
   895   this._rejectToOnWait = Explore;
   896   Gesture.call(this, aTimeStamp, aPoints, aLastEvent);
   897   checkProgressGesture(this);
   898 }
   900 Swipe.prototype = Object.create(Gesture.prototype);
   901 Swipe.prototype.type = 'swipe';
   902 Swipe.prototype._getDelay = function Swipe__getDelay(aTimeStamp) {
   903   // Swipe should be completed within the GestureSettings.swipeMaxDuration from
   904   // the initial pointer down event.
   905   return GestureSettings.swipeMaxDuration - this.startTime + aTimeStamp;
   906 };
   908 /**
   909  * Determine wither the gesture was Swipe or Explore.
   910  * @param  {Booler} aComplete A flag that indicates whether the gesture is and
   911  * will be complete after the test.
   912  */
   913 Swipe.prototype.test = function Swipe_test(aComplete) {
   914   if (!aComplete) {
   915     // No need to test if the gesture is not completing or can't be complete.
   916     return;
   917   }
   918   let reject = true;
   919   // If at least one point travelled for more than SWIPE_MIN_DISTANCE and it was
   920   // direct enough, consider it a Swipe.
   921   for (let identifier in this.points) {
   922     let point = this.points[identifier];
   923     let directDistance = point.directDistanceTraveled;
   924     if (directDistance / Utils.dpi >= SWIPE_MIN_DISTANCE ||
   925       directDistance * DIRECTNESS_COEFF >= point.totalDistanceTraveled) {
   926       reject = false;
   927     }
   928   }
   929   if (reject) {
   930     this._deferred.reject(Explore);
   931   }
   932 };
   934 /**
   935  * Compile a swipe related mozAccessFuGesture event detail.
   936  * @return {Object} A mozAccessFuGesture detail object.
   937  */
   938 Swipe.prototype.compile = function Swipe_compile() {
   939   let type = this.type;
   940   let detail = compileDetail(type, this.points,
   941     {x1: 'startX', y1: 'startY', x2: 'x', y2: 'y'});
   942   let deltaX = detail.deltaX;
   943   let deltaY = detail.deltaY;
   944   if (Math.abs(deltaX) > Math.abs(deltaY)) {
   945     // Horizontal swipe.
   946     detail.type = type + (deltaX > 0 ? 'right' : 'left');
   947   } else {
   948     // Vertival swipe.
   949     detail.type = type + (deltaY > 0 ? 'down' : 'up');
   950   }
   951   return detail;
   952 };

mercurial