Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More event system cleanup and scaffolding #18179

Merged
merged 1 commit into from
Mar 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/legacy-events/PluginModuleType.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export type PluginModule<NativeEvent> = {
nativeTarget: NativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
container?: Document | Element | Node,
container?: Document | Element,
) => ?ReactSyntheticEvent,
tapMoveThreshold?: number,
};
140 changes: 103 additions & 37 deletions packages/react-dom/src/client/ReactDOMComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import {registrationNameModules} from 'legacy-events/EventPluginRegistry';
import {canUseDOM} from 'shared/ExecutionEnvironment';
import endsWith from 'shared/endsWith';
import invariant from 'shared/invariant';
import {setListenToResponderEventTypes} from '../events/DeprecatedDOMEventResponderSystem';

import {
Expand Down Expand Up @@ -59,7 +60,6 @@ import {getListenerMapForElement} from '../events/DOMEventListenerMap';
import {
addResponderEventSystemEvent,
removeActiveResponderEventSystemEvent,
trapBubbledEvent,
} from '../events/ReactDOMEventListener.js';
import {mediaEventTypes} from '../events/DOMTopLevelEventTypes';
import {
Expand All @@ -74,7 +74,12 @@ import {
shouldRemoveAttribute,
} from '../shared/DOMProperty';
import assertValidProps from '../shared/assertValidProps';
import {DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE} from '../shared/HTMLNodeType';
import {
DOCUMENT_NODE,
DOCUMENT_FRAGMENT_NODE,
ELEMENT_NODE,
COMMENT_NODE,
} from '../shared/HTMLNodeType';
import isCustomComponent from '../shared/isCustomComponent';
import possibleStandardNames from '../shared/possibleStandardNames';
import {validateProperties as validateARIAProperties} from '../shared/ReactDOMInvalidARIAHook';
Expand All @@ -84,8 +89,13 @@ import {validateProperties as validateUnknownProperties} from '../shared/ReactDO
import {
enableDeprecatedFlareAPI,
enableTrustedTypesIntegration,
enableModernEventSystem,
} from 'shared/ReactFeatureFlags';
import {legacyListenToEvent} from '../events/DOMLegacyEventPluginSystem';
import {
legacyListenToEvent,
legacyTrapBubbledEvent,
} from '../events/DOMLegacyEventPluginSystem';
import {listenToEvent} from '../events/DOMModernPluginEventSystem';

let didWarnInvalidHydration = false;
let didWarnScriptTags = false;
Expand Down Expand Up @@ -260,16 +270,36 @@ if (__DEV__) {
}

function ensureListeningTo(
rootContainerElement: Element | Node,
rootContainerInstance: Element | Node,
registrationName: string,
): void {
const isDocumentOrFragment =
rootContainerElement.nodeType === DOCUMENT_NODE ||
rootContainerElement.nodeType === DOCUMENT_FRAGMENT_NODE;
const doc = isDocumentOrFragment
? rootContainerElement
: rootContainerElement.ownerDocument;
legacyListenToEvent(registrationName, doc);
if (enableModernEventSystem) {
// If we have a comment node, then use the parent node,
// which should be an element.
const rootContainerElement =
rootContainerInstance.nodeType === COMMENT_NODE
? rootContainerInstance.parentNode
: rootContainerInstance;
// Containers can only ever be element nodes. We do not
// want to register events to document fragments or documents
// with the modern plugin event system.
invariant(
rootContainerElement != null &&
rootContainerElement.nodeType === ELEMENT_NODE,
'ensureListeningTo(): received a container that was not an element node. ' +
'This is likely a bug in React.',
);
listenToEvent(registrationName, ((rootContainerElement: any): Element));
} else {
// Legacy plugin event system path
const isDocumentOrFragment =
rootContainerInstance.nodeType === DOCUMENT_NODE ||
rootContainerInstance.nodeType === DOCUMENT_FRAGMENT_NODE;
const doc = isDocumentOrFragment
? rootContainerInstance
: rootContainerInstance.ownerDocument;
legacyListenToEvent(registrationName, ((doc: any): Document));
}
}

function getOwnerDocumentFromRootContainer(
Expand Down Expand Up @@ -514,41 +544,55 @@ export function setInitialProperties(
case 'iframe':
case 'object':
case 'embed':
trapBubbledEvent(TOP_LOAD, domElement);
if (!enableModernEventSystem) {
legacyTrapBubbledEvent(TOP_LOAD, domElement);
}
props = rawProps;
break;
case 'video':
case 'audio':
// Create listener for each media event
for (let i = 0; i < mediaEventTypes.length; i++) {
trapBubbledEvent(mediaEventTypes[i], domElement);
if (!enableModernEventSystem) {
// Create listener for each media event
for (let i = 0; i < mediaEventTypes.length; i++) {
legacyTrapBubbledEvent(mediaEventTypes[i], domElement);
}
}
props = rawProps;
break;
case 'source':
trapBubbledEvent(TOP_ERROR, domElement);
if (!enableModernEventSystem) {
legacyTrapBubbledEvent(TOP_ERROR, domElement);
}
props = rawProps;
break;
case 'img':
case 'image':
case 'link':
trapBubbledEvent(TOP_ERROR, domElement);
trapBubbledEvent(TOP_LOAD, domElement);
if (!enableModernEventSystem) {
legacyTrapBubbledEvent(TOP_ERROR, domElement);
legacyTrapBubbledEvent(TOP_LOAD, domElement);
}
props = rawProps;
break;
case 'form':
trapBubbledEvent(TOP_RESET, domElement);
trapBubbledEvent(TOP_SUBMIT, domElement);
if (!enableModernEventSystem) {
legacyTrapBubbledEvent(TOP_RESET, domElement);
legacyTrapBubbledEvent(TOP_SUBMIT, domElement);
}
props = rawProps;
break;
case 'details':
trapBubbledEvent(TOP_TOGGLE, domElement);
if (!enableModernEventSystem) {
legacyTrapBubbledEvent(TOP_TOGGLE, domElement);
}
props = rawProps;
break;
case 'input':
ReactDOMInputInitWrapperState(domElement, rawProps);
props = ReactDOMInputGetHostProps(domElement, rawProps);
trapBubbledEvent(TOP_INVALID, domElement);
if (!enableModernEventSystem) {
legacyTrapBubbledEvent(TOP_INVALID, domElement);
}
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
Expand All @@ -560,15 +604,19 @@ export function setInitialProperties(
case 'select':
ReactDOMSelectInitWrapperState(domElement, rawProps);
props = ReactDOMSelectGetHostProps(domElement, rawProps);
trapBubbledEvent(TOP_INVALID, domElement);
if (!enableModernEventSystem) {
legacyTrapBubbledEvent(TOP_INVALID, domElement);
}
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
break;
case 'textarea':
ReactDOMTextareaInitWrapperState(domElement, rawProps);
props = ReactDOMTextareaGetHostProps(domElement, rawProps);
trapBubbledEvent(TOP_INVALID, domElement);
if (!enableModernEventSystem) {
legacyTrapBubbledEvent(TOP_INVALID, domElement);
}
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
Expand Down Expand Up @@ -898,34 +946,48 @@ export function diffHydratedProperties(
case 'iframe':
case 'object':
case 'embed':
trapBubbledEvent(TOP_LOAD, domElement);
if (!enableModernEventSystem) {
legacyTrapBubbledEvent(TOP_LOAD, domElement);
}
break;
case 'video':
case 'audio':
// Create listener for each media event
for (let i = 0; i < mediaEventTypes.length; i++) {
trapBubbledEvent(mediaEventTypes[i], domElement);
if (!enableModernEventSystem) {
// Create listener for each media event
for (let i = 0; i < mediaEventTypes.length; i++) {
legacyTrapBubbledEvent(mediaEventTypes[i], domElement);
}
}
break;
case 'source':
trapBubbledEvent(TOP_ERROR, domElement);
if (!enableModernEventSystem) {
legacyTrapBubbledEvent(TOP_ERROR, domElement);
}
break;
case 'img':
case 'image':
case 'link':
trapBubbledEvent(TOP_ERROR, domElement);
trapBubbledEvent(TOP_LOAD, domElement);
if (!enableModernEventSystem) {
legacyTrapBubbledEvent(TOP_ERROR, domElement);
legacyTrapBubbledEvent(TOP_LOAD, domElement);
}
break;
case 'form':
trapBubbledEvent(TOP_RESET, domElement);
trapBubbledEvent(TOP_SUBMIT, domElement);
if (!enableModernEventSystem) {
legacyTrapBubbledEvent(TOP_RESET, domElement);
legacyTrapBubbledEvent(TOP_SUBMIT, domElement);
}
break;
case 'details':
trapBubbledEvent(TOP_TOGGLE, domElement);
if (!enableModernEventSystem) {
legacyTrapBubbledEvent(TOP_TOGGLE, domElement);
}
break;
case 'input':
ReactDOMInputInitWrapperState(domElement, rawProps);
trapBubbledEvent(TOP_INVALID, domElement);
if (!enableModernEventSystem) {
legacyTrapBubbledEvent(TOP_INVALID, domElement);
}
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
Expand All @@ -935,14 +997,18 @@ export function diffHydratedProperties(
break;
case 'select':
ReactDOMSelectInitWrapperState(domElement, rawProps);
trapBubbledEvent(TOP_INVALID, domElement);
if (!enableModernEventSystem) {
legacyTrapBubbledEvent(TOP_INVALID, domElement);
}
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
break;
case 'textarea':
ReactDOMTextareaInitWrapperState(domElement, rawProps);
trapBubbledEvent(TOP_INVALID, domElement);
if (!enableModernEventSystem) {
legacyTrapBubbledEvent(TOP_INVALID, domElement);
}
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
Expand Down
18 changes: 18 additions & 0 deletions packages/react-dom/src/events/DOMEventListenerMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes';

import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry';

const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
// prettier-ignore
const elementListenerMap:
Expand All @@ -29,3 +31,19 @@ export function getListenerMapForElement(
}
return listenerMap;
}

export function isListeningToAllDependencies(
registrationName: string,
mountAt: Document | Element,
): boolean {
const listenerMap = getListenerMapForElement(mountAt);
const dependencies = registrationNameDependencies[registrationName];

for (let i = 0; i < dependencies.length; i++) {
const dependency = dependencies[i];
if (!listenerMap.has(dependency)) {
return false;
}
}
return true;
}
40 changes: 19 additions & 21 deletions packages/react-dom/src/events/DOMLegacyEventPluginSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry';

import getEventTarget from './getEventTarget';
import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
import {trapCapturedEvent, trapBubbledEvent} from './ReactDOMEventListener';
import {getListenerMapForElement} from './DOMEventListenerMap';
import isEventSupported from './isEventSupported';
import {
Expand All @@ -40,6 +39,7 @@ import {
getRawEventName,
mediaEventTypes,
} from './DOMTopLevelEventTypes';
import {trapEventForPluginEventSystem} from './ReactDOMEventListener';

/**
* Summary of `DOMEventPluginSystem` event handling:
Expand Down Expand Up @@ -309,7 +309,7 @@ export function dispatchEventForLegacyPluginEventSystem(
*/
export function legacyListenToEvent(
registrationName: string,
mountAt: Document | Element | Node,
mountAt: Document | Element,
): void {
const listenerMap = getListenerMapForElement(mountAt);
const dependencies = registrationNameDependencies[registrationName];
Expand All @@ -322,18 +322,18 @@ export function legacyListenToEvent(

export function legacyListenToTopLevelEvent(
topLevelType: DOMTopLevelEventType,
mountAt: Document | Element | Node,
mountAt: Document | Element,
listenerMap: Map<DOMTopLevelEventType | string, null | (any => void)>,
): void {
if (!listenerMap.has(topLevelType)) {
switch (topLevelType) {
case TOP_SCROLL:
trapCapturedEvent(TOP_SCROLL, mountAt);
legacyTrapCapturedEvent(TOP_SCROLL, mountAt);
break;
case TOP_FOCUS:
case TOP_BLUR:
trapCapturedEvent(TOP_FOCUS, mountAt);
trapCapturedEvent(TOP_BLUR, mountAt);
legacyTrapCapturedEvent(TOP_FOCUS, mountAt);
legacyTrapCapturedEvent(TOP_BLUR, mountAt);
// We set the flag for a single dependency later in this function,
// but this ensures we mark both as attached rather than just one.
listenerMap.set(TOP_BLUR, null);
Expand All @@ -342,7 +342,7 @@ export function legacyListenToTopLevelEvent(
case TOP_CANCEL:
case TOP_CLOSE:
if (isEventSupported(getRawEventName(topLevelType))) {
trapCapturedEvent(topLevelType, mountAt);
legacyTrapCapturedEvent(topLevelType, mountAt);
}
break;
case TOP_INVALID:
Expand All @@ -356,26 +356,24 @@ export function legacyListenToTopLevelEvent(
// Media events don't bubble so adding the listener wouldn't do anything.
const isMediaEvent = mediaEventTypes.indexOf(topLevelType) !== -1;
if (!isMediaEvent) {
trapBubbledEvent(topLevelType, mountAt);
legacyTrapBubbledEvent(topLevelType, mountAt);
}
break;
}
listenerMap.set(topLevelType, null);
}
}

export function isListeningToAllDependencies(
registrationName: string,
mountAt: Document | Element,
): boolean {
const listenerMap = getListenerMapForElement(mountAt);
const dependencies = registrationNameDependencies[registrationName];
export function legacyTrapBubbledEvent(
topLevelType: DOMTopLevelEventType,
element: Document | Element,
): void {
trapEventForPluginEventSystem(element, topLevelType, false);
}

for (let i = 0; i < dependencies.length; i++) {
const dependency = dependencies[i];
if (!listenerMap.has(dependency)) {
return false;
}
}
return true;
export function legacyTrapCapturedEvent(
topLevelType: DOMTopLevelEventType,
element: Document | Element,
): void {
trapEventForPluginEventSystem(element, topLevelType, true);
}
Loading