mirror of
https://github.com/novnc/noVNC.git
synced 2026-05-27 23:49:39 +00:00
The VNC protocol can't handle different deltas or speeds for a mouse wheel event. When using a device that sends a lot of small mouse wheel events, instead of fewer larger steps, the effect was that mouse wheel scrolling was way to sensitive. This patch looks at the delta of wheel events and doesn't send events until the combined delta has passed a threshold. Single events that doesn't pass the threshold get sent after a timeout in order to not loose any events. Fixes #577.
314 lines
13 KiB
JavaScript
314 lines
13 KiB
JavaScript
var assert = chai.assert;
|
|
var expect = chai.expect;
|
|
|
|
import Mouse from '../core/input/mouse.js';
|
|
import * as eventUtils from '../core/util/events.js';
|
|
|
|
/* jshint newcap: false, expr: true */
|
|
describe('Mouse Event Handling', function() {
|
|
"use strict";
|
|
|
|
sinon.stub(eventUtils, 'setCapture');
|
|
// This function is only used on target (the canvas)
|
|
// and for these tests we can assume that the canvas is 100x100
|
|
// located at coordinates 10x10
|
|
sinon.stub(Element.prototype, 'getBoundingClientRect').returns(
|
|
{left: 10, right: 110, top: 10, bottom: 110, width: 100, height: 100});
|
|
var target = document.createElement('canvas');
|
|
|
|
// The real constructors might not work everywhere we
|
|
// want to run these tests
|
|
var mouseevent, touchevent;
|
|
mouseevent = touchevent = function(typeArg, MouseEventInit) {
|
|
var e = { type: typeArg };
|
|
for (var key in MouseEventInit) {
|
|
e[key] = MouseEventInit[key];
|
|
}
|
|
e.stopPropagation = sinon.spy();
|
|
e.preventDefault = sinon.spy();
|
|
return e;
|
|
};
|
|
|
|
describe('Decode Mouse Events', function() {
|
|
it('should decode mousedown events', function(done) {
|
|
var mouse = new Mouse({
|
|
onMouseButton: function(x, y, down, bmask) {
|
|
expect(bmask).to.be.equal(0x01);
|
|
expect(down).to.be.equal(1);
|
|
done();
|
|
},
|
|
target: target
|
|
});
|
|
mouse._handleMouseDown(mouseevent('mousedown', { button: '0x01' }));
|
|
});
|
|
it('should decode mouseup events', function(done) {
|
|
var calls = 0;
|
|
var mouse = new Mouse({
|
|
onMouseButton: function(x, y, down, bmask) {
|
|
expect(bmask).to.be.equal(0x01);
|
|
if (calls++ === 1) {
|
|
expect(down).to.not.be.equal(1);
|
|
done();
|
|
}
|
|
},
|
|
target: target
|
|
});
|
|
mouse._handleMouseDown(mouseevent('mousedown', { button: '0x01' }));
|
|
mouse._handleMouseUp(mouseevent('mouseup', { button: '0x01' }));
|
|
});
|
|
it('should decode mousemove events', function(done) {
|
|
var mouse = new Mouse({
|
|
onMouseMove: function(x, y) {
|
|
// Note that target relative coordinates are sent
|
|
expect(x).to.be.equal(40);
|
|
expect(y).to.be.equal(10);
|
|
done();
|
|
},
|
|
target: target
|
|
});
|
|
mouse._handleMouseMove(mouseevent('mousemove',
|
|
{ clientX: 50, clientY: 20 }));
|
|
});
|
|
it('should decode mousewheel events', function(done) {
|
|
var calls = 0;
|
|
var mouse = new Mouse({
|
|
onMouseButton: function(x, y, down, bmask) {
|
|
calls++;
|
|
expect(bmask).to.be.equal(1<<6);
|
|
if (calls === 1) {
|
|
expect(down).to.be.equal(1);
|
|
} else if (calls === 2) {
|
|
expect(down).to.not.be.equal(1);
|
|
done();
|
|
}
|
|
},
|
|
target: target
|
|
});
|
|
mouse._handleMouseWheel(mouseevent('mousewheel',
|
|
{ deltaX: 50, deltaY: 0,
|
|
deltaMode: 0}));
|
|
});
|
|
});
|
|
|
|
describe('Double-click for Touch', function() {
|
|
|
|
beforeEach(function () { this.clock = sinon.useFakeTimers(); });
|
|
afterEach(function () { this.clock.restore(); });
|
|
|
|
it('should use same pos for 2nd tap if close enough', function(done) {
|
|
var calls = 0;
|
|
var mouse = new Mouse({
|
|
onMouseButton: function(x, y, down, bmask) {
|
|
calls++;
|
|
if (calls === 1) {
|
|
expect(down).to.be.equal(1);
|
|
expect(x).to.be.equal(68);
|
|
expect(y).to.be.equal(36);
|
|
} else if (calls === 3) {
|
|
expect(down).to.be.equal(1);
|
|
expect(x).to.be.equal(68);
|
|
expect(y).to.be.equal(36);
|
|
done();
|
|
}
|
|
},
|
|
target: target
|
|
});
|
|
// touch events are sent in an array of events
|
|
// with one item for each touch point
|
|
mouse._handleMouseDown(touchevent(
|
|
'touchstart', { touches: [{ clientX: 78, clientY: 46 }]}));
|
|
this.clock.tick(10);
|
|
mouse._handleMouseUp(touchevent(
|
|
'touchend', { touches: [{ clientX: 79, clientY: 45 }]}));
|
|
this.clock.tick(200);
|
|
mouse._handleMouseDown(touchevent(
|
|
'touchstart', { touches: [{ clientX: 67, clientY: 35 }]}));
|
|
this.clock.tick(10);
|
|
mouse._handleMouseUp(touchevent(
|
|
'touchend', { touches: [{ clientX: 66, clientY: 36 }]}));
|
|
});
|
|
|
|
it('should not modify 2nd tap pos if far apart', function(done) {
|
|
var calls = 0;
|
|
var mouse = new Mouse({
|
|
onMouseButton: function(x, y, down, bmask) {
|
|
calls++;
|
|
if (calls === 1) {
|
|
expect(down).to.be.equal(1);
|
|
expect(x).to.be.equal(68);
|
|
expect(y).to.be.equal(36);
|
|
} else if (calls === 3) {
|
|
expect(down).to.be.equal(1);
|
|
expect(x).to.not.be.equal(68);
|
|
expect(y).to.not.be.equal(36);
|
|
done();
|
|
}
|
|
},
|
|
target: target
|
|
});
|
|
mouse._handleMouseDown(touchevent(
|
|
'touchstart', { touches: [{ clientX: 78, clientY: 46 }]}));
|
|
this.clock.tick(10);
|
|
mouse._handleMouseUp(touchevent(
|
|
'touchend', { touches: [{ clientX: 79, clientY: 45 }]}));
|
|
this.clock.tick(200);
|
|
mouse._handleMouseDown(touchevent(
|
|
'touchstart', { touches: [{ clientX: 57, clientY: 35 }]}));
|
|
this.clock.tick(10);
|
|
mouse._handleMouseUp(touchevent(
|
|
'touchend', { touches: [{ clientX: 56, clientY: 36 }]}));
|
|
});
|
|
|
|
it('should not modify 2nd tap pos if not soon enough', function(done) {
|
|
var calls = 0;
|
|
var mouse = new Mouse({
|
|
onMouseButton: function(x, y, down, bmask) {
|
|
calls++;
|
|
if (calls === 1) {
|
|
expect(down).to.be.equal(1);
|
|
expect(x).to.be.equal(68);
|
|
expect(y).to.be.equal(36);
|
|
} else if (calls === 3) {
|
|
expect(down).to.be.equal(1);
|
|
expect(x).to.not.be.equal(68);
|
|
expect(y).to.not.be.equal(36);
|
|
done();
|
|
}
|
|
},
|
|
target: target
|
|
});
|
|
mouse._handleMouseDown(touchevent(
|
|
'touchstart', { touches: [{ clientX: 78, clientY: 46 }]}));
|
|
this.clock.tick(10);
|
|
mouse._handleMouseUp(touchevent(
|
|
'touchend', { touches: [{ clientX: 79, clientY: 45 }]}));
|
|
this.clock.tick(500);
|
|
mouse._handleMouseDown(touchevent(
|
|
'touchstart', { touches: [{ clientX: 67, clientY: 35 }]}));
|
|
this.clock.tick(10);
|
|
mouse._handleMouseUp(touchevent(
|
|
'touchend', { touches: [{ clientX: 66, clientY: 36 }]}));
|
|
});
|
|
|
|
it('should not modify 2nd tap pos if not touch', function(done) {
|
|
var calls = 0;
|
|
var mouse = new Mouse({
|
|
onMouseButton: function(x, y, down, bmask) {
|
|
calls++;
|
|
if (calls === 1) {
|
|
expect(down).to.be.equal(1);
|
|
expect(x).to.be.equal(68);
|
|
expect(y).to.be.equal(36);
|
|
} else if (calls === 3) {
|
|
expect(down).to.be.equal(1);
|
|
expect(x).to.not.be.equal(68);
|
|
expect(y).to.not.be.equal(36);
|
|
done();
|
|
}
|
|
},
|
|
target: target
|
|
});
|
|
mouse._handleMouseDown(mouseevent(
|
|
'mousedown', { button: '0x01', clientX: 78, clientY: 46 }));
|
|
this.clock.tick(10);
|
|
mouse._handleMouseUp(mouseevent(
|
|
'mouseup', { button: '0x01', clientX: 79, clientY: 45 }));
|
|
this.clock.tick(200);
|
|
mouse._handleMouseDown(mouseevent(
|
|
'mousedown', { button: '0x01', clientX: 67, clientY: 35 }));
|
|
this.clock.tick(10);
|
|
mouse._handleMouseUp(mouseevent(
|
|
'mouseup', { button: '0x01', clientX: 66, clientY: 36 }));
|
|
});
|
|
|
|
});
|
|
|
|
describe('Accumulate mouse wheel events with small delta', function() {
|
|
|
|
beforeEach(function () { this.clock = sinon.useFakeTimers(); });
|
|
afterEach(function () { this.clock.restore(); });
|
|
|
|
it('should accumulate wheel events if small enough', function () {
|
|
var callback = sinon.spy();
|
|
var mouse = new Mouse({ onMouseButton: callback, target: target });
|
|
|
|
mouse._handleMouseWheel(mouseevent(
|
|
'mousewheel', { clientX: 18, clientY: 40,
|
|
deltaX: 4, deltaY: 0, deltaMode: 0 }));
|
|
this.clock.tick(10);
|
|
mouse._handleMouseWheel(mouseevent(
|
|
'mousewheel', { clientX: 18, clientY: 40,
|
|
deltaX: 4, deltaY: 0, deltaMode: 0 }));
|
|
|
|
// threshold is 10
|
|
expect(mouse._accumulatedWheelDeltaX).to.be.equal(8);
|
|
|
|
this.clock.tick(10);
|
|
mouse._handleMouseWheel(mouseevent(
|
|
'mousewheel', { clientX: 18, clientY: 40,
|
|
deltaX: 4, deltaY: 0, deltaMode: 0 }));
|
|
|
|
expect(callback).to.have.callCount(2); // mouse down and up
|
|
|
|
this.clock.tick(10);
|
|
mouse._handleMouseWheel(mouseevent(
|
|
'mousewheel', { clientX: 18, clientY: 40,
|
|
deltaX: 4, deltaY: 9, deltaMode: 0 }));
|
|
|
|
expect(mouse._accumulatedWheelDeltaX).to.be.equal(4);
|
|
expect(mouse._accumulatedWheelDeltaY).to.be.equal(9);
|
|
|
|
expect(callback).to.have.callCount(2); // still
|
|
});
|
|
|
|
it('should not accumulate large wheel events', function () {
|
|
var callback = sinon.spy();
|
|
var mouse = new Mouse({ onMouseButton: callback, target: target });
|
|
|
|
mouse._handleMouseWheel(mouseevent(
|
|
'mousewheel', { clientX: 18, clientY: 40,
|
|
deltaX: 11, deltaY: 0, deltaMode: 0 }));
|
|
this.clock.tick(10);
|
|
mouse._handleMouseWheel(mouseevent(
|
|
'mousewheel', { clientX: 18, clientY: 40,
|
|
deltaX: 0, deltaY: 70, deltaMode: 0 }));
|
|
this.clock.tick(10);
|
|
mouse._handleMouseWheel(mouseevent(
|
|
'mousewheel', { clientX: 18, clientY: 40,
|
|
deltaX: 400, deltaY: 400, deltaMode: 0 }));
|
|
|
|
expect(callback).to.have.callCount(8); // mouse down and up
|
|
});
|
|
|
|
it('should send even small wheel events after a timeout', function () {
|
|
var callback = sinon.spy();
|
|
var mouse = new Mouse({ onMouseButton: callback, target: target });
|
|
|
|
mouse._handleMouseWheel(mouseevent(
|
|
'mousewheel', { clientX: 18, clientY: 40,
|
|
deltaX: 1, deltaY: 0, deltaMode: 0 }));
|
|
this.clock.tick(51); // timeout on 50 ms
|
|
|
|
expect(callback).to.have.callCount(2); // mouse down and up
|
|
});
|
|
|
|
it('should account for non-zero deltaMode', function () {
|
|
var callback = sinon.spy();
|
|
var mouse = new Mouse({ onMouseButton: callback, target: target });
|
|
|
|
mouse._handleMouseWheel(mouseevent(
|
|
'mousewheel', { clientX: 18, clientY: 40,
|
|
deltaX: 0, deltaY: 2, deltaMode: 1 }));
|
|
|
|
this.clock.tick(10);
|
|
|
|
mouse._handleMouseWheel(mouseevent(
|
|
'mousewheel', { clientX: 18, clientY: 40,
|
|
deltaX: 1, deltaY: 0, deltaMode: 2 }));
|
|
|
|
expect(callback).to.have.callCount(4); // mouse down and up
|
|
});
|
|
});
|
|
|
|
});
|