Skip to content

[BUG] Delegated mouseover listener crashes on synthetic events targeting document (dataset is undefined) #1277

@aramarakelyan88

Description

@aramarakelyan88

Bug description

In v6's event-delegation model, the single document-level mouseover listener throws TypeError: Cannot read properties of undefined (reading 'tooltipId') when an event's target is the document itself (not an Element).

This happens with synthetic / untrusted mouseover events, which are commonly dispatched directly on document by browser extensions, password managers, and accessibility/automation tooling. Real user hovers always target an Element, so this stays hidden in normal use but surfaces in production.

The crash is in resolveDataTooltipAnchor (src/utils/resolve-data-tooltip-anchor.ts):

function resolveDataTooltipAnchor(targetElement, tooltipId) {
  let currentElement = targetElement;          // = document
  while (currentElement) {
    if (currentElement.dataset.tooltipId === tooltipId) {  // document.dataset is undefined -> throws
      return currentElement;
    }
    currentElement = currentElement.parentElement;
  }
  return null;
}

document.dataset is undefined (only HTMLElement / SVGElement expose dataset), so reading .tooltipId from it throws.

The guard in resolveAnchorElementRef (src/components/Tooltip/use-tooltip-events.tsx) only checks targetElement?.isConnected, but document.isConnected === true, so document slips through into the data-tooltip-id branch. Note the anchorSelector branch is wrapped in a try/catch, but the dataTooltipId branch is not, so the common data-tooltip-id usage is unprotected.

Package version

6.0.5 (also confirmed in 6.0.0)

To reproduce

  1. Render a tooltip using the default attribute anchor:
    <a data-tooltip-id="my-tip">hover me</a>
    <Tooltip id="my-tip" content="hi" />
  2. Dispatch a synthetic mouseover on the document (this is what extensions/tooling do):
    document.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));
  3. The delegated listener runs resolveDataTooltipAnchor(document, 'my-tip'), which does document.dataset.tooltipId and throws.

Expected behavior

The delegated mouseover/mouseout/etc. listeners should ignore non-Element targets (such as document) and not throw.

Actual behavior

Uncaught TypeError: Cannot read properties of undefined (reading 'tooltipId')
    at resolveDataTooltipAnchor (resolve-data-tooltip-anchor.ts)
    at resolveAnchorElementRef.current (use-tooltip-events.tsx)
    at handler (use-tooltip-events.tsx)
    at dispatch (event-delegation.ts)

The error escapes to window.onerror.

Suggested fix

Bail on non-Element targets at the guard, and/or harden the walk:

// use-tooltip-events.tsx - resolveAnchorElementRef.current
if (!(targetElement instanceof Element) || !targetElement.isConnected) {
  return null;
}

// and/or resolve-data-tooltip-anchor.ts
while (currentElement) {
  if (currentElement instanceof HTMLElement && currentElement.dataset.tooltipId === tooltipId) {
    return currentElement;
  }
  currentElement = currentElement.parentElement;
}

Environment

  • OS: Windows 10+
  • Browser: Chrome 139
  • React version: 19
  • Framework: Next.js
  • Rendering mode: client

Additional context

Caught via production error monitoring (Sentry): 26 occurrences across 26 unique users over 3 days. The event payload confirms isTrusted: false and target: [object HTMLDocument] on the mouseover event, consistent with a synthetic event dispatched on document rather than a real user hover.

Metadata

Metadata

Assignees

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions