Skip to content
Browse files

Automatically filter mouseevents without the left mouse button

Keep test compatibility by treating undefined button values as left mouse button

Fixes #2166
  • Loading branch information...
1 parent 720f2c0 commit bbc3b572200f1e4d832aa8c60d4a598210f80800 @azakus azakus committed
Showing with 219 additions and 10 deletions.
  1. +104 −9 src/standard/gestures.html
  2. +20 −0 test/unit/gestures-elements.html
  3. +95 −1 test/unit/gestures.html
View
113 src/standard/gestures.html
@@ -26,6 +26,15 @@
// Disabling "mouse" handlers for 2500ms is enough
var MOUSE_TIMEOUT = 2500;
var MOUSE_EVENTS = ['mousedown', 'mousemove', 'mouseup', 'click'];
+ // an array of bitmask values for mapping MouseEvent.which to MouseEvent.buttons
+ var MOUSE_WHICH_TO_BUTTONS = [0, 1, 4, 2];
+ var MOUSE_HAS_BUTTONS = (function() {
+ try {
+ return new MouseEvent('test', {buttons: 1}).buttons === 1;
+ } catch (e) {
+ return false;
+ }
+ })();
// Check for touch-only devices
var IS_TOUCH_ONLY = navigator.userAgent.match(/iP(?:[oa]d|hone)|Android/);
@@ -78,6 +87,30 @@
Polymer.Debounce(POINTERSTATE.mouse.mouseIgnoreJob, unset, MOUSE_TIMEOUT);
}
+ function hasLeftMouseButton(ev) {
+ var type = ev.type;
+ // exit early if the event is not a mouse event
+ if (MOUSE_EVENTS.indexOf(type) === -1) {
+ return false;
+ }
+ // ev.button is not reliable for mousemove (0 is overloaded as both left button and no buttons)
+ // instead we use ev.buttons (bitmask of buttons) or fall back to ev.which (deprecated, 0 for no buttons, 1 for left button)
+ if (type === 'mousemove') {
+ // allow undefined for testing events
+ var buttons = ev.buttons === undefined ? 1 : ev.buttons;
+ if ((ev instanceof window.MouseEvent) && !MOUSE_HAS_BUTTONS) {
+ buttons = MOUSE_WHICH_TO_BUTTONS[ev.which] || 0;
+ }
+ // buttons is a bitmask, check that the left button bit is set (1)
+ return Boolean(buttons & 1);
+ } else {
+ // allow undefined for testing events
+ var button = ev.button === undefined ? 0 : ev.button;
+ // ev.button is 0 in mousedown/mouseup/click for left button activation
+ return button === 0;
+ }
+ }
+
var POINTERSTATE = {
mouse: {
target: null,
@@ -336,16 +369,52 @@
Gestures.register({
name: 'downup',
deps: ['mousedown', 'touchstart', 'touchend'],
+ flow: {
+ start: ['mousedown', 'touchstart'],
+ end: ['mouseup', 'touchend']
+ },
emits: ['down', 'up'],
+ info: {
+ movefn: function(){},
+ upfn: function(){}
+ },
+
+ reset: function() {
+ this.untrackDocument();
+ },
+
+ trackDocument: function(movefn, upfn) {
+ this.info.movefn = movefn;
+ this.info.upfn = upfn;
+ document.addEventListener('mousemove', movefn);
+ document.addEventListener('mouseup', upfn);
+ },
+
+ untrackDocument: function() {
+ document.removeEventListener('mousemove', this.info.movefn);
+ document.removeEventListener('mouseup', this.info.upfn);
+ },
+
mousedown: function(e) {
+ if (!hasLeftMouseButton(e)) {
+ return;
+ }
var t = Gestures.findOriginalTarget(e);
var self = this;
+ var movefn = function movefn(e) {
+ if (!hasLeftMouseButton(e)) {
+ self.fire('up', t, e);
+ self.untrackDocument();
+ }
+ };
var upfn = function upfn(e) {
- self.fire('up', t, e);
- document.removeEventListener('mouseup', upfn);
+ if (hasLeftMouseButton(e)) {
+ self.fire('up', t, e);
+ }
+ self.untrackDocument();
};
- document.addEventListener('mouseup', upfn);
+ this.trackDocument(movefn, upfn);
this.fire('down', t, e);
},
touchstart: function(e) {
@@ -387,9 +456,23 @@
}
this.moves.push(move);
},
+ movefn: function(){},
+ upfn: function(){},
prevent: false
},
+ trackDocument: function(movefn, upfn) {
+ this.info.movefn = movefn;
+ this.info.upfn = upfn;
+ document.addEventListener('mousemove', movefn);
+ document.addEventListener('mouseup', upfn);
+ },
+
+ untrackDocument: function() {
+ document.removeEventListener('mousemove', this.info.movefn);
+ document.removeEventListener('mouseup', this.info.upfn);
+ },
+
reset: function() {
this.info.state = 'start';
this.info.started = false;
@@ -397,6 +480,7 @@
this.info.x = 0;
this.info.y = 0;
this.info.prevent = false;
+ this.untrackDocument();
},
hasMovedEnough: function(x, y) {
@@ -412,6 +496,9 @@
},
mousedown: function(e) {
+ if (!hasLeftMouseButton(e)) {
+ return;
+ }
var t = Gestures.findOriginalTarget(e);
var self = this;
var movefn = function movefn(e) {
@@ -420,6 +507,11 @@
// first move is 'start', subsequent moves are 'move', mouseup is 'end'
self.info.state = self.info.started ? (e.type === 'mouseup' ? 'end' : 'track') : 'start';
self.info.addMove({x: x, y: y});
+ if (!hasLeftMouseButton(e)) {
+ // always fire "end"
+ self.info.state = 'end';
+ self.untrackDocument();
+ }
self.fire(t, e);
self.info.started = true;
}
@@ -429,13 +521,12 @@
Gestures.prevent('tap');
movefn(e);
}
+
// remove the temporary listeners
- document.removeEventListener('mousemove', movefn);
- document.removeEventListener('mouseup', upfn);
+ self.untrackDocument();
};
// add temporary document listeners as mouse retargets
- document.addEventListener('mousemove', movefn);
- document.addEventListener('mouseup', upfn);
+ this.trackDocument(movefn, upfn);
this.info.x = e.clientX;
this.info.y = e.clientY;
},
@@ -523,10 +614,14 @@
},
mousedown: function(e) {
- this.save(e);
+ if (hasLeftMouseButton(e)) {
+ this.save(e);
+ }
},
click: function(e) {
- this.forward(e);
+ if (hasLeftMouseButton(e)) {
+ this.forward(e);
+ }
},
touchstart: function(e) {
View
20 test/unit/gestures-elements.html
@@ -119,3 +119,23 @@
});
</script>
</dom-module>
+
+<dom-module id="x-buttons">
+ <script>
+ Polymer({
+ is: 'x-buttons',
+ listeners: {
+ 'down': 'handle',
+ 'up': 'handle',
+ 'tap': 'handle',
+ 'track': 'handle'
+ },
+ created: function() {
+ this.stream = [];
+ },
+ handle: function(e) {
+ this.stream.push(e);
+ }
+ });
+ </script>
+</dom-module>
View
96 test/unit/gestures.html
@@ -38,7 +38,7 @@
test('tap on x-foo and check localTarget and rootTarget', function() {
var foo = app.$.foo;
- foo.dispatchEvent(new CustomEvent('click', {bubble: true}));
+ foo.dispatchEvent(new CustomEvent('click', {bubbles: true}));
assert.equal(app._testLocalTarget, app, 'local target');
assert.equal(app._testRootTarget, foo, 'root target');
});
@@ -280,6 +280,100 @@
assert.equal(el.stream[1].type, 'up', 'up was found');
});
});
+
+ suite('Buttons', function() {
+ var el;
+
+ setup(function() {
+ el = document.createElement('x-buttons');
+ document.body.appendChild(el);
+ });
+
+ teardown(function() {
+ el.parentNode.removeChild(el);
+ });
+
+ suite('Down and Up', function() {
+ test('Left Mouse Button Only', function() {
+ var options = {bubbles: true};
+ var evLeftDown = new CustomEvent('mousedown', options);
+ // left button
+ evLeftDown.button = 0;
+ evLeftDown.clientX = 1;
+ var evLeftUp = new CustomEvent('mouseup', options);
+ var evRightDown = new CustomEvent('mousedown', options);
+ // right button
+ evRightDown.button = 2;
+ evRightDown.clientX = 2;
+ var evRightUp = new CustomEvent('mouseup', options);
+
+ el.dispatchEvent(evLeftDown);
+ el.dispatchEvent(evLeftUp);
+ el.dispatchEvent(evRightDown);
+ el.dispatchEvent(evRightUp);
+
+ assert.equal(el.stream.length, 2, 'only saw one up and down pair');
+ assert.equal(el.stream[0].type, 'down');
+ assert.equal(el.stream[1].type, 'up');
+ assert.equal(el.stream[0].detail.x, 1, 'only from the left button');
+ });
+
+ test('Recover from right click', function() {
+ var options = {bubbles: true};
+ var evDown = new CustomEvent('mousedown', options);
+ var evMove = new CustomEvent('mousemove', options);
+ evMove.buttons = 0;
+ var evUp = new CustomEvent('mouseup', options);
+
+ el.dispatchEvent(evDown);
+ el.dispatchEvent(evMove);
+ el.dispatchEvent(evUp);
+
+ assert.equal(el.stream.length, 2, 'always get an up');
+ });
+ });
+
+ suite('Tap', function() {
+ test('Left Mouse Button Only', function() {
+ var evMid = new CustomEvent('click', {bubbles: true});
+ evMid.button = 1;
+ var evLeft = new CustomEvent('click', {bubbles: true});
+ evLeft.button = 0;
+
+ el.dispatchEvent(evMid);
+ el.dispatchEvent(evLeft);
+
+ assert.equal(el.stream.length, 1, 'only one tap');
+ });
+ });
+
+ suite('Track', function() {
+ test('Left Mouse Button Only', function() {
+ var options = {bubbles: true};
+ var ev = new CustomEvent('mousedown', options);
+ ev.clientX = ev.clientY = 0;
+ el.dispatchEvent(ev);
+ for (var i = 0; i < 5; i++) {
+ ev = new CustomEvent('mousemove', options);
+ ev.clientX = 10 * i;
+ ev.clientY = 10 * i;
+ // left button until move 4
+ ev.buttons = (i > 3) ? 2 : 1;
+ el.dispatchEvent(ev);
+ }
+ el.dispatchEvent(new CustomEvent('mouseup', options));
+
+ // down, <skipped>, track:start, track:track, track:track, track:end, up
+ assert.equal(el.stream.length, 6);
+ assert.equal(el.stream[0].type, 'down');
+ assert.equal(el.stream[1].detail.state, 'start');
+ assert.equal(el.stream[2].detail.state, 'track');
+ assert.equal(el.stream[3].detail.state, 'track');
+ assert.equal(el.stream[4].type, 'up');
+ assert.equal(el.stream[5].detail.state, 'end');
+ });
+ });
+ });
</script>
</body>

0 comments on commit bbc3b57

Please sign in to comment.
Something went wrong with that request. Please try again.