1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/accessible/src/jsat/PointerAdapter.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,206 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +/* global Components, XPCOMUtils, Utils, Logger, GestureSettings, 1.9 + GestureTracker */ 1.10 +/* exported PointerRelay, PointerAdapter */ 1.11 + 1.12 +'use strict'; 1.13 + 1.14 +const Ci = Components.interfaces; 1.15 +const Cu = Components.utils; 1.16 + 1.17 +this.EXPORTED_SYMBOLS = ['PointerRelay', 'PointerAdapter']; // jshint ignore:line 1.18 + 1.19 +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); 1.20 + 1.21 +XPCOMUtils.defineLazyModuleGetter(this, 'Utils', // jshint ignore:line 1.22 + 'resource://gre/modules/accessibility/Utils.jsm'); 1.23 +XPCOMUtils.defineLazyModuleGetter(this, 'Logger', // jshint ignore:line 1.24 + 'resource://gre/modules/accessibility/Utils.jsm'); 1.25 +XPCOMUtils.defineLazyModuleGetter(this, 'GestureSettings', // jshint ignore:line 1.26 + 'resource://gre/modules/accessibility/Gestures.jsm'); 1.27 +XPCOMUtils.defineLazyModuleGetter(this, 'GestureTracker', // jshint ignore:line 1.28 + 'resource://gre/modules/accessibility/Gestures.jsm'); 1.29 + 1.30 +// The virtual touch ID generated by a mouse event. 1.31 +const MOUSE_ID = 'mouse'; 1.32 +// Synthesized touch ID. 1.33 +const SYNTH_ID = -1; 1.34 + 1.35 +let PointerRelay = { // jshint ignore:line 1.36 + /** 1.37 + * A mapping of events we should be intercepting. Entries with a value of 1.38 + * |true| are used for compiling high-level gesture events. Entries with a 1.39 + * value of |false| are cancelled and do not propogate to content. 1.40 + */ 1.41 + get _eventsOfInterest() { 1.42 + delete this._eventsOfInterest; 1.43 + 1.44 + switch (Utils.widgetToolkit) { 1.45 + case 'gonk': 1.46 + this._eventsOfInterest = { 1.47 + 'touchstart' : true, 1.48 + 'touchmove' : true, 1.49 + 'touchend' : true, 1.50 + 'mousedown' : false, 1.51 + 'mousemove' : false, 1.52 + 'mouseup': false, 1.53 + 'click': false }; 1.54 + break; 1.55 + 1.56 + case 'android': 1.57 + this._eventsOfInterest = { 1.58 + 'touchstart' : true, 1.59 + 'touchmove' : true, 1.60 + 'touchend' : true, 1.61 + 'mousemove' : true, 1.62 + 'mouseenter' : true, 1.63 + 'mouseleave' : true, 1.64 + 'mousedown' : false, 1.65 + 'mouseup': false, 1.66 + 'click': false }; 1.67 + break; 1.68 + 1.69 + default: 1.70 + // Desktop. 1.71 + this._eventsOfInterest = { 1.72 + 'mousemove' : true, 1.73 + 'mousedown' : true, 1.74 + 'mouseup': true, 1.75 + 'click': false 1.76 + }; 1.77 + if ('ontouchstart' in Utils.win) { 1.78 + for (let eventType of ['touchstart', 'touchmove', 'touchend']) { 1.79 + this._eventsOfInterest[eventType] = true; 1.80 + } 1.81 + } 1.82 + break; 1.83 + } 1.84 + 1.85 + return this._eventsOfInterest; 1.86 + }, 1.87 + 1.88 + _eventMap: { 1.89 + 'touchstart' : 'pointerdown', 1.90 + 'mousedown' : 'pointerdown', 1.91 + 'mouseenter' : 'pointerdown', 1.92 + 'touchmove' : 'pointermove', 1.93 + 'mousemove' : 'pointermove', 1.94 + 'touchend' : 'pointerup', 1.95 + 'mouseup': 'pointerup', 1.96 + 'mouseleave': 'pointerup' 1.97 + }, 1.98 + 1.99 + start: function PointerRelay_start(aOnPointerEvent) { 1.100 + Logger.debug('PointerRelay.start'); 1.101 + this.onPointerEvent = aOnPointerEvent; 1.102 + for (let eventType in this._eventsOfInterest) { 1.103 + Utils.win.addEventListener(eventType, this, true, true); 1.104 + } 1.105 + }, 1.106 + 1.107 + stop: function PointerRelay_stop() { 1.108 + Logger.debug('PointerRelay.stop'); 1.109 + delete this.lastPointerMove; 1.110 + delete this.onPointerEvent; 1.111 + for (let eventType in this._eventsOfInterest) { 1.112 + Utils.win.removeEventListener(eventType, this, true, true); 1.113 + } 1.114 + }, 1.115 + 1.116 + _suppressPointerMove: function PointerRelay__suppressPointerMove(aChangedTouches) { 1.117 + if (!this.lastPointerMove) { 1.118 + return false; 1.119 + } 1.120 + for (let i = 0; i < aChangedTouches.length; ++i) { 1.121 + let touch = aChangedTouches[i]; 1.122 + let lastTouch; 1.123 + try { 1.124 + lastTouch = this.lastPointerMove.identifiedTouch ? 1.125 + this.lastPointerMove.identifiedTouch(touch.identifier) : 1.126 + this.lastPointerMove[i]; 1.127 + } catch (x) { 1.128 + // Sometimes touch object can't be accessed after page navigation. 1.129 + } 1.130 + if (!lastTouch || lastTouch.target !== touch.target || 1.131 + Math.hypot(touch.screenX - lastTouch.screenX, touch.screenY - 1.132 + lastTouch.screenY) / Utils.dpi >= GestureSettings.travelThreshold) { 1.133 + return false; 1.134 + } 1.135 + } 1.136 + return true; 1.137 + }, 1.138 + 1.139 + handleEvent: function PointerRelay_handleEvent(aEvent) { 1.140 + // Don't bother with chrome mouse events. 1.141 + if (Utils.MozBuildApp === 'browser' && 1.142 + aEvent.view.top instanceof Ci.nsIDOMChromeWindow) { 1.143 + return; 1.144 + } 1.145 + if (aEvent.mozInputSource === Ci.nsIDOMMouseEvent.MOZ_SOURCE_UNKNOWN) { 1.146 + // Ignore events that are scripted or clicks from the a11y API. 1.147 + return; 1.148 + } 1.149 + 1.150 + let changedTouches = aEvent.changedTouches || [{ 1.151 + identifier: MOUSE_ID, 1.152 + screenX: aEvent.screenX, 1.153 + screenY: aEvent.screenY, 1.154 + target: aEvent.target 1.155 + }]; 1.156 + 1.157 + if (changedTouches.length === 1 && 1.158 + changedTouches[0].identifier === SYNTH_ID) { 1.159 + return; 1.160 + } 1.161 + 1.162 + aEvent.preventDefault(); 1.163 + aEvent.stopImmediatePropagation(); 1.164 + 1.165 + let type = aEvent.type; 1.166 + if (!this._eventsOfInterest[type]) { 1.167 + return; 1.168 + } 1.169 + let pointerType = this._eventMap[type]; 1.170 + if (pointerType === 'pointermove') { 1.171 + if (this._suppressPointerMove(changedTouches)) { 1.172 + // Do not fire pointermove more than every POINTERMOVE_THROTTLE. 1.173 + return; 1.174 + } 1.175 + this.lastPointerMove = changedTouches; 1.176 + } 1.177 + this.onPointerEvent({ 1.178 + type: pointerType, 1.179 + points: Array.prototype.map.call(changedTouches, 1.180 + function mapTouch(aTouch) { 1.181 + return { 1.182 + identifier: aTouch.identifier, 1.183 + x: aTouch.screenX, 1.184 + y: aTouch.screenY 1.185 + }; 1.186 + } 1.187 + ) 1.188 + }); 1.189 + } 1.190 +}; 1.191 + 1.192 +this.PointerAdapter = { // jshint ignore:line 1.193 + start: function PointerAdapter_start() { 1.194 + Logger.debug('PointerAdapter.start'); 1.195 + GestureTracker.reset(); 1.196 + PointerRelay.start(this.handleEvent); 1.197 + }, 1.198 + 1.199 + stop: function PointerAdapter_stop() { 1.200 + Logger.debug('PointerAdapter.stop'); 1.201 + PointerRelay.stop(); 1.202 + GestureTracker.reset(); 1.203 + }, 1.204 + 1.205 + handleEvent: function PointerAdapter_handleEvent(aDetail) { 1.206 + let timeStamp = Date.now(); 1.207 + GestureTracker.handle(aDetail, timeStamp); 1.208 + } 1.209 +};