From ad834b13e423d7b65e905eee8c2d8a9ab80d5036 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 28 Apr 2020 11:58:28 +0100 Subject: [PATCH] Refactor ElementListenerMap Cleanup Cleanup Address comments --- .../react-dom/src/client/ReactDOMComponent.js | 4 +- .../src/client/ReactDOMComponentTree.js | 26 ++++++- .../src/events/DOMEventListenerMap.js | 70 ------------------- .../src/events/DOMLegacyEventPluginSystem.js | 10 +-- .../src/events/DOMModernPluginEventSystem.js | 12 ++-- .../src/events/ReactDOMEventReplaying.js | 8 +-- .../events/plugins/LegacySelectEventPlugin.js | 23 +++++- .../events/plugins/ModernSelectEventPlugin.js | 28 +++++++- 8 files changed, 89 insertions(+), 92 deletions(-) delete mode 100644 packages/react-dom/src/events/DOMEventListenerMap.js diff --git a/packages/react-dom/src/client/ReactDOMComponent.js b/packages/react-dom/src/client/ReactDOMComponent.js index 5b355e1bd423f..7c4e24ba0d322 100644 --- a/packages/react-dom/src/client/ReactDOMComponent.js +++ b/packages/react-dom/src/client/ReactDOMComponent.js @@ -59,7 +59,6 @@ import { TOP_SUBMIT, TOP_TOGGLE, } from '../events/DOMTopLevelEventTypes'; -import {getListenerMapForElement} from '../events/DOMEventListenerMap'; import {mediaEventTypes} from '../events/DOMTopLevelEventTypes'; import { createDangerousStringForStyles, @@ -96,6 +95,7 @@ import { legacyTrapBubbledEvent, } from '../events/DOMLegacyEventPluginSystem'; import {listenToEvent} from '../events/DOMModernPluginEventSystem'; +import {getEventListenerMap} from './ReactDOMComponentTree'; let didWarnInvalidHydration = false; let didWarnScriptTags = false; @@ -1346,7 +1346,7 @@ export function listenToEventResponderEventTypes( if (enableDeprecatedFlareAPI) { // Get the listening Map for this element. We use this to track // what events we're listening to. - const listenerMap = getListenerMapForElement(document); + const listenerMap = getEventListenerMap(document); // Go through each target event type of the event responder for (let i = 0, length = eventTypes.length; i < length; ++i) { diff --git a/packages/react-dom/src/client/ReactDOMComponentTree.js b/packages/react-dom/src/client/ReactDOMComponentTree.js index c8c6bfc0dfd6c..9ac7aee0a62eb 100644 --- a/packages/react-dom/src/client/ReactDOMComponentTree.js +++ b/packages/react-dom/src/client/ReactDOMComponentTree.js @@ -15,6 +15,7 @@ import type { SuspenseInstance, Props, } from './ReactDOMHostConfig'; +import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes'; import { HostComponent, @@ -30,8 +31,19 @@ const randomKey = Math.random() .toString(36) .slice(2); const internalInstanceKey = '__reactFiber$' + randomKey; -const internalEventHandlersKey = '__reactEvents$' + randomKey; +const internalPropsKey = '__reactProps$' + randomKey; const internalContainerInstanceKey = '__reactContainer$' + randomKey; +const internalEventHandlersKey = '__reactEvents$' + randomKey; + +export type ElementListenerMap = Map< + DOMTopLevelEventType | string, + ElementListenerMapEntry, +>; + +export type ElementListenerMapEntry = { + passive: void | boolean, + listener: any => void, +}; export function precacheFiberNode( hostInst: Fiber, @@ -176,12 +188,20 @@ export function getNodeFromInstance(inst: Fiber): Instance | TextInstance { export function getFiberCurrentPropsFromNode( node: Instance | TextInstance | SuspenseInstance, ): Props { - return (node: any)[internalEventHandlersKey] || null; + return (node: any)[internalPropsKey] || null; } export function updateFiberProps( node: Instance | TextInstance | SuspenseInstance, props: Props, ): void { - (node: any)[internalEventHandlersKey] = props; + (node: any)[internalPropsKey] = props; +} + +export function getEventListenerMap(node: EventTarget): ElementListenerMap { + let elementListenerMap = (node: any)[internalEventHandlersKey]; + if (elementListenerMap === undefined) { + elementListenerMap = (node: any)[internalEventHandlersKey] = new Map(); + } + return elementListenerMap; } diff --git a/packages/react-dom/src/events/DOMEventListenerMap.js b/packages/react-dom/src/events/DOMEventListenerMap.js deleted file mode 100644 index 8a61eb38d6f02..0000000000000 --- a/packages/react-dom/src/events/DOMEventListenerMap.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes'; - -import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry'; - -const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map; -// $FlowFixMe: Flow cannot handle polymorphic WeakMaps -const elementListenerMap: WeakMap< - EventTarget, - ElementListenerMap, -> = new PossiblyWeakMap(); - -export type ElementListenerMap = Map< - DOMTopLevelEventType | string, - ElementListenerMapEntry, ->; - -export type ElementListenerMapEntry = { - passive: void | boolean, - listener: any => void, -}; - -export function getListenerMapForElement( - target: EventTarget, -): ElementListenerMap { - let listenerMap = elementListenerMap.get(target); - if (listenerMap === undefined) { - listenerMap = new Map(); - elementListenerMap.set(target, listenerMap); - } - return listenerMap; -} - -export function isListeningToAllDependencies( - registrationName: string, - mountAt: Document | Element, -): boolean { - const dependencies = registrationNameDependencies[registrationName]; - return isListeningToEvents(dependencies, mountAt); -} - -export function isListeningToEvents( - events: Array, - mountAt: Document | Element, -): boolean { - const listenerMap = getListenerMapForElement(mountAt); - for (let i = 0; i < events.length; i++) { - const event = events[i]; - if (!listenerMap.has(event)) { - return false; - } - } - return true; -} - -export function isListeningToEvent( - registrationName: string, - mountAt: Document | Element, -): boolean { - const listenerMap = getListenerMapForElement(mountAt); - return listenerMap.has(registrationName); -} diff --git a/packages/react-dom/src/events/DOMLegacyEventPluginSystem.js b/packages/react-dom/src/events/DOMLegacyEventPluginSystem.js index 2354e50dc08e9..4e6041123e138 100644 --- a/packages/react-dom/src/events/DOMLegacyEventPluginSystem.js +++ b/packages/react-dom/src/events/DOMLegacyEventPluginSystem.js @@ -9,7 +9,7 @@ import type {AnyNativeEvent} from 'legacy-events/PluginModuleType'; import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes'; -import type {ElementListenerMap} from '../events/DOMEventListenerMap'; +import type {ElementListenerMap} from '../client/ReactDOMComponentTree'; import type {EventSystemFlags} from './EventSystemFlags'; import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; import type {PluginModule} from 'legacy-events/PluginModuleType'; @@ -29,8 +29,10 @@ import accumulateInto from 'legacy-events/accumulateInto'; import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry'; import getEventTarget from './getEventTarget'; -import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree'; -import {getListenerMapForElement} from './DOMEventListenerMap'; +import { + getClosestInstanceFromNode, + getEventListenerMap, +} from '../client/ReactDOMComponentTree'; import isEventSupported from './isEventSupported'; import { TOP_BLUR, @@ -319,7 +321,7 @@ export function legacyListenToEvent( registrationName: string, mountAt: Document | Element, ): void { - const listenerMap = getListenerMapForElement(mountAt); + const listenerMap = getEventListenerMap(mountAt); const dependencies = registrationNameDependencies[registrationName]; for (let i = 0; i < dependencies.length; i++) { diff --git a/packages/react-dom/src/events/DOMModernPluginEventSystem.js b/packages/react-dom/src/events/DOMModernPluginEventSystem.js index 85780aa3d29da..5387d6de4b5e7 100644 --- a/packages/react-dom/src/events/DOMModernPluginEventSystem.js +++ b/packages/react-dom/src/events/DOMModernPluginEventSystem.js @@ -12,7 +12,7 @@ import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes'; import type { ElementListenerMap, ElementListenerMapEntry, -} from '../events/DOMEventListenerMap'; +} from '../client/ReactDOMComponentTree'; import type {EventSystemFlags} from './EventSystemFlags'; import type {EventPriority, ReactScopeMethods} from 'shared/ReactTypes'; import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; @@ -36,7 +36,6 @@ import { } from 'react-reconciler/src/ReactWorkTags'; import getEventTarget from './getEventTarget'; -import {getListenerMapForElement} from './DOMEventListenerMap'; import { TOP_FOCUS, TOP_LOAD, @@ -74,7 +73,10 @@ import { TOP_SELECTION_CHANGE, getRawEventName, } from './DOMTopLevelEventTypes'; -import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree'; +import { + getClosestInstanceFromNode, + getEventListenerMap, +} from '../client/ReactDOMComponentTree'; import {COMMENT_NODE} from '../shared/HTMLNodeType'; import {batchedEventUpdates} from './ReactDOMUpdateBatching'; import getListener from './getListener'; @@ -237,7 +239,7 @@ export function listenToTopLevelEvent( // triggered on the document directly. if (topLevelType === TOP_SELECTION_CHANGE) { targetContainer = (targetContainer: any).ownerDocument || targetContainer; - listenerMap = getListenerMapForElement(targetContainer); + listenerMap = getEventListenerMap(targetContainer); } const listenerEntry: ElementListenerMapEntry | void = listenerMap.get( topLevelType, @@ -262,7 +264,7 @@ export function listenToEvent( registrationName: string, rootContainerElement: Element, ): void { - const listenerMap = getListenerMapForElement(rootContainerElement); + const listenerMap = getEventListenerMap(rootContainerElement); const dependencies = registrationNameDependencies[registrationName]; for (let i = 0; i < dependencies.length; i++) { diff --git a/packages/react-dom/src/events/ReactDOMEventReplaying.js b/packages/react-dom/src/events/ReactDOMEventReplaying.js index 7954b6727c69c..7ece12abadb6d 100644 --- a/packages/react-dom/src/events/ReactDOMEventReplaying.js +++ b/packages/react-dom/src/events/ReactDOMEventReplaying.js @@ -10,7 +10,7 @@ import type {AnyNativeEvent} from 'legacy-events/PluginModuleType'; import type {Container, SuspenseInstance} from '../client/ReactDOMHostConfig'; import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes'; -import type {ElementListenerMap} from '../events/DOMEventListenerMap'; +import type {ElementListenerMap} from '../client/ReactDOMComponentTree'; import type {EventSystemFlags} from './EventSystemFlags'; import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes'; @@ -31,10 +31,10 @@ import { getSuspenseInstanceFromFiber, } from 'react-reconciler/src/ReactFiberTreeReflection'; import {attemptToDispatchEvent} from './ReactDOMEventListener'; -import {getListenerMapForElement} from './DOMEventListenerMap'; import { getInstanceFromNode, getClosestInstanceFromNode, + getEventListenerMap, } from '../client/ReactDOMComponentTree'; import {unsafeCastDOMTopLevelTypeToString} from 'legacy-events/TopLevelEventTypes'; import {HostRoot, SuspenseComponent} from 'react-reconciler/src/ReactWorkTags'; @@ -256,10 +256,10 @@ export function eagerlyTrapReplayableEvents( container: Container, document: Document, ) { - const listenerMapForDoc = getListenerMapForElement(document); + const listenerMapForDoc = getEventListenerMap(document); let listenerMapForContainer; if (enableModernEventSystem) { - listenerMapForContainer = getListenerMapForElement(container); + listenerMapForContainer = getEventListenerMap(container); } // Discrete discreteReplayableEvents.forEach(topLevelType => { diff --git a/packages/react-dom/src/events/plugins/LegacySelectEventPlugin.js b/packages/react-dom/src/events/plugins/LegacySelectEventPlugin.js index 74d1d9fb74e35..39406c2deb8b9 100644 --- a/packages/react-dom/src/events/plugins/LegacySelectEventPlugin.js +++ b/packages/react-dom/src/events/plugins/LegacySelectEventPlugin.js @@ -22,12 +22,16 @@ import { TOP_SELECTION_CHANGE, } from '../DOMTopLevelEventTypes'; import getActiveElement from '../../client/getActiveElement'; -import {getNodeFromInstance} from '../../client/ReactDOMComponentTree'; +import { + getNodeFromInstance, + getEventListenerMap, +} from '../../client/ReactDOMComponentTree'; import {hasSelectionCapabilities} from '../../client/ReactInputSelection'; import {DOCUMENT_NODE} from '../../shared/HTMLNodeType'; -import {isListeningToAllDependencies} from '../DOMEventListenerMap'; import {accumulateTwoPhaseDispatches} from '../DOMLegacyEventPluginSystem'; +import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry'; + const skipSelectionChangeEvent = canUseDOM && 'documentMode' in document && document.documentMode <= 11; @@ -143,6 +147,21 @@ function constructSelectEvent(nativeEvent, nativeEventTarget) { return null; } +function isListeningToAllDependencies( + registrationName: string, + mountAt: Document | Element, +): boolean { + const dependencies = registrationNameDependencies[registrationName]; + const listenerMap = getEventListenerMap(mountAt); + for (let i = 0; i < dependencies.length; i++) { + const event = dependencies[i]; + if (!listenerMap.has(event)) { + return false; + } + } + return true; +} + /** * This plugin creates an `onSelect` event that normalizes select events * across form elements. diff --git a/packages/react-dom/src/events/plugins/ModernSelectEventPlugin.js b/packages/react-dom/src/events/plugins/ModernSelectEventPlugin.js index 1c86ba48bbbbb..5abb63ceb5dd2 100644 --- a/packages/react-dom/src/events/plugins/ModernSelectEventPlugin.js +++ b/packages/react-dom/src/events/plugins/ModernSelectEventPlugin.js @@ -22,10 +22,12 @@ import { TOP_SELECTION_CHANGE, } from '../DOMTopLevelEventTypes'; import getActiveElement from '../../client/getActiveElement'; -import {getNodeFromInstance} from '../../client/ReactDOMComponentTree'; +import { + getNodeFromInstance, + getEventListenerMap, +} from '../../client/ReactDOMComponentTree'; import {hasSelectionCapabilities} from '../../client/ReactInputSelection'; import {DOCUMENT_NODE} from '../../shared/HTMLNodeType'; -import {isListeningToEvent, isListeningToEvents} from '../DOMEventListenerMap'; import {accumulateTwoPhaseListeners} from '../DOMModernPluginEventSystem'; const skipSelectionChangeEvent = @@ -144,6 +146,28 @@ function constructSelectEvent(nativeEvent, nativeEventTarget) { return null; } +function isListeningToEvents( + events: Array, + mountAt: Document | Element, +): boolean { + const listenerMap = getEventListenerMap(mountAt); + for (let i = 0; i < events.length; i++) { + const event = events[i]; + if (!listenerMap.has(event)) { + return false; + } + } + return true; +} + +function isListeningToEvent( + registrationName: string, + mountAt: Document | Element, +): boolean { + const listenerMap = getEventListenerMap(mountAt); + return listenerMap.has(registrationName); +} + /** * This plugin creates an `onSelect` event that normalizes select events * across form elements.