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):
14 AndroidTap -> TripleTap (v)
18 DoubleTap -> TripleTap (v)
22 TripleTap -> DoubleTapHold (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 ******************************************************************************/
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
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;
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
90 // Multiply timeouts by this constant, x2 works great too for slower users.
91 const TIMEOUT_MULTIPLIER = 1;
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
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;
109 * Update the current point coordiates.
110 * @param {Object} aPoint A new point coordinates.
112 update: function Point_update(aPoint) {
117 this.distanceTraveled = this.getDistanceToCoord(lastX, lastY);
118 this.totalDistanceTraveled += this.distanceTraveled;
121 reset: function Point_reset() {
122 this.distanceTraveled = 0;
123 this.totalDistanceTraveled = 0;
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
133 getDistanceToCoord: function Point_getDistanceToCoord(aX, aY) {
134 return Math.hypot(this.x - aX, this.y - aY);
138 * Get the direct distance travelled by the point so far.
140 get directDistanceTraveled() {
141 return this.getDistanceToCoord(this.startX, this.startY);
146 * An externally accessible collection of settings used in gesture resolition.
149 this.GestureSettings = { // jshint ignore:line
151 * Maximum duration of swipe
154 swipeMaxDuration: SWIPE_MAX_DURATION * TIMEOUT_MULTIPLIER,
157 * Maximum amount of time allowed for a gesture to be considered a multitouch.
160 maxMultitouch: MAX_MULTITOUCH * TIMEOUT_MULTIPLIER,
163 * Maximum consecutive pointer event timeout.
166 maxConsecutiveGestureDelay:
167 MAX_CONSECUTIVE_GESTURE_DELAY * TIMEOUT_MULTIPLIER,
170 * Delay before tap turns into dwell
173 dwellThreshold: DWELL_THRESHOLD * TIMEOUT_MULTIPLIER,
176 * Minimum distance that needs to be travelled for the pointer move to be
180 travelThreshold: 0.025
184 * An interface that handles the pointer events and calculates the appropriate
188 this.GestureTracker = { // jshint ignore:line
190 * Reset GestureTracker to its initial state.
191 * @return {[type]} [description]
193 reset: function GestureTracker_reset() {
195 this.current.clearTimer();
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).
207 _init: function GestureTracker__init(aDetail, aTimeStamp, aGesture = Tap) {
208 // Only create a new gesture on |pointerdown| event.
209 if (aDetail.type !== 'pointerdown') {
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;
220 this._create(GestureConstructor);
221 this._update(aDetail, aTimeStamp);
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.
230 handle: function GestureTracker_handle(aDetail, aTimeStamp) {
231 Logger.gesture(() => {
232 return ['Pointer event', aDetail.type, 'at:', aTimeStamp,
233 JSON.stringify(aDetail.points)];
235 this[this.current ? '_update' : '_init'](aDetail, aTimeStamp);
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
244 * @param {?String} aLastEvent Last pointer event type.
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));
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.
256 _update: function GestureTracker_update(aDetail, aTimeStamp) {
257 this.current[aDetail.type](aDetail.points, aTimeStamp);
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.
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
270 if (!current || current.id !== id) {
273 // Only create a gesture if we got a constructor.
275 this._create(gestureType, current.startTime, current.points,
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
289 * @param {String} yKey A default key for the y coordinate. Default is
291 * @return {Object} a mozAccessFuGesture detail structure.
293 function compileDetail(aType, aPoints, keyMap = {x: 'startX', y: 'startY'}) {
297 for (let identifier in aPoints) {
298 let point = aPoints[identifier];
300 for (let key in keyMap) {
301 touch[key] = point[keyMap[key]];
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)) {
310 if (Math.abs(maxDeltaY) < Math.abs(deltaY)) {
313 // Since the gesture is resolving, reset the points' distance information
314 // since they are passed to the next potential gesture.
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.
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 = {
348 * Get the gesture timeout delay.
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;
360 * Clear the existing timer.
362 clearTimer: function Gesture_clearTimer() {
363 clearTimeout(this._timer);
368 * Start the timer for gesture timeout.
369 * @param {Number} aTimeStamp An original pointer event's timeStamp that
370 * started the gesture resolution sequence.
372 startTimer: function Gesture_startTimer(aTimeStamp) {
374 let delay = this._getDelay(aTimeStamp);
375 let handler = () => {
377 if (!this._inProgress) {
378 this._deferred.reject();
379 } else if (this._rejectToOnWait) {
380 this._deferred.reject(this._rejectToOnWait);
386 this._timer = setTimeout(handler, delay);
391 * Add a gesture promise resolution callback.
392 * @param {Function} aCallback
394 then: function Gesture_then(aCallback) {
395 this.promise.then(aCallback);
399 * Update gesture's points. Test the points set with the optional gesture test
401 * @param {Array} aPoints An array with the changed points from the new
403 * @param {String} aType Pointer event type.
404 * @param {Boolean} aCanCreate A flag that enables including the new points.
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).
412 _update: function Gesture__update(aPoints, aType, aCanCreate = false, aNeedComplete = false) {
415 for (let point of aPoints) {
416 let identifier = point.identifier;
417 let gesturePoint = this.points[identifier];
419 gesturePoint.update(point);
421 // Since the gesture is completing and at least one of the gesture
422 // points is updated, set the return value to true.
425 lastEvent = lastEvent || aType;
426 } else if (aCanCreate) {
427 // Only create a new point if aCanCreate is true.
428 this.points[identifier] =
430 lastEvent = lastEvent || aType;
433 this.lastEvent = lastEvent || this.lastEvent;
434 // If test function is defined test the points.
442 * Emit a mozAccessFuGesture (when the gesture is resolved).
443 * @param {Object} aDetail a compiled mozAccessFuGesture detail structure.
445 _emit: function Gesture__emit(aDetail) {
446 let evt = new Utils.win.CustomEvent('mozAccessFuGesture', {
451 Utils.win.dispatchEvent(evt);
455 * Handle the pointer down event.
456 * @param {Array} aPoints A new pointer down points.
457 * @param {Number} aTimeStamp A new pointer down timeStamp.
459 pointerdown: function Gesture_pointerdown(aPoints, aTimeStamp) {
460 this._inProgress = true;
461 this._update(aPoints, 'pointerdown',
462 aTimeStamp - this.startTime < GestureSettings.maxMultitouch);
466 * Handle the pointer move event.
467 * @param {Array} aPoints A new pointer move points.
469 pointermove: function Gesture_pointermove(aPoints) {
470 this._update(aPoints, 'pointermove');
474 * Handle the pointer up event.
475 * @param {Array} aPoints A new pointer up points.
477 pointerup: function Gesture_pointerup(aPoints) {
478 let complete = this._update(aPoints, 'pointerup', false, true);
480 this._deferred.resolve();
485 * A subsequent gesture constructor to resolve the current one to. E.g.
486 * tap->doubletap, dwell->dwellend, etc.
492 * A unique id for the gesture. Composed of the type + timeStamp.
496 this._id = this.type + this.startTime;
501 * A gesture promise resolve callback. Compile and emit the gesture.
502 * @return {Object} Returns a structure to the gesture handler that looks like
504 * id: current gesture id,
505 * gestureType: an optional subsequent gesture constructor.
508 _handleResolve: function Gesture__handleResolve() {
509 if (this.isComplete) {
512 Logger.gesture('Resolving', this.id, 'gesture.');
513 this.isComplete = true;
514 let detail = this.compile();
520 gestureType: this.resolveTo
525 * A gesture promise reject callback.
526 * @return {Object} Returns a structure to the gesture handler that looks like
528 * id: current gesture id,
529 * gestureType: an optional subsequent gesture constructor.
532 _handleReject: function Gesture__handleReject(aRejectTo) {
533 if (this.isComplete) {
536 Logger.gesture('Rejecting', this.id, 'gesture.');
537 this.isComplete = true;
540 gestureType: aRejectTo
545 * A default compilation function used to build the mozAccessFuGesture event
546 * detail. The detail always includes the type and the touches associated
548 * @return {Object} Gesture event detail.
550 compile: function Gesture_compile() {
551 return compileDetail(this.type, this.points);
556 * A mixin for an explore related object.
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'});
567 * Check the in progress gesture for completion.
569 function checkProgressGesture(aGesture) {
570 aGesture._inProgress = true;
571 if (aGesture.lastEvent === 'pointerup') {
575 aGesture._deferred.resolve();
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
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).
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.
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);
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.
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.
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
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.
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.
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,
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;
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.
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.
719 function AndroidTap(aTimeStamp, aPoints, aLastEvent) {
720 // If the pointer travels, reject to Swipe. On dwell threshold reject to
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.
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();
745 TapGesture.prototype.pointermove.call(this, aPoints);
748 AndroidTap.prototype.pointerup = function AndroidTap_pointerup(aPoints) {
750 // If there was a pointer move - handle the real gesture.
751 TapGesture.prototype.pointerup.call(this, aPoints);
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);
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
770 AndroidTap.prototype._handleReject = function AndroidTap__handleReject(aRejectTo) {
771 let keys = Object.keys(this.points);
772 if (aRejectTo === Swipe && keys.length === 1) {
774 let point = this.points[key];
775 // Two finger swipe is translated into single swipe.
776 this.points[key + '-copy'] = point;
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.
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.
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.
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);
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.
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;
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.
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.
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;
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.
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.
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';
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.
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.
931 Swipe.prototype.test = function Swipe_test(aComplete) {
933 // No need to test if the gesture is not completing or can't be complete.
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) {
948 this._deferred.reject(Explore);
953 * Compile a swipe related mozAccessFuGesture event detail.
954 * @return {Object} A mozAccessFuGesture detail object.
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)) {
965 let startPoints = [touch.x1 for (touch of detail.touches)];
967 detail.type = type + 'right';
968 detail.edge = Math.min.apply(null, startPoints) <= edge;
970 detail.type = type + 'left';
972 Utils.win.screen.width - Math.max.apply(null, startPoints) <= edge;
976 let startPoints = [touch.y1 for (touch of detail.touches)];
978 detail.type = type + 'down';
979 detail.edge = Math.min.apply(null, startPoints) <= edge;
981 detail.type = type + 'up';
983 Utils.win.screen.height - Math.max.apply(null, startPoints) <= edge;