From c2cebbf9ee54ee1a2e6c5408f3f003aa5125a23b Mon Sep 17 00:00:00 2001 From: Tianyu Yao Date: Fri, 20 Jan 2023 16:26:05 -0800 Subject: [PATCH] Attach new listeners to ReactDOMRoot --- .../src/events/DOMPluginEventSystem.js | 23 ++----------- .../src/events/ReactDOMEventListener.js | 34 +++++++++++++++++++ packages/react-dom/src/client/ReactDOMRoot.js | 3 ++ .../DOMPluginEventSystem-test.internal.js | 22 ++++++------ 4 files changed, 49 insertions(+), 33 deletions(-) diff --git a/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js b/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js index 8d7a783bd5..4011636dca 100644 --- a/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js +++ b/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js @@ -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); - } - } } } diff --git a/packages/react-dom-bindings/src/events/ReactDOMEventListener.js b/packages/react-dom-bindings/src/events/ReactDOMEventListener.js index 487420be00..f70fe79a03 100644 --- a/packages/react-dom-bindings/src/events/ReactDOMEventListener.js +++ b/packages/react-dom-bindings/src/events/ReactDOMEventListener.js @@ -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, diff --git a/packages/react-dom/src/client/ReactDOMRoot.js b/packages/react-dom/src/client/ReactDOMRoot.js index 12f62e2f1c..74d593e309 100644 --- a/packages/react-dom/src/client/ReactDOMRoot.js +++ b/packages/react-dom/src/client/ReactDOMRoot.js @@ -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) { diff --git a/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js b/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js index 9a4504e5c6..6f37e439ab 100644 --- a/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js +++ b/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js @@ -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']); });