From d2bb48036317538671b1ecf0b1fc61b77649d9a4 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Fri, 24 Jul 2020 20:05:39 -0700 Subject: [PATCH 01/29] Browser sniffing and pointer events fixes and enhancements --- Gruntfile.js | 2 +- changelog.txt | 12 +- src/button.js | 73 +++- src/buttongroup.js | 7 +- src/mousetracker.js | 533 +++++++++++++++++++++++------- src/navigator.js | 1 + src/openseadragon.js | 171 +++++++++- src/referencestrip.js | 9 +- src/viewer.js | 16 +- test/helpers/legacy.mouse.shim.js | 10 +- test/helpers/test.js | 4 +- test/modules/events.js | 96 +++--- test/modules/navigator.js | 2 +- 13 files changed, 704 insertions(+), 232 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index e030188c..e3395308 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -202,7 +202,7 @@ module.exports = function(grunt) { target: sources }, "git-describe": { - "options": { + options: { failOnError: false }, build: {} diff --git a/changelog.txt b/changelog.txt index 11ca2b6d..bae5de01 100644 --- a/changelog.txt +++ b/changelog.txt @@ -7,6 +7,16 @@ OPENSEADRAGON CHANGELOG * Documentation fix (#1814 @kenanchristian) * Better cleanup on destruction, to avoid memory leaks (#1832 @JoFrMueller) * Miscellaneous code cleanup (#1840 @msalsbery) +* Improved browser sniffing - detect EDGE and CHROMEEDGE browsers +* Improved DOM event model feature detection +* Added support for options parameter on addEvent()/removeEvent (to support passive option) +* Added OpenSeadragon.eventIsCanceled() function for defaultPrevented detection on DOM events +* MouseTracker: better PointerEvent model detection - removed use of deprecated window.navigator.pointerEnabled +* MouseTracker: added overHandler/outHandler options for handling corresponding pointerover/pointerout events +* MouseTracker: changed enterHandler/leaveHandler to use DOM pointerenter/pointerleave events instead of simulating using pointerover/pointerout +* DEPRECATION: MouseTracker exitHandler deprecated for name change to leaveHandler for consistency with DOM event names +* Added missing Button event object properties (were listed in documentation but not implemented) +* Fixed bug in Button class where two MouseTracker event handlers used an invalid "this" causing issues in some browsers 2.4.2: @@ -54,7 +64,7 @@ OPENSEADRAGON CHANGELOG * You can now prevent canvas-click events on the navigator (#1416) * The navigator can now be restricted to just horizontal or just vertical panning (#1416) * Fixed DziTileSource so it doesn't load levels above maxLevel or below minLevel, if set (#1492) - + 2.3.1: * Debug mode now uses different colors for different tiled images (customizable via debugGridColor) (#1271) diff --git a/src/button.js b/src/button.js index 7b00f9ee..9c3f2db0 100644 --- a/src/button.js +++ b/src/button.js @@ -77,6 +77,7 @@ $.ButtonState = { * @param {OpenSeadragon.EventHandler} [options.onExit=null] Event handler callback for {@link OpenSeadragon.Button.event:exit}. * @param {OpenSeadragon.EventHandler} [options.onFocus=null] Event handler callback for {@link OpenSeadragon.Button.event:focus}. * @param {OpenSeadragon.EventHandler} [options.onBlur=null] Event handler callback for {@link OpenSeadragon.Button.event:blur}. + * @param {Object} [options.userData=null] Arbitrary object to be passed unchanged to any attached handler methods. */ $.Button = function( options ) { @@ -111,7 +112,8 @@ $.Button = function( options ) { onEnter: null, onExit: null, onFocus: null, - onBlur: null + onBlur: null, + userData: null }, options ); @@ -203,6 +205,7 @@ $.Button = function( options ) { */ this.tracker = new $.MouseTracker({ + userData: 'Button.tracker', element: this.element, clickTimeThreshold: this.clickTimeThreshold, clickDistThreshold: this.clickDistThreshold, @@ -220,14 +223,18 @@ $.Button = function( options ) { * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - _this.raiseEvent( "enter", { originalEvent: event.originalEvent } ); + _this.raiseEvent( "enter", { + eventSource: _this, + originalEvent: event.originalEvent, + userData: _this.userData + } ); } else if ( !event.buttonDownAny ) { inTo( _this, $.ButtonState.HOVER ); } }, focusHandler: function ( event ) { - this.enterHandler( event ); + _this.tracker.enterHandler( event ); /** * Raised when the Button element receives focus. * @@ -238,10 +245,14 @@ $.Button = function( options ) { * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - _this.raiseEvent( "focus", { originalEvent: event.originalEvent } ); + _this.raiseEvent( "focus", { + eventSource: _this, + originalEvent: event.originalEvent, + userData: _this.userData + } ); }, - exitHandler: function( event ) { + leaveHandler: function( event ) { outTo( _this, $.ButtonState.GROUP ); if ( event.insideElementPressed ) { /** @@ -254,12 +265,16 @@ $.Button = function( options ) { * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - _this.raiseEvent( "exit", { originalEvent: event.originalEvent } ); + _this.raiseEvent( "exit", { + eventSource: _this, + originalEvent: event.originalEvent, + userData: _this.userData + } ); } }, blurHandler: function ( event ) { - this.exitHandler( event ); + _this.tracker.leaveHandler( event ); /** * Raised when the Button element loses focus. * @@ -270,7 +285,11 @@ $.Button = function( options ) { * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - _this.raiseEvent( "blur", { originalEvent: event.originalEvent } ); + _this.raiseEvent( "blur", { + eventSource: _this, + originalEvent: event.originalEvent, + userData: _this.userData + } ); }, pressHandler: function ( event ) { @@ -285,7 +304,11 @@ $.Button = function( options ) { * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - _this.raiseEvent( "press", { originalEvent: event.originalEvent } ); + _this.raiseEvent( "press", { + eventSource: _this, + originalEvent: event.originalEvent, + userData: _this.userData + } ); }, releaseHandler: function( event ) { @@ -301,7 +324,11 @@ $.Button = function( options ) { * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - _this.raiseEvent( "release", { originalEvent: event.originalEvent } ); + _this.raiseEvent( "release", { + eventSource: _this, + originalEvent: event.originalEvent, + userData: _this.userData + } ); } else if ( event.insideElementPressed ) { outTo( _this, $.ButtonState.GROUP ); } else { @@ -321,7 +348,11 @@ $.Button = function( options ) { * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - _this.raiseEvent("click", { originalEvent: event.originalEvent }); + _this.raiseEvent("click", { + eventSource: _this, + originalEvent: event.originalEvent, + userData: _this.userData + }); } }, @@ -338,7 +369,11 @@ $.Button = function( options ) { * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - _this.raiseEvent( "click", { originalEvent: event.originalEvent } ); + _this.raiseEvent( "click", { + eventSource: _this, + originalEvent: event.originalEvent, + userData: _this.userData + } ); /*** * Raised when the mouse button is released or touch ends in the Button element. * @@ -349,7 +384,11 @@ $.Button = function( options ) { * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - _this.raiseEvent( "release", { originalEvent: event.originalEvent } ); + _this.raiseEvent( "release", { + eventSource: _this, + originalEvent: event.originalEvent, + userData: _this.userData + } ); return false; } return true; @@ -363,8 +402,8 @@ $.Button = function( options ) { $.extend( $.Button.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.Button.prototype */{ /** - * TODO: Determine what this function is intended to do and if it's actually - * useful as an API point. + * Used by a button container element (e.g. a ButtonGroup) to transition the button state + * to ButtonState.GROUP. * @function */ notifyGroupEnter: function() { @@ -372,8 +411,8 @@ $.extend( $.Button.prototype, $.EventSource.prototype, /** @lends OpenSeadragon. }, /** - * TODO: Determine what this function is intended to do and if it's actually - * useful as an API point. + * Used by a button container element (e.g. a ButtonGroup) to transition the button state + * to ButtonState.REST. * @function */ notifyGroupExit: function() { diff --git a/src/buttongroup.js b/src/buttongroup.js index 995f90d3..5af71fea 100644 --- a/src/buttongroup.js +++ b/src/buttongroup.js @@ -88,6 +88,7 @@ $.ButtonGroup = function( options ) { * @memberof OpenSeadragon.ButtonGroup# */ this.tracker = new $.MouseTracker({ + userData: 'ButtonGroup.tracker', element: this.element, clickTimeThreshold: this.clickTimeThreshold, clickDistThreshold: this.clickDistThreshold, @@ -97,7 +98,7 @@ $.ButtonGroup = function( options ) { _this.buttons[ i ].notifyGroupEnter(); } }, - exitHandler: function ( event ) { + leaveHandler: function ( event ) { var i; if ( !event.insideElementPressed ) { for ( i = 0; i < _this.buttons.length; i++ ) { @@ -127,8 +128,8 @@ $.ButtonGroup.prototype = { * @function * @private */ - emulateExit: function() { - this.tracker.exitHandler( { eventSource: this.tracker } ); + emulateLeave: function() { + this.tracker.leaveHandler( { eventSource: this.tracker } ); }, destroy: function() { diff --git a/src/mousetracker.js b/src/mousetracker.js index d1b584ae..72658f2f 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -74,8 +74,14 @@ * event is fired. * @param {OpenSeadragon.EventHandler} [options.enterHandler=null] * An optional handler for pointer enter. + * @param {OpenSeadragon.EventHandler} [options.leaveHandler=null] + * An optional handler for pointer leave. * @param {OpenSeadragon.EventHandler} [options.exitHandler=null] - * An optional handler for pointer exit. + * An optional handler for pointer leave. Deprecated. Use leaveHandler instead. + * @param {OpenSeadragon.EventHandler} [options.overHandler=null] + * An optional handler for pointer over. + * @param {OpenSeadragon.EventHandler} [options.outHandler=null] + * An optional handler for pointer out. * @param {OpenSeadragon.EventHandler} [options.pressHandler=null] * An optional handler for pointer press. * @param {OpenSeadragon.EventHandler} [options.nonPrimaryPressHandler=null] @@ -165,7 +171,10 @@ this.stopDelay = options.stopDelay || 50; this.enterHandler = options.enterHandler || null; + this.leaveHandler = options.leaveHandler || null; this.exitHandler = options.exitHandler || null; + this.overHandler = options.overHandler || null; + this.outHandler = options.outHandler || null; this.pressHandler = options.pressHandler || null; this.nonPrimaryPressHandler = options.nonPrimaryPressHandler || null; this.releaseHandler = options.releaseHandler || null; @@ -207,10 +216,10 @@ DOMMouseScroll: function ( event ) { onMouseWheel( _this, event ); }, MozMousePixelScroll: function ( event ) { onMouseWheel( _this, event ); }, - mouseenter: function ( event ) { onMouseEnter( _this, event ); }, // Used on IE8 only - mouseleave: function ( event ) { onMouseLeave( _this, event ); }, // Used on IE8 only - mouseover: function ( event ) { onMouseOver( _this, event ); }, - mouseout: function ( event ) { onMouseOut( _this, event ); }, + mouseover: function ( event ) { onMouseOver( _this, event ); }, // IE9+ only + mouseout: function ( event ) { onMouseOut( _this, event ); }, // IE9+ only + mouseenter: function ( event ) { onMouseEnter( _this, event ); }, + mouseleave: function ( event ) { onMouseLeave( _this, event ); }, mousedown: function ( event ) { onMouseDown( _this, event ); }, mouseup: function ( event ) { onMouseUp( _this, event ); }, mouseupcaptured: function ( event ) { onMouseUpCaptured( _this, event ); }, @@ -231,6 +240,10 @@ MSPointerOver: function ( event ) { onPointerOver( _this, event ); }, pointerout: function ( event ) { onPointerOut( _this, event ); }, MSPointerOut: function ( event ) { onPointerOut( _this, event ); }, + pointerenter: function ( event ) { onPointerEnter( _this, event ); }, + MSPointerEnter: function ( event ) { onPointerEnter( _this, event ); }, + pointerleave: function ( event ) { onPointerLeave( _this, event ); }, + MSPointerLeave: function ( event ) { onPointerLeave( _this, event ); }, pointerdown: function ( event ) { onPointerDown( _this, event ); }, MSPointerDown: function ( event ) { onPointerDown( _this, event ); }, pointerup: function ( event ) { onPointerUp( _this, event ); }, @@ -403,12 +416,74 @@ * True if the original event is a touch event, otherwise false. Deprecated. Use pointerType and/or originalEvent instead. * @param {Object} event.originalEvent * The original event object. + * @param {Object} event.userData + * Arbitrary user-defined object. + */ + enterHandler: function () { }, + + /** + * Implement or assign implementation to these handlers during or after + * calling the constructor. + * @function + * @deprecated Use leaveHandler instead + * @param {Object} event + * @param {OpenSeadragon.MouseTracker} event.eventSource + * A reference to the tracker instance. + * @param {String} event.pointerType + * "mouse", "touch", "pen", etc. + * @param {OpenSeadragon.Point} event.position + * The position of the event relative to the tracked element. + * @param {Number} event.buttons + * Current buttons pressed. + * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. + * @param {Number} event.pointers + * Number of pointers (all types) active in the tracked element. + * @param {Boolean} event.insideElementPressed + * True if the left mouse button is currently being pressed and was + * initiated inside the tracked element, otherwise false. + * @param {Boolean} event.buttonDownAny + * Was the button down anywhere in the screen during the event. Deprecated. Use buttons instead. + * @param {Boolean} event.isTouchEvent + * True if the original event is a touch event, otherwise false. Deprecated. Use pointerType and/or originalEvent instead. + * @param {Object} event.originalEvent + * The original event object. + * @param {Object} event.userData + * Arbitrary user-defined object. + */ + leaveHandler: function () { }, + + /** + * Implement or assign implementation to these handlers during or after + * calling the constructor. + * @function + * @deprecated Use leaveHandler instead + * @param {Object} event + * @param {OpenSeadragon.MouseTracker} event.eventSource + * A reference to the tracker instance. + * @param {String} event.pointerType + * "mouse", "touch", "pen", etc. + * @param {OpenSeadragon.Point} event.position + * The position of the event relative to the tracked element. + * @param {Number} event.buttons + * Current buttons pressed. + * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. + * @param {Number} event.pointers + * Number of pointers (all types) active in the tracked element. + * @param {Boolean} event.insideElementPressed + * True if the left mouse button is currently being pressed and was + * initiated inside the tracked element, otherwise false. + * @param {Boolean} event.buttonDownAny + * Was the button down anywhere in the screen during the event. Deprecated. Use buttons instead. + * @param {Boolean} event.isTouchEvent + * True if the original event is a touch event, otherwise false. Deprecated. Use pointerType and/or originalEvent instead. + * @param {Object} event.originalEvent + * The original event object. * @param {Boolean} event.preventDefaultAction * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. * @param {Object} event.userData * Arbitrary user-defined object. */ - enterHandler: function () { }, + exitHandler: function () { }, /** * Implement or assign implementation to these handlers during or after @@ -440,7 +515,39 @@ * @param {Object} event.userData * Arbitrary user-defined object. */ - exitHandler: function () { }, + overHandler: function () { }, + + /** + * Implement or assign implementation to these handlers during or after + * calling the constructor. + * @function + * @param {Object} event + * @param {OpenSeadragon.MouseTracker} event.eventSource + * A reference to the tracker instance. + * @param {String} event.pointerType + * "mouse", "touch", "pen", etc. + * @param {OpenSeadragon.Point} event.position + * The position of the event relative to the tracked element. + * @param {Number} event.buttons + * Current buttons pressed. + * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. + * @param {Number} event.pointers + * Number of pointers (all types) active in the tracked element. + * @param {Boolean} event.insideElementPressed + * True if the left mouse button is currently being pressed and was + * initiated inside the tracked element, otherwise false. + * @param {Boolean} event.buttonDownAny + * Was the button down anywhere in the screen during the event. Deprecated. Use buttons instead. + * @param {Boolean} event.isTouchEvent + * True if the original event is a touch event, otherwise false. Deprecated. Use pointerType and/or originalEvent instead. + * @param {Object} event.originalEvent + * The original event object. + * @param {Boolean} event.preventDefaultAction + * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. + * @param {Object} event.userData + * Arbitrary user-defined object. + */ + outHandler: function () { }, /** * Implement or assign implementation to these handlers during or after @@ -1020,38 +1127,37 @@ $.MouseTracker.subscribeEvents.push( "MozMousePixelScroll" ); } - // Note: window.navigator.pointerEnable is deprecated on IE 11 and not part of W3C spec. - if ( window.PointerEvent && ( window.navigator.pointerEnabled || $.Browser.vendor !== $.BROWSERS.IE ) ) { + if ( window.PointerEvent ) { // IE11 and other W3C Pointer Event implementations (see http://www.w3.org/TR/pointerevents) $.MouseTracker.havePointerEvents = true; - $.MouseTracker.subscribeEvents.push( "pointerover", "pointerout", "pointerdown", "pointerup", "pointermove", "pointercancel" ); + $.MouseTracker.subscribeEvents.push( "pointerenter", "pointerleave", "pointerover", "pointerout", "pointerdown", "pointerup", "pointermove", "pointercancel" ); $.MouseTracker.unprefixedPointerEvents = true; if( navigator.maxTouchPoints ) { $.MouseTracker.maxTouchPoints = navigator.maxTouchPoints; } else { $.MouseTracker.maxTouchPoints = 0; } - $.MouseTracker.haveMouseEnter = false; + $.MouseTracker.haveMouseOver = true; } else if ( window.MSPointerEvent && window.navigator.msPointerEnabled ) { // IE10 $.MouseTracker.havePointerEvents = true; - $.MouseTracker.subscribeEvents.push( "MSPointerOver", "MSPointerOut", "MSPointerDown", "MSPointerUp", "MSPointerMove", "MSPointerCancel" ); + $.MouseTracker.subscribeEvents.push( "MSPointerEnter", "MSPointerLeave", "MSPointerOver", "MSPointerOut", "MSPointerDown", "MSPointerUp", "MSPointerMove", "MSPointerCancel" ); $.MouseTracker.unprefixedPointerEvents = false; if( navigator.msMaxTouchPoints ) { $.MouseTracker.maxTouchPoints = navigator.msMaxTouchPoints; } else { $.MouseTracker.maxTouchPoints = 0; } - $.MouseTracker.haveMouseEnter = false; + $.MouseTracker.haveMouseOver = true; } else { // Legacy W3C mouse events $.MouseTracker.havePointerEvents = false; - if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { - $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" ); - $.MouseTracker.haveMouseEnter = true; - } else { + $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" ); + if ( $.Browser.vendor !== $.BROWSERS.IE || $.Browser.version > 8 ) { $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout" ); - $.MouseTracker.haveMouseEnter = false; + $.MouseTracker.haveMouseOver = true; + } else { + $.MouseTracker.haveMouseOver = false; } $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" ); if ( 'ontouchstart' in window ) { @@ -1832,38 +1938,12 @@ /** - * Only used on IE 8 - * * @private * @inner */ function onMouseEnter( tracker, event ) { event = $.getEvent( event ); - handleMouseEnter( tracker, event ); - } - - - /** - * @private - * @inner - */ - function onMouseOver( tracker, event ) { - event = $.getEvent( event ); - - if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { - return; - } - - handleMouseEnter( tracker, event ); - } - - - /** - * @private - * @inner - */ - function handleMouseEnter( tracker, event ) { var gPoint = { id: $.MouseTracker.mousePointerId, type: 'mouse', @@ -1873,42 +1953,21 @@ }; updatePointersEnter( tracker, event, [ gPoint ] ); + + //TODO simulate mouseover on IE < 9? + // if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { + // handleMouseOver( tracker, event ); + // } } /** - * Only used on IE 8 - * * @private * @inner */ function onMouseLeave( tracker, event ) { event = $.getEvent( event ); - handleMouseExit( tracker, event ); - } - - - /** - * @private - * @inner - */ - function onMouseOut( tracker, event ) { - event = $.getEvent( event ); - - if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { - return; - } - - handleMouseExit( tracker, event ); - } - - - /** - * @private - * @inner - */ - function handleMouseExit( tracker, event ) { var gPoint = { id: $.MouseTracker.mousePointerId, type: 'mouse', @@ -1917,7 +1976,76 @@ currentTime: $.now() }; - updatePointersExit( tracker, event, [ gPoint ] ); + updatePointersLeave( tracker, event, [ gPoint ] ); + + //TODO simulate mouseoout on IE < 9? + // if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { + // handleMouseOut( tracker, event ); + // } + } + + + /** + * IE9+ only + * + * @private + * @inner + */ + function onMouseOver( tracker, event ) { + // if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { + // return; + // } + + handleMouseOver( tracker, event ); + } + + + /** + * @private + * @inner + */ + function handleMouseOver( tracker, event ) { + var gPoint = { + id: $.MouseTracker.mousePointerId, + type: 'mouse', + isPrimary: true, + currentPos: getMouseAbsolute( event ), + currentTime: $.now() + }; + + updatePointersOver( tracker, event, [ gPoint ] ); + } + + + /** + * IE9+ only + * + * @private + * @inner + */ + function onMouseOut( tracker, event ) { + // if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { + // return; + // } + + handleMouseOut( tracker, event ); + } + + + /** + * @private + * @inner + */ + function handleMouseOut( tracker, event ) { + var gPoint = { + id: $.MouseTracker.mousePointerId, + type: 'mouse', + isPrimary: true, + currentPos: getMouseAbsolute( event ), + currentTime: $.now() + }; + + updatePointersOut( tracker, event, [ gPoint ] ); } @@ -2083,7 +2211,7 @@ pointsList.captureCount = 1; releasePointer( tracker, pointsList.type ); // simulate touchleave/mouseout - updatePointersExit( tracker, event, abortGPoints ); + updatePointersLeave( tracker, event, abortGPoints ); } } } @@ -2199,7 +2327,7 @@ } // simulate touchleave on our tracked element - updatePointersExit( tracker, event, gPoints ); + updatePointersLeave( tracker, event, gPoints ); // simulate touchleave on our tracked element's tracked ancestor elements for ( i = 0; i < MOUSETRACKERS.length; i++ ) { @@ -2214,7 +2342,7 @@ currentTime: time } ); } - updatePointersExit( MOUSETRACKERS[ i ], event, parentGPoints ); + updatePointersLeave( MOUSETRACKERS[ i ], event, parentGPoints ); } } @@ -2306,12 +2434,10 @@ * @private * @inner */ - function onPointerOver( tracker, event ) { + function onPointerEnter( tracker, event ) { var gPoint; - if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { - return; - } + //$.console.log('pointerenter ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); gPoint = { id: event.pointerId, @@ -2322,6 +2448,52 @@ }; updatePointersEnter( tracker, event, [ gPoint ] ); +} + + + /** + * @private + * @inner + */ + function onPointerLeave( tracker, event ) { + var gPoint; + + //$.console.log('pointerleave ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + + gPoint = { + id: event.pointerId, + type: getPointerType( event ), + isPrimary: event.isPrimary, + currentPos: getMouseAbsolute( event ), + currentTime: $.now() + }; + + updatePointersLeave( tracker, event, [ gPoint ] ); + } + + + /** + * @private + * @inner + */ + function onPointerOver( tracker, event ) { + var gPoint; + + $.console.log('pointerover ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + + //if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { + // return; + //} + + gPoint = { + id: event.pointerId, + type: getPointerType( event ), + isPrimary: event.isPrimary, + currentPos: getMouseAbsolute( event ), + currentTime: $.now() + }; + + updatePointersOver( tracker, event, [ gPoint ] ); } @@ -2332,9 +2504,11 @@ function onPointerOut( tracker, event ) { var gPoint; - if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { - return; - } + $.console.log('pointerout ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + + //if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { + // return; + //} gPoint = { id: event.pointerId, @@ -2344,7 +2518,7 @@ currentTime: $.now() }; - updatePointersExit( tracker, event, [ gPoint ] ); + updatePointersOut( tracker, event, [ gPoint ] ); } @@ -2568,8 +2742,7 @@ i, gPointCount = gPoints.length, curGPoint, - updateGPoint, - propagate; + updateGPoint; for ( i = 0; i < gPointCount; i++ ) { curGPoint = gPoints[ i ]; @@ -2592,9 +2765,129 @@ startTrackingPointer( pointsList, curGPoint ); } - // Enter + // Enter (doesn't bubble and not cancelable) if ( tracker.enterHandler ) { - propagate = tracker.enterHandler( + tracker.enterHandler( + { + eventSource: tracker, + pointerType: curGPoint.type, + position: getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ), + buttons: pointsList.buttons, + pointers: tracker.getActivePointerCount(), + insideElementPressed: curGPoint.insideElementPressed, + buttonDownAny: pointsList.buttons !== 0, + isTouchEvent: curGPoint.type === 'touch', + originalEvent: event, + userData: tracker.userData + } + ); + } + } + } + + + /** + * @function + * @private + * @inner + * @param {OpenSeadragon.MouseTracker} tracker + * A reference to the MouseTracker instance. + * @param {Object} event + * A reference to the originating DOM event. + * @param {Array.} gPoints + * Gesture points associated with the event. + */ + function updatePointersLeave( tracker, event, gPoints ) { + var pointsList = tracker.getActivePointersListByType(gPoints[0].type), + i, + gPointCount = gPoints.length, + curGPoint, + updateGPoint, + dispatchEventObj; + + for ( i = 0; i < gPointCount; i++ ) { + curGPoint = gPoints[ i ]; + updateGPoint = pointsList.getById( curGPoint.id ); + + if ( updateGPoint ) { + // Already tracking the pointer. If captured then update it, else stop tracking it + if ( updateGPoint.captured ) { + updateGPoint.insideElement = false; + updateGPoint.lastPos = updateGPoint.currentPos; + updateGPoint.lastTime = updateGPoint.currentTime; + updateGPoint.currentPos = curGPoint.currentPos; + updateGPoint.currentTime = curGPoint.currentTime; + } else { + stopTrackingPointer( pointsList, updateGPoint ); + } + + curGPoint = updateGPoint; + } + + // Leave (doesn't bubble and not cancelable) + // Note: exitHandler is deprecated, replaced by leaveHandler + if ( tracker.leaveHandler || tracker.exitHandler ) { + dispatchEventObj = { + eventSource: tracker, + pointerType: curGPoint.type, + position: curGPoint.currentPos && getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ), + buttons: pointsList.buttons, + pointers: tracker.getActivePointerCount(), + insideElementPressed: updateGPoint ? updateGPoint.insideElementPressed : false, + buttonDownAny: pointsList.buttons !== 0, + isTouchEvent: curGPoint.type === 'touch', + originalEvent: event, + userData: tracker.userData + }; + + if ( tracker.leaveHandler ) { + tracker.leaveHandler( dispatchEventObj ); + } + if ( tracker.exitHandler ) { + tracker.exitHandler( dispatchEventObj ); + } + } + } + } + + + /** + * @function + * @private + * @inner + * @param {OpenSeadragon.MouseTracker} tracker + * A reference to the MouseTracker instance. + * @param {Object} event + * A reference to the originating DOM event. + * @param {Array.} gPoints + * Gesture points associated with the event. + */ + function updatePointersOver( tracker, event, gPoints ) { + var pointsList, + i, + gPointCount, + curGPoint, + updateGPoint, + propagate; + + if ( tracker.overHandler ) { + pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ); + gPointCount = gPoints.length; + + for ( i = 0; i < gPointCount; i++ ) { + curGPoint = gPoints[ i ]; + updateGPoint = pointsList.getById( curGPoint.id ); + + if ( updateGPoint ) { + curGPoint = updateGPoint; + } else { + //curGPoint.captured = false; // Tracked by updatePointersEnter + curGPoint.insideElementPressed = false; + //curGPoint.insideElement = true; // Tracked by updatePointersEnter + } + + // Over + propagate = tracker.overHandler( { eventSource: tracker, pointerType: curGPoint.type, @@ -2616,7 +2909,6 @@ } } - /** * @function * @private @@ -2628,51 +2920,40 @@ * @param {Array.} gPoints * Gesture points associated with the event. */ - function updatePointersExit( tracker, event, gPoints ) { - var pointsList = tracker.getActivePointersListByType(gPoints[0].type), + function updatePointersOut( tracker, event, gPoints ) { + var pointsList, i, - gPointCount = gPoints.length, + gPointCount, curGPoint, updateGPoint, propagate; - for ( i = 0; i < gPointCount; i++ ) { - curGPoint = gPoints[ i ]; - updateGPoint = pointsList.getById( curGPoint.id ); + if ( tracker.outHandler ) { + pointsList = tracker.getActivePointersListByType(gPoints[0].type); + gPointCount = gPoints.length; - if ( updateGPoint ) { - // Already tracking the pointer. If captured then update it, else stop tracking it - if ( updateGPoint.captured ) { - updateGPoint.insideElement = false; - updateGPoint.lastPos = updateGPoint.currentPos; - updateGPoint.lastTime = updateGPoint.currentTime; - updateGPoint.currentPos = curGPoint.currentPos; - updateGPoint.currentTime = curGPoint.currentTime; - } else { - stopTrackingPointer( pointsList, updateGPoint ); + for ( i = 0; i < gPointCount; i++ ) { + curGPoint = gPoints[ i ]; + updateGPoint = pointsList.getById( curGPoint.id ); + + if ( updateGPoint ) { + curGPoint = updateGPoint; } - curGPoint = updateGPoint; - } - - // Exit - if ( tracker.exitHandler ) { - propagate = tracker.exitHandler( - { - eventSource: tracker, - pointerType: curGPoint.type, - position: curGPoint.currentPos && getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ), - buttons: pointsList.buttons, - pointers: tracker.getActivePointerCount(), - insideElementPressed: updateGPoint ? updateGPoint.insideElementPressed : false, - buttonDownAny: pointsList.buttons !== 0, - isTouchEvent: curGPoint.type === 'touch', - originalEvent: event, - preventDefaultAction: false, - userData: tracker.userData - } - ); - + // Out + propagate = tracker.outHandler( { + eventSource: tracker, + pointerType: curGPoint.type, + position: curGPoint.currentPos && getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ), + buttons: pointsList.buttons, + pointers: tracker.getActivePointerCount(), + insideElementPressed: updateGPoint ? updateGPoint.insideElementPressed : false, + buttonDownAny: pointsList.buttons !== 0, + isTouchEvent: curGPoint.type === 'touch', + originalEvent: event, + preventDefaultAction: false, + userData: tracker.userData + } ); if ( propagate === false ) { $.cancelEvent( event ); } @@ -3326,7 +3607,7 @@ */ function updatePointersCancel( tracker, event, gPoints ) { updatePointersUp( tracker, event, gPoints, 0 ); - updatePointersExit( tracker, event, gPoints ); + updatePointersLeave( tracker, event, gPoints ); } @@ -3367,7 +3648,7 @@ * @function * @private * @inner - * @returns {Boolean} True if the target has access rights to events, otherwise false. + * @returns {Boolean} True if the target supports DOM Level 2 event subscription methods, otherwise false. */ function canAccessEvents (target) { try { diff --git a/src/navigator.js b/src/navigator.js index f0bdf452..4a8db20a 100644 --- a/src/navigator.js +++ b/src/navigator.js @@ -221,6 +221,7 @@ $.Navigator = function( options ){ // Remove the base class' (Viewer's) innerTracker and replace it with our own this.innerTracker.destroy(); this.innerTracker = new $.MouseTracker({ + userData: 'Navigator.innerTracker', element: this.element, dragHandler: $.delegate( this, onCanvasDrag ), clickHandler: $.delegate( this, onCanvasClick ), diff --git a/src/openseadragon.js b/src/openseadragon.js index f035c436..d23291b8 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -917,6 +917,58 @@ function OpenSeadragon( options ){ return isTainted; }; + /** + * True if the browser supports the EventTarget.removeEventListener() method + * @member {Boolean} supportsAddEventListener + * @memberof OpenSeadragon + */ + $.supportsAddEventListener = (function () { + return !!(document.documentElement.addEventListener && document.addEventListener); + }()); + + /** + * True if the browser supports the EventTarget.removeEventListener() method + * @member {Boolean} supportsRemoveEventListener + * @memberof OpenSeadragon + */ + $.supportsRemoveEventListener = (function () { + return !!(document.documentElement.removeEventListener && document.removeEventListener); + }()); + + /** + * True if the browser supports the newer EventTarget.addEventListener options argument + * @member {Boolean} supportsEventListenerOptions + * @memberof OpenSeadragon + */ + $.supportsEventListenerOptions = (function () { + var supported = 0; + + if ( $.supportsAddEventListener ) { + try { + var options = { + get capture() { + supported++; + return false; + }, + get once() { + supported++; + return false; + }, + get passive() { + supported++; + return false; + } + }; + window.addEventListener("test", null, options); + window.removeEventListener("test", null, options); + } catch ( e ) { + supported = 0; + } + } + + return supported >= 3; + }()); + /** * A ratio comparing the device screen's pixel density to the canvas's backing store pixel density, * clamped to a minimum of 1. Defaults to 1 if canvas isn't supported by the browser. @@ -942,7 +994,7 @@ function OpenSeadragon( options ){ /** * This closure defines all static methods available to the OpenSeadragon - * namespace. Many, if not most, are taked directly from jQuery for use + * namespace. Many, if not most, are taken directly from jQuery for use * to simplify and reduce common programming patterns. More static methods * from jQuery may eventually make their way into this though we are * attempting to avoid an explicit dependency on jQuery only because @@ -1315,6 +1367,8 @@ function OpenSeadragon( options ){ * @property {Number} SAFARI * @property {Number} CHROME * @property {Number} OPERA + * @property {Number} EDGE + * @property {Number} CHROMEEDGE */ BROWSERS: { UNKNOWN: 0, @@ -1322,7 +1376,9 @@ function OpenSeadragon( options ){ FIREFOX: 2, SAFARI: 3, CHROME: 4, - OPERA: 5 + OPERA: 5, + EDGE: 6, + CHROMEEDGE: 7 }, @@ -1978,6 +2034,34 @@ function OpenSeadragon( options ){ element.className = newClasses.join(' '); }, + /** + * COnvert passed addEventListener() options to boolean or options object, + * depending on browser support. + * @function + * @param {Boolean|Object} [options] Boolean useCapture, or if [supportsEventListenerOptions]{@link OpenSeadragon.supportsEventListenerOptions}, can be an object + * @param {Boolean} [options.capture] + * @param {Boolean} [options.passive] + * @param {Boolean} [options.once] + * @return {String} The protocol (http:, https:, file:, ftp: ...) + */ + normalizeEventListenerOptions: function (options) { + var opts; + if ( typeof options !== 'undefined' ) { + if ( typeof options === 'boolean' ) { + // Legacy Boolean useCapture + opts = $.supportsEventListenerOptions ? { capture: options } : options; + } else { + // Options object + opts = $.supportsEventListenerOptions ? options : + ( ( typeof options.capture !== 'undefined' ) ? options.capture : false ); + } + } else { + // No options specified - Legacy optional useCapture argument + // (for IE, first supported on version 9, so we'll pass a Boolean) + opts = $.supportsEventListenerOptions ? { capture: false } : false; + } + return opts; + }, /** * Adds an event listener for the given element, eventName and handler. @@ -1985,16 +2069,23 @@ function OpenSeadragon( options ){ * @param {Element|String} element * @param {String} eventName * @param {Function} handler - * @param {Boolean} [useCapture] + * @param {Boolean|Object} [options] Boolean useCapture, or if [supportsEventListenerOptions]{@link OpenSeadragon.supportsEventListenerOptions}, can be an object + * @param {Boolean} [options.capture] + * @param {Boolean} [options.passive] + * @param {Boolean} [options.once] */ + // undefined - false or {capture: false} + // bool - bool or (capture: bool} + // obje - obje.capture or obje addEvent: (function () { - if ( window.addEventListener ) { - return function ( element, eventName, handler, useCapture ) { + if ( $.supportsAddEventListener ) { + return function ( element, eventName, handler, options ) { + options = $.normalizeEventListenerOptions(options); element = $.getElement( element ); - element.addEventListener( eventName, handler, useCapture ); + element.addEventListener( eventName, handler, options ); }; - } else if ( window.attachEvent ) { - return function ( element, eventName, handler, useCapture ) { + } else if ( document.documentElement.attachEvent && document.attachEvent ) { + return function ( element, eventName, handler ) { element = $.getElement( element ); element.attachEvent( 'on' + eventName, handler ); }; @@ -2011,16 +2102,18 @@ function OpenSeadragon( options ){ * @param {Element|String} element * @param {String} eventName * @param {Function} handler - * @param {Boolean} [useCapture] + * @param {Boolean|Object} [options] Boolean useCapture, or if [supportsEventListenerOptions]{@link OpenSeadragon.supportsEventListenerOptions}, can be an object + * @param {Boolean} [options.capture] */ removeEvent: (function () { - if ( window.removeEventListener ) { - return function ( element, eventName, handler, useCapture ) { + if ( $.supportsRemoveEventListener ) { + return function ( element, eventName, handler, options ) { + options = $.normalizeEventListenerOptions(options); element = $.getElement( element ); - element.removeEventListener( eventName, handler, useCapture ); + element.removeEventListener( eventName, handler, options ); }; - } else if ( window.detachEvent ) { - return function( element, eventName, handler, useCapture ) { + } else if ( document.documentElement.detachEvent && document.detachEvent ) { + return function( element, eventName, handler ) { element = $.getElement( element ); element.detachEvent( 'on' + eventName, handler ); }; @@ -2049,7 +2142,7 @@ function OpenSeadragon( options ){ event = $.getEvent( event ); // legacy for preventing default event.cancel = true; - // IE for preventing default + // IE < 9 for preventing default event.returnValue = false; }; } @@ -2058,7 +2151,39 @@ function OpenSeadragon( options ){ /** - * Stops the propagation of the event up the DOM. + * Returns true if {@link OpenSeadragon.cancelEvent|cancelEvent} has been called on + * the event, otherwise returns false. + * @function + * @param {Event} [event] + */ + eventIsCanceled: function( event ) { + event = $.getEvent( event ); + + if ( event.preventDefault ) { + $.eventIsCanceled = function( event ){ + // W3C + return event.defaultPrevented; + }; + } else { + $.eventIsCanceled = function( event ){ + event = $.getEvent( event ); + if ( typeof event.returnValue !== 'undefined' ) { + // IE < 9 + return !event.returnValue; + } else if ( typeof event.cancel !== 'undefined' ) { + // legacy + return event.cancel; + } else { + return false; + } + }; + } + return $.eventIsCanceled( event ); + }, + + + /** + * Stops the propagation of the event through the DOM in the capturing and bubbling phases. * @function * @param {Event} [event] */ @@ -2071,7 +2196,7 @@ function OpenSeadragon( options ){ event.stopPropagation(); }; } else { - // IE for stopping propagation + // IE < 9 for stopping propagation $.stopEvent = function( event ){ event = $.getEvent( event ); event.cancelBubble = true; @@ -2569,7 +2694,17 @@ function OpenSeadragon( options ){ break; case "Netscape": if (window.addEventListener) { - if ( ua.indexOf( "Firefox" ) >= 0 ) { + if ( ua.indexOf( "Edge" ) >= 0 ) { + $.Browser.vendor = $.BROWSERS.EDGE; + $.Browser.version = parseFloat( + ua.substring( ua.indexOf( "Edge" ) + 5 ) + ); + } else if ( ua.indexOf( "Edg" ) >= 0 ) { + $.Browser.vendor = $.BROWSERS.CHROMEEDGE; + $.Browser.version = parseFloat( + ua.substring( ua.indexOf( "Edg" ) + 4 ) + ); + } else if ( ua.indexOf( "Firefox" ) >= 0 ) { $.Browser.vendor = $.BROWSERS.FIREFOX; $.Browser.version = parseFloat( ua.substring( ua.indexOf( "Firefox" ) + 8 ) diff --git a/src/referencestrip.js b/src/referencestrip.js index 31340e43..ede83662 100644 --- a/src/referencestrip.js +++ b/src/referencestrip.js @@ -120,11 +120,12 @@ $.ReferenceStrip = function ( options ) { this.viewer = viewer; this.innerTracker = new $.MouseTracker( { + userData: 'ReferenceStrip.innerTracker', element: this.element, dragHandler: $.delegate( this, onStripDrag ), scrollHandler: $.delegate( this, onStripScroll ), enterHandler: $.delegate( this, onStripEnter ), - exitHandler: $.delegate( this, onStripExit ), + leaveHandler: $.delegate( this, onStripLeave ), keyDownHandler: $.delegate( this, onKeyDown ), keyHandler: $.delegate( this, onKeyPress ) } ); @@ -196,6 +197,7 @@ $.ReferenceStrip = function ( options ) { $.setElementTouchActionNone( element ); element.innerTracker = new $.MouseTracker( { + userData: 'ReferenceStrip.TileSource.innerTracker', element: element, clickTimeThreshold: this.clickTimeThreshold, clickDistThreshold: this.clickDistThreshold, @@ -475,7 +477,8 @@ function loadPanels( strip, viewerSize, scroll ) { // TODO: What is this for? Future keyboard navigation support? miniViewer.displayRegion.innerTracker = new $.MouseTracker( { - element: miniViewer.displayRegion, + userData: 'ReferenceStrip.miniViewer.innerTracker', + element: miniViewer.displayRegion, startDisabled: true } ); @@ -524,7 +527,7 @@ function onStripEnter( event ) { * @inner * @function */ -function onStripExit( event ) { +function onStripLeave( event ) { var element = event.eventSource.element; if ( 'horizontal' === this.scroll ) { diff --git a/src/viewer.js b/src/viewer.js index b5b24318..7cb756a2 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -280,6 +280,7 @@ $.Viewer = function( options ) { this.docOverflow = document.documentElement.style.overflow; this.innerTracker = new $.MouseTracker({ + userData: 'Viewer.innerTracker', element: this.canvas, startDisabled: !this.mouseNavEnabled, clickTimeThreshold: this.clickTimeThreshold, @@ -293,7 +294,7 @@ $.Viewer = function( options ) { dragHandler: $.delegate( this, onCanvasDrag ), dragEndHandler: $.delegate( this, onCanvasDragEnd ), enterHandler: $.delegate( this, onCanvasEnter ), - exitHandler: $.delegate( this, onCanvasExit ), + leaveHandler: $.delegate( this, onCanvasLeave ), pressHandler: $.delegate( this, onCanvasPress ), releaseHandler: $.delegate( this, onCanvasRelease ), nonPrimaryPressHandler: $.delegate( this, onCanvasNonPrimaryPress ), @@ -303,6 +304,7 @@ $.Viewer = function( options ) { }); this.outerTracker = new $.MouseTracker({ + userData: 'Viewer.outerTracker', element: this.container, startDisabled: !this.mouseNavEnabled, clickTimeThreshold: this.clickTimeThreshold, @@ -310,7 +312,7 @@ $.Viewer = function( options ) { dblClickTimeThreshold: this.dblClickTimeThreshold, dblClickDistThreshold: this.dblClickDistThreshold, enterHandler: $.delegate( this, onContainerEnter ), - exitHandler: $.delegate( this, onContainerExit ) + leaveHandler: $.delegate( this, onContainerLeave ) }); if( this.toolbar ){ @@ -1099,7 +1101,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, THIS[ this.hash ].fullPage = false; // mouse will likely be outside now - $.delegate( this, onContainerExit )( { } ); + $.delegate( this, onContainerLeave )( { } ); } @@ -2925,7 +2927,7 @@ function onCanvasEnter( event ) { }); } -function onCanvasExit( event ) { +function onCanvasLeave( event ) { if (window.location !== window.parent.location){ $.MouseTracker.resetAllMouseTrackers(); @@ -3227,7 +3229,7 @@ function onContainerEnter( event ) { }); } -function onContainerExit( event ) { +function onContainerLeave( event ) { if ( event.pointers < 1 ) { THIS[ this.hash ].mouseInside = false; if ( !THIS[ this.hash ].animating ) { @@ -3483,7 +3485,7 @@ function doSingleZoomOut() { function lightUp() { this.buttons.emulateEnter(); - this.buttons.emulateExit(); + this.buttons.emulateLeave(); } @@ -3503,7 +3505,7 @@ function onFullScreen() { } // correct for no mouseout event on change if ( this.buttons ) { - this.buttons.emulateExit(); + this.buttons.emulateLeave(); } this.fullPageButton.element.focus(); if ( this.viewport ) { diff --git a/test/helpers/legacy.mouse.shim.js b/test/helpers/legacy.mouse.shim.js index 3609ed85..e946c5f9 100644 --- a/test/helpers/legacy.mouse.shim.js +++ b/test/helpers/legacy.mouse.shim.js @@ -12,12 +12,12 @@ } $.MouseTracker.havePointerEvents = false; - if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { - $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" ); - $.MouseTracker.haveMouseEnter = true; - } else { + $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" ); + if ( $.Browser.vendor !== $.BROWSERS.IE || $.Browser.version > 8 ) { $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout" ); - $.MouseTracker.haveMouseEnter = false; + $.MouseTracker.haveMouseOver = true; + } else { + $.MouseTracker.haveMouseOver = false; } $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" ); if ( 'ontouchstart' in window ) { diff --git a/test/helpers/test.js b/test/helpers/test.js index 8e7685d8..60931bba 100644 --- a/test/helpers/test.js +++ b/test/helpers/test.js @@ -37,7 +37,7 @@ }; $canvas - .simulate( OpenSeadragon.MouseTracker.haveMouseEnter ? 'mouseenter' : 'mouseover', event ) + .simulate( 'mouseenter', event ) .simulate( 'mousedown', event ); for ( var i = 0; i < args.dragCount; i++ ) { event.clientX += args.dragDx; @@ -47,7 +47,7 @@ } $canvas .simulate( 'mouseup', event ) - .simulate( OpenSeadragon.MouseTracker.haveMouseEnter ? 'mouseleave' : 'mouseout', event ); + .simulate( 'mouseleave', event ); }, // ---------- diff --git a/test/modules/events.js b/test/modules/events.js index 849e86f9..68e6232d 100644 --- a/test/modules/events.js +++ b/test/modules/events.js @@ -32,7 +32,7 @@ offset = $canvas.offset(), tracker = viewer.innerTracker, origEnterHandler, - origExitHandler, + origLeaveHandler, origPressHandler, origReleaseHandler, origNonPrimaryPressHandler, @@ -43,7 +43,7 @@ origDragHandler, origDragEndHandler, enterCount, - exitCount, + leaveCount, pressCount, releaseCount, rightPressCount, @@ -71,11 +71,11 @@ return true; } }; - origExitHandler = tracker.exitHandler; - tracker.exitHandler = function ( event ) { - exitCount++; - if (origExitHandler) { - return origExitHandler( event ); + origLeaveHandler = tracker.leaveHandler; + tracker.leaveHandler = function ( event ) { + leaveCount++; + if (origLeaveHandler) { + return origLeaveHandler( event ); } else { return true; } @@ -182,7 +182,7 @@ var unhookViewerHandlers = function () { tracker.enterHandler = origEnterHandler; - tracker.exitHandler = origExitHandler; + tracker.leaveHandler = origLeaveHandler; tracker.pressHandler = origPressHandler; tracker.releaseHandler = origReleaseHandler; tracker.moveHandler = origMoveHandler; @@ -195,21 +195,21 @@ var simulateEnter = function (x, y) { simEvent.clientX = offset.left + x; simEvent.clientY = offset.top + y; - $canvas.simulate( OpenSeadragon.MouseTracker.haveMouseEnter ? 'mouseenter' : 'mouseover', simEvent ); + $canvas.simulate( 'mouseenter', simEvent ); }; var simulateLeave = function (x, y) { simEvent.clientX = offset.left + x; simEvent.clientY = offset.top + y; simEvent.relatedTarget = document.body; - $canvas.simulate( OpenSeadragon.MouseTracker.haveMouseEnter ? 'mouseleave' : 'mouseout', simEvent ); + $canvas.simulate( 'mouseleave', simEvent ); }; //var simulateLeaveFrame = function (x, y) { // simEvent.clientX = offset.left + x; // simEvent.clientY = offset.top + y; // simEvent.relatedTarget = document.getElementsByTagName("html")[0]; - // $canvas.simulate( OpenSeadragon.MouseTracker.haveMouseEnter ? 'mouseleave' : 'mouseout', simEvent ); + // $canvas.simulate( 'mouseleave', simEvent ); //}; var simulateDown = function (x, y) { @@ -256,7 +256,7 @@ clientY: offset.top }; enterCount = 0; - exitCount = 0; + leaveCount = 0; pressCount = 0; releaseCount = 0; rightPressCount = 0; @@ -280,8 +280,8 @@ if ('enterCount' in expected) { assert.equal( enterCount, expected.enterCount, expected.description + 'enterHandler event count matches expected (' + expected.enterCount + ')' ); } - if ('exitCount' in expected) { - assert.equal( exitCount, expected.exitCount, expected.description + 'exitHandler event count matches expected (' + expected.exitCount + ')' ); + if ('leaveCount' in expected) { + assert.equal( leaveCount, expected.leaveCount, expected.description + 'leaveHandler event count matches expected (' + expected.leaveCount + ')' ); } if ('pressCount' in expected) { assert.equal( pressCount, expected.pressCount, expected.description + 'pressHandler event count matches expected (' + expected.pressCount + ')' ); @@ -355,7 +355,7 @@ assessGestureExpectations({ description: 'enter-move-release (release in tracked element, press in unknown element): ', enterCount: 1, - exitCount: 0, + leaveCount: 0, pressCount: 0, releaseCount: 1, rightPressCount: 0, @@ -375,16 +375,16 @@ }); simulateLeave(-1, -1); // flush tracked pointer - // enter-move-exit (fly-over) + // enter-move-leave (fly-over) resetForAssessment(); simulateEnter(0, 0); simulateMove(1, 1, 10); simulateMove(-1, -1, 10); simulateLeave(-1, -1); assessGestureExpectations({ - description: 'enter-move-exit (fly-over): ', + description: 'enter-move-leave (fly-over): ', enterCount: 1, - exitCount: 1, + leaveCount: 1, pressCount: 0, releaseCount: 0, rightPressCount: 0, @@ -403,15 +403,15 @@ //quickClick: false }); - // move-exit (fly-over, no enter event) + // move-leave (fly-over, no enter event) resetForAssessment(); simulateMove(1, 1, 10); simulateMove(-1, -1, 10); simulateLeave(-1, -1); assessGestureExpectations({ - description: 'move-exit (fly-over, no enter event): ', + description: 'move-leave (fly-over, no enter event): ', enterCount: 0, - exitCount: 1, + leaveCount: 1, pressCount: 0, releaseCount: 0, rightPressCount: 0, @@ -430,7 +430,7 @@ //quickClick: false }); - // enter-press-release-press-release-exit (primary/left double click) + // enter-press-release-press-release-leave (primary/left double click) resetForAssessment(); simulateEnter(0, 0); simulateDown(0, 0); @@ -439,9 +439,9 @@ simulateUp(0, 0); simulateLeave(-1, -1); assessGestureExpectations({ - description: 'enter-press-release-press-release-exit (primary/left double click): ', + description: 'enter-press-release-press-release-leave (primary/left double click): ', enterCount: 1, - exitCount: 1, + leaveCount: 1, pressCount: 2, releaseCount: 2, rightPressCount: 0, @@ -460,16 +460,16 @@ //quickClick: true }); - // enter-press-release-exit (primary/left click) + // enter-press-release-leave (primary/left click) resetForAssessment(); simulateEnter(0, 0); simulateDown(0, 0); simulateUp(0, 0); simulateLeave(-1, -1); assessGestureExpectations({ - description: 'enter-press-release-exit (primary/left click): ', + description: 'enter-press-release-leave (primary/left click): ', enterCount: 1, - exitCount: 1, + leaveCount: 1, pressCount: 1, releaseCount: 1, rightPressCount: 0, @@ -488,16 +488,16 @@ quickClick: true }); - // enter-nonprimarypress-nonprimaryrelease-exit (secondary/right click) + // enter-nonprimarypress-nonprimaryrelease-leave (secondary/right click) resetForAssessment(); simulateEnter(0, 0); simulateNonPrimaryDown(0, 0, 2); simulateNonPrimaryUp(0, 0, 2); simulateLeave(-1, -1); assessGestureExpectations({ - description: 'enter-nonprimarypress-nonprimaryrelease-exit (secondary/right click): ', + description: 'enter-nonprimarypress-nonprimaryrelease-leave (secondary/right click): ', enterCount: 1, - exitCount: 1, + leaveCount: 1, pressCount: 0, releaseCount: 0, rightPressCount: 1, @@ -516,16 +516,16 @@ //quickClick: true }); - // enter-nonprimarypress-nonprimaryrelease-exit (aux/middle click) + // enter-nonprimarypress-nonprimaryrelease-leave (aux/middle click) resetForAssessment(); simulateEnter(0, 0); simulateNonPrimaryDown(0, 0, 1); simulateNonPrimaryUp(0, 0, 1); simulateLeave(-1, -1); assessGestureExpectations({ - description: 'enter-nonprimarypress-nonprimaryrelease-exit (aux/middle click): ', + description: 'enter-nonprimarypress-nonprimaryrelease-leave (aux/middle click): ', enterCount: 1, - exitCount: 1, + leaveCount: 1, pressCount: 0, releaseCount: 0, rightPressCount: 0, @@ -544,7 +544,7 @@ //quickClick: true }); - // enter-nonprimarypress-move-nonprimaryrelease-move-exit (secondary/right button drag, release in tracked element) + // enter-nonprimarypress-move-nonprimaryrelease-move-leave (secondary/right button drag, release in tracked element) resetForAssessment(); simulateEnter(0, 0); simulateNonPrimaryDown(0, 0, 2); @@ -553,9 +553,9 @@ simulateMove(-1, -1, 100); simulateLeave(-1, -1); assessGestureExpectations({ - description: 'enter-nonprimarypress-move-nonprimaryrelease-move-exit (secondary/right button drag, release in tracked element): ', + description: 'enter-nonprimarypress-move-nonprimaryrelease-move-leave (secondary/right button drag, release in tracked element): ', enterCount: 1, - exitCount: 1, + leaveCount: 1, pressCount: 0, releaseCount: 0, rightPressCount: 1, @@ -574,7 +574,7 @@ //quickClick: false }); - // enter-press-move-release-move-exit (drag, release in tracked element) + // enter-press-move-release-move-leave (drag, release in tracked element) resetForAssessment(); simulateEnter(0, 0); simulateDown(0, 0); @@ -583,9 +583,9 @@ simulateMove(-1, -1, 100); simulateLeave(-1, -1); assessGestureExpectations({ - description: 'enter-press-move-release-move-exit (drag, release in tracked element): ', + description: 'enter-press-move-release-move-leave (drag, release in tracked element): ', enterCount: 1, - exitCount: 1, + leaveCount: 1, pressCount: 1, releaseCount: 1, rightPressCount: 0, @@ -604,7 +604,7 @@ quickClick: false }); - // enter-press-move-exit-move-release (drag, release outside tracked element) + // enter-press-move-leave-move-release (drag, release outside tracked element) resetForAssessment(); simulateEnter(0, 0); simulateDown(0, 0); @@ -614,9 +614,9 @@ simulateMove(-1, -1, 5); simulateUp(-5, -5); assessGestureExpectations({ - description: 'enter-press-move-exit-move-release (drag, release outside tracked element): ', + description: 'enter-press-move-leave-move-release (drag, release outside tracked element): ', enterCount: 1, - exitCount: 1, + leaveCount: 1, pressCount: 1, releaseCount: 1, rightPressCount: 0, @@ -635,7 +635,7 @@ quickClick: false }); - //// enter-press-move-exit-move-release-outside (drag, release outside iframe) + //// enter-press-move-leave-move-release-outside (drag, release outside iframe) //resetForAssessment(); //simulateEnter(0, 0); //simulateDown(0, 0); @@ -644,9 +644,9 @@ //simulateLeaveFrame(-1, -1); //// you don't actually receive the mouseup if you mouseup outside of the document //assessGestureExpectations({ - // description: 'enter-press-move-exit-move-release-outside (drag, release outside iframe): ', - // enterCount: 1, - // exitCount: 1, + // description: 'enter-press-move-leave-move-release-outside (drag, release outside iframe): ', + // enterCount: 1, + // leaveCount: 1, // pressCount: 1, // releaseCount: 1, // rightPressCount: 0, @@ -953,7 +953,7 @@ dragEndHandler: onMouseTrackerDragEnd, releaseHandler: onMouseTrackerRelease, clickHandler: onMouseTrackerClick, - exitHandler: onMouseTrackerExit + leaveHandler: onMouseTrackerLeave } ); var event = { @@ -1050,7 +1050,7 @@ checkOriginalEventReceived( event ); }; - var onMouseTrackerExit = function ( event ) { + var onMouseTrackerLeave = function ( event ) { checkOriginalEventReceived( event ); mouseTracker.destroy(); diff --git a/test/modules/navigator.js b/test/modules/navigator.js index e7f5f327..d1f98dea 100644 --- a/test/modules/navigator.js +++ b/test/modules/navigator.js @@ -216,7 +216,7 @@ clientY: offset.top + locationY }; $canvas - .simulate(OpenSeadragon.MouseTracker.haveMouseEnter ? 'mouseenter' : 'mouseover', event) + .simulate('mouseenter', event) .simulate('mousedown', event) .simulate('mouseup', event); }; From 2d4a72385474a8cee55341107a4ba167e98b76d2 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Sat, 25 Jul 2020 18:30:06 -0700 Subject: [PATCH 02/29] MouseTracker enhancements, improved IE <=10 support --- changelog.txt | 2 + src/mousetracker.js | 137 ++++++++++++++++-------------- test/helpers/legacy.mouse.shim.js | 6 +- 3 files changed, 79 insertions(+), 66 deletions(-) diff --git a/changelog.txt b/changelog.txt index bae5de01..12c5cf25 100644 --- a/changelog.txt +++ b/changelog.txt @@ -17,6 +17,8 @@ OPENSEADRAGON CHANGELOG * DEPRECATION: MouseTracker exitHandler deprecated for name change to leaveHandler for consistency with DOM event names * Added missing Button event object properties (were listed in documentation but not implemented) * Fixed bug in Button class where two MouseTracker event handlers used an invalid "this" causing issues in some browsers +* MouseTracker: IE 10 - MSPointerEnter/MSPointerLeave didn't exist then, simulated with MSPointerOver/MSPointerOut +* MouseTracker: Simulate mouseover/mouseout on IE < 9 2.4.2: diff --git a/src/mousetracker.js b/src/mousetracker.js index 72658f2f..af42e9e3 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -172,7 +172,7 @@ this.enterHandler = options.enterHandler || null; this.leaveHandler = options.leaveHandler || null; - this.exitHandler = options.exitHandler || null; + this.exitHandler = options.exitHandler || null; // Deprecated v2.4.3 this.overHandler = options.overHandler || null; this.outHandler = options.outHandler || null; this.pressHandler = options.pressHandler || null; @@ -216,10 +216,10 @@ DOMMouseScroll: function ( event ) { onMouseWheel( _this, event ); }, MozMousePixelScroll: function ( event ) { onMouseWheel( _this, event ); }, - mouseover: function ( event ) { onMouseOver( _this, event ); }, // IE9+ only - mouseout: function ( event ) { onMouseOut( _this, event ); }, // IE9+ only mouseenter: function ( event ) { onMouseEnter( _this, event ); }, mouseleave: function ( event ) { onMouseLeave( _this, event ); }, + mouseover: function ( event ) { onMouseOver( _this, event ); }, // IE9+ only + mouseout: function ( event ) { onMouseOut( _this, event ); }, // IE9+ only mousedown: function ( event ) { onMouseDown( _this, event ); }, mouseup: function ( event ) { onMouseUp( _this, event ); }, mouseupcaptured: function ( event ) { onMouseUpCaptured( _this, event ); }, @@ -236,14 +236,12 @@ gesturestart: function ( event ) { onGestureStart( _this, event ); }, gesturechange: function ( event ) { onGestureChange( _this, event ); }, + pointerenter: function ( event ) { onPointerEnter( _this, event ); }, + pointerleave: function ( event ) { onPointerLeave( _this, event ); }, pointerover: function ( event ) { onPointerOver( _this, event ); }, MSPointerOver: function ( event ) { onPointerOver( _this, event ); }, pointerout: function ( event ) { onPointerOut( _this, event ); }, MSPointerOut: function ( event ) { onPointerOut( _this, event ); }, - pointerenter: function ( event ) { onPointerEnter( _this, event ); }, - MSPointerEnter: function ( event ) { onPointerEnter( _this, event ); }, - pointerleave: function ( event ) { onPointerLeave( _this, event ); }, - MSPointerLeave: function ( event ) { onPointerLeave( _this, event ); }, pointerdown: function ( event ) { onPointerDown( _this, event ); }, MSPointerDown: function ( event ) { onPointerDown( _this, event ); }, pointerup: function ( event ) { onPointerUp( _this, event ); }, @@ -425,7 +423,7 @@ * Implement or assign implementation to these handlers during or after * calling the constructor. * @function - * @deprecated Use leaveHandler instead + * @since v2.4.3 * @param {Object} event * @param {OpenSeadragon.MouseTracker} event.eventSource * A reference to the tracker instance. @@ -456,7 +454,7 @@ * Implement or assign implementation to these handlers during or after * calling the constructor. * @function - * @deprecated Use leaveHandler instead + * @deprecated v2.4.3 Use leaveHandler instead * @param {Object} event * @param {OpenSeadragon.MouseTracker} event.eventSource * A reference to the tracker instance. @@ -489,6 +487,7 @@ * Implement or assign implementation to these handlers during or after * calling the constructor. * @function + * @since v2.4.3 * @param {Object} event * @param {OpenSeadragon.MouseTracker} event.eventSource * A reference to the tracker instance. @@ -521,6 +520,7 @@ * Implement or assign implementation to these handlers during or after * calling the constructor. * @function + * @since v2.4.3 * @param {Object} event * @param {OpenSeadragon.MouseTracker} event.eventSource * A reference to the tracker instance. @@ -1109,14 +1109,6 @@ document.onmousewheel !== undefined ? 'mousewheel' : // Webkit and IE support at least 'mousewheel' 'DOMMouseScroll'; // Assume old Firefox - /** - * Detect legacy mouse capture support. - */ - $.MouseTracker.supportsMouseCapture = (function () { - var divElement = document.createElement( 'div' ); - return $.isFunction( divElement.setCapture ) && $.isFunction( divElement.releaseCapture ); - }()); - /** * Detect browser pointer device event model(s) and build appropriate list of events to subscribe to. */ @@ -1132,34 +1124,41 @@ $.MouseTracker.havePointerEvents = true; $.MouseTracker.subscribeEvents.push( "pointerenter", "pointerleave", "pointerover", "pointerout", "pointerdown", "pointerup", "pointermove", "pointercancel" ); $.MouseTracker.unprefixedPointerEvents = true; - if( navigator.maxTouchPoints ) { - $.MouseTracker.maxTouchPoints = navigator.maxTouchPoints; - } else { - $.MouseTracker.maxTouchPoints = 0; - } - $.MouseTracker.haveMouseOver = true; + $.MouseTracker.havePointerOverOut = true; + // Pointer events capture support + $.MouseTracker.havePointerCapture = (function () { + var divElement = document.createElement( 'div' ); + return $.isFunction( divElement.setPointerCapture ) && $.isFunction( divElement.releasePointerCapture ); + }()); } else if ( window.MSPointerEvent && window.navigator.msPointerEnabled ) { - // IE10 + // IE10 (MSPointerEnter/MSPointerLeave simulated with MSPointerOver/MSPointerOut) $.MouseTracker.havePointerEvents = true; - $.MouseTracker.subscribeEvents.push( "MSPointerEnter", "MSPointerLeave", "MSPointerOver", "MSPointerOut", "MSPointerDown", "MSPointerUp", "MSPointerMove", "MSPointerCancel" ); + $.MouseTracker.subscribeEvents.push( "MSPointerOver", "MSPointerOut", "MSPointerDown", "MSPointerUp", "MSPointerMove", "MSPointerCancel" ); $.MouseTracker.unprefixedPointerEvents = false; - if( navigator.msMaxTouchPoints ) { - $.MouseTracker.maxTouchPoints = navigator.msMaxTouchPoints; - } else { - $.MouseTracker.maxTouchPoints = 0; - } - $.MouseTracker.haveMouseOver = true; + $.MouseTracker.havePointerOverOut = true; + // Prefixed pointer events capture support + $.MouseTracker.havePointerCapture = (function () { + var divElement = document.createElement( 'div' ); + return $.isFunction( divElement.msSetPointerCapture ) && $.isFunction( divElement.msReleasePointerCapture ); + }()); } else { // Legacy W3C mouse events $.MouseTracker.havePointerEvents = false; $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" ); if ( $.Browser.vendor !== $.BROWSERS.IE || $.Browser.version > 8 ) { $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout" ); - $.MouseTracker.haveMouseOver = true; + $.MouseTracker.havePointerOverOut = true; } else { - $.MouseTracker.haveMouseOver = false; + $.MouseTracker.havePointerOverOut = false; } $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" ); + $.MouseTracker.mousePointerId = "legacy-mouse"; + // Legacy mouse events capture support + $.MouseTracker.havePointerCapture = (function () { + var divElement = document.createElement( 'div' ); + return $.isFunction( divElement.setCapture ) && $.isFunction( divElement.releaseCapture ); + }()); + // Legacy touch events if ( 'ontouchstart' in window ) { // iOS, Android, and other W3c Touch Event implementations // (see http://www.w3.org/TR/touch-events/) @@ -1172,8 +1171,6 @@ // Subscribe to these to prevent default gesture handling $.MouseTracker.subscribeEvents.push( "gesturestart", "gesturechange" ); } - $.MouseTracker.mousePointerId = "legacy-mouse"; - $.MouseTracker.maxTouchPoints = 10; } @@ -1954,10 +1951,10 @@ updatePointersEnter( tracker, event, [ gPoint ] ); - //TODO simulate mouseover on IE < 9? - // if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { - // handleMouseOver( tracker, event ); - // } + // Simulate mouseover on IE < 9 + if ( !$.MouseTracker.havePointerOverOut ) { + handleMouseOver( tracker, event ); + } } @@ -1978,10 +1975,10 @@ updatePointersLeave( tracker, event, [ gPoint ] ); - //TODO simulate mouseoout on IE < 9? - // if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { - // handleMouseOut( tracker, event ); - // } + // Simulate mouseoout on IE < 9 + if ( !$.MouseTracker.havePointerOverOut ) { + handleMouseOut( tracker, event ); + } } @@ -1992,10 +1989,6 @@ * @inner */ function onMouseOver( tracker, event ) { - // if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { - // return; - // } - handleMouseOver( tracker, event ); } @@ -2024,10 +2017,6 @@ * @inner */ function onMouseOut( tracker, event ) { - // if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { - // return; - // } - handleMouseOut( tracker, event ); } @@ -2435,9 +2424,18 @@ * @inner */ function onPointerEnter( tracker, event ) { + handlePointerEnter( tracker, event ); + } + + + /** + * @private + * @inner + */ + function handlePointerEnter( tracker, event ) { var gPoint; - //$.console.log('pointerenter ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + //$.console.log('pointerenter ' + (tracker.userData ? tracker.userData.toString() : '')); gPoint = { id: event.pointerId, @@ -2448,7 +2446,7 @@ }; updatePointersEnter( tracker, event, [ gPoint ] ); -} + } /** @@ -2456,9 +2454,18 @@ * @inner */ function onPointerLeave( tracker, event ) { + handlePointerLeave( tracker, event ); + } + + + /** + * @private + * @inner + */ + function handlePointerLeave( tracker, event ) { var gPoint; - //$.console.log('pointerleave ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + //$.console.log('pointerleave ' + (tracker.userData ? tracker.userData.toString() : '')); gPoint = { id: event.pointerId, @@ -2479,11 +2486,14 @@ function onPointerOver( tracker, event ) { var gPoint; - $.console.log('pointerover ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + //$.console.log('pointerover ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); - //if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { - // return; - //} + // If on IE 10, simulate MSPointerEnter + if ( !$.MouseTracker.unprefixedPointerEvents && + event.currentTarget !== event.relatedTarget && + !isParentChild( event.currentTarget, event.relatedTarget ) ) { + handlePointerEnter( tracker, event ); + } gPoint = { id: event.pointerId, @@ -2504,11 +2514,14 @@ function onPointerOut( tracker, event ) { var gPoint; - $.console.log('pointerout ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + //$.console.log('pointerout ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); - //if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { - // return; - //} + // If on IE 10, simulate MSPointerLeave + if ( !$.MouseTracker.unprefixedPointerEvents && + event.currentTarget !== event.relatedTarget && + !isParentChild( event.currentTarget, event.relatedTarget ) ) { + handlePointerLeave( tracker, event ); + } gPoint = { id: event.pointerId, diff --git a/test/helpers/legacy.mouse.shim.js b/test/helpers/legacy.mouse.shim.js index e946c5f9..3a318a1f 100644 --- a/test/helpers/legacy.mouse.shim.js +++ b/test/helpers/legacy.mouse.shim.js @@ -15,9 +15,9 @@ $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" ); if ( $.Browser.vendor !== $.BROWSERS.IE || $.Browser.version > 8 ) { $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout" ); - $.MouseTracker.haveMouseOver = true; + $.MouseTracker.havePointerOverOut = true; } else { - $.MouseTracker.haveMouseOver = false; + $.MouseTracker.havePointerOverOut = false; } $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" ); if ( 'ontouchstart' in window ) { @@ -33,7 +33,5 @@ $.MouseTracker.subscribeEvents.push( "gesturestart", "gesturechange" ); } $.MouseTracker.mousePointerId = "legacy-mouse"; - $.MouseTracker.maxTouchPoints = 10; - }(OpenSeadragon)); From ef45f1df1d6063330aa878833d81c1160884926d Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Sat, 25 Jul 2020 20:08:52 -0700 Subject: [PATCH 03/29] Fixed/enhanced Viewer event object properties --- changelog.txt | 3 +++ src/viewer.js | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/changelog.txt b/changelog.txt index 12c5cf25..dfe05953 100644 --- a/changelog.txt +++ b/changelog.txt @@ -14,11 +14,14 @@ OPENSEADRAGON CHANGELOG * MouseTracker: better PointerEvent model detection - removed use of deprecated window.navigator.pointerEnabled * MouseTracker: added overHandler/outHandler options for handling corresponding pointerover/pointerout events * MouseTracker: changed enterHandler/leaveHandler to use DOM pointerenter/pointerleave events instead of simulating using pointerover/pointerout +* All internal uses of MouseTracker use pointerenter/pointerleave events instead of pointerover/pointerout events for more consistent pointer tracking * DEPRECATION: MouseTracker exitHandler deprecated for name change to leaveHandler for consistency with DOM event names * Added missing Button event object properties (were listed in documentation but not implemented) * Fixed bug in Button class where two MouseTracker event handlers used an invalid "this" causing issues in some browsers * MouseTracker: IE 10 - MSPointerEnter/MSPointerLeave didn't exist then, simulated with MSPointerOver/MSPointerOut * MouseTracker: Simulate mouseover/mouseout on IE < 9 +* Pass missing eventSource property in Viewer canvas-enter, canvas-exit, container-enter, container-exit, canvas-drag, canvas-drag-end, canvas-pinch events +* Added pointerType property to Viewer container-enter, container-exit, canvas-drag, canvas-drag-end, canvas-pinch events 2.4.2: diff --git a/src/viewer.js b/src/viewer.js index 7cb756a2..45943ba8 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2783,7 +2783,9 @@ function onCanvasDrag( event ) { var gestureSettings; var canvasDragEventArgs = { + eventSource: this, tracker: event.eventSource, + pointerType: event.pointerType, position: event.position, delta: event.delta, speed: event.speed, @@ -2801,6 +2803,7 @@ function onCanvasDrag( event ) { * @type {object} * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. + * @property {String} pointerType - "mouse", "touch", "pen", etc. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. * @property {OpenSeadragon.Point} delta - The x,y components of the difference between start drag and end drag. * @property {Number} speed - Current computed speed, in pixels per second. @@ -2880,6 +2883,7 @@ function onCanvasDragEnd( event ) { * @type {object} * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. + * @property {String} pointerType - "mouse", "touch", "pen", etc. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. * @property {Number} speed - Speed at the end of a drag gesture, in pixels per second. * @property {Number} direction - Direction at the end of a drag gesture, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0. @@ -2888,7 +2892,9 @@ function onCanvasDragEnd( event ) { * @property {?Object} userData - Arbitrary subscriber-defined object. */ this.raiseEvent('canvas-drag-end', { + eventSource: this, tracker: event.eventSource, + pointerType: event.pointerType, position: event.position, speed: event.speed, direction: event.direction, @@ -2916,6 +2922,7 @@ function onCanvasEnter( event ) { * @property {?Object} userData - Arbitrary subscriber-defined object. */ this.raiseEvent( 'canvas-enter', { + eventSource: this, tracker: event.eventSource, pointerType: event.pointerType, position: event.position, @@ -2951,6 +2958,7 @@ function onCanvasLeave( event ) { * @property {?Object} userData - Arbitrary subscriber-defined object. */ this.raiseEvent( 'canvas-exit', { + eventSource: this, tracker: event.eventSource, pointerType: event.pointerType, position: event.position, @@ -3111,6 +3119,7 @@ function onCanvasPinch( event ) { * @type {object} * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. + * @property {String} pointerType - "mouse", "touch", "pen", etc. * @property {Array.} gesturePoints - Gesture points associated with the gesture. Velocity data can be found here. * @property {OpenSeadragon.Point} lastCenter - The previous center point of the two pinch contact points relative to the tracked element. * @property {OpenSeadragon.Point} center - The center point of the two pinch contact points relative to the tracked element. @@ -3121,7 +3130,9 @@ function onCanvasPinch( event ) { * @property {?Object} userData - Arbitrary subscriber-defined object. */ this.raiseEvent('canvas-pinch', { + eventSource: this, tracker: event.eventSource, + pointerType: event.pointerType, gesturePoints: event.gesturePoints, lastCenter: event.lastCenter, center: event.center, @@ -3210,6 +3221,7 @@ function onContainerEnter( event ) { * @type {object} * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. + * @property {String} pointerType - "mouse", "touch", "pen", etc. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. * @property {Number} pointers - Number of pointers (all types) active in the tracked element. @@ -3219,7 +3231,9 @@ function onContainerEnter( event ) { * @property {?Object} userData - Arbitrary subscriber-defined object. */ this.raiseEvent( 'container-enter', { + eventSource: this, tracker: event.eventSource, + pointerType: event.pointerType, position: event.position, buttons: event.buttons, pointers: event.pointers, @@ -3244,6 +3258,7 @@ function onContainerLeave( event ) { * @type {object} * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. + * @property {String} pointerType - "mouse", "touch", "pen", etc. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. * @property {Number} pointers - Number of pointers (all types) active in the tracked element. @@ -3253,7 +3268,9 @@ function onContainerLeave( event ) { * @property {?Object} userData - Arbitrary subscriber-defined object. */ this.raiseEvent( 'container-exit', { + eventSource: this, tracker: event.eventSource, + pointerType: event.pointerType, position: event.position, buttons: event.buttons, pointers: event.pointers, From e47f629a9e0f3a329d7ff4ad26e878729565944d Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Tue, 28 Jul 2020 20:20:09 -0700 Subject: [PATCH 04/29] Undo unnecessary event property changes from previous commit --- changelog.txt | 4 +--- src/button.js | 54 +++++++++------------------------------------------ src/viewer.js | 7 ------- 3 files changed, 10 insertions(+), 55 deletions(-) diff --git a/changelog.txt b/changelog.txt index dfe05953..19434936 100644 --- a/changelog.txt +++ b/changelog.txt @@ -9,18 +9,16 @@ OPENSEADRAGON CHANGELOG * Miscellaneous code cleanup (#1840 @msalsbery) * Improved browser sniffing - detect EDGE and CHROMEEDGE browsers * Improved DOM event model feature detection -* Added support for options parameter on addEvent()/removeEvent (to support passive option) +* Added support for options parameter on addEvent()/removeEvent (to support passive option) (#1669) * Added OpenSeadragon.eventIsCanceled() function for defaultPrevented detection on DOM events * MouseTracker: better PointerEvent model detection - removed use of deprecated window.navigator.pointerEnabled * MouseTracker: added overHandler/outHandler options for handling corresponding pointerover/pointerout events * MouseTracker: changed enterHandler/leaveHandler to use DOM pointerenter/pointerleave events instead of simulating using pointerover/pointerout * All internal uses of MouseTracker use pointerenter/pointerleave events instead of pointerover/pointerout events for more consistent pointer tracking * DEPRECATION: MouseTracker exitHandler deprecated for name change to leaveHandler for consistency with DOM event names -* Added missing Button event object properties (were listed in documentation but not implemented) * Fixed bug in Button class where two MouseTracker event handlers used an invalid "this" causing issues in some browsers * MouseTracker: IE 10 - MSPointerEnter/MSPointerLeave didn't exist then, simulated with MSPointerOver/MSPointerOut * MouseTracker: Simulate mouseover/mouseout on IE < 9 -* Pass missing eventSource property in Viewer canvas-enter, canvas-exit, container-enter, container-exit, canvas-drag, canvas-drag-end, canvas-pinch events * Added pointerType property to Viewer container-enter, container-exit, canvas-drag, canvas-drag-end, canvas-pinch events 2.4.2: diff --git a/src/button.js b/src/button.js index 9c3f2db0..e9663334 100644 --- a/src/button.js +++ b/src/button.js @@ -223,11 +223,7 @@ $.Button = function( options ) { * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - _this.raiseEvent( "enter", { - eventSource: _this, - originalEvent: event.originalEvent, - userData: _this.userData - } ); + _this.raiseEvent( "enter", { originalEvent: event.originalEvent } ); } else if ( !event.buttonDownAny ) { inTo( _this, $.ButtonState.HOVER ); } @@ -245,11 +241,7 @@ $.Button = function( options ) { * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - _this.raiseEvent( "focus", { - eventSource: _this, - originalEvent: event.originalEvent, - userData: _this.userData - } ); + _this.raiseEvent( "focus", { originalEvent: event.originalEvent } ); }, leaveHandler: function( event ) { @@ -265,11 +257,7 @@ $.Button = function( options ) { * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - _this.raiseEvent( "exit", { - eventSource: _this, - originalEvent: event.originalEvent, - userData: _this.userData - } ); + _this.raiseEvent( "exit", { originalEvent: event.originalEvent } ); } }, @@ -285,11 +273,7 @@ $.Button = function( options ) { * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - _this.raiseEvent( "blur", { - eventSource: _this, - originalEvent: event.originalEvent, - userData: _this.userData - } ); + _this.raiseEvent( "blur", { originalEvent: event.originalEvent } ); }, pressHandler: function ( event ) { @@ -304,11 +288,7 @@ $.Button = function( options ) { * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - _this.raiseEvent( "press", { - eventSource: _this, - originalEvent: event.originalEvent, - userData: _this.userData - } ); + _this.raiseEvent( "press", { originalEvent: event.originalEvent } ); }, releaseHandler: function( event ) { @@ -324,11 +304,7 @@ $.Button = function( options ) { * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - _this.raiseEvent( "release", { - eventSource: _this, - originalEvent: event.originalEvent, - userData: _this.userData - } ); + _this.raiseEvent( "release", { originalEvent: event.originalEvent } ); } else if ( event.insideElementPressed ) { outTo( _this, $.ButtonState.GROUP ); } else { @@ -348,11 +324,7 @@ $.Button = function( options ) { * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - _this.raiseEvent("click", { - eventSource: _this, - originalEvent: event.originalEvent, - userData: _this.userData - }); + _this.raiseEvent("click", { originalEvent: event.originalEvent }); } }, @@ -369,11 +341,7 @@ $.Button = function( options ) { * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - _this.raiseEvent( "click", { - eventSource: _this, - originalEvent: event.originalEvent, - userData: _this.userData - } ); + _this.raiseEvent( "click", { originalEvent: event.originalEvent } ); /*** * Raised when the mouse button is released or touch ends in the Button element. * @@ -384,11 +352,7 @@ $.Button = function( options ) { * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - _this.raiseEvent( "release", { - eventSource: _this, - originalEvent: event.originalEvent, - userData: _this.userData - } ); + _this.raiseEvent( "release", { originalEvent: event.originalEvent } ); return false; } return true; diff --git a/src/viewer.js b/src/viewer.js index 45943ba8..6b58bfa5 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2783,7 +2783,6 @@ function onCanvasDrag( event ) { var gestureSettings; var canvasDragEventArgs = { - eventSource: this, tracker: event.eventSource, pointerType: event.pointerType, position: event.position, @@ -2892,7 +2891,6 @@ function onCanvasDragEnd( event ) { * @property {?Object} userData - Arbitrary subscriber-defined object. */ this.raiseEvent('canvas-drag-end', { - eventSource: this, tracker: event.eventSource, pointerType: event.pointerType, position: event.position, @@ -2922,7 +2920,6 @@ function onCanvasEnter( event ) { * @property {?Object} userData - Arbitrary subscriber-defined object. */ this.raiseEvent( 'canvas-enter', { - eventSource: this, tracker: event.eventSource, pointerType: event.pointerType, position: event.position, @@ -2958,7 +2955,6 @@ function onCanvasLeave( event ) { * @property {?Object} userData - Arbitrary subscriber-defined object. */ this.raiseEvent( 'canvas-exit', { - eventSource: this, tracker: event.eventSource, pointerType: event.pointerType, position: event.position, @@ -3130,7 +3126,6 @@ function onCanvasPinch( event ) { * @property {?Object} userData - Arbitrary subscriber-defined object. */ this.raiseEvent('canvas-pinch', { - eventSource: this, tracker: event.eventSource, pointerType: event.pointerType, gesturePoints: event.gesturePoints, @@ -3231,7 +3226,6 @@ function onContainerEnter( event ) { * @property {?Object} userData - Arbitrary subscriber-defined object. */ this.raiseEvent( 'container-enter', { - eventSource: this, tracker: event.eventSource, pointerType: event.pointerType, position: event.position, @@ -3268,7 +3262,6 @@ function onContainerLeave( event ) { * @property {?Object} userData - Arbitrary subscriber-defined object. */ this.raiseEvent( 'container-exit', { - eventSource: this, tracker: event.eventSource, pointerType: event.pointerType, position: event.position, From e7a62c2717b7f64e8d760f9cf141cad81a0524e0 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Thu, 30 Jul 2020 21:08:02 -0700 Subject: [PATCH 05/29] Enhanced pointer capture implementation, IE bug workarounds --- changelog.txt | 2 + src/mousetracker.js | 559 ++++++++++++++++++++++++++++++-------------- src/viewer.js | 8 +- 3 files changed, 390 insertions(+), 179 deletions(-) diff --git a/changelog.txt b/changelog.txt index 19434936..79025147 100644 --- a/changelog.txt +++ b/changelog.txt @@ -20,6 +20,8 @@ OPENSEADRAGON CHANGELOG * MouseTracker: IE 10 - MSPointerEnter/MSPointerLeave didn't exist then, simulated with MSPointerOver/MSPointerOut * MouseTracker: Simulate mouseover/mouseout on IE < 9 * Added pointerType property to Viewer container-enter, container-exit, canvas-drag, canvas-drag-end, canvas-pinch events +* MouseTracker: Fire dragEndHandler event even if release point same as initial contact point (#1459) +* MouseTracker: Pointer capture implemented with capture APIs where available. Only fallback to emulated capture on extremely old browsers 2.4.2: diff --git a/src/mousetracker.js b/src/mousetracker.js index af42e9e3..cc2c3cd5 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -216,6 +216,7 @@ DOMMouseScroll: function ( event ) { onMouseWheel( _this, event ); }, MozMousePixelScroll: function ( event ) { onMouseWheel( _this, event ); }, + losecapture: function ( event ) { onLoseCapture( _this, event ); }, mouseenter: function ( event ) { onMouseEnter( _this, event ); }, mouseleave: function ( event ) { onMouseLeave( _this, event ); }, mouseover: function ( event ) { onMouseOver( _this, event ); }, // IE9+ only @@ -236,6 +237,10 @@ gesturestart: function ( event ) { onGestureStart( _this, event ); }, gesturechange: function ( event ) { onGestureChange( _this, event ); }, + gotpointercapture: function ( event ) { onGotPointerCapture( _this, event ); }, + MSGotPointerCapture: function ( event ) { onGotPointerCapture( _this, event ); }, + lostpointercapture: function ( event ) { onLostPointerCapture( _this, event ); }, + MSLostPointerCapture: function ( event ) { onLostPointerCapture( _this, event ); }, pointerenter: function ( event ) { onPointerEnter( _this, event ); }, pointerleave: function ( event ) { onPointerLeave( _this, event ); }, pointerover: function ( event ) { onPointerOver( _this, event ); }, @@ -989,20 +994,50 @@ }; /** - * Resets all active mousetrakers. (Added to patch issue #697 "Mouse up outside map will cause "canvas-drag" event to stick") - * + * True if inside an iframe, otherwise false. + * @member {Boolean} isInIframe * @private - * @member resetAllMouseTrackers - * @memberof OpenSeadragon.MouseTracker + * @inner */ - $.MouseTracker.resetAllMouseTrackers = function(){ - for(var i = 0; i < MOUSETRACKERS.length; i++){ - if (MOUSETRACKERS[i].isTracking()){ - MOUSETRACKERS[i].setTracking(false); - MOUSETRACKERS[i].setTracking(true); - } + var isInIframe = (function() { + try { + return window.self !== window.top; + } catch (e) { + return true; } - }; + })(); + + /** + * @function + * @private + * @inner + * @returns {Boolean} True if the target supports DOM Level 2 event subscription methods, otherwise false. + */ + function canAccessEvents (target) { + try { + return target.addEventListener && target.removeEventListener; + } catch (e) { + return false; + } + } + + //TODO Revisit this if there's still an issue. The PointerEvent model should have no problems + // like the issue this code attempts to fix. + // /** + // * Resets all active mousetrakers. (Added to patch issue #697 "Mouse up outside map will cause "canvas-drag" event to stick") + // * + // * @private + // * @member resetAllMouseTrackers + // * @memberof OpenSeadragon.MouseTracker + // */ + // $.MouseTracker.resetAllMouseTrackers = function(){ + // for(var i = 0; i < MOUSETRACKERS.length; i++){ + // if (MOUSETRACKERS[i].isTracking()){ + // MOUSETRACKERS[i].setTracking(false); + // MOUSETRACKERS[i].setTracking(true); + // } + // } + // }; /** * Provides continuous computation of velocity (speed and direction) of active pointers. @@ -1130,6 +1165,9 @@ var divElement = document.createElement( 'div' ); return $.isFunction( divElement.setPointerCapture ) && $.isFunction( divElement.releasePointerCapture ); }()); + if ( $.MouseTracker.havePointerCapture ) { + $.MouseTracker.subscribeEvents.push( "gotpointercapture", "lostpointercapture" ); + } } else if ( window.MSPointerEvent && window.navigator.msPointerEnabled ) { // IE10 (MSPointerEnter/MSPointerLeave simulated with MSPointerOver/MSPointerOut) $.MouseTracker.havePointerEvents = true; @@ -1141,9 +1179,13 @@ var divElement = document.createElement( 'div' ); return $.isFunction( divElement.msSetPointerCapture ) && $.isFunction( divElement.msReleasePointerCapture ); }()); + if ( $.MouseTracker.havePointerCapture ) { + $.MouseTracker.subscribeEvents.push( "MSGotPointerCapture", "MSLostPointerCapture" ); + } } else { // Legacy W3C mouse events $.MouseTracker.havePointerEvents = false; + $.MouseTracker.unprefixedPointerEvents = true; $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" ); if ( $.Browser.vendor !== $.BROWSERS.IE || $.Browser.version > 8 ) { $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout" ); @@ -1153,11 +1195,14 @@ } $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" ); $.MouseTracker.mousePointerId = "legacy-mouse"; - // Legacy mouse events capture support + // Legacy mouse events capture support (IE/Firefox only?) $.MouseTracker.havePointerCapture = (function () { var divElement = document.createElement( 'div' ); return $.isFunction( divElement.setCapture ) && $.isFunction( divElement.releaseCapture ); }()); + if ( $.MouseTracker.havePointerCapture ) { + $.MouseTracker.subscribeEvents.push( "losecapture" ); + } // Legacy touch events if ( 'ontouchstart' in window ) { // iOS, Android, and other W3c Touch Event implementations @@ -1522,42 +1567,51 @@ * @private * @inner */ - function capturePointer( tracker, pointerType, pointerCount ) { - var pointsList = tracker.getActivePointersListByType( pointerType ), - eventParams; + function capturePointer( tracker, gPoint ) { + var eventParams; - pointsList.captureCount += (pointerCount || 1); - - if ( pointsList.captureCount === 1 ) { - if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { - tracker.element.setCapture( true ); - } else { - eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : pointerType ); - // We emulate mouse capture by hanging listeners on the document object. - // (Note we listen on the capture phase so the captured handlers will get called first) - // eslint-disable-next-line no-use-before-define - if (isInIframe && canAccessEvents(window.top)) { - $.addEvent( - window.top, - eventParams.upName, - eventParams.upHandler, - true - ); + if ( $.MouseTracker.havePointerCapture ) { + if ( $.MouseTracker.havePointerEvents ) { + if ( $.MouseTracker.unprefixedPointerEvents ) { + tracker.element.setPointerCapture( gPoint.id ); + $.console.log('element.setPointerCapture() called'); + } else { + tracker.element.msSetPointerCapture( gPoint.id ); + $.console.log('element.msSetPointerCapture() called'); } + } else { + tracker.element.setCapture( true ); + $.console.log('element.setCapture() called'); + } + } else { + // Emulate mouse capture by hanging listeners on the document object. + // (Note we listen on the capture phase so the captured handlers will get called first) + // eslint-disable-next-line no-use-before-define + $.console.log('Emulated mouse capture set'); + eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : gPoint.type ); + if (isInIframe && canAccessEvents(window.top)) { $.addEvent( - $.MouseTracker.captureElement, + window.top, eventParams.upName, eventParams.upHandler, true ); - $.addEvent( - $.MouseTracker.captureElement, - eventParams.moveName, - eventParams.moveHandler, - true - ); } + $.addEvent( + $.MouseTracker.captureElement, + eventParams.upName, + eventParams.upHandler, + true + ); + $.addEvent( + $.MouseTracker.captureElement, + eventParams.moveName, + eventParams.moveHandler, + true + ); } + + updatePointerCaptured( tracker, gPoint, true ); } @@ -1566,42 +1620,50 @@ * @private * @inner */ - function releasePointer( tracker, pointerType, pointerCount ) { - var pointsList = tracker.getActivePointersListByType( pointerType ), - eventParams; + function releasePointer( tracker, gPoint ) { + var eventParams; - pointsList.captureCount -= (pointerCount || 1); - - if ( pointsList.captureCount === 0 ) { - if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { - tracker.element.releaseCapture(); - } else { - eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : pointerType ); - // We emulate mouse capture by hanging listeners on the document object. - // (Note we listen on the capture phase so the captured handlers will get called first) - // eslint-disable-next-line no-use-before-define - if (isInIframe && canAccessEvents(window.top)) { - $.removeEvent( - window.top, - eventParams.upName, - eventParams.upHandler, - true - ); + if ( $.MouseTracker.havePointerCapture ) { + if ( $.MouseTracker.havePointerEvents ) { + if ( $.MouseTracker.unprefixedPointerEvents ) { + tracker.element.releasePointerCapture( gPoint.id ); + $.console.log('element.releasePointerCapture() called'); + } else { + tracker.element.msReleasePointerCapture( gPoint.id ); + $.console.log('element.msReleasePointerCapture() called'); } + } else { + tracker.element.releaseCapture(); + $.console.log('element.releaseCapture() called'); + } + } else { + // Emulate mouse capture by hanging listeners on the document object. + // (Note we listen on the capture phase so the captured handlers will get called first) + $.console.log('Emulated mouse capture release'); + eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : gPoint.type ); + if (isInIframe && canAccessEvents(window.top)) { $.removeEvent( - $.MouseTracker.captureElement, - eventParams.moveName, - eventParams.moveHandler, - true - ); - $.removeEvent( - $.MouseTracker.captureElement, + window.top, eventParams.upName, eventParams.upHandler, true ); } + $.removeEvent( + $.MouseTracker.captureElement, + eventParams.moveName, + eventParams.moveHandler, + true + ); + $.removeEvent( + $.MouseTracker.captureElement, + eventParams.upName, + eventParams.upHandler, + true + ); } + + updatePointerCaptured( tracker, gPoint, false ); } @@ -1612,9 +1674,12 @@ * @inner */ function getPointerType( event ) { + // Note: IE pointer events bug - sends invalid pointerType on lostpointercapture events + // and possibly other events. We rely on sane, valid property values in DOM events, so for + // IE, when the pointerType is missing, we'll default to 'mouse'...should be right most of the time var pointerTypeStr; if ( $.MouseTracker.unprefixedPointerEvents ) { - pointerTypeStr = event.pointerType; + pointerTypeStr = event.pointerType || (( $.Browser.vendor === $.BROWSERS.IE ) ? 'mouse' : ''); } else { // IE10 // MSPOINTER_TYPE_TOUCH: 0x00000002 @@ -1632,7 +1697,7 @@ pointerTypeStr = 'mouse'; break; default: - pointerTypeStr = ''; + pointerTypeStr = 'mouse'; } } return pointerTypeStr; @@ -1934,6 +1999,24 @@ } + /** + * TODO Never actually seen this event fired, documentation is tough to find + * @private + * @inner + */ + function onLoseCapture( tracker, event ) { + $.console.log('losecapture ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + event = $.getEvent( event ); + + var gPoint = { + id: $.MouseTracker.mousePointerId, + type: 'mouse' + }; + + updatePointerCaptured( tracker, gPoint, false ); + } + + /** * @private * @inner @@ -2072,6 +2155,8 @@ event = $.getEvent( event ); + //$.console.log('onMouseDown ' + (tracker.userData ? tracker.userData.toString() : '')); + gPoint = { id: $.MouseTracker.mousePointerId, type: 'mouse', @@ -2080,14 +2165,16 @@ currentTime: $.now() }; - if ( updatePointersDown( tracker, event, [ gPoint ], getStandardizedButton( event.button ) ) ) { - $.stopEvent( event ); - capturePointer( tracker, 'mouse' ); - } + $.cancelEvent( event ); + $.stopEvent( event ); + // if ( updatePointersDown( tracker, event, [ gPoint ], getStandardizedButton( event.button ) ) ) { + // $.stopEvent( event ); + capturePointer( tracker, gPoint ); + // } - if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler ) { - $.cancelEvent( event ); - } + // if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler ) { + // $.cancelEvent( event ); + // } } @@ -2121,6 +2208,8 @@ event = $.getEvent( event ); + //$.console.log('onMouseUp ' + (tracker.userData ? tracker.userData.toString() : '')); + gPoint = { id: $.MouseTracker.mousePointerId, type: 'mouse', @@ -2129,9 +2218,11 @@ currentTime: $.now() }; - if ( updatePointersUp( tracker, event, [ gPoint ], getStandardizedButton( event.button ) ) ) { - releasePointer( tracker, 'mouse' ); - } + $.cancelEvent( event ); + $.stopEvent( event ); + // if ( updatePointersUp( tracker, event, [ gPoint ], getStandardizedButton( event.button ) ) ) { + releasePointer( tracker, gPoint ); + // } } @@ -2140,7 +2231,9 @@ * @inner */ function onMouseMove( tracker, event ) { - handleMouseMove( tracker, event ); + // handleMouseMove( tracker, event ); + $.cancelEvent( event ); + $.stopEvent( event ); } @@ -2178,32 +2271,34 @@ } - /** - * @private - * @inner - */ - function abortContacts( tracker, event, pointsList ) { - var i, - gPointCount = pointsList.getLength(), - abortGPoints = []; + //TODO Revisit this if there's still an issue. The PointerEvent model should have no problems + // like the issue this code attempts to fix. + // /** + // * @private + // * @inner + // */ + // function abortContacts( tracker, event, pointsList ) { + // var i, + // gPointCount = pointsList.getLength(), + // abortGPoints = []; - // Check contact count for hoverable pointer types before aborting - if (pointsList.type === 'touch' || pointsList.contacts > 0) { - for ( i = 0; i < gPointCount; i++ ) { - abortGPoints.push( pointsList.getByIndex( i ) ); - } + // // Check contact count for hoverable pointer types before aborting + // if (pointsList.type === 'touch' || pointsList.contacts > 0) { + // for ( i = 0; i < gPointCount; i++ ) { + // abortGPoints.push( pointsList.getByIndex( i ) ); + // } - if ( abortGPoints.length > 0 ) { - // simulate touchend/mouseup - updatePointersUp( tracker, event, abortGPoints, 0 ); // 0 means primary button press/release or touch contact - // release pointer capture - pointsList.captureCount = 1; - releasePointer( tracker, pointsList.type ); - // simulate touchleave/mouseout - updatePointersLeave( tracker, event, abortGPoints ); - } - } - } + // if ( abortGPoints.length > 0 ) { + // // simulate touchend/mouseup + // updatePointersUp( tracker, event, abortGPoints, 0 ); // 0 means primary button press/release or touch contact + // // release pointer capture + // pointsList.captureCount = 1; + // //releasePointer( tracker, pointsList.type ); + // // simulate touchleave/mouseout + // updatePointersLeave( tracker, event, abortGPoints ); + // } + // } + // } /** @@ -2221,9 +2316,16 @@ time = $.now(); + //$.console.log('onTouchStart ' + (tracker.userData ? tracker.userData.toString() : '')); + + //TODO Revisit this if there's still an issue. The PointerEvent model should have no problems + // like the issue this code attempts to fix. + // if ( pointsList.getLength() > event.touches.length - touchCount ) { + // $.console.warn('Tracked touch contact count doesn\'t match event.touches.length. Removing all tracked touch pointers.'); + // abortContacts( tracker, event, pointsList ); + // } if ( pointsList.getLength() > event.touches.length - touchCount ) { - $.console.warn('Tracked touch contact count doesn\'t match event.touches.length. Removing all tracked touch pointers.'); - abortContacts( tracker, event, pointsList ); + $.console.warn('Tracked touch contact count doesn\'t match event.touches.length'); } for ( i = 0; i < touchCount; i++ ) { @@ -2256,12 +2358,17 @@ } } - if ( updatePointersDown( tracker, event, gPoints, 0 ) ) { // 0 means primary button press/release or touch contact - $.stopEvent( event ); - capturePointer( tracker, 'touch', touchCount ); + $.cancelEvent( event ); + $.stopEvent( event ); + // if ( updatePointersDown( tracker, event, gPoints, 0 ) ) { // 0 means primary button press/release or touch contact + // $.stopEvent( event ); + // } + + for ( i = 0; i < touchCount; i++ ) { + updatePointerCaptured( tracker, gPoints[ i ], true ); } - $.cancelEvent( event ); + // $.cancelEvent( event ); } @@ -2301,6 +2408,8 @@ time = $.now(); + //$.console.log('onTouchEnd ' + (tracker.userData ? tracker.userData.toString() : '')); + for ( i = 0; i < touchCount; i++ ) { gPoints.push( { id: event.changedTouches[ i ].identifier, @@ -2311,8 +2420,13 @@ } ); } - if ( updatePointersUp( tracker, event, gPoints, 0 ) ) { - releasePointer( tracker, 'touch', touchCount ); + $.cancelEvent( event ); + $.stopEvent( event ); + // //if ( updatePointersUp( tracker, event, gPoints, 0 ) ) {} + // updatePointersUp( tracker, event, gPoints, 0 ); + + for ( i = 0; i < touchCount; i++ ) { + updatePointerCaptured( tracker, gPoints[ i ], false ); } // simulate touchleave on our tracked element @@ -2335,7 +2449,7 @@ } } - $.cancelEvent( event ); + // $.cancelEvent( event ); } @@ -2344,7 +2458,9 @@ * @inner */ function onTouchMove( tracker, event ) { - handleTouchMove( tracker, event ); + // handleTouchMove( tracker, event ); + $.cancelEvent( event ); + $.stopEvent( event ); } @@ -2391,9 +2507,12 @@ * @inner */ function onTouchCancel( tracker, event ) { - var pointsList = tracker.getActivePointersListByType('touch'); + //var pointsList = tracker.getActivePointersListByType('touch'); - abortContacts( tracker, event, pointsList ); + $.console.log('onTouchCancel ' + (tracker.userData ? tracker.userData.toString() : '')); + + //TODO handle only the canceled touches + //abortContacts( tracker, event, pointsList ); } @@ -2419,6 +2538,44 @@ } + /** + * @private + * @inner + */ + function onGotPointerCapture( tracker, event ) { + $.console.log('gotpointercapture ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + + if ( event.target === tracker.element ) { + //$.console.log('gotpointercapture ' + (tracker.userData ? tracker.userData.toString() : '')); + ////$.cancelEvent( event ); not cancelable! + //$.stopEvent( event ); + updatePointerCaptured( tracker, { + id: event.pointerId, + type: getPointerType( event ) + }, true ); + } + } + + + /** + * @private + * @inner + */ + function onLostPointerCapture( tracker, event ) { + $.console.log('lostpointercapture ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + + if ( event.target === tracker.element ) { + //$.console.log('lostpointercapture ' + (tracker.userData ? tracker.userData.toString() : '')); + ////$.cancelEvent( event ); not cancelable! + //$.stopEvent( event ); + updatePointerCaptured( tracker, { + id: event.pointerId, + type: getPointerType( event ) + }, false ); + } + } + + /** * @private * @inner @@ -2541,6 +2698,18 @@ */ function onPointerDown( tracker, event ) { var gPoint; + // var shouldCapture; + + //$.console.log('onPointerDown ' + (tracker.userData ? tracker.userData.toString() : '')); + + // Most browsers implicitly capture touch pointer events...handle that appropriately + // Note IE10 with its prefixed PointerEvent model...no IE versions have + // element.hasPointerCapture() so no implicit pointer capture + var implicitlyCaptured = (tracker.element.hasPointerCapture && $.MouseTracker.unprefixedPointerEvents) ? + tracker.element.hasPointerCapture(event.pointerId) : false; + if (implicitlyCaptured) { + $.console.log('implicitlyCaptured ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + } gPoint = { id: event.pointerId, @@ -2550,14 +2719,31 @@ currentTime: $.now() }; - if ( updatePointersDown( tracker, event, [ gPoint ], event.button ) ) { - $.stopEvent( event ); - capturePointer( tracker, gPoint.type ); + $.cancelEvent( event ); + $.stopEvent( event ); + if (!implicitlyCaptured) { + capturePointer( tracker, gPoint ); } - if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) { - $.cancelEvent( event ); - } + // shouldCapture = updatePointersDown( tracker, event, [ gPoint ], event.button ); + + // if ( shouldCapture ) { + // $.cancelEvent( event ); + // } + + // if ( shouldCapture && !implicitlyCaptured ) { + // $.stopEvent( event ); + // capturePointer( tracker, gPoint ); + // } else if ( !shouldCapture && implicitlyCaptured ) { + // $.stopEvent( event ); + // releasePointer( tracker, gPoint ); //TODO should we do this? Investigate when implementing bubble handling + // } else if ( shouldCapture && implicitlyCaptured ) { + // $.stopEvent( event ); + // } + + // if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) { + // $.cancelEvent( event ); + // } } @@ -2593,6 +2779,8 @@ function handlePointerUp( tracker, event ) { var gPoint; + //$.console.log('onPointerUp ' + (tracker.userData ? tracker.userData.toString() : '')); + gPoint = { id: event.pointerId, type: getPointerType( event ), @@ -2601,9 +2789,11 @@ currentTime: $.now() }; - if ( updatePointersUp( tracker, event, [ gPoint ], event.button ) ) { - releasePointer( tracker, gPoint.type ); - } + $.cancelEvent( event ); + $.stopEvent( event ); + //if ( updatePointersUp( tracker, event, [ gPoint ], event.button ) ) { + releasePointer( tracker, gPoint ); + //} } @@ -2612,7 +2802,9 @@ * @inner */ function onPointerMove( tracker, event ) { - handlePointerMove( tracker, event ); + // handlePointerMove( tracker, event ); + $.cancelEvent( event ); + $.stopEvent( event ); } @@ -2657,6 +2849,7 @@ * @inner */ function onPointerCancel( tracker, event ) { + $.console.log('pointercancel ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.isPrimary ? 'isPrimary' : '')); var gPoint; gPoint = { @@ -2772,7 +2965,7 @@ curGPoint = updateGPoint; } else { // Initialize for tracking and add to the tracking list - curGPoint.captured = false; + curGPoint.captured = false; // Handled by updatePointerCaptured() curGPoint.insideElementPressed = false; curGPoint.insideElement = true; startTrackingPointer( pointsList, curGPoint ); @@ -2894,7 +3087,7 @@ if ( updateGPoint ) { curGPoint = updateGPoint; } else { - //curGPoint.captured = false; // Tracked by updatePointersEnter + //curGPoint.captured = false; // Handled by updatePointerCaptured() curGPoint.insideElementPressed = false; //curGPoint.insideElement = true; // Tracked by updatePointersEnter } @@ -3047,13 +3240,15 @@ } } - // Some pointers may steal control from another pointer without firing the appropriate release events - // e.g. Touching a screen while click-dragging with certain mice. - var otherPointsLists = tracker.getActivePointersListsExceptType(gPoints[ 0 ].type); - for (i = 0; i < otherPointsLists.length; i++) { - //If another pointer has contact, simulate the release - abortContacts(tracker, event, otherPointsLists[i]); // No-op if no active pointer - } + //TODO Revisit this if there's still an issue. The PointerEvent model should have no problems + // like the issue this code attempts to fix. + // // Some pointers may steal control from another pointer without firing the appropriate release events + // // e.g. Touching a screen while click-dragging with certain mice. + // var otherPointsLists = tracker.getActivePointersListsExceptType(gPoints[ 0 ].type); + // for (i = 0; i < otherPointsLists.length; i++) { + // //If another pointer has contact, simulate the release + // abortContacts(tracker, event, otherPointsLists[i]); // No-op if no active pointer + // } // Only capture and track primary button, pen, and touch contacts if ( buttonChanged !== 0 ) { @@ -3086,7 +3281,7 @@ if ( updateGPoint ) { // Already tracking the pointer...update it - updateGPoint.captured = true; + //updateGPoint.captured = true; // Handled by updatePointerCaptured() updateGPoint.insideElementPressed = true; updateGPoint.insideElement = true; updateGPoint.contactPos = curGPoint.currentPos; @@ -3099,7 +3294,7 @@ curGPoint = updateGPoint; } else { // Initialize for tracking and add to the tracking list (no pointerover or pointermove event occurred before this) - curGPoint.captured = true; + curGPoint.captured = false; // Handled by updatePointerCaptured() curGPoint.insideElementPressed = true; curGPoint.insideElement = true; startTrackingPointer( pointsList, curGPoint ); @@ -3244,20 +3439,25 @@ } } - // A primary mouse button may have been released while the non-primary button was down - var otherPointsList = tracker.getActivePointersListByType("mouse"); - // Stop tracking the mouse; see https://github.com/openseadragon/openseadragon/pull/1223 - abortContacts(tracker, event, otherPointsList); // No-op if no active pointer + //TODO Revisit this if there's still an issue. The PointerEvent model should have no problems + // like the issue this code attempts to fix. + // // A primary mouse button may have been released while the non-primary button was down + // var otherPointsList = tracker.getActivePointersListByType("mouse"); + // // Stop tracking the mouse; see https://github.com/openseadragon/openseadragon/pull/1223 + // abortContacts(tracker, event, otherPointsList); // No-op if no active pointer return false; } - // OS-specific gestures (e.g. swipe up with four fingers in iPadOS 13) - if (typeof gPoints[ 0 ].currentPos === "undefined") { - abortContacts(tracker, event, pointsList); + //TODO Revisit this if there's still an issue. The PointerEvent model should have no problems + // like the issue this code attempts to fix. + // // OS-specific gestures (e.g. swipe up with four fingers in iPadOS 13) + // if (typeof gPoints[ 0 ].currentPos === "undefined") { + // $.console.log('typeof gPoints[ 0 ].currentPos === "undefined" ' + (tracker.userData ? tracker.userData.toString() : '')); + // abortContacts(tracker, event, pointsList); - return false; - } + // return false; + // } for ( i = 0; i < gPointCount; i++ ) { curGPoint = gPoints[ i ]; @@ -3265,7 +3465,7 @@ if ( updateGPoint ) { // Update the pointer, stop tracking it if not still in this element if ( updateGPoint.captured ) { - updateGPoint.captured = false; + //updateGPoint.captured = false; // Handled by updatePointerCaptured() releaseCapture = true; wasCaptured = true; } @@ -3314,7 +3514,7 @@ } // Drag End - if ( tracker.dragEndHandler && !updateGPoint.currentPos.equals( updateGPoint.contactPos ) ) { + if ( tracker.dragEndHandler ) { propagate = tracker.dragEndHandler( { eventSource: tracker, @@ -3432,6 +3632,41 @@ } + /** + * Sets or resets the captured property on the tracked pointer matching the passed gPoint's id/type + * + * @function + * @private + * @inner + * @param {OpenSeadragon.MouseTracker} tracker + * A reference to the MouseTracker instance. + * @param {Object} gPoint + * An object with id and type properties describing the pointer to update. + * @param {Boolean} isCaptured + * Value to set the captured property to. + */ + function updatePointerCaptured( tracker, gPoint, isCaptured ) { + var pointsList = tracker.getActivePointersListByType( gPoint.type ); + var updateGPoint = pointsList.getById( gPoint.id ); + + if ( updateGPoint ) { + if ( isCaptured && !updateGPoint.captured ) { + updateGPoint.captured = true; + pointsList.captureCount++; + } else if ( !isCaptured && updateGPoint.captured ) { + updateGPoint.captured = false; + pointsList.captureCount--; + if ( pointsList.captureCount < 0 ) { + pointsList.captureCount = 0; + $.console.warn('updatePointerCaptured() - pointsList.captureCount went negative'); + } + } + } else { + $.console.warn('updatePointerCaptured() called on untracked pointer'); + } + } + + /** * Call when pointer(s) change coordinates, button state, pressure, tilt, or contact geometry (e.g. width and height) * @@ -3475,7 +3710,7 @@ updateGPoint.currentTime = curGPoint.currentTime; } else { // Initialize for tracking and add to the tracking list (no pointerover or pointerdown event occurred before this) - curGPoint.captured = false; + curGPoint.captured = false; // Handled by updatePointerCaptured() curGPoint.insideElementPressed = false; curGPoint.insideElement = true; startTrackingPointer( pointsList, curGPoint ); @@ -3643,32 +3878,4 @@ } } - /** - * True if inside an iframe, otherwise false. - * @member {Boolean} isInIframe - * @private - * @inner - */ - var isInIframe = (function() { - try { - return window.self !== window.top; - } catch (e) { - return true; - } - })(); - - /** - * @function - * @private - * @inner - * @returns {Boolean} True if the target supports DOM Level 2 event subscription methods, otherwise false. - */ - function canAccessEvents (target) { - try { - return target.addEventListener && target.removeEventListener; - } catch (e) { - return false; - } - } - }(OpenSeadragon)); diff --git a/src/viewer.js b/src/viewer.js index 6b58bfa5..73a2f395 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2933,9 +2933,11 @@ function onCanvasEnter( event ) { function onCanvasLeave( event ) { - if (window.location !== window.parent.location){ - $.MouseTracker.resetAllMouseTrackers(); - } + //TODO Revisit this if there's still an issue. The PointerEvent model should have no problems + // like the issue this code attempts to fix. + // if (window.location !== window.parent.location){ + // $.MouseTracker.resetAllMouseTrackers(); + // } /** * Raised when a pointer leaves the {@link OpenSeadragon.Viewer#canvas} element. From e74d83f1045e18df80c0d07a82a481fdd18f9ae9 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Wed, 12 Aug 2020 21:22:48 -0700 Subject: [PATCH 06/29] New preProcessEvent event --- src/button.js | 7 + src/drawer.js | 4 + src/mousetracker.js | 1944 ++++++++++++++++++++++++----------------- src/navigator.js | 15 +- src/openseadragon.js | 13 + src/referencestrip.js | 119 +-- src/viewer.js | 3 + 7 files changed, 1241 insertions(+), 864 deletions(-) diff --git a/src/button.js b/src/button.js index e9663334..4f7c1638 100644 --- a/src/button.js +++ b/src/button.js @@ -138,6 +138,13 @@ $.Button = function( options ) { this.imgDown.alt = this.tooltip; + // Allow pointer events to pass through the img elements so implicit + // pointer capture works on touch devices + $.setElementPointerEventsNone( this.imgRest ); + $.setElementPointerEventsNone( this.imgGroup ); + $.setElementPointerEventsNone( this.imgHover ); + $.setElementPointerEventsNone( this.imgDown ); + this.element.style.position = "relative"; $.setElementTouchActionNone( this.element ); diff --git a/src/drawer.js b/src/drawer.js index 31968224..fca4b956 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -126,6 +126,10 @@ $.Drawer = function( options ) { this.canvas.style.height = "100%"; this.canvas.style.position = "absolute"; $.setElementOpacity( this.canvas, this.opacity, true ); + // Allow pointer events to pass through the canvas element so implicit + // pointer capture works on touch devices + $.setElementPointerEventsNone( this.canvas ); + $.setElementTouchActionNone( this.canvas ); // explicit left-align this.container.style.textAlign = "left"; diff --git a/src/mousetracker.js b/src/mousetracker.js index cc2c3cd5..33a6e880 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -72,6 +72,8 @@ * @param {Number} [options.stopDelay=50] * The number of milliseconds without pointer move before the stop * event is fired. + * @param {OpenSeadragon.EventHandler} [options.preProcessEventHandler=null] + * An optional handler for controlling DOM event propagation and processing. * @param {OpenSeadragon.EventHandler} [options.enterHandler=null] * An optional handler for pointer enter. * @param {OpenSeadragon.EventHandler} [options.leaveHandler=null] @@ -170,6 +172,7 @@ this.userData = options.userData || null; this.stopDelay = options.stopDelay || 50; + this.preProcessEventHandler = options.preProcessEventHandler || null; this.enterHandler = options.enterHandler || null; this.leaveHandler = options.leaveHandler || null; this.exitHandler = options.exitHandler || null; // Deprecated v2.4.3 @@ -229,13 +232,14 @@ touchstart: function ( event ) { onTouchStart( _this, event ); }, touchend: function ( event ) { onTouchEnd( _this, event ); }, - touchendcaptured: function ( event ) { onTouchEndCaptured( _this, event ); }, touchmove: function ( event ) { onTouchMove( _this, event ); }, - touchmovecaptured: function ( event ) { onTouchMoveCaptured( _this, event ); }, touchcancel: function ( event ) { onTouchCancel( _this, event ); }, - gesturestart: function ( event ) { onGestureStart( _this, event ); }, - gesturechange: function ( event ) { onGestureChange( _this, event ); }, + gesturestart: function ( event ) { onGestureStart( _this, event ); }, // Safari/Safari iOS + gesturechange: function ( event ) { onGestureChange( _this, event ); }, // Safari/Safari iOS + + MSGestureStart: function ( event ) { onGestureStart( _this, event ); }, // IE10 + MSGestureChange: function ( event ) { onGestureChange( _this, event ); }, // IE10 gotpointercapture: function ( event ) { onGotPointerCapture( _this, event ); }, MSGotPointerCapture: function ( event ) { onGotPointerCapture( _this, event ); }, @@ -278,6 +282,13 @@ currentPinchCenter: null }; + this.hasGestureHandlers = !!( this.pressHandler || this.nonPrimaryPressHandler || + this.releaseHandler || this.nonPrimaryReleaseHandler || + this.clickHandler || this.dblClickHandler || + this.dragHandler || this.dragEndHandler || + this.pinchHandler ); + this.hasScrollHandler = !!this.scrollHandler; + if ( !options.startDisabled ) { this.setTracking( true ); } @@ -394,6 +405,14 @@ return count; }, + /** + * Implement or assign implementation to these handlers during or after + * calling the constructor. + * @function + * @param {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo + */ + preProcessEventHandler: function () { }, + /** * Implement or assign implementation to these handlers during or after * calling the constructor. @@ -1182,6 +1201,7 @@ if ( $.MouseTracker.havePointerCapture ) { $.MouseTracker.subscribeEvents.push( "MSGotPointerCapture", "MSLostPointerCapture" ); } + $.MouseTracker.subscribeEvents.push( "MSGestureStart", "MSGestureChange" ); } else { // Legacy W3C mouse events $.MouseTracker.havePointerEvents = false; @@ -1223,6 +1243,51 @@ // Classes and typedefs /////////////////////////////////////////////////////////////////////////////// + /** + * Used for the processing/disposition of DOM events (propagation, default handling, capture, etc.) + * + * @typedef {Object} EventProcessInfo + * @memberof OpenSeadragon.MouseTracker + * + * @property {OpenSeadragon.MouseTracker} eventSource + * A reference to the tracker instance. + * @property {Object} originalEvent + * The original DOM event object. + * @property {Number} eventPhase + * 0 == NONE, 1 == CAPTURING_PHASE, 2 == AT_TARGET, 3 == BUBBLING_PHASE. + * @property {String} eventType + * "gotpointercapture", "lostpointercapture", "pointerenter", "pointerleave", "pointerover", "pointerout", "pointerdown", "pointerup", "pointermove", "pointercancel", "wheel". + * @property {String} pointerType + * "mouse", "touch", "pen", etc. + * @property {Boolean} isEmulated + * True if this is an emulated event. If true, originalEvent is the event that caused + * the emulated event or null if no DOM event applies. Emulated events + * can occur on eventType "pointerenter", "pointerleave", "pointerover", "pointerout". + * @property {Boolean} isStopable + * True if propagation of the event (e.g. bubbling) can be stopped with stopPropagation/stopImmediatePropagation. + * @property {Boolean} isCancelable + * True if the event's default handling by the browser can be prevented with preventDefault. + * @property {Boolean} defaultPrevented + * True if the event's default handling has already been prevented by a descendent element. + * @property {Boolean} preventDefault + * Set to true to prevent the event's default handling by the browser. + * @property {Boolean} preventGesture + * Set to true to prevent this MouseTracker from generating a gesture from the event. + * Valid on eventType "pointerdown". + * @property {Boolean} stopPropagation + * Set to true prevent the event from propagating to ancestor/descendent elements on capture/bubble phase. + * @property {Boolean} stopImmediatePropagation + * Same as stopPropagation, but also prevents any other handlers on the tracker's element for + * this event from being called. + * @property {Boolean} shouldCapture + * (Internal Use) Set to true if the pointer should be captured (events (re)targeted to tracker element). + * @property {Boolean} shouldReleaseCapture + * (Internal Use) Set to true if the captured pointer should be released. + * @property {Object} userData + * Arbitrary user-defined object. + */ + + /** * Represents a point of contact on the screen made by a mouse cursor, pen, touch, or other pointer device. * @@ -1394,6 +1459,7 @@ ++this.contacts; if (this.contacts > 1 && (this.type === "mouse" || this.type === "pen")) { + $.console.warn('GesturePointList.addContact() Implausible contacts value'); this.contacts = 1; } }, @@ -1407,6 +1473,7 @@ --this.contacts; if (this.contacts < 0) { + $.console.warn('GesturePointList.removeContact() Implausible contacts value'); this.contacts = 0; } } @@ -1574,20 +1641,20 @@ if ( $.MouseTracker.havePointerEvents ) { if ( $.MouseTracker.unprefixedPointerEvents ) { tracker.element.setPointerCapture( gPoint.id ); - $.console.log('element.setPointerCapture() called'); + //$.console.log('element.setPointerCapture() called'); } else { tracker.element.msSetPointerCapture( gPoint.id ); - $.console.log('element.msSetPointerCapture() called'); + //$.console.log('element.msSetPointerCapture() called'); } } else { tracker.element.setCapture( true ); - $.console.log('element.setCapture() called'); + //$.console.log('element.setCapture() called'); } } else { // Emulate mouse capture by hanging listeners on the document object. // (Note we listen on the capture phase so the captured handlers will get called first) // eslint-disable-next-line no-use-before-define - $.console.log('Emulated mouse capture set'); + //$.console.log('Emulated mouse capture set'); eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : gPoint.type ); if (isInIframe && canAccessEvents(window.top)) { $.addEvent( @@ -1627,19 +1694,19 @@ if ( $.MouseTracker.havePointerEvents ) { if ( $.MouseTracker.unprefixedPointerEvents ) { tracker.element.releasePointerCapture( gPoint.id ); - $.console.log('element.releasePointerCapture() called'); + //$.console.log('element.releasePointerCapture() called'); } else { tracker.element.msReleasePointerCapture( gPoint.id ); - $.console.log('element.msReleasePointerCapture() called'); + //$.console.log('element.msReleasePointerCapture() called'); } } else { tracker.element.releaseCapture(); - $.console.log('element.releaseCapture() called'); + //$.console.log('element.releaseCapture() called'); } } else { // Emulate mouse capture by hanging listeners on the document object. // (Note we listen on the capture phase so the captured handlers will get called first) - $.console.log('Emulated mouse capture release'); + //$.console.log('Emulated mouse capture release'); eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : gPoint.type ); if (isInIframe && canAccessEvents(window.top)) { $.removeEvent( @@ -1953,7 +2020,7 @@ */ function handleWheelEvent( tracker, event, originalEvent ) { var nDelta = 0, - propagate; + eventInfo; // The nDelta variable is gated to provide smooth z-index scrolling // since the mouse wheel allows for substantial deltas meant for rapid @@ -1962,8 +2029,18 @@ // TODO: Deltas in pixel mode should be accumulated then a scroll value computed after $.DEFAULT_SETTINGS.pixelsPerWheelLine threshold reached nDelta = event.deltaY < 0 ? 1 : -1; - if ( tracker.scrollHandler ) { - propagate = tracker.scrollHandler( + eventInfo = { + originalEvent: event, + eventType: 'wheel', + pointerType: 'mouse', + isEmulated: event !== originalEvent + }; + preProcessEvent( tracker, eventInfo ); + + if ( tracker.scrollHandler && !eventInfo.preventGesture && !eventInfo.defaultPrevented ) { + eventInfo.preventDefault = true; + + tracker.scrollHandler( { eventSource: tracker, pointerType: 'mouse', @@ -1976,11 +2053,15 @@ userData: tracker.userData } ); - if ( propagate === false ) { - $.cancelEvent( originalEvent ); - } } - } + + if ( eventInfo.stopPropagation ) { + $.stopEvent( originalEvent ); + } + if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { + $.cancelEvent( originalEvent ); + } +} /** @@ -2000,7 +2081,7 @@ /** - * TODO Never actually seen this event fired, documentation is tough to find + * TODO Never actually seen this event fired, and documentation is tough to find * @private * @inner */ @@ -2013,7 +2094,19 @@ type: 'mouse' }; + var eventInfo = { + originalEvent: event, + eventType: 'lostpointercapture', + pointerType: 'mouse', + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); + updatePointerCaptured( tracker, gPoint, false ); + + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); + } } @@ -2022,6 +2115,8 @@ * @inner */ function onMouseEnter( tracker, event ) { + //$.console.log('mouseenter ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + event = $.getEvent( event ); var gPoint = { @@ -2032,11 +2127,22 @@ currentTime: $.now() }; - updatePointersEnter( tracker, event, [ gPoint ] ); + // pointerenter doesn't bubble and is not cancelable, but we call + // preProcessEvent() so it's dispatched to preProcessEventHandler + // if necessary + var eventInfo = { + originalEvent: event, + eventType: 'pointerenter', + pointerType: gPoint.type, + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); + + updatePointerEnter( tracker, eventInfo, gPoint ); // Simulate mouseover on IE < 9 if ( !$.MouseTracker.havePointerOverOut ) { - handleMouseOver( tracker, event ); + handleMouseOver( tracker, event, true ); } } @@ -2046,6 +2152,8 @@ * @inner */ function onMouseLeave( tracker, event ) { + //$.console.log('mouseleave ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + event = $.getEvent( event ); var gPoint = { @@ -2056,12 +2164,23 @@ currentTime: $.now() }; - updatePointersLeave( tracker, event, [ gPoint ] ); + // pointerleave doesn't bubble and is not cancelable, but we call + // preProcessEvent() so it's dispatched to preProcessEventHandler + // if necessary + var eventInfo = { + originalEvent: event, + eventType: 'pointerleave', + pointerType: gPoint.type, + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); // Simulate mouseoout on IE < 9 if ( !$.MouseTracker.havePointerOverOut ) { - handleMouseOut( tracker, event ); + handleMouseOut( tracker, event, true ); } + + updatePointerLeave( tracker, eventInfo, gPoint ); } @@ -2072,7 +2191,9 @@ * @inner */ function onMouseOver( tracker, event ) { - handleMouseOver( tracker, event ); + //$.console.log('mouseover ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + + handleMouseOver( tracker, event, false ); } @@ -2080,7 +2201,7 @@ * @private * @inner */ - function handleMouseOver( tracker, event ) { + function handleMouseOver( tracker, event, isEmulated ) { var gPoint = { id: $.MouseTracker.mousePointerId, type: 'mouse', @@ -2089,7 +2210,24 @@ currentTime: $.now() }; - updatePointersOver( tracker, event, [ gPoint ] ); + var eventInfo = { + originalEvent: event, + eventType: 'pointerover', + pointerType: gPoint.type, + isEmulated: isEmulated + }; + preProcessEvent( tracker, eventInfo ); + + updatePointerOver( tracker, eventInfo, gPoint ); + + if ( !isEmulated ) { + if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { + $.cancelEvent( event ); + } + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); + } + } } @@ -2100,7 +2238,9 @@ * @inner */ function onMouseOut( tracker, event ) { - handleMouseOut( tracker, event ); + //$.console.log('mouseout ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + + handleMouseOut( tracker, event, false ); } @@ -2108,7 +2248,7 @@ * @private * @inner */ - function handleMouseOut( tracker, event ) { + function handleMouseOut( tracker, event, isEmulated ) { var gPoint = { id: $.MouseTracker.mousePointerId, type: 'mouse', @@ -2117,7 +2257,24 @@ currentTime: $.now() }; - updatePointersOut( tracker, event, [ gPoint ] ); + var eventInfo = { + originalEvent: event, + eventType: 'pointerout', + pointerType: gPoint.type, + isEmulated: isEmulated + }; + preProcessEvent( tracker, eventInfo ); + + updatePointerOut( tracker, eventInfo, gPoint ); + + if ( !isEmulated ) { + if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { + $.cancelEvent( event ); + } + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); + } + } } @@ -2165,16 +2322,27 @@ currentTime: $.now() }; - $.cancelEvent( event ); - $.stopEvent( event ); - // if ( updatePointersDown( tracker, event, [ gPoint ], getStandardizedButton( event.button ) ) ) { - // $.stopEvent( event ); - capturePointer( tracker, gPoint ); - // } + var eventInfo = { + originalEvent: event, + eventType: 'pointerdown', + pointerType: gPoint.type, + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); - // if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler ) { - // $.cancelEvent( event ); - // } + updatePointerDown( tracker, eventInfo, gPoint, getStandardizedButton( event.button ) ); + + if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { + $.cancelEvent( event ); + } + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); + } + if ( eventInfo.shouldCapture ) { + //$.stopEvent( event ); + //$.console.log('mousedown calling capturePointer() ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + capturePointer( tracker, gPoint ); + } } @@ -2218,11 +2386,27 @@ currentTime: $.now() }; - $.cancelEvent( event ); - $.stopEvent( event ); - // if ( updatePointersUp( tracker, event, [ gPoint ], getStandardizedButton( event.button ) ) ) { - releasePointer( tracker, gPoint ); - // } + var eventInfo = { + originalEvent: event, + eventType: 'pointerup', + pointerType: gPoint.type, + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); + + updatePointerUp( tracker, eventInfo, gPoint, getStandardizedButton( event.button ) ); + + if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { + $.cancelEvent( event ); + } + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); + } + + if ( eventInfo.shouldReleaseCapture ) { + //$.stopEvent( event ); + releasePointer( tracker, gPoint ); + } } @@ -2231,9 +2415,7 @@ * @inner */ function onMouseMove( tracker, event ) { - // handleMouseMove( tracker, event ); - $.cancelEvent( event ); - $.stopEvent( event ); + handleMouseMove( tracker, event ); } @@ -2267,7 +2449,22 @@ currentTime: $.now() }; - updatePointersMove( tracker, event, [ gPoint ] ); + var eventInfo = { + originalEvent: event, + eventType: 'pointermove', + pointerType: gPoint.type, + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); + + updatePointerMove( tracker, eventInfo, gPoint ); + + if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { + $.cancelEvent( event ); + } + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); + } } @@ -2290,12 +2487,12 @@ // if ( abortGPoints.length > 0 ) { // // simulate touchend/mouseup - // updatePointersUp( tracker, event, abortGPoints, 0 ); // 0 means primary button press/release or touch contact + // updatePointerUp( tracker, eventInfo, , 0 ); // 0 means primary button press/release or touch contact // // release pointer capture // pointsList.captureCount = 1; // //releasePointer( tracker, pointsList.type ); // // simulate touchleave/mouseout - // updatePointersLeave( tracker, event, abortGPoints ); + // updatePointerLeave( tracker, eventInfo, ); // } // } // } @@ -2308,15 +2505,13 @@ function onTouchStart( tracker, event ) { var time, i, - j, touchCount = event.changedTouches.length, - gPoints = [], - parentGPoints, + gPoint, pointsList = tracker.getActivePointersListByType( 'touch' ); time = $.now(); - //$.console.log('onTouchStart ' + (tracker.userData ? tracker.userData.toString() : '')); + //$.console.log('touchstart ' + (tracker.userData ? tracker.userData.toString() : '')); //TODO Revisit this if there's still an issue. The PointerEvent model should have no problems // like the issue this code attempts to fix. @@ -2328,47 +2523,37 @@ $.console.warn('Tracked touch contact count doesn\'t match event.touches.length'); } + var eventInfo = { + originalEvent: event, + eventType: 'pointerdown', + pointerType: 'touch', + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); + for ( i = 0; i < touchCount; i++ ) { - gPoints.push( { + gPoint = { id: event.changedTouches[ i ].identifier, type: 'touch', // isPrimary not set - let the updatePointers functions determine it currentPos: getMouseAbsolute( event.changedTouches[ i ] ), currentTime: time - } ); + }; + + // simulate touchenter on our tracked element + updatePointerEnter( tracker, eventInfo, gPoint ); + + updatePointerCaptured( tracker, gPoint, true ); + + updatePointerDown( tracker, eventInfo, gPoint, 0 ); } - // simulate touchenter on our tracked element - updatePointersEnter( tracker, event, gPoints ); - - // simulate touchenter on our tracked element's tracked ancestor elements - for ( i = 0; i < MOUSETRACKERS.length; i++ ) { - if ( MOUSETRACKERS[ i ] !== tracker && MOUSETRACKERS[ i ].isTracking() && isParentChild( MOUSETRACKERS[ i ].element, tracker.element ) ) { - parentGPoints = []; - for ( j = 0; j < touchCount; j++ ) { - parentGPoints.push( { - id: event.changedTouches[ j ].identifier, - type: 'touch', - // isPrimary not set - let the updatePointers functions determine it - currentPos: getMouseAbsolute( event.changedTouches[ j ] ), - currentTime: time - } ); - } - updatePointersEnter( MOUSETRACKERS[ i ], event, parentGPoints ); - } + if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { + $.cancelEvent( event ); } - - $.cancelEvent( event ); - $.stopEvent( event ); - // if ( updatePointersDown( tracker, event, gPoints, 0 ) ) { // 0 means primary button press/release or touch contact - // $.stopEvent( event ); - // } - - for ( i = 0; i < touchCount; i++ ) { - updatePointerCaptured( tracker, gPoints[ i ], true ); + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); } - - // $.cancelEvent( event ); } @@ -2377,79 +2562,46 @@ * @inner */ function onTouchEnd( tracker, event ) { - handleTouchEnd( tracker, event ); - } - - - /** - * This handler is attached to the window object (on the capture phase) to emulate pointer capture. - * onTouchEnd is still attached to the tracked element, so stop propagation to avoid processing twice. - * - * @private - * @inner - */ - function onTouchEndCaptured( tracker, event ) { - handleTouchEnd( tracker, event ); - $.stopEvent( event ); - } - - - /** - * @private - * @inner - */ - function handleTouchEnd( tracker, event ) { var time, i, - j, touchCount = event.changedTouches.length, - gPoints = [], - parentGPoints; + gPoint; time = $.now(); - //$.console.log('onTouchEnd ' + (tracker.userData ? tracker.userData.toString() : '')); + //$.console.log('touchend ' + (tracker.userData ? tracker.userData.toString() : '')); + + var eventInfo = { + originalEvent: event, + eventType: 'pointerup', + pointerType: 'touch', + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); for ( i = 0; i < touchCount; i++ ) { - gPoints.push( { + gPoint = { id: event.changedTouches[ i ].identifier, type: 'touch', // isPrimary not set - let the updatePointers functions determine it currentPos: getMouseAbsolute( event.changedTouches[ i ] ), currentTime: time - } ); + }; + + updatePointerUp( tracker, eventInfo, gPoint, 0 ); + + updatePointerCaptured( tracker, gPoint, false ); + + // simulate touchleave on our tracked element + updatePointerLeave( tracker, eventInfo, gPoint ); } - $.cancelEvent( event ); - $.stopEvent( event ); - // //if ( updatePointersUp( tracker, event, gPoints, 0 ) ) {} - // updatePointersUp( tracker, event, gPoints, 0 ); - - for ( i = 0; i < touchCount; i++ ) { - updatePointerCaptured( tracker, gPoints[ i ], false ); + if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { + $.cancelEvent( event ); } - - // simulate touchleave on our tracked element - updatePointersLeave( tracker, event, gPoints ); - - // simulate touchleave on our tracked element's tracked ancestor elements - for ( i = 0; i < MOUSETRACKERS.length; i++ ) { - if ( MOUSETRACKERS[ i ] !== tracker && MOUSETRACKERS[ i ].isTracking() && isParentChild( MOUSETRACKERS[ i ].element, tracker.element ) ) { - parentGPoints = []; - for ( j = 0; j < touchCount; j++ ) { - parentGPoints.push( { - id: event.changedTouches[ j ].identifier, - type: 'touch', - // isPrimary not set - let the updatePointers functions determine it - currentPos: getMouseAbsolute( event.changedTouches[ j ] ), - currentTime: time - } ); - } - updatePointersLeave( MOUSETRACKERS[ i ], event, parentGPoints ); - } + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); } - - // $.cancelEvent( event ); } @@ -2458,47 +2610,39 @@ * @inner */ function onTouchMove( tracker, event ) { - // handleTouchMove( tracker, event ); - $.cancelEvent( event ); - $.stopEvent( event ); - } - - - /** - * This handler is attached to the window object (on the capture phase) to emulate pointer capture. - * onTouchMove is still attached to the tracked element, so stop propagation to avoid processing twice. - * - * @private - * @inner - */ - function onTouchMoveCaptured( tracker, event ) { - handleTouchMove( tracker, event ); - $.stopEvent( event ); - } - - - /** - * @private - * @inner - */ - function handleTouchMove( tracker, event ) { - var i, + var time, + i, touchCount = event.changedTouches.length, - gPoints = []; + gPoint; + + time = $.now(); + + var eventInfo = { + originalEvent: event, + eventType: 'pointermove', + pointerType: 'touch', + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); for ( i = 0; i < touchCount; i++ ) { - gPoints.push( { + gPoint = { id: event.changedTouches[ i ].identifier, type: 'touch', // isPrimary not set - let the updatePointers functions determine it currentPos: getMouseAbsolute( event.changedTouches[ i ] ), - currentTime: $.now() - } ); + currentTime: time + }; + + updatePointerMove( tracker, eventInfo, gPoint ); } - updatePointersMove( tracker, event, gPoints ); - - $.cancelEvent( event ); + if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { + $.cancelEvent( event ); + } + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); + } } @@ -2507,12 +2651,33 @@ * @inner */ function onTouchCancel( tracker, event ) { - //var pointsList = tracker.getActivePointersListByType('touch'); + var touchCount = event.changedTouches.length, + i, + gPoint; - $.console.log('onTouchCancel ' + (tracker.userData ? tracker.userData.toString() : '')); + //$.console.log('touchcancel ' + (tracker.userData ? tracker.userData.toString() : '')); - //TODO handle only the canceled touches - //abortContacts( tracker, event, pointsList ); + var eventInfo = { + originalEvent: event, + eventType: 'pointercancel', + pointerType: 'touch', + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); + + for ( i = 0; i < touchCount; i++ ) { + gPoint = { + id: event.changedTouches[ i ].identifier, + type: 'touch' + }; + + //TODO need to only do this if our element is target? + updatePointerCancel( tracker, eventInfo, gPoint ); + } + + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); + } } @@ -2521,8 +2686,9 @@ * @inner */ function onGestureStart( tracker, event ) { - event.stopPropagation(); - event.preventDefault(); + if ( !$.eventIsCanceled( event ) ) { + event.preventDefault(); + } return false; } @@ -2532,8 +2698,9 @@ * @inner */ function onGestureChange( tracker, event ) { - event.stopPropagation(); - event.preventDefault(); + if ( !$.eventIsCanceled( event ) ) { + event.preventDefault(); + } return false; } @@ -2543,7 +2710,15 @@ * @inner */ function onGotPointerCapture( tracker, event ) { - $.console.log('gotpointercapture ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + //$.console.log('gotpointercapture ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + + var eventInfo = { + originalEvent: event, + eventType: 'gotpointercapture', + pointerType: getPointerType( event ), + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); if ( event.target === tracker.element ) { //$.console.log('gotpointercapture ' + (tracker.userData ? tracker.userData.toString() : '')); @@ -2554,6 +2729,10 @@ type: getPointerType( event ) }, true ); } + + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); + } } @@ -2562,7 +2741,15 @@ * @inner */ function onLostPointerCapture( tracker, event ) { - $.console.log('lostpointercapture ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + //$.console.log('lostpointercapture ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + + var eventInfo = { + originalEvent: event, + eventType: 'lostpointercapture', + pointerType: getPointerType( event ), + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); if ( event.target === tracker.element ) { //$.console.log('lostpointercapture ' + (tracker.userData ? tracker.userData.toString() : '')); @@ -2573,6 +2760,10 @@ type: getPointerType( event ) }, false ); } + + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); + } } @@ -2581,7 +2772,7 @@ * @inner */ function onPointerEnter( tracker, event ) { - handlePointerEnter( tracker, event ); + handlePointerEnter( tracker, event, false ); } @@ -2589,7 +2780,7 @@ * @private * @inner */ - function handlePointerEnter( tracker, event ) { + function handlePointerEnter( tracker, event, isEmulated ) { var gPoint; //$.console.log('pointerenter ' + (tracker.userData ? tracker.userData.toString() : '')); @@ -2602,7 +2793,18 @@ currentTime: $.now() }; - updatePointersEnter( tracker, event, [ gPoint ] ); + // pointerenter doesn't bubble and is not cancelable, but we call + // preProcessEvent() so it's dispatched to preProcessEventHandler + // if necessary + var eventInfo = { + originalEvent: event, + eventType: 'pointerenter', + pointerType: gPoint.type, + isEmulated: isEmulated + }; + preProcessEvent( tracker, eventInfo ); + + updatePointerEnter( tracker, eventInfo, gPoint ); } @@ -2611,7 +2813,7 @@ * @inner */ function onPointerLeave( tracker, event ) { - handlePointerLeave( tracker, event ); + handlePointerLeave( tracker, event, false ); } @@ -2619,7 +2821,7 @@ * @private * @inner */ - function handlePointerLeave( tracker, event ) { + function handlePointerLeave( tracker, event, isEmulated ) { var gPoint; //$.console.log('pointerleave ' + (tracker.userData ? tracker.userData.toString() : '')); @@ -2632,7 +2834,18 @@ currentTime: $.now() }; - updatePointersLeave( tracker, event, [ gPoint ] ); + // pointerleave doesn't bubble and is not cancelable, but we call + // preProcessEvent() so it's dispatched to preProcessEventHandler + // if necessary + var eventInfo = { + originalEvent: event, + eventType: 'pointerleave', + pointerType: gPoint.type, + isEmulated: isEmulated + }; + preProcessEvent( tracker, eventInfo ); + + updatePointerLeave( tracker, eventInfo, gPoint ); } @@ -2641,18 +2854,9 @@ * @inner */ function onPointerOver( tracker, event ) { - var gPoint; - //$.console.log('pointerover ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); - // If on IE 10, simulate MSPointerEnter - if ( !$.MouseTracker.unprefixedPointerEvents && - event.currentTarget !== event.relatedTarget && - !isParentChild( event.currentTarget, event.relatedTarget ) ) { - handlePointerEnter( tracker, event ); - } - - gPoint = { + var gPoint = { id: event.pointerId, type: getPointerType( event ), isPrimary: event.isPrimary, @@ -2660,7 +2864,29 @@ currentTime: $.now() }; - updatePointersOver( tracker, event, [ gPoint ] ); + var eventInfo = { + originalEvent: event, + eventType: 'pointerover', + pointerType: gPoint.type, + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); + + // If on IE 10, simulate MSPointerEnter + if ( !$.MouseTracker.unprefixedPointerEvents && + event.currentTarget !== event.relatedTarget && + !isParentChild( event.currentTarget, event.relatedTarget ) ) { + handlePointerEnter( tracker, event, true ); + } + + updatePointerOver( tracker, eventInfo, gPoint ); + + if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { + $.cancelEvent( event ); + } + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); + } } @@ -2669,18 +2895,9 @@ * @inner */ function onPointerOut( tracker, event ) { - var gPoint; - //$.console.log('pointerout ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); - // If on IE 10, simulate MSPointerLeave - if ( !$.MouseTracker.unprefixedPointerEvents && - event.currentTarget !== event.relatedTarget && - !isParentChild( event.currentTarget, event.relatedTarget ) ) { - handlePointerLeave( tracker, event ); - } - - gPoint = { + var gPoint = { id: event.pointerId, type: getPointerType( event ), isPrimary: event.isPrimary, @@ -2688,7 +2905,29 @@ currentTime: $.now() }; - updatePointersOut( tracker, event, [ gPoint ] ); + var eventInfo = { + originalEvent: event, + eventType: 'pointerout', + pointerType: gPoint.type, + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); + + updatePointerOut( tracker, eventInfo, gPoint ); + + // If on IE 10, simulate MSPointerLeave + if ( !$.MouseTracker.unprefixedPointerEvents && + event.currentTarget !== event.relatedTarget && + !isParentChild( event.currentTarget, event.relatedTarget ) ) { + handlePointerLeave( tracker, event, true ); + } + + if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { + $.cancelEvent( event ); + } + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); + } } @@ -2698,18 +2937,23 @@ */ function onPointerDown( tracker, event ) { var gPoint; - // var shouldCapture; //$.console.log('onPointerDown ' + (tracker.userData ? tracker.userData.toString() : '')); + // $.console.log('onPointerDown ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + event.target.tagName); + // event.target.style.background = '#F0F'; - // Most browsers implicitly capture touch pointer events...handle that appropriately - // Note IE10 with its prefixed PointerEvent model...no IE versions have - // element.hasPointerCapture() so no implicit pointer capture - var implicitlyCaptured = (tracker.element.hasPointerCapture && $.MouseTracker.unprefixedPointerEvents) ? + // Most browsers implicitly capture touch pointer events + // Note no IE versions have element.hasPointerCapture() so no implicit + // pointer capture possible + var implicitlyCaptured = (tracker.element.hasPointerCapture && + $.Browser.vendor !== $.BROWSERS.IE && + $.MouseTracker.unprefixedPointerEvents) ? tracker.element.hasPointerCapture(event.pointerId) : false; - if (implicitlyCaptured) { - $.console.log('implicitlyCaptured ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); - } + // if (implicitlyCaptured) { + // $.console.log('pointerdown implicitlyCaptured ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + // } else { + // $.console.log('pointerdown not implicitlyCaptured ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + // } gPoint = { id: event.pointerId, @@ -2719,30 +2963,33 @@ currentTime: $.now() }; - $.cancelEvent( event ); - $.stopEvent( event ); - if (!implicitlyCaptured) { - capturePointer( tracker, gPoint ); + var eventInfo = { + originalEvent: event, + eventType: 'pointerdown', + pointerType: gPoint.type, + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); + + updatePointerDown( tracker, eventInfo, gPoint, event.button ); + + if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { + $.cancelEvent( event ); } - - // shouldCapture = updatePointersDown( tracker, event, [ gPoint ], event.button ); - - // if ( shouldCapture ) { - // $.cancelEvent( event ); - // } - - // if ( shouldCapture && !implicitlyCaptured ) { - // $.stopEvent( event ); - // capturePointer( tracker, gPoint ); - // } else if ( !shouldCapture && implicitlyCaptured ) { - // $.stopEvent( event ); - // releasePointer( tracker, gPoint ); //TODO should we do this? Investigate when implementing bubble handling - // } else if ( shouldCapture && implicitlyCaptured ) { - // $.stopEvent( event ); - // } - - // if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) { - // $.cancelEvent( event ); + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); + } + if ( eventInfo.shouldCapture && !implicitlyCaptured ) { + //$.stopEvent( event ); + //$.console.log('pointerdown calling capturePointer() ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + capturePointer( tracker, gPoint ); + } else if ( !eventInfo.shouldCapture && implicitlyCaptured ) { + //$.stopEvent( event ); + //$.console.log('pointerdown calling releasePointer() ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + releasePointer( tracker, gPoint ); //TODO should we do this? Investigate when implementing bubble handling + } + // else if ( eventInfo.shouldCapture && implicitlyCaptured ) { + // //$.stopEvent( event ); // } } @@ -2789,11 +3036,30 @@ currentTime: $.now() }; - $.cancelEvent( event ); - $.stopEvent( event ); - //if ( updatePointersUp( tracker, event, [ gPoint ], event.button ) ) { - releasePointer( tracker, gPoint ); - //} + var eventInfo = { + originalEvent: event, + eventType: 'pointerup', + pointerType: gPoint.type, + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); + + updatePointerUp( tracker, eventInfo, gPoint, event.button ); + + if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { + $.cancelEvent( event ); + } + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); + } + + // Per spec, pointerup events are supposed to release capture. Not all browser + // versions have adhered to the spec, and there's no harm in releasing + // explicitly + if ( eventInfo.shouldReleaseCapture && event.target === tracker.element ) { + //$.stopEvent( event ); + releasePointer( tracker, gPoint ); + } } @@ -2802,9 +3068,7 @@ * @inner */ function onPointerMove( tracker, event ) { - // handlePointerMove( tracker, event ); - $.cancelEvent( event ); - $.stopEvent( event ); + handlePointerMove( tracker, event ); } @@ -2840,7 +3104,22 @@ currentTime: $.now() }; - updatePointersMove( tracker, event, [ gPoint ] ); + var eventInfo = { + originalEvent: event, + eventType: 'pointermove', + pointerType: gPoint.type, + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); + + updatePointerMove( tracker, eventInfo, gPoint ); + + if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { + $.cancelEvent( event ); + } + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); + } } @@ -2849,15 +3128,27 @@ * @inner */ function onPointerCancel( tracker, event ) { - $.console.log('pointercancel ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.isPrimary ? 'isPrimary' : '')); - var gPoint; + //$.console.log('pointercancel ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.isPrimary ? 'isPrimary' : '')); - gPoint = { + var gPoint = { id: event.pointerId, type: getPointerType( event ) }; - updatePointersCancel( tracker, event, [ gPoint ] ); + var eventInfo = { + originalEvent: event, + eventType: 'pointercancel', + pointerType: gPoint.type, + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); + + //TODO need to only do this if our element is target? + updatePointerCancel( tracker, eventInfo, gPoint ); + + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); + } } @@ -2908,22 +3199,28 @@ * @returns {Number} Number of gesture points in pointsList. */ function stopTrackingPointer( pointsList, gPoint ) { - var listLength, - primaryPoint; + var listLength; + + var trackedGPoint = pointsList.getById( gPoint.id ); + + if ( trackedGPoint ) { + if ( trackedGPoint.captured ) { + pointsList.removeContact(); + } - if ( pointsList.getById( gPoint.id ) ) { listLength = pointsList.removeById( gPoint.id ); - // If isPrimary is not known for the pointer and we just removed the primary pointer from the list then we need to set another pointer as primary - if ( !Object.prototype.hasOwnProperty.call( gPoint, 'isPrimary' ) ) { - primaryPoint = pointsList.getPrimary(); - if ( !primaryPoint ) { - primaryPoint = pointsList.getByIndex( 0 ); - if ( primaryPoint ) { - primaryPoint.isPrimary = true; - } - } - } + //TODO Browsers don't re-assign primary pointers so this is probably incorrect + // // If isPrimary is not known for the pointer and we just removed the primary pointer from the list then we need to set another pointer as primary + // if ( !Object.prototype.hasOwnProperty.call( gPoint, 'isPrimary' ) ) { + // primaryPoint = pointsList.getPrimary(); + // if ( !primaryPoint ) { + // primaryPoint = pointsList.getByIndex( 0 ); + // if ( primaryPoint ) { + // primaryPoint.isPrimary = true; + // } + // } + // } } else { listLength = pointsList.getLength(); } @@ -2933,61 +3230,142 @@ /** + * @function + * @private + * @inner + */ + function getEventProcessDefaults( tracker, eventInfo ) { + switch ( eventInfo.eventType ) { + case 'pointermove': + eventInfo.isStopable = true; + eventInfo.isCancelable = true; + eventInfo.preventDefault = false; + eventInfo.preventGesture = !tracker.hasGestureHandlers; + eventInfo.stopPropagation = false; + eventInfo.stopImmediatePropagation = false; + break; + case 'pointerover': + case 'pointerout': + eventInfo.isStopable = true; + eventInfo.isCancelable = true; + eventInfo.preventDefault = false; + eventInfo.preventGesture = false; + eventInfo.stopPropagation = false; + eventInfo.stopImmediatePropagation = false; + break; + case 'pointerdown': + eventInfo.isStopable = true; + eventInfo.isCancelable = true; + eventInfo.preventDefault = false;//tracker.hasGestureHandlers; + eventInfo.preventGesture = !tracker.hasGestureHandlers; + eventInfo.stopPropagation = false; + eventInfo.stopImmediatePropagation = false; + break; + case 'pointerup': + eventInfo.isStopable = true; + eventInfo.isCancelable = true; + eventInfo.preventDefault = false; + eventInfo.preventGesture = !tracker.hasGestureHandlers; + eventInfo.stopPropagation = false; + eventInfo.stopImmediatePropagation = false; + break; + case 'wheel': + eventInfo.isStopable = true; + eventInfo.isCancelable = true; + eventInfo.preventDefault = false;//tracker.hasScrollHandler; + eventInfo.preventGesture = !tracker.hasScrollHandler; + eventInfo.stopPropagation = false; + eventInfo.stopImmediatePropagation = false; + break; + case 'gotpointercapture': + case 'lostpointercapture': + case 'pointercancel': + eventInfo.isStopable = true; + eventInfo.isCancelable = false; + eventInfo.preventDefault = false; + eventInfo.preventGesture = false; + eventInfo.stopPropagation = false; + eventInfo.stopImmediatePropagation = false; + break; + case 'pointerenter': + case 'pointerleave': + default: + eventInfo.isStopable = false; + eventInfo.isCancelable = false; + eventInfo.preventDefault = false; + eventInfo.preventGesture = false; + eventInfo.stopPropagation = false; + eventInfo.stopImmediatePropagation = false; + break; + } + } + + + /** + * Sets up for and calls preProcessEventHandler. Call with the following parameters - + * this function will fill in the rest of the preProcessEventHandler event object + * properties + * * @function * @private * @inner * @param {OpenSeadragon.MouseTracker} tracker * A reference to the MouseTracker instance. - * @param {Object} event - * A reference to the originating DOM event. - * @param {Array.} gPoints - * Gesture points associated with the event. + * @param {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo + * @param {Object} eventInfo.originalEvent + * @param {String} eventInfo.eventType + * @param {String} eventInfo.pointerType + * @param {Boolean} eventInfo.isEmulated */ - function updatePointersEnter( tracker, event, gPoints ) { - var pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ), - i, - gPointCount = gPoints.length, - curGPoint, - updateGPoint; + function preProcessEvent( tracker, eventInfo ) { + eventInfo.eventSource = tracker; + eventInfo.eventPhase = eventInfo.originalEvent ? + ((typeof eventInfo.originalEvent.eventPhase !== 'undefined') ? + eventInfo.originalEvent.eventPhase : 0) : 0; + eventInfo.defaultPrevented = $.eventIsCanceled( eventInfo.originalEvent ); + eventInfo.shouldCapture = false; + eventInfo.shouldReleaseCapture = false; + eventInfo.userData = tracker.userData; - for ( i = 0; i < gPointCount; i++ ) { - curGPoint = gPoints[ i ]; - updateGPoint = pointsList.getById( curGPoint.id ); + getEventProcessDefaults( tracker, eventInfo ); - if ( updateGPoint ) { - // Already tracking the pointer...update it - updateGPoint.insideElement = true; - updateGPoint.lastPos = updateGPoint.currentPos; - updateGPoint.lastTime = updateGPoint.currentTime; - updateGPoint.currentPos = curGPoint.currentPos; - updateGPoint.currentTime = curGPoint.currentTime; + if ( tracker.preProcessEventHandler ) { + tracker.preProcessEventHandler( eventInfo ); + } + } - curGPoint = updateGPoint; - } else { - // Initialize for tracking and add to the tracking list - curGPoint.captured = false; // Handled by updatePointerCaptured() - curGPoint.insideElementPressed = false; - curGPoint.insideElement = true; - startTrackingPointer( pointsList, curGPoint ); - } - - // Enter (doesn't bubble and not cancelable) - if ( tracker.enterHandler ) { - tracker.enterHandler( - { - eventSource: tracker, - pointerType: curGPoint.type, - position: getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ), - buttons: pointsList.buttons, - pointers: tracker.getActivePointerCount(), - insideElementPressed: curGPoint.insideElementPressed, - buttonDownAny: pointsList.buttons !== 0, - isTouchEvent: curGPoint.type === 'touch', - originalEvent: event, - userData: tracker.userData - } - ); + + /** + * Sets or resets the captured property on the tracked pointer matching the passed gPoint's id/type + * + * @function + * @private + * @inner + * @param {OpenSeadragon.MouseTracker} tracker + * A reference to the MouseTracker instance. + * @param {Object} gPoint + * An object with id and type properties describing the pointer to update. + * @param {Boolean} isCaptured + * Value to set the captured property to. + */ + function updatePointerCaptured( tracker, gPoint, isCaptured ) { + var pointsList = tracker.getActivePointersListByType( gPoint.type ); + var updateGPoint = pointsList.getById( gPoint.id ); + + if ( updateGPoint ) { + if ( isCaptured && !updateGPoint.captured ) { + updateGPoint.captured = true; + pointsList.captureCount++; + } else if ( !isCaptured && updateGPoint.captured ) { + updateGPoint.captured = false; + pointsList.captureCount--; + if ( pointsList.captureCount < 0 ) { + pointsList.captureCount = 0; + $.console.warn('updatePointerCaptured() - pointsList.captureCount went negative'); + } } + } else { + $.console.warn('updatePointerCaptured() called on untracked pointer'); } } @@ -2998,60 +3376,113 @@ * @inner * @param {OpenSeadragon.MouseTracker} tracker * A reference to the MouseTracker instance. - * @param {Object} event - * A reference to the originating DOM event. - * @param {Array.} gPoints - * Gesture points associated with the event. + * @param {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo + * Processing info for originating DOM event. + * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint + * Gesture point associated with the event. */ - function updatePointersLeave( tracker, event, gPoints ) { - var pointsList = tracker.getActivePointersListByType(gPoints[0].type), - i, - gPointCount = gPoints.length, - curGPoint, + function updatePointerEnter( tracker, eventInfo, gPoint ) { + var pointsList = tracker.getActivePointersListByType( gPoint.type ), + updateGPoint; + + updateGPoint = pointsList.getById( gPoint.id ); + + if ( updateGPoint ) { + // Already tracking the pointer...update it + updateGPoint.insideElement = true; + updateGPoint.lastPos = updateGPoint.currentPos; + updateGPoint.lastTime = updateGPoint.currentTime; + updateGPoint.currentPos = gPoint.currentPos; + updateGPoint.currentTime = gPoint.currentTime; + + gPoint = updateGPoint; + } else { + // Initialize for tracking and add to the tracking list + gPoint.captured = false; // Handled by updatePointerCaptured() + gPoint.insideElementPressed = false; + gPoint.insideElement = true; + startTrackingPointer( pointsList, gPoint ); + } + + // Enter (doesn't bubble and not cancelable) + if ( tracker.enterHandler ) { + tracker.enterHandler( + { + eventSource: tracker, + pointerType: gPoint.type, + position: getPointRelativeToAbsolute( gPoint.currentPos, tracker.element ), + buttons: pointsList.buttons, + pointers: tracker.getActivePointerCount(), + insideElementPressed: gPoint.insideElementPressed, + buttonDownAny: pointsList.buttons !== 0, + isTouchEvent: gPoint.type === 'touch', + originalEvent: eventInfo.originalEvent, + userData: tracker.userData + } + ); + } + } + + + /** + * @function + * @private + * @inner + * @param {OpenSeadragon.MouseTracker} tracker + * A reference to the MouseTracker instance. + * @param {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo + * Processing info for originating DOM event. + * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint + * Gesture point associated with the event. + */ + function updatePointerLeave( tracker, eventInfo, gPoint ) { + var pointsList = tracker.getActivePointersListByType(gPoint.type), updateGPoint, dispatchEventObj; - for ( i = 0; i < gPointCount; i++ ) { - curGPoint = gPoints[ i ]; - updateGPoint = pointsList.getById( curGPoint.id ); + updateGPoint = pointsList.getById( gPoint.id ); - if ( updateGPoint ) { - // Already tracking the pointer. If captured then update it, else stop tracking it - if ( updateGPoint.captured ) { - updateGPoint.insideElement = false; - updateGPoint.lastPos = updateGPoint.currentPos; - updateGPoint.lastTime = updateGPoint.currentTime; - updateGPoint.currentPos = curGPoint.currentPos; - updateGPoint.currentTime = curGPoint.currentTime; - } else { - stopTrackingPointer( pointsList, updateGPoint ); - } - - curGPoint = updateGPoint; + if ( updateGPoint ) { + // Already tracking the pointer. If captured then update it, else stop tracking it + if ( updateGPoint.captured ) { + updateGPoint.insideElement = false; + updateGPoint.lastPos = updateGPoint.currentPos; + updateGPoint.lastTime = updateGPoint.currentTime; + updateGPoint.currentPos = gPoint.currentPos; + updateGPoint.currentTime = gPoint.currentTime; + } else { + stopTrackingPointer( pointsList, updateGPoint ); } - // Leave (doesn't bubble and not cancelable) - // Note: exitHandler is deprecated, replaced by leaveHandler - if ( tracker.leaveHandler || tracker.exitHandler ) { - dispatchEventObj = { - eventSource: tracker, - pointerType: curGPoint.type, - position: curGPoint.currentPos && getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ), - buttons: pointsList.buttons, - pointers: tracker.getActivePointerCount(), - insideElementPressed: updateGPoint ? updateGPoint.insideElementPressed : false, - buttonDownAny: pointsList.buttons !== 0, - isTouchEvent: curGPoint.type === 'touch', - originalEvent: event, - userData: tracker.userData - }; + gPoint = updateGPoint; + } else { + gPoint.captured = false; // Handled by updatePointerCaptured() + gPoint.insideElementPressed = false; + } - if ( tracker.leaveHandler ) { - tracker.leaveHandler( dispatchEventObj ); - } - if ( tracker.exitHandler ) { - tracker.exitHandler( dispatchEventObj ); - } + // Leave (doesn't bubble and not cancelable) + // Note: exitHandler is deprecated, replaced by leaveHandler + if ( tracker.leaveHandler || tracker.exitHandler ) { + dispatchEventObj = { + eventSource: tracker, + pointerType: gPoint.type, + // GitHub PR: https://github.com/openseadragon/openseadragon/pull/1754 (gPoint.currentPos && ) + position: gPoint.currentPos && getPointRelativeToAbsolute( gPoint.currentPos, tracker.element ), + buttons: pointsList.buttons, + pointers: tracker.getActivePointerCount(), + insideElementPressed: gPoint.insideElementPressed, + buttonDownAny: pointsList.buttons !== 0, + isTouchEvent: gPoint.type === 'touch', + originalEvent: eventInfo.originalEvent, + userData: tracker.userData + }; + + if ( tracker.leaveHandler ) { + tracker.leaveHandler( dispatchEventObj ); + } + // Deprecated + if ( tracker.exitHandler ) { + tracker.exitHandler( dispatchEventObj ); } } } @@ -3063,107 +3494,89 @@ * @inner * @param {OpenSeadragon.MouseTracker} tracker * A reference to the MouseTracker instance. - * @param {Object} event - * A reference to the originating DOM event. - * @param {Array.} gPoints - * Gesture points associated with the event. + * @param {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo + * Processing info for originating DOM event. + * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint + * Gesture point associated with the event. */ - function updatePointersOver( tracker, event, gPoints ) { + function updatePointerOver( tracker, eventInfo, gPoint ) { var pointsList, - i, - gPointCount, - curGPoint, - updateGPoint, - propagate; + updateGPoint; + + pointsList = tracker.getActivePointersListByType( gPoint.type ); + + updateGPoint = pointsList.getById( gPoint.id ); + + if ( updateGPoint ) { + gPoint = updateGPoint; + } else { + gPoint.captured = false; + gPoint.insideElementPressed = false; + //gPoint.insideElement = true; // Tracked by updatePointerEnter + } if ( tracker.overHandler ) { - pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ); - gPointCount = gPoints.length; - - for ( i = 0; i < gPointCount; i++ ) { - curGPoint = gPoints[ i ]; - updateGPoint = pointsList.getById( curGPoint.id ); - - if ( updateGPoint ) { - curGPoint = updateGPoint; - } else { - //curGPoint.captured = false; // Handled by updatePointerCaptured() - curGPoint.insideElementPressed = false; - //curGPoint.insideElement = true; // Tracked by updatePointersEnter - } - - // Over - propagate = tracker.overHandler( - { - eventSource: tracker, - pointerType: curGPoint.type, - position: getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ), - buttons: pointsList.buttons, - pointers: tracker.getActivePointerCount(), - insideElementPressed: curGPoint.insideElementPressed, - buttonDownAny: pointsList.buttons !== 0, - isTouchEvent: curGPoint.type === 'touch', - originalEvent: event, - preventDefaultAction: false, - userData: tracker.userData - } - ); - if ( propagate === false ) { - $.cancelEvent( event ); - } - } - } - } - - /** - * @function - * @private - * @inner - * @param {OpenSeadragon.MouseTracker} tracker - * A reference to the MouseTracker instance. - * @param {Object} event - * A reference to the originating DOM event. - * @param {Array.} gPoints - * Gesture points associated with the event. - */ - function updatePointersOut( tracker, event, gPoints ) { - var pointsList, - i, - gPointCount, - curGPoint, - updateGPoint, - propagate; - - if ( tracker.outHandler ) { - pointsList = tracker.getActivePointersListByType(gPoints[0].type); - gPointCount = gPoints.length; - - for ( i = 0; i < gPointCount; i++ ) { - curGPoint = gPoints[ i ]; - updateGPoint = pointsList.getById( curGPoint.id ); - - if ( updateGPoint ) { - curGPoint = updateGPoint; - } - - // Out - propagate = tracker.outHandler( { + // Over + tracker.overHandler( + { eventSource: tracker, - pointerType: curGPoint.type, - position: curGPoint.currentPos && getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ), + pointerType: gPoint.type, + position: getPointRelativeToAbsolute( gPoint.currentPos, tracker.element ), buttons: pointsList.buttons, pointers: tracker.getActivePointerCount(), - insideElementPressed: updateGPoint ? updateGPoint.insideElementPressed : false, + insideElementPressed: gPoint.insideElementPressed, buttonDownAny: pointsList.buttons !== 0, - isTouchEvent: curGPoint.type === 'touch', - originalEvent: event, + isTouchEvent: gPoint.type === 'touch', + originalEvent: eventInfo.originalEvent, preventDefaultAction: false, userData: tracker.userData - } ); - if ( propagate === false ) { - $.cancelEvent( event ); } - } + ); + } + } + + /** + * @function + * @private + * @inner + * @param {OpenSeadragon.MouseTracker} tracker + * A reference to the MouseTracker instance. + * @param {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo + * Processing info for originating DOM event. + * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint + * Gesture point associated with the event. + */ + function updatePointerOut( tracker, eventInfo, gPoint ) { + var pointsList, + updateGPoint; + + pointsList = tracker.getActivePointersListByType(gPoint.type); + + updateGPoint = pointsList.getById( gPoint.id ); + + if ( updateGPoint ) { + gPoint = updateGPoint; + } else { + gPoint.captured = false; + gPoint.insideElementPressed = false; + //gPoint.insideElement = true; // Tracked by updatePointerEnter + } + + if ( tracker.outHandler ) { + // Out + tracker.outHandler( { + eventSource: tracker, + pointerType: gPoint.type, + position: gPoint.currentPos && getPointRelativeToAbsolute( gPoint.currentPos, tracker.element ), + buttons: pointsList.buttons, + pointers: tracker.getActivePointerCount(), + insideElementPressed: gPoint.insideElementPressed, + buttonDownAny: pointsList.buttons !== 0, + isTouchEvent: gPoint.type === 'touch', + originalEvent: eventInfo.originalEvent, + preventDefaultAction: false, + userData: tracker.userData + } ); } } @@ -3174,28 +3587,22 @@ * @inner * @param {OpenSeadragon.MouseTracker} tracker * A reference to the MouseTracker instance. - * @param {Object} event - * A reference to the originating DOM event. - * @param {Array.} gPoints - * Gesture points associated with the event. + * @param {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo + * Processing info for originating DOM event. + * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint + * Gesture point associated with the event. * @param {Number} buttonChanged * The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser. * Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model, * only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events. - * - * @returns {Boolean} True if pointers should be captured to the tracked element, otherwise false. */ - function updatePointersDown( tracker, event, gPoints, buttonChanged ) { + function updatePointerDown( tracker, eventInfo, gPoint, buttonChanged ) { var delegate = THIS[ tracker.hash ], - propagate, - pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ), - i, - gPointCount = gPoints.length, - curGPoint, + pointsList = tracker.getActivePointersListByType( gPoint.type ), updateGPoint; - if ( typeof event.buttons !== 'undefined' ) { - pointsList.buttons = event.buttons; + if ( typeof eventInfo.originalEvent.buttons !== 'undefined' ) { + pointsList.buttons = eventInfo.originalEvent.buttons; } else { if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { if ( buttonChanged === 0 ) { @@ -3244,7 +3651,7 @@ // like the issue this code attempts to fix. // // Some pointers may steal control from another pointer without firing the appropriate release events // // e.g. Touching a screen while click-dragging with certain mice. - // var otherPointsLists = tracker.getActivePointersListsExceptType(gPoints[ 0 ].type); + // var otherPointsLists = tracker.getActivePointersListsExceptType(gPoint.type); // for (i = 0; i < otherPointsLists.length; i++) { // //If another pointer has contact, simulate the release // abortContacts(tracker, event, otherPointsLists[i]); // No-op if no active pointer @@ -3252,91 +3659,96 @@ // Only capture and track primary button, pen, and touch contacts if ( buttonChanged !== 0 ) { + eventInfo.shouldCapture = false; + eventInfo.shouldReleaseCapture = false; + // Aux Press - if ( tracker.nonPrimaryPressHandler ) { - propagate = tracker.nonPrimaryPressHandler( + if ( tracker.nonPrimaryPressHandler && + !eventInfo.preventGesture && + !eventInfo.defaultPrevented ) { + eventInfo.preventDefault = true; + + tracker.nonPrimaryPressHandler( { eventSource: tracker, - pointerType: gPoints[ 0 ].type, - position: getPointRelativeToAbsolute( gPoints[ 0 ].currentPos, tracker.element ), + pointerType: gPoint.type, + position: getPointRelativeToAbsolute( gPoint.currentPos, tracker.element ), button: buttonChanged, buttons: pointsList.buttons, - isTouchEvent: gPoints[ 0 ].type === 'touch', - originalEvent: event, + isTouchEvent: gPoint.type === 'touch', + originalEvent: eventInfo.originalEvent, preventDefaultAction: false, userData: tracker.userData } ); - if ( propagate === false ) { - $.cancelEvent( event ); - } } - return false; + return; } - for ( i = 0; i < gPointCount; i++ ) { - curGPoint = gPoints[ i ]; - updateGPoint = pointsList.getById( curGPoint.id ); + updateGPoint = pointsList.getById( gPoint.id ); - if ( updateGPoint ) { - // Already tracking the pointer...update it - //updateGPoint.captured = true; // Handled by updatePointerCaptured() - updateGPoint.insideElementPressed = true; - updateGPoint.insideElement = true; - updateGPoint.contactPos = curGPoint.currentPos; - updateGPoint.contactTime = curGPoint.currentTime; - updateGPoint.lastPos = updateGPoint.currentPos; - updateGPoint.lastTime = updateGPoint.currentTime; - updateGPoint.currentPos = curGPoint.currentPos; - updateGPoint.currentTime = curGPoint.currentTime; + if ( updateGPoint ) { + // Already tracking the pointer...update it + //updateGPoint.captured = true; // Handled by updatePointerCaptured() + updateGPoint.insideElementPressed = true; + updateGPoint.insideElement = true; + updateGPoint.contactPos = gPoint.currentPos; + updateGPoint.contactTime = gPoint.currentTime; + updateGPoint.lastPos = updateGPoint.currentPos; + updateGPoint.lastTime = updateGPoint.currentTime; + updateGPoint.currentPos = gPoint.currentPos; + updateGPoint.currentTime = gPoint.currentTime; - curGPoint = updateGPoint; - } else { - // Initialize for tracking and add to the tracking list (no pointerover or pointermove event occurred before this) - curGPoint.captured = false; // Handled by updatePointerCaptured() - curGPoint.insideElementPressed = true; - curGPoint.insideElement = true; - startTrackingPointer( pointsList, curGPoint ); - } + gPoint = updateGPoint; + } else { + // Initialize for tracking and add to the tracking list (no pointerover or pointermove event occurred before this) + gPoint.captured = false; // Handled by updatePointerCaptured() + gPoint.insideElementPressed = true; + gPoint.insideElement = true; + startTrackingPointer( pointsList, gPoint ); + } + + if ( !eventInfo.preventGesture && !eventInfo.defaultPrevented ) { + eventInfo.shouldCapture = true; + eventInfo.shouldReleaseCapture = false; + eventInfo.preventDefault = true; pointsList.addContact(); //$.console.log('contacts++ ', pointsList.contacts); if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) { - $.MouseTracker.gesturePointVelocityTracker.addPoint( tracker, curGPoint ); + $.MouseTracker.gesturePointVelocityTracker.addPoint( tracker, gPoint ); } if ( pointsList.contacts === 1 ) { // Press - if ( tracker.pressHandler ) { - propagate = tracker.pressHandler( + if ( tracker.pressHandler && !eventInfo.preventGesture ) { + tracker.pressHandler( { eventSource: tracker, - pointerType: curGPoint.type, - position: getPointRelativeToAbsolute( curGPoint.contactPos, tracker.element ), + pointerType: gPoint.type, + position: getPointRelativeToAbsolute( gPoint.contactPos, tracker.element ), buttons: pointsList.buttons, - isTouchEvent: curGPoint.type === 'touch', - originalEvent: event, + isTouchEvent: gPoint.type === 'touch', + originalEvent: eventInfo.originalEvent, preventDefaultAction: false, userData: tracker.userData } ); - if ( propagate === false ) { - $.cancelEvent( event ); - } } } else if ( pointsList.contacts === 2 ) { - if ( tracker.pinchHandler && curGPoint.type === 'touch' ) { + if ( tracker.pinchHandler && gPoint.type === 'touch' ) { // Initialize for pinch delegate.pinchGPoints = pointsList.asArray(); delegate.lastPinchDist = delegate.currentPinchDist = delegate.pinchGPoints[ 0 ].currentPos.distanceTo( delegate.pinchGPoints[ 1 ].currentPos ); delegate.lastPinchCenter = delegate.currentPinchCenter = getCenterPoint( delegate.pinchGPoints[ 0 ].currentPos, delegate.pinchGPoints[ 1 ].currentPos ); } } + } else { + eventInfo.shouldCapture = false; + eventInfo.shouldReleaseCapture = false; } - - return true; } @@ -3346,33 +3758,26 @@ * @inner * @param {OpenSeadragon.MouseTracker} tracker * A reference to the MouseTracker instance. - * @param {Object} event - * A reference to the originating DOM event. - * @param {Array.} gPoints + * @param {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo + * Processing info for originating DOM event. + * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint * Gesture points associated with the event. * @param {Number} buttonChanged * The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser. * Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model, * only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events. - * - * @returns {Boolean} True if pointer capture should be released from the tracked element, otherwise false. */ - function updatePointersUp( tracker, event, gPoints, buttonChanged ) { + function updatePointerUp( tracker, eventInfo, gPoint, buttonChanged ) { var delegate = THIS[ tracker.hash ], - pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ), - propagate, + pointsList = tracker.getActivePointersListByType( gPoint.type ), releasePoint, releaseTime, - i, - gPointCount = gPoints.length, - curGPoint, updateGPoint, - releaseCapture = false, wasCaptured = false, quick; - if ( typeof event.buttons !== 'undefined' ) { - pointsList.buttons = event.buttons; + if ( typeof eventInfo.originalEvent.buttons !== 'undefined' ) { + pointsList.buttons = eventInfo.originalEvent.buttons; } else { if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { if ( buttonChanged === 0 ) { @@ -3417,26 +3822,31 @@ } } + eventInfo.shouldCapture = false; + // Only capture and track primary button, pen, and touch contacts if ( buttonChanged !== 0 ) { + eventInfo.shouldReleaseCapture = false; + // Aux Release - if ( tracker.nonPrimaryReleaseHandler ) { - propagate = tracker.nonPrimaryReleaseHandler( + if ( tracker.nonPrimaryReleaseHandler && + !eventInfo.preventGesture && + !eventInfo.defaultPrevented ) { + eventInfo.preventDefault = true; + + tracker.nonPrimaryReleaseHandler( { eventSource: tracker, - pointerType: gPoints[ 0 ].type, - position: getPointRelativeToAbsolute(gPoints[0].currentPos, tracker.element), + pointerType: gPoint.type, + position: getPointRelativeToAbsolute(gPoint.currentPos, tracker.element), button: buttonChanged, buttons: pointsList.buttons, - isTouchEvent: gPoints[ 0 ].type === 'touch', - originalEvent: event, + isTouchEvent: gPoint.type === 'touch', + originalEvent: eventInfo.originalEvent, preventDefaultAction: false, userData: tracker.userData } ); - if ( propagate === false ) { - $.cancelEvent( event ); - } } //TODO Revisit this if there's still an issue. The PointerEvent model should have no problems @@ -3446,167 +3856,68 @@ // // Stop tracking the mouse; see https://github.com/openseadragon/openseadragon/pull/1223 // abortContacts(tracker, event, otherPointsList); // No-op if no active pointer - return false; + return; } //TODO Revisit this if there's still an issue. The PointerEvent model should have no problems // like the issue this code attempts to fix. + // GitHub PR: https://github.com/openseadragon/openseadragon/pull/1754 // // OS-specific gestures (e.g. swipe up with four fingers in iPadOS 13) - // if (typeof gPoints[ 0 ].currentPos === "undefined") { - // $.console.log('typeof gPoints[ 0 ].currentPos === "undefined" ' + (tracker.userData ? tracker.userData.toString() : '')); + // if (typeof gPoint.currentPos === "undefined") { + // $.console.log('typeof gPoint.currentPos === "undefined" ' + (tracker.userData ? tracker.userData.toString() : '')); // abortContacts(tracker, event, pointsList); // return false; // } - for ( i = 0; i < gPointCount; i++ ) { - curGPoint = gPoints[ i ]; - updateGPoint = pointsList.getById( curGPoint.id ); - if ( updateGPoint ) { - // Update the pointer, stop tracking it if not still in this element - if ( updateGPoint.captured ) { - //updateGPoint.captured = false; // Handled by updatePointerCaptured() - releaseCapture = true; - wasCaptured = true; - } - updateGPoint.lastPos = updateGPoint.currentPos; - updateGPoint.lastTime = updateGPoint.currentTime; - updateGPoint.currentPos = curGPoint.currentPos; - updateGPoint.currentTime = curGPoint.currentTime; - if ( !updateGPoint.insideElement ) { - stopTrackingPointer( pointsList, updateGPoint ); + updateGPoint = pointsList.getById( gPoint.id ); + + if ( updateGPoint ) { + // Update the pointer, stop tracking it if not still in this element + if ( updateGPoint.captured ) { + //updateGPoint.captured = false; // Handled by updatePointerCaptured() + wasCaptured = true; + } + updateGPoint.lastPos = updateGPoint.currentPos; + updateGPoint.lastTime = updateGPoint.currentTime; + updateGPoint.currentPos = gPoint.currentPos; + updateGPoint.currentTime = gPoint.currentTime; + if ( !updateGPoint.insideElement ) { + stopTrackingPointer( pointsList, updateGPoint ); + } + + releasePoint = updateGPoint.currentPos; + releaseTime = updateGPoint.currentTime; + } else { + // should never get here...we'll start to track pointer anyway + $.console.warn('updatePointerUp(): pointerup on untracked gPoint'); + gPoint.captured = false; // Handled by updatePointerCaptured() + gPoint.insideElementPressed = false; + gPoint.insideElement = true; + startTrackingPointer( pointsList, gPoint ); + + updateGPoint = gPoint; + } + + if ( !eventInfo.preventGesture && !eventInfo.defaultPrevented ) { + if ( wasCaptured ) { + // Pointer was activated in our element but could have been removed in any element since events are captured to our element + + eventInfo.shouldReleaseCapture = true; + eventInfo.preventDefault = true; + + pointsList.removeContact(); + //$.console.log('contacts-- ', pointsList.contacts); + + if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) { + $.MouseTracker.gesturePointVelocityTracker.removePoint( tracker, updateGPoint ); } - releasePoint = updateGPoint.currentPos; - releaseTime = updateGPoint.currentTime; + if ( pointsList.contacts === 0 ) { - if ( wasCaptured ) { - // Pointer was activated in our element but could have been removed in any element since events are captured to our element - - pointsList.removeContact(); - //$.console.log('contacts-- ', pointsList.contacts); - - if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) { - $.MouseTracker.gesturePointVelocityTracker.removePoint( tracker, updateGPoint ); - } - - if ( pointsList.contacts === 0 ) { - - // Release (pressed in our element) - if ( tracker.releaseHandler ) { - propagate = tracker.releaseHandler( - { - eventSource: tracker, - pointerType: updateGPoint.type, - position: getPointRelativeToAbsolute( releasePoint, tracker.element ), - buttons: pointsList.buttons, - insideElementPressed: updateGPoint.insideElementPressed, - insideElementReleased: updateGPoint.insideElement, - isTouchEvent: updateGPoint.type === 'touch', - originalEvent: event, - preventDefaultAction: false, - userData: tracker.userData - } - ); - if ( propagate === false ) { - $.cancelEvent( event ); - } - } - - // Drag End - if ( tracker.dragEndHandler ) { - propagate = tracker.dragEndHandler( - { - eventSource: tracker, - pointerType: updateGPoint.type, - position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), - speed: updateGPoint.speed, - direction: updateGPoint.direction, - shift: event.shiftKey, - isTouchEvent: updateGPoint.type === 'touch', - originalEvent: event, - preventDefaultAction: false, - userData: tracker.userData - } - ); - if ( propagate === false ) { - $.cancelEvent( event ); - } - } - - // Click / Double-Click - if ( ( tracker.clickHandler || tracker.dblClickHandler ) && updateGPoint.insideElement ) { - quick = releaseTime - updateGPoint.contactTime <= tracker.clickTimeThreshold && - updateGPoint.contactPos.distanceTo( releasePoint ) <= tracker.clickDistThreshold; - - // Click - if ( tracker.clickHandler ) { - propagate = tracker.clickHandler( - { - eventSource: tracker, - pointerType: updateGPoint.type, - position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), - quick: quick, - shift: event.shiftKey, - isTouchEvent: updateGPoint.type === 'touch', - originalEvent: event, - preventDefaultAction: false, - userData: tracker.userData - } - ); - if ( propagate === false ) { - $.cancelEvent( event ); - } - } - - // Double-Click - if ( tracker.dblClickHandler && quick ) { - pointsList.clicks++; - if ( pointsList.clicks === 1 ) { - delegate.lastClickPos = releasePoint; - /*jshint loopfunc:true*/ - delegate.dblClickTimeOut = setTimeout( function() { - pointsList.clicks = 0; - }, tracker.dblClickTimeThreshold ); - /*jshint loopfunc:false*/ - } else if ( pointsList.clicks === 2 ) { - clearTimeout( delegate.dblClickTimeOut ); - pointsList.clicks = 0; - if ( delegate.lastClickPos.distanceTo( releasePoint ) <= tracker.dblClickDistThreshold ) { - propagate = tracker.dblClickHandler( - { - eventSource: tracker, - pointerType: updateGPoint.type, - position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), - shift: event.shiftKey, - isTouchEvent: updateGPoint.type === 'touch', - originalEvent: event, - preventDefaultAction: false, - userData: tracker.userData - } - ); - if ( propagate === false ) { - $.cancelEvent( event ); - } - } - delegate.lastClickPos = null; - } - } - } - } else if ( pointsList.contacts === 2 ) { - if ( tracker.pinchHandler && updateGPoint.type === 'touch' ) { - // Reset for pinch - delegate.pinchGPoints = pointsList.asArray(); - delegate.lastPinchDist = delegate.currentPinchDist = delegate.pinchGPoints[ 0 ].currentPos.distanceTo( delegate.pinchGPoints[ 1 ].currentPos ); - delegate.lastPinchCenter = delegate.currentPinchCenter = getCenterPoint( delegate.pinchGPoints[ 0 ].currentPos, delegate.pinchGPoints[ 1 ].currentPos ); - } - } - } else { - // Pointer was activated in another element but removed in our element - - // Release (pressed in another element) + // Release (pressed in our element) if ( tracker.releaseHandler ) { - propagate = tracker.releaseHandler( + tracker.releaseHandler( { eventSource: tracker, pointerType: updateGPoint.type, @@ -3615,55 +3926,117 @@ insideElementPressed: updateGPoint.insideElementPressed, insideElementReleased: updateGPoint.insideElement, isTouchEvent: updateGPoint.type === 'touch', - originalEvent: event, + originalEvent: eventInfo.originalEvent, preventDefaultAction: false, userData: tracker.userData } ); - if ( propagate === false ) { - $.cancelEvent( event ); + } + + // Drag End + if ( tracker.dragEndHandler ) { + tracker.dragEndHandler( + { + eventSource: tracker, + pointerType: updateGPoint.type, + position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), + speed: updateGPoint.speed, + direction: updateGPoint.direction, + shift: eventInfo.originalEvent.shiftKey, + isTouchEvent: updateGPoint.type === 'touch', + originalEvent: eventInfo.originalEvent, + preventDefaultAction: false, + userData: tracker.userData + } + ); + } + + // Click / Double-Click + if ( ( tracker.clickHandler || tracker.dblClickHandler ) && updateGPoint.insideElement ) { + quick = releaseTime - updateGPoint.contactTime <= tracker.clickTimeThreshold && + updateGPoint.contactPos.distanceTo( releasePoint ) <= tracker.clickDistThreshold; + + // Click + if ( tracker.clickHandler ) { + tracker.clickHandler( + { + eventSource: tracker, + pointerType: updateGPoint.type, + position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), + quick: quick, + shift: eventInfo.originalEvent.shiftKey, + isTouchEvent: updateGPoint.type === 'touch', + originalEvent: eventInfo.originalEvent, + preventDefaultAction: false, + userData: tracker.userData + } + ); + } + + // Double-Click + if ( tracker.dblClickHandler && quick ) { + pointsList.clicks++; + if ( pointsList.clicks === 1 ) { + delegate.lastClickPos = releasePoint; + /*jshint loopfunc:true*/ + delegate.dblClickTimeOut = setTimeout( function() { + pointsList.clicks = 0; + }, tracker.dblClickTimeThreshold ); + /*jshint loopfunc:false*/ + } else if ( pointsList.clicks === 2 ) { + clearTimeout( delegate.dblClickTimeOut ); + pointsList.clicks = 0; + if ( delegate.lastClickPos.distanceTo( releasePoint ) <= tracker.dblClickDistThreshold ) { + tracker.dblClickHandler( + { + eventSource: tracker, + pointerType: updateGPoint.type, + position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), + shift: eventInfo.originalEvent.shiftKey, + isTouchEvent: updateGPoint.type === 'touch', + originalEvent: eventInfo.originalEvent, + preventDefaultAction: false, + userData: tracker.userData + } + ); + } + delegate.lastClickPos = null; + } } } + } else if ( pointsList.contacts === 2 ) { + if ( tracker.pinchHandler && updateGPoint.type === 'touch' ) { + // Reset for pinch + delegate.pinchGPoints = pointsList.asArray(); + delegate.lastPinchDist = delegate.currentPinchDist = delegate.pinchGPoints[ 0 ].currentPos.distanceTo( delegate.pinchGPoints[ 1 ].currentPos ); + delegate.lastPinchCenter = delegate.currentPinchCenter = getCenterPoint( delegate.pinchGPoints[ 0 ].currentPos, delegate.pinchGPoints[ 1 ].currentPos ); + } + } + } else { + // Pointer was activated in another element but removed in our element + + eventInfo.shouldReleaseCapture = false; + + // Release (pressed in another element) + if ( tracker.releaseHandler ) { + tracker.releaseHandler( + { + eventSource: tracker, + pointerType: updateGPoint.type, + position: getPointRelativeToAbsolute( releasePoint, tracker.element ), + buttons: pointsList.buttons, + insideElementPressed: updateGPoint.insideElementPressed, + insideElementReleased: updateGPoint.insideElement, + isTouchEvent: updateGPoint.type === 'touch', + originalEvent: eventInfo.originalEvent, + preventDefaultAction: false, + userData: tracker.userData + } + ); + eventInfo.preventDefault = true; } } } - - return releaseCapture; - } - - - /** - * Sets or resets the captured property on the tracked pointer matching the passed gPoint's id/type - * - * @function - * @private - * @inner - * @param {OpenSeadragon.MouseTracker} tracker - * A reference to the MouseTracker instance. - * @param {Object} gPoint - * An object with id and type properties describing the pointer to update. - * @param {Boolean} isCaptured - * Value to set the captured property to. - */ - function updatePointerCaptured( tracker, gPoint, isCaptured ) { - var pointsList = tracker.getActivePointersListByType( gPoint.type ); - var updateGPoint = pointsList.getById( gPoint.id ); - - if ( updateGPoint ) { - if ( isCaptured && !updateGPoint.captured ) { - updateGPoint.captured = true; - pointsList.captureCount++; - } else if ( !isCaptured && updateGPoint.captured ) { - updateGPoint.captured = false; - pointsList.captureCount--; - if ( pointsList.captureCount < 0 ) { - pointsList.captureCount = 0; - $.console.warn('updatePointerCaptured() - pointsList.captureCount went negative'); - } - } - } else { - $.console.warn('updatePointerCaptured() called on untracked pointer'); - } } @@ -3675,101 +4048,91 @@ * @inner * @param {OpenSeadragon.MouseTracker} tracker * A reference to the MouseTracker instance. - * @param {Object} event - * A reference to the originating DOM event. - * @param {Array.} gPoints + * @param {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo + * Processing info for originating DOM event. + * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint * Gesture points associated with the event. */ - function updatePointersMove( tracker, event, gPoints ) { + function updatePointerMove( tracker, eventInfo, gPoint ) { var delegate = THIS[ tracker.hash ], - pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ), - i, - gPointCount = gPoints.length, - curGPoint, + pointsList = tracker.getActivePointersListByType( gPoint.type ), updateGPoint, gPointArray, - delta, - propagate; + delta; - if ( typeof event.buttons !== 'undefined' ) { - pointsList.buttons = event.buttons; + if ( typeof eventInfo.originalEvent.buttons !== 'undefined' ) { + pointsList.buttons = eventInfo.originalEvent.buttons; } - for ( i = 0; i < gPointCount; i++ ) { - curGPoint = gPoints[ i ]; - updateGPoint = pointsList.getById( curGPoint.id ); + updateGPoint = pointsList.getById( gPoint.id ); - if ( updateGPoint ) { - // Already tracking the pointer...update it - if ( Object.prototype.hasOwnProperty.call( curGPoint, 'isPrimary' ) ) { - updateGPoint.isPrimary = curGPoint.isPrimary; - } - updateGPoint.lastPos = updateGPoint.currentPos; - updateGPoint.lastTime = updateGPoint.currentTime; - updateGPoint.currentPos = curGPoint.currentPos; - updateGPoint.currentTime = curGPoint.currentTime; - } else { - // Initialize for tracking and add to the tracking list (no pointerover or pointerdown event occurred before this) - curGPoint.captured = false; // Handled by updatePointerCaptured() - curGPoint.insideElementPressed = false; - curGPoint.insideElement = true; - startTrackingPointer( pointsList, curGPoint ); + if ( updateGPoint ) { + // Already tracking the pointer...update it + if ( Object.prototype.hasOwnProperty.call( gPoint, 'isPrimary' ) ) { + updateGPoint.isPrimary = gPoint.isPrimary; } + updateGPoint.lastPos = updateGPoint.currentPos; + updateGPoint.lastTime = updateGPoint.currentTime; + updateGPoint.currentPos = gPoint.currentPos; + updateGPoint.currentTime = gPoint.currentTime; + } else { + // Initialize for tracking and add to the tracking list (no pointerover or pointerdown event occurred before this) + gPoint.captured = false; // Handled by updatePointerCaptured() + gPoint.insideElementPressed = false; + gPoint.insideElement = true; + startTrackingPointer( pointsList, gPoint ); } + eventInfo.shouldCapture = false; + eventInfo.shouldReleaseCapture = false; + // Stop (mouse only) - if ( tracker.stopHandler && gPoints[ 0 ].type === 'mouse' ) { + if ( tracker.stopHandler && gPoint.type === 'mouse' ) { clearTimeout( tracker.stopTimeOut ); tracker.stopTimeOut = setTimeout( function() { - handlePointerStop( tracker, event, gPoints[ 0 ].type ); + handlePointerStop( tracker, eventInfo.originalEvent, gPoint.type ); }, tracker.stopDelay ); } if ( pointsList.contacts === 0 ) { // Move (no contacts: hovering mouse or other hover-capable device) if ( tracker.moveHandler ) { - propagate = tracker.moveHandler( + tracker.moveHandler( { eventSource: tracker, - pointerType: gPoints[ 0 ].type, - position: getPointRelativeToAbsolute( gPoints[ 0 ].currentPos, tracker.element ), + pointerType: gPoint.type, + position: getPointRelativeToAbsolute( gPoint.currentPos, tracker.element ), buttons: pointsList.buttons, - isTouchEvent: gPoints[ 0 ].type === 'touch', - originalEvent: event, + isTouchEvent: gPoint.type === 'touch', + originalEvent: eventInfo.originalEvent, preventDefaultAction: false, userData: tracker.userData } ); - if ( propagate === false ) { - $.cancelEvent( event ); - } } } else if ( pointsList.contacts === 1 ) { // Move (1 contact) if ( tracker.moveHandler ) { updateGPoint = pointsList.asArray()[ 0 ]; - propagate = tracker.moveHandler( + tracker.moveHandler( { eventSource: tracker, pointerType: updateGPoint.type, position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), buttons: pointsList.buttons, isTouchEvent: updateGPoint.type === 'touch', - originalEvent: event, + originalEvent: eventInfo.originalEvent, preventDefaultAction: false, userData: tracker.userData } ); - if ( propagate === false ) { - $.cancelEvent( event ); - } } // Drag - if ( tracker.dragHandler ) { + if ( tracker.dragHandler && !eventInfo.preventGesture && !eventInfo.defaultPrevented ) { updateGPoint = pointsList.asArray()[ 0 ]; delta = updateGPoint.currentPos.minus( updateGPoint.lastPos ); - propagate = tracker.dragHandler( + tracker.dragHandler( { eventSource: tracker, pointerType: updateGPoint.type, @@ -3778,47 +4141,43 @@ delta: delta, speed: updateGPoint.speed, direction: updateGPoint.direction, - shift: event.shiftKey, + shift: eventInfo.originalEvent.shiftKey, isTouchEvent: updateGPoint.type === 'touch', - originalEvent: event, + originalEvent: eventInfo.originalEvent, preventDefaultAction: false, userData: tracker.userData } ); - if ( propagate === false ) { - $.cancelEvent( event ); - } + eventInfo.preventDefault = true; } } else if ( pointsList.contacts === 2 ) { // Move (2 contacts, use center) if ( tracker.moveHandler ) { gPointArray = pointsList.asArray(); - propagate = tracker.moveHandler( + tracker.moveHandler( { eventSource: tracker, pointerType: gPointArray[ 0 ].type, position: getPointRelativeToAbsolute( getCenterPoint( gPointArray[ 0 ].currentPos, gPointArray[ 1 ].currentPos ), tracker.element ), buttons: pointsList.buttons, isTouchEvent: gPointArray[ 0 ].type === 'touch', - originalEvent: event, + originalEvent: eventInfo.originalEvent, preventDefaultAction: false, userData: tracker.userData } ); - if ( propagate === false ) { - $.cancelEvent( event ); - } } // Pinch - if ( tracker.pinchHandler && gPoints[ 0 ].type === 'touch' ) { + if ( tracker.pinchHandler && gPoint.type === 'touch' && + !eventInfo.preventGesture && !eventInfo.defaultPrevented ) { delta = delegate.pinchGPoints[ 0 ].currentPos.distanceTo( delegate.pinchGPoints[ 1 ].currentPos ); if ( delta !== delegate.currentPinchDist ) { delegate.lastPinchDist = delegate.currentPinchDist; delegate.currentPinchDist = delta; delegate.lastPinchCenter = delegate.currentPinchCenter; delegate.currentPinchCenter = getCenterPoint( delegate.pinchGPoints[ 0 ].currentPos, delegate.pinchGPoints[ 1 ].currentPos ); - propagate = tracker.pinchHandler( + tracker.pinchHandler( { eventSource: tracker, pointerType: 'touch', @@ -3827,15 +4186,13 @@ center: getPointRelativeToAbsolute( delegate.currentPinchCenter, tracker.element ), lastDistance: delegate.lastPinchDist, distance: delegate.currentPinchDist, - shift: event.shiftKey, - originalEvent: event, + shift: eventInfo.originalEvent.shiftKey, + originalEvent: eventInfo.originalEvent, preventDefaultAction: false, userData: tracker.userData } ); - if ( propagate === false ) { - $.cancelEvent( event ); - } + eventInfo.preventDefault = true; } } } @@ -3848,14 +4205,23 @@ * @inner * @param {OpenSeadragon.MouseTracker} tracker * A reference to the MouseTracker instance. - * @param {Object} event - * A reference to the originating DOM event. - * @param {Array.} gPoints + * @param {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo + * Processing info for originating DOM event. + * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint * Gesture points associated with the event. */ - function updatePointersCancel( tracker, event, gPoints ) { - updatePointersUp( tracker, event, gPoints, 0 ); - updatePointersLeave( tracker, event, gPoints ); + function updatePointerCancel( tracker, eventInfo, gPoint ) { + var pointsList = tracker.getActivePointersListByType( gPoint.type ), + updateGPoint; + + updateGPoint = pointsList.getById( gPoint.id ); + + if ( updateGPoint ) { + stopTrackingPointer( pointsList, updateGPoint ); + } else { + // should never get here? + $.console.warn('updatePointerUp(): pointerup on untracked gPoint'); + } } diff --git a/src/navigator.js b/src/navigator.js index 4a8db20a..94452e42 100644 --- a/src/navigator.js +++ b/src/navigator.js @@ -167,12 +167,16 @@ $.Navigator = function( options ){ style.zIndex = 999999999; style.cursor = 'default'; }( this.displayRegion.style, this.borderWidth )); + $.setElementPointerEventsNone( this.displayRegion ); + $.setElementTouchActionNone( this.displayRegion ); this.displayRegionContainer = $.makeNeutralElement("div"); this.displayRegionContainer.id = this.element.id + '-displayregioncontainer'; this.displayRegionContainer.className = "displayregioncontainer"; this.displayRegionContainer.style.width = "100%"; this.displayRegionContainer.style.height = "100%"; + $.setElementPointerEventsNone( this.displayRegionContainer ); + $.setElementTouchActionNone( this.displayRegionContainer ); viewer.addControl( this.element, @@ -222,12 +226,21 @@ $.Navigator = function( options ){ this.innerTracker.destroy(); this.innerTracker = new $.MouseTracker({ userData: 'Navigator.innerTracker', - element: this.element, + element: this.element, //this.canvas, dragHandler: $.delegate( this, onCanvasDrag ), clickHandler: $.delegate( this, onCanvasClick ), releaseHandler: $.delegate( this, onCanvasRelease ), scrollHandler: $.delegate( this, onCanvasScroll ) }); + this.outerTracker.userData = 'Navigator.outerTracker'; + + // this.innerTracker is attached to this.element...we need to allow pointer + // events to pass through this Viewer's canvas/container elements so implicit + // pointer capture works on touch devices + //TODO an alternative is to attach the new MouseTracker to this.canvas...not + // sure why it isn't already (see MouseTracker constructor call above) + $.setElementPointerEventsNone( this.canvas ); + $.setElementPointerEventsNone( this.container ); this.addHandler("reset-size", function() { if (_this.viewport) { diff --git a/src/openseadragon.js b/src/openseadragon.js index d23291b8..9366c896 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -1949,6 +1949,19 @@ function OpenSeadragon( options ){ }, + /** + * Sets the specified element's pointer-events style attribute to 'none'. + * @function + * @param {Element|String} element + */ + setElementPointerEventsNone: function( element ) { + element = $.getElement( element ); + if ( typeof element.style.pointerEvents !== 'undefined' ) { + element.style.pointerEvents = 'none'; + } + }, + + /** * Add the specified CSS class to the element if not present. * @function diff --git a/src/referencestrip.js b/src/referencestrip.js index ede83662..ee609057 100644 --- a/src/referencestrip.js +++ b/src/referencestrip.js @@ -85,14 +85,7 @@ $.ReferenceStrip = function ( options ) { scroll: $.DEFAULT_SETTINGS.referenceStripScroll, clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold }, options, { - //required overrides - element: this.element, - //These need to be overridden to prevent recursion since - //the navigator is a viewer and a viewer has a navigator - showNavigator: false, - mouseNavEnabled: false, - showNavigationControl: false, - showSequenceControl: false + element: this.element } ); $.extend( this, options ); @@ -119,9 +112,10 @@ $.ReferenceStrip = function ( options ) { $.setElementOpacity( this.element, 0.8 ); this.viewer = viewer; - this.innerTracker = new $.MouseTracker( { - userData: 'ReferenceStrip.innerTracker', + this.tracker = new $.MouseTracker( { + userData: 'ReferenceStrip.tracker', element: this.element, + clickHandler: $.delegate( this, onStripClick ), dragHandler: $.delegate( this, onStripDrag ), scrollHandler: $.delegate( this, onStripScroll ), enterHandler: $.delegate( this, onStripEnter ), @@ -190,35 +184,12 @@ $.ReferenceStrip = function ( options ) { element.style.width = _this.panelWidth + 'px'; element.style.height = _this.panelHeight + 'px'; element.style.display = 'inline'; - element.style.float = 'left'; //Webkit + element.style['float'] = 'left'; //Webkit element.style.cssFloat = 'left'; //Firefox element.style.styleFloat = 'left'; //IE element.style.padding = '2px'; $.setElementTouchActionNone( element ); - - element.innerTracker = new $.MouseTracker( { - userData: 'ReferenceStrip.TileSource.innerTracker', - element: element, - clickTimeThreshold: this.clickTimeThreshold, - clickDistThreshold: this.clickDistThreshold, - pressHandler: function ( event ) { - event.eventSource.dragging = $.now(); - }, - releaseHandler: function ( event ) { - var tracker = event.eventSource, - id = tracker.element.id, - page = Number( id.split( '-' )[2] ), - now = $.now(); - - if ( event.insideElementPressed && - event.insideElementReleased && - tracker.dragging && - ( now - tracker.dragging ) < tracker.clickTimeThreshold ) { - tracker.dragging = null; - viewer.goToPage( page ); - } - } - } ); + $.setElementPointerEventsNone( element ); this.element.appendChild( element ); @@ -232,7 +203,8 @@ $.ReferenceStrip = function ( options ) { }; -$.extend( $.ReferenceStrip.prototype, $.EventSource.prototype, $.Viewer.prototype, /** @lends OpenSeadragon.ReferenceStrip.prototype */{ +/** @lends OpenSeadragon.ReferenceStrip.prototype */ +$.ReferenceStrip.prototype = { /** * @function @@ -279,7 +251,7 @@ $.extend( $.ReferenceStrip.prototype, $.EventSource.prototype, $.Viewer.prototyp } this.currentPage = page; - onStripEnter.call( this, { eventSource: this.innerTracker } ); + onStripEnter.call( this, { eventSource: this.tracker } ); } }, @@ -294,7 +266,6 @@ $.extend( $.ReferenceStrip.prototype, $.EventSource.prototype, $.Viewer.prototyp return false; }, - // Overrides Viewer.destroy destroy: function() { if (this.miniViewers) { for (var key in this.miniViewers) { @@ -302,14 +273,34 @@ $.extend( $.ReferenceStrip.prototype, $.EventSource.prototype, $.Viewer.prototyp } } + this.tracker.destroy(); + if (this.element) { this.element.parentNode.removeChild(this.element); } } -} ); +}; +/** + * @private + * @inner + * @function + */ +function onStripClick( event ) { + if ( event.quick ) { + var page; + + if ( 'horizontal' === this.scroll ) { + page = Math.floor(event.position.x / this.panelWidth); + } else { + page = Math.floor(event.position.y / this.panelHeight); + } + + this.viewer.goToPage( page ); + } +} /** @@ -319,13 +310,14 @@ $.extend( $.ReferenceStrip.prototype, $.EventSource.prototype, $.Viewer.prototyp */ function onStripDrag( event ) { - var offsetLeft = Number( this.element.style.marginLeft.replace( 'px', '' ) ), + this.dragging = true; + if ( this.element ) { + var offsetLeft = Number( this.element.style.marginLeft.replace( 'px', '' ) ), offsetTop = Number( this.element.style.marginTop.replace( 'px', '' ) ), scrollWidth = Number( this.element.style.width.replace( 'px', '' ) ), scrollHeight = Number( this.element.style.height.replace( 'px', '' ) ), viewerSize = $.getElementSize( this.viewer.canvas ); - this.dragging = true; - if ( this.element ) { + if ( 'horizontal' === this.scroll ) { if ( -event.delta.x > 0 ) { //forward @@ -368,12 +360,13 @@ function onStripDrag( event ) { * @function */ function onStripScroll( event ) { - var offsetLeft = Number( this.element.style.marginLeft.replace( 'px', '' ) ), + if ( this.element ) { + var offsetLeft = Number( this.element.style.marginLeft.replace( 'px', '' ) ), offsetTop = Number( this.element.style.marginTop.replace( 'px', '' ) ), scrollWidth = Number( this.element.style.width.replace( 'px', '' ) ), scrollHeight = Number( this.element.style.height.replace( 'px', '' ) ), viewerSize = $.getElementSize( this.viewer.canvas ); - if ( this.element ) { + if ( 'horizontal' === this.scroll ) { if ( event.scroll > 0 ) { //forward @@ -414,7 +407,6 @@ function loadPanels( strip, viewerSize, scroll ) { activePanelsStart, activePanelsEnd, miniViewer, - style, i, element; if ( 'horizontal' === strip.scroll ) { @@ -456,35 +448,14 @@ function loadPanels( strip, viewerSize, scroll ) { ajaxHeaders: strip.viewer.ajaxHeaders, useCanvas: strip.useCanvas } ); - - miniViewer.displayRegion = $.makeNeutralElement( "div" ); - miniViewer.displayRegion.id = element.id + '-displayregion'; - miniViewer.displayRegion.className = 'displayregion'; - - style = miniViewer.displayRegion.style; - style.position = 'relative'; - style.top = '0px'; - style.left = '0px'; - style.fontSize = '0px'; - style.overflow = 'hidden'; - style.float = 'left'; //Webkit - style.cssFloat = 'left'; //Firefox - style.styleFloat = 'left'; //IE - style.zIndex = 999999999; - style.cursor = 'default'; - style.width = ( strip.panelWidth - 4 ) + 'px'; - style.height = ( strip.panelHeight - 4 ) + 'px'; - - // TODO: What is this for? Future keyboard navigation support? - miniViewer.displayRegion.innerTracker = new $.MouseTracker( { - userData: 'ReferenceStrip.miniViewer.innerTracker', - element: miniViewer.displayRegion, - startDisabled: true - } ); - - element.getElementsByTagName( 'div' )[0].appendChild( - miniViewer.displayRegion - ); + // Allow pointer events to pass through miniViewer's canvas/container + // elements so implicit pointer capture works on touch devices + $.setElementPointerEventsNone( miniViewer.canvas ); + $.setElementPointerEventsNone( miniViewer.container ); + // We'll use event delegation from the reference strip element instead of + // handling events on every miniViewer + miniViewer.innerTracker.setTracking( false ); + miniViewer.outerTracker.setTracking( false ); strip.miniViewers[element.id] = miniViewer; diff --git a/src/viewer.js b/src/viewer.js index 73a2f395..7e92c6e5 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -267,6 +267,7 @@ $.Viewer = function( options ) { style.top = "0px"; style.textAlign = "left"; // needed to protect against }( this.container.style )); + $.setElementTouchActionNone( this.container ); this.container.insertBefore( this.canvas, this.container.firstChild ); this.element.appendChild( this.container ); @@ -405,6 +406,8 @@ $.Viewer = function( options ) { // Overlay container this.overlaysContainer = $.makeNeutralElement( "div" ); + $.setElementPointerEventsNone( this.overlaysContainer ); + $.setElementTouchActionNone( this.overlaysContainer ); this.canvas.appendChild( this.overlaysContainer ); // Now that we have a drawer, see if it supports rotate. If not we need to remove the rotate buttons From 5a9874e4fa61dff60fe8ae82110f3f2b7ce696da Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Thu, 13 Aug 2020 10:09:17 -0700 Subject: [PATCH 07/29] changelog update --- changelog.txt | 3 +++ src/mousetracker.js | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/changelog.txt b/changelog.txt index 79025147..7bbc562c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -22,6 +22,9 @@ OPENSEADRAGON CHANGELOG * Added pointerType property to Viewer container-enter, container-exit, canvas-drag, canvas-drag-end, canvas-pinch events * MouseTracker: Fire dragEndHandler event even if release point same as initial contact point (#1459) * MouseTracker: Pointer capture implemented with capture APIs where available. Only fallback to emulated capture on extremely old browsers +* MouseTracker: Added preProcessEventHandler option to allow MouseTracker instances to control bubbling and default behavior of events on their associated element +* Updated Viewer, Button, Drawer, Navigator, ReferenceStrip DOM for proper DOM event handling +* Added OpenSeadragon.setElementPointerEventsNone() for setting pointer-events:'none' on DOM elements 2.4.2: diff --git a/src/mousetracker.js b/src/mousetracker.js index 33a6e880..0ebf03a0 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -4218,10 +4218,11 @@ if ( updateGPoint ) { stopTrackingPointer( pointsList, updateGPoint ); - } else { - // should never get here? - $.console.warn('updatePointerUp(): pointerup on untracked gPoint'); } + //else { + // // should never get here? + // $.console.warn('updatePointerCancel(): pointercancel on untracked gPoint'); + //} } From 8fc83c696aa8d3014289e2a07991e975170849e1 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Thu, 13 Aug 2020 16:43:49 -0700 Subject: [PATCH 08/29] Improved releasing of tracked pointers on destroy()/stopTracking() (#1346) --- changelog.txt | 2 + src/mousetracker.js | 110 ++++++++++++++++++++------------------------ 2 files changed, 51 insertions(+), 61 deletions(-) diff --git a/changelog.txt b/changelog.txt index 7bbc562c..5d8f1978 100644 --- a/changelog.txt +++ b/changelog.txt @@ -23,6 +23,8 @@ OPENSEADRAGON CHANGELOG * MouseTracker: Fire dragEndHandler event even if release point same as initial contact point (#1459) * MouseTracker: Pointer capture implemented with capture APIs where available. Only fallback to emulated capture on extremely old browsers * MouseTracker: Added preProcessEventHandler option to allow MouseTracker instances to control bubbling and default behavior of events on their associated element +* MouseTracker: Improved handling of canceled events (#1728) +* MouseTracker: Improved releasing of tracked pointers on destroy()/stopTracking() (#1346) * Updated Viewer, Button, Drawer, Navigator, ReferenceStrip DOM for proper DOM event handling * Added OpenSeadragon.setElementPointerEventsNone() for setting pointer-events:'none' on DOM elements diff --git a/src/mousetracker.js b/src/mousetracker.js index 0ebf03a0..d2c3c89a 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -344,24 +344,26 @@ return this; }, - /** - * Returns the {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} for all but the given pointer device type. - * @function - * @param {String} type - The pointer device type: "mouse", "touch", "pen", etc. - * @returns {Array.} - */ - getActivePointersListsExceptType: function ( type ) { - var delegate = THIS[ this.hash ]; - var listArray = []; + // //TODO Revisit this if there's still an issue. The PointerEvent model should have no problems + // // like the issue this code attempts to fix. + // /** + // * Returns the {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} for all but the given pointer device type. + // * @function + // * @param {String} type - The pointer device type: "mouse", "touch", "pen", etc. + // * @returns {Array.} + // */ + // getActivePointersListsExceptType: function ( type ) { + // var delegate = THIS[ this.hash ]; + // var listArray = []; - for (var i = 0; i < delegate.activePointersLists.length; ++i) { - if (delegate.activePointersLists[i].type !== type) { - listArray.push(delegate.activePointersLists[i]); - } - } + // for (var i = 0; i < delegate.activePointersLists.length; ++i) { + // if (delegate.activePointersLists[i].type !== type) { + // listArray.push(delegate.activePointersLists[i]); + // } + // } - return listArray; - }, + // return listArray; + // }, /** * Returns the {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} for the given pointer device type, @@ -1012,6 +1014,7 @@ blurHandler: function () { } }; + // https://github.com/openseadragon/openseadragon/pull/790 /** * True if inside an iframe, otherwise false. * @member {Boolean} isInIframe @@ -1026,6 +1029,7 @@ } })(); + // https://github.com/openseadragon/openseadragon/pull/790 /** * @function * @private @@ -1451,7 +1455,7 @@ }, /** - * Increment this pointer's contact count. + * Increment this pointer list's contact count. * It will evaluate whether this pointer type is allowed to have multiple contacts. * @function */ @@ -1465,7 +1469,7 @@ }, /** - * Decrement this pointer's contact count. + * Decrement this pointer list's contact count. * It will make sure the count does not go below 0. * @function */ @@ -1491,49 +1495,28 @@ */ function clearTrackedPointers( tracker ) { var delegate = THIS[ tracker.hash ], - i, + i, j, + pointsList, + gPoints, + gPointsToRemove, pointerListCount = delegate.activePointersLists.length; for ( i = 0; i < pointerListCount; i++ ) { - if ( delegate.activePointersLists[ i ].captureCount > 0 ) { - $.removeEvent( - $.MouseTracker.captureElement, - 'mousemove', - delegate.mousemovecaptured, - true - ); - $.removeEvent( - $.MouseTracker.captureElement, - 'mouseup', - delegate.mouseupcaptured, - true - ); - $.removeEvent( - $.MouseTracker.captureElement, - $.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove', - delegate.pointermovecaptured, - true - ); - $.removeEvent( - $.MouseTracker.captureElement, - $.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp', - delegate.pointerupcaptured, - true - ); - $.removeEvent( - $.MouseTracker.captureElement, - 'touchmove', - delegate.touchmovecaptured, - true - ); - $.removeEvent( - $.MouseTracker.captureElement, - 'touchend', - delegate.touchendcaptured, - true - ); + pointsList = delegate.activePointersLists[ i ]; - delegate.activePointersLists[ i ].captureCount = 0; + if ( pointsList.getLength() > 0 ) { + // Make an array containing references to the gPoints in the pointer list + // (because calls to stopTrackingPointer() are going to modify the pointer list) + gPointsToRemove = []; + gPoints = pointsList.asArray(); + for ( j = 0; j < gPoints.length; j++ ) { + gPointsToRemove.push( gPoints[ j ] ); + } + + // Release and remove all gPoints from the pointer list + for ( j = 0; j < gPointsToRemove.length; j++ ) { + stopTrackingPointer( tracker, pointsList, gPointsToRemove[ j ] ); + } } } @@ -1656,6 +1639,7 @@ // eslint-disable-next-line no-use-before-define //$.console.log('Emulated mouse capture set'); eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : gPoint.type ); + // https://github.com/openseadragon/openseadragon/pull/790 if (isInIframe && canAccessEvents(window.top)) { $.addEvent( window.top, @@ -1708,6 +1692,7 @@ // (Note we listen on the capture phase so the captured handlers will get called first) //$.console.log('Emulated mouse capture release'); eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : gPoint.type ); + // https://github.com/openseadragon/openseadragon/pull/790 if (isInIframe && canAccessEvents(window.top)) { $.removeEvent( window.top, @@ -3192,19 +3177,22 @@ * @function * @private * @inner + * @param {OpenSeadragon.MouseTracker} tracker + * A reference to the MouseTracker instance. * @param {OpenSeadragon.MouseTracker.GesturePointList} pointsList * The GesturePointList to stop tracking the pointer on. * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint * Gesture point to stop tracking. * @returns {Number} Number of gesture points in pointsList. */ - function stopTrackingPointer( pointsList, gPoint ) { + function stopTrackingPointer( tracker, pointsList, gPoint ) { var listLength; var trackedGPoint = pointsList.getById( gPoint.id ); if ( trackedGPoint ) { if ( trackedGPoint.captured ) { + releasePointer( tracker, trackedGPoint ); pointsList.removeContact(); } @@ -3451,7 +3439,7 @@ updateGPoint.currentPos = gPoint.currentPos; updateGPoint.currentTime = gPoint.currentTime; } else { - stopTrackingPointer( pointsList, updateGPoint ); + stopTrackingPointer( tracker, pointsList, updateGPoint ); } gPoint = updateGPoint; @@ -3883,7 +3871,7 @@ updateGPoint.currentPos = gPoint.currentPos; updateGPoint.currentTime = gPoint.currentTime; if ( !updateGPoint.insideElement ) { - stopTrackingPointer( pointsList, updateGPoint ); + stopTrackingPointer( tracker, pointsList, updateGPoint ); } releasePoint = updateGPoint.currentPos; @@ -4217,7 +4205,7 @@ updateGPoint = pointsList.getById( gPoint.id ); if ( updateGPoint ) { - stopTrackingPointer( pointsList, updateGPoint ); + stopTrackingPointer( tracker, pointsList, updateGPoint ); } //else { // // should never get here? From 6b500f1ff2ee7570641c1cf39264ec09916f7353 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Thu, 13 Aug 2020 17:21:32 -0700 Subject: [PATCH 09/29] docs update --- src/mousetracker.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/mousetracker.js b/src/mousetracker.js index d2c3c89a..3b35bbca 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -175,7 +175,7 @@ this.preProcessEventHandler = options.preProcessEventHandler || null; this.enterHandler = options.enterHandler || null; this.leaveHandler = options.leaveHandler || null; - this.exitHandler = options.exitHandler || null; // Deprecated v2.4.3 + this.exitHandler = options.exitHandler || null; // Deprecated v2.5.0 this.overHandler = options.overHandler || null; this.outHandler = options.outHandler || null; this.pressHandler = options.pressHandler || null; @@ -449,7 +449,7 @@ * Implement or assign implementation to these handlers during or after * calling the constructor. * @function - * @since v2.4.3 + * @since v2.5.0 * @param {Object} event * @param {OpenSeadragon.MouseTracker} event.eventSource * A reference to the tracker instance. @@ -480,7 +480,7 @@ * Implement or assign implementation to these handlers during or after * calling the constructor. * @function - * @deprecated v2.4.3 Use leaveHandler instead + * @deprecated v2.5.0 Use leaveHandler instead * @param {Object} event * @param {OpenSeadragon.MouseTracker} event.eventSource * A reference to the tracker instance. @@ -513,7 +513,7 @@ * Implement or assign implementation to these handlers during or after * calling the constructor. * @function - * @since v2.4.3 + * @since v2.5.0 * @param {Object} event * @param {OpenSeadragon.MouseTracker} event.eventSource * A reference to the tracker instance. @@ -546,7 +546,7 @@ * Implement or assign implementation to these handlers during or after * calling the constructor. * @function - * @since v2.4.3 + * @since v2.5.0 * @param {Object} event * @param {OpenSeadragon.MouseTracker} event.eventSource * A reference to the tracker instance. @@ -1252,6 +1252,7 @@ * * @typedef {Object} EventProcessInfo * @memberof OpenSeadragon.MouseTracker + * @since v2.5.0 * * @property {OpenSeadragon.MouseTracker} eventSource * A reference to the tracker instance. From 3769af532b62dad2079e0a68fea89dffd206f704 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Thu, 13 Aug 2020 19:56:22 -0700 Subject: [PATCH 10/29] Fixed tests to account for #1459 fix (fire drag-end event even if pointer didn't move) --- src/openseadragon.js | 2 +- test/helpers/legacy.mouse.shim.js | 11 ++++++++++- test/modules/events.js | 4 ++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/openseadragon.js b/src/openseadragon.js index 9366c896..97602289 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -2048,7 +2048,7 @@ function OpenSeadragon( options ){ }, /** - * COnvert passed addEventListener() options to boolean or options object, + * Convert passed addEventListener() options to boolean or options object, * depending on browser support. * @function * @param {Boolean|Object} [options] Boolean useCapture, or if [supportsEventListenerOptions]{@link OpenSeadragon.supportsEventListenerOptions}, can be an object diff --git a/test/helpers/legacy.mouse.shim.js b/test/helpers/legacy.mouse.shim.js index 3a318a1f..e4740138 100644 --- a/test/helpers/legacy.mouse.shim.js +++ b/test/helpers/legacy.mouse.shim.js @@ -12,6 +12,7 @@ } $.MouseTracker.havePointerEvents = false; + $.MouseTracker.unprefixedPointerEvents = true; $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" ); if ( $.Browser.vendor !== $.BROWSERS.IE || $.Browser.version > 8 ) { $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout" ); @@ -20,6 +21,15 @@ $.MouseTracker.havePointerOverOut = false; } $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" ); + $.MouseTracker.mousePointerId = "legacy-mouse"; + // Legacy mouse events capture support (IE/Firefox only?) + $.MouseTracker.havePointerCapture = (function () { + var divElement = document.createElement( 'div' ); + return $.isFunction( divElement.setCapture ) && $.isFunction( divElement.releaseCapture ); + }()); + if ( $.MouseTracker.havePointerCapture ) { + $.MouseTracker.subscribeEvents.push( "losecapture" ); + } if ( 'ontouchstart' in window ) { // iOS, Android, and other W3c Touch Event implementations // (see http://www.w3.org/TR/touch-events/) @@ -32,6 +42,5 @@ // Subscribe to these to prevent default gesture handling $.MouseTracker.subscribeEvents.push( "gesturestart", "gesturechange" ); } - $.MouseTracker.mousePointerId = "legacy-mouse"; }(OpenSeadragon)); diff --git a/test/modules/events.js b/test/modules/events.js index 68e6232d..d07a3eea 100644 --- a/test/modules/events.js +++ b/test/modules/events.js @@ -452,7 +452,7 @@ clickCount: 2, dblClickCount: 1, dragCount: 0, - dragEndCount: 0, + dragEndCount: 2, // v2.5.0+ drag-end event now fired even if pointer didn't move (#1459) insideElementPressed: true, insideElementReleased: true, contacts: 0, @@ -480,7 +480,7 @@ clickCount: 1, dblClickCount: 0, dragCount: 0, - dragEndCount: 0, + dragEndCount: 1, // v2.5.0+ drag-end event now fired even if pointer didn't move (#1459) insideElementPressed: true, insideElementReleased: true, contacts: 0, From b07d47011ffd16147a4e86f1e373b8f8eee29010 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Fri, 14 Aug 2020 16:42:06 -0700 Subject: [PATCH 11/29] preventDefaultAction docs --- src/navigator.js | 2 +- src/viewer.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/navigator.js b/src/navigator.js index 94452e42..cbe0780e 100644 --- a/src/navigator.js +++ b/src/navigator.js @@ -540,7 +540,7 @@ function onCanvasDrag( event ) { * @property {Boolean} shift - True if the shift key was pressed during this event. * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. - * @property {Boolean} preventDefaultAction - Set to true to prevent default click to zoom behaviour. Default: false. + * @property {Boolean} preventDefaultAction - Set to true to prevent default drag to pan behaviour. Default: false. */ this.viewer.raiseEvent('navigator-drag', canvasDragEventArgs); diff --git a/src/viewer.js b/src/viewer.js index 7e92c6e5..24b88109 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2812,7 +2812,7 @@ function onCanvasDrag( event ) { * @property {Number} direction - Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0. * @property {Boolean} shift - True if the shift key was pressed during this event. * @property {Object} originalEvent - The original DOM event. - * @property {Boolean} preventDefaultAction - Set to true to prevent default drag behaviour. Default: false. + * @property {Boolean} preventDefaultAction - Set to true to prevent default drag to pan behaviour. Default: false. * @property {?Object} userData - Arbitrary subscriber-defined object. */ this.raiseEvent( 'canvas-drag', canvasDragEventArgs); From dc8026407484a7271eaf19ffb9571ac23959675f Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Fri, 14 Aug 2020 20:50:37 -0700 Subject: [PATCH 12/29] changelog update --- changelog.txt | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/changelog.txt b/changelog.txt index 532fd230..4223234b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,33 +1,33 @@ OPENSEADRAGON CHANGELOG ======================= -2.4.3: (In progress) +2.5.0: (In progress) * Now when "simple image" tile sources are removed from the viewer, they free the memory used by the pyramid they create (#1789 @TakumaKira) * Documentation fix (#1814 @kenanchristian) * Better cleanup on destruction, to avoid memory leaks (#1832 @JoFrMueller) * Miscellaneous code cleanup (#1840 @msalsbery) * You can now specify tileSize for the Zoomify Tile Source (#1868 @abrlam) -* Improved browser sniffing - detect EDGE and CHROMEEDGE browsers -* Improved DOM event model feature detection -* Added support for options parameter on addEvent()/removeEvent (to support passive option) (#1669) -* Added OpenSeadragon.eventIsCanceled() function for defaultPrevented detection on DOM events -* MouseTracker: better PointerEvent model detection - removed use of deprecated window.navigator.pointerEnabled -* MouseTracker: added overHandler/outHandler options for handling corresponding pointerover/pointerout events -* MouseTracker: changed enterHandler/leaveHandler to use DOM pointerenter/pointerleave events instead of simulating using pointerover/pointerout -* All internal uses of MouseTracker use pointerenter/pointerleave events instead of pointerover/pointerout events for more consistent pointer tracking -* DEPRECATION: MouseTracker exitHandler deprecated for name change to leaveHandler for consistency with DOM event names -* Fixed bug in Button class where two MouseTracker event handlers used an invalid "this" causing issues in some browsers -* MouseTracker: IE 10 - MSPointerEnter/MSPointerLeave didn't exist then, simulated with MSPointerOver/MSPointerOut -* MouseTracker: Simulate mouseover/mouseout on IE < 9 -* Added pointerType property to Viewer container-enter, container-exit, canvas-drag, canvas-drag-end, canvas-pinch events -* MouseTracker: Fire dragEndHandler event even if release point same as initial contact point (#1459) -* MouseTracker: Pointer capture implemented with capture APIs where available. Only fallback to emulated capture on extremely old browsers -* MouseTracker: Added preProcessEventHandler option to allow MouseTracker instances to control bubbling and default behavior of events on their associated element -* MouseTracker: Improved handling of canceled events (#1728) -* MouseTracker: Improved releasing of tracked pointers on destroy()/stopTracking() (#1346) -* Updated Viewer, Button, Drawer, Navigator, ReferenceStrip DOM for proper DOM event handling -* Added OpenSeadragon.setElementPointerEventsNone() for setting pointer-events:'none' on DOM elements +* Improved browser sniffing - detect EDGE and CHROMEEDGE browsers (#1872 @msalsbery) +* Improved DOM event model feature detection (#1872 @msalsbery) +* Added support for options parameter on addEvent()/removeEvent (to support passive option) (#1872 @msalsbery) +* Added OpenSeadragon.eventIsCanceled() function for defaultPrevented detection on DOM events (#1872 @msalsbery) +* MouseTracker: better PointerEvent model detection - removed use of deprecated window.navigator.pointerEnabled (#1872 @msalsbery) +* MouseTracker: added overHandler/outHandler options for handling corresponding pointerover/pointerout events (#1872 @msalsbery) +* MouseTracker: changed enterHandler/leaveHandler to use DOM pointerenter/pointerleave events instead of simulating using pointerover/pointerout (#1872 @msalsbery) +* All internal uses of MouseTracker use pointerenter/pointerleave events instead of pointerover/pointerout events for more consistent pointer tracking (#1872 @msalsbery) +* DEPRECATION: MouseTracker exitHandler deprecated for name change to leaveHandler for consistency with DOM event names (#1872 @msalsbery) +* Fixed bug in Button class where two MouseTracker event handlers used an invalid "this" causing issues in some browsers (#1872 @msalsbery) +* MouseTracker: IE 10 - MSPointerEnter/MSPointerLeave didn't exist then, simulated with MSPointerOver/MSPointerOut (#1872 @msalsbery) +* MouseTracker: Simulate mouseover/mouseout on IE < 9 (#1872 @msalsbery) +* Added pointerType property to Viewer container-enter, container-exit, canvas-drag, canvas-drag-end, canvas-pinch events (#1872 @msalsbery) +* MouseTracker: Fire dragEndHandler event even if release point same as initial contact point (#1872 @msalsbery) +* MouseTracker: Pointer capture implemented with capture APIs where available. Only fallback to emulated capture on extremely old browsers (#1872 @msalsbery) +* MouseTracker: Added preProcessEventHandler option to allow MouseTracker instances to control bubbling and default behavior of events on their associated element (#1872 @msalsbery) +* MouseTracker: Improved handling of canceled events (#1872 @msalsbery) +* MouseTracker: Improved releasing of tracked pointers on destroy()/stopTracking() (#1872 @msalsbery) +* Updated Viewer, Button, Drawer, Navigator, ReferenceStrip DOM for proper DOM event handling (#1872 @msalsbery) +* Added OpenSeadragon.setElementPointerEventsNone() for setting pointer-events:'none' on DOM elements (#1872 @msalsbery) 2.4.2: From 11f4f31ba3a81bebf8225e7716f8660d76dbd7ea Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Fri, 14 Aug 2020 21:37:26 -0700 Subject: [PATCH 13/29] changelog - deprecation to top --- changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 4223234b..4a258184 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,7 @@ OPENSEADRAGON CHANGELOG 2.5.0: (In progress) +* DEPRECATION: MouseTracker exitHandler deprecated for name change to leaveHandler for consistency with DOM event names (#1872 @msalsbery) * Now when "simple image" tile sources are removed from the viewer, they free the memory used by the pyramid they create (#1789 @TakumaKira) * Documentation fix (#1814 @kenanchristian) * Better cleanup on destruction, to avoid memory leaks (#1832 @JoFrMueller) @@ -16,7 +17,6 @@ OPENSEADRAGON CHANGELOG * MouseTracker: added overHandler/outHandler options for handling corresponding pointerover/pointerout events (#1872 @msalsbery) * MouseTracker: changed enterHandler/leaveHandler to use DOM pointerenter/pointerleave events instead of simulating using pointerover/pointerout (#1872 @msalsbery) * All internal uses of MouseTracker use pointerenter/pointerleave events instead of pointerover/pointerout events for more consistent pointer tracking (#1872 @msalsbery) -* DEPRECATION: MouseTracker exitHandler deprecated for name change to leaveHandler for consistency with DOM event names (#1872 @msalsbery) * Fixed bug in Button class where two MouseTracker event handlers used an invalid "this" causing issues in some browsers (#1872 @msalsbery) * MouseTracker: IE 10 - MSPointerEnter/MSPointerLeave didn't exist then, simulated with MSPointerOver/MSPointerOut (#1872 @msalsbery) * MouseTracker: Simulate mouseover/mouseout on IE < 9 (#1872 @msalsbery) From f38e0c7967d1175dc198b4856b237a5885f96577 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Fri, 14 Aug 2020 22:03:23 -0700 Subject: [PATCH 14/29] diff review cleanup --- src/openseadragon.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/openseadragon.js b/src/openseadragon.js index 97602289..98d7fa33 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -918,7 +918,7 @@ function OpenSeadragon( options ){ }; /** - * True if the browser supports the EventTarget.removeEventListener() method + * True if the browser supports the EventTarget.addEventListener() method * @member {Boolean} supportsAddEventListener * @memberof OpenSeadragon */ @@ -2087,9 +2087,6 @@ function OpenSeadragon( options ){ * @param {Boolean} [options.passive] * @param {Boolean} [options.once] */ - // undefined - false or {capture: false} - // bool - bool or (capture: bool} - // obje - obje.capture or obje addEvent: (function () { if ( $.supportsAddEventListener ) { return function ( element, eventName, handler, options ) { From e7bc65c0af58b0963498269feaed88b794769f68 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Tue, 18 Aug 2020 20:03:38 -0700 Subject: [PATCH 15/29] Remove stopImmediatePropagation stub for implementation in future release --- src/mousetracker.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/mousetracker.js b/src/mousetracker.js index 3b35bbca..581bc19b 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -1281,9 +1281,6 @@ * Valid on eventType "pointerdown". * @property {Boolean} stopPropagation * Set to true prevent the event from propagating to ancestor/descendent elements on capture/bubble phase. - * @property {Boolean} stopImmediatePropagation - * Same as stopPropagation, but also prevents any other handlers on the tracker's element for - * this event from being called. * @property {Boolean} shouldCapture * (Internal Use) Set to true if the pointer should be captured (events (re)targeted to tracker element). * @property {Boolean} shouldReleaseCapture @@ -3231,7 +3228,6 @@ eventInfo.preventDefault = false; eventInfo.preventGesture = !tracker.hasGestureHandlers; eventInfo.stopPropagation = false; - eventInfo.stopImmediatePropagation = false; break; case 'pointerover': case 'pointerout': @@ -3240,7 +3236,6 @@ eventInfo.preventDefault = false; eventInfo.preventGesture = false; eventInfo.stopPropagation = false; - eventInfo.stopImmediatePropagation = false; break; case 'pointerdown': eventInfo.isStopable = true; @@ -3248,7 +3243,6 @@ eventInfo.preventDefault = false;//tracker.hasGestureHandlers; eventInfo.preventGesture = !tracker.hasGestureHandlers; eventInfo.stopPropagation = false; - eventInfo.stopImmediatePropagation = false; break; case 'pointerup': eventInfo.isStopable = true; @@ -3256,7 +3250,6 @@ eventInfo.preventDefault = false; eventInfo.preventGesture = !tracker.hasGestureHandlers; eventInfo.stopPropagation = false; - eventInfo.stopImmediatePropagation = false; break; case 'wheel': eventInfo.isStopable = true; @@ -3264,7 +3257,6 @@ eventInfo.preventDefault = false;//tracker.hasScrollHandler; eventInfo.preventGesture = !tracker.hasScrollHandler; eventInfo.stopPropagation = false; - eventInfo.stopImmediatePropagation = false; break; case 'gotpointercapture': case 'lostpointercapture': @@ -3274,7 +3266,6 @@ eventInfo.preventDefault = false; eventInfo.preventGesture = false; eventInfo.stopPropagation = false; - eventInfo.stopImmediatePropagation = false; break; case 'pointerenter': case 'pointerleave': @@ -3284,7 +3275,6 @@ eventInfo.preventDefault = false; eventInfo.preventGesture = false; eventInfo.stopPropagation = false; - eventInfo.stopImmediatePropagation = false; break; } } From c22e624a3dd113e1007f1df75afd9a9259f71742 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Thu, 20 Aug 2020 16:56:57 -0700 Subject: [PATCH 16/29] isPrimary handling --- src/mousetracker.js | 48 ++++----------------------------------------- 1 file changed, 4 insertions(+), 44 deletions(-) diff --git a/src/mousetracker.js b/src/mousetracker.js index 581bc19b..5bb32655 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -2069,7 +2069,7 @@ * @inner */ function onLoseCapture( tracker, event ) { - $.console.log('losecapture ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + //$.console.log('losecapture ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); event = $.getEvent( event ); var gPoint = { @@ -2518,7 +2518,8 @@ gPoint = { id: event.changedTouches[ i ].identifier, type: 'touch', - // isPrimary not set - let the updatePointers functions determine it + // Simulate isPrimary + isPrimary: pointsList.getLength() === 0, currentPos: getMouseAbsolute( event.changedTouches[ i ] ), currentTime: time }; @@ -2566,7 +2567,6 @@ gPoint = { id: event.changedTouches[ i ].identifier, type: 'touch', - // isPrimary not set - let the updatePointers functions determine it currentPos: getMouseAbsolute( event.changedTouches[ i ] ), currentTime: time }; @@ -2612,7 +2612,6 @@ gPoint = { id: event.changedTouches[ i ].identifier, type: 'touch', - // isPrimary not set - let the updatePointers functions determine it currentPos: getMouseAbsolute( event.changedTouches[ i ] ), currentTime: time }; @@ -2705,8 +2704,6 @@ if ( event.target === tracker.element ) { //$.console.log('gotpointercapture ' + (tracker.userData ? tracker.userData.toString() : '')); - ////$.cancelEvent( event ); not cancelable! - //$.stopEvent( event ); updatePointerCaptured( tracker, { id: event.pointerId, type: getPointerType( event ) @@ -2736,8 +2733,6 @@ if ( event.target === tracker.element ) { //$.console.log('lostpointercapture ' + (tracker.userData ? tracker.userData.toString() : '')); - ////$.cancelEvent( event ); not cancelable! - //$.stopEvent( event ); updatePointerCaptured( tracker, { id: event.pointerId, type: getPointerType( event ) @@ -2923,7 +2918,6 @@ //$.console.log('onPointerDown ' + (tracker.userData ? tracker.userData.toString() : '')); // $.console.log('onPointerDown ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + event.target.tagName); - // event.target.style.background = '#F0F'; // Most browsers implicitly capture touch pointer events // Note no IE versions have element.hasPointerCapture() so no implicit @@ -2963,17 +2957,12 @@ $.stopEvent( event ); } if ( eventInfo.shouldCapture && !implicitlyCaptured ) { - //$.stopEvent( event ); //$.console.log('pointerdown calling capturePointer() ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); capturePointer( tracker, gPoint ); } else if ( !eventInfo.shouldCapture && implicitlyCaptured ) { - //$.stopEvent( event ); //$.console.log('pointerdown calling releasePointer() ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); releasePointer( tracker, gPoint ); //TODO should we do this? Investigate when implementing bubble handling } - // else if ( eventInfo.shouldCapture && implicitlyCaptured ) { - // //$.stopEvent( event ); - // } } @@ -3111,7 +3100,7 @@ * @inner */ function onPointerCancel( tracker, event ) { - //$.console.log('pointercancel ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.isPrimary ? 'isPrimary' : '')); + //$.console.log('pointercancel ' + (tracker.userData ? tracker.userData.toString() : '')); var gPoint = { id: event.pointerId, @@ -3150,16 +3139,6 @@ * @returns {Number} Number of gesture points in pointsList. */ function startTrackingPointer( pointsList, gPoint ) { - - // If isPrimary is not known for the pointer then set it according to our rules: - // true if the first pointer in the gesture, otherwise false - if ( !Object.prototype.hasOwnProperty.call( gPoint, 'isPrimary' ) ) { - if ( pointsList.getLength() === 0 ) { - gPoint.isPrimary = true; - } else { - gPoint.isPrimary = false; - } - } gPoint.speed = 0; gPoint.direction = 0; gPoint.contactPos = gPoint.currentPos; @@ -3195,18 +3174,6 @@ } listLength = pointsList.removeById( gPoint.id ); - - //TODO Browsers don't re-assign primary pointers so this is probably incorrect - // // If isPrimary is not known for the pointer and we just removed the primary pointer from the list then we need to set another pointer as primary - // if ( !Object.prototype.hasOwnProperty.call( gPoint, 'isPrimary' ) ) { - // primaryPoint = pointsList.getPrimary(); - // if ( !primaryPoint ) { - // primaryPoint = pointsList.getByIndex( 0 ); - // if ( primaryPoint ) { - // primaryPoint.isPrimary = true; - // } - // } - // } } else { listLength = pointsList.getLength(); } @@ -4047,9 +4014,6 @@ if ( updateGPoint ) { // Already tracking the pointer...update it - if ( Object.prototype.hasOwnProperty.call( gPoint, 'isPrimary' ) ) { - updateGPoint.isPrimary = gPoint.isPrimary; - } updateGPoint.lastPos = updateGPoint.currentPos; updateGPoint.lastTime = updateGPoint.currentTime; updateGPoint.currentPos = gPoint.currentPos; @@ -4198,10 +4162,6 @@ if ( updateGPoint ) { stopTrackingPointer( tracker, pointsList, updateGPoint ); } - //else { - // // should never get here? - // $.console.warn('updatePointerCancel(): pointercancel on untracked gPoint'); - //} } From 5360e65ee933e0749366536145acbf750f871be3 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Tue, 25 Aug 2020 08:51:00 -0700 Subject: [PATCH 17/29] setPointerCapture/releasePointerCapture exception handling --- src/mousetracker.js | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/mousetracker.js b/src/mousetracker.js index 5bb32655..ba8116e4 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -1620,12 +1620,18 @@ if ( $.MouseTracker.havePointerCapture ) { if ( $.MouseTracker.havePointerEvents ) { - if ( $.MouseTracker.unprefixedPointerEvents ) { - tracker.element.setPointerCapture( gPoint.id ); - //$.console.log('element.setPointerCapture() called'); - } else { - tracker.element.msSetPointerCapture( gPoint.id ); - //$.console.log('element.msSetPointerCapture() called'); + // Can throw InvalidPointerId + // (should never happen for setPointerCapture so we'll log a warning) + try { + if ( $.MouseTracker.unprefixedPointerEvents ) { + tracker.element.setPointerCapture( gPoint.id ); + //$.console.log('element.setPointerCapture() called'); + } else { + tracker.element.msSetPointerCapture( gPoint.id ); + //$.console.log('element.msSetPointerCapture() called'); + } + } catch ( e ) { + $.console.warn('setPointerCapture() called on invalid pointer ID'); } } else { tracker.element.setCapture( true ); @@ -1674,12 +1680,19 @@ if ( $.MouseTracker.havePointerCapture ) { if ( $.MouseTracker.havePointerEvents ) { - if ( $.MouseTracker.unprefixedPointerEvents ) { - tracker.element.releasePointerCapture( gPoint.id ); - //$.console.log('element.releasePointerCapture() called'); - } else { - tracker.element.msReleasePointerCapture( gPoint.id ); - //$.console.log('element.msReleasePointerCapture() called'); + // Can throw InvalidPointerId + // (can happen depending on browser event timing (Firefox touch) + // so we won't log a warning) + try { + if ( $.MouseTracker.unprefixedPointerEvents ) { + tracker.element.releasePointerCapture( gPoint.id ); + //$.console.log('element.releasePointerCapture() called'); + } else { + tracker.element.msReleasePointerCapture( gPoint.id ); + //$.console.log('element.msReleasePointerCapture() called'); + } + } catch ( e ) { + /* eslint-disable no-empty */ } } else { tracker.element.releaseCapture(); From 35b1dcf36221af5621689c97388a064d7e8d2d5a Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Tue, 25 Aug 2020 09:11:00 -0700 Subject: [PATCH 18/29] Better setPointerCapture/releasePointerCapture exception handling --- src/mousetracker.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/mousetracker.js b/src/mousetracker.js index ba8116e4..dd24bfc7 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -1621,7 +1621,7 @@ if ( $.MouseTracker.havePointerCapture ) { if ( $.MouseTracker.havePointerEvents ) { // Can throw InvalidPointerId - // (should never happen for setPointerCapture so we'll log a warning) + // (should never happen so we'll log a warning) try { if ( $.MouseTracker.unprefixedPointerEvents ) { tracker.element.setPointerCapture( gPoint.id ); @@ -1677,12 +1677,18 @@ */ function releasePointer( tracker, gPoint ) { var eventParams; + var pointsList; + var cachedGPoint; if ( $.MouseTracker.havePointerCapture ) { if ( $.MouseTracker.havePointerEvents ) { + pointsList = tracker.getActivePointersListByType( gPoint.type ); + cachedGPoint = pointsList.getById( gPoint.id ); + if ( !cachedGPoint || !cachedGPoint.captured ) { + return; + } // Can throw InvalidPointerId - // (can happen depending on browser event timing (Firefox touch) - // so we won't log a warning) + // (should never happen so we'll log a warning) try { if ( $.MouseTracker.unprefixedPointerEvents ) { tracker.element.releasePointerCapture( gPoint.id ); @@ -1692,7 +1698,7 @@ //$.console.log('element.msReleasePointerCapture() called'); } } catch ( e ) { - /* eslint-disable no-empty */ + $.console.warn('releasePointerCapture() called on invalid pointer ID'); } } else { tracker.element.releaseCapture(); From c969f852398f3480b50794bf8e449fee4bcb2312 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Tue, 25 Aug 2020 11:46:35 -0700 Subject: [PATCH 19/29] contextmenu --- changelog.txt | 2 ++ src/mousetracker.js | 74 ++++++++++++++++++++++++++++++++++++++++++--- src/viewer.js | 21 +++++++++++++ 3 files changed, 93 insertions(+), 4 deletions(-) diff --git a/changelog.txt b/changelog.txt index 9b7ee4ff..d29ef6e1 100644 --- a/changelog.txt +++ b/changelog.txt @@ -29,6 +29,8 @@ OPENSEADRAGON CHANGELOG * MouseTracker: Improved releasing of tracked pointers on destroy()/stopTracking() (#1872 @msalsbery) * Updated Viewer, Button, Drawer, Navigator, ReferenceStrip DOM for proper DOM event handling (#1872 @msalsbery) * Added OpenSeadragon.setElementPointerEventsNone() for setting pointer-events:'none' on DOM elements (#1872 @msalsbery) +* MouseTracker: added contextMenuHandler option for handling contextmenu events (#1872 @msalsbery) +* Viewer: added a canvas-contextmenu event (#1872 @msalsbery) 2.4.2: diff --git a/src/mousetracker.js b/src/mousetracker.js index dd24bfc7..01469a65 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -74,6 +74,8 @@ * event is fired. * @param {OpenSeadragon.EventHandler} [options.preProcessEventHandler=null] * An optional handler for controlling DOM event propagation and processing. + * @param {OpenSeadragon.EventHandler} [options.contextMenuHandler=null] + * An optional handler for contextmenu. * @param {OpenSeadragon.EventHandler} [options.enterHandler=null] * An optional handler for pointer enter. * @param {OpenSeadragon.EventHandler} [options.leaveHandler=null] @@ -173,6 +175,7 @@ this.stopDelay = options.stopDelay || 50; this.preProcessEventHandler = options.preProcessEventHandler || null; + this.contextMenuHandler = options.contextMenuHandler || null; this.enterHandler = options.enterHandler || null; this.leaveHandler = options.leaveHandler || null; this.exitHandler = options.exitHandler || null; // Deprecated v2.5.0 @@ -213,6 +216,7 @@ keypress: function ( event ) { onKeyPress( _this, event ); }, focus: function ( event ) { onFocus( _this, event ); }, blur: function ( event ) { onBlur( _this, event ); }, + contextmenu: function ( event ) { onContextMenu( _this, event ); }, wheel: function ( event ) { onWheel( _this, event ); }, mousewheel: function ( event ) { onMouseWheel( _this, event ); }, @@ -288,6 +292,7 @@ this.dragHandler || this.dragEndHandler || this.pinchHandler ); this.hasScrollHandler = !!this.scrollHandler; + this.hasContextMenuHandler = !!this.contextMenuHandler; if ( !options.startDisabled ) { this.setTracking( true ); @@ -415,6 +420,22 @@ */ preProcessEventHandler: function () { }, + /** + * Implement or assign implementation to these handlers during or after + * calling the constructor. + * @function + * @param {Object} event + * @param {OpenSeadragon.MouseTracker} event.eventSource + * A reference to the tracker instance. + * @param {OpenSeadragon.Point} event.position + * The position of the event relative to the tracked element. + * @param {Object} event.originalEvent + * The original event object. + * @param {Object} event.userData + * Arbitrary user-defined object. + */ + contextMenuHandler: function () { }, + /** * Implement or assign implementation to these handlers during or after * calling the constructor. @@ -1170,7 +1191,7 @@ /** * Detect browser pointer device event model(s) and build appropriate list of events to subscribe to. */ - $.MouseTracker.subscribeEvents = [ "click", "dblclick", "keydown", "keyup", "keypress", "focus", "blur", $.MouseTracker.wheelEventName ]; + $.MouseTracker.subscribeEvents = [ "click", "dblclick", "keydown", "keyup", "keypress", "focus", "blur", "contextmenu", $.MouseTracker.wheelEventName ]; if( $.MouseTracker.wheelEventName === "DOMMouseScroll" ) { // Older Firefox @@ -1261,7 +1282,7 @@ * @property {Number} eventPhase * 0 == NONE, 1 == CAPTURING_PHASE, 2 == AT_TARGET, 3 == BUBBLING_PHASE. * @property {String} eventType - * "gotpointercapture", "lostpointercapture", "pointerenter", "pointerleave", "pointerover", "pointerout", "pointerdown", "pointerup", "pointermove", "pointercancel", "wheel". + * "contextmenu", "gotpointercapture", "lostpointercapture", "pointerenter", "pointerleave", "pointerover", "pointerout", "pointerdown", "pointerup", "pointermove", "pointercancel", "wheel". * @property {String} pointerType * "mouse", "touch", "pen", etc. * @property {Boolean} isEmulated @@ -1688,7 +1709,7 @@ return; } // Can throw InvalidPointerId - // (should never happen so we'll log a warning) + // (should never happen, but it does on Firefox 79 touch so we won't log a warning) try { if ( $.MouseTracker.unprefixedPointerEvents ) { tracker.element.releasePointerCapture( gPoint.id ); @@ -1698,7 +1719,7 @@ //$.console.log('element.msReleasePointerCapture() called'); } } catch ( e ) { - $.console.warn('releasePointerCapture() called on invalid pointer ID'); + //$.console.warn('releasePointerCapture() called on invalid pointer ID'); } } else { tracker.element.releaseCapture(); @@ -1968,6 +1989,44 @@ } + /** + * @private + * @inner + */ + function onContextMenu( tracker, event ) { + //$.console.log('contextmenu ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + + event = $.getEvent( event ); + + var eventInfo = { + originalEvent: event, + eventType: 'contextmenu', + pointerType: 'mouse', + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); + + // ContextMenu + if ( tracker.contextMenuHandler ) { + tracker.contextMenuHandler( + { + eventSource: tracker, + position: getPointRelativeToAbsolute( getMouseAbsolute( event ), tracker.element ), + originalEvent: eventInfo.originalEvent, + userData: tracker.userData + } + ); + } + + if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { + $.cancelEvent( event ); + } + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); + } + } + + /** * Handler for 'wheel' events * @@ -3253,6 +3312,13 @@ eventInfo.preventGesture = false; eventInfo.stopPropagation = false; break; + case 'contextmenu': + eventInfo.isStopable = true; + eventInfo.isCancelable = true; + eventInfo.preventDefault = tracker.hasContextMenuHandler; + eventInfo.preventGesture = !tracker.hasContextMenuHandler; + eventInfo.stopPropagation = false; + break; case 'pointerenter': case 'pointerleave': default: diff --git a/src/viewer.js b/src/viewer.js index 24b88109..1eb6f45e 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -288,6 +288,7 @@ $.Viewer = function( options ) { clickDistThreshold: this.clickDistThreshold, dblClickTimeThreshold: this.dblClickTimeThreshold, dblClickDistThreshold: this.dblClickDistThreshold, + contextMenuHandler: $.delegate( this, onCanvasContextMenu ), keyDownHandler: $.delegate( this, onCanvasKeyDown ), keyHandler: $.delegate( this, onCanvasKeyPress ), clickHandler: $.delegate( this, onCanvasClick ), @@ -2538,6 +2539,26 @@ function onBlur(){ } +function onCanvasContextMenu( event ) { + /** + * Raised when a contextmenu event occurs in the {@link OpenSeadragon.Viewer#canvas} element. + * + * @event canvas-contextmenu + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. + * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. + * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. + * @property {Object} originalEvent - The original DOM event. + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + this.raiseEvent( 'canvas-contextmenu', { + tracker: event.eventSource, + position: event.position, + originalEvent: event.originalEvent + }); +} + function onCanvasKeyDown( event ) { var canvasKeyDownEventArgs = { originalEvent: event.originalEvent, From 73dc6895f152fd167d8245ef30d27f7f41d12895 Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Tue, 9 Feb 2021 16:28:42 -0800 Subject: [PATCH 20/29] Removed commented legacy fix code (#1872) --- src/mousetracker.js | 103 -------------------------------------------- src/viewer.js | 7 --- 2 files changed, 110 deletions(-) diff --git a/src/mousetracker.js b/src/mousetracker.js index 01469a65..f5863bf1 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -349,27 +349,6 @@ return this; }, - // //TODO Revisit this if there's still an issue. The PointerEvent model should have no problems - // // like the issue this code attempts to fix. - // /** - // * Returns the {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} for all but the given pointer device type. - // * @function - // * @param {String} type - The pointer device type: "mouse", "touch", "pen", etc. - // * @returns {Array.} - // */ - // getActivePointersListsExceptType: function ( type ) { - // var delegate = THIS[ this.hash ]; - // var listArray = []; - - // for (var i = 0; i < delegate.activePointersLists.length; ++i) { - // if (delegate.activePointersLists[i].type !== type) { - // listArray.push(delegate.activePointersLists[i]); - // } - // } - - // return listArray; - // }, - /** * Returns the {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} for the given pointer device type, * creating and caching a new {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} if one doesn't already exist for the type. @@ -1065,24 +1044,6 @@ } } - //TODO Revisit this if there's still an issue. The PointerEvent model should have no problems - // like the issue this code attempts to fix. - // /** - // * Resets all active mousetrakers. (Added to patch issue #697 "Mouse up outside map will cause "canvas-drag" event to stick") - // * - // * @private - // * @member resetAllMouseTrackers - // * @memberof OpenSeadragon.MouseTracker - // */ - // $.MouseTracker.resetAllMouseTrackers = function(){ - // for(var i = 0; i < MOUSETRACKERS.length; i++){ - // if (MOUSETRACKERS[i].isTracking()){ - // MOUSETRACKERS[i].setTracking(false); - // MOUSETRACKERS[i].setTracking(true); - // } - // } - // }; - /** * Provides continuous computation of velocity (speed and direction) of active pointers. * This is a singleton, used by all MouseTracker instances, as it is unlikely there will ever be more than @@ -2529,36 +2490,6 @@ } - //TODO Revisit this if there's still an issue. The PointerEvent model should have no problems - // like the issue this code attempts to fix. - // /** - // * @private - // * @inner - // */ - // function abortContacts( tracker, event, pointsList ) { - // var i, - // gPointCount = pointsList.getLength(), - // abortGPoints = []; - - // // Check contact count for hoverable pointer types before aborting - // if (pointsList.type === 'touch' || pointsList.contacts > 0) { - // for ( i = 0; i < gPointCount; i++ ) { - // abortGPoints.push( pointsList.getByIndex( i ) ); - // } - - // if ( abortGPoints.length > 0 ) { - // // simulate touchend/mouseup - // updatePointerUp( tracker, eventInfo, , 0 ); // 0 means primary button press/release or touch contact - // // release pointer capture - // pointsList.captureCount = 1; - // //releasePointer( tracker, pointsList.type ); - // // simulate touchleave/mouseout - // updatePointerLeave( tracker, eventInfo, ); - // } - // } - // } - - /** * @private * @inner @@ -2574,12 +2505,6 @@ //$.console.log('touchstart ' + (tracker.userData ? tracker.userData.toString() : '')); - //TODO Revisit this if there's still an issue. The PointerEvent model should have no problems - // like the issue this code attempts to fix. - // if ( pointsList.getLength() > event.touches.length - touchCount ) { - // $.console.warn('Tracked touch contact count doesn\'t match event.touches.length. Removing all tracked touch pointers.'); - // abortContacts( tracker, event, pointsList ); - // } if ( pointsList.getLength() > event.touches.length - touchCount ) { $.console.warn('Tracked touch contact count doesn\'t match event.touches.length'); } @@ -3678,16 +3603,6 @@ } } - //TODO Revisit this if there's still an issue. The PointerEvent model should have no problems - // like the issue this code attempts to fix. - // // Some pointers may steal control from another pointer without firing the appropriate release events - // // e.g. Touching a screen while click-dragging with certain mice. - // var otherPointsLists = tracker.getActivePointersListsExceptType(gPoint.type); - // for (i = 0; i < otherPointsLists.length; i++) { - // //If another pointer has contact, simulate the release - // abortContacts(tracker, event, otherPointsLists[i]); // No-op if no active pointer - // } - // Only capture and track primary button, pen, and touch contacts if ( buttonChanged !== 0 ) { eventInfo.shouldCapture = false; @@ -3880,27 +3795,9 @@ ); } - //TODO Revisit this if there's still an issue. The PointerEvent model should have no problems - // like the issue this code attempts to fix. - // // A primary mouse button may have been released while the non-primary button was down - // var otherPointsList = tracker.getActivePointersListByType("mouse"); - // // Stop tracking the mouse; see https://github.com/openseadragon/openseadragon/pull/1223 - // abortContacts(tracker, event, otherPointsList); // No-op if no active pointer - return; } - //TODO Revisit this if there's still an issue. The PointerEvent model should have no problems - // like the issue this code attempts to fix. - // GitHub PR: https://github.com/openseadragon/openseadragon/pull/1754 - // // OS-specific gestures (e.g. swipe up with four fingers in iPadOS 13) - // if (typeof gPoint.currentPos === "undefined") { - // $.console.log('typeof gPoint.currentPos === "undefined" ' + (tracker.userData ? tracker.userData.toString() : '')); - // abortContacts(tracker, event, pointsList); - - // return false; - // } - updateGPoint = pointsList.getById( gPoint.id ); if ( updateGPoint ) { diff --git a/src/viewer.js b/src/viewer.js index 1eb6f45e..dfb80a1c 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2956,13 +2956,6 @@ function onCanvasEnter( event ) { } function onCanvasLeave( event ) { - - //TODO Revisit this if there's still an issue. The PointerEvent model should have no problems - // like the issue this code attempts to fix. - // if (window.location !== window.parent.location){ - // $.MouseTracker.resetAllMouseTrackers(); - // } - /** * Raised when a pointer leaves the {@link OpenSeadragon.Viewer#canvas} element. * From 4b40400cd32ce22dd06d7d506538d9cb10a92740 Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Wed, 10 Feb 2021 07:29:36 -0800 Subject: [PATCH 21/29] MouseTracker contextmenu enabled by default --- src/mousetracker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mousetracker.js b/src/mousetracker.js index f5863bf1..c032d229 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -3240,8 +3240,8 @@ case 'contextmenu': eventInfo.isStopable = true; eventInfo.isCancelable = true; - eventInfo.preventDefault = tracker.hasContextMenuHandler; - eventInfo.preventGesture = !tracker.hasContextMenuHandler; + eventInfo.preventDefault = false;//tracker.hasContextMenuHandler; + eventInfo.preventGesture = true;//!tracker.hasContextMenuHandler; eventInfo.stopPropagation = false; break; case 'pointerenter': From 72fe7a902596c9b48937a07863934a390cb1e9e7 Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Wed, 10 Feb 2021 08:32:00 -0800 Subject: [PATCH 22/29] Show deprecation warning in console for MouseTracker.exitHandler --- src/mousetracker.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/mousetracker.js b/src/mousetracker.js index c032d229..703e33ec 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -294,6 +294,10 @@ this.hasScrollHandler = !!this.scrollHandler; this.hasContextMenuHandler = !!this.contextMenuHandler; + if (this.exitHandler) { + $.console.error("MouseTracker.exitHandler is deprecated. Use MouseTracker.leaveHandler instead."); + } + if ( !options.startDisabled ) { this.setTracking( true ); } @@ -3417,7 +3421,7 @@ } // Leave (doesn't bubble and not cancelable) - // Note: exitHandler is deprecated, replaced by leaveHandler + // Note: exitHandler is deprecated (v2.5.0), replaced by leaveHandler if ( tracker.leaveHandler || tracker.exitHandler ) { dispatchEventObj = { eventSource: tracker, From ce098f889c79599cea476db2c6757bdb7704b937 Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Wed, 10 Feb 2021 08:51:17 -0800 Subject: [PATCH 23/29] Added additional documentation for the zoomPerSecond viewer option --- changelog.txt | 1 + src/openseadragon.js | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 0e9405ff..f9ca4eb1 100644 --- a/changelog.txt +++ b/changelog.txt @@ -32,6 +32,7 @@ OPENSEADRAGON CHANGELOG * Added OpenSeadragon.setElementPointerEventsNone() for setting pointer-events:'none' on DOM elements (#1872 @msalsbery) * MouseTracker: added contextMenuHandler option for handling contextmenu events (#1872 @msalsbery) * Viewer: added a canvas-contextmenu event (#1872 @msalsbery) +* Added additional documentation for the zoomPerSecond viewer option (#1872 @msalsbery) 2.4.2: diff --git a/src/openseadragon.js b/src/openseadragon.js index 98d7fa33..86320fdb 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -381,7 +381,11 @@ * The "zoom distance" per mouse scroll or touch pinch. Note: Setting this to 1.0 effectively disables the mouse-wheel zoom feature (also see gestureSettings[Mouse|Touch|Pen].scrollToZoom}). * * @property {Number} [zoomPerSecond=1.0] - * The number of seconds to animate a single zoom event over. + * Sets the zoom amount per second when zoomIn/zoomOut buttons are pressed and held. + * The value is a factor of the current zoom, so 1.0 (the default) disables zooming when the zoomIn/zoomOut buttons + * are held. Higher values will increase the rate of zoom when the zoomIn/zoomOut buttons are held. Note that values + * < 1.0 will reverse the operation of the zoomIn/zoomOut buttons (zoomIn button will decrease the zoom, zoomOut will + * increase the zoom). * * @property {Boolean} [showNavigator=false] * Set to true to make the navigator minimap appear. From 6a1a6275ed069b86d5137004cf5224fa7f699a3c Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Tue, 2 Mar 2021 10:29:44 -0800 Subject: [PATCH 24/29] MouseTracker: Allow pre-processing of click and dblclick events --- src/mousetracker.js | 52 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/src/mousetracker.js b/src/mousetracker.js index 703e33ec..60de2503 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -1247,7 +1247,7 @@ * @property {Number} eventPhase * 0 == NONE, 1 == CAPTURING_PHASE, 2 == AT_TARGET, 3 == BUBBLING_PHASE. * @property {String} eventType - * "contextmenu", "gotpointercapture", "lostpointercapture", "pointerenter", "pointerleave", "pointerover", "pointerout", "pointerdown", "pointerup", "pointermove", "pointercancel", "wheel". + * "contextmenu", "gotpointercapture", "lostpointercapture", "pointerenter", "pointerleave", "pointerover", "pointerout", "pointerdown", "pointerup", "pointermove", "pointercancel", "wheel", "click", "dblclick". * @property {String} pointerType * "mouse", "touch", "pen", etc. * @property {Boolean} isEmulated @@ -1802,9 +1802,24 @@ * @inner */ function onClick( tracker, event ) { - if ( tracker.clickHandler ) { + event = $.getEvent( event ); + + //$.console.log('onClick ' + (tracker.userData ? tracker.userData.toString() : '')); + + var eventInfo = { + originalEvent: event, + eventType: 'click', + pointerType: 'mouse', + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); + + if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { $.cancelEvent( event ); } + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); + } } @@ -1813,9 +1828,24 @@ * @inner */ function onDblClick( tracker, event ) { - if ( tracker.dblClickHandler ) { + event = $.getEvent( event ); + + //$.console.log('onDblClick ' + (tracker.userData ? tracker.userData.toString() : '')); + + var eventInfo = { + originalEvent: event, + eventType: 'dblclick', + pointerType: 'mouse', + isEmulated: false + }; + preProcessEvent( tracker, eventInfo ); + + if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { $.cancelEvent( event ); } + if ( eventInfo.stopPropagation ) { + $.stopEvent( event ); + } } @@ -1972,7 +2002,7 @@ preProcessEvent( tracker, eventInfo ); // ContextMenu - if ( tracker.contextMenuHandler ) { + if ( tracker.contextMenuHandler && !eventInfo.preventGesture && !eventInfo.defaultPrevented ) { tracker.contextMenuHandler( { eventSource: tracker, @@ -3241,6 +3271,20 @@ eventInfo.preventGesture = false; eventInfo.stopPropagation = false; break; + case 'click': + eventInfo.isStopable = true; + eventInfo.isCancelable = true; + eventInfo.preventDefault = !!tracker.clickHandler; + eventInfo.preventGesture = false; + eventInfo.stopPropagation = false; + break; + case 'dblclick': + eventInfo.isStopable = true; + eventInfo.isCancelable = true; + eventInfo.preventDefault = !!tracker.dblClickHandler; + eventInfo.preventGesture = false; + eventInfo.stopPropagation = false; + break; case 'contextmenu': eventInfo.isStopable = true; eventInfo.isCancelable = true; From 5674cf47ad2f874a0bc04660c21ee62f075625bd Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Thu, 4 Mar 2021 15:48:13 -0800 Subject: [PATCH 25/29] Per #1863, dropped support for Internet Explorer < 11 --- changelog.txt | 3 +- src/mousetracker.js | 732 ++++++++------------------------------------ 2 files changed, 122 insertions(+), 613 deletions(-) diff --git a/changelog.txt b/changelog.txt index f9ca4eb1..c5cbab61 100644 --- a/changelog.txt +++ b/changelog.txt @@ -20,8 +20,6 @@ OPENSEADRAGON CHANGELOG * MouseTracker: changed enterHandler/leaveHandler to use DOM pointerenter/pointerleave events instead of simulating using pointerover/pointerout (#1872 @msalsbery) * All internal uses of MouseTracker use pointerenter/pointerleave events instead of pointerover/pointerout events for more consistent pointer tracking (#1872 @msalsbery) * Fixed bug in Button class where two MouseTracker event handlers used an invalid "this" causing issues in some browsers (#1872 @msalsbery) -* MouseTracker: IE 10 - MSPointerEnter/MSPointerLeave didn't exist then, simulated with MSPointerOver/MSPointerOut (#1872 @msalsbery) -* MouseTracker: Simulate mouseover/mouseout on IE < 9 (#1872 @msalsbery) * Added pointerType property to Viewer container-enter, container-exit, canvas-drag, canvas-drag-end, canvas-pinch events (#1872 @msalsbery) * MouseTracker: Fire dragEndHandler event even if release point same as initial contact point (#1872 @msalsbery) * MouseTracker: Pointer capture implemented with capture APIs where available. Only fallback to emulated capture on extremely old browsers (#1872 @msalsbery) @@ -33,6 +31,7 @@ OPENSEADRAGON CHANGELOG * MouseTracker: added contextMenuHandler option for handling contextmenu events (#1872 @msalsbery) * Viewer: added a canvas-contextmenu event (#1872 @msalsbery) * Added additional documentation for the zoomPerSecond viewer option (#1872 @msalsbery) +* MouseTracker: Per #1863, dropped support for Internet Explorer < 11 (#1872 @msalsbery) 2.4.2: diff --git a/src/mousetracker.js b/src/mousetracker.js index 60de2503..269477e5 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -224,15 +224,14 @@ MozMousePixelScroll: function ( event ) { onMouseWheel( _this, event ); }, losecapture: function ( event ) { onLoseCapture( _this, event ); }, - mouseenter: function ( event ) { onMouseEnter( _this, event ); }, - mouseleave: function ( event ) { onMouseLeave( _this, event ); }, - mouseover: function ( event ) { onMouseOver( _this, event ); }, // IE9+ only - mouseout: function ( event ) { onMouseOut( _this, event ); }, // IE9+ only - mousedown: function ( event ) { onMouseDown( _this, event ); }, - mouseup: function ( event ) { onMouseUp( _this, event ); }, - mouseupcaptured: function ( event ) { onMouseUpCaptured( _this, event ); }, - mousemove: function ( event ) { onMouseMove( _this, event ); }, - mousemovecaptured: function ( event ) { onMouseMoveCaptured( _this, event ); }, + + mouseenter: function ( event ) { onPointerEnter( _this, event ); }, + mouseleave: function ( event ) { onPointerLeave( _this, event ); }, + mouseover: function ( event ) { onPointerOver( _this, event ); }, + mouseout: function ( event ) { onPointerOut( _this, event ); }, + mousedown: function ( event ) { onPointerDown( _this, event ); }, + mouseup: function ( event ) { onPointerUp( _this, event ); }, + mousemove: function ( event ) { onPointerMove( _this, event ); }, touchstart: function ( event ) { onTouchStart( _this, event ); }, touchend: function ( event ) { onTouchEnd( _this, event ); }, @@ -242,27 +241,16 @@ gesturestart: function ( event ) { onGestureStart( _this, event ); }, // Safari/Safari iOS gesturechange: function ( event ) { onGestureChange( _this, event ); }, // Safari/Safari iOS - MSGestureStart: function ( event ) { onGestureStart( _this, event ); }, // IE10 - MSGestureChange: function ( event ) { onGestureChange( _this, event ); }, // IE10 - gotpointercapture: function ( event ) { onGotPointerCapture( _this, event ); }, - MSGotPointerCapture: function ( event ) { onGotPointerCapture( _this, event ); }, lostpointercapture: function ( event ) { onLostPointerCapture( _this, event ); }, - MSLostPointerCapture: function ( event ) { onLostPointerCapture( _this, event ); }, pointerenter: function ( event ) { onPointerEnter( _this, event ); }, pointerleave: function ( event ) { onPointerLeave( _this, event ); }, pointerover: function ( event ) { onPointerOver( _this, event ); }, - MSPointerOver: function ( event ) { onPointerOver( _this, event ); }, pointerout: function ( event ) { onPointerOut( _this, event ); }, - MSPointerOut: function ( event ) { onPointerOut( _this, event ); }, pointerdown: function ( event ) { onPointerDown( _this, event ); }, - MSPointerDown: function ( event ) { onPointerDown( _this, event ); }, pointerup: function ( event ) { onPointerUp( _this, event ); }, - MSPointerUp: function ( event ) { onPointerUp( _this, event ); }, pointermove: function ( event ) { onPointerMove( _this, event ); }, - MSPointerMove: function ( event ) { onPointerMove( _this, event ); }, pointercancel: function ( event ) { onPointerCancel( _this, event ); }, - MSPointerCancel: function ( event ) { onPointerCancel( _this, event ); }, pointerupcaptured: function ( event ) { onPointerUpCaptured( _this, event ); }, pointermovecaptured: function ( event ) { onPointerMoveCaptured( _this, event ); }, @@ -1167,8 +1155,6 @@ // IE11 and other W3C Pointer Event implementations (see http://www.w3.org/TR/pointerevents) $.MouseTracker.havePointerEvents = true; $.MouseTracker.subscribeEvents.push( "pointerenter", "pointerleave", "pointerover", "pointerout", "pointerdown", "pointerup", "pointermove", "pointercancel" ); - $.MouseTracker.unprefixedPointerEvents = true; - $.MouseTracker.havePointerOverOut = true; // Pointer events capture support $.MouseTracker.havePointerCapture = (function () { var divElement = document.createElement( 'div' ); @@ -1177,33 +1163,10 @@ if ( $.MouseTracker.havePointerCapture ) { $.MouseTracker.subscribeEvents.push( "gotpointercapture", "lostpointercapture" ); } - } else if ( window.MSPointerEvent && window.navigator.msPointerEnabled ) { - // IE10 (MSPointerEnter/MSPointerLeave simulated with MSPointerOver/MSPointerOut) - $.MouseTracker.havePointerEvents = true; - $.MouseTracker.subscribeEvents.push( "MSPointerOver", "MSPointerOut", "MSPointerDown", "MSPointerUp", "MSPointerMove", "MSPointerCancel" ); - $.MouseTracker.unprefixedPointerEvents = false; - $.MouseTracker.havePointerOverOut = true; - // Prefixed pointer events capture support - $.MouseTracker.havePointerCapture = (function () { - var divElement = document.createElement( 'div' ); - return $.isFunction( divElement.msSetPointerCapture ) && $.isFunction( divElement.msReleasePointerCapture ); - }()); - if ( $.MouseTracker.havePointerCapture ) { - $.MouseTracker.subscribeEvents.push( "MSGotPointerCapture", "MSLostPointerCapture" ); - } - $.MouseTracker.subscribeEvents.push( "MSGestureStart", "MSGestureChange" ); } else { // Legacy W3C mouse events $.MouseTracker.havePointerEvents = false; - $.MouseTracker.unprefixedPointerEvents = true; - $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" ); - if ( $.Browser.vendor !== $.BROWSERS.IE || $.Browser.version > 8 ) { - $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout" ); - $.MouseTracker.havePointerOverOut = true; - } else { - $.MouseTracker.havePointerOverOut = false; - } - $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" ); + $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave", "mouseover", "mouseout", "mousedown", "mouseup", "mousemove" ); $.MouseTracker.mousePointerId = "legacy-mouse"; // Legacy mouse events capture support (IE/Firefox only?) $.MouseTracker.havePointerCapture = (function () { @@ -1253,7 +1216,7 @@ * @property {Boolean} isEmulated * True if this is an emulated event. If true, originalEvent is the event that caused * the emulated event or null if no DOM event applies. Emulated events - * can occur on eventType "pointerenter", "pointerleave", "pointerover", "pointerout". + * can occur on eventType "wheel". * @property {Boolean} isStopable * True if propagation of the event (e.g. bubbling) can be stopped with stopPropagation/stopImmediatePropagation. * @property {Boolean} isCancelable @@ -1572,17 +1535,17 @@ if ( pointerType === 'pointerevent' ) { return { - upName: $.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp', + upName: 'pointerup', upHandler: delegate.pointerupcaptured, - moveName: $.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove', + moveName: 'pointermove', moveHandler: delegate.pointermovecaptured }; } else if ( pointerType === 'mouse' ) { return { - upName: 'mouseup', - upHandler: delegate.mouseupcaptured, - moveName: 'mousemove', - moveHandler: delegate.mousemovecaptured + upName: 'pointerup', + upHandler: delegate.pointerupcaptured, + moveName: 'pointermove', + moveHandler: delegate.pointermovecaptured }; } else if ( pointerType === 'touch' ) { return { @@ -1609,13 +1572,8 @@ // Can throw InvalidPointerId // (should never happen so we'll log a warning) try { - if ( $.MouseTracker.unprefixedPointerEvents ) { - tracker.element.setPointerCapture( gPoint.id ); - //$.console.log('element.setPointerCapture() called'); - } else { - tracker.element.msSetPointerCapture( gPoint.id ); - //$.console.log('element.msSetPointerCapture() called'); - } + tracker.element.setPointerCapture( gPoint.id ); + //$.console.log('element.setPointerCapture() called'); } catch ( e ) { $.console.warn('setPointerCapture() called on invalid pointer ID'); } @@ -1676,13 +1634,8 @@ // Can throw InvalidPointerId // (should never happen, but it does on Firefox 79 touch so we won't log a warning) try { - if ( $.MouseTracker.unprefixedPointerEvents ) { - tracker.element.releasePointerCapture( gPoint.id ); - //$.console.log('element.releasePointerCapture() called'); - } else { - tracker.element.msReleasePointerCapture( gPoint.id ); - //$.console.log('element.msReleasePointerCapture() called'); - } + tracker.element.releasePointerCapture( gPoint.id ); + //$.console.log('element.releasePointerCapture() called'); } catch ( e ) { //$.console.warn('releasePointerCapture() called on invalid pointer ID'); } @@ -1722,6 +1675,15 @@ } + /** + * @private + * @inner + */ + function getPointerId( event ) { + return ( $.MouseTracker.havePointerEvents ) ? event.pointerId : $.MouseTracker.mousePointerId; + } + + /** * Gets a W3C Pointer Events model compatible pointer type string from a DOM pointer event. * IE10 used a long integer value, but the W3C specification (and IE11+) use a string "mouse", "touch", "pen", etc. @@ -1729,33 +1691,23 @@ * @inner */ function getPointerType( event ) { - // Note: IE pointer events bug - sends invalid pointerType on lostpointercapture events - // and possibly other events. We rely on sane, valid property values in DOM events, so for - // IE, when the pointerType is missing, we'll default to 'mouse'...should be right most of the time - var pointerTypeStr; - if ( $.MouseTracker.unprefixedPointerEvents ) { - pointerTypeStr = event.pointerType || (( $.Browser.vendor === $.BROWSERS.IE ) ? 'mouse' : ''); + if ( $.MouseTracker.havePointerEvents ) { + // Note: IE pointer events bug - sends invalid pointerType on lostpointercapture events + // and possibly other events. We rely on sane, valid property values in DOM events, so for + // IE, when the pointerType is missing, we'll default to 'mouse'...should be right most of the time + return event.pointerType || (( $.Browser.vendor === $.BROWSERS.IE ) ? 'mouse' : ''); } else { - // IE10 - // MSPOINTER_TYPE_TOUCH: 0x00000002 - // MSPOINTER_TYPE_PEN: 0x00000003 - // MSPOINTER_TYPE_MOUSE: 0x00000004 - switch( event.pointerType ) - { - case 0x00000002: - pointerTypeStr = 'touch'; - break; - case 0x00000003: - pointerTypeStr = 'pen'; - break; - case 0x00000004: - pointerTypeStr = 'mouse'; - break; - default: - pointerTypeStr = 'mouse'; - } + return 'mouse'; } - return pointerTypeStr; + } + + + /** + * @private + * @inner + */ + function getIsPrimary( event ) { + return ( $.MouseTracker.havePointerEvents ) ? event.isPrimary : true; } @@ -1802,8 +1754,6 @@ * @inner */ function onClick( tracker, event ) { - event = $.getEvent( event ); - //$.console.log('onClick ' + (tracker.userData ? tracker.userData.toString() : '')); var eventInfo = { @@ -1828,8 +1778,6 @@ * @inner */ function onDblClick( tracker, event ) { - event = $.getEvent( event ); - //$.console.log('onDblClick ' + (tracker.userData ? tracker.userData.toString() : '')); var eventInfo = { @@ -1857,7 +1805,6 @@ //$.console.log( "keydown %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey ); var propagate; if ( tracker.keyDownHandler ) { - event = $.getEvent( event ); propagate = tracker.keyDownHandler( { eventSource: tracker, @@ -1886,7 +1833,6 @@ //$.console.log( "keyup %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey ); var propagate; if ( tracker.keyUpHandler ) { - event = $.getEvent( event ); propagate = tracker.keyUpHandler( { eventSource: tracker, @@ -1915,7 +1861,6 @@ //$.console.log( "keypress %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey ); var propagate; if ( tracker.keyHandler ) { - event = $.getEvent( event ); propagate = tracker.keyHandler( { eventSource: tracker, @@ -1944,7 +1889,6 @@ //console.log( "focus %s", event ); var propagate; if ( tracker.focusHandler ) { - event = $.getEvent( event ); propagate = tracker.focusHandler( { eventSource: tracker, @@ -1968,7 +1912,6 @@ //console.log( "blur %s", event ); var propagate; if ( tracker.blurHandler ) { - event = $.getEvent( event ); propagate = tracker.blurHandler( { eventSource: tracker, @@ -1991,8 +1934,6 @@ function onContextMenu( tracker, event ) { //$.console.log('contextmenu ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); - event = $.getEvent( event ); - var eventInfo = { originalEvent: event, eventType: 'contextmenu', @@ -2040,8 +1981,6 @@ * @inner */ function onMouseWheel( tracker, event ) { - event = $.getEvent( event ); - // Simulate a 'wheel' event var simulatedEvent = { target: event.target || event.srcElement, @@ -2120,22 +2059,6 @@ } - /** - * @private - * @inner - */ - function isParentChild( parent, child ) - { - if ( parent === child ) { - return false; - } - while ( child && child !== parent ) { - child = child.parentNode; - } - return child === parent; - } - - /** * TODO Never actually seen this event fired, and documentation is tough to find * @private @@ -2143,7 +2066,6 @@ */ function onLoseCapture( tracker, event ) { //$.console.log('losecapture ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); - event = $.getEvent( event ); var gPoint = { id: $.MouseTracker.mousePointerId, @@ -2166,364 +2088,6 @@ } - /** - * @private - * @inner - */ - function onMouseEnter( tracker, event ) { - //$.console.log('mouseenter ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); - - event = $.getEvent( event ); - - var gPoint = { - id: $.MouseTracker.mousePointerId, - type: 'mouse', - isPrimary: true, - currentPos: getMouseAbsolute( event ), - currentTime: $.now() - }; - - // pointerenter doesn't bubble and is not cancelable, but we call - // preProcessEvent() so it's dispatched to preProcessEventHandler - // if necessary - var eventInfo = { - originalEvent: event, - eventType: 'pointerenter', - pointerType: gPoint.type, - isEmulated: false - }; - preProcessEvent( tracker, eventInfo ); - - updatePointerEnter( tracker, eventInfo, gPoint ); - - // Simulate mouseover on IE < 9 - if ( !$.MouseTracker.havePointerOverOut ) { - handleMouseOver( tracker, event, true ); - } - } - - - /** - * @private - * @inner - */ - function onMouseLeave( tracker, event ) { - //$.console.log('mouseleave ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); - - event = $.getEvent( event ); - - var gPoint = { - id: $.MouseTracker.mousePointerId, - type: 'mouse', - isPrimary: true, - currentPos: getMouseAbsolute( event ), - currentTime: $.now() - }; - - // pointerleave doesn't bubble and is not cancelable, but we call - // preProcessEvent() so it's dispatched to preProcessEventHandler - // if necessary - var eventInfo = { - originalEvent: event, - eventType: 'pointerleave', - pointerType: gPoint.type, - isEmulated: false - }; - preProcessEvent( tracker, eventInfo ); - - // Simulate mouseoout on IE < 9 - if ( !$.MouseTracker.havePointerOverOut ) { - handleMouseOut( tracker, event, true ); - } - - updatePointerLeave( tracker, eventInfo, gPoint ); - } - - - /** - * IE9+ only - * - * @private - * @inner - */ - function onMouseOver( tracker, event ) { - //$.console.log('mouseover ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); - - handleMouseOver( tracker, event, false ); - } - - - /** - * @private - * @inner - */ - function handleMouseOver( tracker, event, isEmulated ) { - var gPoint = { - id: $.MouseTracker.mousePointerId, - type: 'mouse', - isPrimary: true, - currentPos: getMouseAbsolute( event ), - currentTime: $.now() - }; - - var eventInfo = { - originalEvent: event, - eventType: 'pointerover', - pointerType: gPoint.type, - isEmulated: isEmulated - }; - preProcessEvent( tracker, eventInfo ); - - updatePointerOver( tracker, eventInfo, gPoint ); - - if ( !isEmulated ) { - if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { - $.cancelEvent( event ); - } - if ( eventInfo.stopPropagation ) { - $.stopEvent( event ); - } - } - } - - - /** - * IE9+ only - * - * @private - * @inner - */ - function onMouseOut( tracker, event ) { - //$.console.log('mouseout ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); - - handleMouseOut( tracker, event, false ); - } - - - /** - * @private - * @inner - */ - function handleMouseOut( tracker, event, isEmulated ) { - var gPoint = { - id: $.MouseTracker.mousePointerId, - type: 'mouse', - isPrimary: true, - currentPos: getMouseAbsolute( event ), - currentTime: $.now() - }; - - var eventInfo = { - originalEvent: event, - eventType: 'pointerout', - pointerType: gPoint.type, - isEmulated: isEmulated - }; - preProcessEvent( tracker, eventInfo ); - - updatePointerOut( tracker, eventInfo, gPoint ); - - if ( !isEmulated ) { - if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { - $.cancelEvent( event ); - } - if ( eventInfo.stopPropagation ) { - $.stopEvent( event ); - } - } - } - - - /** - * Returns a W3C DOM level 3 standard button value given an event.button property: - * -1 == none, 0 == primary/left, 1 == middle, 2 == secondary/right, 3 == X1/back, 4 == X2/forward, 5 == eraser (pen) - * @private - * @inner - */ - function getStandardizedButton( button ) { - if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { - // On IE 8, 0 == none, 1 == left, 2 == right, 3 == left and right, 4 == middle, 5 == left and middle, 6 == right and middle, 7 == all three - // TODO: Support chorded (multiple) button presses on IE 8? - if ( button === 1 ) { - return 0; - } else if ( button === 2 ) { - return 2; - } else if ( button === 4 ) { - return 1; - } else { - return -1; - } - } else { - return button; - } - } - - - /** - * @private - * @inner - */ - function onMouseDown( tracker, event ) { - var gPoint; - - event = $.getEvent( event ); - - //$.console.log('onMouseDown ' + (tracker.userData ? tracker.userData.toString() : '')); - - gPoint = { - id: $.MouseTracker.mousePointerId, - type: 'mouse', - isPrimary: true, - currentPos: getMouseAbsolute( event ), - currentTime: $.now() - }; - - var eventInfo = { - originalEvent: event, - eventType: 'pointerdown', - pointerType: gPoint.type, - isEmulated: false - }; - preProcessEvent( tracker, eventInfo ); - - updatePointerDown( tracker, eventInfo, gPoint, getStandardizedButton( event.button ) ); - - if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { - $.cancelEvent( event ); - } - if ( eventInfo.stopPropagation ) { - $.stopEvent( event ); - } - if ( eventInfo.shouldCapture ) { - //$.stopEvent( event ); - //$.console.log('mousedown calling capturePointer() ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); - capturePointer( tracker, gPoint ); - } - } - - - /** - * @private - * @inner - */ - function onMouseUp( tracker, event ) { - handleMouseUp( tracker, event ); - } - - /** - * This handler is attached to the window object (on the capture phase) to emulate mouse capture. - * onMouseUp is still attached to the tracked element, so stop propagation to avoid processing twice. - * - * @private - * @inner - */ - function onMouseUpCaptured( tracker, event ) { - handleMouseUp( tracker, event ); - $.stopEvent( event ); - } - - - /** - * @private - * @inner - */ - function handleMouseUp( tracker, event ) { - var gPoint; - - event = $.getEvent( event ); - - //$.console.log('onMouseUp ' + (tracker.userData ? tracker.userData.toString() : '')); - - gPoint = { - id: $.MouseTracker.mousePointerId, - type: 'mouse', - isPrimary: true, - currentPos: getMouseAbsolute( event ), - currentTime: $.now() - }; - - var eventInfo = { - originalEvent: event, - eventType: 'pointerup', - pointerType: gPoint.type, - isEmulated: false - }; - preProcessEvent( tracker, eventInfo ); - - updatePointerUp( tracker, eventInfo, gPoint, getStandardizedButton( event.button ) ); - - if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { - $.cancelEvent( event ); - } - if ( eventInfo.stopPropagation ) { - $.stopEvent( event ); - } - - if ( eventInfo.shouldReleaseCapture ) { - //$.stopEvent( event ); - releasePointer( tracker, gPoint ); - } - } - - - /** - * @private - * @inner - */ - function onMouseMove( tracker, event ) { - handleMouseMove( tracker, event ); - } - - - /** - * This handler is attached to the window object (on the capture phase) to emulate mouse capture. - * onMouseMove is still attached to the tracked element, so stop propagation to avoid processing twice. - * - * @private - * @inner - */ - function onMouseMoveCaptured( tracker, event ) { - handleMouseMove( tracker, event ); - $.stopEvent( event ); - } - - - /** - * @private - * @inner - */ - function handleMouseMove( tracker, event ) { - var gPoint; - - event = $.getEvent( event ); - - gPoint = { - id: $.MouseTracker.mousePointerId, - type: 'mouse', - isPrimary: true, - currentPos: getMouseAbsolute( event ), - currentTime: $.now() - }; - - var eventInfo = { - originalEvent: event, - eventType: 'pointermove', - pointerType: gPoint.type, - isEmulated: false - }; - preProcessEvent( tracker, eventInfo ); - - updatePointerMove( tracker, eventInfo, gPoint ); - - if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { - $.cancelEvent( event ); - } - if ( eventInfo.stopPropagation ) { - $.stopEvent( event ); - } - } - - /** * @private * @inner @@ -2783,27 +2347,20 @@ /** + * Note: Called for both pointer events and legacy mouse events! + * * @private * @inner */ function onPointerEnter( tracker, event ) { - handlePointerEnter( tracker, event, false ); - } - - - /** - * @private - * @inner - */ - function handlePointerEnter( tracker, event, isEmulated ) { var gPoint; //$.console.log('pointerenter ' + (tracker.userData ? tracker.userData.toString() : '')); gPoint = { - id: event.pointerId, + id: getPointerId( event ), type: getPointerType( event ), - isPrimary: event.isPrimary, + isPrimary: getIsPrimary( event ), currentPos: getMouseAbsolute( event ), currentTime: $.now() }; @@ -2815,7 +2372,7 @@ originalEvent: event, eventType: 'pointerenter', pointerType: gPoint.type, - isEmulated: isEmulated + isEmulated: false }; preProcessEvent( tracker, eventInfo ); @@ -2824,27 +2381,20 @@ /** + * Note: Called for both pointer events and legacy mouse events! + * * @private * @inner */ function onPointerLeave( tracker, event ) { - handlePointerLeave( tracker, event, false ); - } - - - /** - * @private - * @inner - */ - function handlePointerLeave( tracker, event, isEmulated ) { var gPoint; //$.console.log('pointerleave ' + (tracker.userData ? tracker.userData.toString() : '')); gPoint = { - id: event.pointerId, + id: getPointerId( event ), type: getPointerType( event ), - isPrimary: event.isPrimary, + isPrimary: getIsPrimary( event ), currentPos: getMouseAbsolute( event ), currentTime: $.now() }; @@ -2856,7 +2406,7 @@ originalEvent: event, eventType: 'pointerleave', pointerType: gPoint.type, - isEmulated: isEmulated + isEmulated: false }; preProcessEvent( tracker, eventInfo ); @@ -2865,6 +2415,8 @@ /** + * Note: Called for both pointer events and legacy mouse events! + * * @private * @inner */ @@ -2872,9 +2424,9 @@ //$.console.log('pointerover ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); var gPoint = { - id: event.pointerId, + id: getPointerId( event ), type: getPointerType( event ), - isPrimary: event.isPrimary, + isPrimary: getIsPrimary( event ), currentPos: getMouseAbsolute( event ), currentTime: $.now() }; @@ -2887,13 +2439,6 @@ }; preProcessEvent( tracker, eventInfo ); - // If on IE 10, simulate MSPointerEnter - if ( !$.MouseTracker.unprefixedPointerEvents && - event.currentTarget !== event.relatedTarget && - !isParentChild( event.currentTarget, event.relatedTarget ) ) { - handlePointerEnter( tracker, event, true ); - } - updatePointerOver( tracker, eventInfo, gPoint ); if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { @@ -2906,6 +2451,8 @@ /** + * Note: Called for both pointer events and legacy mouse events! + * * @private * @inner */ @@ -2913,9 +2460,9 @@ //$.console.log('pointerout ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); var gPoint = { - id: event.pointerId, + id: getPointerId( event ), type: getPointerType( event ), - isPrimary: event.isPrimary, + isPrimary: getIsPrimary( event ), currentPos: getMouseAbsolute( event ), currentTime: $.now() }; @@ -2930,13 +2477,6 @@ updatePointerOut( tracker, eventInfo, gPoint ); - // If on IE 10, simulate MSPointerLeave - if ( !$.MouseTracker.unprefixedPointerEvents && - event.currentTarget !== event.relatedTarget && - !isParentChild( event.currentTarget, event.relatedTarget ) ) { - handlePointerLeave( tracker, event, true ); - } - if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { $.cancelEvent( event ); } @@ -2947,6 +2487,8 @@ /** + * Note: Called for both pointer events and legacy mouse events! + * * @private * @inner */ @@ -2959,9 +2501,9 @@ // Most browsers implicitly capture touch pointer events // Note no IE versions have element.hasPointerCapture() so no implicit // pointer capture possible - var implicitlyCaptured = (tracker.element.hasPointerCapture && - $.Browser.vendor !== $.BROWSERS.IE && - $.MouseTracker.unprefixedPointerEvents) ? + var implicitlyCaptured = ($.MouseTracker.havePointerEvents && + tracker.element.hasPointerCapture && + $.Browser.vendor !== $.BROWSERS.IE) ? tracker.element.hasPointerCapture(event.pointerId) : false; // if (implicitlyCaptured) { // $.console.log('pointerdown implicitlyCaptured ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); @@ -2970,9 +2512,9 @@ // } gPoint = { - id: event.pointerId, + id: getPointerId( event ), type: getPointerType( event ), - isPrimary: event.isPrimary, + isPrimary: getIsPrimary( event ), currentPos: getMouseAbsolute( event ), currentTime: $.now() }; @@ -3004,6 +2546,8 @@ /** + * Note: Called for both pointer events and legacy mouse events! + * * @private * @inner */ @@ -3013,6 +2557,8 @@ /** + * Note: Called for both pointer events and legacy mouse events! + * * This handler is attached to the window object (on the capture phase) to emulate mouse capture. * onPointerUp is still attached to the tracked element, so stop propagation to avoid processing twice. * @@ -3029,6 +2575,8 @@ /** + * Note: Called for both pointer events and legacy mouse events! + * * @private * @inner */ @@ -3038,9 +2586,9 @@ //$.console.log('onPointerUp ' + (tracker.userData ? tracker.userData.toString() : '')); gPoint = { - id: event.pointerId, + id: getPointerId( event ), type: getPointerType( event ), - isPrimary: event.isPrimary, + isPrimary: getIsPrimary( event ), currentPos: getMouseAbsolute( event ), currentTime: $.now() }; @@ -3073,6 +2621,8 @@ /** + * Note: Called for both pointer events and legacy mouse events! + * * @private * @inner */ @@ -3082,6 +2632,8 @@ /** + * Note: Called for both pointer events and legacy mouse events! + * * This handler is attached to the window object (on the capture phase) to emulate mouse capture. * onPointerMove is still attached to the tracked element, so stop propagation to avoid processing twice. * @@ -3098,6 +2650,8 @@ /** + * Note: Called for both pointer events and legacy mouse events! + * * @private * @inner */ @@ -3106,9 +2660,9 @@ var gPoint; gPoint = { - id: event.pointerId, + id: getPointerId( event ), type: getPointerType( event ), - isPrimary: event.isPrimary, + isPrimary: getIsPrimary( event ), currentPos: getMouseAbsolute( event ), currentTime: $.now() }; @@ -3608,46 +3162,24 @@ if ( typeof eventInfo.originalEvent.buttons !== 'undefined' ) { pointsList.buttons = eventInfo.originalEvent.buttons; } else { - if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { - if ( buttonChanged === 0 ) { - // Primary - pointsList.buttons += 1; - } else if ( buttonChanged === 1 ) { - // Aux - pointsList.buttons += 4; - } else if ( buttonChanged === 2 ) { - // Secondary - pointsList.buttons += 2; - } else if ( buttonChanged === 3 ) { - // X1 (Back) - pointsList.buttons += 8; - } else if ( buttonChanged === 4 ) { - // X2 (Forward) - pointsList.buttons += 16; - } else if ( buttonChanged === 5 ) { - // Pen Eraser - pointsList.buttons += 32; - } - } else { - if ( buttonChanged === 0 ) { - // Primary - pointsList.buttons |= 1; - } else if ( buttonChanged === 1 ) { - // Aux - pointsList.buttons |= 4; - } else if ( buttonChanged === 2 ) { - // Secondary - pointsList.buttons |= 2; - } else if ( buttonChanged === 3 ) { - // X1 (Back) - pointsList.buttons |= 8; - } else if ( buttonChanged === 4 ) { - // X2 (Forward) - pointsList.buttons |= 16; - } else if ( buttonChanged === 5 ) { - // Pen Eraser - pointsList.buttons |= 32; - } + if ( buttonChanged === 0 ) { + // Primary + pointsList.buttons |= 1; + } else if ( buttonChanged === 1 ) { + // Aux + pointsList.buttons |= 4; + } else if ( buttonChanged === 2 ) { + // Secondary + pointsList.buttons |= 2; + } else if ( buttonChanged === 3 ) { + // X1 (Back) + pointsList.buttons |= 8; + } else if ( buttonChanged === 4 ) { + // X2 (Forward) + pointsList.buttons |= 16; + } else if ( buttonChanged === 5 ) { + // Pen Eraser + pointsList.buttons |= 32; } } @@ -3773,46 +3305,24 @@ if ( typeof eventInfo.originalEvent.buttons !== 'undefined' ) { pointsList.buttons = eventInfo.originalEvent.buttons; } else { - if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { - if ( buttonChanged === 0 ) { - // Primary - pointsList.buttons -= 1; - } else if ( buttonChanged === 1 ) { - // Aux - pointsList.buttons -= 4; - } else if ( buttonChanged === 2 ) { - // Secondary - pointsList.buttons -= 2; - } else if ( buttonChanged === 3 ) { - // X1 (Back) - pointsList.buttons -= 8; - } else if ( buttonChanged === 4 ) { - // X2 (Forward) - pointsList.buttons -= 16; - } else if ( buttonChanged === 5 ) { - // Pen Eraser - pointsList.buttons -= 32; - } - } else { - if ( buttonChanged === 0 ) { - // Primary - pointsList.buttons ^= ~1; - } else if ( buttonChanged === 1 ) { - // Aux - pointsList.buttons ^= ~4; - } else if ( buttonChanged === 2 ) { - // Secondary - pointsList.buttons ^= ~2; - } else if ( buttonChanged === 3 ) { - // X1 (Back) - pointsList.buttons ^= ~8; - } else if ( buttonChanged === 4 ) { - // X2 (Forward) - pointsList.buttons ^= ~16; - } else if ( buttonChanged === 5 ) { - // Pen Eraser - pointsList.buttons ^= ~32; - } + if ( buttonChanged === 0 ) { + // Primary + pointsList.buttons ^= ~1; + } else if ( buttonChanged === 1 ) { + // Aux + pointsList.buttons ^= ~4; + } else if ( buttonChanged === 2 ) { + // Secondary + pointsList.buttons ^= ~2; + } else if ( buttonChanged === 3 ) { + // X1 (Back) + pointsList.buttons ^= ~8; + } else if ( buttonChanged === 4 ) { + // X2 (Forward) + pointsList.buttons ^= ~16; + } else if ( buttonChanged === 5 ) { + // Pen Eraser + pointsList.buttons ^= ~32; } } From 6b7fcbdc07bcec6ad05e35280e30dec55483a45d Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Fri, 5 Mar 2021 18:06:26 -0800 Subject: [PATCH 26/29] MouseTracker isEmulated documentation update --- src/mousetracker.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/mousetracker.js b/src/mousetracker.js index 269477e5..93e23743 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -1214,9 +1214,10 @@ * @property {String} pointerType * "mouse", "touch", "pen", etc. * @property {Boolean} isEmulated - * True if this is an emulated event. If true, originalEvent is the event that caused - * the emulated event or null if no DOM event applies. Emulated events - * can occur on eventType "wheel". + * True if this is an emulated event. If true, originalEvent is either the event that caused + * the emulated event, a synthetic event object created with values from the actual DOM event, + * or null if no DOM event applies. Emulated events can occur on eventType "wheel" on legacy mouse-scroll + * event emitting user agents. * @property {Boolean} isStopable * True if propagation of the event (e.g. bubbling) can be stopped with stopPropagation/stopImmediatePropagation. * @property {Boolean} isCancelable From 631881cb8aa8b5fc87294470b858ef5986b48471 Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Fri, 5 Mar 2021 18:39:31 -0800 Subject: [PATCH 27/29] Mousetracker documentation update --- src/mousetracker.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/mousetracker.js b/src/mousetracker.js index 93e23743..46e1f8cd 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -1677,6 +1677,8 @@ /** + * Note: Called for both pointer events and legacy mouse events + * ($.MouseTracker.havePointerEvents determines which) * @private * @inner */ @@ -1688,6 +1690,9 @@ /** * Gets a W3C Pointer Events model compatible pointer type string from a DOM pointer event. * IE10 used a long integer value, but the W3C specification (and IE11+) use a string "mouse", "touch", "pen", etc. + * + * Note: Called for both pointer events and legacy mouse events + * ($.MouseTracker.havePointerEvents determines which) * @private * @inner */ @@ -1704,6 +1709,8 @@ /** + * Note: Called for both pointer events and legacy mouse events + * ($.MouseTracker.havePointerEvents determines which) * @private * @inner */ From a94841cf97f0c0a745961c7d2fc3fda2d933cf14 Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Fri, 5 Mar 2021 18:50:40 -0800 Subject: [PATCH 28/29] MouseTracker documentation update --- src/mousetracker.js | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/mousetracker.js b/src/mousetracker.js index 46e1f8cd..2023e2ba 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -2355,7 +2355,8 @@ /** - * Note: Called for both pointer events and legacy mouse events! + * Note: Called for both pointer events and legacy mouse events + * ($.MouseTracker.havePointerEvents determines which) * * @private * @inner @@ -2389,7 +2390,8 @@ /** - * Note: Called for both pointer events and legacy mouse events! + * Note: Called for both pointer events and legacy mouse events + * ($.MouseTracker.havePointerEvents determines which) * * @private * @inner @@ -2423,7 +2425,8 @@ /** - * Note: Called for both pointer events and legacy mouse events! + * Note: Called for both pointer events and legacy mouse events + * ($.MouseTracker.havePointerEvents determines which) * * @private * @inner @@ -2459,7 +2462,8 @@ /** - * Note: Called for both pointer events and legacy mouse events! + * Note: Called for both pointer events and legacy mouse events + * ($.MouseTracker.havePointerEvents determines which) * * @private * @inner @@ -2495,7 +2499,8 @@ /** - * Note: Called for both pointer events and legacy mouse events! + * Note: Called for both pointer events and legacy mouse events + * ($.MouseTracker.havePointerEvents determines which) * * @private * @inner @@ -2554,7 +2559,8 @@ /** - * Note: Called for both pointer events and legacy mouse events! + * Note: Called for both pointer events and legacy mouse events + * ($.MouseTracker.havePointerEvents determines which) * * @private * @inner @@ -2565,7 +2571,8 @@ /** - * Note: Called for both pointer events and legacy mouse events! + * Note: Called for both pointer events and legacy mouse events + * ($.MouseTracker.havePointerEvents determines which) * * This handler is attached to the window object (on the capture phase) to emulate mouse capture. * onPointerUp is still attached to the tracked element, so stop propagation to avoid processing twice. @@ -2583,7 +2590,8 @@ /** - * Note: Called for both pointer events and legacy mouse events! + * Note: Called for both pointer events and legacy mouse events + * ($.MouseTracker.havePointerEvents determines which) * * @private * @inner @@ -2629,7 +2637,8 @@ /** - * Note: Called for both pointer events and legacy mouse events! + * Note: Called for both pointer events and legacy mouse events + * ($.MouseTracker.havePointerEvents determines which) * * @private * @inner @@ -2640,7 +2649,8 @@ /** - * Note: Called for both pointer events and legacy mouse events! + * Note: Called for both pointer events and legacy mouse events + * ($.MouseTracker.havePointerEvents determines which) * * This handler is attached to the window object (on the capture phase) to emulate mouse capture. * onPointerMove is still attached to the tracked element, so stop propagation to avoid processing twice. @@ -2658,7 +2668,8 @@ /** - * Note: Called for both pointer events and legacy mouse events! + * Note: Called for both pointer events and legacy mouse events + * ($.MouseTracker.havePointerEvents determines which) * * @private * @inner From ac97d5ff3c39afa32e4dcd7f24bf3d88d9f29c27 Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Sun, 7 Mar 2021 14:24:32 -0800 Subject: [PATCH 29/29] MouseTracker minor code formatting fixes --- src/mousetracker.js | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/mousetracker.js b/src/mousetracker.js index 2023e2ba..4aefd249 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -2362,11 +2362,9 @@ * @inner */ function onPointerEnter( tracker, event ) { - var gPoint; - //$.console.log('pointerenter ' + (tracker.userData ? tracker.userData.toString() : '')); - gPoint = { + var gPoint = { id: getPointerId( event ), type: getPointerType( event ), isPrimary: getIsPrimary( event ), @@ -2397,11 +2395,9 @@ * @inner */ function onPointerLeave( tracker, event ) { - var gPoint; - //$.console.log('pointerleave ' + (tracker.userData ? tracker.userData.toString() : '')); - gPoint = { + var gPoint = { id: getPointerId( event ), type: getPointerType( event ), isPrimary: getIsPrimary( event ), @@ -2506,8 +2502,6 @@ * @inner */ function onPointerDown( tracker, event ) { - var gPoint; - //$.console.log('onPointerDown ' + (tracker.userData ? tracker.userData.toString() : '')); // $.console.log('onPointerDown ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + event.target.tagName); @@ -2524,7 +2518,7 @@ // $.console.log('pointerdown not implicitlyCaptured ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); // } - gPoint = { + var gPoint = { id: getPointerId( event ), type: getPointerType( event ), isPrimary: getIsPrimary( event ), @@ -2676,9 +2670,8 @@ */ function handlePointerMove( tracker, event ) { // Pointer changed coordinates, button state, pressure, tilt, or contact geometry (e.g. width and height) - var gPoint; - gPoint = { + var gPoint = { id: getPointerId( event ), type: getPointerType( event ), isPrimary: getIsPrimary( event ),