Attach new listeners to ReactDOMRoot

This commit is contained in:
Tianyu Yao
2023-01-20 16:26:05 -08:00
parent 31ca11900f
commit c2cebbf9ee
4 changed files with 49 additions and 33 deletions
@@ -14,7 +14,7 @@ import type {
KnownReactSyntheticEvent,
ReactSyntheticEvent,
} from './ReactSyntheticEventType';
import type {Fiber, FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import {allNativeEvents} from './EventRegistry';
import {
@@ -44,10 +44,7 @@ import {
getEventHandlerListeners,
} from '../client/ReactDOMComponentTree';
import {COMMENT_NODE, DOCUMENT_NODE} from '../shared/HTMLNodeType';
import {
batchedUpdates,
flushPendingContinuousUpdates,
} from './ReactDOMUpdateBatching';
import {batchedUpdates} from './ReactDOMUpdateBatching';
import getListener from './getListener';
import {passiveBrowserEventsSupported} from './checkPassiveEvents';
@@ -75,10 +72,6 @@ import * as ChangeEventPlugin from './plugins/ChangeEventPlugin';
import * as EnterLeaveEventPlugin from './plugins/EnterLeaveEventPlugin';
import * as SelectEventPlugin from './plugins/SelectEventPlugin';
import * as SimpleEventPlugin from './plugins/SimpleEventPlugin';
import {
DiscreteEventPriority,
getCurrentUpdatePriority,
} from 'react-reconciler/src/ReactEventPriorities';
type DispatchListener = {
instance: null | Fiber,
@@ -644,18 +637,6 @@ export function dispatchEventForPluginEventSystem(
}
node = node.return;
}
// Special case: Flush continuous updates before the capture phase of a discrete event.
if (
eventSystemFlags & IS_CAPTURE_PHASE &&
node !== null &&
getCurrentUpdatePriority() === DiscreteEventPriority
) {
const root: ?FiberRoot = node.stateNode;
if (root != null) {
flushPendingContinuousUpdates(root);
}
}
}
}
@@ -27,7 +27,9 @@ import {
getSuspenseInstanceFromFiber,
} from 'react-reconciler/src/ReactFiberTreeReflection';
import {HostRoot, SuspenseComponent} from 'react-reconciler/src/ReactWorkTags';
import {flushPendingContinuousUpdates} from './ReactDOMUpdateBatching';
import {type EventSystemFlags, IS_CAPTURE_PHASE} from './EventSystemFlags';
import {allNativeEvents} from './EventRegistry';
import getEventTarget from './getEventTarget';
import {
@@ -111,6 +113,38 @@ export function createEventListenerWrapperWithPriority(
);
}
const listeningMarker =
'_reactListening' +
Math.random()
.toString(36)
.slice(2);
// For flushing continuous events before a discrete event in capture phase
// this function needs to be called before `listenToAllSupportedEvents` so
// that it runs before event system's capture phase callback.
export function listenToCapturePhaseDiscreteEvents(
rootContainerElement: EventTarget,
root: FiberRoot,
) {
if ((rootContainerElement: any)[listeningMarker]) {
return;
}
(rootContainerElement: any)[listeningMarker] = true;
function onEvent() {
flushPendingContinuousUpdates(root);
}
allNativeEvents.forEach(domEventName => {
const eventPriority = getEventPriority(domEventName);
if (eventPriority !== DiscreteEventPriority) {
return;
}
rootContainerElement.addEventListener(domEventName, onEvent, {
capture: true,
});
});
}
function dispatchDiscreteEvent(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
+3
View File
@@ -61,6 +61,7 @@ import {
unmarkContainerAsRoot,
} from 'react-dom-bindings/src/client/ReactDOMComponentTree';
import {listenToAllSupportedEvents} from 'react-dom-bindings/src/events/DOMPluginEventSystem';
import {listenToCapturePhaseDiscreteEvents} from 'react-dom-bindings/src/events/ReactDOMEventLIstener';
import {
ELEMENT_NODE,
COMMENT_NODE,
@@ -256,6 +257,7 @@ export function createRoot(
container.nodeType === COMMENT_NODE
? (container.parentNode: any)
: container;
listenToCapturePhaseDiscreteEvents(rootContainerElement, root);
listenToAllSupportedEvents(rootContainerElement);
// $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
@@ -344,6 +346,7 @@ export function hydrateRoot(
Dispatcher.current = ReactDOMClientDispatcher;
}
// This can't be a comment node since hydration doesn't work on comment nodes anyway.
listenToCapturePhaseDiscreteEvents(container, root);
listenToAllSupportedEvents(container);
if (mutableSources) {
@@ -1287,18 +1287,16 @@ describe('DOMPluginEventSystem', () => {
const buttonElement = buttonRef.current;
// Expect the click event to be able to get the latest state value set by mouse over events
await act(async () => {
buttonElement.dispatchEvent(
new MouseEvent('mouseover', {
bubbles: true,
capture: true,
cancelable: true,
relatedTarget: null,
clientX: 5,
}),
);
dispatchClickEvent(buttonElement);
});
buttonElement.dispatchEvent(
new MouseEvent('mouseover', {
bubbles: true,
capture: true,
cancelable: true,
relatedTarget: null,
clientX: 5,
}),
);
dispatchClickEvent(buttonElement);
expect(Scheduler).toHaveYielded(['Render:5', 'Click:5']);
});