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

Enable eager listeners statically #19983

Merged
merged 1 commit into from
Oct 8, 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
1 change: 0 additions & 1 deletion packages/react-dom/src/__tests__/ReactDOMFiber-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1040,7 +1040,6 @@ describe('ReactDOMFiber', () => {
expect(ops).toEqual([]);
});

// @gate enableEagerRootListeners
it('listens to events that do not exist in the Portal subtree', () => {
const onClick = jest.fn();

Expand Down
88 changes: 5 additions & 83 deletions packages/react-dom/src/client/ReactDOMComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,25 +61,16 @@ import {
shouldRemoveAttribute,
} from '../shared/DOMProperty';
import assertValidProps from '../shared/assertValidProps';
import {
DOCUMENT_NODE,
ELEMENT_NODE,
COMMENT_NODE,
DOCUMENT_FRAGMENT_NODE,
} from '../shared/HTMLNodeType';
import {DOCUMENT_NODE} from '../shared/HTMLNodeType';
import isCustomComponent from '../shared/isCustomComponent';
import possibleStandardNames from '../shared/possibleStandardNames';
import {validateProperties as validateARIAProperties} from '../shared/ReactDOMInvalidARIAHook';
import {validateProperties as validateInputProperties} from '../shared/ReactDOMNullInputValuePropHook';
import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook';
import {REACT_OPAQUE_ID_TYPE} from 'shared/ReactSymbols';

import {enableTrustedTypesIntegration} from 'shared/ReactFeatureFlags';
import {
enableTrustedTypesIntegration,
enableEagerRootListeners,
} from 'shared/ReactFeatureFlags';
import {
listenToReactEvent,
mediaEventTypes,
listenToNonDelegatedEvent,
} from '../events/DOMPluginEventSystem';
Expand Down Expand Up @@ -253,39 +244,6 @@ if (__DEV__) {
};
}

export function ensureListeningTo(
rootContainerInstance: Element | Node,
reactPropEvent: string,
targetElement: Element | null,
): void {
if (!enableEagerRootListeners) {
// 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;
if (__DEV__) {
if (
rootContainerElement == null ||
(rootContainerElement.nodeType !== ELEMENT_NODE &&
// This is to support rendering into a ShadowRoot:
rootContainerElement.nodeType !== DOCUMENT_FRAGMENT_NODE)
) {
console.error(
'ensureListeningTo(): received a container that was not an element node. ' +
'This is likely a bug in React. Please file an issue.',
);
}
}
listenToReactEvent(
reactPropEvent,
((rootContainerElement: any): Element),
targetElement,
);
}
}

function getOwnerDocumentFromRootContainer(
rootContainerElement: Element | Document,
): Document {
Expand Down Expand Up @@ -364,9 +322,7 @@ function setInitialDOMProperties(
if (__DEV__ && typeof nextProp !== 'function') {
warnForInvalidEventListener(propKey, nextProp);
}
if (!enableEagerRootListeners) {
ensureListeningTo(rootContainerElement, propKey, domElement);
} else if (propKey === 'onScroll') {
if (propKey === 'onScroll') {
listenToNonDelegatedEvent('scroll', domElement);
}
}
Expand Down Expand Up @@ -577,11 +533,6 @@ export function setInitialProperties(
// We listen to this event in case to ensure emulated bubble
// listeners still fire for the invalid event.
listenToNonDelegatedEvent('invalid', domElement);
if (!enableEagerRootListeners) {
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange', domElement);
}
break;
case 'option':
ReactDOMOptionValidateProps(domElement, rawProps);
Expand All @@ -593,23 +544,13 @@ export function setInitialProperties(
// We listen to this event in case to ensure emulated bubble
// listeners still fire for the invalid event.
listenToNonDelegatedEvent('invalid', domElement);
if (!enableEagerRootListeners) {
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange', domElement);
}
break;
case 'textarea':
ReactDOMTextareaInitWrapperState(domElement, rawProps);
props = ReactDOMTextareaGetHostProps(domElement, rawProps);
// We listen to this event in case to ensure emulated bubble
// listeners still fire for the invalid event.
listenToNonDelegatedEvent('invalid', domElement);
if (!enableEagerRootListeners) {
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange', domElement);
}
break;
default:
props = rawProps;
Expand Down Expand Up @@ -827,9 +768,7 @@ export function diffProperties(
if (__DEV__ && typeof nextProp !== 'function') {
warnForInvalidEventListener(propKey, nextProp);
}
if (!enableEagerRootListeners) {
ensureListeningTo(rootContainerElement, propKey, domElement);
} else if (propKey === 'onScroll') {
if (propKey === 'onScroll') {
listenToNonDelegatedEvent('scroll', domElement);
}
}
Expand Down Expand Up @@ -983,11 +922,6 @@ export function diffHydratedProperties(
// We listen to this event in case to ensure emulated bubble
// listeners still fire for the invalid event.
listenToNonDelegatedEvent('invalid', domElement);
if (!enableEagerRootListeners) {
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange', domElement);
}
break;
case 'option':
ReactDOMOptionValidateProps(domElement, rawProps);
Expand All @@ -997,22 +931,12 @@ export function diffHydratedProperties(
// We listen to this event in case to ensure emulated bubble
// listeners still fire for the invalid event.
listenToNonDelegatedEvent('invalid', domElement);
if (!enableEagerRootListeners) {
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange', domElement);
}
break;
case 'textarea':
ReactDOMTextareaInitWrapperState(domElement, rawProps);
// We listen to this event in case to ensure emulated bubble
// listeners still fire for the invalid event.
listenToNonDelegatedEvent('invalid', domElement);
if (!enableEagerRootListeners) {
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange', domElement);
}
break;
}

Expand Down Expand Up @@ -1079,9 +1003,7 @@ export function diffHydratedProperties(
if (__DEV__ && typeof nextProp !== 'function') {
warnForInvalidEventListener(propKey, nextProp);
}
if (!enableEagerRootListeners) {
ensureListeningTo(rootContainerElement, propKey, domElement);
} else if (propKey === 'onScroll') {
if (propKey === 'onScroll') {
listenToNonDelegatedEvent('scroll', domElement);
}
}
Expand Down
87 changes: 3 additions & 84 deletions packages/react-dom/src/client/ReactDOMEventHandle.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,26 @@ import type {

import {allNativeEvents} from '../events/EventRegistry';
import {
getClosestInstanceFromNode,
getEventHandlerListeners,
setEventHandlerListeners,
getFiberFromScopeInstance,
doesTargetHaveEventHandle,
addEventHandleToTarget,
} from './ReactDOMComponentTree';
import {ELEMENT_NODE, COMMENT_NODE} from '../shared/HTMLNodeType';
import {ELEMENT_NODE} from '../shared/HTMLNodeType';
import {listenToNativeEvent} from '../events/DOMPluginEventSystem';

import {HostRoot, HostPortal} from 'react-reconciler/src/ReactWorkTags';
import {IS_EVENT_HANDLE_NON_MANAGED_NODE} from '../events/EventSystemFlags';

import {
enableScopeAPI,
enableCreateEventHandleAPI,
enableEagerRootListeners,
} from 'shared/ReactFeatureFlags';
import invariant from 'shared/invariant';

type EventHandleOptions = {|
capture?: boolean,
|};

function getNearestRootOrPortalContainer(node: Fiber): null | Element {
while (node !== null) {
const tag = node.tag;
// Once we encounter a host container or root container
// we can return their DOM instance.
if (tag === HostRoot || tag === HostPortal) {
return node.stateNode.containerInfo;
}
node = node.return;
}
return null;
}

function isValidEventTarget(target: EventTarget | ReactScopeInstance): boolean {
return typeof (target: Object).addEventListener === 'function';
}
Expand All @@ -73,79 +56,15 @@ function createEventHandleListener(
};
}

function registerEventOnNearestTargetContainer(
targetFiber: Fiber,
domEventName: DOMEventName,
isCapturePhaseListener: boolean,
targetElement: Element | null,
): void {
if (!enableEagerRootListeners) {
// If it is, find the nearest root or portal and make it
// our event handle target container.
let targetContainer = getNearestRootOrPortalContainer(targetFiber);
if (targetContainer === null) {
if (__DEV__) {
console.error(
'ReactDOM.createEventHandle: setListener called on an target ' +
'that did not have a corresponding root. This is likely a bug in React.',
);
}
return;
}
if (targetContainer.nodeType === COMMENT_NODE) {
targetContainer = ((targetContainer.parentNode: any): Element);
}
listenToNativeEvent(
domEventName,
isCapturePhaseListener,
targetContainer,
targetElement,
);
}
}

function registerReactDOMEvent(
target: EventTarget | ReactScopeInstance,
domEventName: DOMEventName,
isCapturePhaseListener: boolean,
): void {
// Check if the target is a DOM element.
if ((target: any).nodeType === ELEMENT_NODE) {
if (!enableEagerRootListeners) {
const targetElement = ((target: any): Element);
// Check if the DOM element is managed by React.
const targetFiber = getClosestInstanceFromNode(targetElement);
if (targetFiber === null) {
if (__DEV__) {
console.error(
'ReactDOM.createEventHandle: setListener called on an element ' +
'target that is not managed by React. Ensure React rendered the DOM element.',
);
}
return;
}
registerEventOnNearestTargetContainer(
targetFiber,
domEventName,
isCapturePhaseListener,
targetElement,
);
}
// Do nothing. We already attached all root listeners.
} else if (enableScopeAPI && isReactScope(target)) {
if (!enableEagerRootListeners) {
const scopeTarget = ((target: any): ReactScopeInstance);
const targetFiber = getFiberFromScopeInstance(scopeTarget);
if (targetFiber === null) {
// Scope is unmounted, do not proceed.
return;
}
registerEventOnNearestTargetContainer(
targetFiber,
domEventName,
isCapturePhaseListener,
null,
);
}
// Do nothing. We already attached all root listeners.
} else if (isValidEventTarget(target)) {
const eventTarget = ((target: any): EventTarget);
// These are valid event targets, but they are also
Expand Down
12 changes: 2 additions & 10 deletions packages/react-dom/src/client/ReactDOMHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,9 @@ import {
enableFundamentalAPI,
enableCreateEventHandleAPI,
enableScopeAPI,
enableEagerRootListeners,
} from 'shared/ReactFeatureFlags';
import {HostComponent, HostText} from 'react-reconciler/src/ReactWorkTags';
import {
listenToReactEvent,
listenToAllSupportedEvents,
} from '../events/DOMPluginEventSystem';
import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem';

export type Type = string;
export type Props = {
Expand Down Expand Up @@ -1073,11 +1069,7 @@ export function makeOpaqueHydratingObject(
}

export function preparePortalMount(portalInstance: Instance): void {
if (enableEagerRootListeners) {
listenToAllSupportedEvents(portalInstance);
} else {
listenToReactEvent('onMouseEnter', portalInstance, null);
}
listenToAllSupportedEvents(portalInstance);
}

export function prepareScopeUpdate(
Expand Down
31 changes: 4 additions & 27 deletions packages/react-dom/src/client/ReactDOMRoot.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,12 @@ import {
unmarkContainerAsRoot,
} from './ReactDOMComponentTree';
import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem';
import {eagerlyTrapReplayableEvents} from '../events/ReactDOMEventReplaying';
import {
ELEMENT_NODE,
COMMENT_NODE,
DOCUMENT_NODE,
DOCUMENT_FRAGMENT_NODE,
} from '../shared/HTMLNodeType';
import {ensureListeningTo} from './ReactDOMComponent';

import {
createContainer,
Expand All @@ -52,7 +50,6 @@ import {
registerMutableSourceForHydration,
} from 'react-reconciler/src/ReactFiberReconciler';
import invariant from 'shared/invariant';
import {enableEagerRootListeners} from 'shared/ReactFeatureFlags';
import {
BlockingRoot,
ConcurrentRoot,
Expand Down Expand Up @@ -133,30 +130,10 @@ function createRootImpl(
null;
const root = createContainer(container, tag, hydrate, hydrationCallbacks);
markContainerAsRoot(root.current, container);
const containerNodeType = container.nodeType;

if (enableEagerRootListeners) {
const rootContainerElement =
container.nodeType === COMMENT_NODE ? container.parentNode : container;
listenToAllSupportedEvents(rootContainerElement);
} else {
if (hydrate && tag !== LegacyRoot) {
const doc =
containerNodeType === DOCUMENT_NODE
? container
: container.ownerDocument;
// We need to cast this because Flow doesn't work
// with the hoisted containerNodeType. If we inline
// it, then Flow doesn't complain. We intentionally
// hoist it to reduce code-size.
eagerlyTrapReplayableEvents(container, ((doc: any): Document));
} else if (
containerNodeType !== DOCUMENT_FRAGMENT_NODE &&
containerNodeType !== DOCUMENT_NODE
) {
ensureListeningTo(container, 'onMouseEnter', null);
}
}

const rootContainerElement =
container.nodeType === COMMENT_NODE ? container.parentNode : container;
listenToAllSupportedEvents(rootContainerElement);

if (mutableSources) {
for (let i = 0; i < mutableSources.length; i++) {
Expand Down
Loading