|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 /* global Components, XPCOMUtils, Utils, Logger, GestureSettings, |
|
6 GestureTracker */ |
|
7 /* exported PointerRelay, PointerAdapter */ |
|
8 |
|
9 'use strict'; |
|
10 |
|
11 const Ci = Components.interfaces; |
|
12 const Cu = Components.utils; |
|
13 |
|
14 this.EXPORTED_SYMBOLS = ['PointerRelay', 'PointerAdapter']; // jshint ignore:line |
|
15 |
|
16 Cu.import('resource://gre/modules/XPCOMUtils.jsm'); |
|
17 |
|
18 XPCOMUtils.defineLazyModuleGetter(this, 'Utils', // jshint ignore:line |
|
19 'resource://gre/modules/accessibility/Utils.jsm'); |
|
20 XPCOMUtils.defineLazyModuleGetter(this, 'Logger', // jshint ignore:line |
|
21 'resource://gre/modules/accessibility/Utils.jsm'); |
|
22 XPCOMUtils.defineLazyModuleGetter(this, 'GestureSettings', // jshint ignore:line |
|
23 'resource://gre/modules/accessibility/Gestures.jsm'); |
|
24 XPCOMUtils.defineLazyModuleGetter(this, 'GestureTracker', // jshint ignore:line |
|
25 'resource://gre/modules/accessibility/Gestures.jsm'); |
|
26 |
|
27 // The virtual touch ID generated by a mouse event. |
|
28 const MOUSE_ID = 'mouse'; |
|
29 // Synthesized touch ID. |
|
30 const SYNTH_ID = -1; |
|
31 |
|
32 let PointerRelay = { // jshint ignore:line |
|
33 /** |
|
34 * A mapping of events we should be intercepting. Entries with a value of |
|
35 * |true| are used for compiling high-level gesture events. Entries with a |
|
36 * value of |false| are cancelled and do not propogate to content. |
|
37 */ |
|
38 get _eventsOfInterest() { |
|
39 delete this._eventsOfInterest; |
|
40 |
|
41 switch (Utils.widgetToolkit) { |
|
42 case 'gonk': |
|
43 this._eventsOfInterest = { |
|
44 'touchstart' : true, |
|
45 'touchmove' : true, |
|
46 'touchend' : true, |
|
47 'mousedown' : false, |
|
48 'mousemove' : false, |
|
49 'mouseup': false, |
|
50 'click': false }; |
|
51 break; |
|
52 |
|
53 case 'android': |
|
54 this._eventsOfInterest = { |
|
55 'touchstart' : true, |
|
56 'touchmove' : true, |
|
57 'touchend' : true, |
|
58 'mousemove' : true, |
|
59 'mouseenter' : true, |
|
60 'mouseleave' : true, |
|
61 'mousedown' : false, |
|
62 'mouseup': false, |
|
63 'click': false }; |
|
64 break; |
|
65 |
|
66 default: |
|
67 // Desktop. |
|
68 this._eventsOfInterest = { |
|
69 'mousemove' : true, |
|
70 'mousedown' : true, |
|
71 'mouseup': true, |
|
72 'click': false |
|
73 }; |
|
74 if ('ontouchstart' in Utils.win) { |
|
75 for (let eventType of ['touchstart', 'touchmove', 'touchend']) { |
|
76 this._eventsOfInterest[eventType] = true; |
|
77 } |
|
78 } |
|
79 break; |
|
80 } |
|
81 |
|
82 return this._eventsOfInterest; |
|
83 }, |
|
84 |
|
85 _eventMap: { |
|
86 'touchstart' : 'pointerdown', |
|
87 'mousedown' : 'pointerdown', |
|
88 'mouseenter' : 'pointerdown', |
|
89 'touchmove' : 'pointermove', |
|
90 'mousemove' : 'pointermove', |
|
91 'touchend' : 'pointerup', |
|
92 'mouseup': 'pointerup', |
|
93 'mouseleave': 'pointerup' |
|
94 }, |
|
95 |
|
96 start: function PointerRelay_start(aOnPointerEvent) { |
|
97 Logger.debug('PointerRelay.start'); |
|
98 this.onPointerEvent = aOnPointerEvent; |
|
99 for (let eventType in this._eventsOfInterest) { |
|
100 Utils.win.addEventListener(eventType, this, true, true); |
|
101 } |
|
102 }, |
|
103 |
|
104 stop: function PointerRelay_stop() { |
|
105 Logger.debug('PointerRelay.stop'); |
|
106 delete this.lastPointerMove; |
|
107 delete this.onPointerEvent; |
|
108 for (let eventType in this._eventsOfInterest) { |
|
109 Utils.win.removeEventListener(eventType, this, true, true); |
|
110 } |
|
111 }, |
|
112 |
|
113 _suppressPointerMove: function PointerRelay__suppressPointerMove(aChangedTouches) { |
|
114 if (!this.lastPointerMove) { |
|
115 return false; |
|
116 } |
|
117 for (let i = 0; i < aChangedTouches.length; ++i) { |
|
118 let touch = aChangedTouches[i]; |
|
119 let lastTouch; |
|
120 try { |
|
121 lastTouch = this.lastPointerMove.identifiedTouch ? |
|
122 this.lastPointerMove.identifiedTouch(touch.identifier) : |
|
123 this.lastPointerMove[i]; |
|
124 } catch (x) { |
|
125 // Sometimes touch object can't be accessed after page navigation. |
|
126 } |
|
127 if (!lastTouch || lastTouch.target !== touch.target || |
|
128 Math.hypot(touch.screenX - lastTouch.screenX, touch.screenY - |
|
129 lastTouch.screenY) / Utils.dpi >= GestureSettings.travelThreshold) { |
|
130 return false; |
|
131 } |
|
132 } |
|
133 return true; |
|
134 }, |
|
135 |
|
136 handleEvent: function PointerRelay_handleEvent(aEvent) { |
|
137 // Don't bother with chrome mouse events. |
|
138 if (Utils.MozBuildApp === 'browser' && |
|
139 aEvent.view.top instanceof Ci.nsIDOMChromeWindow) { |
|
140 return; |
|
141 } |
|
142 if (aEvent.mozInputSource === Ci.nsIDOMMouseEvent.MOZ_SOURCE_UNKNOWN) { |
|
143 // Ignore events that are scripted or clicks from the a11y API. |
|
144 return; |
|
145 } |
|
146 |
|
147 let changedTouches = aEvent.changedTouches || [{ |
|
148 identifier: MOUSE_ID, |
|
149 screenX: aEvent.screenX, |
|
150 screenY: aEvent.screenY, |
|
151 target: aEvent.target |
|
152 }]; |
|
153 |
|
154 if (changedTouches.length === 1 && |
|
155 changedTouches[0].identifier === SYNTH_ID) { |
|
156 return; |
|
157 } |
|
158 |
|
159 aEvent.preventDefault(); |
|
160 aEvent.stopImmediatePropagation(); |
|
161 |
|
162 let type = aEvent.type; |
|
163 if (!this._eventsOfInterest[type]) { |
|
164 return; |
|
165 } |
|
166 let pointerType = this._eventMap[type]; |
|
167 if (pointerType === 'pointermove') { |
|
168 if (this._suppressPointerMove(changedTouches)) { |
|
169 // Do not fire pointermove more than every POINTERMOVE_THROTTLE. |
|
170 return; |
|
171 } |
|
172 this.lastPointerMove = changedTouches; |
|
173 } |
|
174 this.onPointerEvent({ |
|
175 type: pointerType, |
|
176 points: Array.prototype.map.call(changedTouches, |
|
177 function mapTouch(aTouch) { |
|
178 return { |
|
179 identifier: aTouch.identifier, |
|
180 x: aTouch.screenX, |
|
181 y: aTouch.screenY |
|
182 }; |
|
183 } |
|
184 ) |
|
185 }); |
|
186 } |
|
187 }; |
|
188 |
|
189 this.PointerAdapter = { // jshint ignore:line |
|
190 start: function PointerAdapter_start() { |
|
191 Logger.debug('PointerAdapter.start'); |
|
192 GestureTracker.reset(); |
|
193 PointerRelay.start(this.handleEvent); |
|
194 }, |
|
195 |
|
196 stop: function PointerAdapter_stop() { |
|
197 Logger.debug('PointerAdapter.stop'); |
|
198 PointerRelay.stop(); |
|
199 GestureTracker.reset(); |
|
200 }, |
|
201 |
|
202 handleEvent: function PointerAdapter_handleEvent(aDetail) { |
|
203 let timeStamp = Date.now(); |
|
204 GestureTracker.handle(aDetail, timeStamp); |
|
205 } |
|
206 }; |