Skip to content

forwardHtmlEvents: uncaught NotFoundError from releasePointerCapture when pointer is already released #496

Description

@freedanjeremiah

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

  1. Use forwardHtmlEvents() from @pmndrs/pointer-events in a web app (in my case, indirectly via @iwer/devui running as a WebXR fallback)
  2. Open the page in a Chromium-based browser (Meta Quest browser or desktop Chrome without native WebXR)
  3. Interact with the UI — click, drag, or release outside the captured element
  4. 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;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions