Skip to content

Commit

Permalink
Make EventTarget compatible with the existing implementation of ReadO…
Browse files Browse the repository at this point in the history
…nlyNode (facebook#48427)

Summary:

Changelog: [internal]

The `ReactNativeElement` class was refactored for performance reasons, and the current implementation does **NOT** call `super()`, and it inlines the parent constructor instead.

When it eventually extends `EventTarget`, things won't work as expected because the existing `EventTarget` implementation has constructor dependencies.

This refactors the current implementation of `EventTarget` to eliminate those constructor side-effects, and eliminates the constructor altogether. 

This breaks encapsulation, but it has some positive side-effects on performance:
1. Creating `EventTarget` instances is faster because it has no constructor logic.
2. Improves memory by not creating maps to hold the event listeners if no event listeners are ever added to the target (which is very common).
3. Improves the overall runtime performance of the methods in the class by migrating away from private methods (which are known to be slow on the babel transpiled version we're currently using).

Extra: it also simplifies making window/the global scope implement the EventTarget interface :)

Differential Revision: D67758408
  • Loading branch information
rubennorte authored and facebook-github-bot committed Jan 7, 2025
1 parent b1453b7 commit 47883ab
Showing 1 changed file with 48 additions and 23 deletions.
71 changes: 48 additions & 23 deletions packages/react-native/src/private/webapis/dom/events/EventTarget.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ type EventListenerRegistration = {
removed: boolean,
};

type ListenersMap = Map<string, Array<EventListenerRegistration>>;

const CAPTURING_LISTENERS_KEY = Symbol('capturingListeners');
const BUBBLING_LISTENERS_KEY = Symbol('bubblingListeners');

function getDefaultPassiveValue(
type: string,
eventTarget: EventTarget,
Expand All @@ -65,9 +70,6 @@ function getDefaultPassiveValue(
}

export default class EventTarget {
#listeners: Map<string, Array<EventListenerRegistration>> = new Map();
#captureListeners: Map<string, Array<EventListenerRegistration>> = new Map();

addEventListener(
type: string,
callback: EventListener | null,
Expand Down Expand Up @@ -114,11 +116,15 @@ export default class EventTarget {
return;
}

const listenerMap = capture ? this.#captureListeners : this.#listeners;
let listenerList = listenerMap.get(processedType);
let listenersMap = this._getListenersMap(capture);
let listenerList = listenersMap?.get(processedType);
if (listenerList == null) {
if (listenersMap == null) {
listenersMap = new Map();
this._setListenersMap(capture, listenersMap);
}
listenerList = [];
listenerMap.set(processedType, listenerList);
listenersMap.set(processedType, listenerList);
} else {
for (const listener of listenerList) {
if (listener.callback === callback) {
Expand All @@ -139,7 +145,7 @@ export default class EventTarget {

if (signal != null) {
signal.addEventListener('abort', () => {
this.#removeEventListenerRegistration(listener, nonNullListenerList);
this._removeEventListenerRegistration(listener, nonNullListenerList);
});
}
}
Expand Down Expand Up @@ -168,8 +174,8 @@ export default class EventTarget {
? optionsOrUseCapture
: optionsOrUseCapture.capture ?? false;

const listenerMap = capture ? this.#captureListeners : this.#listeners;
const listenerList = listenerMap.get(processedType);
const listenersMap = this._getListenersMap(capture);
const listenerList = listenersMap?.get(processedType);
if (listenerList == null) {
return;
}
Expand Down Expand Up @@ -200,7 +206,7 @@ export default class EventTarget {

setIsTrusted(event, false);

this.#dispatch(event);
this._dispatch(event);

return !event.defaultPrevented;
}
Expand All @@ -213,10 +219,10 @@ export default class EventTarget {
* Implements the "event dispatch" concept
* (see https://dom.spec.whatwg.org/#concept-event-dispatch).
*/
#dispatch(event: Event): void {
_dispatch(event: Event): void {
setEventDispatchFlag(event, true);

const eventPath = this.#getEventPath(event);
const eventPath = this._getEventPath(event);
setComposedPath(event, eventPath);
setTarget(event, this);

Expand All @@ -230,7 +236,7 @@ export default class EventTarget {
event,
target === this ? Event.AT_TARGET : Event.CAPTURING_PHASE,
);
target.#invoke(event, Event.CAPTURING_PHASE);
target._invoke(event, Event.CAPTURING_PHASE);
}

for (const target of eventPath) {
Expand All @@ -248,7 +254,7 @@ export default class EventTarget {
event,
target === this ? Event.AT_TARGET : Event.BUBBLING_PHASE,
);
target.#invoke(event, Event.BUBBLING_PHASE);
target._invoke(event, Event.BUBBLING_PHASE);
}

setEventPhase(event, Event.NONE);
Expand All @@ -266,7 +272,7 @@ export default class EventTarget {
*
* The return value is also set as `composedPath` for the event.
*/
#getEventPath(event: Event): $ReadOnlyArray<EventTarget> {
_getEventPath(event: Event): $ReadOnlyArray<EventTarget> {
const path = [];
// eslint-disable-next-line consistent-this
let target: EventTarget | null = this;
Expand All @@ -284,20 +290,21 @@ export default class EventTarget {
* Implements the event listener invoke concept
* (see https://dom.spec.whatwg.org/#concept-event-listener-invoke).
*/
#invoke(event: Event, eventPhase: EventPhase) {
const listenerMap =
eventPhase === Event.CAPTURING_PHASE
? this.#captureListeners
: this.#listeners;
_invoke(event: Event, eventPhase: EventPhase) {
const listenersMap = this._getListenersMap(
eventPhase === Event.CAPTURING_PHASE,
);

setCurrentTarget(event, this);

// This is a copy so listeners added during dispatch are NOT executed.
const listenerList = listenerMap.get(event.type)?.slice();
const listenerList = listenersMap?.get(event.type)?.slice();
if (listenerList == null) {
return;
}

setCurrentTarget(event, this);

for (const listener of listenerList) {
if (listener.removed) {
continue;
Expand Down Expand Up @@ -344,7 +351,7 @@ export default class EventTarget {
}
}

#removeEventListenerRegistration(
_removeEventListenerRegistration(
registration: EventListenerRegistration,
listenerList: Array<EventListenerRegistration>,
): void {
Expand All @@ -359,6 +366,24 @@ export default class EventTarget {
}
}

_getListenersMap(isCapture: boolean): ?ListenersMap {
return isCapture
? // $FlowExpectedError[prop-missing]
this[CAPTURING_LISTENERS_KEY]
: // $FlowExpectedError[prop-missing]
this[BUBBLING_LISTENERS_KEY];
}

_setListenersMap(isCapture: boolean, listenersMap: ListenersMap): void {
if (isCapture) {
// $FlowExpectedError[prop-missing]
this[CAPTURING_LISTENERS_KEY] = listenersMap;
} else {
// $FlowExpectedError[prop-missing]
this[BUBBLING_LISTENERS_KEY] = listenersMap;
}
}

/**
* This a "protected" method to be overridden by a subclass to allow event
* propagation.
Expand All @@ -376,7 +401,7 @@ export default class EventTarget {
*/
// $FlowExpectedError[unsupported-syntax]
[INTERNAL_DISPATCH_METHOD_KEY](event: Event): void {
this.#dispatch(event);
this._dispatch(event);
}
}

Expand Down

0 comments on commit 47883ab

Please sign in to comment.