From d2bb48036317538671b1ecf0b1fc61b77649d9a4 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Fri, 24 Jul 2020 20:05:39 -0700 Subject: [PATCH 01/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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 8178687298626533c48a94aa3099416e0fbbacda Mon Sep 17 00:00:00 2001 From: Steve Halasz Date: Thu, 10 Sep 2020 17:40:30 -0400 Subject: [PATCH 20/41] Better handle destruction when navigator in custom location --- src/button.js | 24 ++++++++++++++++-------- src/control.js | 4 +++- src/viewer.js | 10 +++++++++- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/button.js b/src/button.js index 7b00f9ee..5668c871 100644 --- a/src/button.js +++ b/src/button.js @@ -399,14 +399,22 @@ $.extend( $.Button.prototype, $.EventSource.prototype, /** @lends OpenSeadragon. }, destroy: function() { - this.element.removeChild(this.imgRest); - this.imgRest = null; - this.element.removeChild(this.imgGroup); - this.imgGroup = null; - this.element.removeChild(this.imgHover); - this.imgHover = null; - this.element.removeChild(this.imgDown); - this.imgDown = null; + if (this.imgRest) { + this.element.removeChild(this.imgRest); + this.imgRest = null; + } + if (this.imgGroup) { + this.element.removeChild(this.imgGroup); + this.imgGroup = null; + } + if (this.imgHover) { + this.element.removeChild(this.imgHover); + this.imgHover = null; + } + if (this.imgDown) { + this.element.removeChild(this.imgDown); + this.imgDown = null; + } this.removeAllHandlers(); this.tracker.destroy(); this.element = null; diff --git a/src/control.js b/src/control.js index 64f762d7..6cb229f8 100644 --- a/src/control.js +++ b/src/control.js @@ -161,7 +161,9 @@ $.Control.prototype = { */ destroy: function() { this.wrapper.removeChild( this.element ); - this.container.removeChild( this.wrapper ); + if (this.anchor !== $.ControlAnchor.NONE) { + this.container.removeChild(this.wrapper); + } }, /** diff --git a/src/viewer.js b/src/viewer.js index b5b24318..9f6e767a 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -775,7 +775,13 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, this.removeAllHandlers(); if (this.buttons) { - this.buttons.destroy(); + if (this.buttons instanceof $.ButtonGroup) { + this.buttons.destroy(); + } else { + while (this.buttons.length) { + this.buttons.pop().destroy(); + } + } } if (this.paging) { @@ -1869,6 +1875,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, {anchor: this.navigationControlAnchor || $.ControlAnchor.TOP_LEFT} ); } + } else { + this.buttons = buttons; } } From 066de5f13f32e211854273a813d0c3885b58324e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Dec 2020 20:04:36 +0000 Subject: [PATCH 21/41] Bump ini from 1.3.5 to 1.3.7 Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.7. - [Release notes](https://github.com/isaacs/ini/releases) - [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.7) Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 04ffb707..d236a0a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2578,9 +2578,9 @@ "dev": true }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", "dev": true }, "is-accessor-descriptor": { From 0ede8992de954e2a1641b7ce60fb865d3c3849a8 Mon Sep 17 00:00:00 2001 From: Steve Halasz Date: Fri, 18 Dec 2020 13:25:29 -0500 Subject: [PATCH 22/41] Separate properties for buttonGroup and customButtons This makes it more clear what we're testing for or calling methods on, vs. assigning to this.buttons in both the useGroup true and false cases. --- src/viewer.js | 40 ++++++++++++++++++++-------------------- test/modules/controls.js | 12 ++++++------ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/viewer.js b/src/viewer.js index 9f6e767a..07d55e9e 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -409,14 +409,14 @@ $.Viewer = function( options ) { if (!this.drawer.canRotate()) { // Disable/remove the rotate left/right buttons since they aren't supported if (this.rotateLeft) { - i = this.buttons.buttons.indexOf(this.rotateLeft); - this.buttons.buttons.splice(i, 1); - this.buttons.element.removeChild(this.rotateLeft.element); + i = this.buttonGroup.buttons.indexOf(this.rotateLeft); + this.buttonGroup.buttons.splice(i, 1); + this.buttonGroup.element.removeChild(this.rotateLeft.element); } if (this.rotateRight) { - i = this.buttons.buttons.indexOf(this.rotateRight); - this.buttons.buttons.splice(i, 1); - this.buttons.element.removeChild(this.rotateRight.element); + i = this.buttonGroup.buttons.indexOf(this.rotateRight); + this.buttonGroup.buttons.splice(i, 1); + this.buttonGroup.element.removeChild(this.rotateRight.element); } } @@ -774,13 +774,11 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, this.removeAllHandlers(); - if (this.buttons) { - if (this.buttons instanceof $.ButtonGroup) { - this.buttons.destroy(); - } else { - while (this.buttons.length) { - this.buttons.pop().destroy(); - } + if (this.buttonGroup) { + this.buttonGroup.destroy(); + } else if (this.customButtons) { + while (this.customButtons.length) { + this.customButtons.pop().destroy(); } } @@ -1855,13 +1853,13 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, } if ( useGroup ) { - this.buttons = new $.ButtonGroup({ + this.buttonGroup = new $.ButtonGroup({ buttons: buttons, clickTimeThreshold: this.clickTimeThreshold, clickDistThreshold: this.clickDistThreshold }); - this.navControl = this.buttons.element; + this.navControl = this.buttonGroup.element; this.addHandler( 'open', $.delegate( this, lightUp ) ); if( this.toolbar ){ @@ -1876,7 +1874,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, ); } } else { - this.buttons = buttons; + this.customButtons = buttons; } } @@ -3490,8 +3488,10 @@ function doSingleZoomOut() { function lightUp() { - this.buttons.emulateEnter(); - this.buttons.emulateExit(); + if (this.buttonGroup) { + this.buttonGroup.emulateEnter(); + this.buttonGroup.emulateExit(); + } } @@ -3510,8 +3510,8 @@ function onFullScreen() { this.setFullScreen( !this.isFullPage() ); } // correct for no mouseout event on change - if ( this.buttons ) { - this.buttons.emulateExit(); + if ( this.buttonGroup ) { + this.buttonGroup.emulateExit(); } this.fullPageButton.element.focus(); if ( this.viewport ) { diff --git a/test/modules/controls.js b/test/modules/controls.js index 57d827c9..0e6bdf7c 100644 --- a/test/modules/controls.js +++ b/test/modules/controls.js @@ -53,9 +53,9 @@ assert.ok(viewer.showZoomControl, 'showZoomControl should be on'); assert.ok(!!viewer.zoomInButton, "zoomIn button should not be null"); assert.ok(!!viewer.zoomOutButton, "zoomOut button should not be null"); - assert.notEqual(viewer.buttons.buttons.indexOf(viewer.zoomInButton), -1, + assert.notEqual(viewer.buttonGroup.buttons.indexOf(viewer.zoomInButton), -1, "The zoomIn button should be present"); - assert.notEqual(viewer.buttons.buttons.indexOf(viewer.zoomOutButton), -1, + assert.notEqual(viewer.buttonGroup.buttons.indexOf(viewer.zoomOutButton), -1, "The zoomOut button should be present"); var oldZoom = viewer.viewport.getZoom(); @@ -108,7 +108,7 @@ viewer.removeHandler('open', openHandler); assert.ok(viewer.showHomeControl, 'showHomeControl should be on'); assert.ok(!!viewer.homeButton, "Home button should not be null"); - assert.notEqual(viewer.buttons.buttons.indexOf(viewer.homeButton), -1, + assert.notEqual(viewer.buttonGroup.buttons.indexOf(viewer.homeButton), -1, "The home button should be present"); viewer.viewport.zoomBy(1.1); @@ -167,7 +167,7 @@ viewer.removeHandler('open', openHandler); assert.ok(viewer.showHomeControl, 'showFullPageControl should be on'); assert.ok(!!viewer.fullPageButton, "FullPage button should not be null"); - assert.notEqual(viewer.buttons.buttons.indexOf(viewer.fullPageButton), -1, + assert.notEqual(viewer.buttonGroup.buttons.indexOf(viewer.fullPageButton), -1, "The full page button should be present"); assert.ok(!viewer.isFullPage(), "OSD should not be in full page."); @@ -223,9 +223,9 @@ assert.ok(viewer.drawer, 'Drawer exists'); assert.ok(viewer.drawer.canRotate(), 'drawer.canRotate needs to be true'); assert.ok(viewer.showRotationControl, 'showRotationControl should be true'); - assert.notEqual(viewer.buttons.buttons.indexOf(viewer.rotateLeftButton), -1, + assert.notEqual(viewer.buttonGroup.buttons.indexOf(viewer.rotateLeftButton), -1, "rotateLeft should be found"); - assert.notEqual(viewer.buttons.buttons.indexOf(viewer.rotateRightButton), -1, + assert.notEqual(viewer.buttonGroup.buttons.indexOf(viewer.rotateRightButton), -1, "rotateRight should be found"); // Now simulate the left/right button clicks. From 323f653d2a32a72ce3b6ac991402110402df03fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Feb 2021 18:46:21 +0000 Subject: [PATCH 23/41] Bump bl from 1.2.2 to 1.2.3 Bumps [bl](https://github.com/rvagg/bl) from 1.2.2 to 1.2.3. - [Release notes](https://github.com/rvagg/bl/releases) - [Commits](https://github.com/rvagg/bl/compare/v1.2.2...v1.2.3) Signed-off-by: dependabot[bot] --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index d236a0a3..96715f72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -389,9 +389,9 @@ } }, "bl": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", "dev": true, "requires": { "readable-stream": "^2.3.5", @@ -4938,9 +4938,9 @@ }, "dependencies": { "bl": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz", - "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, "optional": true, "requires": { From 73dc6895f152fd167d8245ef30d27f7f41d12895 Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Tue, 9 Feb 2021 16:28:42 -0800 Subject: [PATCH 24/41] 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 25/41] 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 26/41] 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 27/41] 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 28/41] 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 29/41] 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 30/41] 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 31/41] 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 32/41] 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 33/41] 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 ), From 6c317d080775cf7519962a77083b1f053dd61258 Mon Sep 17 00:00:00 2001 From: Steve Halasz Date: Mon, 8 Mar 2021 08:18:40 -0500 Subject: [PATCH 34/41] Changleg for #1878 --- changelog.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index d2e5f249..8dec804a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -6,6 +6,7 @@ OPENSEADRAGON CHANGELOG * 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) +* Better handle destruction when navigator in custom location (#1884 @woodchuck) * Miscellaneous code cleanup (#1840 @msalsbery) * You can now specify tileSize for the Zoomify Tile Source (#1868 @abrlam) * Better use of IIIF "max" and "full" URL parameters (#1871 @MImranAsghar) @@ -56,7 +57,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) From 6fa083d2e06066a877db23b329ec91bab774a62c Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Mon, 8 Mar 2021 13:33:05 -0800 Subject: [PATCH 35/41] Remove support for IE < 11 (#1863) --- changelog.txt | 2 + src/openseadragon.js | 161 +++--------------------------- test/helpers/legacy.mouse.shim.js | 12 +-- 3 files changed, 16 insertions(+), 159 deletions(-) diff --git a/changelog.txt b/changelog.txt index 0044fb07..d99b4d73 100644 --- a/changelog.txt +++ b/changelog.txt @@ -33,6 +33,8 @@ OPENSEADRAGON CHANGELOG * 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) +* Dropped support for older browsers (IE < 11) +* Removed deprecated OpenSeadragon.getEvent function 2.4.2: diff --git a/src/openseadragon.js b/src/openseadragon.js index 86320fdb..1f7fbe60 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -1577,29 +1577,6 @@ function OpenSeadragon( options ){ }, - /** - * Gets the latest event, really only useful internally since its - * specific to IE behavior. - * @function - * @param {Event} [event] - * @returns {Event} - * @deprecated For internal use only - * @private - */ - getEvent: function( event ) { - if( event ){ - $.getEvent = function( event ) { - return event; - }; - } else { - $.getEvent = function() { - return window.event; - }; - } - return $.getEvent( event ); - }, - - /** * Gets the position of the mouse on the screen for a given event. * @function @@ -1612,7 +1589,6 @@ function OpenSeadragon( options ){ $.getMousePosition = function( event ){ var result = new $.Point(); - event = $.getEvent( event ); result.x = event.pageX; result.y = event.pageY; @@ -1622,7 +1598,6 @@ function OpenSeadragon( options ){ $.getMousePosition = function( event ){ var result = new $.Point(); - event = $.getEvent( event ); result.x = event.clientX + document.body.scrollLeft + @@ -1858,51 +1833,16 @@ function OpenSeadragon( options ){ /** * Ensures an image is loaded correctly to support alpha transparency. - * Generally only IE has issues doing this correctly for formats like - * png. * @function * @param {String} src * @returns {Element} */ makeTransparentImage: function( src ) { + var img = $.makeNeutralElement( "img" ); - $.makeTransparentImage = function( src ){ - var img = $.makeNeutralElement( "img" ); + img.src = src; - img.src = src; - - return img; - }; - - if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 7 ) { - - $.makeTransparentImage = function( src ){ - var img = $.makeNeutralElement( "img" ), - element = null; - - element = $.makeNeutralElement("span"); - element.style.display = "inline-block"; - - img.onload = function() { - element.style.width = element.style.width || img.width + "px"; - element.style.height = element.style.height || img.height + "px"; - - img.onload = null; - img = null; // to prevent memory leaks in IE - }; - - img.src = src; - element.style.filter = - "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + - src + - "', sizingMethod='scale')"; - - return element; - }; - - } - - return $.makeTransparentImage( src ); + return img; }, @@ -2144,23 +2084,7 @@ function OpenSeadragon( options ){ * @param {Event} [event] */ cancelEvent: function( event ) { - event = $.getEvent( event ); - - if ( event.preventDefault ) { - $.cancelEvent = function( event ){ - // W3C for preventing default - event.preventDefault(); - }; - } else { - $.cancelEvent = function( event ){ - event = $.getEvent( event ); - // legacy for preventing default - event.cancel = true; - // IE < 9 for preventing default - event.returnValue = false; - }; - } - $.cancelEvent( event ); + event.preventDefault(); }, @@ -2171,28 +2095,7 @@ function OpenSeadragon( options ){ * @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 ); + return event.defaultPrevented; }, @@ -2202,23 +2105,7 @@ function OpenSeadragon( options ){ * @param {Event} [event] */ stopEvent: function( event ) { - event = $.getEvent( event ); - - if ( event.stopPropagation ) { - // W3C for stopping propagation - $.stopEvent = function( event ){ - event.stopPropagation(); - }; - } else { - // IE < 9 for stopping propagation - $.stopEvent = function( event ){ - event = $.getEvent( event ); - event.cancelBubble = true; - }; - - } - - $.stopEvent( event ); + event.stopPropagation(); }, @@ -2407,25 +2294,7 @@ function OpenSeadragon( options ){ request.send(null); } catch (e) { - var msg = e.message; - - /* - IE < 10 does not support CORS and an XHR request to a different origin will fail as soon - as send() is called. This is particularly easy to miss during development and appear in - production if you use a CDN or domain sharding and the security policy is likely to break - exception handlers since any attempt to access a property of the request object will - raise an access denied TypeError inside the catch block. - - To be friendlier, we'll check for this specific error and add a documentation pointer - to point developers in the right direction. We test the exception number because IE's - error messages are localized. - */ - var oldIE = $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 10; - if ( oldIE && typeof ( e.number ) !== "undefined" && e.number === -2147024891 ) { - msg += "\nSee http://msdn.microsoft.com/en-us/library/ms537505(v=vs.85).aspx#xdomain"; - } - - $.console.log( "%s while making AJAX request: %s", e.name, msg ); + $.console.log( "%s while making AJAX request: %s", e.name, e.message ); request.onreadystatechange = function(){}; @@ -2772,21 +2641,15 @@ function OpenSeadragon( options ){ //determine if this browser supports image alpha transparency $.Browser.alpha = !( - ( - $.Browser.vendor === $.BROWSERS.IE && - $.Browser.version < 9 - ) || ( - $.Browser.vendor === $.BROWSERS.CHROME && - $.Browser.version < 2 - ) + $.Browser.vendor === $.BROWSERS.CHROME && $.Browser.version < 2 ); //determine if this browser supports element.style.opacity - $.Browser.opacity = !( - $.Browser.vendor === $.BROWSERS.IE && - $.Browser.version < 9 - ); + $.Browser.opacity = true; + if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 11 ) { + $.console.error('Internet Explorer versions < 11 are not supported by OpenSeadragon'); + } })(); diff --git a/test/helpers/legacy.mouse.shim.js b/test/helpers/legacy.mouse.shim.js index e4740138..19374147 100644 --- a/test/helpers/legacy.mouse.shim.js +++ b/test/helpers/legacy.mouse.shim.js @@ -6,21 +6,13 @@ $.MouseTracker.subscribeEvents = [ "click", "dblclick", "keypress", "focus", "blur", $.MouseTracker.wheelEventName ]; - if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) { + if( $.MouseTracker.wheelEventName === "DOMMouseScroll" ) { // Older Firefox $.MouseTracker.subscribeEvents.push( "MozMousePixelScroll" ); } $.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 () { From a520da0d559d6446b0fc8fb21c59e52dcca08fbc Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Mon, 8 Mar 2021 13:35:27 -0800 Subject: [PATCH 36/41] Changelog update --- changelog.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index d99b4d73..8caee8c4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -33,8 +33,8 @@ OPENSEADRAGON CHANGELOG * 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) -* Dropped support for older browsers (IE < 11) -* Removed deprecated OpenSeadragon.getEvent function +* Dropped support for older browsers (IE < 11) (#1949 @msalsbery) +* Removed deprecated OpenSeadragon.getEvent function (#1949 @msalsbery) 2.4.2: From 5eea11e2a527b3a3aab78b1f9d8685dc4d0b8762 Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Mon, 8 Mar 2021 15:42:12 -0800 Subject: [PATCH 37/41] Fixed simulated drag events in navigator tests --- changelog.txt | 1 + test/modules/navigator.js | 27 ++++++++++++++++++++------- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/changelog.txt b/changelog.txt index 8caee8c4..4e9ae9d0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -35,6 +35,7 @@ OPENSEADRAGON CHANGELOG * MouseTracker: Per #1863, dropped support for Internet Explorer < 11 (#1872 @msalsbery) * Dropped support for older browsers (IE < 11) (#1949 @msalsbery) * Removed deprecated OpenSeadragon.getEvent function (#1949 @msalsbery) +* Fixed simulated drag events in navigator tests (#1949 @msalsbery) 2.4.2: diff --git a/test/modules/navigator.js b/test/modules/navigator.js index d1f98dea..67bfbd43 100644 --- a/test/modules/navigator.js +++ b/test/modules/navigator.js @@ -222,13 +222,26 @@ }; var simulateNavigatorDrag = function (viewer, distanceX, distanceY) { - var $canvas = $(viewer.element).find('.displayregion'), - event = { - dx: Math.floor(distanceX), - dy: Math.floor(distanceY) - }; - $canvas - .simulate('drag', event); + var $canvas = $(viewer.element).find('.openseadragon-canvas'), + offset = $canvas.offset(), + event = {}; + + event.clientX = offset.left + 1; + event.clientY = offset.top + 1; + $canvas.simulate( 'mouseenter', event ); + + event.button = 0; + $canvas.simulate( 'mousedown', event ); + + event.clientX += distanceX; + event.clientY += distanceY; + $canvas.simulate( 'mousemove', event ); + + event.button = 0; + $canvas.simulate( 'mouseup', event ); + + event.relatedTarget = document.body; + $canvas.simulate( 'mouseleave', event ); }; var dragNavigatorBackToCenter = function () { From 4f94de0ef6038a3a61a53a2f2ccb2a49fbda3281 Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Mon, 8 Mar 2021 16:10:15 -0800 Subject: [PATCH 38/41] Changelog update --- changelog.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index 4e9ae9d0..1c325eba 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,8 @@ OPENSEADRAGON CHANGELOG 2.5.0: (In progress) +* BREAKING CHANGE: Dropped support for older browsers (IE < 11) (#1949 @msalsbery) +* BREAKING CHANGE: Removed deprecated OpenSeadragon.getEvent function (#1949 @msalsbery) * 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) @@ -33,8 +35,6 @@ OPENSEADRAGON CHANGELOG * 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) -* Dropped support for older browsers (IE < 11) (#1949 @msalsbery) -* Removed deprecated OpenSeadragon.getEvent function (#1949 @msalsbery) * Fixed simulated drag events in navigator tests (#1949 @msalsbery) 2.4.2: From bf0b7842cac6a1fd2d30caa33c3ee07005686cce Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Tue, 9 Mar 2021 11:22:34 -0800 Subject: [PATCH 39/41] Sane pointer coords for navigator tests drag simulations --- test/modules/navigator.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/modules/navigator.js b/test/modules/navigator.js index 67bfbd43..e55c5400 100644 --- a/test/modules/navigator.js +++ b/test/modules/navigator.js @@ -240,6 +240,8 @@ event.button = 0; $canvas.simulate( 'mouseup', event ); + event.clientX = offset.left - 1; + event.clientY = offset.top - 1; event.relatedTarget = document.body; $canvas.simulate( 'mouseleave', event ); }; From 515a155cf66425bd119811bc2d836ff141eb7d5d Mon Sep 17 00:00:00 2001 From: rmontroy Date: Wed, 10 Mar 2021 13:48:09 -0500 Subject: [PATCH 40/41] Remove support for IE < 11 --- src/openseadragon.js | 47 +++----------------------------------------- 1 file changed, 3 insertions(+), 44 deletions(-) diff --git a/src/openseadragon.js b/src/openseadragon.js index 1f7fbe60..f61e0133 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -2298,40 +2298,8 @@ function OpenSeadragon( options ){ request.onreadystatechange = function(){}; - if (window.XDomainRequest) { // IE9 or IE8 might as well try to use XDomainRequest - var xdr = new window.XDomainRequest(); - if (xdr) { - xdr.onload = function (e) { - if ( $.isFunction( onSuccess ) ) { - onSuccess({ // Faking an xhr object - responseText: xdr.responseText, - status: 200, // XDomainRequest doesn't support status codes, so we just fake one! :/ - statusText: 'OK' - }); - } - }; - xdr.onerror = function (e) { - if ($.isFunction(onError)) { - onError({ // Faking an xhr object - responseText: xdr.responseText, - status: 444, // 444 No Response - statusText: 'An error happened. Due to an XDomainRequest deficiency we can not extract any information about this error. Upgrade your browser.' - }); - } - }; - try { - xdr.open('GET', url); - xdr.send(); - } catch (e2) { - if ( $.isFunction( onError ) ) { - onError( request, e ); - } - } - } - } else { - if ( $.isFunction( onError ) ) { - onError( request, e ); - } + if ( $.isFunction( onError ) ) { + onError( request, e ); } } @@ -2470,16 +2438,7 @@ function OpenSeadragon( options ){ * @returns {Object} */ parseJSON: function(string) { - if (window.JSON && window.JSON.parse) { - $.parseJSON = window.JSON.parse; - } else { - // Should only be used by IE8 in non standards mode - $.parseJSON = function(string) { - /*jshint evil:true*/ - //eslint-disable-next-line no-eval - return eval('(' + string + ')'); - }; - } + $.parseJSON = window.JSON.parse; return $.parseJSON(string); }, From a96e369c0f3f71833c2540416709f84adf120f53 Mon Sep 17 00:00:00 2001 From: Rob Montroy Date: Wed, 10 Mar 2021 19:04:07 -0500 Subject: [PATCH 41/41] Changelog update --- changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 1c325eba..38fcc2d4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -34,7 +34,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) +* MouseTracker: Per #1863, dropped support for Internet Explorer < 11 (#1872 @msalsbery) (#1950 @rmontroy) * Fixed simulated drag events in navigator tests (#1949 @msalsbery) 2.4.2: