From 00187cb5a178fb6e23f88c8ba16b7f10e5fb94b1 Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Mon, 3 May 2021 11:38:20 -0700 Subject: [PATCH 1/7] Fixed legacy mouse and touch event handling --- src/button.js | 8 +-- src/drawer.js | 2 +- src/mousetracker.js | 129 ++++++++++++++++++++++++++---------------- src/navigator.js | 8 +-- src/openseadragon.js | 7 ++- src/referencestrip.js | 6 +- src/viewer.js | 2 +- 7 files changed, 96 insertions(+), 66 deletions(-) diff --git a/src/button.js b/src/button.js index 7abee6a7..ecf3101f 100644 --- a/src/button.js +++ b/src/button.js @@ -140,10 +140,10 @@ $.Button = function( options ) { // 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 ); + $.setElementPointerEvents( this.imgRest, 'none' ); + $.setElementPointerEvents( this.imgGroup, 'none' ); + $.setElementPointerEvents( this.imgHover, 'none' ); + $.setElementPointerEvents( this.imgDown, 'none' ); this.element.style.position = "relative"; $.setElementTouchActionNone( this.element ); diff --git a/src/drawer.js b/src/drawer.js index fca4b956..a022850a 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -128,7 +128,7 @@ $.Drawer = function( options ) { $.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 ); + $.setElementPointerEvents( this.canvas, 'none' ); $.setElementTouchActionNone( this.canvas ); // explicit left-align diff --git a/src/mousetracker.js b/src/mousetracker.js index f4214a27..b203e301 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -281,6 +281,10 @@ this.pinchHandler ); this.hasScrollHandler = !!this.scrollHandler; + if ( $.MouseTracker.havePointerEvents ) { + $.setElementPointerEvents( this.element, 'auto' ); + } + if (this.exitHandler) { $.console.error("MouseTracker.exitHandler is deprecated. Use MouseTracker.leaveHandler instead."); } @@ -1539,13 +1543,14 @@ if ( $.MouseTracker.havePointerCapture ) { if ( $.MouseTracker.havePointerEvents ) { - // Can throw InvalidPointerId + // Can throw NotFoundError (InvalidPointerId Firefox < 82) // (should never happen so we'll log a warning) try { tracker.element.setPointerCapture( gPoint.id ); //$.console.log('element.setPointerCapture() called'); } catch ( e ) { $.console.warn('setPointerCapture() called on invalid pointer ID'); + return; } } else { tracker.element.setCapture( true ); @@ -1601,7 +1606,7 @@ if ( !cachedGPoint || !cachedGPoint.captured ) { return; } - // Can throw InvalidPointerId + // Can throw NotFoundError (InvalidPointerId Firefox < 82) // (should never happen, but it does on Firefox 79 touch so we won't log a warning) try { tracker.element.releasePointerCapture( gPoint.id ); @@ -1731,7 +1736,7 @@ * @inner */ function onClick( tracker, event ) { - //$.console.log('onClick ' + (tracker.userData ? tracker.userData.toString() : '')); + //$.console.log('click ' + (tracker.userData ? tracker.userData.toString() : '')); var eventInfo = { originalEvent: event, @@ -1755,7 +1760,7 @@ * @inner */ function onDblClick( tracker, event ) { - //$.console.log('onDblClick ' + (tracker.userData ? tracker.userData.toString() : '')); + //$.console.log('dblclick ' + (tracker.userData ? tracker.userData.toString() : '')); var eventInfo = { originalEvent: event, @@ -1904,7 +1909,7 @@ * @inner */ function onFocus( tracker, event ) { - //console.log( "focus %s", event ); + //$.console.log('focus ' + (tracker.userData ? tracker.userData.toString() : '')); // focus doesn't bubble and is not cancelable, but we call // preProcessEvent() so it's dispatched to preProcessEventHandler @@ -1934,7 +1939,7 @@ * @inner */ function onBlur( tracker, event ) { - //console.log( "blur %s", event ); + //$.console.log('blur ' + (tracker.userData ? tracker.userData.toString() : '')); // blur doesn't bubble and is not cancelable, but we call // preProcessEvent() so it's dispatched to preProcessEventHandler @@ -2116,7 +2121,9 @@ }; preProcessEvent( tracker, eventInfo ); - updatePointerCaptured( tracker, gPoint, false ); + if ( event.target === tracker.element ) { + updatePointerCaptured( tracker, gPoint, false ); + } if ( eventInfo.stopPropagation ) { $.stopEvent( event ); @@ -2137,7 +2144,7 @@ time = $.now(); - //$.console.log('touchstart ' + (tracker.userData ? tracker.userData.toString() : '')); + $.console.log('touchstart ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); if ( pointsList.getLength() > event.touches.length - touchCount ) { $.console.warn('Tracked touch contact count doesn\'t match event.touches.length'); @@ -2164,9 +2171,9 @@ // simulate touchenter on our tracked element updatePointerEnter( tracker, eventInfo, gPoint ); - updatePointerCaptured( tracker, gPoint, true ); - updatePointerDown( tracker, eventInfo, gPoint, 0 ); + + updatePointerCaptured( tracker, gPoint, true ); } if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { @@ -2190,7 +2197,7 @@ time = $.now(); - //$.console.log('touchend ' + (tracker.userData ? tracker.userData.toString() : '')); + $.console.log('touchend ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); var eventInfo = { originalEvent: event, @@ -2329,7 +2336,7 @@ * @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, @@ -2358,7 +2365,7 @@ * @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, @@ -2530,22 +2537,6 @@ * @inner */ function onPointerDown( tracker, event ) { - //$.console.log('onPointerDown ' + (tracker.userData ? tracker.userData.toString() : '')); - // $.console.log('onPointerDown ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + event.target.tagName); - - // Most browsers implicitly capture touch pointer events - // Note no IE versions have element.hasPointerCapture() so no implicit - // pointer capture possible - 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' : '')); - // } else { - // $.console.log('pointerdown not implicitlyCaptured ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); - // } - var gPoint = { id: getPointerId( event ), type: getPointerType( event ), @@ -2554,6 +2545,31 @@ currentTime: $.now() }; + var ourElement = event.target === tracker.element; + + // Most browsers implicitly capture touch pointer events + // Note no IE versions have element.hasPointerCapture() so no implicit + // pointer capture possible + // var implicitlyCaptured = ($.MouseTracker.havePointerEvents && + // event.target.hasPointerCapture && + // $.Browser.vendor !== $.BROWSERS.IE) ? + // event.target.hasPointerCapture(event.pointerId) : false; + var implicitlyCaptured = $.MouseTracker.havePointerEvents && + gPoint.type === 'touch' && + $.Browser.vendor !== $.BROWSERS.IE; + + //$.console.log('pointerdown ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (ourElement ? 'tracker.element' : '')); + + if (implicitlyCaptured) { + $.console.log('pointerdown implicitlyCaptured ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (ourElement ? 'tracker.element' : '')); + } else { + $.console.log('pointerdown not implicitlyCaptured ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (ourElement ? 'tracker.element' : '')); + } + + // if ( implicitlyCaptured && ourElement ) { + // updatePointerCaptured( tracker, gPoint, true ); + // } + var eventInfo = { originalEvent: event, eventType: 'pointerdown', @@ -2570,13 +2586,20 @@ if ( eventInfo.stopPropagation ) { $.stopEvent( event ); } - if ( eventInfo.shouldCapture && !implicitlyCaptured ) { - //$.console.log('pointerdown calling capturePointer() ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); - capturePointer( tracker, gPoint ); - } else if ( !eventInfo.shouldCapture && implicitlyCaptured ) { - //$.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 - } + if ( eventInfo.shouldCapture ) { + if ( implicitlyCaptured ) { + $.console.log('pointerdown calling updatePointerCaptured() ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + updatePointerCaptured( tracker, gPoint, true ); + } else { + $.console.log('pointerdown calling capturePointer() ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + capturePointer( tracker, gPoint ); + } + }// else if ( implicitlyCaptured && ourElement ) { + // //$.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 + // $.console.log('pointerdown calling updatePointerCaptured(false) ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + // updatePointerCaptured( tracker, gPoint, false ); + // } } @@ -2622,6 +2645,7 @@ var gPoint; //$.console.log('onPointerUp ' + (tracker.userData ? tracker.userData.toString() : '')); + $.console.log('pointerup ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); gPoint = { id: getPointerId( event ), @@ -2651,9 +2675,14 @@ // 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 ); + if ( eventInfo.shouldReleaseCapture ) { + if ( event.target === tracker.element ) { + $.console.log('pointerup calling releasePointer() ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + releasePointer( tracker, gPoint ); + } else { + $.console.log('pointerup calling updatePointerCaptured() ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + updatePointerCaptured( tracker, gPoint, false ); + } } } @@ -2732,6 +2761,7 @@ */ function onPointerCancel( tracker, event ) { //$.console.log('pointercancel ' + (tracker.userData ? tracker.userData.toString() : '')); + $.console.log('pointercancel ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); var gPoint = { id: event.pointerId, @@ -2770,6 +2800,7 @@ * @returns {Number} Number of gesture points in pointsList. */ function startTrackingPointer( pointsList, gPoint ) { + //$.console.log('startTrackingPointer *** ' + pointsList.type + ' ' + gPoint.id.toString()); gPoint.speed = 0; gPoint.direction = 0; gPoint.contactPos = gPoint.currentPos; @@ -2794,6 +2825,7 @@ * @returns {Number} Number of gesture points in pointsList. */ function stopTrackingPointer( tracker, pointsList, gPoint ) { + //$.console.log('stopTrackingPointer *** ' + pointsList.type + ' ' + gPoint.id.toString()); var listLength; var trackedGPoint = pointsList.getById( gPoint.id ); @@ -3271,14 +3303,14 @@ startTrackingPointer( pointsList, gPoint ); } + pointsList.addContact(); + //$.console.log('contacts++ ', pointsList.contacts); + 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, gPoint ); } @@ -3419,6 +3451,9 @@ updateGPoint = gPoint; } + pointsList.removeContact(); + //$.console.log('contacts-- ', pointsList.contacts); + 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 @@ -3426,9 +3461,6 @@ 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 ); } @@ -3588,11 +3620,8 @@ 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 ); + // Should never get here, but due to user agent bugs (e.g. legacy touch) it sometimes happens + return; } eventInfo.shouldCapture = false; diff --git a/src/navigator.js b/src/navigator.js index 5414a53e..bb79be7e 100644 --- a/src/navigator.js +++ b/src/navigator.js @@ -167,7 +167,7 @@ $.Navigator = function( options ){ style.zIndex = 999999999; style.cursor = 'default'; }( this.displayRegion.style, this.borderWidth )); - $.setElementPointerEventsNone( this.displayRegion ); + $.setElementPointerEvents( this.displayRegion, 'none' ); $.setElementTouchActionNone( this.displayRegion ); this.displayRegionContainer = $.makeNeutralElement("div"); @@ -175,7 +175,7 @@ $.Navigator = function( options ){ this.displayRegionContainer.className = "displayregioncontainer"; this.displayRegionContainer.style.width = "100%"; this.displayRegionContainer.style.height = "100%"; - $.setElementPointerEventsNone( this.displayRegionContainer ); + $.setElementPointerEvents( this.displayRegionContainer, 'none' ); $.setElementTouchActionNone( this.displayRegionContainer ); viewer.addControl( @@ -246,8 +246,8 @@ $.Navigator = function( options ){ // 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 ); + $.setElementPointerEvents( this.canvas, 'none' ); + $.setElementPointerEvents( this.container, 'none' ); this.addHandler("reset-size", function() { if (_this.viewport) { diff --git a/src/openseadragon.js b/src/openseadragon.js index f7467ee2..5a9814b9 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -1911,14 +1911,15 @@ function OpenSeadragon( options ){ /** - * Sets the specified element's pointer-events style attribute to 'none'. + * Sets the specified element's pointer-events style attribute to the passed value. * @function * @param {Element|String} element + * @param {String} value */ - setElementPointerEventsNone: function( element ) { + setElementPointerEvents: function( element, value ) { element = $.getElement( element ); if ( typeof element.style.pointerEvents !== 'undefined' ) { - element.style.pointerEvents = 'none'; + element.style.pointerEvents = value; } }, diff --git a/src/referencestrip.js b/src/referencestrip.js index 20dd9ea9..7e27519f 100644 --- a/src/referencestrip.js +++ b/src/referencestrip.js @@ -196,7 +196,7 @@ $.ReferenceStrip = function ( options ) { element.style.styleFloat = 'left'; //IE element.style.padding = '2px'; $.setElementTouchActionNone( element ); - $.setElementPointerEventsNone( element ); + $.setElementPointerEvents( element, 'none' ); this.element.appendChild( element ); @@ -458,8 +458,8 @@ function loadPanels( strip, viewerSize, scroll ) { } ); // 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 ); + $.setElementPointerEvents( miniViewer.canvas, 'none' ); + $.setElementPointerEvents( miniViewer.container, 'none' ); // We'll use event delegation from the reference strip element instead of // handling events on every miniViewer miniViewer.innerTracker.setTracking( false ); diff --git a/src/viewer.js b/src/viewer.js index ba3c1e2a..77458d6d 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -408,7 +408,7 @@ $.Viewer = function( options ) { // Overlay container this.overlaysContainer = $.makeNeutralElement( "div" ); - $.setElementPointerEventsNone( this.overlaysContainer ); + $.setElementPointerEvents( this.overlaysContainer, 'none' ); $.setElementTouchActionNone( this.overlaysContainer ); this.canvas.appendChild( this.overlaysContainer ); From 4b4da7c4a1a2e5d16318be55489f661de680e93f Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Mon, 3 May 2021 13:43:47 -0700 Subject: [PATCH 2/7] Workaround for WebKit Pointer Event Implicit Capture Bug #1962 --- changelog.txt | 1 + src/mousetracker.js | 48 ++++++++++++++++----------------------------- 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/changelog.txt b/changelog.txt index 7ec30e54..686c20de 100644 --- a/changelog.txt +++ b/changelog.txt @@ -48,6 +48,7 @@ OPENSEADRAGON CHANGELOG * Accessibility: we now take the browser's zoom into account when choosing what detail level to draw (#1937 @ronnymikalsen) * Fixed a bug causing overlays to disappear in Sequence Mode (#1865 @gunmiosb) * Fixed a bug where the ajaxHeaders provided per-image were not being used for image requests (#1968 @maxshuty) +* MouseTracker: Added workaround for WebKit Pointer Event Implicit Capture Bug 2.4.2: diff --git a/src/mousetracker.js b/src/mousetracker.js index b203e301..cee88e99 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -1436,6 +1436,7 @@ // Release and remove all gPoints from the pointer list for ( j = 0; j < gPointsToRemove.length; j++ ) { + $.console.log('stopTrackingPointer called from clerTrackedPointers '); stopTrackingPointer( tracker, pointsList, gPointsToRemove[ j ] ); } } @@ -2144,7 +2145,7 @@ time = $.now(); - $.console.log('touchstart ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + //$.console.log('touchstart ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); if ( pointsList.getLength() > event.touches.length - touchCount ) { $.console.warn('Tracked touch contact count doesn\'t match event.touches.length'); @@ -2197,7 +2198,7 @@ time = $.now(); - $.console.log('touchend ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + //$.console.log('touchend ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); var eventInfo = { originalEvent: event, @@ -2336,7 +2337,7 @@ * @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, @@ -2365,7 +2366,7 @@ * @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, @@ -2545,8 +2546,6 @@ currentTime: $.now() }; - var ourElement = event.target === tracker.element; - // Most browsers implicitly capture touch pointer events // Note no IE versions have element.hasPointerCapture() so no implicit // pointer capture possible @@ -2558,17 +2557,7 @@ gPoint.type === 'touch' && $.Browser.vendor !== $.BROWSERS.IE; - //$.console.log('pointerdown ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (ourElement ? 'tracker.element' : '')); - - if (implicitlyCaptured) { - $.console.log('pointerdown implicitlyCaptured ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (ourElement ? 'tracker.element' : '')); - } else { - $.console.log('pointerdown not implicitlyCaptured ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (ourElement ? 'tracker.element' : '')); - } - - // if ( implicitlyCaptured && ourElement ) { - // updatePointerCaptured( tracker, gPoint, true ); - // } + //$.console.log('pointerdown ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); var eventInfo = { originalEvent: event, @@ -2588,18 +2577,11 @@ } if ( eventInfo.shouldCapture ) { if ( implicitlyCaptured ) { - $.console.log('pointerdown calling updatePointerCaptured() ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); updatePointerCaptured( tracker, gPoint, true ); } else { - $.console.log('pointerdown calling capturePointer() ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); capturePointer( tracker, gPoint ); } - }// else if ( implicitlyCaptured && ourElement ) { - // //$.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 - // $.console.log('pointerdown calling updatePointerCaptured(false) ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); - // updatePointerCaptured( tracker, gPoint, false ); - // } + } } @@ -2644,8 +2626,7 @@ function handlePointerUp( tracker, event ) { var gPoint; - //$.console.log('onPointerUp ' + (tracker.userData ? tracker.userData.toString() : '')); - $.console.log('pointerup ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + //$.console.log('pointerup ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); gPoint = { id: getPointerId( event ), @@ -2677,10 +2658,8 @@ // explicitly if ( eventInfo.shouldReleaseCapture ) { if ( event.target === tracker.element ) { - $.console.log('pointerup calling releasePointer() ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); releasePointer( tracker, gPoint ); } else { - $.console.log('pointerup calling updatePointerCaptured() ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); updatePointerCaptured( tracker, gPoint, false ); } } @@ -2760,8 +2739,7 @@ * @inner */ function onPointerCancel( tracker, event ) { - //$.console.log('pointercancel ' + (tracker.userData ? tracker.userData.toString() : '')); - $.console.log('pointercancel ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); + //$.console.log('pointercancel ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); var gPoint = { id: event.pointerId, @@ -2832,7 +2810,15 @@ if ( trackedGPoint ) { if ( trackedGPoint.captured ) { + $.console.warn('stopTrackingPointer() called on captured pointer'); releasePointer( tracker, trackedGPoint ); + } + + // If child element relinquishes capture to a parent we may get here + // from a pointerleave event while a pointerup event will never be received. + // In that case, we'll clean up the contact count + if ( (pointsList.type === 'mouse' || pointsList.type === 'pen') && + pointsList.contacts > 0 ) { pointsList.removeContact(); } From 0f1432ce826e47e8f4ea1ab04770f81fa85f0d76 Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Mon, 3 May 2021 13:48:33 -0700 Subject: [PATCH 3/7] changelog update --- changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 686c20de..a1faa382 100644 --- a/changelog.txt +++ b/changelog.txt @@ -48,7 +48,7 @@ OPENSEADRAGON CHANGELOG * Accessibility: we now take the browser's zoom into account when choosing what detail level to draw (#1937 @ronnymikalsen) * Fixed a bug causing overlays to disappear in Sequence Mode (#1865 @gunmiosb) * Fixed a bug where the ajaxHeaders provided per-image were not being used for image requests (#1968 @maxshuty) -* MouseTracker: Added workaround for WebKit Pointer Event Implicit Capture Bug +* MouseTracker: Added workaround for WebKit Pointer Event Implicit Capture Bug (#1972 @msalsbery) 2.4.2: From a53550ea0e198e594339404810a6043bc6ca723c Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Mon, 3 May 2021 13:57:14 -0700 Subject: [PATCH 4/7] Removed test for move-leave (fly-over, no enter event) --- changelog.txt | 1 + test/modules/events.js | 27 --------------------------- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/changelog.txt b/changelog.txt index a1faa382..fb81ebef 100644 --- a/changelog.txt +++ b/changelog.txt @@ -49,6 +49,7 @@ OPENSEADRAGON CHANGELOG * Fixed a bug causing overlays to disappear in Sequence Mode (#1865 @gunmiosb) * Fixed a bug where the ajaxHeaders provided per-image were not being used for image requests (#1968 @maxshuty) * MouseTracker: Added workaround for WebKit Pointer Event Implicit Capture Bug (#1972 @msalsbery) +* Removed test for move-leave (fly-over, no enter event)...not a valid, handleable event state, no longer supported (#1972 @msalsbery) 2.4.2: diff --git a/test/modules/events.js b/test/modules/events.js index 94e11da1..7a2b00ca 100644 --- a/test/modules/events.js +++ b/test/modules/events.js @@ -403,33 +403,6 @@ //quickClick: false }); - // move-leave (fly-over, no enter event) - resetForAssessment(); - simulateMove(1, 1, 10); - simulateMove(-1, -1, 10); - simulateLeave(-1, -1); - assessGestureExpectations({ - description: 'move-leave (fly-over, no enter event): ', - enterCount: 0, - leaveCount: 1, - pressCount: 0, - releaseCount: 0, - rightPressCount: 0, - rightReleaseCount: 0, - middlePressCount: 0, - middleReleaseCount: 0, - moveCount: 20, - clickCount: 0, - dblClickCount: 0, - dragCount: 0, - dragEndCount: 0, - //insideElementPressed: false, - //insideElementReleased: false, - contacts: 0, - trackedPointers: 0 - //quickClick: false - }); - // enter-press-release-press-release-leave (primary/left double click) resetForAssessment(); simulateEnter(0, 0); From 7fbff2cc27096975db9a5475b12d812833c712d7 Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Mon, 3 May 2021 14:02:29 -0700 Subject: [PATCH 5/7] cleanup debug code --- src/mousetracker.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mousetracker.js b/src/mousetracker.js index cee88e99..60127c97 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -1436,7 +1436,6 @@ // Release and remove all gPoints from the pointer list for ( j = 0; j < gPointsToRemove.length; j++ ) { - $.console.log('stopTrackingPointer called from clerTrackedPointers '); stopTrackingPointer( tracker, pointsList, gPointsToRemove[ j ] ); } } From 72fc528b3c47602a83d411dd0afc9b95886c0a98 Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Tue, 4 May 2021 16:55:37 -0700 Subject: [PATCH 6/7] removeContact only on existing tracked pointer --- src/mousetracker.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/mousetracker.js b/src/mousetracker.js index 60127c97..75ec7be0 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -3281,11 +3281,13 @@ gPoint = updateGPoint; } else { - // Initialize for tracking and add to the tracking list (no pointerover or pointermove event occurred before this) + // Initialize for tracking and add to the tracking list (no pointerenter event occurred before this) + $.console.warn('pointerdown event on untracked pointer'); gPoint.captured = false; // Handled by updatePointerCaptured() gPoint.insideElementPressed = true; gPoint.insideElement = true; startTrackingPointer( pointsList, gPoint ); + return; } pointsList.addContact(); @@ -3410,6 +3412,9 @@ updateGPoint = pointsList.getById( gPoint.id ); if ( updateGPoint ) { + pointsList.removeContact(); + //$.console.log('contacts-- ', pointsList.contacts); + // Update the pointer, stop tracking it if not still in this element if ( updateGPoint.captured ) { //updateGPoint.captured = false; // Handled by updatePointerCaptured() @@ -3436,9 +3441,6 @@ updateGPoint = gPoint; } - pointsList.removeContact(); - //$.console.log('contacts-- ', pointsList.contacts); - 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 From 40623f1e99769f56443f83c348cacf75fb19efb7 Mon Sep 17 00:00:00 2001 From: Mark Salsbery <> Date: Wed, 5 May 2021 12:43:53 -0700 Subject: [PATCH 7/7] Re-added the OpenSeadragon.setElementPointerEventsNone() function --- changelog.txt | 1 + src/button.js | 8 ++++---- src/drawer.js | 2 +- src/navigator.js | 8 ++++---- src/openseadragon.js | 10 ++++++++++ src/referencestrip.js | 6 +++--- src/viewer.js | 2 +- 7 files changed, 24 insertions(+), 13 deletions(-) diff --git a/changelog.txt b/changelog.txt index fb81ebef..e3c57618 100644 --- a/changelog.txt +++ b/changelog.txt @@ -50,6 +50,7 @@ OPENSEADRAGON CHANGELOG * Fixed a bug where the ajaxHeaders provided per-image were not being used for image requests (#1968 @maxshuty) * MouseTracker: Added workaround for WebKit Pointer Event Implicit Capture Bug (#1972 @msalsbery) * Removed test for move-leave (fly-over, no enter event)...not a valid, handleable event state, no longer supported (#1972 @msalsbery) +* Added OpenSeadragon.setElementPointerEvents() for setting pointer-events to other values besides 'none' on DOM elements (#1972 @msalsbery) 2.4.2: diff --git a/src/button.js b/src/button.js index ecf3101f..7abee6a7 100644 --- a/src/button.js +++ b/src/button.js @@ -140,10 +140,10 @@ $.Button = function( options ) { // Allow pointer events to pass through the img elements so implicit // pointer capture works on touch devices - $.setElementPointerEvents( this.imgRest, 'none' ); - $.setElementPointerEvents( this.imgGroup, 'none' ); - $.setElementPointerEvents( this.imgHover, 'none' ); - $.setElementPointerEvents( this.imgDown, 'none' ); + $.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 a022850a..fca4b956 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -128,7 +128,7 @@ $.Drawer = function( options ) { $.setElementOpacity( this.canvas, this.opacity, true ); // Allow pointer events to pass through the canvas element so implicit // pointer capture works on touch devices - $.setElementPointerEvents( this.canvas, 'none' ); + $.setElementPointerEventsNone( this.canvas ); $.setElementTouchActionNone( this.canvas ); // explicit left-align diff --git a/src/navigator.js b/src/navigator.js index bb79be7e..5414a53e 100644 --- a/src/navigator.js +++ b/src/navigator.js @@ -167,7 +167,7 @@ $.Navigator = function( options ){ style.zIndex = 999999999; style.cursor = 'default'; }( this.displayRegion.style, this.borderWidth )); - $.setElementPointerEvents( this.displayRegion, 'none' ); + $.setElementPointerEventsNone( this.displayRegion ); $.setElementTouchActionNone( this.displayRegion ); this.displayRegionContainer = $.makeNeutralElement("div"); @@ -175,7 +175,7 @@ $.Navigator = function( options ){ this.displayRegionContainer.className = "displayregioncontainer"; this.displayRegionContainer.style.width = "100%"; this.displayRegionContainer.style.height = "100%"; - $.setElementPointerEvents( this.displayRegionContainer, 'none' ); + $.setElementPointerEventsNone( this.displayRegionContainer ); $.setElementTouchActionNone( this.displayRegionContainer ); viewer.addControl( @@ -246,8 +246,8 @@ $.Navigator = function( options ){ // 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) - $.setElementPointerEvents( this.canvas, 'none' ); - $.setElementPointerEvents( this.container, 'none' ); + $.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 5a9814b9..01cfc864 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -1924,6 +1924,16 @@ function OpenSeadragon( options ){ }, + /** + * Sets the specified element's pointer-events style attribute to 'none'. + * @function + * @param {Element|String} element + */ + setElementPointerEventsNone: function( element ) { + $.setElementPointerEvents( element, 'none' ); + }, + + /** * Add the specified CSS class to the element if not present. * @function diff --git a/src/referencestrip.js b/src/referencestrip.js index 7e27519f..20dd9ea9 100644 --- a/src/referencestrip.js +++ b/src/referencestrip.js @@ -196,7 +196,7 @@ $.ReferenceStrip = function ( options ) { element.style.styleFloat = 'left'; //IE element.style.padding = '2px'; $.setElementTouchActionNone( element ); - $.setElementPointerEvents( element, 'none' ); + $.setElementPointerEventsNone( element ); this.element.appendChild( element ); @@ -458,8 +458,8 @@ function loadPanels( strip, viewerSize, scroll ) { } ); // Allow pointer events to pass through miniViewer's canvas/container // elements so implicit pointer capture works on touch devices - $.setElementPointerEvents( miniViewer.canvas, 'none' ); - $.setElementPointerEvents( miniViewer.container, 'none' ); + $.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 ); diff --git a/src/viewer.js b/src/viewer.js index 77458d6d..ba3c1e2a 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -408,7 +408,7 @@ $.Viewer = function( options ) { // Overlay container this.overlaysContainer = $.makeNeutralElement( "div" ); - $.setElementPointerEvents( this.overlaysContainer, 'none' ); + $.setElementPointerEventsNone( this.overlaysContainer ); $.setElementTouchActionNone( this.overlaysContainer ); this.canvas.appendChild( this.overlaysContainer );