Wed, 31 Dec 2014 06:09:35 +0100
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 };