Skip to content

fix: [#2064] Defensively initialize EventTarget listener maps#2075

Open
TrevorBurnham wants to merge 1 commit intocapricorn86:masterfrom
TrevorBurnham:fix/2064-eventtarget-uninitialized-listener-maps
Open

fix: [#2064] Defensively initialize EventTarget listener maps#2075
TrevorBurnham wants to merge 1 commit intocapricorn86:masterfrom
TrevorBurnham:fix/2064-eventtarget-uninitialized-listener-maps

Conversation

@TrevorBurnham
Copy link
Copy Markdown
Contributor

Fixes #2064

Problem

EventTarget.addEventListener() throws a TypeError when PropertySymbol.listeners or PropertySymbol.listenerOptions is undefined:

TypeError: Cannot read properties of undefined (reading 'capturing')
    at addEventListener (…/happy-dom/lib/event/EventTarget.js:36:55)

This happens when third-party libraries (e.g. PixiJS) attach event listeners very early in their initialization process, before the class field initializers on EventTarget have run. Since the listeners and listenerOptions maps are initialized as class fields, they only exist after the constructor completes. Objects that inherit from EventTarget without calling super(), or that interact with the prototype before construction finishes, hit undefined.

Fix

Added an ensureListenerMaps() helper function that lazily initializes both maps if they are undefined. It is called at the entry point of:

  • addEventListener()
  • removeEventListener()
  • #callDispatchEventListeners() (internal, used by dispatchEvent())

The destroy() method also guards against undefined with an early return.

On the normal path (maps already initialized), this is a single falsy check with zero allocation.

Test

Added a test that simulates the uninitialized state by constructing an EventTarget and then forcibly clearing both internal maps to undefined. Verifies that addEventListener, dispatchEvent, and removeEventListener all work without throwing.

EventTarget.addEventListener(), removeEventListener(), dispatchEvent(),
and destroy() now defensively initialize PropertySymbol.listeners and
PropertySymbol.listenerOptions if they are undefined.

Class field initializers only run when the constructor executes. If a
third-party library creates an object that inherits from EventTarget
without calling super() (or attaches listeners before the initializers
have run), the internal maps are undefined and every method that
accesses them throws a TypeError.

The fix adds an ensureListenerMaps() helper that lazily creates the
maps on first access, keeping the normal (already-initialized) path
as a single falsy check with no allocation.

Closes capricorn86#2064
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EventTarget.addEventListener throws TypeError due to uninitialized internal listener maps

1 participant