michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* global Components, XPCOMUtils, Utils, Logger, GestureSettings, michael@0: GestureTracker */ michael@0: /* exported PointerRelay, PointerAdapter */ michael@0: michael@0: 'use strict'; michael@0: michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: michael@0: this.EXPORTED_SYMBOLS = ['PointerRelay', 'PointerAdapter']; // jshint ignore:line michael@0: michael@0: Cu.import('resource://gre/modules/XPCOMUtils.jsm'); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'Utils', // jshint ignore:line michael@0: 'resource://gre/modules/accessibility/Utils.jsm'); michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'Logger', // jshint ignore:line michael@0: 'resource://gre/modules/accessibility/Utils.jsm'); michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'GestureSettings', // jshint ignore:line michael@0: 'resource://gre/modules/accessibility/Gestures.jsm'); michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'GestureTracker', // jshint ignore:line michael@0: 'resource://gre/modules/accessibility/Gestures.jsm'); michael@0: michael@0: // The virtual touch ID generated by a mouse event. michael@0: const MOUSE_ID = 'mouse'; michael@0: // Synthesized touch ID. michael@0: const SYNTH_ID = -1; michael@0: michael@0: let PointerRelay = { // jshint ignore:line michael@0: /** michael@0: * A mapping of events we should be intercepting. Entries with a value of michael@0: * |true| are used for compiling high-level gesture events. Entries with a michael@0: * value of |false| are cancelled and do not propogate to content. michael@0: */ michael@0: get _eventsOfInterest() { michael@0: delete this._eventsOfInterest; michael@0: michael@0: switch (Utils.widgetToolkit) { michael@0: case 'gonk': michael@0: this._eventsOfInterest = { michael@0: 'touchstart' : true, michael@0: 'touchmove' : true, michael@0: 'touchend' : true, michael@0: 'mousedown' : false, michael@0: 'mousemove' : false, michael@0: 'mouseup': false, michael@0: 'click': false }; michael@0: break; michael@0: michael@0: case 'android': michael@0: this._eventsOfInterest = { michael@0: 'touchstart' : true, michael@0: 'touchmove' : true, michael@0: 'touchend' : true, michael@0: 'mousemove' : true, michael@0: 'mouseenter' : true, michael@0: 'mouseleave' : true, michael@0: 'mousedown' : false, michael@0: 'mouseup': false, michael@0: 'click': false }; michael@0: break; michael@0: michael@0: default: michael@0: // Desktop. michael@0: this._eventsOfInterest = { michael@0: 'mousemove' : true, michael@0: 'mousedown' : true, michael@0: 'mouseup': true, michael@0: 'click': false michael@0: }; michael@0: if ('ontouchstart' in Utils.win) { michael@0: for (let eventType of ['touchstart', 'touchmove', 'touchend']) { michael@0: this._eventsOfInterest[eventType] = true; michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: michael@0: return this._eventsOfInterest; michael@0: }, michael@0: michael@0: _eventMap: { michael@0: 'touchstart' : 'pointerdown', michael@0: 'mousedown' : 'pointerdown', michael@0: 'mouseenter' : 'pointerdown', michael@0: 'touchmove' : 'pointermove', michael@0: 'mousemove' : 'pointermove', michael@0: 'touchend' : 'pointerup', michael@0: 'mouseup': 'pointerup', michael@0: 'mouseleave': 'pointerup' michael@0: }, michael@0: michael@0: start: function PointerRelay_start(aOnPointerEvent) { michael@0: Logger.debug('PointerRelay.start'); michael@0: this.onPointerEvent = aOnPointerEvent; michael@0: for (let eventType in this._eventsOfInterest) { michael@0: Utils.win.addEventListener(eventType, this, true, true); michael@0: } michael@0: }, michael@0: michael@0: stop: function PointerRelay_stop() { michael@0: Logger.debug('PointerRelay.stop'); michael@0: delete this.lastPointerMove; michael@0: delete this.onPointerEvent; michael@0: for (let eventType in this._eventsOfInterest) { michael@0: Utils.win.removeEventListener(eventType, this, true, true); michael@0: } michael@0: }, michael@0: michael@0: _suppressPointerMove: function PointerRelay__suppressPointerMove(aChangedTouches) { michael@0: if (!this.lastPointerMove) { michael@0: return false; michael@0: } michael@0: for (let i = 0; i < aChangedTouches.length; ++i) { michael@0: let touch = aChangedTouches[i]; michael@0: let lastTouch; michael@0: try { michael@0: lastTouch = this.lastPointerMove.identifiedTouch ? michael@0: this.lastPointerMove.identifiedTouch(touch.identifier) : michael@0: this.lastPointerMove[i]; michael@0: } catch (x) { michael@0: // Sometimes touch object can't be accessed after page navigation. michael@0: } michael@0: if (!lastTouch || lastTouch.target !== touch.target || michael@0: Math.hypot(touch.screenX - lastTouch.screenX, touch.screenY - michael@0: lastTouch.screenY) / Utils.dpi >= GestureSettings.travelThreshold) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: }, michael@0: michael@0: handleEvent: function PointerRelay_handleEvent(aEvent) { michael@0: // Don't bother with chrome mouse events. michael@0: if (Utils.MozBuildApp === 'browser' && michael@0: aEvent.view.top instanceof Ci.nsIDOMChromeWindow) { michael@0: return; michael@0: } michael@0: if (aEvent.mozInputSource === Ci.nsIDOMMouseEvent.MOZ_SOURCE_UNKNOWN) { michael@0: // Ignore events that are scripted or clicks from the a11y API. michael@0: return; michael@0: } michael@0: michael@0: let changedTouches = aEvent.changedTouches || [{ michael@0: identifier: MOUSE_ID, michael@0: screenX: aEvent.screenX, michael@0: screenY: aEvent.screenY, michael@0: target: aEvent.target michael@0: }]; michael@0: michael@0: if (changedTouches.length === 1 && michael@0: changedTouches[0].identifier === SYNTH_ID) { michael@0: return; michael@0: } michael@0: michael@0: aEvent.preventDefault(); michael@0: aEvent.stopImmediatePropagation(); michael@0: michael@0: let type = aEvent.type; michael@0: if (!this._eventsOfInterest[type]) { michael@0: return; michael@0: } michael@0: let pointerType = this._eventMap[type]; michael@0: if (pointerType === 'pointermove') { michael@0: if (this._suppressPointerMove(changedTouches)) { michael@0: // Do not fire pointermove more than every POINTERMOVE_THROTTLE. michael@0: return; michael@0: } michael@0: this.lastPointerMove = changedTouches; michael@0: } michael@0: this.onPointerEvent({ michael@0: type: pointerType, michael@0: points: Array.prototype.map.call(changedTouches, michael@0: function mapTouch(aTouch) { michael@0: return { michael@0: identifier: aTouch.identifier, michael@0: x: aTouch.screenX, michael@0: y: aTouch.screenY michael@0: }; michael@0: } michael@0: ) michael@0: }); michael@0: } michael@0: }; michael@0: michael@0: this.PointerAdapter = { // jshint ignore:line michael@0: start: function PointerAdapter_start() { michael@0: Logger.debug('PointerAdapter.start'); michael@0: GestureTracker.reset(); michael@0: PointerRelay.start(this.handleEvent); michael@0: }, michael@0: michael@0: stop: function PointerAdapter_stop() { michael@0: Logger.debug('PointerAdapter.stop'); michael@0: PointerRelay.stop(); michael@0: GestureTracker.reset(); michael@0: }, michael@0: michael@0: handleEvent: function PointerAdapter_handleEvent(aDetail) { michael@0: let timeStamp = Date.now(); michael@0: GestureTracker.handle(aDetail, timeStamp); michael@0: } michael@0: };