toolkit/devtools/touch-events.js

branch
TOR_BUG_3246
changeset 6
8bccb770b82d
equal deleted inserted replaced
-1:000000000000 0:46f954b78ff0
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;

mercurial