|
1 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 let {CC, Cc, Ci, Cu, Cr} = require('chrome'); |
|
7 |
|
8 Cu.import('resource://gre/modules/Services.jsm'); |
|
9 |
|
10 let handlerCount = 0; |
|
11 |
|
12 let orig_w3c_touch_events = Services.prefs.getIntPref('dom.w3c_touch_events.enabled'); |
|
13 |
|
14 let trackedWindows = new WeakMap(); |
|
15 |
|
16 // =================== Touch ==================== |
|
17 // Simulate touch events on desktop |
|
18 function TouchEventHandler (window) { |
|
19 // Returns an already instanciated handler for this window |
|
20 let cached = trackedWindows.get(window); |
|
21 if (cached) { |
|
22 return cached; |
|
23 } |
|
24 |
|
25 let contextMenuTimeout = 0; |
|
26 |
|
27 |
|
28 let threshold = 25; |
|
29 try { |
|
30 threshold = Services.prefs.getIntPref('ui.dragThresholdX'); |
|
31 } catch(e) {} |
|
32 |
|
33 let delay = 500; |
|
34 try { |
|
35 delay = Services.prefs.getIntPref('ui.click_hold_context_menus.delay'); |
|
36 } catch(e) {} |
|
37 |
|
38 let TouchEventHandler = { |
|
39 enabled: false, |
|
40 events: ['mousedown', 'mousemove', 'mouseup'], |
|
41 start: function teh_start() { |
|
42 if (this.enabled) |
|
43 return false; |
|
44 this.enabled = true; |
|
45 let isReloadNeeded = Services.prefs.getIntPref('dom.w3c_touch_events.enabled') != 1; |
|
46 Services.prefs.setIntPref('dom.w3c_touch_events.enabled', 1); |
|
47 this.events.forEach((function(evt) { |
|
48 // Only listen trusted events to prevent messing with |
|
49 // event dispatched manually within content documents |
|
50 window.addEventListener(evt, this, true, false); |
|
51 }).bind(this)); |
|
52 return isReloadNeeded; |
|
53 }, |
|
54 stop: function teh_stop() { |
|
55 if (!this.enabled) |
|
56 return; |
|
57 this.enabled = false; |
|
58 Services.prefs.setIntPref('dom.w3c_touch_events.enabled', orig_w3c_touch_events); |
|
59 this.events.forEach((function(evt) { |
|
60 window.removeEventListener(evt, this, true); |
|
61 }).bind(this)); |
|
62 }, |
|
63 handleEvent: function teh_handleEvent(evt) { |
|
64 // Ignore all but real mouse event coming from physical mouse |
|
65 // (especially ignore mouse event being dispatched from a touch event) |
|
66 if (evt.button || evt.mozInputSource != Ci.nsIDOMMouseEvent.MOZ_SOURCE_MOUSE || evt.isSynthesized) { |
|
67 return; |
|
68 } |
|
69 |
|
70 // The gaia system window use an hybrid system even on the device which is |
|
71 // a mix of mouse/touch events. So let's not cancel *all* mouse events |
|
72 // if it is the current target. |
|
73 let content = this.getContent(evt.target); |
|
74 let isSystemWindow = content.location.toString().indexOf("system.gaiamobile.org") != -1; |
|
75 |
|
76 let eventTarget = this.target; |
|
77 let type = ''; |
|
78 switch (evt.type) { |
|
79 case 'mousedown': |
|
80 this.target = evt.target; |
|
81 |
|
82 contextMenuTimeout = |
|
83 this.sendContextMenu(evt.target, evt.pageX, evt.pageY, delay); |
|
84 |
|
85 this.cancelClick = false; |
|
86 this.startX = evt.pageX; |
|
87 this.startY = evt.pageY; |
|
88 |
|
89 // Capture events so if a different window show up the events |
|
90 // won't be dispatched to something else. |
|
91 evt.target.setCapture(false); |
|
92 |
|
93 type = 'touchstart'; |
|
94 break; |
|
95 |
|
96 case 'mousemove': |
|
97 if (!eventTarget) |
|
98 return; |
|
99 |
|
100 if (!this.cancelClick) { |
|
101 if (Math.abs(this.startX - evt.pageX) > threshold || |
|
102 Math.abs(this.startY - evt.pageY) > threshold) { |
|
103 this.cancelClick = true; |
|
104 content.clearTimeout(contextMenuTimeout); |
|
105 } |
|
106 } |
|
107 |
|
108 type = 'touchmove'; |
|
109 break; |
|
110 |
|
111 case 'mouseup': |
|
112 if (!eventTarget) |
|
113 return; |
|
114 this.target = null; |
|
115 |
|
116 content.clearTimeout(contextMenuTimeout); |
|
117 type = 'touchend'; |
|
118 |
|
119 // Only register click listener after mouseup to ensure |
|
120 // catching only real user click. (Especially ignore click |
|
121 // being dispatched on form submit) |
|
122 if (evt.detail == 1) { |
|
123 window.addEventListener('click', this, true, false); |
|
124 } |
|
125 break; |
|
126 |
|
127 case 'click': |
|
128 // Mouse events has been cancelled so dispatch a sequence |
|
129 // of events to where touchend has been fired |
|
130 evt.preventDefault(); |
|
131 evt.stopImmediatePropagation(); |
|
132 |
|
133 window.removeEventListener('click', this, true, false); |
|
134 |
|
135 if (this.cancelClick) |
|
136 return; |
|
137 |
|
138 ignoreEvents = true; |
|
139 content.setTimeout(function dispatchMouseEvents(self) { |
|
140 try { |
|
141 self.fireMouseEvent('mousedown', evt); |
|
142 self.fireMouseEvent('mousemove', evt); |
|
143 self.fireMouseEvent('mouseup', evt); |
|
144 } catch(e) { |
|
145 Cu.reportError('Exception in touch event helper: ' + e); |
|
146 } |
|
147 ignoreEvents = false; |
|
148 }, 0, this); |
|
149 |
|
150 return; |
|
151 } |
|
152 |
|
153 let target = eventTarget || this.target; |
|
154 if (target && type) { |
|
155 this.sendTouchEvent(evt, target, type); |
|
156 } |
|
157 |
|
158 if (!isSystemWindow) { |
|
159 evt.preventDefault(); |
|
160 evt.stopImmediatePropagation(); |
|
161 } |
|
162 }, |
|
163 fireMouseEvent: function teh_fireMouseEvent(type, evt) { |
|
164 let content = this.getContent(evt.target); |
|
165 var utils = content.QueryInterface(Ci.nsIInterfaceRequestor) |
|
166 .getInterface(Ci.nsIDOMWindowUtils); |
|
167 utils.sendMouseEvent(type, evt.clientX, evt.clientY, 0, 1, 0, true, 0, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH); |
|
168 }, |
|
169 sendContextMenu: function teh_sendContextMenu(target, x, y, delay) { |
|
170 let doc = target.ownerDocument; |
|
171 let evt = doc.createEvent('MouseEvent'); |
|
172 evt.initMouseEvent('contextmenu', true, true, doc.defaultView, |
|
173 0, x, y, x, y, false, false, false, false, |
|
174 0, null); |
|
175 |
|
176 let content = this.getContent(target); |
|
177 let timeout = content.setTimeout((function contextMenu() { |
|
178 target.dispatchEvent(evt); |
|
179 this.cancelClick = true; |
|
180 }).bind(this), delay); |
|
181 |
|
182 return timeout; |
|
183 }, |
|
184 sendTouchEvent: function teh_sendTouchEvent(evt, target, name) { |
|
185 // When running OOP b2g desktop, we need to send the touch events |
|
186 // using the mozbrowser api on the unwrapped frame. |
|
187 if (target.localName == "iframe" && target.mozbrowser === true) { |
|
188 if (name == "touchstart") { |
|
189 this.touchstartTime = Date.now(); |
|
190 } else if (name == "touchend") { |
|
191 // If we have a 'fast' tap, don't send a click as both will be turned |
|
192 // into a click and that breaks eg. checkboxes. |
|
193 if (Date.now() - this.touchstartTime < delay) { |
|
194 this.cancelClick = true; |
|
195 } |
|
196 } |
|
197 let unwraped = XPCNativeWrapper.unwrap(target); |
|
198 unwraped.sendTouchEvent(name, [0], // event type, id |
|
199 [evt.clientX], [evt.clientY], // x, y |
|
200 [1], [1], // rx, ry |
|
201 [0], [0], // rotation, force |
|
202 1); // count |
|
203 return; |
|
204 } |
|
205 let document = target.ownerDocument; |
|
206 let content = this.getContent(target); |
|
207 |
|
208 let touchEvent = document.createEvent('touchevent'); |
|
209 let point = document.createTouch(content, target, 0, |
|
210 evt.pageX, evt.pageY, |
|
211 evt.screenX, evt.screenY, |
|
212 evt.clientX, evt.clientY, |
|
213 1, 1, 0, 0); |
|
214 let touches = document.createTouchList(point); |
|
215 let targetTouches = touches; |
|
216 let changedTouches = touches; |
|
217 touchEvent.initTouchEvent(name, true, true, content, 0, |
|
218 false, false, false, false, |
|
219 touches, targetTouches, changedTouches); |
|
220 target.dispatchEvent(touchEvent); |
|
221 return touchEvent; |
|
222 }, |
|
223 getContent: function teh_getContent(target) { |
|
224 let win = target.ownerDocument.defaultView; |
|
225 return win; |
|
226 } |
|
227 }; |
|
228 trackedWindows.set(window, TouchEventHandler); |
|
229 |
|
230 return TouchEventHandler; |
|
231 } |
|
232 |
|
233 exports.TouchEventHandler = TouchEventHandler; |