Merge pull request #1972 from openseadragon/ms-webkit-bug

Workaround for WebKit Pointer Event Implicit Capture Bug
This commit is contained in:
Ian Gilman 2021-05-06 11:22:42 -07:00 committed by GitHub
commit f775526d81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 84 additions and 81 deletions

View File

@ -48,6 +48,9 @@ OPENSEADRAGON CHANGELOG
* Accessibility: we now take the browser's zoom into account when choosing what detail level to draw (#1937 @ronnymikalsen) * 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 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) * 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: 2.4.2:

View File

@ -281,6 +281,10 @@
this.pinchHandler ); this.pinchHandler );
this.hasScrollHandler = !!this.scrollHandler; this.hasScrollHandler = !!this.scrollHandler;
if ( $.MouseTracker.havePointerEvents ) {
$.setElementPointerEvents( this.element, 'auto' );
}
if (this.exitHandler) { if (this.exitHandler) {
$.console.error("MouseTracker.exitHandler is deprecated. Use MouseTracker.leaveHandler instead."); $.console.error("MouseTracker.exitHandler is deprecated. Use MouseTracker.leaveHandler instead.");
} }
@ -1539,13 +1543,14 @@
if ( $.MouseTracker.havePointerCapture ) { if ( $.MouseTracker.havePointerCapture ) {
if ( $.MouseTracker.havePointerEvents ) { if ( $.MouseTracker.havePointerEvents ) {
// Can throw InvalidPointerId // Can throw NotFoundError (InvalidPointerId Firefox < 82)
// (should never happen so we'll log a warning) // (should never happen so we'll log a warning)
try { try {
tracker.element.setPointerCapture( gPoint.id ); tracker.element.setPointerCapture( gPoint.id );
//$.console.log('element.setPointerCapture() called'); //$.console.log('element.setPointerCapture() called');
} catch ( e ) { } catch ( e ) {
$.console.warn('setPointerCapture() called on invalid pointer ID'); $.console.warn('setPointerCapture() called on invalid pointer ID');
return;
} }
} else { } else {
tracker.element.setCapture( true ); tracker.element.setCapture( true );
@ -1601,7 +1606,7 @@
if ( !cachedGPoint || !cachedGPoint.captured ) { if ( !cachedGPoint || !cachedGPoint.captured ) {
return; 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) // (should never happen, but it does on Firefox 79 touch so we won't log a warning)
try { try {
tracker.element.releasePointerCapture( gPoint.id ); tracker.element.releasePointerCapture( gPoint.id );
@ -1731,7 +1736,7 @@
* @inner * @inner
*/ */
function onClick( tracker, event ) { function onClick( tracker, event ) {
//$.console.log('onClick ' + (tracker.userData ? tracker.userData.toString() : '')); //$.console.log('click ' + (tracker.userData ? tracker.userData.toString() : ''));
var eventInfo = { var eventInfo = {
originalEvent: event, originalEvent: event,
@ -1755,7 +1760,7 @@
* @inner * @inner
*/ */
function onDblClick( tracker, event ) { function onDblClick( tracker, event ) {
//$.console.log('onDblClick ' + (tracker.userData ? tracker.userData.toString() : '')); //$.console.log('dblclick ' + (tracker.userData ? tracker.userData.toString() : ''));
var eventInfo = { var eventInfo = {
originalEvent: event, originalEvent: event,
@ -1904,7 +1909,7 @@
* @inner * @inner
*/ */
function onFocus( tracker, event ) { 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 // focus doesn't bubble and is not cancelable, but we call
// preProcessEvent() so it's dispatched to preProcessEventHandler // preProcessEvent() so it's dispatched to preProcessEventHandler
@ -1934,7 +1939,7 @@
* @inner * @inner
*/ */
function onBlur( tracker, event ) { 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 // blur doesn't bubble and is not cancelable, but we call
// preProcessEvent() so it's dispatched to preProcessEventHandler // preProcessEvent() so it's dispatched to preProcessEventHandler
@ -2116,7 +2121,9 @@
}; };
preProcessEvent( tracker, eventInfo ); preProcessEvent( tracker, eventInfo );
if ( event.target === tracker.element ) {
updatePointerCaptured( tracker, gPoint, false ); updatePointerCaptured( tracker, gPoint, false );
}
if ( eventInfo.stopPropagation ) { if ( eventInfo.stopPropagation ) {
$.stopEvent( event ); $.stopEvent( event );
@ -2137,7 +2144,7 @@
time = $.now(); 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 ) { if ( pointsList.getLength() > event.touches.length - touchCount ) {
$.console.warn('Tracked touch contact count doesn\'t match event.touches.length'); $.console.warn('Tracked touch contact count doesn\'t match event.touches.length');
@ -2164,9 +2171,9 @@
// simulate touchenter on our tracked element // simulate touchenter on our tracked element
updatePointerEnter( tracker, eventInfo, gPoint ); updatePointerEnter( tracker, eventInfo, gPoint );
updatePointerCaptured( tracker, gPoint, true );
updatePointerDown( tracker, eventInfo, gPoint, 0 ); updatePointerDown( tracker, eventInfo, gPoint, 0 );
updatePointerCaptured( tracker, gPoint, true );
} }
if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) {
@ -2190,7 +2197,7 @@
time = $.now(); 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 = { var eventInfo = {
originalEvent: event, originalEvent: event,
@ -2530,22 +2537,6 @@
* @inner * @inner
*/ */
function onPointerDown( tracker, event ) { 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 = { var gPoint = {
id: getPointerId( event ), id: getPointerId( event ),
type: getPointerType( event ), type: getPointerType( event ),
@ -2554,6 +2545,19 @@
currentTime: $.now() currentTime: $.now()
}; };
// 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() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : ''));
var eventInfo = { var eventInfo = {
originalEvent: event, originalEvent: event,
eventType: 'pointerdown', eventType: 'pointerdown',
@ -2570,12 +2574,12 @@
if ( eventInfo.stopPropagation ) { if ( eventInfo.stopPropagation ) {
$.stopEvent( event ); $.stopEvent( event );
} }
if ( eventInfo.shouldCapture && !implicitlyCaptured ) { if ( eventInfo.shouldCapture ) {
//$.console.log('pointerdown calling capturePointer() ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); if ( implicitlyCaptured ) {
updatePointerCaptured( tracker, gPoint, true );
} else {
capturePointer( tracker, gPoint ); 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
} }
} }
@ -2621,7 +2625,7 @@
function handlePointerUp( tracker, event ) { function handlePointerUp( tracker, event ) {
var gPoint; 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 = { gPoint = {
id: getPointerId( event ), id: getPointerId( event ),
@ -2651,9 +2655,12 @@
// Per spec, pointerup events are supposed to release capture. Not all browser // 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 // versions have adhered to the spec, and there's no harm in releasing
// explicitly // explicitly
if ( eventInfo.shouldReleaseCapture && event.target === tracker.element ) { if ( eventInfo.shouldReleaseCapture ) {
//$.stopEvent( event ); if ( event.target === tracker.element ) {
releasePointer( tracker, gPoint ); releasePointer( tracker, gPoint );
} else {
updatePointerCaptured( tracker, gPoint, false );
}
} }
} }
@ -2731,7 +2738,7 @@
* @inner * @inner
*/ */
function onPointerCancel( tracker, event ) { 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 = { var gPoint = {
id: event.pointerId, id: event.pointerId,
@ -2770,6 +2777,7 @@
* @returns {Number} Number of gesture points in pointsList. * @returns {Number} Number of gesture points in pointsList.
*/ */
function startTrackingPointer( pointsList, gPoint ) { function startTrackingPointer( pointsList, gPoint ) {
//$.console.log('startTrackingPointer *** ' + pointsList.type + ' ' + gPoint.id.toString());
gPoint.speed = 0; gPoint.speed = 0;
gPoint.direction = 0; gPoint.direction = 0;
gPoint.contactPos = gPoint.currentPos; gPoint.contactPos = gPoint.currentPos;
@ -2794,13 +2802,22 @@
* @returns {Number} Number of gesture points in pointsList. * @returns {Number} Number of gesture points in pointsList.
*/ */
function stopTrackingPointer( tracker, pointsList, gPoint ) { function stopTrackingPointer( tracker, pointsList, gPoint ) {
//$.console.log('stopTrackingPointer *** ' + pointsList.type + ' ' + gPoint.id.toString());
var listLength; var listLength;
var trackedGPoint = pointsList.getById( gPoint.id ); var trackedGPoint = pointsList.getById( gPoint.id );
if ( trackedGPoint ) { if ( trackedGPoint ) {
if ( trackedGPoint.captured ) { if ( trackedGPoint.captured ) {
$.console.warn('stopTrackingPointer() called on captured pointer');
releasePointer( tracker, trackedGPoint ); 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(); pointsList.removeContact();
} }
@ -3264,21 +3281,23 @@
gPoint = updateGPoint; gPoint = updateGPoint;
} else { } 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.captured = false; // Handled by updatePointerCaptured()
gPoint.insideElementPressed = true; gPoint.insideElementPressed = true;
gPoint.insideElement = true; gPoint.insideElement = true;
startTrackingPointer( pointsList, gPoint ); startTrackingPointer( pointsList, gPoint );
return;
} }
pointsList.addContact();
//$.console.log('contacts++ ', pointsList.contacts);
if ( !eventInfo.preventGesture && !eventInfo.defaultPrevented ) { if ( !eventInfo.preventGesture && !eventInfo.defaultPrevented ) {
eventInfo.shouldCapture = true; eventInfo.shouldCapture = true;
eventInfo.shouldReleaseCapture = false; eventInfo.shouldReleaseCapture = false;
eventInfo.preventDefault = true; eventInfo.preventDefault = true;
pointsList.addContact();
//$.console.log('contacts++ ', pointsList.contacts);
if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) { if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
$.MouseTracker.gesturePointVelocityTracker.addPoint( tracker, gPoint ); $.MouseTracker.gesturePointVelocityTracker.addPoint( tracker, gPoint );
} }
@ -3393,6 +3412,9 @@
updateGPoint = pointsList.getById( gPoint.id ); updateGPoint = pointsList.getById( gPoint.id );
if ( updateGPoint ) { if ( updateGPoint ) {
pointsList.removeContact();
//$.console.log('contacts-- ', pointsList.contacts);
// Update the pointer, stop tracking it if not still in this element // Update the pointer, stop tracking it if not still in this element
if ( updateGPoint.captured ) { if ( updateGPoint.captured ) {
//updateGPoint.captured = false; // Handled by updatePointerCaptured() //updateGPoint.captured = false; // Handled by updatePointerCaptured()
@ -3426,9 +3448,6 @@
eventInfo.shouldReleaseCapture = true; eventInfo.shouldReleaseCapture = true;
eventInfo.preventDefault = true; eventInfo.preventDefault = true;
pointsList.removeContact();
//$.console.log('contacts-- ', pointsList.contacts);
if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) { if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
$.MouseTracker.gesturePointVelocityTracker.removePoint( tracker, updateGPoint ); $.MouseTracker.gesturePointVelocityTracker.removePoint( tracker, updateGPoint );
} }
@ -3588,11 +3607,8 @@
updateGPoint.currentPos = gPoint.currentPos; updateGPoint.currentPos = gPoint.currentPos;
updateGPoint.currentTime = gPoint.currentTime; updateGPoint.currentTime = gPoint.currentTime;
} else { } else {
// Initialize for tracking and add to the tracking list (no pointerover or pointerdown event occurred before this) // Should never get here, but due to user agent bugs (e.g. legacy touch) it sometimes happens
gPoint.captured = false; // Handled by updatePointerCaptured() return;
gPoint.insideElementPressed = false;
gPoint.insideElement = true;
startTrackingPointer( pointsList, gPoint );
} }
eventInfo.shouldCapture = false; eventInfo.shouldCapture = false;

View File

@ -1910,16 +1910,27 @@ function OpenSeadragon( options ){
}, },
/**
* Sets the specified element's pointer-events style attribute to the passed value.
* @function
* @param {Element|String} element
* @param {String} value
*/
setElementPointerEvents: function( element, value ) {
element = $.getElement( element );
if ( typeof element.style.pointerEvents !== 'undefined' ) {
element.style.pointerEvents = value;
}
},
/** /**
* Sets the specified element's pointer-events style attribute to 'none'. * Sets the specified element's pointer-events style attribute to 'none'.
* @function * @function
* @param {Element|String} element * @param {Element|String} element
*/ */
setElementPointerEventsNone: function( element ) { setElementPointerEventsNone: function( element ) {
element = $.getElement( element ); $.setElementPointerEvents( element, 'none' );
if ( typeof element.style.pointerEvents !== 'undefined' ) {
element.style.pointerEvents = 'none';
}
}, },

View File

@ -403,33 +403,6 @@
//quickClick: false //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) // enter-press-release-press-release-leave (primary/left double click)
resetForAssessment(); resetForAssessment();
simulateEnter(0, 0); simulateEnter(0, 0);