mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 981015 - added a number of improvements to make the TouchAdapter(now called PointerAdapter) more reliable. Fixed and updated tests for stability. r=eeejay, marcoz
--- accessible/src/jsat/AccessFu.jsm | 9 +- accessible/src/jsat/Gestures.jsm | 948 +++++++++++++++++++++ accessible/src/jsat/PointerAdapter.jsm | 210 +++++ accessible/src/jsat/TouchAdapter.jsm | 431 ---------- accessible/src/jsat/Utils.jsm | 10 + accessible/src/jsat/moz.build | 3 +- accessible/tests/mochitest/jsat/a11y.ini | 4 +- accessible/tests/mochitest/jsat/dom_helper.js | 74 +- accessible/tests/mochitest/jsat/gestures.json | 253 +++--- ...ouch_adapter.html => test_gesture_tracker.html} | 42 +- .../tests/mochitest/jsat/test_pointer_relay.html | 95 +++ 11 files changed, 1499 insertions(+), 580 deletions(-) create mode 100644 accessible/src/jsat/Gestures.jsm create mode 100644 accessible/src/jsat/PointerAdapter.jsm delete mode 100644 accessible/src/jsat/TouchAdapter.jsm rename accessible/tests/mochitest/jsat/{test_touch_adapter.html => test_gesture_tracker.html} (51%) create mode 100644 accessible/tests/mochitest/jsat/test_pointer_relay.html --HG-- rename : accessible/tests/mochitest/jsat/test_touch_adapter.html => accessible/tests/mochitest/jsat/test_gesture_tracker.html
This commit is contained in:
parent
9790077578
commit
6ad74c4020
@ -82,7 +82,7 @@ this.AccessFu = {
|
||||
this._enabled = true;
|
||||
|
||||
Cu.import('resource://gre/modules/accessibility/Utils.jsm');
|
||||
Cu.import('resource://gre/modules/accessibility/TouchAdapter.jsm');
|
||||
Cu.import('resource://gre/modules/accessibility/PointerAdapter.jsm');
|
||||
Cu.import('resource://gre/modules/accessibility/Presentation.jsm');
|
||||
|
||||
Logger.info('Enabled');
|
||||
@ -115,7 +115,7 @@ this.AccessFu = {
|
||||
|
||||
this.Input.start();
|
||||
Output.start();
|
||||
TouchAdapter.start();
|
||||
PointerAdapter.start();
|
||||
|
||||
Services.obs.addObserver(this, 'remote-browser-shown', false);
|
||||
Services.obs.addObserver(this, 'inprocess-browser-shown', false);
|
||||
@ -165,7 +165,7 @@ this.AccessFu = {
|
||||
|
||||
this.Input.stop();
|
||||
Output.stop();
|
||||
TouchAdapter.stop();
|
||||
PointerAdapter.stop();
|
||||
|
||||
Utils.win.removeEventListener('TabOpen', this);
|
||||
Utils.win.removeEventListener('TabClose', this);
|
||||
@ -787,7 +787,8 @@ var Input = {
|
||||
switch (gestureName) {
|
||||
case 'dwell1':
|
||||
case 'explore1':
|
||||
this.moveToPoint('Simple', aGesture.x, aGesture.y);
|
||||
this.moveToPoint('Simple', aGesture.touches[0].x,
|
||||
aGesture.touches[0].y);
|
||||
break;
|
||||
case 'doubletap1':
|
||||
this.activateCurrent();
|
||||
|
948
accessible/src/jsat/Gestures.jsm
Normal file
948
accessible/src/jsat/Gestures.jsm
Normal file
@ -0,0 +1,948 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* global Components, GestureSettings, XPCOMUtils, Utils, Promise, Logger */
|
||||
/* exported GestureSettings, GestureTracker */
|
||||
|
||||
/******************************************************************************
|
||||
All gestures have the following pathways when being resolved(v)/rejected(x):
|
||||
Tap -> DoubleTap (v)
|
||||
-> Dwell (x)
|
||||
-> Swipe (x)
|
||||
|
||||
AndroidTap -> TripleTap (v)
|
||||
-> TapHold (x)
|
||||
-> Swipe (x)
|
||||
|
||||
DoubleTap -> TripleTap (v)
|
||||
-> TapHold (x)
|
||||
-> Explore (x)
|
||||
|
||||
TripleTap -> DoubleTapHold (x)
|
||||
-> Explore (x)
|
||||
|
||||
Dwell -> DwellEnd (v)
|
||||
|
||||
Swipe -> Explore (x)
|
||||
|
||||
TapHold -> TapHoldEnd (v)
|
||||
|
||||
DoubleTapHold -> DoubleTapHoldEnd (v)
|
||||
|
||||
DwellEnd -> Explore (x)
|
||||
|
||||
TapHoldEnd -> Explore (x)
|
||||
|
||||
DoubleTapHoldEnd -> Explore (x)
|
||||
|
||||
ExploreEnd -> Explore (x)
|
||||
|
||||
Explore -> ExploreEnd (v)
|
||||
******************************************************************************/
|
||||
|
||||
'use strict';
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
this.EXPORTED_SYMBOLS = ['GestureSettings', 'GestureTracker']; // jshint ignore:line
|
||||
|
||||
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Utils', // jshint ignore:line
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Logger', // jshint ignore:line
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'setTimeout', // jshint ignore:line
|
||||
'resource://gre/modules/Timer.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'clearTimeout', // jshint ignore:line
|
||||
'resource://gre/modules/Timer.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Promise', // jshint ignore:line
|
||||
'resource://gre/modules/Promise.jsm');
|
||||
|
||||
// Maximum amount of time allowed for a gesture to be considered a multitouch.
|
||||
const MAX_MULTITOUCH = 250;
|
||||
// Minimal swipe distance in inches
|
||||
const SWIPE_MIN_DISTANCE = 0.4;
|
||||
// Maximum distance the pointer could move during a tap in inches
|
||||
const TAP_MAX_RADIUS = 0.2;
|
||||
// Directness coefficient. It is based on the maximum 15 degree angle between
|
||||
// consequent pointer move lines.
|
||||
const DIRECTNESS_COEFF = 1.44;
|
||||
// An android flag.
|
||||
const IS_ANDROID = Utils.MozBuildApp === 'mobile/android' &&
|
||||
Utils.AndroidSdkVersion >= 14;
|
||||
// A single pointer down/up sequence periodically precedes the tripple swipe
|
||||
// gesture on Android. This delay acounts for that.
|
||||
const ANDROID_TRIPLE_SWIPE_DELAY = 50;
|
||||
// The virtual touch ID generated by a mouse event.
|
||||
const MOUSE_ID = 'mouse';
|
||||
|
||||
/**
|
||||
* A point object containing distance travelled data.
|
||||
* @param {Object} aPoint A point object that looks like: {
|
||||
* x: x coordinate in pixels,
|
||||
* y: y coordinate in pixels
|
||||
* }
|
||||
*/
|
||||
function Point(aPoint) {
|
||||
this.startX = this.x = aPoint.x;
|
||||
this.startY = this.y = aPoint.y;
|
||||
this.distanceTraveled = 0;
|
||||
this.totalDistanceTraveled = 0;
|
||||
}
|
||||
|
||||
Point.prototype = {
|
||||
/**
|
||||
* Update the current point coordiates.
|
||||
* @param {Object} aPoint A new point coordinates.
|
||||
*/
|
||||
update: function Point_update(aPoint) {
|
||||
let lastX = this.x;
|
||||
let lastY = this.y;
|
||||
this.x = aPoint.x;
|
||||
this.y = aPoint.y;
|
||||
this.distanceTraveled = this.getDistanceToCoord(lastX, lastY);
|
||||
this.totalDistanceTraveled += this.distanceTraveled;
|
||||
},
|
||||
|
||||
reset: function Point_reset() {
|
||||
this.distanceTraveled = 0;
|
||||
this.totalDistanceTraveled = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get distance between the current point coordinates and the given ones.
|
||||
* @param {Number} aX A pixel value for the x coordinate.
|
||||
* @param {Number} aY A pixel value for the y coordinate.
|
||||
* @return {Number} A distance between point's current and the given
|
||||
* coordinates.
|
||||
*/
|
||||
getDistanceToCoord: function Point_getDistanceToCoord(aX, aY) {
|
||||
return Math.hypot(this.x - aX, this.y - aY);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the direct distance travelled by the point so far.
|
||||
*/
|
||||
get directDistanceTraveled() {
|
||||
return this.getDistanceToCoord(this.startX, this.startY);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An externally accessible collection of settings used in gesture resolition.
|
||||
* @type {Object}
|
||||
*/
|
||||
this.GestureSettings = { // jshint ignore:line
|
||||
/**
|
||||
* Maximum duration of swipe
|
||||
* @type {Number}
|
||||
*/
|
||||
swipeMaxDuration: 400,
|
||||
|
||||
/**
|
||||
* Maximum consecutive pointer event timeout.
|
||||
* @type {Number}
|
||||
*/
|
||||
maxConsecutiveGestureDelay: 400,
|
||||
|
||||
/**
|
||||
* Delay before tap turns into dwell
|
||||
* @type {Number}
|
||||
*/
|
||||
dwellThreshold: 500,
|
||||
|
||||
/**
|
||||
* Minimum distance that needs to be travelled for the pointer move to be
|
||||
* fired.
|
||||
* @type {Number}
|
||||
*/
|
||||
travelThreshold: 0.025
|
||||
};
|
||||
|
||||
/**
|
||||
* An interface that handles the pointer events and calculates the appropriate
|
||||
* gestures.
|
||||
* @type {Object}
|
||||
*/
|
||||
this.GestureTracker = { // jshint ignore:line
|
||||
/**
|
||||
* Reset GestureTracker to its initial state.
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
reset: function GestureTracker_reset() {
|
||||
if (this.current) {
|
||||
this.current.clearTimer();
|
||||
}
|
||||
delete this.current;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new gesture object and attach resolution handler to it as well as
|
||||
* handle the incoming pointer event.
|
||||
* @param {Object} aDetail A new pointer event detail.
|
||||
* @param {Number} aTimeStamp A new pointer event timeStamp.
|
||||
* @param {Function} aGesture A gesture constructor (default: Tap).
|
||||
*/
|
||||
_init: function GestureTracker__init(aDetail, aTimeStamp, aGesture = Tap) {
|
||||
// Only create a new gesture on |pointerdown| event.
|
||||
if (aDetail.type !== 'pointerdown') {
|
||||
return;
|
||||
}
|
||||
let points = aDetail.points;
|
||||
let GestureConstructor = aGesture;
|
||||
if (IS_ANDROID && GestureConstructor === Tap && points.length === 1 &&
|
||||
points[0].identifier !== MOUSE_ID) {
|
||||
// Handle Android events when EBT is enabled. Two finger gestures are
|
||||
// translated to one.
|
||||
GestureConstructor = AndroidTap;
|
||||
}
|
||||
this._create(GestureConstructor);
|
||||
this._update(aDetail, aTimeStamp);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle the incoming pointer event with the existing gesture object(if
|
||||
* present) or with the newly created one.
|
||||
* @param {Object} aDetail A new pointer event detail.
|
||||
* @param {Number} aTimeStamp A new pointer event timeStamp.
|
||||
*/
|
||||
handle: function GestureTracker_handle(aDetail, aTimeStamp) {
|
||||
this[this.current ? '_update' : '_init'](aDetail, aTimeStamp);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new gesture object and attach resolution handler to it.
|
||||
* @param {Function} aGesture A gesture constructor.
|
||||
* @param {Number} aTimeStamp An original pointer event timeStamp.
|
||||
* @param {Array} aPoints All changed points associated with the new pointer
|
||||
* event.
|
||||
* @param {?String} aLastEvent Last pointer event type.
|
||||
*/
|
||||
_create: function GestureTracker__create(aGesture, aTimeStamp, aPoints, aLastEvent) {
|
||||
this.current = new aGesture(aTimeStamp, aPoints, aLastEvent); /* A constructor name should start with an uppercase letter. */ // jshint ignore:line
|
||||
this.current.then(this._onFulfill.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle the incoming pointer event with the existing gesture object.
|
||||
* @param {Object} aDetail A new pointer event detail.
|
||||
* @param {Number} aTimeStamp A new pointer event timeStamp.
|
||||
*/
|
||||
_update: function GestureTracker_update(aDetail, aTimeStamp) {
|
||||
this.current[aDetail.type](aDetail.points, aTimeStamp);
|
||||
},
|
||||
|
||||
/**
|
||||
* A resolution handler function for the current gesture promise.
|
||||
* @param {Object} aResult A resolution payload with the relevant gesture id
|
||||
* and an optional new gesture contructor.
|
||||
*/
|
||||
_onFulfill: function GestureTracker__onFulfill(aResult) {
|
||||
let {id, gestureType} = aResult;
|
||||
let current = this.current;
|
||||
// Do nothing if there's no existing gesture or there's already a newer
|
||||
// gesture.
|
||||
if (!current || current.id !== id) {
|
||||
return;
|
||||
}
|
||||
// Only create a gesture if we got a constructor.
|
||||
if (gestureType) {
|
||||
this._create(gestureType, current.startTime, current.points,
|
||||
current.lastEvent);
|
||||
} else {
|
||||
delete this.current;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compile a mozAccessFuGesture detail structure.
|
||||
* @param {String} aType A gesture type.
|
||||
* @param {Object} aPoints Gesture's points.
|
||||
* @param {String} xKey A default key for the x coordinate. Default is
|
||||
* 'startX'.
|
||||
* @param {String} yKey A default key for the y coordinate. Default is
|
||||
* 'startY'.
|
||||
* @return {Object} a mozAccessFuGesture detail structure.
|
||||
*/
|
||||
function compileDetail(aType, aPoints, keyMap = {x: 'startX', y: 'startY'}) {
|
||||
let touches = [];
|
||||
let maxDeltaX = 0;
|
||||
let maxDeltaY = 0;
|
||||
for (let identifier in aPoints) {
|
||||
let point = aPoints[identifier];
|
||||
let touch = {};
|
||||
for (let key in keyMap) {
|
||||
touch[key] = point[keyMap[key]];
|
||||
}
|
||||
touches.push(touch);
|
||||
let deltaX = point.x - point.startX;
|
||||
let deltaY = point.y - point.startY;
|
||||
// Determine the maximum x and y travel intervals.
|
||||
if (Math.abs(maxDeltaX) < Math.abs(deltaX)) {
|
||||
maxDeltaX = deltaX;
|
||||
}
|
||||
if (Math.abs(maxDeltaY) < Math.abs(deltaY)) {
|
||||
maxDeltaY = deltaY;
|
||||
}
|
||||
// Since the gesture is resolving, reset the points' distance information
|
||||
// since they are passed to the next potential gesture.
|
||||
point.reset();
|
||||
}
|
||||
return {
|
||||
type: aType,
|
||||
touches: touches,
|
||||
deltaX: maxDeltaX,
|
||||
deltaY: maxDeltaY
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A general gesture object.
|
||||
* @param {Number} aTimeStamp An original pointer event's timeStamp that started
|
||||
* the gesture resolution sequence.
|
||||
* @param {Object} aPoints An existing set of points (from previous events).
|
||||
* Default is an empty object.
|
||||
* @param {?String} aLastEvent Last pointer event type.
|
||||
*/
|
||||
function Gesture(aTimeStamp, aPoints = {}, aLastEvent = undefined) {
|
||||
this.startTime = Date.now();
|
||||
Logger.debug('Creating', this.id, 'gesture.');
|
||||
this.points = aPoints;
|
||||
this.lastEvent = aLastEvent;
|
||||
this._deferred = Promise.defer();
|
||||
// Call this._handleResolve or this._handleReject when the promise is
|
||||
// fulfilled with either resolve or reject.
|
||||
this.promise = this._deferred.promise.then(this._handleResolve.bind(this),
|
||||
this._handleReject.bind(this));
|
||||
this.startTimer(aTimeStamp);
|
||||
}
|
||||
|
||||
Gesture.prototype = {
|
||||
/**
|
||||
* Get the gesture timeout delay.
|
||||
* @return {Number}
|
||||
*/
|
||||
_getDelay: function Gesture__getDelay() {
|
||||
// If nothing happens withing the
|
||||
// GestureSettings.maxConsecutiveGestureDelay, we should not wait for any
|
||||
// more pointer events and consider them the part of the same gesture -
|
||||
// reject this gesture promise.
|
||||
return GestureSettings.maxConsecutiveGestureDelay;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the existing timer.
|
||||
*/
|
||||
clearTimer: function Gesture_clearTimer() {
|
||||
clearTimeout(this._timer);
|
||||
delete this._timer;
|
||||
},
|
||||
|
||||
/**
|
||||
* Start the timer for gesture timeout.
|
||||
* @param {Number} aTimeStamp An original pointer event's timeStamp that
|
||||
* started the gesture resolution sequence.
|
||||
*/
|
||||
startTimer: function Gesture_startTimer(aTimeStamp) {
|
||||
this.clearTimer();
|
||||
let delay = this._getDelay(aTimeStamp);
|
||||
let handler = () => {
|
||||
delete this._timer;
|
||||
if (!this._inProgress) {
|
||||
this._deferred.reject();
|
||||
} else if (this._rejectToOnWait) {
|
||||
this._deferred.reject(this._rejectToOnWait);
|
||||
}
|
||||
};
|
||||
if (delay <= 0) {
|
||||
handler();
|
||||
} else {
|
||||
this._timer = setTimeout(handler, delay);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a gesture promise resolution callback.
|
||||
* @param {Function} aCallback
|
||||
*/
|
||||
then: function Gesture_then(aCallback) {
|
||||
this.promise.then(aCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update gesture's points. Test the points set with the optional gesture test
|
||||
* function.
|
||||
* @param {Array} aPoints An array with the changed points from the new
|
||||
* pointer event.
|
||||
* @param {String} aType Pointer event type.
|
||||
* @param {Boolean} aCanCreate A flag that enables including the new points.
|
||||
* Default is false.
|
||||
* @param {Boolean} aNeedComplete A flag that indicates that the gesture is
|
||||
* completing. Default is false.
|
||||
* @return {Boolean} Indicates whether the gesture can be complete (it is
|
||||
* set to true iff the aNeedComplete is true and there was a change to at
|
||||
* least one point that belongs to the gesture).
|
||||
*/
|
||||
_update: function Gesture__update(aPoints, aType, aCanCreate = false, aNeedComplete = false) {
|
||||
let complete;
|
||||
let lastEvent;
|
||||
for (let point of aPoints) {
|
||||
let identifier = point.identifier;
|
||||
let gesturePoint = this.points[identifier];
|
||||
if (gesturePoint) {
|
||||
gesturePoint.update(point);
|
||||
if (aNeedComplete) {
|
||||
// Since the gesture is completing and at least one of the gesture
|
||||
// points is updated, set the return value to true.
|
||||
complete = true;
|
||||
}
|
||||
lastEvent = lastEvent || aType;
|
||||
} else if (aCanCreate) {
|
||||
// Only create a new point if aCanCreate is true.
|
||||
this.points[identifier] =
|
||||
new Point(point);
|
||||
lastEvent = lastEvent || aType;
|
||||
}
|
||||
}
|
||||
this.lastEvent = lastEvent || this.lastEvent;
|
||||
// If test function is defined test the points.
|
||||
if (this.test) {
|
||||
this.test(complete);
|
||||
}
|
||||
return complete;
|
||||
},
|
||||
|
||||
/**
|
||||
* Emit a mozAccessFuGesture (when the gesture is resolved).
|
||||
* @param {Object} aDetail a compiled mozAccessFuGesture detail structure.
|
||||
*/
|
||||
_emit: function Gesture__emit(aDetail) {
|
||||
let evt = new Utils.win.CustomEvent('mozAccessFuGesture', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
detail: aDetail
|
||||
});
|
||||
Utils.win.dispatchEvent(evt);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle the pointer down event.
|
||||
* @param {Array} aPoints A new pointer down points.
|
||||
* @param {Number} aTimeStamp A new pointer down timeStamp.
|
||||
*/
|
||||
pointerdown: function Gesture_pointerdown(aPoints, aTimeStamp) {
|
||||
this._inProgress = true;
|
||||
this._update(aPoints, 'pointerdown',
|
||||
aTimeStamp - this.startTime < MAX_MULTITOUCH);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle the pointer move event.
|
||||
* @param {Array} aPoints A new pointer move points.
|
||||
*/
|
||||
pointermove: function Gesture_pointermove(aPoints) {
|
||||
this._update(aPoints, 'pointermove');
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle the pointer up event.
|
||||
* @param {Array} aPoints A new pointer up points.
|
||||
*/
|
||||
pointerup: function Gesture_pointerup(aPoints) {
|
||||
let complete = this._update(aPoints, 'pointerup', false, true);
|
||||
if (complete) {
|
||||
this._deferred.resolve();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* A subsequent gesture constructor to resolve the current one to. E.g.
|
||||
* tap->doubletap, dwell->dwellend, etc.
|
||||
* @type {Function}
|
||||
*/
|
||||
resolveTo: null,
|
||||
|
||||
/**
|
||||
* A unique id for the gesture. Composed of the type + timeStamp.
|
||||
*/
|
||||
get id() {
|
||||
delete this._id;
|
||||
this._id = this.type + this.startTime;
|
||||
return this._id;
|
||||
},
|
||||
|
||||
/**
|
||||
* A gesture promise resolve callback. Compile and emit the gesture.
|
||||
* @return {Object} Returns a structure to the gesture handler that looks like
|
||||
* this: {
|
||||
* id: current gesture id,
|
||||
* gestureType: an optional subsequent gesture constructor.
|
||||
* }
|
||||
*/
|
||||
_handleResolve: function Gesture__handleResolve() {
|
||||
if (this.isComplete) {
|
||||
return;
|
||||
}
|
||||
Logger.debug('Resolving', this.id, 'gesture.');
|
||||
this.isComplete = true;
|
||||
let detail = this.compile();
|
||||
if (detail) {
|
||||
this._emit(detail);
|
||||
}
|
||||
return {
|
||||
id: this.id,
|
||||
gestureType: this.resolveTo
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* A gesture promise reject callback.
|
||||
* @return {Object} Returns a structure to the gesture handler that looks like
|
||||
* this: {
|
||||
* id: current gesture id,
|
||||
* gestureType: an optional subsequent gesture constructor.
|
||||
* }
|
||||
*/
|
||||
_handleReject: function Gesture__handleReject(aRejectTo) {
|
||||
if (this.isComplete) {
|
||||
return;
|
||||
}
|
||||
Logger.debug('Rejecting', this.id, 'gesture.');
|
||||
this.isComplete = true;
|
||||
return {
|
||||
id: this.id,
|
||||
gestureType: aRejectTo
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* A default compilation function used to build the mozAccessFuGesture event
|
||||
* detail. The detail always includes the type and the touches associated
|
||||
* with the gesture.
|
||||
* @return {Object} Gesture event detail.
|
||||
*/
|
||||
compile: function Gesture_compile() {
|
||||
return compileDetail(this.type, this.points);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A mixin for an explore related object.
|
||||
*/
|
||||
function ExploreGesture() {
|
||||
this.compile = () => {
|
||||
// Unlike most of other gestures explore based gestures compile using the
|
||||
// current point position and not the start one.
|
||||
return compileDetail(this.type, this.points, {x: 'x', y: 'y'});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the in progress gesture for completion.
|
||||
*/
|
||||
function checkProgressGesture(aGesture) {
|
||||
aGesture._inProgress = true;
|
||||
if (aGesture.lastEvent === 'pointerup') {
|
||||
if (aGesture.test) {
|
||||
aGesture.test(true);
|
||||
}
|
||||
aGesture._deferred.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A common travel gesture. When the travel gesture is created, all subsequent
|
||||
* pointer events' points are tested for their total distance traveled. If that
|
||||
* distance exceeds the _threshold distance, the gesture will be rejected to a
|
||||
* _travelTo gesture.
|
||||
* @param {Number} aTimeStamp An original pointer event's timeStamp that started
|
||||
* the gesture resolution sequence.
|
||||
* @param {Object} aPoints An existing set of points (from previous events).
|
||||
* @param {?String} aLastEvent Last pointer event type.
|
||||
* @param {Function} aTravelTo A contructor for the gesture to reject to when
|
||||
* travelling (default: Explore).
|
||||
* @param {Number} aThreshold Travel threshold (default:
|
||||
* GestureSettings.travelThreshold).
|
||||
*/
|
||||
function TravelGesture(aTimeStamp, aPoints, aLastEvent, aTravelTo = Explore, aThreshold = GestureSettings.travelThreshold) {
|
||||
Gesture.call(this, aTimeStamp, aPoints, aLastEvent);
|
||||
this._travelTo = aTravelTo;
|
||||
this._threshold = aThreshold;
|
||||
}
|
||||
|
||||
TravelGesture.prototype = Object.create(Gesture.prototype);
|
||||
|
||||
/**
|
||||
* Test the gesture points for travel. The gesture will be rejected to
|
||||
* this._travelTo gesture iff at least one point crosses this._threshold.
|
||||
*/
|
||||
TravelGesture.prototype.test = function TravelGesture_test() {
|
||||
for (let identifier in this.points) {
|
||||
let point = this.points[identifier];
|
||||
if (point.totalDistanceTraveled / Utils.dpi > this._threshold) {
|
||||
this._deferred.reject(this._travelTo);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DwellEnd gesture.
|
||||
* @param {Number} aTimeStamp An original pointer event's timeStamp that started
|
||||
* the gesture resolution sequence.
|
||||
* @param {Object} aPoints An existing set of points (from previous events).
|
||||
* @param {?String} aLastEvent Last pointer event type.
|
||||
*/
|
||||
function DwellEnd(aTimeStamp, aPoints, aLastEvent) {
|
||||
this._inProgress = true;
|
||||
// If the pointer travels, reject to Explore.
|
||||
TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
|
||||
checkProgressGesture(this);
|
||||
}
|
||||
|
||||
DwellEnd.prototype = Object.create(TravelGesture.prototype);
|
||||
DwellEnd.prototype.type = 'dwellend';
|
||||
|
||||
/**
|
||||
* TapHoldEnd gesture. This gesture can be represented as the following diagram:
|
||||
* pointerdown-pointerup-pointerdown-*wait*-pointerup.
|
||||
* @param {Number} aTimeStamp An original pointer event's timeStamp that started
|
||||
* the gesture resolution sequence.
|
||||
* @param {Object} aPoints An existing set of points (from previous events).
|
||||
* @param {?String} aLastEvent Last pointer event type.
|
||||
*/
|
||||
function TapHoldEnd(aTimeStamp, aPoints, aLastEvent) {
|
||||
this._inProgress = true;
|
||||
// If the pointer travels, reject to Explore.
|
||||
TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
|
||||
checkProgressGesture(this);
|
||||
}
|
||||
|
||||
TapHoldEnd.prototype = Object.create(TravelGesture.prototype);
|
||||
TapHoldEnd.prototype.type = 'tapholdend';
|
||||
|
||||
/**
|
||||
* DoubleTapHoldEnd gesture. This gesture can be represented as the following
|
||||
* diagram:
|
||||
* pointerdown-pointerup-pointerdown-pointerup-pointerdown-*wait*-pointerup.
|
||||
* @param {Number} aTimeStamp An original pointer event's timeStamp that started
|
||||
* the gesture resolution sequence.
|
||||
* @param {Object} aPoints An existing set of points (from previous events).
|
||||
* @param {?String} aLastEvent Last pointer event type.
|
||||
*/
|
||||
function DoubleTapHoldEnd(aTimeStamp, aPoints, aLastEvent) {
|
||||
this._inProgress = true;
|
||||
// If the pointer travels, reject to Explore.
|
||||
TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
|
||||
checkProgressGesture(this);
|
||||
}
|
||||
|
||||
DoubleTapHoldEnd.prototype = Object.create(TravelGesture.prototype);
|
||||
DoubleTapHoldEnd.prototype.type = 'doubletapholdend';
|
||||
|
||||
/**
|
||||
* A common tap gesture object.
|
||||
* @param {Number} aTimeStamp An original pointer event's timeStamp that started
|
||||
* the gesture resolution sequence.
|
||||
* @param {Object} aPoints An existing set of points (from previous events).
|
||||
* @param {?String} aLastEvent Last pointer event type.
|
||||
* @param {Function} aRejectTo A constructor for the next gesture to reject to
|
||||
* in case no pointermove or pointerup happens within the
|
||||
* GestureSettings.dwellThreshold.
|
||||
* @param {Function} aTravelTo An optional constuctor for the next gesture to
|
||||
* reject to in case the the TravelGesture test fails.
|
||||
*/
|
||||
function TapGesture(aTimeStamp, aPoints, aLastEvent, aRejectTo, aTravelTo) {
|
||||
this._rejectToOnWait = aRejectTo;
|
||||
// If the pointer travels, reject to aTravelTo.
|
||||
TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent, aTravelTo,
|
||||
TAP_MAX_RADIUS);
|
||||
}
|
||||
|
||||
TapGesture.prototype = Object.create(TravelGesture.prototype);
|
||||
TapGesture.prototype._getDelay = function TapGesture__getDelay() {
|
||||
// If, for TapGesture, no pointermove or pointerup happens within the
|
||||
// GestureSettings.dwellThreshold, reject.
|
||||
// Note: the original pointer event's timeStamp is irrelevant here.
|
||||
return GestureSettings.dwellThreshold;
|
||||
};
|
||||
|
||||
/**
|
||||
* Tap gesture.
|
||||
* @param {Number} aTimeStamp An original pointer event's timeStamp that started
|
||||
* the gesture resolution sequence.
|
||||
* @param {Object} aPoints An existing set of points (from previous events).
|
||||
* @param {?String} aLastEvent Last pointer event type.
|
||||
*/
|
||||
function Tap(aTimeStamp, aPoints, aLastEvent) {
|
||||
// If the pointer travels, reject to Swipe.
|
||||
TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, Dwell, Swipe);
|
||||
}
|
||||
|
||||
Tap.prototype = Object.create(TapGesture.prototype);
|
||||
Tap.prototype.type = 'tap';
|
||||
Tap.prototype.resolveTo = DoubleTap;
|
||||
|
||||
/**
|
||||
* Tap (multi) gesture on Android.
|
||||
* @param {Number} aTimeStamp An original pointer event's timeStamp that started
|
||||
* the gesture resolution sequence.
|
||||
* @param {Object} aPoints An existing set of points (from previous events).
|
||||
* @param {?String} aLastEvent Last pointer event type.
|
||||
*/
|
||||
function AndroidTap(aTimeStamp, aPoints, aLastEvent) {
|
||||
// If the pointer travels, reject to Swipe. On dwell threshold reject to
|
||||
// TapHold.
|
||||
TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, TapHold, Swipe);
|
||||
}
|
||||
AndroidTap.prototype = Object.create(TapGesture.prototype);
|
||||
// Android double taps are translated to single taps.
|
||||
AndroidTap.prototype.type = 'doubletap';
|
||||
AndroidTap.prototype.resolveTo = TripleTap;
|
||||
|
||||
/**
|
||||
* Clear the pointerup handler timer in case of the 3 pointer swipe.
|
||||
*/
|
||||
AndroidTap.prototype.clearThreeFingerSwipeTimer = function AndroidTap_clearThreeFingerSwipeTimer() {
|
||||
clearTimeout(this._threeFingerSwipeTimer);
|
||||
delete this._threeFingerSwipeTimer;
|
||||
};
|
||||
|
||||
AndroidTap.prototype.pointerdown = function AndroidTap_pointerdown(aPoints, aTimeStamp) {
|
||||
this.clearThreeFingerSwipeTimer();
|
||||
TapGesture.prototype.pointerdown.call(this, aPoints, aTimeStamp);
|
||||
};
|
||||
|
||||
AndroidTap.prototype.pointermove = function AndroidTap_pointermove(aPoints) {
|
||||
this.clearThreeFingerSwipeTimer();
|
||||
this._moved = true;
|
||||
TapGesture.prototype.pointermove.call(this, aPoints);
|
||||
};
|
||||
|
||||
AndroidTap.prototype.pointerup = function AndroidTap_pointerup(aPoints) {
|
||||
if (this._moved) {
|
||||
// If there was a pointer move - handle the real gesture.
|
||||
TapGesture.prototype.pointerup.call(this, aPoints);
|
||||
} else {
|
||||
// Primptively delay the multi pointer gesture resolution, because Android
|
||||
// sometimes fires a pointerdown/poitnerup sequence before the real events.
|
||||
this._threeFingerSwipeTimer = setTimeout(() => {
|
||||
delete this._threeFingerSwipeTimer;
|
||||
TapGesture.prototype.pointerup.call(this, aPoints);
|
||||
}, ANDROID_TRIPLE_SWIPE_DELAY);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reject an android tap gesture.
|
||||
* @param {?Function} aRejectTo An optional next gesture constructor.
|
||||
* @return {Object} structure that looks like {
|
||||
* id: gesture_id, // Current AndroidTap gesture id.
|
||||
* gestureType: next_gesture // Optional
|
||||
* }
|
||||
*/
|
||||
AndroidTap.prototype._handleReject = function AndroidTap__handleReject(aRejectTo) {
|
||||
let keys = Object.keys(this.points);
|
||||
if (aRejectTo === Swipe && keys.length === 1) {
|
||||
let key = keys[0];
|
||||
let point = this.points[key];
|
||||
// Two finger swipe is translated into single swipe.
|
||||
this.points[key + '-copy'] = point;
|
||||
}
|
||||
return TapGesture.prototype._handleReject.call(this, aRejectTo);
|
||||
};
|
||||
|
||||
/**
|
||||
* Double Tap gesture.
|
||||
* @param {Number} aTimeStamp An original pointer event's timeStamp that started
|
||||
* the gesture resolution sequence.
|
||||
* @param {Object} aPoints An existing set of points (from previous events).
|
||||
* @param {?String} aLastEvent Last pointer event type.
|
||||
*/
|
||||
function DoubleTap(aTimeStamp, aPoints, aLastEvent) {
|
||||
TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, TapHold);
|
||||
}
|
||||
|
||||
DoubleTap.prototype = Object.create(TapGesture.prototype);
|
||||
DoubleTap.prototype.type = 'doubletap';
|
||||
DoubleTap.prototype.resolveTo = TripleTap;
|
||||
|
||||
/**
|
||||
* Triple Tap gesture.
|
||||
* @param {Number} aTimeStamp An original pointer event's timeStamp that started
|
||||
* the gesture resolution sequence.
|
||||
* @param {Object} aPoints An existing set of points (from previous events).
|
||||
* @param {?String} aLastEvent Last pointer event type.
|
||||
*/
|
||||
function TripleTap(aTimeStamp, aPoints, aLastEvent) {
|
||||
TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, DoubleTapHold);
|
||||
}
|
||||
|
||||
TripleTap.prototype = Object.create(TapGesture.prototype);
|
||||
TripleTap.prototype.type = 'tripletap';
|
||||
|
||||
/**
|
||||
* Common base object for gestures that are created as resolved.
|
||||
* @param {Number} aTimeStamp An original pointer event's timeStamp that started
|
||||
* the gesture resolution sequence.
|
||||
* @param {Object} aPoints An existing set of points (from previous events).
|
||||
* @param {?String} aLastEvent Last pointer event type.
|
||||
*/
|
||||
function ResolvedGesture(aTimeStamp, aPoints, aLastEvent) {
|
||||
Gesture.call(this, aTimeStamp, aPoints, aLastEvent);
|
||||
// Resolve the guesture right away.
|
||||
this._deferred.resolve();
|
||||
}
|
||||
|
||||
ResolvedGesture.prototype = Object.create(Gesture.prototype);
|
||||
|
||||
/**
|
||||
* Dwell gesture
|
||||
* @param {Number} aTimeStamp An original pointer event's timeStamp that started
|
||||
* the gesture resolution sequence.
|
||||
* @param {Object} aPoints An existing set of points (from previous events).
|
||||
* @param {?String} aLastEvent Last pointer event type.
|
||||
*/
|
||||
function Dwell(aTimeStamp, aPoints, aLastEvent) {
|
||||
ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
|
||||
}
|
||||
|
||||
Dwell.prototype = Object.create(ResolvedGesture.prototype);
|
||||
Dwell.prototype.type = 'dwell';
|
||||
Dwell.prototype.resolveTo = DwellEnd;
|
||||
|
||||
/**
|
||||
* TapHold gesture
|
||||
* @param {Number} aTimeStamp An original pointer event's timeStamp that started
|
||||
* the gesture resolution sequence.
|
||||
* @param {Object} aPoints An existing set of points (from previous events).
|
||||
* @param {?String} aLastEvent Last pointer event type.
|
||||
*/
|
||||
function TapHold(aTimeStamp, aPoints, aLastEvent) {
|
||||
ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
|
||||
}
|
||||
|
||||
TapHold.prototype = Object.create(ResolvedGesture.prototype);
|
||||
TapHold.prototype.type = 'taphold';
|
||||
TapHold.prototype.resolveTo = TapHoldEnd;
|
||||
|
||||
/**
|
||||
* DoubleTapHold gesture
|
||||
* @param {Number} aTimeStamp An original pointer event's timeStamp that started
|
||||
* the gesture resolution sequence.
|
||||
* @param {Object} aPoints An existing set of points (from previous events).
|
||||
* @param {?String} aLastEvent Last pointer event type.
|
||||
*/
|
||||
function DoubleTapHold(aTimeStamp, aPoints, aLastEvent) {
|
||||
ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
|
||||
}
|
||||
|
||||
DoubleTapHold.prototype = Object.create(ResolvedGesture.prototype);
|
||||
DoubleTapHold.prototype.type = 'doubletaphold';
|
||||
DoubleTapHold.prototype.resolveTo = DoubleTapHoldEnd;
|
||||
|
||||
/**
|
||||
* Explore gesture
|
||||
* @param {Number} aTimeStamp An original pointer event's timeStamp that started
|
||||
* the gesture resolution sequence.
|
||||
* @param {Object} aPoints An existing set of points (from previous events).
|
||||
* @param {?String} aLastEvent Last pointer event type.
|
||||
*/
|
||||
function Explore(aTimeStamp, aPoints, aLastEvent) {
|
||||
ExploreGesture.call(this);
|
||||
ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
|
||||
}
|
||||
|
||||
Explore.prototype = Object.create(ResolvedGesture.prototype);
|
||||
Explore.prototype.type = 'explore';
|
||||
Explore.prototype.resolveTo = ExploreEnd;
|
||||
|
||||
/**
|
||||
* ExploreEnd gesture.
|
||||
* @param {Number} aTimeStamp An original pointer event's timeStamp that started
|
||||
* the gesture resolution sequence.
|
||||
* @param {Object} aPoints An existing set of points (from previous events).
|
||||
* @param {?String} aLastEvent Last pointer event type.
|
||||
*/
|
||||
function ExploreEnd(aTimeStamp, aPoints, aLastEvent) {
|
||||
this._inProgress = true;
|
||||
ExploreGesture.call(this);
|
||||
// If the pointer travels, reject to Explore.
|
||||
TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
|
||||
checkProgressGesture(this);
|
||||
}
|
||||
|
||||
ExploreEnd.prototype = Object.create(TravelGesture.prototype);
|
||||
ExploreEnd.prototype.type = 'exploreend';
|
||||
|
||||
/**
|
||||
* Swipe gesture.
|
||||
* @param {Number} aTimeStamp An original pointer event's timeStamp that started
|
||||
* the gesture resolution sequence.
|
||||
* @param {Object} aPoints An existing set of points (from previous events).
|
||||
* @param {?String} aLastEvent Last pointer event type.
|
||||
*/
|
||||
function Swipe(aTimeStamp, aPoints, aLastEvent) {
|
||||
this._inProgress = true;
|
||||
this._rejectToOnWait = Explore;
|
||||
Gesture.call(this, aTimeStamp, aPoints, aLastEvent);
|
||||
checkProgressGesture(this);
|
||||
}
|
||||
|
||||
Swipe.prototype = Object.create(Gesture.prototype);
|
||||
Swipe.prototype.type = 'swipe';
|
||||
Swipe.prototype._getDelay = function Swipe__getDelay(aTimeStamp) {
|
||||
// Swipe should be completed within the GestureSettings.swipeMaxDuration from
|
||||
// the initial pointer down event.
|
||||
return GestureSettings.swipeMaxDuration - this.startTime + aTimeStamp;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine wither the gesture was Swipe or Explore.
|
||||
* @param {Booler} aComplete A flag that indicates whether the gesture is and
|
||||
* will be complete after the test.
|
||||
*/
|
||||
Swipe.prototype.test = function Swipe_test(aComplete) {
|
||||
if (!aComplete) {
|
||||
// No need to test if the gesture is not completing or can't be complete.
|
||||
return;
|
||||
}
|
||||
let reject = true;
|
||||
// If at least one point travelled for more than SWIPE_MIN_DISTANCE and it was
|
||||
// direct enough, consider it a Swipe.
|
||||
for (let identifier in this.points) {
|
||||
let point = this.points[identifier];
|
||||
let directDistance = point.directDistanceTraveled;
|
||||
if (directDistance / Utils.dpi >= SWIPE_MIN_DISTANCE ||
|
||||
directDistance * DIRECTNESS_COEFF >= point.totalDistanceTraveled) {
|
||||
reject = false;
|
||||
}
|
||||
}
|
||||
if (reject) {
|
||||
this._deferred.reject(Explore);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compile a swipe related mozAccessFuGesture event detail.
|
||||
* @return {Object} A mozAccessFuGesture detail object.
|
||||
*/
|
||||
Swipe.prototype.compile = function Swipe_compile() {
|
||||
let type = this.type;
|
||||
let detail = compileDetail(type, this.points,
|
||||
{x1: 'startX', y1: 'startY', x2: 'x', y2: 'y'});
|
||||
let deltaX = detail.deltaX;
|
||||
let deltaY = detail.deltaY;
|
||||
if (Math.abs(deltaX) > Math.abs(deltaY)) {
|
||||
// Horizontal swipe.
|
||||
detail.type = type + (deltaX > 0 ? 'right' : 'left');
|
||||
} else {
|
||||
// Vertival swipe.
|
||||
detail.type = type + (deltaY > 0 ? 'down' : 'up');
|
||||
}
|
||||
return detail;
|
||||
};
|
210
accessible/src/jsat/PointerAdapter.jsm
Normal file
210
accessible/src/jsat/PointerAdapter.jsm
Normal file
@ -0,0 +1,210 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* global Components, XPCOMUtils, Utils, Logger, GestureSettings,
|
||||
GestureTracker */
|
||||
/* exported PointerRelay, PointerAdapter */
|
||||
|
||||
'use strict';
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
this.EXPORTED_SYMBOLS = ['PointerRelay', 'PointerAdapter']; // jshint ignore:line
|
||||
|
||||
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Utils', // jshint ignore:line
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Logger', // jshint ignore:line
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'GestureSettings', // jshint ignore:line
|
||||
'resource://gre/modules/accessibility/Gestures.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'GestureTracker', // jshint ignore:line
|
||||
'resource://gre/modules/accessibility/Gestures.jsm');
|
||||
|
||||
// The virtual touch ID generated by a mouse event.
|
||||
const MOUSE_ID = 'mouse';
|
||||
// Synthesized touch ID.
|
||||
const SYNTH_ID = -1;
|
||||
|
||||
let PointerRelay = { // jshint ignore:line
|
||||
/**
|
||||
* A mapping of events we should be intercepting. Entries with a value of
|
||||
* |true| are used for compiling high-level gesture events. Entries with a
|
||||
* value of |false| are cancelled and do not propogate to content.
|
||||
*/
|
||||
get _eventsOfInterest() {
|
||||
delete this._eventsOfInterest;
|
||||
|
||||
switch (Utils.widgetToolkit) {
|
||||
case 'gonk':
|
||||
this._eventsOfInterest = {
|
||||
'touchstart' : true,
|
||||
'touchmove' : true,
|
||||
'touchend' : true,
|
||||
'mousedown' : false,
|
||||
'mousemove' : false,
|
||||
'mouseup': false,
|
||||
'click': false };
|
||||
break;
|
||||
|
||||
case 'android':
|
||||
this._eventsOfInterest = {
|
||||
'touchstart' : true,
|
||||
'touchmove' : true,
|
||||
'touchend' : true,
|
||||
'mousemove' : true,
|
||||
'mouseenter' : true,
|
||||
'mouseleave' : true,
|
||||
'mousedown' : false,
|
||||
'mouseup': false,
|
||||
'click': false };
|
||||
break;
|
||||
|
||||
default:
|
||||
// Desktop.
|
||||
this._eventsOfInterest = {
|
||||
'mousemove' : true,
|
||||
'mousedown' : true,
|
||||
'mouseup': true,
|
||||
'click': false
|
||||
};
|
||||
if ('ontouchstart' in Utils.win) {
|
||||
for (let eventType of ['touchstart', 'touchmove', 'touchend']) {
|
||||
this._eventsOfInterest[eventType] = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return this._eventsOfInterest;
|
||||
},
|
||||
|
||||
_eventMap: {
|
||||
'touchstart' : 'pointerdown',
|
||||
'mousedown' : 'pointerdown',
|
||||
'mouseenter' : 'pointerdown',
|
||||
'touchmove' : 'pointermove',
|
||||
'mousemove' : 'pointermove',
|
||||
'touchend' : 'pointerup',
|
||||
'mouseup': 'pointerup',
|
||||
'mouseleave': 'pointerup'
|
||||
},
|
||||
|
||||
start: function PointerRelay_start(aOnPointerEvent) {
|
||||
Logger.debug('PointerRelay.start');
|
||||
this.onPointerEvent = aOnPointerEvent;
|
||||
for (let eventType in this._eventsOfInterest) {
|
||||
Utils.win.addEventListener(eventType, this, true, true);
|
||||
}
|
||||
},
|
||||
|
||||
stop: function PointerRelay_stop() {
|
||||
Logger.debug('PointerRelay.stop');
|
||||
delete this.lastPointerMove;
|
||||
delete this.onPointerEvent;
|
||||
for (let eventType in this._eventsOfInterest) {
|
||||
Utils.win.removeEventListener(eventType, this, true, true);
|
||||
}
|
||||
},
|
||||
|
||||
_suppressPointerMove: function PointerRelay__suppressPointerMove(aChangedTouches) {
|
||||
if (!this.lastPointerMove) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < aChangedTouches.length; ++i) {
|
||||
let touch = aChangedTouches[i];
|
||||
let lastTouch;
|
||||
try {
|
||||
lastTouch = this.lastPointerMove.identifiedTouch ?
|
||||
this.lastPointerMove.identifiedTouch(touch.identifier) :
|
||||
this.lastPointerMove[i];
|
||||
} catch (x) {
|
||||
// Sometimes touch object can't be accessed after page navigation.
|
||||
}
|
||||
if (!lastTouch || lastTouch.target !== touch.target ||
|
||||
Math.hypot(touch.screenX - lastTouch.screenX, touch.screenY -
|
||||
lastTouch.screenY) / Utils.dpi >= GestureSettings.travelThreshold) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
handleEvent: function PointerRelay_handleEvent(aEvent) {
|
||||
// Don't bother with chrome mouse events.
|
||||
if (Utils.MozBuildApp === 'browser' &&
|
||||
aEvent.view.top instanceof Ci.nsIDOMChromeWindow) {
|
||||
return;
|
||||
}
|
||||
if (aEvent.mozInputSource === Ci.nsIDOMMouseEvent.MOZ_SOURCE_UNKNOWN) {
|
||||
// Ignore events that are scripted or clicks from the a11y API.
|
||||
return;
|
||||
}
|
||||
|
||||
let changedTouches = aEvent.changedTouches || [{
|
||||
identifier: MOUSE_ID,
|
||||
screenX: aEvent.screenX,
|
||||
screenY: aEvent.screenY,
|
||||
target: aEvent.target
|
||||
}];
|
||||
|
||||
if (changedTouches.length === 1 &&
|
||||
changedTouches[0].identifier === SYNTH_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
aEvent.preventDefault();
|
||||
aEvent.stopImmediatePropagation();
|
||||
|
||||
let type = aEvent.type;
|
||||
if (!this._eventsOfInterest[type]) {
|
||||
return;
|
||||
}
|
||||
let pointerType = this._eventMap[type];
|
||||
if (pointerType === 'pointermove') {
|
||||
if (this._suppressPointerMove(changedTouches)) {
|
||||
// Do not fire pointermove more than every POINTERMOVE_THROTTLE.
|
||||
return;
|
||||
}
|
||||
this.lastPointerMove = changedTouches;
|
||||
}
|
||||
this.onPointerEvent({
|
||||
type: pointerType,
|
||||
points: Array.prototype.map.call(changedTouches,
|
||||
function mapTouch(aTouch) {
|
||||
return {
|
||||
identifier: aTouch.identifier,
|
||||
x: aTouch.screenX,
|
||||
y: aTouch.screenY
|
||||
};
|
||||
}
|
||||
)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.PointerAdapter = { // jshint ignore:line
|
||||
start: function PointerAdapter_start() {
|
||||
Logger.debug('PointerAdapter.start');
|
||||
GestureTracker.reset();
|
||||
PointerRelay.start(this.handleEvent);
|
||||
},
|
||||
|
||||
stop: function PointerAdapter_stop() {
|
||||
Logger.debug('PointerAdapter.stop');
|
||||
PointerRelay.stop();
|
||||
GestureTracker.reset();
|
||||
},
|
||||
|
||||
handleEvent: function PointerAdapter_handleEvent(aDetail) {
|
||||
let timeStamp = Date.now();
|
||||
Logger.debug(() => {
|
||||
return ['Pointer event', aDetail.type, 'at:', timeStamp,
|
||||
JSON.stringify(aDetail.points)];
|
||||
});
|
||||
GestureTracker.handle(aDetail, timeStamp);
|
||||
}
|
||||
};
|
@ -1,431 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
|
||||
this.EXPORTED_SYMBOLS = ['TouchAdapter'];
|
||||
|
||||
Cu.import('resource://gre/modules/accessibility/Utils.jsm');
|
||||
|
||||
// We should not be emitting explore events more than 10 times a second.
|
||||
// It is granular enough to feel natural, and it does not hammer the CPU.
|
||||
const EXPLORE_THROTTLE = 100;
|
||||
|
||||
this.TouchAdapter = {
|
||||
// minimal swipe distance in inches
|
||||
SWIPE_MIN_DISTANCE: 0.4,
|
||||
|
||||
// maximum duration of swipe
|
||||
SWIPE_MAX_DURATION: 400,
|
||||
|
||||
// how straight does a swipe need to be
|
||||
SWIPE_DIRECTNESS: 1.2,
|
||||
|
||||
// maximum consecutive
|
||||
MAX_CONSECUTIVE_GESTURE_DELAY: 400,
|
||||
|
||||
// delay before tap turns into dwell
|
||||
DWELL_THRESHOLD: 500,
|
||||
|
||||
// delay before distinct dwell events
|
||||
DWELL_REPEAT_DELAY: 300,
|
||||
|
||||
// maximum distance the mouse could move during a tap in inches
|
||||
TAP_MAX_RADIUS: 0.2,
|
||||
|
||||
// The virtual touch ID generated by a mouse event.
|
||||
MOUSE_ID: 'mouse',
|
||||
|
||||
// Synthesized touch ID.
|
||||
SYNTH_ID: -1,
|
||||
|
||||
start: function TouchAdapter_start() {
|
||||
Logger.debug('TouchAdapter.start');
|
||||
|
||||
this._touchPoints = {};
|
||||
this._dwellTimeout = 0;
|
||||
this._prevGestures = {};
|
||||
this._lastExploreTime = 0;
|
||||
this._dpi = Utils.win.QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIDOMWindowUtils).displayDPI;
|
||||
|
||||
let target = Utils.win;
|
||||
|
||||
for (let eventType in this.eventsOfInterest) {
|
||||
target.addEventListener(eventType, this, true, true);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
stop: function TouchAdapter_stop() {
|
||||
Logger.debug('TouchAdapter.stop');
|
||||
|
||||
let target = Utils.win;
|
||||
|
||||
for (let eventType in this.eventsOfInterest) {
|
||||
target.removeEventListener(eventType, this, true, true);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* A mapping of events we should be intercepting. Entries with a value of
|
||||
* |true| are used for compiling high-level gesture events. Entries with
|
||||
* a value of |false| are cancelled and do not propogate to content.
|
||||
*/
|
||||
get eventsOfInterest() {
|
||||
delete this.eventsOfInterest;
|
||||
|
||||
switch (Utils.widgetToolkit) {
|
||||
case 'gonk':
|
||||
this.eventsOfInterest = {
|
||||
'touchstart' : true,
|
||||
'touchmove' : true,
|
||||
'touchend' : true,
|
||||
'mousedown' : false,
|
||||
'mousemove' : false,
|
||||
'mouseup': false,
|
||||
'click': false };
|
||||
break;
|
||||
|
||||
case 'android':
|
||||
this.eventsOfInterest = {
|
||||
'touchstart' : true,
|
||||
'touchmove' : true,
|
||||
'touchend' : true,
|
||||
'mousemove' : true,
|
||||
'mouseenter' : true,
|
||||
'mouseleave' : true,
|
||||
'mousedown' : false,
|
||||
'mouseup': false,
|
||||
'click': false };
|
||||
break;
|
||||
|
||||
default:
|
||||
this.eventsOfInterest = {
|
||||
'mousemove' : true,
|
||||
'mousedown' : true,
|
||||
'mouseup': true,
|
||||
'click': false };
|
||||
if ('ontouchstart' in Utils.win) {
|
||||
for (let eventType of ['touchstart', 'touchmove', 'touchend']) {
|
||||
this.eventsOfInterest[eventType] = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return this.eventsOfInterest;
|
||||
},
|
||||
|
||||
handleEvent: function TouchAdapter_handleEvent(aEvent) {
|
||||
// Don't bother with chrome mouse events.
|
||||
if (Utils.MozBuildApp == 'browser' &&
|
||||
aEvent.view.top instanceof Ci.nsIDOMChromeWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aEvent.mozInputSource == Ci.nsIDOMMouseEvent.MOZ_SOURCE_UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
let changedTouches = aEvent.changedTouches || [aEvent];
|
||||
|
||||
if (changedTouches.length == 1 &&
|
||||
changedTouches[0].identifier == this.SYNTH_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.eventsOfInterest[aEvent.type]) {
|
||||
aEvent.preventDefault();
|
||||
aEvent.stopImmediatePropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._delayedEvent) {
|
||||
Utils.win.clearTimeout(this._delayedEvent);
|
||||
delete this._delayedEvent;
|
||||
}
|
||||
|
||||
// XXX: Until bug 77992 is resolved, on desktop we get microseconds
|
||||
// instead of milliseconds.
|
||||
let timeStamp = (Utils.OS == 'Android') ? aEvent.timeStamp : Date.now();
|
||||
switch (aEvent.type) {
|
||||
case 'mousedown':
|
||||
case 'mouseenter':
|
||||
case 'touchstart':
|
||||
for (var i = 0; i < changedTouches.length; i++) {
|
||||
let touch = changedTouches[i];
|
||||
let touchPoint = new TouchPoint(touch, timeStamp, this._dpi);
|
||||
let identifier = (touch.identifier == undefined) ?
|
||||
this.MOUSE_ID : touch.identifier;
|
||||
this._touchPoints[identifier] = touchPoint;
|
||||
this._lastExploreTime = timeStamp + this.SWIPE_MAX_DURATION;
|
||||
}
|
||||
this._dwellTimeout = Utils.win.setTimeout(
|
||||
(function () {
|
||||
this.compileAndEmit(timeStamp + this.DWELL_THRESHOLD);
|
||||
}).bind(this), this.DWELL_THRESHOLD);
|
||||
break;
|
||||
case 'mousemove':
|
||||
case 'touchmove':
|
||||
for (var i = 0; i < changedTouches.length; i++) {
|
||||
let touch = changedTouches[i];
|
||||
let identifier = (touch.identifier == undefined) ?
|
||||
this.MOUSE_ID : touch.identifier;
|
||||
let touchPoint = this._touchPoints[identifier];
|
||||
if (touchPoint)
|
||||
touchPoint.update(touch, timeStamp);
|
||||
}
|
||||
if (timeStamp - this._lastExploreTime >= EXPLORE_THROTTLE) {
|
||||
this.compileAndEmit(timeStamp);
|
||||
this._lastExploreTime = timeStamp;
|
||||
}
|
||||
break;
|
||||
case 'mouseup':
|
||||
case 'mouseleave':
|
||||
case 'touchend':
|
||||
for (var i = 0; i < changedTouches.length; i++) {
|
||||
let touch = changedTouches[i];
|
||||
let identifier = (touch.identifier == undefined) ?
|
||||
this.MOUSE_ID : touch.identifier;
|
||||
let touchPoint = this._touchPoints[identifier];
|
||||
if (touchPoint) {
|
||||
touchPoint.update(touch, timeStamp);
|
||||
touchPoint.finish();
|
||||
}
|
||||
}
|
||||
this.compileAndEmit(timeStamp);
|
||||
break;
|
||||
}
|
||||
|
||||
aEvent.preventDefault();
|
||||
aEvent.stopImmediatePropagation();
|
||||
},
|
||||
|
||||
cleanupTouches: function cleanupTouches() {
|
||||
for (var identifier in this._touchPoints) {
|
||||
if (!this._touchPoints[identifier].done)
|
||||
continue;
|
||||
|
||||
delete this._touchPoints[identifier];
|
||||
}
|
||||
},
|
||||
|
||||
compile: function TouchAdapter_compile(aTime) {
|
||||
let multiDetails = {};
|
||||
|
||||
// Compound multiple simultaneous touch gestures.
|
||||
for (let identifier in this._touchPoints) {
|
||||
let touchPoint = this._touchPoints[identifier];
|
||||
let details = touchPoint.compile(aTime);
|
||||
|
||||
if (!details)
|
||||
continue;
|
||||
|
||||
details.touches = [identifier];
|
||||
|
||||
let otherTouches = multiDetails[details.type];
|
||||
if (otherTouches) {
|
||||
otherTouches.touches.push(identifier);
|
||||
otherTouches.startTime =
|
||||
Math.min(otherTouches.startTime, touchPoint.startTime);
|
||||
} else {
|
||||
details.startTime = touchPoint.startTime;
|
||||
details.endTime = aTime;
|
||||
multiDetails[details.type] = details;
|
||||
}
|
||||
}
|
||||
|
||||
// Compound multiple consecutive touch gestures.
|
||||
for each (let details in multiDetails) {
|
||||
let idhash = details.touches.slice().sort().toString();
|
||||
let prevGesture = this._prevGestures[idhash];
|
||||
|
||||
if (prevGesture) {
|
||||
// The time delta is calculated as the period between the end of the
|
||||
// last gesture and the start of this one.
|
||||
let timeDelta = details.startTime - prevGesture.endTime;
|
||||
if (timeDelta > this.MAX_CONSECUTIVE_GESTURE_DELAY) {
|
||||
delete this._prevGestures[idhash];
|
||||
} else {
|
||||
let sequence = prevGesture.type + '-' + details.type;
|
||||
switch (sequence) {
|
||||
case 'tap-tap':
|
||||
details.type = 'doubletap';
|
||||
break;
|
||||
case 'doubletap-tap':
|
||||
details.type = 'tripletap';
|
||||
break;
|
||||
case 'doubletap-dwell':
|
||||
details.type = 'doubletaphold';
|
||||
break;
|
||||
case 'tap-dwell':
|
||||
details.type = 'taphold';
|
||||
break;
|
||||
case 'explore-explore':
|
||||
details.deltaX = details.x - prevGesture.x;
|
||||
details.deltaY = details.y - prevGesture.y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._prevGestures[idhash] = details;
|
||||
}
|
||||
|
||||
Utils.win.clearTimeout(this._dwellTimeout);
|
||||
this.cleanupTouches();
|
||||
|
||||
return multiDetails;
|
||||
},
|
||||
|
||||
emitGesture: function TouchAdapter_emitGesture(aDetails) {
|
||||
let emitDelay = 0;
|
||||
|
||||
// Unmutate gestures we are getting from Android when EBT is enabled.
|
||||
// Two finger gestures are translated to one. Double taps are translated
|
||||
// to single taps.
|
||||
if (Utils.MozBuildApp == 'mobile/android' &&
|
||||
Utils.AndroidSdkVersion >= 14 &&
|
||||
aDetails.touches[0] != this.MOUSE_ID) {
|
||||
if (aDetails.touches.length == 1) {
|
||||
if (aDetails.type == 'tap') {
|
||||
emitDelay = 50;
|
||||
aDetails.type = 'doubletap';
|
||||
} else if (aDetails.type == 'dwell') {
|
||||
emitDelay = 50;
|
||||
aDetails.type = 'doubletaphold';
|
||||
} else {
|
||||
aDetails.touches.push(this.MOUSE_ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let emit = function emit() {
|
||||
let evt = Utils.win.document.createEvent('CustomEvent');
|
||||
evt.initCustomEvent('mozAccessFuGesture', true, true, aDetails);
|
||||
Utils.win.dispatchEvent(evt);
|
||||
delete this._delayedEvent;
|
||||
}.bind(this);
|
||||
|
||||
if (emitDelay) {
|
||||
this._delayedEvent = Utils.win.setTimeout(emit, emitDelay);
|
||||
} else {
|
||||
emit();
|
||||
}
|
||||
},
|
||||
|
||||
compileAndEmit: function TouchAdapter_compileAndEmit(aTime) {
|
||||
for each (let details in this.compile(aTime)) {
|
||||
this.emitGesture(details);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/***
|
||||
* A TouchPoint represents a single touch from the moment of contact until it is
|
||||
* lifted from the surface. It is capable of compiling gestures from the scope
|
||||
* of one single touch.
|
||||
*/
|
||||
function TouchPoint(aTouch, aTime, aDPI) {
|
||||
this.startX = this.x = aTouch.screenX;
|
||||
this.startY = this.y = aTouch.screenY;
|
||||
this.startTime = aTime;
|
||||
this.distanceTraveled = 0;
|
||||
this.dpi = aDPI;
|
||||
this.done = false;
|
||||
}
|
||||
|
||||
TouchPoint.prototype = {
|
||||
update: function TouchPoint_update(aTouch, aTime) {
|
||||
let lastX = this.x;
|
||||
let lastY = this.y;
|
||||
this.x = aTouch.screenX;
|
||||
this.y = aTouch.screenY;
|
||||
this.time = aTime;
|
||||
|
||||
this.distanceTraveled += this.getDistanceToCoord(lastX, lastY);
|
||||
},
|
||||
|
||||
getDistanceToCoord: function TouchPoint_getDistanceToCoord(aX, aY) {
|
||||
return Math.sqrt(Math.pow(this.x - aX, 2) + Math.pow(this.y - aY, 2));
|
||||
},
|
||||
|
||||
finish: function TouchPoint_finish() {
|
||||
this.done = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Compile a gesture from an individual touch point. This is used by the
|
||||
* TouchAdapter to compound multiple single gestures in to higher level
|
||||
* gestures.
|
||||
*/
|
||||
compile: function TouchPoint_compile(aTime) {
|
||||
let directDistance = this.directDistanceTraveled;
|
||||
let duration = aTime - this.startTime;
|
||||
|
||||
// To be considered a tap/dwell...
|
||||
if ((this.distanceTraveled / this.dpi) < TouchAdapter.TAP_MAX_RADIUS) { // Didn't travel
|
||||
if (duration < TouchAdapter.DWELL_THRESHOLD) {
|
||||
// Mark it as done so we don't use this touch for another gesture.
|
||||
this.finish();
|
||||
return {type: 'tap', x: this.startX, y: this.startY};
|
||||
} else if (!this.done && duration == TouchAdapter.DWELL_THRESHOLD) {
|
||||
return {type: 'dwell', x: this.startX, y: this.startY};
|
||||
} else if (this.done && duration > TouchAdapter.DWELL_THRESHOLD) {
|
||||
return {type: 'dwellend', x: this.startX, y: this.startY};
|
||||
}
|
||||
}
|
||||
|
||||
// To be considered a swipe...
|
||||
if (duration <= TouchAdapter.SWIPE_MAX_DURATION && // Quick enough
|
||||
(directDistance / this.dpi) >= TouchAdapter.SWIPE_MIN_DISTANCE && // Traveled far
|
||||
(directDistance * 1.2) >= this.distanceTraveled) { // Direct enough
|
||||
|
||||
let swipeGesture = {x1: this.startX, y1: this.startY,
|
||||
x2: this.x, y2: this.y};
|
||||
let deltaX = this.x - this.startX;
|
||||
let deltaY = this.y - this.startY;
|
||||
|
||||
if (Math.abs(deltaX) > Math.abs(deltaY)) {
|
||||
// Horizontal swipe.
|
||||
if (deltaX > 0)
|
||||
swipeGesture.type = 'swiperight';
|
||||
else
|
||||
swipeGesture.type = 'swipeleft';
|
||||
} else if (Math.abs(deltaX) < Math.abs(deltaY)) {
|
||||
// Vertical swipe.
|
||||
if (deltaY > 0)
|
||||
swipeGesture.type = 'swipedown';
|
||||
else
|
||||
swipeGesture.type = 'swipeup';
|
||||
} else {
|
||||
// A perfect 45 degree swipe?? Not in our book.
|
||||
return null;
|
||||
}
|
||||
|
||||
this.finish();
|
||||
|
||||
return swipeGesture;
|
||||
}
|
||||
|
||||
// To be considered an explore...
|
||||
if (duration > TouchAdapter.SWIPE_MAX_DURATION &&
|
||||
(this.distanceTraveled / this.dpi) > TouchAdapter.TAP_MAX_RADIUS) {
|
||||
return {type: this.done ? 'exploreend' : 'explore',
|
||||
x: this.x, y: this.y};
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
get directDistanceTraveled() {
|
||||
return this.getDistanceToCoord(this.startX, this.startY);
|
||||
}
|
||||
};
|
@ -247,6 +247,16 @@ this.Utils = {
|
||||
return new Rect(objX.value, objY.value, objW.value, objH.value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get current display DPI.
|
||||
*/
|
||||
get dpi() {
|
||||
delete this.dpi;
|
||||
this.dpi = this.win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(
|
||||
Ci.nsIDOMWindowUtils).displayDPI;
|
||||
return this.dpi;
|
||||
},
|
||||
|
||||
isInSubtree: function isInSubtree(aAccessible, aSubTreeRoot) {
|
||||
let acc = aAccessible;
|
||||
while (acc) {
|
||||
|
@ -11,9 +11,10 @@ EXTRA_JS_MODULES += [
|
||||
'Constants.jsm',
|
||||
'ContentControl.jsm',
|
||||
'EventManager.jsm',
|
||||
'Gestures.jsm',
|
||||
'OutputGenerator.jsm',
|
||||
'PointerAdapter.jsm',
|
||||
'Presentation.jsm',
|
||||
'TouchAdapter.jsm',
|
||||
'TraversalRules.jsm',
|
||||
'Utils.jsm'
|
||||
]
|
||||
|
@ -12,10 +12,10 @@ support-files =
|
||||
[test_content_integration.html]
|
||||
[test_content_text.html]
|
||||
[test_explicit_names.html]
|
||||
[test_gesture_tracker.html]
|
||||
[test_landmarks.html]
|
||||
[test_live_regions.html]
|
||||
[test_output.html]
|
||||
[test_tables.html]
|
||||
[test_touch_adapter.html]
|
||||
skip-if = true # disabled for a number of intermitten failures: Bug 982326
|
||||
[test_pointer_relay.html]
|
||||
[test_traversal.html]
|
||||
|
@ -1,18 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
/*global getMainChromeWindow, getBoundsForDOMElm, AccessFuTest, Point*/
|
||||
/* exported loadJSON*/
|
||||
/* global getMainChromeWindow, AccessFuTest, GestureSettings, GestureTracker,
|
||||
SimpleTest, getBoundsForDOMElm, Point, Utils */
|
||||
/* exported loadJSON, eventMap */
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import('resource://gre/modules/accessibility/Utils.jsm');
|
||||
Cu.import('resource://gre/modules/Geometry.jsm');
|
||||
Cu.import("resource://gre/modules/accessibility/Gestures.jsm");
|
||||
|
||||
var win = getMainChromeWindow(window);
|
||||
|
||||
var winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(
|
||||
Ci.nsIDOMWindowUtils);
|
||||
/**
|
||||
* Convert inch based point coordinates into pixels.
|
||||
* @param {Array} aPoints Array of coordinates in inches.
|
||||
* @return {Array} Array of coordinates in pixels.
|
||||
*/
|
||||
function convertPointCoordinates(aPoints) {
|
||||
var dpi = Utils.dpi;
|
||||
return aPoints.map(function convert(aPoint) {
|
||||
return {
|
||||
x: aPoint.x * dpi,
|
||||
y: aPoint.y * dpi,
|
||||
identifier: aPoint.identifier
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given list of points calculate their coordinates in relation to the
|
||||
@ -28,12 +43,11 @@ var winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(
|
||||
*/
|
||||
function calculateTouchListCoordinates(aTouchPoints) {
|
||||
var coords = [];
|
||||
var dpi = winUtils.displayDPI;
|
||||
for (var i = 0, target = aTouchPoints[i]; i < aTouchPoints.length; ++i) {
|
||||
var bounds = getBoundsForDOMElm(target.base);
|
||||
var parentBounds = getBoundsForDOMElm('root');
|
||||
var point = new Point(target.x || 0, target.y || 0);
|
||||
point.scale(dpi);
|
||||
point.scale(Utils.dpi);
|
||||
point.add(bounds[0], bounds[1]);
|
||||
point.add(bounds[2] / 2, bounds[3] / 2);
|
||||
point.subtract(parentBounds[0], parentBounds[0]);
|
||||
@ -86,6 +100,9 @@ var eventMap = {
|
||||
touchmove: sendTouchEvent
|
||||
};
|
||||
|
||||
var originalDwellThreshold = GestureSettings.dwellThreshold;
|
||||
var originalSwipeMaxDuration = GestureSettings.swipeMaxDuration;
|
||||
|
||||
/**
|
||||
* Attach a listener for the mozAccessFuGesture event that tests its
|
||||
* type.
|
||||
@ -112,18 +129,49 @@ function testMozAccessFuGesture(aExpectedGestures) {
|
||||
win.addEventListener('mozAccessFuGesture', handleGesture);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the thresholds and max delays that affect gesture rejection.
|
||||
* @param {Number} aTimeStamp Gesture time stamp.
|
||||
* @param {Boolean} aRemoveDwellThreshold An optional flag to reset dwell
|
||||
* threshold.
|
||||
* @param {Boolean} aRemoveSwipeMaxDuration An optional flag to reset swipe max
|
||||
* duration.
|
||||
*/
|
||||
function setTimers(aTimeStamp, aRemoveDwellThreshold, aRemoveSwipeMaxDuration) {
|
||||
GestureSettings.dwellThreshold = originalDwellThreshold;
|
||||
GestureSettings.swipeMaxDuration = originalSwipeMaxDuration;
|
||||
if (!aRemoveDwellThreshold && !aRemoveSwipeMaxDuration) {
|
||||
return;
|
||||
}
|
||||
if (aRemoveDwellThreshold) {
|
||||
GestureSettings.dwellThreshold = 0;
|
||||
}
|
||||
if (aRemoveSwipeMaxDuration) {
|
||||
GestureSettings.swipeMaxDuration = 0;
|
||||
}
|
||||
GestureTracker.current.clearTimer();
|
||||
GestureTracker.current.startTimer(aTimeStamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* An extention to AccessFuTest that adds an ability to test a sequence of
|
||||
* touch/mouse/etc events and their expected mozAccessFuGesture events.
|
||||
* @param {Object} aSequence An object that has a list of touch/mouse/etc events
|
||||
* to be generated and the expected mozAccessFuGesture events.
|
||||
* pointer events and their expected mozAccessFuGesture events.
|
||||
* @param {Object} aSequence An object that has a list of pointer events to be
|
||||
* generated and the expected mozAccessFuGesture events.
|
||||
*/
|
||||
AccessFuTest.addSequence = function AccessFuTest_addSequence(aSequence) {
|
||||
AccessFuTest.addFunc(function testSequence() {
|
||||
testMozAccessFuGesture(aSequence.expectedGestures);
|
||||
var events = aSequence.events;
|
||||
function fireEvent(aEvent) {
|
||||
eventMap[aEvent.type](aEvent.target, aEvent.type);
|
||||
var event = {
|
||||
points: convertPointCoordinates(aEvent.points),
|
||||
type: aEvent.type
|
||||
};
|
||||
var timeStamp = Date.now();
|
||||
GestureTracker.handle(event, timeStamp);
|
||||
setTimers(timeStamp, aEvent.removeDwellThreshold,
|
||||
aEvent.removeSwipeMaxDuration);
|
||||
processEvents();
|
||||
}
|
||||
function processEvents() {
|
||||
@ -131,11 +179,9 @@ AccessFuTest.addSequence = function AccessFuTest_addSequence(aSequence) {
|
||||
return;
|
||||
}
|
||||
var event = events.shift();
|
||||
if (event.delay) {
|
||||
window.setTimeout(fireEvent, event.delay, event);
|
||||
} else {
|
||||
SimpleTest.executeSoon(function() {
|
||||
fireEvent(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
processEvents();
|
||||
});
|
||||
|
@ -1,193 +1,248 @@
|
||||
[
|
||||
{
|
||||
"events": [
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
|
||||
{"type": "touchend", "delay": 50}
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": "tap"
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
|
||||
{"type": "touchmove",
|
||||
"target": [{"base": "button", "x": 0.03, "y": 0.02}], "delay": 25},
|
||||
{"type": "touchend", "delay": 25}
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointermove",
|
||||
"points": [{"x": 1.03, "y": 1.03, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 1.03, "y": 1.03, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": "tap"
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
|
||||
{"type": "touchend", "delay": 525}
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}],
|
||||
"removeDwellThreshold": true},
|
||||
{"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": ["dwell", "dwellend"]
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
|
||||
{"type": "touchmove",
|
||||
"target": [{"base": "button", "x": 0.03, "y": 0.02}], "delay": 25},
|
||||
{"type": "touchend", "delay": 25},
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}],
|
||||
"delay": 25},
|
||||
{"type": "touchmove",
|
||||
"target": [{"base": "button", "x": -0.03, "y": 0.01}], "delay": 25},
|
||||
{"type": "touchend", "delay": 25}
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointermove",
|
||||
"points": [{"x": 1.03, "y": 1.02, "identifier": 1}]},
|
||||
{"type": "pointerup",
|
||||
"points": [{"x": 1.03, "y": 1.02, "identifier": 1}]},
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointermove",
|
||||
"points": [{"x": 0.97, "y": 1.01, "identifier": 1}]},
|
||||
{"type": "pointerup",
|
||||
"points": [{"x": 0.97, "y": 1.01, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": ["tap", "doubletap"]
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
|
||||
{"type": "touchend", "delay": 25},
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}],
|
||||
"delay": 25},
|
||||
{"type": "touchend", "delay": 25},
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}],
|
||||
"delay": 25},
|
||||
{"type": "touchend", "delay": 25}
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": ["tap", "doubletap", "tripletap"]
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
|
||||
{"type": "touchend", "delay": 25},
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}],
|
||||
"delay": 25},
|
||||
{"type": "touchend", "delay": 25},
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}],
|
||||
"delay": 25},
|
||||
{"type": "touchend", "delay": 525}
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}],
|
||||
"removeDwellThreshold": true},
|
||||
{"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": ["tap", "doubletap", "doubletaphold", "dwellend"]
|
||||
"expectedGestures": ["tap", "doubletap", "doubletaphold",
|
||||
"doubletapholdend"]
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
|
||||
{"type": "touchend", "delay": 25},
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}],
|
||||
"delay": 25},
|
||||
{"type": "touchend", "delay": 525}
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}],
|
||||
"removeDwellThreshold": true},
|
||||
{"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": ["tap", "taphold", "dwellend"]
|
||||
"expectedGestures": ["tap", "taphold", "tapholdend"]
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
|
||||
{"type": "touchmove", "target": [{"base": "button", "x": 0.5, "y": 0}],
|
||||
"delay": 25},
|
||||
{"type": "touchend", "delay": 25}
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointermove", "points": [{"x": 1.5, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 1.5, "y": 1, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": "swiperight"
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0.5, "y": 0}]},
|
||||
{"type": "touchmove", "target": [{"base": "button", "x": 0, "y": 0}],
|
||||
"delay": 25},
|
||||
{"type": "touchend", "delay": 25}
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointermove", "points": [{"x": 1.15, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointermove", "points": [{"x": 1.3, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 1.3, "y": 1, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": "swiperight"
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "pointerdown", "points": [{"x": 1.5, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": "swipeleft"
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
|
||||
{"type": "touchmove", "target": [{"base": "button", "x": 0, "y": 0.5}],
|
||||
"delay": 25},
|
||||
{"type": "touchend", "delay": 25}
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointermove", "points": [{"x": 1, "y": 1.5, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 1, "y": 1.5, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": "swipedown"
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0.5}]},
|
||||
{"type": "touchmove", "target": [{"base": "button", "x": 0, "y": 0}],
|
||||
"delay": 25},
|
||||
{"type": "touchend", "delay": 25}
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1.5, "identifier": 1}]},
|
||||
{"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": "swipeup"
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
|
||||
{"type": "touchmove", "target": [{"base": "button", "x": 0.5, "y": 0.1}],
|
||||
"delay": 25},
|
||||
{"type": "touchend", "delay": 25}
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointermove",
|
||||
"points": [{"x": 1.5, "y": 1.1, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 1.5, "y": 1.1, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": "swiperight"
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "touchstart",
|
||||
"target": [{"base": "button", "x": 0.5, "y": 0.1}]},
|
||||
{"type": "touchmove", "target": [{"base": "button", "x": 0, "y": -0.05}],
|
||||
"delay": 25},
|
||||
{"type": "touchend", "delay": 25}
|
||||
{"type": "pointerdown",
|
||||
"points": [{"x": 1.5, "y": 1.1, "identifier": 1}]},
|
||||
{"type": "pointermove", "points": [{"x": 1, "y": 0.95, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 1, "y": 0.95, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": "swipeleft"
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
|
||||
{"type": "touchmove",
|
||||
"target": [{"base": "button", "x": -0.1, "y": 0.5}], "delay": 25},
|
||||
{"type": "touchend", "delay": 25}
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointermove",
|
||||
"points": [{"x": 0.9, "y": 1.5, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 0.9, "y": 1.5, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": "swipedown"
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "touchstart",
|
||||
"target": [{"base": "button", "x": 0.1, "y": 0.5}]},
|
||||
{"type": "touchmove", "target": [{"base": "button", "x": 0, "y": 0}],
|
||||
"delay": 25},
|
||||
{"type": "touchend", "delay": 25}
|
||||
{"type": "pointerdown",
|
||||
"points": [{"x": 1.1, "y": 1.5, "identifier": 1}]},
|
||||
{"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": "swipeup"
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0},
|
||||
{"base": "button", "x": 0, "y": 0.5}]},
|
||||
{"type": "touchmove", "target": [{"base": "button", "x": 0.5, "y": 0},
|
||||
{"base": "button", "x": 0.5, "y": 0.5}], "delay": 25},
|
||||
{"type": "touchend", "delay": 25}
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1},
|
||||
{"x": 1, "y": 1.5, "identifier": 2}]},
|
||||
{"type": "pointermove", "points": [{"x": 1.5, "y": 1, "identifier": 1},
|
||||
{"x": 1.5, "y": 1.5, "identifier": 2}]},
|
||||
{"type": "pointerup", "points": [{"x": 1.5, "y": 1, "identifier": 1},
|
||||
{"x": 1.5, "y": 1.5, "identifier": 2}]}
|
||||
],
|
||||
"expectedGestures": "swiperight"
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0},
|
||||
{"base": "button", "x": 0, "y": 0.5},
|
||||
{"base": "button", "x": 0, "y": 1}]},
|
||||
{"type": "touchmove", "target": [{"base": "button", "x": 0.5, "y": 0},
|
||||
{"base": "button", "x": 0.5, "y": 0.5},
|
||||
{"base": "button", "x": 0.5, "y": 1}],
|
||||
"delay": 25},
|
||||
{"type": "touchend", "delay": 25}
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1},
|
||||
{"x": 1, "y": 1.5, "identifier": 2}]},
|
||||
{"type": "pointermove", "points": [{"x": 1.5, "y": 1, "identifier": 1},
|
||||
{"x": 1.5, "y": 1.5, "identifier": 2}]},
|
||||
{"type": "pointerup", "points": [{"x": 1.5, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 1.5, "y": 1.5, "identifier": 2}]}
|
||||
],
|
||||
"expectedGestures": "swiperight"
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "touchstart",
|
||||
"target": [{"base": "button", "x": 0.6, "y": 0.5}]},
|
||||
{"type": "touchmove", "target": [{"base": "button", "x": 0, "y": 0}],
|
||||
"delay": 525},
|
||||
{"type": "touchend", "delay": 25}
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1},
|
||||
{"x": 1, "y": 1.5, "identifier": 2},
|
||||
{"x": 1, "y": 2, "identifier": 3}]},
|
||||
{"type": "pointermove", "points": [{"x": 1.5, "y": 1, "identifier": 1},
|
||||
{"x": 1.5, "y": 1.5, "identifier": 2},
|
||||
{"x": 1.5, "y": 2, "identifier": 3}]},
|
||||
{"type": "pointerup", "points": [{"x": 1.5, "y": 1, "identifier": 1},
|
||||
{"x": 1.5, "y": 1.5, "identifier": 2},
|
||||
{"x": 1.5, "y": 2, "identifier": 3}]}
|
||||
],
|
||||
"expectedGestures": "swiperight"
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "pointerdown",
|
||||
"points": [{"x": 1.6, "y": 1.5, "identifier": 1}],
|
||||
"removeDwellThreshold": true},
|
||||
{"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": ["dwell", "explore", "exploreend"]
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "touchstart",
|
||||
"target": [{"base": "button", "x": 0, "y": 0.5}]},
|
||||
{"type": "touchmove", "target": [{"base": "button", "x": 0, "y": 0}],
|
||||
"delay": 525},
|
||||
{"type": "touchmove", "target": [{"base": "button", "x": 0.5, "y": 0}],
|
||||
"delay": 125},
|
||||
{"type": "touchend", "delay": 25}
|
||||
{"type": "pointerdown",
|
||||
"points": [{"x": 1.6, "y": 1.5, "identifier": 1}],
|
||||
"removeDwellThreshold": true},
|
||||
{"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointermove", "points": [{"x": 2, "y": 2, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 2, "y": 2, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": ["dwell", "explore", "explore", "exploreend"]
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "pointerdown",
|
||||
"points": [{"x": 1.6, "y": 1.5, "identifier": 1}]},
|
||||
{"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}],
|
||||
"removeSwipeMaxDuration": true},
|
||||
{"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": ["explore", "exploreend"]
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1.5, "identifier": 1}]},
|
||||
{"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}],
|
||||
"removeSwipeMaxDuration": true},
|
||||
{"type": "pointermove", "points": [{"x": 1.5, "y": 1, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 1.5, "y": 1, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": ["explore", "explore", "exploreend"]
|
||||
},
|
||||
{
|
||||
"events": [
|
||||
{"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}],
|
||||
"removeDwellThreshold": true},
|
||||
{"type": "pointermove",
|
||||
"points": [{"x": 1.5, "y": 1.5, "identifier": 1}]},
|
||||
{"type": "pointermove",
|
||||
"points": [{"x": 1.55, "y": 1.5, "identifier": 1}]},
|
||||
{"type": "pointermove",
|
||||
"points": [{"x": 1.6, "y": 1.5, "identifier": 1}]},
|
||||
{"type": "pointermove",
|
||||
"points": [{"x": 1.65, "y": 1.5, "identifier": 1}]},
|
||||
{"type": "pointermove",
|
||||
"points": [{"x": 1.7, "y": 1.5, "identifier": 1}]},
|
||||
{"type": "pointermove",
|
||||
"points": [{"x": 1.75, "y": 1.5, "identifier": 1}]},
|
||||
{"type": "pointerup", "points": [{"x": 1.75, "y": 1.5, "identifier": 1}]}
|
||||
],
|
||||
"expectedGestures": ["dwell", "explore", "explore", "exploreend"]
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>AccessFu tests for touch adapters.</title>
|
||||
<title>AccessFu tests for gesture tracker.</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
|
||||
@ -13,36 +13,23 @@
|
||||
<script type="application/javascript" src="./dom_helper.js"></script>
|
||||
<script type="application/javascript">
|
||||
|
||||
Components.utils.import(
|
||||
"resource://gre/modules/accessibility/TouchAdapter.jsm");
|
||||
|
||||
function startComponents() {
|
||||
TouchAdapter.start();
|
||||
function startGestureTracker() {
|
||||
GestureTracker.reset();
|
||||
AccessFuTest.nextTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all state data related to previous events/gestures.
|
||||
*/
|
||||
function cleanupTouchAdapter() {
|
||||
TouchAdapter.cleanupTouches();
|
||||
TouchAdapter._touchPoints = {};
|
||||
TouchAdapter._dwellTimeout = 0;
|
||||
TouchAdapter._prevGestures = {};
|
||||
TouchAdapter._lastExploreTime = 0;
|
||||
}
|
||||
|
||||
function stopComponents() {
|
||||
TouchAdapter.stop();
|
||||
function stopGestureTracker() {
|
||||
GestureTracker.reset();
|
||||
AccessFuTest.finish();
|
||||
}
|
||||
|
||||
function doTest() {
|
||||
loadJSON("./gestures.json", function onSuccess(gestures) {
|
||||
AccessFuTest.addFunc(startComponents);
|
||||
AccessFuTest.sequenceCleanup = cleanupTouchAdapter;
|
||||
AccessFuTest.addFunc(startGestureTracker);
|
||||
AccessFuTest.sequenceCleanup = GestureTracker.reset.bind(
|
||||
GestureTracker);
|
||||
gestures.forEach(AccessFuTest.addSequence);
|
||||
AccessFuTest.addFunc(stopComponents);
|
||||
AccessFuTest.addFunc(stopGestureTracker);
|
||||
AccessFuTest.waitForExplicitFinish();
|
||||
AccessFuTest.runTests();
|
||||
});
|
||||
@ -53,14 +40,11 @@
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body id="root">
|
||||
<body>
|
||||
<a target="_blank"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=976082"
|
||||
title="[AccessFu] Provide tests for touch adapter.">
|
||||
Mozilla Bug 976082
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=981015"
|
||||
title="AccessFu tests for gesture tracker.">
|
||||
Mozilla Bug 981015
|
||||
</a>
|
||||
<div>
|
||||
<button id="button">I am a button</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
95
accessible/tests/mochitest/jsat/test_pointer_relay.html
Normal file
95
accessible/tests/mochitest/jsat/test_pointer_relay.html
Normal file
@ -0,0 +1,95 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>AccessFu tests for pointer relay.</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="../common.js"></script>
|
||||
<script type="application/javascript" src="../layout.js"></script>
|
||||
<script type="application/javascript" src="./jsatcommon.js"></script>
|
||||
<script type="application/javascript" src="./dom_helper.js"></script>
|
||||
<script type="application/javascript">
|
||||
|
||||
Components.utils.import(
|
||||
"resource://gre/modules/accessibility/PointerAdapter.jsm");
|
||||
|
||||
var tests = [
|
||||
{
|
||||
type: 'touchstart', target: [{base: 'button'}],
|
||||
expected: {type: 'pointerdown', length: 1}
|
||||
},
|
||||
{
|
||||
type: 'touchmove', target: [{base: 'button'}],
|
||||
expected: {type: 'pointermove', length: 1}
|
||||
},
|
||||
{
|
||||
type: 'touchend', target: [{base: 'button'}],
|
||||
expected: {type: 'pointerup'}
|
||||
},
|
||||
{
|
||||
type: 'touchstart', target: [{base: 'button'},
|
||||
{base: 'button', x: 0.5, y: 0.3}],
|
||||
expected: {type: 'pointerdown', length: 2}
|
||||
},
|
||||
{
|
||||
type: 'touchend', target: [{base: 'button'},
|
||||
{base: 'button', x: 0.5, y: 0.3}],
|
||||
expected: {type: 'pointerup'}
|
||||
},
|
||||
{
|
||||
type: 'touchstart', target: [{base: 'button'},
|
||||
{base: 'button', x: 0.5, y: 0.3},
|
||||
{base: 'button', x: 0.5, y: -0.3}],
|
||||
expected: {type: 'pointerdown', length: 3}
|
||||
},
|
||||
{
|
||||
type: 'touchend', target: [{base: 'button'},
|
||||
{base: 'button', x: 0.5, y: 0.3},
|
||||
{base: 'button', x: 0.5, y: -0.3}],
|
||||
expected: {type: 'pointerup'}
|
||||
}
|
||||
];
|
||||
|
||||
function makeTestFromSpec(test) {
|
||||
return function runTest() {
|
||||
PointerRelay.start(function onPointerEvent(aDetail) {
|
||||
is(aDetail.type, test.expected.type,
|
||||
'mozAccessFuPointerEvent is correct.');
|
||||
if (test.expected.length) {
|
||||
is(aDetail.points.length, test.expected.length,
|
||||
'mozAccessFuPointerEvent points length is correct.');
|
||||
}
|
||||
PointerRelay.stop();
|
||||
AccessFuTest.nextTest();
|
||||
});
|
||||
eventMap[test.type](test.target, test.type);
|
||||
};
|
||||
}
|
||||
|
||||
function doTest() {
|
||||
tests.forEach(function addTest(test) {
|
||||
AccessFuTest.addFunc(makeTestFromSpec(test));
|
||||
});
|
||||
AccessFuTest.waitForExplicitFinish();
|
||||
AccessFuTest.runTests();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addA11yLoadEvent(doTest);
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body id="root">
|
||||
<a target="_blank"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=976082"
|
||||
title="[AccessFu] Provide tests for pointer relay.">
|
||||
Mozilla Bug 981015
|
||||
</a>
|
||||
<div>
|
||||
<button id="button">I am a button</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user