mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-08 12:37:37 +00:00
240 lines
8.1 KiB
JavaScript
240 lines
8.1 KiB
JavaScript
/* ***** 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;
|
|
evt.target.setCapture(false);
|
|
|
|
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);
|
|
eventTarget.ownerDocument.releaseCapture();
|
|
this.target = null;
|
|
type = 'touchend';
|
|
break;
|
|
|
|
case 'unload':
|
|
if (!eventTarget)
|
|
return;
|
|
|
|
window.clearTimeout(contextMenuTimeout);
|
|
eventTarget.ownerDocument.releaseCapture();
|
|
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();
|
|
});
|
|
})();
|
|
|