'use strict'; /* global getMainChromeWindow, AccessFuTest, GestureSettings, GestureTracker, SimpleTest, getBoundsForDOMElm, Point, Utils */ /* exported loadJSON, eventMap */ var Ci = Components.interfaces; var Cu = Components.utils; Cu.import('resource://gre/modules/Geometry.jsm'); var win = getMainChromeWindow(window); /** * 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 * document body. * @param {Array} aTouchPoints An array of objects of the following format: { * base: {String}, // Id of an element to server as a base for the touch. * x: {Number}, // An optional x offset from the base element's geometric * // centre. * y: {Number} // An optional y offset from the base element's geometric * // centre. * } * @return {JSON} An array of {x, y} coordinations. */ function calculateTouchListCoordinates(aTouchPoints) { var coords = []; 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(Utils.dpi); point.add(bounds[0], bounds[1]); point.add(bounds[2] / 2, bounds[3] / 2); point.subtract(parentBounds[0], parentBounds[0]); coords.push({ x: point.x, y: point.y }); } return coords; } /** * Send a touch event with specified touchPoints. * @param {Array} aTouchPoints An array of points to be associated with * touches. * @param {String} aName A name of the touch event. */ function sendTouchEvent(aTouchPoints, aName) { var touchList = sendTouchEvent.touchList; if (aName === 'touchend') { sendTouchEvent.touchList = null; } else { var coords = calculateTouchListCoordinates(aTouchPoints); var touches = []; for (var i = 0; i < coords.length; ++i) { var {x, y} = coords[i]; var node = document.elementFromPoint(x, y); var touch = document.createTouch(window, node, aName === 'touchstart' ? 1 : touchList.item(i).identifier, x, y, x, y); touches.push(touch); } touchList = document.createTouchList(touches); sendTouchEvent.touchList = touchList; } var evt = document.createEvent('TouchEvent'); evt.initTouchEvent(aName, true, true, window, 0, false, false, false, false, touchList, touchList, touchList); document.dispatchEvent(evt); } sendTouchEvent.touchList = null; /** * A map of event names to the functions that actually send them. * @type {Object} */ var eventMap = { touchstart: sendTouchEvent, touchend: sendTouchEvent, touchmove: sendTouchEvent }; /** * Attach a listener for the mozAccessFuGesture event that tests its * type. * @param {Array} aExpectedGestures A stack of expected event types. * @param {String} aTitle Title of this sequence, if any. * Note: the listener is removed once the stack reaches 0. */ function testMozAccessFuGesture(aExpectedGestures, aTitle) { var types = aExpectedGestures; function handleGesture(aEvent) { if (aEvent.detail.type !== types[0].type) { info('Got ' + aEvent.detail.type + ' waiting for ' + types[0].type); // The is not the event of interest. return; } is(!!aEvent.detail.edge, !!types[0].edge); is(aEvent.detail.touches.length, types[0].fingers || 1, 'failed to count fingers: ' + types[0].type); ok(true, 'Received correct mozAccessFuGesture: ' + JSON.stringify(types.shift()) + '. (' + aTitle + ')'); if (types.length === 0) { win.removeEventListener('mozAccessFuGesture', handleGesture); if (AccessFuTest.sequenceCleanup) { AccessFuTest.sequenceCleanup(); } AccessFuTest.nextTest(); } } 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) { if (!aRemoveDwellThreshold && !aRemoveSwipeMaxDuration) { return; } if (aRemoveDwellThreshold) { GestureSettings.dwellThreshold = 0; } if (aRemoveSwipeMaxDuration) { GestureSettings.swipeMaxDuration = 0; } GestureTracker.current.clearTimer(); GestureTracker.current.startTimer(aTimeStamp); } function resetTimers(aRemoveGestureResolveDelay) { GestureSettings.dwellThreshold = AccessFuTest.dwellThreshold; GestureSettings.swipeMaxDuration = AccessFuTest.swipeMaxDuration; GestureSettings.maxGestureResolveTimeout = aRemoveGestureResolveDelay ? 0 : AccessFuTest.maxGestureResolveTimeout; } /** * An extention to AccessFuTest that adds an ability to test a sequence of * 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, aSequence.title); var events = aSequence.events; function fireEvent(aEvent) { var event = { points: convertPointCoordinates(aEvent.points), type: aEvent.type }; var timeStamp = Date.now(); resetTimers(aEvent.removeGestureResolveDelay); GestureTracker.handle(event, timeStamp); setTimers(timeStamp, aEvent.removeDwellThreshold, aEvent.removeSwipeMaxDuration); processEvents(); } function processEvents() { if (events.length === 0) { return; } var event = events.shift(); SimpleTest.executeSoon(function() { fireEvent(event); }); } processEvents(); }); }; /** * A helper function that loads JSON files. * @param {String} aPath A path to a JSON file. * @param {Function} aCallback A callback to be called on success. */ function loadJSON(aPath, aCallback) { var request = new XMLHttpRequest(); request.open('GET', aPath, true); request.responseType = 'json'; request.onload = function onload() { aCallback(request.response); }; request.send(); }