Skip to content
This repository has been archived by the owner on Feb 11, 2021. It is now read-only.

Commit

Permalink
Improve performance
Browse files Browse the repository at this point in the history
- Use raw Event objects with assignments
  - Significantly faster than new MouseEvent and defineProperty
  - Break spec compatibility about "readonly" properties
- Remove WeakMap Dependency
- Forward pageX/pageY
  • Loading branch information
dfreedm committed Mar 18, 2014
1 parent 30493f1 commit a0d41f5
Show file tree
Hide file tree
Showing 9 changed files with 68 additions and 159 deletions.
3 changes: 0 additions & 3 deletions bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@
"keywords": [
"PointerEvents"
],
"dependencies": {
"WeakMap": "Polymer/WeakMap#master"
},
"license": "BSD",
"private": true,
"ignore": [
Expand Down
1 change: 0 additions & 1 deletion build.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
[
"../WeakMap/weakmap.js",
"src/boot.js",
"src/touch-action.js",
"src/PointerEvent.js",
Expand Down
1 change: 0 additions & 1 deletion pointerevents.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
var thisFile = 'pointerevents.js';
var scopeName = 'PointerEventsPolyfill';
var modules = [
'../WeakMap/weakmap.js',
'src/boot.js',
'src/touch-action.js',
'src/PointerEvent.js',
Expand Down
109 changes: 23 additions & 86 deletions src/PointerEvent.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,6 @@
* @return {Event} A new PointerEvent of type `inType` and initialized with properties from `inDict`.
*/
(function(scope) {
// test for DOM Level 4 Events
var NEW_MOUSE_EVENT = false;
var HAS_BUTTONS = false;
try {
var ev = new MouseEvent('click', {buttons: 1});
NEW_MOUSE_EVENT = true;
HAS_BUTTONS = ev.buttons === 1;
ev = null;
} catch(e) {
}

var MOUSE_PROPS = [
'bubbles',
Expand All @@ -45,6 +35,8 @@
'metaKey',
'button',
'relatedTarget',
'pageX',
'pageY'
];

var MOUSE_DEFAULTS = [
Expand All @@ -61,101 +53,46 @@
false,
false,
0,
null
null,
0,
0
];

function PointerEvent(inType, inDict) {
inDict = inDict || {};
// According to the w3c spec,
// http://www.w3.org/TR/DOM-Level-3-Events/#events-MouseEvent-button
// MouseEvent.button == 0 can mean either no mouse button depressed, or the
// left mouse button depressed.
//
// As of now, the only way to distinguish between the two states of
// MouseEvent.button is by using the deprecated MouseEvent.which property, as
// this maps mouse buttons to positive integers > 0, and uses 0 to mean that
// no mouse button is held.
//
// MouseEvent.which is derived from MouseEvent.button at MouseEvent creation,
// but initMouseEvent does not expose an argument with which to set
// MouseEvent.which. Calling initMouseEvent with a buttonArg of 0 will set
// MouseEvent.button == 0 and MouseEvent.which == 1, breaking the expectations
// of app developers.
//
// The only way to propagate the correct state of MouseEvent.which and
// MouseEvent.button to a new MouseEvent.button == 0 and MouseEvent.which == 0
// is to call initMouseEvent with a buttonArg value of -1.
//
// This is fixed with DOM Level 4's use of buttons
var buttons = inDict.buttons;
// touch has two possible buttons state: 0 and 1, rely on being told the right one
if (!HAS_BUTTONS && !buttons && inType !== 'touch') {
switch (inDict.which) {
case 1: buttons = 1; break;
case 2: buttons = 4; break;
case 3: buttons = 2; break;
default: buttons = 0;
}
}

var e;
if (NEW_MOUSE_EVENT) {
e = new MouseEvent(inType, inDict);
} else {
e = document.createEvent('MouseEvent');
inDict = inDict || Object.create(null);

// import values from the given dictionary
var props = {}, p;
for(var i = 0; i < MOUSE_PROPS.length; i++) {
p = MOUSE_PROPS[i];
props[p] = inDict[p] || MOUSE_DEFAULTS[i];
}
var e = document.createEvent('Event');
e.initEvent(inType, inDict.bubbles || false, inDict.cancelable || false);

// define the properties inherited from MouseEvent
e.initMouseEvent(
inType, props.bubbles, props.cancelable, props.view, props.detail,
props.screenX, props.screenY, props.clientX, props.clientY, props.ctrlKey,
props.altKey, props.shiftKey, props.metaKey, props.button, props.relatedTarget
);
}

// make the event pass instanceof checks
e.__proto__ = PointerEvent.prototype;

// define the buttons property according to DOM Level 3 spec
if (!HAS_BUTTONS) {
// IE 10 has buttons on MouseEvent.prototype as a getter w/o any setting
// mechanism
Object.defineProperty(e, 'buttons', {get: function(){ return buttons; }, enumerable: true});
// define inherited MouseEvent properties
for(var i = 0, p; i < MOUSE_PROPS.length; i++) {
p = MOUSE_PROPS[i];
e[p] = inDict[p] || MOUSE_DEFAULTS[i];
}
e.buttons = inDict.buttons || 0;

// Spec requires that pointers without pressure specified use 0.5 for down
// state and 0 for up state.
var pressure = 0;
if (inDict.pressure) {
pressure = inDict.pressure;
} else {
pressure = buttons ? 0.5 : 0;
pressure = e.buttons ? 0.5 : 0;
}

// define the properties of the PointerEvent interface
Object.defineProperties(e, {
pointerId: { value: inDict.pointerId || 0, enumerable: true },
width: { value: inDict.width || 0, enumerable: true },
height: { value: inDict.height || 0, enumerable: true },
pressure: { value: pressure, enumerable: true },
tiltX: { value: inDict.tiltX || 0, enumerable: true },
tiltY: { value: inDict.tiltY || 0, enumerable: true },
pointerType: { value: inDict.pointerType || '', enumerable: true },
hwTimestamp: { value: inDict.hwTimestamp || 0, enumerable: true },
isPrimary: { value: inDict.isPrimary || false, enumerable: true }
});
e.pointerId = inDict.pointerId || 0;
e.width = inDict.width || 0;
e.height = inDict.height || 0;
e.pressure = pressure;
e.tiltX = inDict.tiltX || 0;
e.tiltY = inDict.tiltY || 0;
e.pointerType = inDict.pointerType || '';
e.hwTimestamp = inDict.hwTimestamp || 0;
e.isPrimary = inDict.isPrimary || false;
return e;
}

// PointerEvent extends MouseEvent
PointerEvent.prototype = Object.create(MouseEvent.prototype);

// attach to window
if (!scope.PointerEvent) {
scope.PointerEvent = PointerEvent;
Expand Down
24 changes: 13 additions & 11 deletions src/dispatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
'type',
'target',
'currentTarget',
'which'
'which',
'pageX',
'pageY'
];

var CLONE_DEFAULTS = [
Expand Down Expand Up @@ -72,6 +74,8 @@
'',
null,
null,
0,
0,
0
];

Expand All @@ -90,13 +94,11 @@
* - pointercancel: a pointer will no longer generate events
*/
var dispatcher = {
targets: new WeakMap(),
handledEvents: new WeakMap(),
pointermap: new scope.PointerMap(),
eventMap: {},
// Scope objects for native events.
// This exists for ease of testing.
eventSources: {},
eventSources: Object.create(null),
eventSourceList: [],
/**
* Add a new event source that will generate pointer events.
Expand Down Expand Up @@ -186,15 +188,15 @@
// This is used to prevent multiple dispatch of pointerevents from
// platform events. This can happen when two elements in different scopes
// are set up to create pointer events, which is relevant to Shadow DOM.
if (this.handledEvents.get(inEvent)) {
if (inEvent._handledByPE) {
return;
}
var type = inEvent.type;
var fn = this.eventMap && this.eventMap[type];
if (fn) {
fn(inEvent);
}
this.handledEvents.set(inEvent, true);
inEvent._handledByPE = true;
},
// set up event listeners
listen: function(target, events) {
Expand Down Expand Up @@ -232,7 +234,7 @@
if (inEvent.preventDefault) {
e.preventDefault = inEvent.preventDefault;
}
this.targets.set(e, this.targets.get(inEvent) || inEvent.target);
e._target = e._target || inEvent.target;
return e;
},
// make and dispatch an event in one call
Expand All @@ -248,7 +250,7 @@
* properties.
*/
cloneEvent: function(inEvent) {
var eventCopy = {}, p;
var eventCopy = Object.create(null), p;
for (var i = 0; i < CLONE_PROPS.length; i++) {
p = CLONE_PROPS[i];
eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i];
Expand Down Expand Up @@ -277,7 +279,7 @@
return this.captureInfo.target;
}
}
return this.targets.get(inEvent);
return inEvent._target;
},
setCapture: function(inPointerId, inTarget) {
if (this.captureInfo) {
Expand All @@ -288,7 +290,7 @@
this.implicitRelease = this.releaseCapture.bind(this, inPointerId);
document.addEventListener('pointerup', this.implicitRelease);
document.addEventListener('pointercancel', this.implicitRelease);
this.targets.set(e, inTarget);
e._target = inTarget;
this.asyncDispatchEvent(e);
},
releaseCapture: function(inPointerId) {
Expand All @@ -298,7 +300,7 @@
this.captureInfo = null;
document.removeEventListener('pointerup', this.implicitRelease);
document.removeEventListener('pointercancel', this.implicitRelease);
this.targets.set(e, t);
e._target = t;
this.asyncDispatchEvent(e);
}
},
Expand Down
10 changes: 10 additions & 0 deletions src/mouse.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@
// radius around touchend that swallows mouse events
var DEDUP_DIST = 25;

var WHICH_TO_BUTTONS = [0, 1, 4, 2];

var HAS_BUTTONS = false;
try {
HAS_BUTTONS = new MouseEvent('test', {buttons: 1}).buttons === 1;
} catch (e) {}

// handler block for native mouse events
var mouseEvents = {
POINTER_ID: 1,
Expand Down Expand Up @@ -51,6 +58,9 @@
e.pointerId = this.POINTER_ID;
e.isPrimary = true;
e.pointerType = this.POINTER_TYPE;
if (!HAS_BUTTONS) {
e.buttons = WHICH_TO_BUTTONS[e.which] || 0;
}
return e;
},
mousedown: function(inEvent) {
Expand Down
Loading

0 comments on commit a0d41f5

Please sign in to comment.