Bumping manifests a=b2g-bump
[gecko.git] / accessible / jsat / Gestures.jsm
blob3d2fd2fde982cf12d3870ab18b04bcb22f339405
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 // Default maximum duration of swipe
65 const SWIPE_MAX_DURATION = 200;
66 // Default maximum amount of time allowed for a gesture to be considered a
67 // multitouch
68 const MAX_MULTITOUCH = 125;
69 // Default maximum consecutive pointer event timeout
70 const MAX_CONSECUTIVE_GESTURE_DELAY = 200;
71 // Default delay before tap turns into dwell
72 const DWELL_THRESHOLD = 250;
73 // Minimal swipe distance in inches
74 const SWIPE_MIN_DISTANCE = 0.4;
75 // Maximum distance the pointer could move during a tap in inches
76 const TAP_MAX_RADIUS = 0.2;
77 // Directness coefficient. It is based on the maximum 15 degree angle between
78 // consequent pointer move lines.
79 const DIRECTNESS_COEFF = 1.44;
80 // An android flag.
81 const IS_ANDROID = Utils.MozBuildApp === 'mobile/android' &&
82   Utils.AndroidSdkVersion >= 14;
83 // A single pointer down/up sequence periodically precedes the tripple swipe
84 // gesture on Android. This delay acounts for that.
85 const ANDROID_TRIPLE_SWIPE_DELAY = 50;
86 // The virtual touch ID generated by a mouse event.
87 const MOUSE_ID = 'mouse';
88 // Amount in inches from the edges of the screen for it to be an edge swipe
89 const EDGE = 0.1;
90 // Multiply timeouts by this constant, x2 works great too for slower users.
91 const TIMEOUT_MULTIPLIER = 1;
93 /**
94  * A point object containing distance travelled data.
95  * @param {Object} aPoint A point object that looks like: {
96  *   x: x coordinate in pixels,
97  *   y: y coordinate in pixels
98  * }
99  */
100 function Point(aPoint) {
101   this.startX = this.x = aPoint.x;
102   this.startY = this.y = aPoint.y;
103   this.distanceTraveled = 0;
104   this.totalDistanceTraveled = 0;
107 Point.prototype = {
108   /**
109    * Update the current point coordiates.
110    * @param  {Object} aPoint A new point coordinates.
111    */
112   update: function Point_update(aPoint) {
113     let lastX = this.x;
114     let lastY = this.y;
115     this.x = aPoint.x;
116     this.y = aPoint.y;
117     this.distanceTraveled = this.getDistanceToCoord(lastX, lastY);
118     this.totalDistanceTraveled += this.distanceTraveled;
119   },
121   reset: function Point_reset() {
122     this.distanceTraveled = 0;
123     this.totalDistanceTraveled = 0;
124   },
126   /**
127    * Get distance between the current point coordinates and the given ones.
128    * @param  {Number} aX A pixel value for the x coordinate.
129    * @param  {Number} aY A pixel value for the y coordinate.
130    * @return {Number} A distance between point's current and the given
131    * coordinates.
132    */
133   getDistanceToCoord: function Point_getDistanceToCoord(aX, aY) {
134     return Math.hypot(this.x - aX, this.y - aY);
135   },
137   /**
138    * Get the direct distance travelled by the point so far.
139    */
140   get directDistanceTraveled() {
141     return this.getDistanceToCoord(this.startX, this.startY);
142   }
146  * An externally accessible collection of settings used in gesture resolition.
147  * @type {Object}
148  */
149 this.GestureSettings = { // jshint ignore:line
150   /**
151    * Maximum duration of swipe
152    * @type {Number}
153    */
154   swipeMaxDuration: SWIPE_MAX_DURATION * TIMEOUT_MULTIPLIER,
156   /**
157    * Maximum amount of time allowed for a gesture to be considered a multitouch.
158    * @type {Number}
159    */
160   maxMultitouch: MAX_MULTITOUCH * TIMEOUT_MULTIPLIER,
162   /**
163    * Maximum consecutive pointer event timeout.
164    * @type {Number}
165    */
166   maxConsecutiveGestureDelay:
167     MAX_CONSECUTIVE_GESTURE_DELAY * TIMEOUT_MULTIPLIER,
169   /**
170    * Delay before tap turns into dwell
171    * @type {Number}
172    */
173   dwellThreshold: DWELL_THRESHOLD * TIMEOUT_MULTIPLIER,
175   /**
176    * Minimum distance that needs to be travelled for the pointer move to be
177    * fired.
178    * @type {Number}
179    */
180   travelThreshold: 0.025
184  * An interface that handles the pointer events and calculates the appropriate
185  * gestures.
186  * @type {Object}
187  */
188 this.GestureTracker = { // jshint ignore:line
189   /**
190    * Reset GestureTracker to its initial state.
191    * @return {[type]} [description]
192    */
193   reset: function GestureTracker_reset() {
194     if (this.current) {
195       this.current.clearTimer();
196     }
197     delete this.current;
198   },
200   /**
201    * Create a new gesture object and attach resolution handler to it as well as
202    * handle the incoming pointer event.
203    * @param  {Object} aDetail A new pointer event detail.
204    * @param  {Number} aTimeStamp A new pointer event timeStamp.
205    * @param  {Function} aGesture A gesture constructor (default: Tap).
206    */
207   _init: function GestureTracker__init(aDetail, aTimeStamp, aGesture = Tap) {
208     // Only create a new gesture on |pointerdown| event.
209     if (aDetail.type !== 'pointerdown') {
210       return;
211     }
212     let points = aDetail.points;
213     let GestureConstructor = aGesture;
214     if (IS_ANDROID && GestureConstructor === Tap && points.length === 1 &&
215       points[0].identifier !== MOUSE_ID) {
216       // Handle Android events when EBT is enabled. Two finger gestures are
217       // translated to one.
218       GestureConstructor = AndroidTap;
219     }
220     this._create(GestureConstructor);
221     this._update(aDetail, aTimeStamp);
222   },
224   /**
225    * Handle the incoming pointer event with the existing gesture object(if
226    * present) or with the newly created one.
227    * @param  {Object} aDetail A new pointer event detail.
228    * @param  {Number} aTimeStamp A new pointer event timeStamp.
229    */
230   handle: function GestureTracker_handle(aDetail, aTimeStamp) {
231     Logger.gesture(() => {
232       return ['Pointer event', aDetail.type, 'at:', aTimeStamp,
233         JSON.stringify(aDetail.points)];
234     });
235     this[this.current ? '_update' : '_init'](aDetail, aTimeStamp);
236   },
238   /**
239    * Create a new gesture object and attach resolution handler to it.
240    * @param  {Function} aGesture A gesture constructor.
241    * @param  {Number} aTimeStamp An original pointer event timeStamp.
242    * @param  {Array} aPoints All changed points associated with the new pointer
243    * event.
244    * @param {?String} aLastEvent Last pointer event type.
245    */
246   _create: function GestureTracker__create(aGesture, aTimeStamp, aPoints, aLastEvent) {
247     this.current = new aGesture(aTimeStamp, aPoints, aLastEvent); /* A constructor name should start with an uppercase letter. */ // jshint ignore:line
248     this.current.then(this._onFulfill.bind(this));
249   },
251   /**
252    * Handle the incoming pointer event with the existing gesture object.
253    * @param  {Object} aDetail A new pointer event detail.
254    * @param  {Number} aTimeStamp A new pointer event timeStamp.
255    */
256   _update: function GestureTracker_update(aDetail, aTimeStamp) {
257     this.current[aDetail.type](aDetail.points, aTimeStamp);
258   },
260   /**
261    * A resolution handler function for the current gesture promise.
262    * @param  {Object} aResult A resolution payload with the relevant gesture id
263    * and an optional new gesture contructor.
264    */
265   _onFulfill: function GestureTracker__onFulfill(aResult) {
266     let {id, gestureType} = aResult;
267     let current = this.current;
268     // Do nothing if there's no existing gesture or there's already a newer
269     // gesture.
270     if (!current || current.id !== id) {
271       return;
272     }
273     // Only create a gesture if we got a constructor.
274     if (gestureType) {
275       this._create(gestureType, current.startTime, current.points,
276         current.lastEvent);
277     } else {
278       delete this.current;
279     }
280   }
284  * Compile a mozAccessFuGesture detail structure.
285  * @param  {String} aType A gesture type.
286  * @param  {Object} aPoints Gesture's points.
287  * @param  {String} xKey A default key for the x coordinate. Default is
288  * 'startX'.
289  * @param  {String} yKey A default key for the y coordinate. Default is
290  * 'startY'.
291  * @return {Object} a mozAccessFuGesture detail structure.
292  */
293 function compileDetail(aType, aPoints, keyMap = {x: 'startX', y: 'startY'}) {
294   let touches = [];
295   let maxDeltaX = 0;
296   let maxDeltaY = 0;
297   for (let identifier in aPoints) {
298     let point = aPoints[identifier];
299     let touch = {};
300     for (let key in keyMap) {
301       touch[key] = point[keyMap[key]];
302     }
303     touches.push(touch);
304     let deltaX = point.x - point.startX;
305     let deltaY = point.y - point.startY;
306     // Determine the maximum x and y travel intervals.
307     if (Math.abs(maxDeltaX) < Math.abs(deltaX)) {
308       maxDeltaX = deltaX;
309     }
310     if (Math.abs(maxDeltaY) < Math.abs(deltaY)) {
311       maxDeltaY = deltaY;
312     }
313     // Since the gesture is resolving, reset the points' distance information
314     // since they are passed to the next potential gesture.
315     point.reset();
316   }
317   return {
318     type: aType,
319     touches: touches,
320     deltaX: maxDeltaX,
321     deltaY: maxDeltaY
322   };
326  * A general gesture object.
327  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
328  * the gesture resolution sequence.
329  * @param {Object} aPoints An existing set of points (from previous events).
330  * Default is an empty object.
331  * @param {?String} aLastEvent Last pointer event type.
332  */
333 function Gesture(aTimeStamp, aPoints = {}, aLastEvent = undefined) {
334   this.startTime = Date.now();
335   Logger.gesture('Creating', this.id, 'gesture.');
336   this.points = aPoints;
337   this.lastEvent = aLastEvent;
338   this._deferred = Promise.defer();
339   // Call this._handleResolve or this._handleReject when the promise is
340   // fulfilled with either resolve or reject.
341   this.promise = this._deferred.promise.then(this._handleResolve.bind(this),
342     this._handleReject.bind(this));
343   this.startTimer(aTimeStamp);
346 Gesture.prototype = {
347   /**
348    * Get the gesture timeout delay.
349    * @return {Number}
350    */
351   _getDelay: function Gesture__getDelay() {
352     // If nothing happens withing the
353     // GestureSettings.maxConsecutiveGestureDelay, we should not wait for any
354     // more pointer events and consider them the part of the same gesture -
355     // reject this gesture promise.
356     return GestureSettings.maxConsecutiveGestureDelay;
357   },
359   /**
360    * Clear the existing timer.
361    */
362   clearTimer: function Gesture_clearTimer() {
363     clearTimeout(this._timer);
364     delete this._timer;
365   },
367   /**
368    * Start the timer for gesture timeout.
369    * @param {Number} aTimeStamp An original pointer event's timeStamp that
370    * started the gesture resolution sequence.
371    */
372   startTimer: function Gesture_startTimer(aTimeStamp) {
373     this.clearTimer();
374     let delay = this._getDelay(aTimeStamp);
375     let handler = () => {
376       delete this._timer;
377       if (!this._inProgress) {
378         this._deferred.reject();
379       } else if (this._rejectToOnWait) {
380         this._deferred.reject(this._rejectToOnWait);
381       }
382     };
383     if (delay <= 0) {
384       handler();
385     } else {
386       this._timer = setTimeout(handler, delay);
387     }
388   },
390   /**
391    * Add a gesture promise resolution callback.
392    * @param  {Function} aCallback
393    */
394   then: function Gesture_then(aCallback) {
395     this.promise.then(aCallback);
396   },
398   /**
399    * Update gesture's points. Test the points set with the optional gesture test
400    * function.
401    * @param  {Array} aPoints An array with the changed points from the new
402    * pointer event.
403    * @param {String} aType Pointer event type.
404    * @param  {Boolean} aCanCreate A flag that enables including the new points.
405    * Default is false.
406    * @param  {Boolean} aNeedComplete A flag that indicates that the gesture is
407    * completing. Default is false.
408    * @return {Boolean} Indicates whether the gesture can be complete (it is
409    * set to true iff the aNeedComplete is true and there was a change to at
410    * least one point that belongs to the gesture).
411    */
412   _update: function Gesture__update(aPoints, aType, aCanCreate = false, aNeedComplete = false) {
413     let complete;
414     let lastEvent;
415     for (let point of aPoints) {
416       let identifier = point.identifier;
417       let gesturePoint = this.points[identifier];
418       if (gesturePoint) {
419         gesturePoint.update(point);
420         if (aNeedComplete) {
421           // Since the gesture is completing and at least one of the gesture
422           // points is updated, set the return value to true.
423           complete = true;
424         }
425         lastEvent = lastEvent || aType;
426       } else if (aCanCreate) {
427         // Only create a new point if aCanCreate is true.
428         this.points[identifier] =
429           new Point(point);
430         lastEvent = lastEvent || aType;
431       }
432     }
433     this.lastEvent = lastEvent || this.lastEvent;
434     // If test function is defined test the points.
435     if (this.test) {
436       this.test(complete);
437     }
438     return complete;
439   },
441   /**
442    * Emit a mozAccessFuGesture (when the gesture is resolved).
443    * @param  {Object} aDetail a compiled mozAccessFuGesture detail structure.
444    */
445   _emit: function Gesture__emit(aDetail) {
446     let evt = new Utils.win.CustomEvent('mozAccessFuGesture', {
447       bubbles: true,
448       cancelable: true,
449       detail: aDetail
450     });
451     Utils.win.dispatchEvent(evt);
452   },
454   /**
455    * Handle the pointer down event.
456    * @param  {Array} aPoints A new pointer down points.
457    * @param  {Number} aTimeStamp A new pointer down timeStamp.
458    */
459   pointerdown: function Gesture_pointerdown(aPoints, aTimeStamp) {
460     this._inProgress = true;
461     this._update(aPoints, 'pointerdown',
462       aTimeStamp - this.startTime < GestureSettings.maxMultitouch);
463   },
465   /**
466    * Handle the pointer move event.
467    * @param  {Array} aPoints A new pointer move points.
468    */
469   pointermove: function Gesture_pointermove(aPoints) {
470     this._update(aPoints, 'pointermove');
471   },
473   /**
474    * Handle the pointer up event.
475    * @param  {Array} aPoints A new pointer up points.
476    */
477   pointerup: function Gesture_pointerup(aPoints) {
478     let complete = this._update(aPoints, 'pointerup', false, true);
479     if (complete) {
480       this._deferred.resolve();
481     }
482   },
484   /**
485    * A subsequent gesture constructor to resolve the current one to. E.g.
486    * tap->doubletap, dwell->dwellend, etc.
487    * @type {Function}
488    */
489   resolveTo: null,
491   /**
492    * A unique id for the gesture. Composed of the type + timeStamp.
493    */
494   get id() {
495     delete this._id;
496     this._id = this.type + this.startTime;
497     return this._id;
498   },
500   /**
501    * A gesture promise resolve callback. Compile and emit the gesture.
502    * @return {Object} Returns a structure to the gesture handler that looks like
503    * this: {
504    *   id: current gesture id,
505    *   gestureType: an optional subsequent gesture constructor.
506    * }
507    */
508   _handleResolve: function Gesture__handleResolve() {
509     if (this.isComplete) {
510       return;
511     }
512     Logger.gesture('Resolving', this.id, 'gesture.');
513     this.isComplete = true;
514     let detail = this.compile();
515     if (detail) {
516       this._emit(detail);
517     }
518     return {
519       id: this.id,
520       gestureType: this.resolveTo
521     };
522   },
524   /**
525    * A gesture promise reject callback.
526    * @return {Object} Returns a structure to the gesture handler that looks like
527    * this: {
528    *   id: current gesture id,
529    *   gestureType: an optional subsequent gesture constructor.
530    * }
531    */
532   _handleReject: function Gesture__handleReject(aRejectTo) {
533     if (this.isComplete) {
534       return;
535     }
536     Logger.gesture('Rejecting', this.id, 'gesture.');
537     this.isComplete = true;
538     return {
539       id: this.id,
540       gestureType: aRejectTo
541     };
542   },
544   /**
545    * A default compilation function used to build the mozAccessFuGesture event
546    * detail. The detail always includes the type and the touches associated
547    * with the gesture.
548    * @return {Object} Gesture event detail.
549    */
550   compile: function Gesture_compile() {
551     return compileDetail(this.type, this.points);
552   }
556  * A mixin for an explore related object.
557  */
558 function ExploreGesture() {
559   this.compile = () => {
560     // Unlike most of other gestures explore based gestures compile using the
561     // current point position and not the start one.
562     return compileDetail(this.type, this.points, {x: 'x', y: 'y'});
563   };
567  * Check the in progress gesture for completion.
568  */
569 function checkProgressGesture(aGesture) {
570   aGesture._inProgress = true;
571   if (aGesture.lastEvent === 'pointerup') {
572     if (aGesture.test) {
573       aGesture.test(true);
574     }
575     aGesture._deferred.resolve();
576   }
580  * A common travel gesture. When the travel gesture is created, all subsequent
581  * pointer events' points are tested for their total distance traveled. If that
582  * distance exceeds the _threshold distance, the gesture will be rejected to a
583  * _travelTo gesture.
584  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
585  * the gesture resolution sequence.
586  * @param {Object} aPoints An existing set of points (from previous events).
587  * @param {?String} aLastEvent Last pointer event type.
588  * @param {Function} aTravelTo A contructor for the gesture to reject to when
589  * travelling (default: Explore).
590  * @param {Number} aThreshold Travel threshold (default:
591  * GestureSettings.travelThreshold).
592  */
593 function TravelGesture(aTimeStamp, aPoints, aLastEvent, aTravelTo = Explore, aThreshold = GestureSettings.travelThreshold) {
594   Gesture.call(this, aTimeStamp, aPoints, aLastEvent);
595   this._travelTo = aTravelTo;
596   this._threshold = aThreshold;
599 TravelGesture.prototype = Object.create(Gesture.prototype);
602  * Test the gesture points for travel. The gesture will be rejected to
603  * this._travelTo gesture iff at least one point crosses this._threshold.
604  */
605 TravelGesture.prototype.test = function TravelGesture_test() {
606   for (let identifier in this.points) {
607     let point = this.points[identifier];
608     if (point.totalDistanceTraveled / Utils.dpi > this._threshold) {
609       this._deferred.reject(this._travelTo);
610       return;
611     }
612   }
616  * DwellEnd gesture.
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 DwellEnd(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);
629 DwellEnd.prototype = Object.create(TravelGesture.prototype);
630 DwellEnd.prototype.type = 'dwellend';
633  * TapHoldEnd gesture. This gesture can be represented as the following diagram:
634  * pointerdown-pointerup-pointerdown-*wait*-pointerup.
635  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
636  * the gesture resolution sequence.
637  * @param {Object} aPoints An existing set of points (from previous events).
638  * @param {?String} aLastEvent Last pointer event type.
639  */
640 function TapHoldEnd(aTimeStamp, aPoints, aLastEvent) {
641   this._inProgress = true;
642   // If the pointer travels, reject to Explore.
643   TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
644   checkProgressGesture(this);
647 TapHoldEnd.prototype = Object.create(TravelGesture.prototype);
648 TapHoldEnd.prototype.type = 'tapholdend';
651  * DoubleTapHoldEnd gesture. This gesture can be represented as the following
652  * diagram:
653  * pointerdown-pointerup-pointerdown-pointerup-pointerdown-*wait*-pointerup.
654  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
655  * the gesture resolution sequence.
656  * @param {Object} aPoints An existing set of points (from previous events).
657  * @param {?String} aLastEvent Last pointer event type.
658  */
659 function DoubleTapHoldEnd(aTimeStamp, aPoints, aLastEvent) {
660   this._inProgress = true;
661   // If the pointer travels, reject to Explore.
662   TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
663   checkProgressGesture(this);
666 DoubleTapHoldEnd.prototype = Object.create(TravelGesture.prototype);
667 DoubleTapHoldEnd.prototype.type = 'doubletapholdend';
670  * A common tap gesture object.
671  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
672  * the gesture resolution sequence.
673  * @param {Object} aPoints An existing set of points (from previous events).
674  * @param {?String} aLastEvent Last pointer event type.
675  * @param {Function} aRejectTo A constructor for the next gesture to reject to
676  * in case no pointermove or pointerup happens within the
677  * GestureSettings.dwellThreshold.
678  * @param {Function} aTravelTo An optional constuctor for the next gesture to
679  * reject to in case the the TravelGesture test fails.
680  */
681 function TapGesture(aTimeStamp, aPoints, aLastEvent, aRejectTo, aTravelTo) {
682   this._rejectToOnWait = aRejectTo;
683   // If the pointer travels, reject to aTravelTo.
684   TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent, aTravelTo,
685     TAP_MAX_RADIUS);
688 TapGesture.prototype = Object.create(TravelGesture.prototype);
689 TapGesture.prototype._getDelay = function TapGesture__getDelay() {
690   // If, for TapGesture, no pointermove or pointerup happens within the
691   // GestureSettings.dwellThreshold, reject.
692   // Note: the original pointer event's timeStamp is irrelevant here.
693   return GestureSettings.dwellThreshold;
697  * Tap gesture.
698  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
699  * the gesture resolution sequence.
700  * @param {Object} aPoints An existing set of points (from previous events).
701  * @param {?String} aLastEvent Last pointer event type.
702  */
703 function Tap(aTimeStamp, aPoints, aLastEvent) {
704   // If the pointer travels, reject to Swipe.
705   TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, Dwell, Swipe);
708 Tap.prototype = Object.create(TapGesture.prototype);
709 Tap.prototype.type = 'tap';
710 Tap.prototype.resolveTo = DoubleTap;
713  * Tap (multi) gesture on Android.
714  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
715  * the gesture resolution sequence.
716  * @param {Object} aPoints An existing set of points (from previous events).
717  * @param {?String} aLastEvent Last pointer event type.
718  */
719 function AndroidTap(aTimeStamp, aPoints, aLastEvent) {
720   // If the pointer travels, reject to Swipe. On dwell threshold reject to
721   // TapHold.
722   TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, TapHold, Swipe);
724 AndroidTap.prototype = Object.create(TapGesture.prototype);
725 // Android double taps are translated to single taps.
726 AndroidTap.prototype.type = 'doubletap';
727 AndroidTap.prototype.resolveTo = TripleTap;
730  * Clear the pointerup handler timer in case of the 3 pointer swipe.
731  */
732 AndroidTap.prototype.clearThreeFingerSwipeTimer = function AndroidTap_clearThreeFingerSwipeTimer() {
733   clearTimeout(this._threeFingerSwipeTimer);
734   delete this._threeFingerSwipeTimer;
737 AndroidTap.prototype.pointerdown = function AndroidTap_pointerdown(aPoints, aTimeStamp) {
738   this.clearThreeFingerSwipeTimer();
739   TapGesture.prototype.pointerdown.call(this, aPoints, aTimeStamp);
742 AndroidTap.prototype.pointermove = function AndroidTap_pointermove(aPoints) {
743   this.clearThreeFingerSwipeTimer();
744   this._moved = true;
745   TapGesture.prototype.pointermove.call(this, aPoints);
748 AndroidTap.prototype.pointerup = function AndroidTap_pointerup(aPoints) {
749   if (this._moved) {
750     // If there was a pointer move - handle the real gesture.
751     TapGesture.prototype.pointerup.call(this, aPoints);
752   } else {
753     // Primptively delay the multi pointer gesture resolution, because Android
754     // sometimes fires a pointerdown/poitnerup sequence before the real events.
755     this._threeFingerSwipeTimer = setTimeout(() => {
756       delete this._threeFingerSwipeTimer;
757       TapGesture.prototype.pointerup.call(this, aPoints);
758     }, ANDROID_TRIPLE_SWIPE_DELAY);
759   }
763  * Reject an android tap gesture.
764  * @param  {?Function} aRejectTo An optional next gesture constructor.
765  * @return {Object} structure that looks like {
766  *   id: gesture_id, // Current AndroidTap gesture id.
767  *   gestureType: next_gesture // Optional
768  * }
769  */
770 AndroidTap.prototype._handleReject = function AndroidTap__handleReject(aRejectTo) {
771   let keys = Object.keys(this.points);
772   if (aRejectTo === Swipe && keys.length === 1) {
773     let key = keys[0];
774     let point = this.points[key];
775     // Two finger swipe is translated into single swipe.
776     this.points[key + '-copy'] = point;
777   }
778   return TapGesture.prototype._handleReject.call(this, aRejectTo);
782  * Double Tap gesture.
783  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
784  * the gesture resolution sequence.
785  * @param {Object} aPoints An existing set of points (from previous events).
786  * @param {?String} aLastEvent Last pointer event type.
787  */
788 function DoubleTap(aTimeStamp, aPoints, aLastEvent) {
789   TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, TapHold);
792 DoubleTap.prototype = Object.create(TapGesture.prototype);
793 DoubleTap.prototype.type = 'doubletap';
794 DoubleTap.prototype.resolveTo = TripleTap;
797  * Triple Tap gesture.
798  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
799  * the gesture resolution sequence.
800  * @param {Object} aPoints An existing set of points (from previous events).
801  * @param {?String} aLastEvent Last pointer event type.
802  */
803 function TripleTap(aTimeStamp, aPoints, aLastEvent) {
804   TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, DoubleTapHold);
807 TripleTap.prototype = Object.create(TapGesture.prototype);
808 TripleTap.prototype.type = 'tripletap';
811  * Common base object for gestures that are created as resolved.
812  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
813  * the gesture resolution sequence.
814  * @param {Object} aPoints An existing set of points (from previous events).
815  * @param {?String} aLastEvent Last pointer event type.
816  */
817 function ResolvedGesture(aTimeStamp, aPoints, aLastEvent) {
818   Gesture.call(this, aTimeStamp, aPoints, aLastEvent);
819   // Resolve the guesture right away.
820   this._deferred.resolve();
823 ResolvedGesture.prototype = Object.create(Gesture.prototype);
826  * Dwell gesture
827  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
828  * the gesture resolution sequence.
829  * @param {Object} aPoints An existing set of points (from previous events).
830  * @param {?String} aLastEvent Last pointer event type.
831  */
832 function Dwell(aTimeStamp, aPoints, aLastEvent) {
833   ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
836 Dwell.prototype = Object.create(ResolvedGesture.prototype);
837 Dwell.prototype.type = 'dwell';
838 Dwell.prototype.resolveTo = DwellEnd;
841  * TapHold gesture
842  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
843  * the gesture resolution sequence.
844  * @param {Object} aPoints An existing set of points (from previous events).
845  * @param {?String} aLastEvent Last pointer event type.
846  */
847 function TapHold(aTimeStamp, aPoints, aLastEvent) {
848   ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
851 TapHold.prototype = Object.create(ResolvedGesture.prototype);
852 TapHold.prototype.type = 'taphold';
853 TapHold.prototype.resolveTo = TapHoldEnd;
856  * DoubleTapHold gesture
857  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
858  * the gesture resolution sequence.
859  * @param {Object} aPoints An existing set of points (from previous events).
860  * @param {?String} aLastEvent Last pointer event type.
861  */
862 function DoubleTapHold(aTimeStamp, aPoints, aLastEvent) {
863   ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
866 DoubleTapHold.prototype = Object.create(ResolvedGesture.prototype);
867 DoubleTapHold.prototype.type = 'doubletaphold';
868 DoubleTapHold.prototype.resolveTo = DoubleTapHoldEnd;
871  * Explore gesture
872  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
873  * the gesture resolution sequence.
874  * @param {Object} aPoints An existing set of points (from previous events).
875  * @param {?String} aLastEvent Last pointer event type.
876  */
877 function Explore(aTimeStamp, aPoints, aLastEvent) {
878   ExploreGesture.call(this);
879   ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
882 Explore.prototype = Object.create(ResolvedGesture.prototype);
883 Explore.prototype.type = 'explore';
884 Explore.prototype.resolveTo = ExploreEnd;
887  * ExploreEnd 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 ExploreEnd(aTimeStamp, aPoints, aLastEvent) {
894   this._inProgress = true;
895   ExploreGesture.call(this);
896   // If the pointer travels, reject to Explore.
897   TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
898   checkProgressGesture(this);
901 ExploreEnd.prototype = Object.create(TravelGesture.prototype);
902 ExploreEnd.prototype.type = 'exploreend';
905  * Swipe gesture.
906  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
907  * the gesture resolution sequence.
908  * @param {Object} aPoints An existing set of points (from previous events).
909  * @param {?String} aLastEvent Last pointer event type.
910  */
911 function Swipe(aTimeStamp, aPoints, aLastEvent) {
912   this._inProgress = true;
913   this._rejectToOnWait = Explore;
914   Gesture.call(this, aTimeStamp, aPoints, aLastEvent);
915   checkProgressGesture(this);
918 Swipe.prototype = Object.create(Gesture.prototype);
919 Swipe.prototype.type = 'swipe';
920 Swipe.prototype._getDelay = function Swipe__getDelay(aTimeStamp) {
921   // Swipe should be completed within the GestureSettings.swipeMaxDuration from
922   // the initial pointer down event.
923   return GestureSettings.swipeMaxDuration - this.startTime + aTimeStamp;
927  * Determine wither the gesture was Swipe or Explore.
928  * @param  {Booler} aComplete A flag that indicates whether the gesture is and
929  * will be complete after the test.
930  */
931 Swipe.prototype.test = function Swipe_test(aComplete) {
932   if (!aComplete) {
933     // No need to test if the gesture is not completing or can't be complete.
934     return;
935   }
936   let reject = true;
937   // If at least one point travelled for more than SWIPE_MIN_DISTANCE and it was
938   // direct enough, consider it a Swipe.
939   for (let identifier in this.points) {
940     let point = this.points[identifier];
941     let directDistance = point.directDistanceTraveled;
942     if (directDistance / Utils.dpi >= SWIPE_MIN_DISTANCE ||
943       directDistance * DIRECTNESS_COEFF >= point.totalDistanceTraveled) {
944       reject = false;
945     }
946   }
947   if (reject) {
948     this._deferred.reject(Explore);
949   }
953  * Compile a swipe related mozAccessFuGesture event detail.
954  * @return {Object} A mozAccessFuGesture detail object.
955  */
956 Swipe.prototype.compile = function Swipe_compile() {
957   let type = this.type;
958   let detail = compileDetail(type, this.points,
959     {x1: 'startX', y1: 'startY', x2: 'x', y2: 'y'});
960   let deltaX = detail.deltaX;
961   let deltaY = detail.deltaY;
962   let edge = EDGE * Utils.dpi;
963   if (Math.abs(deltaX) > Math.abs(deltaY)) {
964     // Horizontal swipe.
965     let startPoints = [touch.x1 for (touch of detail.touches)];
966     if (deltaX > 0) {
967       detail.type = type + 'right';
968       detail.edge = Math.min.apply(null, startPoints) <= edge;
969     } else {
970       detail.type = type + 'left';
971       detail.edge =
972         Utils.win.screen.width - Math.max.apply(null, startPoints) <= edge;
973     }
974   } else {
975     // Vertical swipe.
976     let startPoints = [touch.y1 for (touch of detail.touches)];
977     if (deltaY > 0) {
978       detail.type = type + 'down';
979       detail.edge = Math.min.apply(null, startPoints) <= edge;
980     } else {
981       detail.type = type + 'up';
982       detail.edge =
983         Utils.win.screen.height - Math.max.apply(null, startPoints) <= edge;
984     }
985   }
986   return detail;