/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is B2G. * * The Initial Developer of the Original Code is * the Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ (function touchEventHandler() { let debugging = false; function debug(str) { if (debugging) dump(str + '\n'); }; let contextMenuTimeout = 0; // This guard is used to not re-enter the events processing loop for // self dispatched events let ignoreEvents = false; // During a 'touchstart' and the first 'touchmove' mouse events can be // prevented for the current touch sequence. let canPreventMouseEvents = false; // Used to track the first mousemove and to cancel click dispatc if it's not // true. let isNewTouchAction = false; // If this is set to true all mouse events will be cancelled by calling // both evt.preventDefault() and evt.stopPropagation(). // This will not prevent a contextmenu event to be fired. // This can be turned on if canPreventMouseEvents is true and the consumer // application call evt.preventDefault(); let preventMouseEvents = false; let TouchEventHandler = { events: ['mousedown', 'mousemove', 'mouseup', 'click', 'unload'], start: function teh_start() { this.events.forEach((function(evt) { shell.home.addEventListener(evt, this, true); }).bind(this)); }, stop: function teh_stop() { this.events.forEach((function(evt) { shell.home.removeEventListener(evt, this, true); }).bind(this)); }, handleEvent: function teh_handleEvent(evt) { if (evt.button || ignoreEvents) return; let eventTarget = this.target; let type = ''; switch (evt.type) { case 'mousedown': debug('mousedown:'); this.target = evt.target; this.timestamp = evt.timeStamp; preventMouseEvents = false; canPreventMouseEvents = true; isNewTouchAction = true; contextMenuTimeout = this.sendContextMenu(evt.target, evt.pageX, evt.pageY, 2000); this.startX = evt.pageX; this.startY = evt.pageY; type = 'touchstart'; break; case 'mousemove': if (!eventTarget) return; // On device a mousemove event if fired right after the mousedown // because of the size of the finger, so let's ignore what happens // below 5ms if (evt.timeStamp - this.timestamp < 30) break; if (isNewTouchAction) { canPreventMouseEvents = true; isNewTouchAction = false; } if (Math.abs(this.startX - evt.pageX) > 15 || Math.abs(this.startY - evt.pageY) > 15) window.clearTimeout(contextMenuTimeout); type = 'touchmove'; break; case 'mouseup': if (!eventTarget) return; debug('mouseup:'); window.clearTimeout(contextMenuTimeout); this.target = null; type = 'touchend'; break; case 'unload': if (!eventTarget) return; window.clearTimeout(contextMenuTimeout); this.target = null; TouchEventHandler.stop(); return; case 'click': if (!isNewTouchAction) { debug('click: cancel'); evt.preventDefault(); evt.stopPropagation(); } else { // Mouse events has been cancelled so dispatch a sequence // of events to where touchend has been fired if (preventMouseEvents) { evt.preventDefault(); evt.stopPropagation(); let target = evt.target; ignoreEvents = true; window.setTimeout(function dispatchMouseEvents(self) { self.fireMouseEvent('mousemove', evt); self.fireMouseEvent('mousedown', evt); self.fireMouseEvent('mouseup', evt); ignoreEvents = false; }, 0, this); } debug('click: fire'); } return; } let target = eventTarget || this.target; if (target && type) { let touchEvent = this.sendTouchEvent(evt, target, type); if (touchEvent.defaultPrevented && canPreventMouseEvents) preventMouseEvents = true; } if (preventMouseEvents) { evt.preventDefault(); evt.stopPropagation(); if (type != 'touchmove') debug('cancelled (fire ' + type + ')'); } }, fireMouseEvent: function teh_fireMouseEvent(type, evt) { debug(type + ': fire'); let content = evt.target.ownerDocument.defaultView; var utils = content.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); utils.sendMouseEvent(type, evt.pageX, evt.pageY, 0, 1, 0, true); }, sendContextMenu: function teh_sendContextMenu(target, x, y, delay) { let doc = target.ownerDocument; let evt = doc.createEvent('MouseEvent'); evt.initMouseEvent('contextmenu', true, true, doc.defaultView, 0, x, y, x, y, false, false, false, false, 0, null); let timeout = window.setTimeout((function contextMenu() { debug('fire context-menu'); target.dispatchEvent(evt); if (!evt.defaultPrevented) return; doc.releaseCapture(); this.target = null; isNewTouchAction = false; }).bind(this), delay); return timeout; }, sendTouchEvent: function teh_sendTouchEvent(evt, target, name) { let touchEvent = document.createEvent('touchevent'); let point = document.createTouch(window, target, 0, evt.pageX, evt.pageY, evt.screenX, evt.screenY, evt.clientX, evt.clientY, 1, 1, 0, 0); let touches = document.createTouchList(point); let targetTouches = touches; let changedTouches = touches; touchEvent.initTouchEvent(name, true, true, window, 0, false, false, false, false, touches, targetTouches, changedTouches); target.dispatchEvent(touchEvent); return touchEvent; } }; window.addEventListener('ContentStart', function touchStart(evt) { window.removeEventListener('ContentStart', touchStart); TouchEventHandler.start(); }); })();