Bug Report
Discovered via downstream usage in @iwer/devui, which calls forwardHtmlEvents() from @pmndrs/pointer-events. The IWER maintainer asked me to file upstream here:
Environment
@pmndrs/pointer-events version: ^6.6.17 (resolved to 6.6.29 at time of report) — loaded transitively via @iwer/devui@1.1.2 from the unpkg CDN
@iwer/devui version: 1.1.2
iwer version: 2.1.1
- Browser: Chromium-based, tested on Meta Quest browser
- Also reproducible in desktop Chrome when WebXR is not natively supported and IWER loads as fallback
Description
When forwardHtmlEvents() is active and the user interacts with the page (click/drag), the library calls element.releasePointerCapture(pointerId) on a pointer that has sometimes already been released by the browser (e.g. on pointer up, focus loss, or other race conditions). The browser throws an uncaught NotFoundError, which bubbles up and crashes the consuming app.
Error
Failed to execute 'releasePointerCapture' on 'Element': No active pointer with the given id is found.
NotFoundError: Failed to execute 'releasePointerCapture' on 'Element': No active pointer with the given id is found.
Root Cause
In packages/pointer-events/src/forward.ts (lines 61–65), releasePointerCapture() is called without a try/catch:
|
(pointerId) => { |
|
if (fromElement.hasPointerCapture(pointerId)) { |
|
fromElement.releasePointerCapture(pointerId) |
|
} |
|
}, |
If the pointer is already gone by the time this fires, the browser throws NotFoundError. Since the library doesn't catch it, the error propagates to the consumer and crashes the app.
Steps to Reproduce
- Use
forwardHtmlEvents() from @pmndrs/pointer-events in a web app (in my case, indirectly via @iwer/devui running as a WebXR fallback)
- Open the page in a Chromium-based browser (Meta Quest browser or desktop Chrome without native WebXR)
- Interact with the UI — click, drag, or release outside the captured element
NotFoundError appears repeatedly in the console and crashes the runtime
Expected Behavior
releasePointerCapture() failing because the pointer is already gone is a benign no-op and should be swallowed internally. It shouldn't crash the consuming app.
Workaround (consumer-side)
Monkey-patch Element.prototype.releasePointerCapture before loading anything that uses forwardHtmlEvents():
const _orig = Element.prototype.releasePointerCapture;
Element.prototype.releasePointerCapture = function(pointerId) {
try {
_orig.call(this, pointerId);
} catch (e) {
if (e?.name !== 'NotFoundError') throw e;
}
};
This works but is global and hacky — the proper fix belongs in forward.ts.
Fix Suggestion
Wrap the releasePointerCapture() call in packages/pointer-events/src/forward.ts in a try/catch and ignore NotFoundError, since the pointer being already released is an expected race condition:
try {
element.releasePointerCapture(pointerId);
} catch (e) {
if ((e as Error)?.name !== 'NotFoundError') throw e;
}
Bug Report
Discovered via downstream usage in
@iwer/devui, which callsforwardHtmlEvents()from@pmndrs/pointer-events. The IWER maintainer asked me to file upstream here:forwardHtmlEvents()call site: https://github.com/meta-quest/immersive-web-emulation-runtime/blob/b2b8a616a942679dec754edf2d925acba32e73ed/devui/src/scene.ts#L164-L168Environment
@pmndrs/pointer-eventsversion:^6.6.17(resolved to6.6.29at time of report) — loaded transitively via@iwer/devui@1.1.2from the unpkg CDN@iwer/devuiversion: 1.1.2iwerversion: 2.1.1Description
When
forwardHtmlEvents()is active and the user interacts with the page (click/drag), the library callselement.releasePointerCapture(pointerId)on a pointer that has sometimes already been released by the browser (e.g. on pointer up, focus loss, or other race conditions). The browser throws an uncaughtNotFoundError, which bubbles up and crashes the consuming app.Error
Failed to execute 'releasePointerCapture' on 'Element': No active pointer with the given id is found.
NotFoundError: Failed to execute 'releasePointerCapture' on 'Element': No active pointer with the given id is found.
Root Cause
In
packages/pointer-events/src/forward.ts(lines 61–65),releasePointerCapture()is called without atry/catch:xr/packages/pointer-events/src/forward.ts
Lines 61 to 65 in 1e881cd
If the pointer is already gone by the time this fires, the browser throws
NotFoundError. Since the library doesn't catch it, the error propagates to the consumer and crashes the app.Steps to Reproduce
forwardHtmlEvents()from@pmndrs/pointer-eventsin a web app (in my case, indirectly via@iwer/devuirunning as a WebXR fallback)NotFoundErrorappears repeatedly in the console and crashes the runtimeExpected Behavior
releasePointerCapture()failing because the pointer is already gone is a benign no-op and should be swallowed internally. It shouldn't crash the consuming app.Workaround (consumer-side)
Monkey-patch
Element.prototype.releasePointerCapturebefore loading anything that usesforwardHtmlEvents():This works but is global and hacky — the proper fix belongs in
forward.ts.Fix Suggestion
Wrap the
releasePointerCapture()call inpackages/pointer-events/src/forward.tsin atry/catchand ignoreNotFoundError, since the pointer being already released is an expected race condition: