diff options
author | Samuel Mannehed <samuel@cendio.se> | 2017-09-27 15:37:04 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-09-27 15:37:04 +0200 |
commit | 05f032d1dd3d540bb2f4f15a126f150ee3e2d332 (patch) | |
tree | 8c0715107b13744300884611a0f04d66fd9b3831 | |
parent | a49ade5fa057cf55b3c35a333fb5e364a5577e30 (diff) | |
parent | d92b701393510f503000e01a047e006b2077627f (diff) | |
download | novnc-05f032d1dd3d540bb2f4f15a126f150ee3e2d332.tar.gz |
Merge pull request #896 from novnc/combinemousewheel
Combine small mouse wheel events
-rw-r--r-- | core/input/keyboard.js (renamed from core/input/devices.js) | 225 | ||||
-rw-r--r-- | core/input/mouse.js | 300 | ||||
-rw-r--r-- | core/rfb.js | 3 | ||||
-rw-r--r-- | tests/input.html | 3 | ||||
-rw-r--r-- | tests/test.keyboard.js | 4 | ||||
-rw-r--r-- | tests/test.mouse.js | 313 | ||||
-rw-r--r-- | tests/vnc_perf.html | 4 |
7 files changed, 622 insertions, 230 deletions
diff --git a/core/input/devices.js b/core/input/keyboard.js index ae09c7f..7aa6288 100644 --- a/core/input/devices.js +++ b/core/input/keyboard.js @@ -9,8 +9,7 @@ /*global window, Util */ import * as Log from '../util/logging.js'; -import { isTouchDevice } from '../util/browsers.js'; -import { setCapture, stopEvent, getPointerEvent } from '../util/events.js'; +import { stopEvent } from '../util/events.js'; import { set_defaults, make_properties } from '../util/properties.js'; import * as KeyboardUtil from "./util.js"; import KeyTable from "./keysym.js"; @@ -19,7 +18,7 @@ import KeyTable from "./keysym.js"; // Keyboard event handler // -function Keyboard(defaults) { +export default function Keyboard(defaults) { this._keyDownList = {}; // List of depressed keys // (even if they are happy) this._pendingKey = null; // Key waiting for keypress @@ -353,223 +352,3 @@ make_properties(Keyboard, [ ['onKeyEvent', 'rw', 'func'] // Handler for key press/release ]); - -function Mouse(defaults) { - - this._doubleClickTimer = null; - this._lastTouchPos = null; - - // Configuration attributes - set_defaults(this, defaults, { - 'target': document, - 'focused': true, - 'touchButton': 1 - }); - - this._eventHandlers = { - 'mousedown': this._handleMouseDown.bind(this), - 'mouseup': this._handleMouseUp.bind(this), - 'mousemove': this._handleMouseMove.bind(this), - 'mousewheel': this._handleMouseWheel.bind(this), - 'mousedisable': this._handleMouseDisable.bind(this) - }; -}; - -Mouse.prototype = { - // private methods - - _resetDoubleClickTimer: function () { - this._doubleClickTimer = null; - }, - - _handleMouseButton: function (e, down) { - if (!this._focused) { return; } - - var pos = this._getMousePosition(e); - - var bmask; - if (e.touches || e.changedTouches) { - // Touch device - - // When two touches occur within 500 ms of each other and are - // close enough together a double click is triggered. - if (down == 1) { - if (this._doubleClickTimer === null) { - this._lastTouchPos = pos; - } else { - clearTimeout(this._doubleClickTimer); - - // When the distance between the two touches is small enough - // force the position of the latter touch to the position of - // the first. - - var xs = this._lastTouchPos.x - pos.x; - var ys = this._lastTouchPos.y - pos.y; - var d = Math.sqrt((xs * xs) + (ys * ys)); - - // The goal is to trigger on a certain physical width, the - // devicePixelRatio brings us a bit closer but is not optimal. - var threshold = 20 * (window.devicePixelRatio || 1); - if (d < threshold) { - pos = this._lastTouchPos; - } - } - this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500); - } - bmask = this._touchButton; - // If bmask is set - } else if (e.which) { - /* everything except IE */ - bmask = 1 << e.button; - } else { - /* IE including 9 */ - bmask = (e.button & 0x1) + // Left - (e.button & 0x2) * 2 + // Right - (e.button & 0x4) / 2; // Middle - } - - if (this._onMouseButton) { - Log.Debug("onMouseButton " + (down ? "down" : "up") + - ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask); - this._onMouseButton(pos.x, pos.y, down, bmask); - } - stopEvent(e); - }, - - _handleMouseDown: function (e) { - // Touch events have implicit capture - if (e.type === "mousedown") { - setCapture(this._target); - } - - this._handleMouseButton(e, 1); - }, - - _handleMouseUp: function (e) { - this._handleMouseButton(e, 0); - }, - - _handleMouseWheel: function (e) { - if (!this._focused) { return; } - - var pos = this._getMousePosition(e); - - if (this._onMouseButton) { - if (e.deltaX < 0) { - this._onMouseButton(pos.x, pos.y, 1, 1 << 5); - this._onMouseButton(pos.x, pos.y, 0, 1 << 5); - } else if (e.deltaX > 0) { - this._onMouseButton(pos.x, pos.y, 1, 1 << 6); - this._onMouseButton(pos.x, pos.y, 0, 1 << 6); - } - - if (e.deltaY < 0) { - this._onMouseButton(pos.x, pos.y, 1, 1 << 3); - this._onMouseButton(pos.x, pos.y, 0, 1 << 3); - } else if (e.deltaY > 0) { - this._onMouseButton(pos.x, pos.y, 1, 1 << 4); - this._onMouseButton(pos.x, pos.y, 0, 1 << 4); - } - } - - stopEvent(e); - }, - - _handleMouseMove: function (e) { - if (! this._focused) { return; } - - var pos = this._getMousePosition(e); - if (this._onMouseMove) { - this._onMouseMove(pos.x, pos.y); - } - stopEvent(e); - }, - - _handleMouseDisable: function (e) { - if (!this._focused) { return; } - - /* - * Stop propagation if inside canvas area - * Note: This is only needed for the 'click' event as it fails - * to fire properly for the target element so we have - * to listen on the document element instead. - */ - if (e.target == this._target) { - stopEvent(e); - } - }, - - // Return coordinates relative to target - _getMousePosition: function(e) { - e = getPointerEvent(e); - var bounds = this._target.getBoundingClientRect(); - var x, y; - // Clip to target bounds - if (e.clientX < bounds.left) { - x = 0; - } else if (e.clientX >= bounds.right) { - x = bounds.width - 1; - } else { - x = e.clientX - bounds.left; - } - if (e.clientY < bounds.top) { - y = 0; - } else if (e.clientY >= bounds.bottom) { - y = bounds.height - 1; - } else { - y = e.clientY - bounds.top; - } - return {x:x, y:y}; - }, - - // Public methods - grab: function () { - var c = this._target; - - if (isTouchDevice) { - c.addEventListener('touchstart', this._eventHandlers.mousedown); - c.addEventListener('touchend', this._eventHandlers.mouseup); - c.addEventListener('touchmove', this._eventHandlers.mousemove); - } - c.addEventListener('mousedown', this._eventHandlers.mousedown); - c.addEventListener('mouseup', this._eventHandlers.mouseup); - c.addEventListener('mousemove', this._eventHandlers.mousemove); - c.addEventListener('wheel', this._eventHandlers.mousewheel); - - /* Prevent middle-click pasting (see above for why we bind to document) */ - document.addEventListener('click', this._eventHandlers.mousedisable); - - /* preventDefault() on mousedown doesn't stop this event for some - reason so we have to explicitly block it */ - c.addEventListener('contextmenu', this._eventHandlers.mousedisable); - }, - - ungrab: function () { - var c = this._target; - - if (isTouchDevice) { - c.removeEventListener('touchstart', this._eventHandlers.mousedown); - c.removeEventListener('touchend', this._eventHandlers.mouseup); - c.removeEventListener('touchmove', this._eventHandlers.mousemove); - } - c.removeEventListener('mousedown', this._eventHandlers.mousedown); - c.removeEventListener('mouseup', this._eventHandlers.mouseup); - c.removeEventListener('mousemove', this._eventHandlers.mousemove); - c.removeEventListener('wheel', this._eventHandlers.mousewheel); - - document.removeEventListener('click', this._eventHandlers.mousedisable); - - c.removeEventListener('contextmenu', this._eventHandlers.mousedisable); - } -}; - -make_properties(Mouse, [ - ['target', 'ro', 'dom'], // DOM element that captures mouse input - ['focused', 'rw', 'bool'], // Capture and send mouse clicks/movement - - ['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release - ['onMouseMove', 'rw', 'func'], // Handler for mouse movement - ['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks) -]); - -export { Keyboard, Mouse }; diff --git a/core/input/mouse.js b/core/input/mouse.js new file mode 100644 index 0000000..2e75807 --- /dev/null +++ b/core/input/mouse.js @@ -0,0 +1,300 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2012 Joel Martin + * Copyright (C) 2013 Samuel Mannehed for Cendio AB + * Licensed under MPL 2.0 or any later version (see LICENSE.txt) + */ + +/*jslint browser: true, white: false */ +/*global window, Util */ + +import * as Log from '../util/logging.js'; +import { isTouchDevice } from '../util/browsers.js'; +import { setCapture, stopEvent, getPointerEvent } from '../util/events.js'; +import { set_defaults, make_properties } from '../util/properties.js'; + +var WHEEL_STEP = 10; // Delta threshold for a mouse wheel step +var WHEEL_STEP_TIMEOUT = 50; // ms +var WHEEL_LINE_HEIGHT = 19; + +export default function Mouse(defaults) { + + this._doubleClickTimer = null; + this._lastTouchPos = null; + + this._pos = null; + this._wheelStepXTimer = null; + this._wheelStepYTimer = null; + this._accumulatedWheelDeltaX = 0; + this._accumulatedWheelDeltaY = 0; + + // Configuration attributes + set_defaults(this, defaults, { + 'target': document, + 'focused': true, + 'touchButton': 1 + }); + + this._eventHandlers = { + 'mousedown': this._handleMouseDown.bind(this), + 'mouseup': this._handleMouseUp.bind(this), + 'mousemove': this._handleMouseMove.bind(this), + 'mousewheel': this._handleMouseWheel.bind(this), + 'mousedisable': this._handleMouseDisable.bind(this) + }; +}; + +Mouse.prototype = { + // private methods + + _resetDoubleClickTimer: function () { + this._doubleClickTimer = null; + }, + + _handleMouseButton: function (e, down) { + if (!this._focused) { return; } + + this._updateMousePosition(e); + var pos = this._pos; + + var bmask; + if (e.touches || e.changedTouches) { + // Touch device + + // When two touches occur within 500 ms of each other and are + // close enough together a double click is triggered. + if (down == 1) { + if (this._doubleClickTimer === null) { + this._lastTouchPos = pos; + } else { + clearTimeout(this._doubleClickTimer); + + // When the distance between the two touches is small enough + // force the position of the latter touch to the position of + // the first. + + var xs = this._lastTouchPos.x - pos.x; + var ys = this._lastTouchPos.y - pos.y; + var d = Math.sqrt((xs * xs) + (ys * ys)); + + // The goal is to trigger on a certain physical width, the + // devicePixelRatio brings us a bit closer but is not optimal. + var threshold = 20 * (window.devicePixelRatio || 1); + if (d < threshold) { + pos = this._lastTouchPos; + } + } + this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500); + } + bmask = this._touchButton; + // If bmask is set + } else if (e.which) { + /* everything except IE */ + bmask = 1 << e.button; + } else { + /* IE including 9 */ + bmask = (e.button & 0x1) + // Left + (e.button & 0x2) * 2 + // Right + (e.button & 0x4) / 2; // Middle + } + + if (this._onMouseButton) { + Log.Debug("onMouseButton " + (down ? "down" : "up") + + ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask); + this._onMouseButton(pos.x, pos.y, down, bmask); + } + stopEvent(e); + }, + + _handleMouseDown: function (e) { + // Touch events have implicit capture + if (e.type === "mousedown") { + setCapture(this._target); + } + + this._handleMouseButton(e, 1); + }, + + _handleMouseUp: function (e) { + this._handleMouseButton(e, 0); + }, + + // Mouse wheel events are sent in steps over VNC. This means that the VNC + // protocol can't handle a wheel event with specific distance or speed. + // Therefor, if we get a lot of small mouse wheel events we combine them. + _generateWheelStepX: function () { + + if (this._accumulatedWheelDeltaX < 0) { + this._onMouseButton(this._pos.x, this._pos.y, 1, 1 << 5); + this._onMouseButton(this._pos.x, this._pos.y, 0, 1 << 5); + } else if (this._accumulatedWheelDeltaX > 0) { + this._onMouseButton(this._pos.x, this._pos.y, 1, 1 << 6); + this._onMouseButton(this._pos.x, this._pos.y, 0, 1 << 6); + } + + this._accumulatedWheelDeltaX = 0; + }, + + _generateWheelStepY: function () { + + if (this._accumulatedWheelDeltaY < 0) { + this._onMouseButton(this._pos.x, this._pos.y, 1, 1 << 3); + this._onMouseButton(this._pos.x, this._pos.y, 0, 1 << 3); + } else if (this._accumulatedWheelDeltaY > 0) { + this._onMouseButton(this._pos.x, this._pos.y, 1, 1 << 4); + this._onMouseButton(this._pos.x, this._pos.y, 0, 1 << 4); + } + + this._accumulatedWheelDeltaY = 0; + }, + + _resetWheelStepTimers: function () { + window.clearTimeout(this._wheelStepXTimer); + window.clearTimeout(this._wheelStepYTimer); + this._wheelStepXTimer = null; + this._wheelStepYTimer = null; + }, + + _handleMouseWheel: function (e) { + if (!this._focused || !this._onMouseButton) { return; } + + this._resetWheelStepTimers(); + + this._updateMousePosition(e); + + var dX = e.deltaX; + var dY = e.deltaY; + + // Pixel units unless it's non-zero. + // Note that if deltamode is line or page won't matter since we aren't + // sending the mouse wheel delta to the server anyway. + // The difference between pixel and line can be important however since + // we have a threshold that can be smaller than the line height. + if (e.deltaMode !== 0) { + dX *= WHEEL_LINE_HEIGHT; + dY *= WHEEL_LINE_HEIGHT; + } + + this._accumulatedWheelDeltaX += dX; + this._accumulatedWheelDeltaY += dY; + + // Generate a mouse wheel step event when the accumulated delta + // for one of the axes is large enough. + // Small delta events that do not pass the threshold get sent + // after a timeout. + if (Math.abs(this._accumulatedWheelDeltaX) > WHEEL_STEP) { + this._generateWheelStepX(); + } else { + this._wheelStepXTimer = + window.setTimeout(this._generateWheelStepX.bind(this), + WHEEL_STEP_TIMEOUT); + } + if (Math.abs(this._accumulatedWheelDeltaY) > WHEEL_STEP) { + this._generateWheelStepY(); + } else { + this._wheelStepYTimer = + window.setTimeout(this._generateWheelStepY.bind(this), + WHEEL_STEP_TIMEOUT); + } + + stopEvent(e); + }, + + _handleMouseMove: function (e) { + if (! this._focused) { return; } + + this._updateMousePosition(e); + if (this._onMouseMove) { + this._onMouseMove(this._pos.x, this._pos.y); + } + stopEvent(e); + }, + + _handleMouseDisable: function (e) { + if (!this._focused) { return; } + + /* + * Stop propagation if inside canvas area + * Note: This is only needed for the 'click' event as it fails + * to fire properly for the target element so we have + * to listen on the document element instead. + */ + if (e.target == this._target) { + stopEvent(e); + } + }, + + // Update coordinates relative to target + _updateMousePosition: function(e) { + e = getPointerEvent(e); + var bounds = this._target.getBoundingClientRect(); + var x, y; + // Clip to target bounds + if (e.clientX < bounds.left) { + x = 0; + } else if (e.clientX >= bounds.right) { + x = bounds.width - 1; + } else { + x = e.clientX - bounds.left; + } + if (e.clientY < bounds.top) { + y = 0; + } else if (e.clientY >= bounds.bottom) { + y = bounds.height - 1; + } else { + y = e.clientY - bounds.top; + } + this._pos = {x:x, y:y}; + }, + + // Public methods + grab: function () { + var c = this._target; + + if (isTouchDevice) { + c.addEventListener('touchstart', this._eventHandlers.mousedown); + c.addEventListener('touchend', this._eventHandlers.mouseup); + c.addEventListener('touchmove', this._eventHandlers.mousemove); + } + c.addEventListener('mousedown', this._eventHandlers.mousedown); + c.addEventListener('mouseup', this._eventHandlers.mouseup); + c.addEventListener('mousemove', this._eventHandlers.mousemove); + c.addEventListener('wheel', this._eventHandlers.mousewheel); + + /* Prevent middle-click pasting (see above for why we bind to document) */ + document.addEventListener('click', this._eventHandlers.mousedisable); + + /* preventDefault() on mousedown doesn't stop this event for some + reason so we have to explicitly block it */ + c.addEventListener('contextmenu', this._eventHandlers.mousedisable); + }, + + ungrab: function () { + var c = this._target; + + this._resetWheelStepTimers(); + + if (isTouchDevice) { + c.removeEventListener('touchstart', this._eventHandlers.mousedown); + c.removeEventListener('touchend', this._eventHandlers.mouseup); + c.removeEventListener('touchmove', this._eventHandlers.mousemove); + } + c.removeEventListener('mousedown', this._eventHandlers.mousedown); + c.removeEventListener('mouseup', this._eventHandlers.mouseup); + c.removeEventListener('mousemove', this._eventHandlers.mousemove); + c.removeEventListener('wheel', this._eventHandlers.mousewheel); + + document.removeEventListener('click', this._eventHandlers.mousedisable); + + c.removeEventListener('contextmenu', this._eventHandlers.mousedisable); + } +}; + +make_properties(Mouse, [ + ['target', 'ro', 'dom'], // DOM element that captures mouse input + ['focused', 'rw', 'bool'], // Capture and send mouse clicks/movement + + ['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release + ['onMouseMove', 'rw', 'func'], // Handler for mouse movement + ['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks) +]); diff --git a/core/rfb.js b/core/rfb.js index ece0f00..3dc7cbc 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -15,7 +15,8 @@ import _ from './util/localization.js'; import { decodeUTF8 } from './util/strings.js'; import { set_defaults, make_properties } from './util/properties.js'; import Display from "./display.js"; -import { Keyboard, Mouse } from "./input/devices.js"; +import Keyboard from "./input/keyboard.js"; +import Mouse from "./input/mouse.js"; import Websock from "./websock.js"; import Base64 from "./base64.js"; import DES from "./des.js"; diff --git a/tests/input.html b/tests/input.html index 4925a3a..5626178 100644 --- a/tests/input.html +++ b/tests/input.html @@ -31,7 +31,8 @@ <script src="../core/input/xtscancodes.js"></script> <script src="../core/input/vkeys.js"></script> <script src="../core/input/util.js"></script> - <script src="../core/input/devices.js"></script> + <script src="../core/input/keyboard.js"></script> + <script src="../core/input/mouse.js"></script> <script src="../core/display.js"></script> <script> var msg_cnt = 0, iterations, diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index 3585591..4654df9 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -1,9 +1,7 @@ var assert = chai.assert; var expect = chai.expect; -import { Keyboard } from '../core/input/devices.js'; -import keysyms from '../core/input/keysymdef.js'; -import * as KeyboardUtil from '../core/input/util.js'; +import Keyboard from '../core/input/keyboard.js'; function isIE() { return navigator && !!(/trident/i).exec(navigator.userAgent); diff --git a/tests/test.mouse.js b/tests/test.mouse.js new file mode 100644 index 0000000..e6ff754 --- /dev/null +++ b/tests/test.mouse.js @@ -0,0 +1,313 @@ +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 + }); + }); + +}); diff --git a/tests/vnc_perf.html b/tests/vnc_perf.html index acae0d1..001ac28 100644 --- a/tests/vnc_perf.html +++ b/tests/vnc_perf.html @@ -50,8 +50,8 @@ WebUtil.load_scripts({ 'core': ["base64.js", "websock.js", "des.js", "input/keysym.js", "input/keysymdef.js", "input/xtscancodes.js", "input/util.js", - "input/devices.js", "display.js", "rfb.js", "inflator.js", - "input/vkeys.js", "input/fixedkeys.js"], + "input/keyboard.js", "input/mouse.js", "display.js", "rfb.js", + "inflator.js", "input/vkeys.js", "input/fixedkeys.js"], 'tests': ["playback.js"], 'recordings': [fname]}); } else { |