michael@0: 'use strict'; michael@0: michael@0: /* global getMainChromeWindow, AccessFuTest, GestureSettings, GestureTracker, michael@0: SimpleTest, getBoundsForDOMElm, Point, Utils */ michael@0: /* exported loadJSON, eventMap */ michael@0: michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: michael@0: Cu.import('resource://gre/modules/accessibility/Utils.jsm'); michael@0: Cu.import('resource://gre/modules/Geometry.jsm'); michael@0: Cu.import("resource://gre/modules/accessibility/Gestures.jsm"); michael@0: michael@0: var win = getMainChromeWindow(window); michael@0: michael@0: /** michael@0: * Convert inch based point coordinates into pixels. michael@0: * @param {Array} aPoints Array of coordinates in inches. michael@0: * @return {Array} Array of coordinates in pixels. michael@0: */ michael@0: function convertPointCoordinates(aPoints) { michael@0: var dpi = Utils.dpi; michael@0: return aPoints.map(function convert(aPoint) { michael@0: return { michael@0: x: aPoint.x * dpi, michael@0: y: aPoint.y * dpi, michael@0: identifier: aPoint.identifier michael@0: }; michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * For a given list of points calculate their coordinates in relation to the michael@0: * document body. michael@0: * @param {Array} aTouchPoints An array of objects of the following format: { michael@0: * base: {String}, // Id of an element to server as a base for the touch. michael@0: * x: {Number}, // An optional x offset from the base element's geometric michael@0: * // centre. michael@0: * y: {Number} // An optional y offset from the base element's geometric michael@0: * // centre. michael@0: * } michael@0: * @return {JSON} An array of {x, y} coordinations. michael@0: */ michael@0: function calculateTouchListCoordinates(aTouchPoints) { michael@0: var coords = []; michael@0: for (var i = 0, target = aTouchPoints[i]; i < aTouchPoints.length; ++i) { michael@0: var bounds = getBoundsForDOMElm(target.base); michael@0: var parentBounds = getBoundsForDOMElm('root'); michael@0: var point = new Point(target.x || 0, target.y || 0); michael@0: point.scale(Utils.dpi); michael@0: point.add(bounds[0], bounds[1]); michael@0: point.add(bounds[2] / 2, bounds[3] / 2); michael@0: point.subtract(parentBounds[0], parentBounds[0]); michael@0: coords.push({ michael@0: x: point.x, michael@0: y: point.y michael@0: }); michael@0: } michael@0: return coords; michael@0: } michael@0: michael@0: /** michael@0: * Send a touch event with specified touchPoints. michael@0: * @param {Array} aTouchPoints An array of points to be associated with michael@0: * touches. michael@0: * @param {String} aName A name of the touch event. michael@0: */ michael@0: function sendTouchEvent(aTouchPoints, aName) { michael@0: var touchList = sendTouchEvent.touchList; michael@0: if (aName === 'touchend') { michael@0: sendTouchEvent.touchList = null; michael@0: } else { michael@0: var coords = calculateTouchListCoordinates(aTouchPoints); michael@0: var touches = []; michael@0: for (var i = 0; i < coords.length; ++i) { michael@0: var {x, y} = coords[i]; michael@0: var node = document.elementFromPoint(x, y); michael@0: var touch = document.createTouch(window, node, aName === 'touchstart' ? michael@0: 1 : touchList.item(i).identifier, x, y, x, y); michael@0: touches.push(touch); michael@0: } michael@0: touchList = document.createTouchList(touches); michael@0: sendTouchEvent.touchList = touchList; michael@0: } michael@0: var evt = document.createEvent('TouchEvent'); michael@0: evt.initTouchEvent(aName, true, true, window, 0, false, false, false, false, michael@0: touchList, touchList, touchList); michael@0: document.dispatchEvent(evt); michael@0: } michael@0: michael@0: sendTouchEvent.touchList = null; michael@0: michael@0: /** michael@0: * A map of event names to the functions that actually send them. michael@0: * @type {Object} michael@0: */ michael@0: var eventMap = { michael@0: touchstart: sendTouchEvent, michael@0: touchend: sendTouchEvent, michael@0: touchmove: sendTouchEvent michael@0: }; michael@0: michael@0: var originalDwellThreshold = GestureSettings.dwellThreshold; michael@0: var originalSwipeMaxDuration = GestureSettings.swipeMaxDuration; michael@0: michael@0: /** michael@0: * Attach a listener for the mozAccessFuGesture event that tests its michael@0: * type. michael@0: * @param {Array} aExpectedGestures A stack of expected event types. michael@0: * Note: the listener is removed once the stack reaches 0. michael@0: */ michael@0: function testMozAccessFuGesture(aExpectedGestures) { michael@0: var types = typeof aExpectedGestures === "string" ? michael@0: [aExpectedGestures] : aExpectedGestures; michael@0: function handleGesture(aEvent) { michael@0: if (aEvent.detail.type !== types[0]) { michael@0: // The is not the event of interest. michael@0: return; michael@0: } michael@0: ok(true, 'Received correct mozAccessFuGesture: ' + types.shift() + '.'); michael@0: if (types.length === 0) { michael@0: win.removeEventListener('mozAccessFuGesture', handleGesture); michael@0: if (AccessFuTest.sequenceCleanup) { michael@0: AccessFuTest.sequenceCleanup(); michael@0: } michael@0: AccessFuTest.nextTest(); michael@0: } michael@0: } michael@0: win.addEventListener('mozAccessFuGesture', handleGesture); michael@0: } michael@0: michael@0: /** michael@0: * Reset the thresholds and max delays that affect gesture rejection. michael@0: * @param {Number} aTimeStamp Gesture time stamp. michael@0: * @param {Boolean} aRemoveDwellThreshold An optional flag to reset dwell michael@0: * threshold. michael@0: * @param {Boolean} aRemoveSwipeMaxDuration An optional flag to reset swipe max michael@0: * duration. michael@0: */ michael@0: function setTimers(aTimeStamp, aRemoveDwellThreshold, aRemoveSwipeMaxDuration) { michael@0: if (!aRemoveDwellThreshold && !aRemoveSwipeMaxDuration) { michael@0: return; michael@0: } michael@0: if (aRemoveDwellThreshold) { michael@0: GestureSettings.dwellThreshold = 0; michael@0: } michael@0: if (aRemoveSwipeMaxDuration) { michael@0: GestureSettings.swipeMaxDuration = 0; michael@0: } michael@0: GestureTracker.current.clearTimer(); michael@0: GestureTracker.current.startTimer(aTimeStamp); michael@0: } michael@0: michael@0: function resetTimers() { michael@0: GestureSettings.dwellThreshold = originalDwellThreshold; michael@0: GestureSettings.swipeMaxDuration = originalSwipeMaxDuration; michael@0: } michael@0: michael@0: /** michael@0: * An extention to AccessFuTest that adds an ability to test a sequence of michael@0: * pointer events and their expected mozAccessFuGesture events. michael@0: * @param {Object} aSequence An object that has a list of pointer events to be michael@0: * generated and the expected mozAccessFuGesture events. michael@0: */ michael@0: AccessFuTest.addSequence = function AccessFuTest_addSequence(aSequence) { michael@0: AccessFuTest.addFunc(function testSequence() { michael@0: testMozAccessFuGesture(aSequence.expectedGestures); michael@0: var events = aSequence.events; michael@0: function fireEvent(aEvent) { michael@0: var event = { michael@0: points: convertPointCoordinates(aEvent.points), michael@0: type: aEvent.type michael@0: }; michael@0: var timeStamp = Date.now(); michael@0: resetTimers(); michael@0: GestureTracker.handle(event, timeStamp); michael@0: setTimers(timeStamp, aEvent.removeDwellThreshold, michael@0: aEvent.removeSwipeMaxDuration); michael@0: processEvents(); michael@0: } michael@0: function processEvents() { michael@0: if (events.length === 0) { michael@0: return; michael@0: } michael@0: var event = events.shift(); michael@0: SimpleTest.executeSoon(function() { michael@0: fireEvent(event); michael@0: }); michael@0: } michael@0: processEvents(); michael@0: }); michael@0: }; michael@0: michael@0: /** michael@0: * A helper function that loads JSON files. michael@0: * @param {String} aPath A path to a JSON file. michael@0: * @param {Function} aCallback A callback to be called on success. michael@0: */ michael@0: function loadJSON(aPath, aCallback) { michael@0: var request = new XMLHttpRequest(); michael@0: request.open('GET', aPath, true); michael@0: request.responseType = 'json'; michael@0: request.onload = function onload() { michael@0: aCallback(request.response); michael@0: }; michael@0: request.send(); michael@0: }