toolkit/devtools/touch-events.js

changeset 0
6474c204b198
     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;

mercurial