'Detect iOS pointer capture bug for polyfilling
As reported in the WebKit Bugilla, iOS 13-15 seems to have a bug when trying to use pointer capture on touch and stylus inputs. The problem is that calling setPointerCapture doesn't actually redirect future events when called on an element that was not the event's original target. However, calling hasPointerCapture will still work and return true as expected. Is there any way I would be able to feature test for this problem in order to implement a polyfill for it?
I created a demo for this problem on GitHub if it helps. The green and yellow divs will correctly track the pointer movement if the pointer starts on the respective element, but the orange div should act the same and the red div should never move. Both of the latter will still track the pointer as long as the pointer remains over the divs.
The code that I currently have for polyfilling the bug is as below. It works, but its detection procedure is a UA string test and therefore won't work in the other iOS browsers even though they share the same JavaScript engine.
// Need to figure out some way to test if this is needed.
if (navigator.userAgent.match(/Version\/1[345]\.\d+(?:\.\d+)? Safari/)) {
const {
setPointerCapture: set,
hasPointerCapture: has,
releasePointerCapture: release
} = Element.prototype
let targets = {},
captures = {}
Element.prototype.setPointerCapture = function setPointerCapture(pointerId) {
if (pointerId in captures) {
if (document.contains(this)) {
captures[pointerId] = this
return set.call(targets[pointerId], pointerId)
} else {
throw new TypeError("Element not in valid location")
}
} else {
return set.call(this, pointerId)
}
}
Element.prototype.hasPointerCapture = function hasPointerCapture(pointerId) {
if (pointerId in captures) {
return captures[pointerId] == this
} else {
return has.call(this, pointerId)
}
}
Element.prototype.releasePointerCapture = function releasePointerCapture(pointerId) {
if (pointerId in captures) {
if (this.hasPointerCapture(pointerId)) {
captures[pointerId] = null
return release.call(targets[pointerId], pointerId)
}
} else {
return release.call(this, pointerId)
}
}
let registerPointer = function registerPointer(event) {
if (event.pointerType == "touch" || event.pointerType == "pen") {
targets[event.pointerId] = event.target
captures[event.pointerId] = null
}
},
redirectPointer = function redirectPointer(event) {
if (captures[event.pointerId] != null && captures[event.pointerId] != event.target) {
// Stop the original event
event.preventDefault()
event.stopPropagation()
// Redispatch a new, cloned event
captures[event.pointerId].dispatchEvent(new PointerEvent(event.type, event))
}
},
redirectAndUnregisterPointer = function redirectAndUnregisterPointer(event) {
redirectPointer(event)
delete targets[event.pointerId]
delete captures[event.pointerId]
}
addEventListener("pointerdown", registerPointer, {capture: true, passive: true})
addEventListener("pointermove", redirectPointer, {capture: true, passive: false})
addEventListener("pointerup", redirectAndUnregisterPointer, {capture: true, passive: false})
addEventListener("pointercancel", redirectAndUnregisterPointer, {capture: true, passive: false})
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
