summaryrefslogtreecommitdiffstats
path: root/accessible/jsat/PointerAdapter.jsm
blob: 1fb77646bf5c2b64c0316c11c898c76c4536f9c6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/* global Components, XPCOMUtils, Utils, Logger, GestureSettings,
   GestureTracker */
/* exported PointerRelay, PointerAdapter */

'use strict';

const Ci = Components.interfaces;
const Cu = Components.utils;

this.EXPORTED_SYMBOLS = ['PointerRelay', 'PointerAdapter']; // jshint ignore:line

Cu.import('resource://gre/modules/XPCOMUtils.jsm');

XPCOMUtils.defineLazyModuleGetter(this, 'Utils', // jshint ignore:line
  'resource://gre/modules/accessibility/Utils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Logger', // jshint ignore:line
  'resource://gre/modules/accessibility/Utils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'GestureSettings', // jshint ignore:line
  'resource://gre/modules/accessibility/Gestures.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'GestureTracker', // jshint ignore:line
  'resource://gre/modules/accessibility/Gestures.jsm');

// The virtual touch ID generated by a mouse event.
const MOUSE_ID = 'mouse';
// Synthesized touch ID.
const SYNTH_ID = -1;

var PointerRelay = { // jshint ignore:line
  /**
   * A mapping of events we should be intercepting. Entries with a value of
   * |true| are used for compiling high-level gesture events. Entries with a
   * value of |false| are cancelled and do not propogate to content.
   */
  get _eventsOfInterest() {
    delete this._eventsOfInterest;

    switch (Utils.widgetToolkit) {
      case 'android':
        this._eventsOfInterest = {
          'touchstart' : true,
          'touchmove' : true,
          'touchend' : true };
        break;

      default:
        // Desktop.
        this._eventsOfInterest = {
          'mousemove' : true,
          'mousedown' : true,
          'mouseup': true,
          'click': false
        };
        if ('ontouchstart' in Utils.win) {
          for (let eventType of ['touchstart', 'touchmove', 'touchend']) {
            this._eventsOfInterest[eventType] = true;
          }
        }
        break;
    }

    return this._eventsOfInterest;
  },

  _eventMap: {
    'touchstart' : 'pointerdown',
    'mousedown' : 'pointerdown',
    'touchmove' : 'pointermove',
    'mousemove' : 'pointermove',
    'touchend' : 'pointerup',
    'mouseup': 'pointerup'
  },

  start: function PointerRelay_start(aOnPointerEvent) {
    Logger.debug('PointerRelay.start');
    this.onPointerEvent = aOnPointerEvent;
    for (let eventType in this._eventsOfInterest) {
      Utils.win.addEventListener(eventType, this, true, true);
    }
  },

  stop: function PointerRelay_stop() {
    Logger.debug('PointerRelay.stop');
    delete this.lastPointerMove;
    delete this.onPointerEvent;
    for (let eventType in this._eventsOfInterest) {
      Utils.win.removeEventListener(eventType, this, true, true);
    }
  },

  handleEvent: function PointerRelay_handleEvent(aEvent) {
    // Don't bother with chrome mouse events.
    if (Utils.MozBuildApp === 'browser' &&
      aEvent.view.top instanceof Ci.nsIDOMChromeWindow) {
      return;
    }
    if (aEvent.mozInputSource === Ci.nsIDOMMouseEvent.MOZ_SOURCE_UNKNOWN ||
        aEvent.isSynthesized) {
      // Ignore events that are scripted or clicks from the a11y API.
      return;
    }

    let changedTouches = aEvent.changedTouches || [{
      identifier: MOUSE_ID,
      screenX: aEvent.screenX,
      screenY: aEvent.screenY,
      target: aEvent.target
    }];

    if (Utils.widgetToolkit === 'android' &&
      changedTouches.length === 1 && changedTouches[0].identifier === 1) {
      return;
    }

    if (changedTouches.length === 1 &&
        changedTouches[0].identifier === SYNTH_ID) {
      return;
    }

    aEvent.preventDefault();
    aEvent.stopImmediatePropagation();

    let type = aEvent.type;
    if (!this._eventsOfInterest[type]) {
      return;
    }
    let pointerType = this._eventMap[type];
    this.onPointerEvent({
      type: pointerType,
      points: Array.prototype.map.call(changedTouches,
        function mapTouch(aTouch) {
          return {
            identifier: aTouch.identifier,
            x: aTouch.screenX,
            y: aTouch.screenY
          };
        }
      )
    });
  }
};

this.PointerAdapter = { // jshint ignore:line
  start: function PointerAdapter_start() {
    Logger.debug('PointerAdapter.start');
    GestureTracker.reset();
    PointerRelay.start(this.handleEvent);
  },

  stop: function PointerAdapter_stop() {
    Logger.debug('PointerAdapter.stop');
    PointerRelay.stop();
    GestureTracker.reset();
  },

  handleEvent: function PointerAdapter_handleEvent(aDetail) {
    let timeStamp = Date.now();
    GestureTracker.handle(aDetail, timeStamp);
  }
};