1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/devtools/touch-events.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,233 @@ 1.4 +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +let {CC, Cc, Ci, Cu, Cr} = require('chrome'); 1.10 + 1.11 +Cu.import('resource://gre/modules/Services.jsm'); 1.12 + 1.13 +let handlerCount = 0; 1.14 + 1.15 +let orig_w3c_touch_events = Services.prefs.getIntPref('dom.w3c_touch_events.enabled'); 1.16 + 1.17 +let trackedWindows = new WeakMap(); 1.18 + 1.19 +// =================== Touch ==================== 1.20 +// Simulate touch events on desktop 1.21 +function TouchEventHandler (window) { 1.22 + // Returns an already instanciated handler for this window 1.23 + let cached = trackedWindows.get(window); 1.24 + if (cached) { 1.25 + return cached; 1.26 + } 1.27 + 1.28 + let contextMenuTimeout = 0; 1.29 + 1.30 + 1.31 + let threshold = 25; 1.32 + try { 1.33 + threshold = Services.prefs.getIntPref('ui.dragThresholdX'); 1.34 + } catch(e) {} 1.35 + 1.36 + let delay = 500; 1.37 + try { 1.38 + delay = Services.prefs.getIntPref('ui.click_hold_context_menus.delay'); 1.39 + } catch(e) {} 1.40 + 1.41 + let TouchEventHandler = { 1.42 + enabled: false, 1.43 + events: ['mousedown', 'mousemove', 'mouseup'], 1.44 + start: function teh_start() { 1.45 + if (this.enabled) 1.46 + return false; 1.47 + this.enabled = true; 1.48 + let isReloadNeeded = Services.prefs.getIntPref('dom.w3c_touch_events.enabled') != 1; 1.49 + Services.prefs.setIntPref('dom.w3c_touch_events.enabled', 1); 1.50 + this.events.forEach((function(evt) { 1.51 + // Only listen trusted events to prevent messing with 1.52 + // event dispatched manually within content documents 1.53 + window.addEventListener(evt, this, true, false); 1.54 + }).bind(this)); 1.55 + return isReloadNeeded; 1.56 + }, 1.57 + stop: function teh_stop() { 1.58 + if (!this.enabled) 1.59 + return; 1.60 + this.enabled = false; 1.61 + Services.prefs.setIntPref('dom.w3c_touch_events.enabled', orig_w3c_touch_events); 1.62 + this.events.forEach((function(evt) { 1.63 + window.removeEventListener(evt, this, true); 1.64 + }).bind(this)); 1.65 + }, 1.66 + handleEvent: function teh_handleEvent(evt) { 1.67 + // Ignore all but real mouse event coming from physical mouse 1.68 + // (especially ignore mouse event being dispatched from a touch event) 1.69 + if (evt.button || evt.mozInputSource != Ci.nsIDOMMouseEvent.MOZ_SOURCE_MOUSE || evt.isSynthesized) { 1.70 + return; 1.71 + } 1.72 + 1.73 + // The gaia system window use an hybrid system even on the device which is 1.74 + // a mix of mouse/touch events. So let's not cancel *all* mouse events 1.75 + // if it is the current target. 1.76 + let content = this.getContent(evt.target); 1.77 + let isSystemWindow = content.location.toString().indexOf("system.gaiamobile.org") != -1; 1.78 + 1.79 + let eventTarget = this.target; 1.80 + let type = ''; 1.81 + switch (evt.type) { 1.82 + case 'mousedown': 1.83 + this.target = evt.target; 1.84 + 1.85 + contextMenuTimeout = 1.86 + this.sendContextMenu(evt.target, evt.pageX, evt.pageY, delay); 1.87 + 1.88 + this.cancelClick = false; 1.89 + this.startX = evt.pageX; 1.90 + this.startY = evt.pageY; 1.91 + 1.92 + // Capture events so if a different window show up the events 1.93 + // won't be dispatched to something else. 1.94 + evt.target.setCapture(false); 1.95 + 1.96 + type = 'touchstart'; 1.97 + break; 1.98 + 1.99 + case 'mousemove': 1.100 + if (!eventTarget) 1.101 + return; 1.102 + 1.103 + if (!this.cancelClick) { 1.104 + if (Math.abs(this.startX - evt.pageX) > threshold || 1.105 + Math.abs(this.startY - evt.pageY) > threshold) { 1.106 + this.cancelClick = true; 1.107 + content.clearTimeout(contextMenuTimeout); 1.108 + } 1.109 + } 1.110 + 1.111 + type = 'touchmove'; 1.112 + break; 1.113 + 1.114 + case 'mouseup': 1.115 + if (!eventTarget) 1.116 + return; 1.117 + this.target = null; 1.118 + 1.119 + content.clearTimeout(contextMenuTimeout); 1.120 + type = 'touchend'; 1.121 + 1.122 + // Only register click listener after mouseup to ensure 1.123 + // catching only real user click. (Especially ignore click 1.124 + // being dispatched on form submit) 1.125 + if (evt.detail == 1) { 1.126 + window.addEventListener('click', this, true, false); 1.127 + } 1.128 + break; 1.129 + 1.130 + case 'click': 1.131 + // Mouse events has been cancelled so dispatch a sequence 1.132 + // of events to where touchend has been fired 1.133 + evt.preventDefault(); 1.134 + evt.stopImmediatePropagation(); 1.135 + 1.136 + window.removeEventListener('click', this, true, false); 1.137 + 1.138 + if (this.cancelClick) 1.139 + return; 1.140 + 1.141 + ignoreEvents = true; 1.142 + content.setTimeout(function dispatchMouseEvents(self) { 1.143 + try { 1.144 + self.fireMouseEvent('mousedown', evt); 1.145 + self.fireMouseEvent('mousemove', evt); 1.146 + self.fireMouseEvent('mouseup', evt); 1.147 + } catch(e) { 1.148 + Cu.reportError('Exception in touch event helper: ' + e); 1.149 + } 1.150 + ignoreEvents = false; 1.151 + }, 0, this); 1.152 + 1.153 + return; 1.154 + } 1.155 + 1.156 + let target = eventTarget || this.target; 1.157 + if (target && type) { 1.158 + this.sendTouchEvent(evt, target, type); 1.159 + } 1.160 + 1.161 + if (!isSystemWindow) { 1.162 + evt.preventDefault(); 1.163 + evt.stopImmediatePropagation(); 1.164 + } 1.165 + }, 1.166 + fireMouseEvent: function teh_fireMouseEvent(type, evt) { 1.167 + let content = this.getContent(evt.target); 1.168 + var utils = content.QueryInterface(Ci.nsIInterfaceRequestor) 1.169 + .getInterface(Ci.nsIDOMWindowUtils); 1.170 + utils.sendMouseEvent(type, evt.clientX, evt.clientY, 0, 1, 0, true, 0, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH); 1.171 + }, 1.172 + sendContextMenu: function teh_sendContextMenu(target, x, y, delay) { 1.173 + let doc = target.ownerDocument; 1.174 + let evt = doc.createEvent('MouseEvent'); 1.175 + evt.initMouseEvent('contextmenu', true, true, doc.defaultView, 1.176 + 0, x, y, x, y, false, false, false, false, 1.177 + 0, null); 1.178 + 1.179 + let content = this.getContent(target); 1.180 + let timeout = content.setTimeout((function contextMenu() { 1.181 + target.dispatchEvent(evt); 1.182 + this.cancelClick = true; 1.183 + }).bind(this), delay); 1.184 + 1.185 + return timeout; 1.186 + }, 1.187 + sendTouchEvent: function teh_sendTouchEvent(evt, target, name) { 1.188 + // When running OOP b2g desktop, we need to send the touch events 1.189 + // using the mozbrowser api on the unwrapped frame. 1.190 + if (target.localName == "iframe" && target.mozbrowser === true) { 1.191 + if (name == "touchstart") { 1.192 + this.touchstartTime = Date.now(); 1.193 + } else if (name == "touchend") { 1.194 + // If we have a 'fast' tap, don't send a click as both will be turned 1.195 + // into a click and that breaks eg. checkboxes. 1.196 + if (Date.now() - this.touchstartTime < delay) { 1.197 + this.cancelClick = true; 1.198 + } 1.199 + } 1.200 + let unwraped = XPCNativeWrapper.unwrap(target); 1.201 + unwraped.sendTouchEvent(name, [0], // event type, id 1.202 + [evt.clientX], [evt.clientY], // x, y 1.203 + [1], [1], // rx, ry 1.204 + [0], [0], // rotation, force 1.205 + 1); // count 1.206 + return; 1.207 + } 1.208 + let document = target.ownerDocument; 1.209 + let content = this.getContent(target); 1.210 + 1.211 + let touchEvent = document.createEvent('touchevent'); 1.212 + let point = document.createTouch(content, target, 0, 1.213 + evt.pageX, evt.pageY, 1.214 + evt.screenX, evt.screenY, 1.215 + evt.clientX, evt.clientY, 1.216 + 1, 1, 0, 0); 1.217 + let touches = document.createTouchList(point); 1.218 + let targetTouches = touches; 1.219 + let changedTouches = touches; 1.220 + touchEvent.initTouchEvent(name, true, true, content, 0, 1.221 + false, false, false, false, 1.222 + touches, targetTouches, changedTouches); 1.223 + target.dispatchEvent(touchEvent); 1.224 + return touchEvent; 1.225 + }, 1.226 + getContent: function teh_getContent(target) { 1.227 + let win = target.ownerDocument.defaultView; 1.228 + return win; 1.229 + } 1.230 + }; 1.231 + trackedWindows.set(window, TouchEventHandler); 1.232 + 1.233 + return TouchEventHandler; 1.234 +} 1.235 + 1.236 +exports.TouchEventHandler = TouchEventHandler;