@samitouri / QOS-React-2 / commits / a3875663f6

React Event System: cleanup plugins + break out update batching logic (#18503)

Dominic Gannaway committed Apr 6, 2020 at 22:01 UTC a3875663f6e5bb8bdc28c9cf71cf1312615c9c84
13 files changed +346 -129
packages/legacy-events/EventPluginRegistry.js
+17
@@ -241,3 +241,20 @@ export function injectEventPluginsByName(
241 recomputePluginOrdering();
242 }
243 }
244 +
245 +export function injectEventPlugins(
246 + eventPlugins: [PluginModule<AnyNativeEvent>],
247 +): void {
248 + for (let i = 0; i < eventPlugins.length; i++) {
249 + const pluginModule = eventPlugins[i];
250 + plugins.push(pluginModule);
251 + const publishedEvents = pluginModule.eventTypes;
252 + for (const eventName in publishedEvents) {
253 + publishEventForPlugin(
254 + publishedEvents[eventName],
255 + pluginModule,
256 + eventName,
257 + );
258 + }
259 + }
260 +}
packages/legacy-events/ReactGenericBatching.js
+1 -58
@@ -5,14 +5,6 @@
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 -import {
9 - needsStateRestore,
10 - restoreStateIfNeeded,
11 -} from './ReactControlledComponent';
12 -
13 -import {enableDeprecatedFlareAPI} from 'shared/ReactFeatureFlags';
14 -import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
15 -
8 // Used as a way to call batchedUpdates when we don't have a reference to
9 // the renderer. Such as when we're dispatching events or if third party
10 // libraries need to call batchedUpdates. Eventually, this API will go away when
@@ -32,21 +24,6 @@ let batchedEventUpdatesImpl = batchedUpdatesImpl;
24 let isInsideEventHandler = false;
25 let isBatchingEventUpdates = false;
26
35 -function finishEventHandler() {
36 - // Here we wait until all updates have propagated, which is important
37 - // when using controlled components within layers:
38 - // https://github.com/facebook/react/issues/1698
39 - // Then we restore state of any controlled component.
40 - const controlledComponentsHavePendingUpdates = needsStateRestore();
41 - if (controlledComponentsHavePendingUpdates) {
42 - // If a controlled event was fired, we may need to restore the state of
43 - // the DOM node back to the controlled value. This is necessary when React
44 - // bails out of the update without touching the DOM.
45 - flushDiscreteUpdatesImpl();
46 - restoreStateIfNeeded();
47 - }
48 -}
49 -
27 export function batchedUpdates(fn, bookkeeping) {
28 if (isInsideEventHandler) {
29 // If we are currently inside another batch, we need to wait until it
@@ -58,7 +35,6 @@ export function batchedUpdates(fn, bookkeeping) {
35 return batchedUpdatesImpl(fn, bookkeeping);
36 } finally {
37 isInsideEventHandler = false;
61 - finishEventHandler();
38 }
39 }
40
@@ -73,19 +49,6 @@ export function batchedEventUpdates(fn, a, b) {
49 return batchedEventUpdatesImpl(fn, a, b);
50 } finally {
51 isBatchingEventUpdates = false;
76 - finishEventHandler();
77 - }
78 -}
79 -
80 -// This is for the React Flare event system
81 -export function executeUserEventHandler(fn: any => void, value: any): void {
82 - const previouslyInEventHandler = isInsideEventHandler;
83 - try {
84 - isInsideEventHandler = true;
85 - const type = typeof value === 'object' && value !== null ? value.type : '';
86 - invokeGuardedCallbackAndCatchFirstError(type, fn, undefined, value);
87 - } finally {
88 - isInsideEventHandler = previouslyInEventHandler;
52 }
53 }
54
@@ -97,32 +60,12 @@ export function discreteUpdates(fn, a, b, c, d) {
60 } finally {
61 isInsideEventHandler = prevIsInsideEventHandler;
62 if (!isInsideEventHandler) {
100 - finishEventHandler();
63 }
64 }
65 }
66
105 -let lastFlushedEventTimeStamp = 0;
67 export function flushDiscreteUpdatesIfNeeded(timeStamp: number) {
107 - // event.timeStamp isn't overly reliable due to inconsistencies in
108 - // how different browsers have historically provided the time stamp.
109 - // Some browsers provide high-resolution time stamps for all events,
110 - // some provide low-resolution time stamps for all events. FF < 52
111 - // even mixes both time stamps together. Some browsers even report
112 - // negative time stamps or time stamps that are 0 (iOS9) in some cases.
113 - // Given we are only comparing two time stamps with equality (!==),
114 - // we are safe from the resolution differences. If the time stamp is 0
115 - // we bail-out of preventing the flush, which can affect semantics,
116 - // such as if an earlier flush removes or adds event listeners that
117 - // are fired in the subsequent flush. However, this is the same
118 - // behaviour as we had before this change, so the risks are low.
119 - if (
120 - !isInsideEventHandler &&
121 - (!enableDeprecatedFlareAPI ||
122 - timeStamp === 0 ||
123 - lastFlushedEventTimeStamp !== timeStamp)
124 - ) {
125 - lastFlushedEventTimeStamp = timeStamp;
68 + if (!isInsideEventHandler) {
69 flushDiscreteUpdatesImpl();
70 }
71 }
packages/react-dom/src/client/ReactDOM.js
+6 -8
@@ -39,13 +39,6 @@ import {
39 } from 'react-reconciler/src/ReactFiberReconciler';
40 import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';
41 import {canUseDOM} from 'shared/ExecutionEnvironment';
42 -import {setBatchingImplementation} from 'legacy-events/ReactGenericBatching';
43 -import {
44 - setRestoreImplementation,
45 - enqueueStateRestore,
46 - restoreStateIfNeeded,
47 -} from 'legacy-events/ReactControlledComponent';
48 -import {runEventsInBatch} from 'legacy-events/EventBatching';
42 import {
43 eventNameDispatchConfigs,
44 injectEventPluginsByName,
@@ -69,6 +62,12 @@ import {
62 setAttemptHydrationAtCurrentPriority,
63 queueExplicitHydrationTarget,
64 } from '../events/ReactDOMEventReplaying';
65 +import {setBatchingImplementation} from '../events/ReactDOMUpdateBatching';
66 +import {
67 + setRestoreImplementation,
68 + enqueueStateRestore,
69 + restoreStateIfNeeded,
70 +} from '../events/ReactDOMControlledComponent';
71
72 setAttemptSynchronousHydration(attemptSynchronousHydration);
73 setAttemptUserBlockingHydration(attemptUserBlockingHydration);
@@ -183,7 +182,6 @@ const Internals = {
182 enqueueStateRestore,
183 restoreStateIfNeeded,
184 dispatchEvent,
186 - runEventsInBatch,
185 flushPassiveEffects,
186 IsThisRendererActing,
187 ],
packages/react-dom/src/client/ReactDOMClientInjection.js
+48 -36
@@ -20,43 +20,55 @@ import SimpleEventPlugin from '../events/SimpleEventPlugin';
20 import {
21 injectEventPluginOrder,
22 injectEventPluginsByName,
23 + injectEventPlugins,
24 } from 'legacy-events/EventPluginRegistry';
25 +import {enableModernEventSystem} from 'shared/ReactFeatureFlags';
26
25 -/**
26 - * Specifies a deterministic ordering of `EventPlugin`s. A convenient way to
27 - * reason about plugins, without having to package every one of them. This
28 - * is better than having plugins be ordered in the same order that they
29 - * are injected because that ordering would be influenced by the packaging order.
30 - * `ResponderEventPlugin` must occur before `SimpleEventPlugin` so that
31 - * preventing default on events is convenient in `SimpleEventPlugin` handlers.
32 - */
33 -const DOMEventPluginOrder = [
34 - 'ResponderEventPlugin',
35 - 'SimpleEventPlugin',
36 - 'EnterLeaveEventPlugin',
37 - 'ChangeEventPlugin',
38 - 'SelectEventPlugin',
39 - 'BeforeInputEventPlugin',
40 -];
27 +if (enableModernEventSystem) {
28 + injectEventPlugins([
29 + SimpleEventPlugin,
30 + EnterLeaveEventPlugin,
31 + ChangeEventPlugin,
32 + SelectEventPlugin,
33 + BeforeInputEventPlugin,
34 + ]);
35 +} else {
36 + /**
37 + * Specifies a deterministic ordering of `EventPlugin`s. A convenient way to
38 + * reason about plugins, without having to package every one of them. This
39 + * is better than having plugins be ordered in the same order that they
40 + * are injected because that ordering would be influenced by the packaging order.
41 + * `ResponderEventPlugin` must occur before `SimpleEventPlugin` so that
42 + * preventing default on events is convenient in `SimpleEventPlugin` handlers.
43 + */
44 + const DOMEventPluginOrder = [
45 + 'ResponderEventPlugin',
46 + 'SimpleEventPlugin',
47 + 'EnterLeaveEventPlugin',
48 + 'ChangeEventPlugin',
49 + 'SelectEventPlugin',
50 + 'BeforeInputEventPlugin',
51 + ];
52
42 -/**
43 - * Inject modules for resolving DOM hierarchy and plugin ordering.
44 - */
45 -injectEventPluginOrder(DOMEventPluginOrder);
46 -setComponentTree(
47 - getFiberCurrentPropsFromNode,
48 - getInstanceFromNode,
49 - getNodeFromInstance,
50 -);
53 + /**
54 + * Inject modules for resolving DOM hierarchy and plugin ordering.
55 + */
56 + injectEventPluginOrder(DOMEventPluginOrder);
57 + setComponentTree(
58 + getFiberCurrentPropsFromNode,
59 + getInstanceFromNode,
60 + getNodeFromInstance,
61 + );
62
52 -/**
53 - * Some important event plugins included by default (without having to require
54 - * them).
55 - */
56 -injectEventPluginsByName({
57 - SimpleEventPlugin: SimpleEventPlugin,
58 - EnterLeaveEventPlugin: EnterLeaveEventPlugin,
59 - ChangeEventPlugin: ChangeEventPlugin,
60 - SelectEventPlugin: SelectEventPlugin,
61 - BeforeInputEventPlugin: BeforeInputEventPlugin,
62 -});
63 + /**
64 + * Some important event plugins included by default (without having to require
65 + * them).
66 + */
67 + injectEventPluginsByName({
68 + SimpleEventPlugin: SimpleEventPlugin,
69 + EnterLeaveEventPlugin: EnterLeaveEventPlugin,
70 + ChangeEventPlugin: ChangeEventPlugin,
71 + SelectEventPlugin: SelectEventPlugin,
72 + BeforeInputEventPlugin: BeforeInputEventPlugin,
73 + });
74 +}
packages/react-dom/src/events/ChangeEventPlugin.js
+12 -4
@@ -6,8 +6,6 @@
6 */
7
8 import {runEventsInBatch} from 'legacy-events/EventBatching';
9 -import {enqueueStateRestore} from 'legacy-events/ReactControlledComponent';
10 -import {batchedUpdates} from 'legacy-events/ReactGenericBatching';
9 import SyntheticEvent from 'legacy-events/SyntheticEvent';
10 import isTextInputElement from './isTextInputElement';
11 import {canUseDOM} from 'shared/ExecutionEnvironment';
@@ -27,9 +25,15 @@ import isEventSupported from './isEventSupported';
25 import {getNodeFromInstance} from '../client/ReactDOMComponentTree';
26 import {updateValueIfChanged} from '../client/inputValueTracking';
27 import {setDefaultValue} from '../client/ReactDOMInput';
28 +import {enqueueStateRestore} from './ReactDOMControlledComponent';
29
31 -import {disableInputAttributeSyncing} from 'shared/ReactFeatureFlags';
30 +import {
31 + disableInputAttributeSyncing,
32 + enableModernEventSystem,
33 +} from 'shared/ReactFeatureFlags';
34 import accumulateTwoPhaseListeners from './accumulateTwoPhaseListeners';
35 +import {batchedUpdates} from './ReactDOMUpdateBatching';
36 +import {dispatchEventsInBatch} from './DOMModernPluginEventSystem';
37
38 const eventTypes = {
39 change: {
@@ -101,7 +105,11 @@ function manualDispatchChangeEvent(nativeEvent) {
105 }
106
107 function runEventInBatch(event) {
104 - runEventsInBatch(event);
108 + if (enableModernEventSystem) {
109 + dispatchEventsInBatch([event]);
110 + } else {
111 + runEventsInBatch(event);
112 + }
113 }
114
115 function getInstIfValueChanged(targetInst) {
packages/react-dom/src/events/DOMLegacyEventPluginSystem.js
+1 -1
@@ -22,7 +22,6 @@ import {
22 HostText,
23 } from 'react-reconciler/src/ReactWorkTags';
24 import {IS_FIRST_ANCESTOR, PLUGIN_EVENT_SYSTEM} from './EventSystemFlags';
25 -import {batchedEventUpdates} from 'legacy-events/ReactGenericBatching';
25 import {runEventsInBatch} from 'legacy-events/EventBatching';
26 import {plugins} from 'legacy-events/EventPluginRegistry';
27 import accumulateInto from 'legacy-events/accumulateInto';
@@ -45,6 +44,7 @@ import {
44 mediaEventTypes,
45 } from './DOMTopLevelEventTypes';
46 import {addTrappedEventListener} from './ReactDOMEventListener';
47 +import {batchedEventUpdates} from './ReactDOMUpdateBatching';
48
49 /**
50 * Summary of `DOMEventPluginSystem` event handling:
packages/react-dom/src/events/DOMModernPluginEventSystem.js
+21 -10
@@ -24,7 +24,6 @@ import type {
24 import type {ReactDOMListener} from '../shared/ReactDOMTypes';
25
26 import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry';
27 -import {batchedEventUpdates} from 'legacy-events/ReactGenericBatching';
27 import {plugins} from 'legacy-events/EventPluginRegistry';
28 import {
29 PLUGIN_EVENT_SYSTEM,
@@ -86,12 +85,16 @@ import {
85 } from '../client/ReactDOMComponentTree';
86 import {COMMENT_NODE} from '../shared/HTMLNodeType';
87 import {topLevelEventsToDispatchConfig} from './DOMEventProperties';
88 +import {batchedEventUpdates} from './ReactDOMUpdateBatching';
89
90 import {
91 enableLegacyFBSupport,
92 enableUseEventAPI,
93 } from 'shared/ReactFeatureFlags';
94 -import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
94 +import {
95 + invokeGuardedCallbackAndCatchFirstError,
96 + rethrowCaughtError,
97 +} from 'shared/ReactErrorUtils';
98
99 const capturePhaseEvents = new Set([
100 TOP_FOCUS,
@@ -213,6 +216,21 @@ function executeDispatchesInOrder(event: ReactSyntheticEvent): void {
216 event._dispatchCurrentTargets = null;
217 }
218
219 +export function dispatchEventsInBatch(
220 + events: Array<ReactSyntheticEvent>,
221 +): void {
222 + for (let i = 0; i < events.length; i++) {
223 + const syntheticEvent = events[i];
224 + executeDispatchesInOrder(syntheticEvent);
225 + // Release the event from the pool if needed
226 + if (!syntheticEvent.isPersistent()) {
227 + syntheticEvent.constructor.release(syntheticEvent);
228 + }
229 + }
230 + // This would be a good time to rethrow if any of the event handlers threw.
231 + rethrowCaughtError();
232 +}
233 +
234 function dispatchEventsForPlugins(
235 topLevelType: DOMTopLevelEventType,
236 eventSystemFlags: EventSystemFlags,
@@ -244,14 +262,7 @@ function dispatchEventsForPlugins(
262 }
263 }
264 }
247 - for (let i = 0; i < syntheticEvents.length; i++) {
248 - const syntheticEvent = syntheticEvents[i];
249 - executeDispatchesInOrder(syntheticEvent);
250 - // Release the event from the pool if needed
251 - if (!syntheticEvent.isPersistent()) {
252 - syntheticEvent.constructor.release(syntheticEvent);
253 - }
254 - }
265 + dispatchEventsInBatch(syntheticEvents);
266 }
267
268 function shouldUpgradeListener(
packages/react-dom/src/events/DeprecatedDOMEventResponderSystem.js
+2 -2
@@ -30,13 +30,13 @@ import {
30 discreteUpdates,
31 flushDiscreteUpdatesIfNeeded,
32 executeUserEventHandler,
33 -} from 'legacy-events/ReactGenericBatching';
34 -import {enqueueStateRestore} from 'legacy-events/ReactControlledComponent';
33 +} from './ReactDOMUpdateBatching';
34 import type {Fiber} from 'react-reconciler/src/ReactFiber';
35 import {enableDeprecatedFlareAPI} from 'shared/ReactFeatureFlags';
36 import invariant from 'shared/invariant';
37
38 import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
39 +import {enqueueStateRestore} from './ReactDOMControlledComponent';
40 import {
41 ContinuousEvent,
42 UserBlockingEvent,
packages/react-dom/src/events/ReactDOMControlledComponent.js renamed
+3 -4
@@ -8,11 +8,10 @@
8 */
9
10 import invariant from 'shared/invariant';
11 -
11 import {
12 getInstanceFromNode,
13 getFiberCurrentPropsFromNode,
15 -} from './EventPluginUtils';
14 +} from '../client/ReactDOMComponentTree';
15
16 // Use to restore controlled state after a change event has fired.
17
@@ -20,7 +19,7 @@ let restoreImpl = null;
19 let restoreTarget = null;
20 let restoreQueue = null;
21
23 -function restoreStateOfTarget(target) {
22 +function restoreStateOfTarget(target: Node) {
23 // We perform this translation at the end of the event loop so that we
24 // always receive the correct fiber here
25 const internalInstance = getInstanceFromNode(target);
@@ -47,7 +46,7 @@ export function setRestoreImplementation(
46 restoreImpl = impl;
47 }
48
50 -export function enqueueStateRestore(target: EventTarget): void {
49 +export function enqueueStateRestore(target: Node): void {
50 if (restoreTarget) {
51 if (restoreQueue) {
52 restoreQueue.push(target);
packages/react-dom/src/events/ReactDOMEventListener.js
+4 -4
@@ -17,10 +17,6 @@ import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes';
17 // CommonJS interop named imports.
18 import * as Scheduler from 'scheduler';
19
20 -import {
21 - discreteUpdates,
22 - flushDiscreteUpdatesIfNeeded,
23 -} from 'legacy-events/ReactGenericBatching';
20 import {DEPRECATED_dispatchEventForResponderEventSystem} from './DeprecatedDOMEventResponderSystem';
21 import {
22 isReplayableDiscreteEvent,
@@ -70,6 +66,10 @@ import {
66 import {getEventPriorityForPluginSystem} from './DOMEventProperties';
67 import {dispatchEventForLegacyPluginEventSystem} from './DOMLegacyEventPluginSystem';
68 import {dispatchEventForPluginEventSystem} from './DOMModernPluginEventSystem';
69 +import {
70 + flushDiscreteUpdatesIfNeeded,
71 + discreteUpdates,
72 +} from './ReactDOMUpdateBatching';
73
74 const {
75 unstable_UserBlockingPriority: UserBlockingPriority,
packages/react-dom/src/events/ReactDOMUpdateBatching.js new
+140
@@ -0,0 +1,140 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +import {
9 + needsStateRestore,
10 + restoreStateIfNeeded,
11 +} from './ReactDOMControlledComponent';
12 +
13 +import {enableDeprecatedFlareAPI} from 'shared/ReactFeatureFlags';
14 +import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
15 +
16 +// Used as a way to call batchedUpdates when we don't have a reference to
17 +// the renderer. Such as when we're dispatching events or if third party
18 +// libraries need to call batchedUpdates. Eventually, this API will go away when
19 +// everything is batched by default. We'll then have a similar API to opt-out of
20 +// scheduled work and instead do synchronous work.
21 +
22 +// Defaults
23 +let batchedUpdatesImpl = function(fn, bookkeeping) {
24 + return fn(bookkeeping);
25 +};
26 +let discreteUpdatesImpl = function(fn, a, b, c, d) {
27 + return fn(a, b, c, d);
28 +};
29 +let flushDiscreteUpdatesImpl = function() {};
30 +let batchedEventUpdatesImpl = batchedUpdatesImpl;
31 +
32 +let isInsideEventHandler = false;
33 +let isBatchingEventUpdates = false;
34 +
35 +function finishEventHandler() {
36 + // Here we wait until all updates have propagated, which is important
37 + // when using controlled components within layers:
38 + // https://github.com/facebook/react/issues/1698
39 + // Then we restore state of any controlled component.
40 + const controlledComponentsHavePendingUpdates = needsStateRestore();
41 + if (controlledComponentsHavePendingUpdates) {
42 + // If a controlled event was fired, we may need to restore the state of
43 + // the DOM node back to the controlled value. This is necessary when React
44 + // bails out of the update without touching the DOM.
45 + flushDiscreteUpdatesImpl();
46 + restoreStateIfNeeded();
47 + }
48 +}
49 +
50 +export function batchedUpdates(fn, bookkeeping) {
51 + if (isInsideEventHandler) {
52 + // If we are currently inside another batch, we need to wait until it
53 + // fully completes before restoring state.
54 + return fn(bookkeeping);
55 + }
56 + isInsideEventHandler = true;
57 + try {
58 + return batchedUpdatesImpl(fn, bookkeeping);
59 + } finally {
60 + isInsideEventHandler = false;
61 + finishEventHandler();
62 + }
63 +}
64 +
65 +export function batchedEventUpdates(fn, a, b) {
66 + if (isBatchingEventUpdates) {
67 + // If we are currently inside another batch, we need to wait until it
68 + // fully completes before restoring state.
69 + return fn(a, b);
70 + }
71 + isBatchingEventUpdates = true;
72 + try {
73 + return batchedEventUpdatesImpl(fn, a, b);
74 + } finally {
75 + isBatchingEventUpdates = false;
76 + finishEventHandler();
77 + }
78 +}
79 +
80 +// This is for the React Flare event system
81 +export function executeUserEventHandler(fn: any => void, value: any): void {
82 + const previouslyInEventHandler = isInsideEventHandler;
83 + try {
84 + isInsideEventHandler = true;
85 + const type = typeof value === 'object' && value !== null ? value.type : '';
86 + invokeGuardedCallbackAndCatchFirstError(type, fn, undefined, value);
87 + } finally {
88 + isInsideEventHandler = previouslyInEventHandler;
89 + }
90 +}
91 +
92 +export function discreteUpdates(fn, a, b, c, d) {
93 + const prevIsInsideEventHandler = isInsideEventHandler;
94 + isInsideEventHandler = true;
95 + try {
96 + return discreteUpdatesImpl(fn, a, b, c, d);
97 + } finally {
98 + isInsideEventHandler = prevIsInsideEventHandler;
99 + if (!isInsideEventHandler) {
100 + finishEventHandler();
101 + }
102 + }
103 +}
104 +
105 +let lastFlushedEventTimeStamp = 0;
106 +export function flushDiscreteUpdatesIfNeeded(timeStamp: number) {
107 + // event.timeStamp isn't overly reliable due to inconsistencies in
108 + // how different browsers have historically provided the time stamp.
109 + // Some browsers provide high-resolution time stamps for all events,
110 + // some provide low-resolution time stamps for all events. FF < 52
111 + // even mixes both time stamps together. Some browsers even report
112 + // negative time stamps or time stamps that are 0 (iOS9) in some cases.
113 + // Given we are only comparing two time stamps with equality (!==),
114 + // we are safe from the resolution differences. If the time stamp is 0
115 + // we bail-out of preventing the flush, which can affect semantics,
116 + // such as if an earlier flush removes or adds event listeners that
117 + // are fired in the subsequent flush. However, this is the same
118 + // behaviour as we had before this change, so the risks are low.
119 + if (
120 + !isInsideEventHandler &&
121 + (!enableDeprecatedFlareAPI ||
122 + timeStamp === 0 ||
123 + lastFlushedEventTimeStamp !== timeStamp)
124 + ) {
125 + lastFlushedEventTimeStamp = timeStamp;
126 + flushDiscreteUpdatesImpl();
127 + }
128 +}
129 +
130 +export function setBatchingImplementation(
131 + _batchedUpdatesImpl,
132 + _discreteUpdatesImpl,
133 + _flushDiscreteUpdatesImpl,
134 + _batchedEventUpdatesImpl,
135 +) {
136 + batchedUpdatesImpl = _batchedUpdatesImpl;
137 + discreteUpdatesImpl = _discreteUpdatesImpl;
138 + flushDiscreteUpdatesImpl = _flushDiscreteUpdatesImpl;
139 + batchedEventUpdatesImpl = _batchedEventUpdatesImpl;
140 +}
packages/react-dom/src/test-utils/ReactTestUtils.js
+91 -1
@@ -23,6 +23,10 @@ import act from './ReactTestUtilsAct';
23 import forEachAccumulated from 'legacy-events/forEachAccumulated';
24 import accumulateInto from 'legacy-events/accumulateInto';
25 import {enableModernEventSystem} from 'shared/ReactFeatureFlags';
26 +import {
27 + rethrowCaughtError,
28 + invokeGuardedCallbackAndCatchFirstError,
29 +} from 'shared/ReactErrorUtils';
30
31 const {findDOMNode} = ReactDOM;
32 // Keep in sync with ReactDOMUnstableNativeDependencies.js
@@ -38,7 +42,6 @@ const [
42 enqueueStateRestore,
43 restoreStateIfNeeded,
44 dispatchEvent,
41 - runEventsInBatch,
45 /* eslint-disable no-unused-vars */
46 flushPassiveEffects,
47 IsThisRendererActing,
@@ -354,6 +357,93 @@ function nativeTouchData(x, y) {
357 // Start of inline: the below functions were inlined from
358 // EventPropagator.js, as they deviated from ReactDOM's newer
359 // implementations.
360 +
361 +/**
362 + * Dispatch the event to the listener.
363 + * @param {SyntheticEvent} event SyntheticEvent to handle
364 + * @param {function} listener Application-level callback
365 + * @param {*} inst Internal component instance
366 + */
367 +function executeDispatch(event, listener, inst) {
368 + const type = event.type || 'unknown-event';
369 + event.currentTarget = getNodeFromInstance(inst);
370 + invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
371 + event.currentTarget = null;
372 +}
373 +
374 +/**
375 + * Standard/simple iteration through an event's collected dispatches.
376 + */
377 +function executeDispatchesInOrder(event) {
378 + const dispatchListeners = event._dispatchListeners;
379 + const dispatchInstances = event._dispatchInstances;
380 + if (Array.isArray(dispatchListeners)) {
381 + for (let i = 0; i < dispatchListeners.length; i++) {
382 + if (event.isPropagationStopped()) {
383 + break;
384 + }
385 + // Listeners and Instances are two parallel arrays that are always in sync.
386 + executeDispatch(event, dispatchListeners[i], dispatchInstances[i]);
387 + }
388 + } else if (dispatchListeners) {
389 + executeDispatch(event, dispatchListeners, dispatchInstances);
390 + }
391 + event._dispatchListeners = null;
392 + event._dispatchInstances = null;
393 +}
394 +
395 +/**
396 + * Internal queue of events that have accumulated their dispatches and are
397 + * waiting to have their dispatches executed.
398 + */
399 +let eventQueue: ?(Array<ReactSyntheticEvent> | ReactSyntheticEvent) = null;
400 +
401 +/**
402 + * Dispatches an event and releases it back into the pool, unless persistent.
403 + *
404 + * @param {?object} event Synthetic event to be dispatched.
405 + * @private
406 + */
407 +const executeDispatchesAndRelease = function(event: ReactSyntheticEvent) {
408 + if (event) {
409 + executeDispatchesInOrder(event);
410 +
411 + if (!event.isPersistent()) {
412 + event.constructor.release(event);
413 + }
414 + }
415 +};
416 +
417 +const executeDispatchesAndReleaseTopLevel = function(e) {
418 + return executeDispatchesAndRelease(e);
419 +};
420 +
421 +function runEventsInBatch(
422 + events: Array<ReactSyntheticEvent> | ReactSyntheticEvent | null,
423 +) {
424 + if (events !== null) {
425 + eventQueue = accumulateInto(eventQueue, events);
426 + }
427 +
428 + // Set `eventQueue` to null before processing it so that we can tell if more
429 + // events get enqueued while processing.
430 + const processingEventQueue = eventQueue;
431 + eventQueue = null;
432 +
433 + if (!processingEventQueue) {
434 + return;
435 + }
436 +
437 + forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);
438 + invariant(
439 + !eventQueue,
440 + 'processEventQueue(): Additional events were enqueued while processing ' +
441 + 'an event queue. Support for this has not yet been implemented.',
442 + );
443 + // This would be a good time to rethrow if any of the event handlers threw.
444 + rethrowCaughtError();
445 +}
446 +
447 function isInteractive(tag) {
448 return (
449 tag === 'button' ||
packages/react-dom/src/test-utils/ReactTestUtilsAct.js
-1
@@ -26,7 +26,6 @@ const [
26 enqueueStateRestore,
27 restoreStateIfNeeded,
28 dispatchEvent,
29 - runEventsInBatch,
29 /* eslint-enable no-unused-vars */
30 flushPassiveEffects,
31 IsThisRendererActing,