Refactor DOM plugin system to single module (#18025)
Dominic Gannaway committed
Feb 14, 2020 at 08:10 UTC
9def56ec0e1e71928ee999f48c00b1803ed8772a
19 files changed
+470
-403
packages/legacy-events/EventPluginHub.js
deleted
-176
@@ -1,176 +0,0 @@
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
- * @flow
7
- */
8
-
9
-import invariant from 'shared/invariant';
10
-
11
-import {
12
- injectEventPluginOrder,
13
- injectEventPluginsByName,
14
- plugins,
15
-} from './EventPluginRegistry';
16
-import {getFiberCurrentPropsFromNode} from './EventPluginUtils';
17
-import accumulateInto from './accumulateInto';
18
-import {runEventsInBatch} from './EventBatching';
19
-
20
-import type {PluginModule} from './PluginModuleType';
21
-import type {ReactSyntheticEvent} from './ReactSyntheticEventType';
22
-import type {Fiber} from 'react-reconciler/src/ReactFiber';
23
-import type {AnyNativeEvent} from './PluginModuleType';
24
-import type {TopLevelType} from './TopLevelEventTypes';
25
-import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
26
-
27
-function isInteractive(tag) {
28
- return (
29
- tag === 'button' ||
30
- tag === 'input' ||
31
- tag === 'select' ||
32
- tag === 'textarea'
33
- );
34
-}
35
-
36
-function shouldPreventMouseEvent(name, type, props) {
37
- switch (name) {
38
- case 'onClick':
39
- case 'onClickCapture':
40
- case 'onDoubleClick':
41
- case 'onDoubleClickCapture':
42
- case 'onMouseDown':
43
- case 'onMouseDownCapture':
44
- case 'onMouseMove':
45
- case 'onMouseMoveCapture':
46
- case 'onMouseUp':
47
- case 'onMouseUpCapture':
48
- case 'onMouseEnter':
49
- return !!(props.disabled && isInteractive(type));
50
- default:
51
- return false;
52
- }
53
-}
54
-
55
-/**
56
- * This is a unified interface for event plugins to be installed and configured.
57
- *
58
- * Event plugins can implement the following properties:
59
- *
60
- * `extractEvents` {function(string, DOMEventTarget, string, object): *}
61
- * Required. When a top-level event is fired, this method is expected to
62
- * extract synthetic events that will in turn be queued and dispatched.
63
- *
64
- * `eventTypes` {object}
65
- * Optional, plugins that fire events must publish a mapping of registration
66
- * names that are used to register listeners. Values of this mapping must
67
- * be objects that contain `registrationName` or `phasedRegistrationNames`.
68
- *
69
- * `executeDispatch` {function(object, function, string)}
70
- * Optional, allows plugins to override how an event gets dispatched. By
71
- * default, the listener is simply invoked.
72
- *
73
- * Each plugin that is injected into `EventsPluginHub` is immediately operable.
74
- *
75
- * @public
76
- */
77
-
78
-/**
79
- * Methods for injecting dependencies.
80
- */
81
-export const injection = {
82
- /**
83
- * @param {array} InjectedEventPluginOrder
84
- * @public
85
- */
86
- injectEventPluginOrder,
87
-
88
- /**
89
- * @param {object} injectedNamesToPlugins Map from names to plugin modules.
90
- */
91
- injectEventPluginsByName,
92
-};
93
-
94
-/**
95
- * @param {object} inst The instance, which is the source of events.
96
- * @param {string} registrationName Name of listener (e.g. `onClick`).
97
- * @return {?function} The stored callback.
98
- */
99
-export function getListener(inst: Fiber, registrationName: string) {
100
- let listener;
101
-
102
- // TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
103
- // live here; needs to be moved to a better place soon
104
- const stateNode = inst.stateNode;
105
- if (!stateNode) {
106
- // Work in progress (ex: onload events in incremental mode).
107
- return null;
108
- }
109
- const props = getFiberCurrentPropsFromNode(stateNode);
110
- if (!props) {
111
- // Work in progress.
112
- return null;
113
- }
114
- listener = props[registrationName];
115
- if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
116
- return null;
117
- }
118
- invariant(
119
- !listener || typeof listener === 'function',
120
- 'Expected `%s` listener to be a function, instead got a value of `%s` type.',
121
- registrationName,
122
- typeof listener,
123
- );
124
- return listener;
125
-}
126
-
127
-/**
128
- * Allows registered plugins an opportunity to extract events from top-level
129
- * native browser events.
130
- *
131
- * @return {*} An accumulation of synthetic events.
132
- * @internal
133
- */
134
-function extractPluginEvents(
135
- topLevelType: TopLevelType,
136
- targetInst: null | Fiber,
137
- nativeEvent: AnyNativeEvent,
138
- nativeEventTarget: null | EventTarget,
139
- eventSystemFlags: EventSystemFlags,
140
-): Array<ReactSyntheticEvent> | ReactSyntheticEvent | null {
141
- let events = null;
142
- for (let i = 0; i < plugins.length; i++) {
143
- // Not every plugin in the ordering may be loaded at runtime.
144
- const possiblePlugin: PluginModule<AnyNativeEvent> = plugins[i];
145
- if (possiblePlugin) {
146
- const extractedEvents = possiblePlugin.extractEvents(
147
- topLevelType,
148
- targetInst,
149
- nativeEvent,
150
- nativeEventTarget,
151
- eventSystemFlags,
152
- );
153
- if (extractedEvents) {
154
- events = accumulateInto(events, extractedEvents);
155
- }
156
- }
157
- }
158
- return events;
159
-}
160
-
161
-export function runExtractedPluginEventsInBatch(
162
- topLevelType: TopLevelType,
163
- targetInst: null | Fiber,
164
- nativeEvent: AnyNativeEvent,
165
- nativeEventTarget: null | EventTarget,
166
- eventSystemFlags: EventSystemFlags,
167
-) {
168
- const events = extractPluginEvents(
169
- topLevelType,
170
- targetInst,
171
- nativeEvent,
172
- nativeEventTarget,
173
- eventSystemFlags,
174
- );
175
- runEventsInBatch(events);
176
-}
packages/legacy-events/EventPluginRegistry.js
+3
-7
@@ -89,7 +89,7 @@ function publishEventForPlugin(
89
): boolean {
90
invariant(
91
!eventNameDispatchConfigs.hasOwnProperty(eventName),
92
- 'EventPluginHub: More than one plugin attempted to publish the same ' +
92
+ 'EventPluginRegistry: More than one plugin attempted to publish the same ' +
93
'event name, `%s`.',
94
eventName,
95
);
@@ -133,7 +133,7 @@ function publishRegistrationName(
133
): void {
134
invariant(
135
!registrationNameModules[registrationName],
136
- 'EventPluginHub: More than one plugin attempted to publish the same ' +
136
+ 'EventPluginRegistry: More than one plugin attempted to publish the same ' +
137
'registration name, `%s`.',
138
registrationName,
139
);
@@ -153,8 +153,6 @@ function publishRegistrationName(
153
154
/**
155
* Registers plugins so that they can extract and dispatch events.
156
- *
157
- * @see {EventPluginHub}
156
*/
157
158
/**
@@ -193,7 +191,6 @@ export const possibleRegistrationNames = __DEV__ ? {} : (null: any);
191
*
192
* @param {array} InjectedEventPluginOrder
193
* @internal
196
- * @see {EventPluginHub.injection.injectEventPluginOrder}
194
*/
195
export function injectEventPluginOrder(
196
injectedEventPluginOrder: EventPluginOrder,
@@ -209,14 +206,13 @@ export function injectEventPluginOrder(
206
}
207
208
/**
212
- * Injects plugins to be used by `EventPluginHub`. The plugin names must be
209
+ * Injects plugins to be used by plugin event system. The plugin names must be
210
* in the ordering injected by `injectEventPluginOrder`.
211
*
212
* Plugins can be injected as part of page initialization or on-the-fly.
213
*
214
* @param {object} injectedNamesToPlugins Map from names to plugin modules.
215
* @internal
219
- * @see {EventPluginHub.injection.injectEventPluginsByName}
216
*/
217
export function injectEventPluginsByName(
218
injectedNamesToPlugins: NamesToPlugins,
packages/legacy-events/EventPropagators.js
+1
-1
@@ -11,7 +11,7 @@ import {
11
traverseEnterLeave,
12
} from 'shared/ReactTreeTraversal';
13
14
-import {getListener} from './EventPluginHub';
14
+import getListener from 'legacy-events/getListener';
15
import accumulateInto from './accumulateInto';
16
import forEachAccumulated from './forEachAccumulated';
17
packages/legacy-events/ResponderEventPlugin.js
+8
-8
@@ -282,7 +282,7 @@ to return true:wantsResponderID| |
282
+ + */
283
284
/**
285
- * A note about event ordering in the `EventPluginHub`.
285
+ * A note about event ordering in the `EventPluginRegistry`.
286
*
287
* Suppose plugins are injected in the following order:
288
*
@@ -301,7 +301,7 @@ to return true:wantsResponderID| |
301
* - When returned from `extractEvents`, deferred-dispatched events contain an
302
* "accumulation" of deferred dispatches.
303
* - These deferred dispatches are accumulated/collected before they are
304
- * returned, but processed at a later time by the `EventPluginHub` (hence the
304
+ * returned, but processed at a later time by the `EventPluginRegistry` (hence the
305
* name deferred).
306
*
307
* In the process of returning their deferred-dispatched events, event plugins
@@ -325,9 +325,9 @@ to return true:wantsResponderID| |
325
* - `R`s on-demand events (if any) (dispatched by `R` on-demand)
326
* - `S`s on-demand events (if any) (dispatched by `S` on-demand)
327
* - `C`s on-demand events (if any) (dispatched by `C` on-demand)
328
- * - `R`s extracted events (if any) (dispatched by `EventPluginHub`)
329
- * - `S`s extracted events (if any) (dispatched by `EventPluginHub`)
330
- * - `C`s extracted events (if any) (dispatched by `EventPluginHub`)
328
+ * - `R`s extracted events (if any) (dispatched by `EventPluginRegistry`)
329
+ * - `S`s extracted events (if any) (dispatched by `EventPluginRegistry`)
330
+ * - `C`s extracted events (if any) (dispatched by `EventPluginRegistry`)
331
*
332
* In the case of `ResponderEventPlugin`: If the `startShouldSetResponder`
333
* on-demand dispatch returns `true` (and some other details are satisfied) the
@@ -336,9 +336,9 @@ to return true:wantsResponderID| |
336
* will appear as follows:
337
*
338
* - `startShouldSetResponder` (`ResponderEventPlugin` dispatches on-demand)
339
- * - `touchStartCapture` (`EventPluginHub` dispatches as usual)
340
- * - `touchStart` (`EventPluginHub` dispatches as usual)
341
- * - `responderGrant/Reject` (`EventPluginHub` dispatches as usual)
339
+ * - `touchStartCapture` (`EventPluginRegistry` dispatches as usual)
340
+ * - `touchStart` (`EventPluginRegistry` dispatches as usual)
341
+ * - `responderGrant/Reject` (`EventPluginRegistry` dispatches as usual)
342
*/
343
344
function setResponderAndExtractTransfer(
packages/legacy-events/__tests__/EventPluginRegistry-test.internal.js
+1
-1
@@ -211,7 +211,7 @@ describe('EventPluginRegistry', () => {
211
expect(function() {
212
EventPluginRegistry.injectEventPluginOrder(['one', 'two']);
213
}).toThrowError(
214
- 'EventPluginHub: More than one plugin attempted to publish the same ' +
214
+ 'EventPluginRegistry: More than one plugin attempted to publish the same ' +
215
'registration name, `onPhotoCapture`.',
216
);
217
});
packages/legacy-events/getListener.js
new
+74
@@ -0,0 +1,74 @@
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
+ * @flow
7
+ */
8
+
9
+import type {Fiber} from 'react-reconciler/src/ReactFiber';
10
+
11
+import invariant from 'shared/invariant';
12
+
13
+import {getFiberCurrentPropsFromNode} from './EventPluginUtils';
14
+
15
+function isInteractive(tag) {
16
+ return (
17
+ tag === 'button' ||
18
+ tag === 'input' ||
19
+ tag === 'select' ||
20
+ tag === 'textarea'
21
+ );
22
+}
23
+
24
+function shouldPreventMouseEvent(name, type, props) {
25
+ switch (name) {
26
+ case 'onClick':
27
+ case 'onClickCapture':
28
+ case 'onDoubleClick':
29
+ case 'onDoubleClickCapture':
30
+ case 'onMouseDown':
31
+ case 'onMouseDownCapture':
32
+ case 'onMouseMove':
33
+ case 'onMouseMoveCapture':
34
+ case 'onMouseUp':
35
+ case 'onMouseUpCapture':
36
+ case 'onMouseEnter':
37
+ return !!(props.disabled && isInteractive(type));
38
+ default:
39
+ return false;
40
+ }
41
+}
42
+
43
+/**
44
+ * @param {object} inst The instance, which is the source of events.
45
+ * @param {string} registrationName Name of listener (e.g. `onClick`).
46
+ * @return {?function} The stored callback.
47
+ */
48
+export default function getListener(inst: Fiber, registrationName: string) {
49
+ let listener;
50
+
51
+ // TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
52
+ // live here; needs to be moved to a better place soon
53
+ const stateNode = inst.stateNode;
54
+ if (!stateNode) {
55
+ // Work in progress (ex: onload events in incremental mode).
56
+ return null;
57
+ }
58
+ const props = getFiberCurrentPropsFromNode(stateNode);
59
+ if (!props) {
60
+ // Work in progress.
61
+ return null;
62
+ }
63
+ listener = props[registrationName];
64
+ if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
65
+ return null;
66
+ }
67
+ invariant(
68
+ !listener || typeof listener === 'function',
69
+ 'Expected `%s` listener to be a function, instead got a value of `%s` type.',
70
+ registrationName,
71
+ typeof listener,
72
+ );
73
+ return listener;
74
+}
packages/react-dom/src/__tests__/InvalidEventListeners-test.js
renamed
+1
-1
@@ -11,7 +11,7 @@
11
12
jest.mock('../events/isEventSupported');
13
14
-describe('EventPluginHub', () => {
14
+describe('InvalidEventListeners', () => {
15
let React;
16
let ReactTestUtils;
17
packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.internal.js
+3
-3
@@ -9,7 +9,7 @@
9
10
'use strict';
11
12
-let EventPluginHub;
12
+let EventPluginGetListener;
13
let EventPluginRegistry;
14
let React;
15
let ReactDOM;
@@ -58,7 +58,7 @@ describe('ReactBrowserEventEmitter', () => {
58
LISTENER.mockClear();
59
60
// TODO: can we express this test with only public API?
61
- EventPluginHub = require('legacy-events/EventPluginHub');
61
+ EventPluginGetListener = require('legacy-events/getListener').default;
62
EventPluginRegistry = require('legacy-events/EventPluginRegistry');
63
React = require('react');
64
ReactDOM = require('react-dom');
@@ -100,7 +100,7 @@ describe('ReactBrowserEventEmitter', () => {
100
101
getListener = function(node, eventName) {
102
const inst = ReactDOMComponentTree.getInstanceFromNode(node);
103
- return EventPluginHub.getListener(inst, eventName);
103
+ return EventPluginGetListener(inst, eventName);
104
};
105
putListener = function(node, eventName, listener) {
106
switch (node) {
packages/react-dom/src/client/ReactDOM.js
+5
-3
@@ -45,9 +45,11 @@ import {
45
enqueueStateRestore,
46
restoreStateIfNeeded,
47
} from 'legacy-events/ReactControlledComponent';
48
-import {injection as EventPluginHubInjection} from 'legacy-events/EventPluginHub';
48
import {runEventsInBatch} from 'legacy-events/EventBatching';
50
-import {eventNameDispatchConfigs} from 'legacy-events/EventPluginRegistry';
49
+import {
50
+ eventNameDispatchConfigs,
51
+ injectEventPluginsByName,
52
+} from 'legacy-events/EventPluginRegistry';
53
import {
54
accumulateTwoPhaseDispatches,
55
accumulateDirectDispatches,
@@ -145,7 +147,7 @@ const ReactDOM: Object = {
147
getInstanceFromNode,
148
getNodeFromInstance,
149
getFiberCurrentPropsFromNode,
148
- EventPluginHubInjection.injectEventPluginsByName,
150
+ injectEventPluginsByName,
151
eventNameDispatchConfigs,
152
accumulateTwoPhaseDispatches,
153
accumulateDirectDispatches,
packages/react-dom/src/client/ReactDOMClientInjection.js
+23
-4
@@ -5,7 +5,6 @@
5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
-import {injection as EventPluginHubInjection} from 'legacy-events/EventPluginHub';
8
import {setComponentTree} from 'legacy-events/EventPluginUtils';
9
10
import {
@@ -15,15 +14,35 @@ import {
14
} from './ReactDOMComponentTree';
15
import BeforeInputEventPlugin from '../events/BeforeInputEventPlugin';
16
import ChangeEventPlugin from '../events/ChangeEventPlugin';
18
-import DOMEventPluginOrder from '../events/DOMEventPluginOrder';
17
import EnterLeaveEventPlugin from '../events/EnterLeaveEventPlugin';
18
import SelectEventPlugin from '../events/SelectEventPlugin';
19
import SimpleEventPlugin from '../events/SimpleEventPlugin';
20
+import {
21
+ injectEventPluginOrder,
22
+ injectEventPluginsByName,
23
+} from 'legacy-events/EventPluginRegistry';
24
+
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
+];
41
42
/**
43
* Inject modules for resolving DOM hierarchy and plugin ordering.
44
*/
26
-EventPluginHubInjection.injectEventPluginOrder(DOMEventPluginOrder);
45
+injectEventPluginOrder(DOMEventPluginOrder);
46
setComponentTree(
47
getFiberCurrentPropsFromNode,
48
getInstanceFromNode,
@@ -34,7 +53,7 @@ setComponentTree(
53
* Some important event plugins included by default (without having to require
54
* them).
55
*/
37
-EventPluginHubInjection.injectEventPluginsByName({
56
+injectEventPluginsByName({
57
SimpleEventPlugin: SimpleEventPlugin,
58
EnterLeaveEventPlugin: EnterLeaveEventPlugin,
59
ChangeEventPlugin: ChangeEventPlugin,
packages/react-dom/src/events/DOMEventPluginOrder.js
deleted
-26
@@ -1,26 +0,0 @@
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
-/**
9
- * Module that is injectable into `EventPluginHub`, that specifies a
10
- * deterministic ordering of `EventPlugin`s. A convenient way to reason about
11
- * plugins, without having to package every one of them. This is better than
12
- * having plugins be ordered in the same order that they are injected because
13
- * that ordering would be influenced by the packaging order.
14
- * `ResponderEventPlugin` must occur before `SimpleEventPlugin` so that
15
- * preventing default on events is convenient in `SimpleEventPlugin` handlers.
16
- */
17
-const DOMEventPluginOrder = [
18
- 'ResponderEventPlugin',
19
- 'SimpleEventPlugin',
20
- 'EnterLeaveEventPlugin',
21
- 'ChangeEventPlugin',
22
- 'SelectEventPlugin',
23
- 'BeforeInputEventPlugin',
24
-];
25
-
26
-export default DOMEventPluginOrder;
packages/react-dom/src/events/DOMEventPluginSystem.js
new
+215
@@ -0,0 +1,215 @@
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
+ * @flow
8
+ */
9
+
10
+import type {AnyNativeEvent} from 'legacy-events/PluginModuleType';
11
+import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes';
12
+import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
13
+import type {Fiber} from 'react-reconciler/src/ReactFiber';
14
+import type {PluginModule} from 'legacy-events/PluginModuleType';
15
+import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType';
16
+import type {TopLevelType} from 'legacy-events/TopLevelEventTypes';
17
+
18
+import {HostRoot, HostComponent, HostText} from 'shared/ReactWorkTags';
19
+import {IS_FIRST_ANCESTOR} from 'legacy-events/EventSystemFlags';
20
+import {batchedEventUpdates} from 'legacy-events/ReactGenericBatching';
21
+import {runEventsInBatch} from 'legacy-events/EventBatching';
22
+import {plugins} from 'legacy-events/EventPluginRegistry';
23
+import accumulateInto from 'legacy-events/accumulateInto';
24
+
25
+import getEventTarget from './getEventTarget';
26
+import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
27
+
28
+const CALLBACK_BOOKKEEPING_POOL_SIZE = 10;
29
+const callbackBookkeepingPool = [];
30
+
31
+type BookKeepingInstance = {|
32
+ topLevelType: DOMTopLevelEventType | null,
33
+ eventSystemFlags: EventSystemFlags,
34
+ nativeEvent: AnyNativeEvent | null,
35
+ targetInst: Fiber | null,
36
+ ancestors: Array<Fiber | null>,
37
+|};
38
+
39
+function releaseTopLevelCallbackBookKeeping(
40
+ instance: BookKeepingInstance,
41
+): void {
42
+ instance.topLevelType = null;
43
+ instance.nativeEvent = null;
44
+ instance.targetInst = null;
45
+ instance.ancestors.length = 0;
46
+ if (callbackBookkeepingPool.length < CALLBACK_BOOKKEEPING_POOL_SIZE) {
47
+ callbackBookkeepingPool.push(instance);
48
+ }
49
+}
50
+
51
+// Used to store ancestor hierarchy in top level callback
52
+function getTopLevelCallbackBookKeeping(
53
+ topLevelType: DOMTopLevelEventType,
54
+ nativeEvent: AnyNativeEvent,
55
+ targetInst: Fiber | null,
56
+ eventSystemFlags: EventSystemFlags,
57
+): BookKeepingInstance {
58
+ if (callbackBookkeepingPool.length) {
59
+ const instance = callbackBookkeepingPool.pop();
60
+ instance.topLevelType = topLevelType;
61
+ instance.eventSystemFlags = eventSystemFlags;
62
+ instance.nativeEvent = nativeEvent;
63
+ instance.targetInst = targetInst;
64
+ return instance;
65
+ }
66
+ return {
67
+ topLevelType,
68
+ eventSystemFlags,
69
+ nativeEvent,
70
+ targetInst,
71
+ ancestors: [],
72
+ };
73
+}
74
+
75
+/**
76
+ * Find the deepest React component completely containing the root of the
77
+ * passed-in instance (for use when entire React trees are nested within each
78
+ * other). If React trees are not nested, returns null.
79
+ */
80
+function findRootContainerNode(inst) {
81
+ if (inst.tag === HostRoot) {
82
+ return inst.stateNode.containerInfo;
83
+ }
84
+ // TODO: It may be a good idea to cache this to prevent unnecessary DOM
85
+ // traversal, but caching is difficult to do correctly without using a
86
+ // mutation observer to listen for all DOM changes.
87
+ while (inst.return) {
88
+ inst = inst.return;
89
+ }
90
+ if (inst.tag !== HostRoot) {
91
+ // This can happen if we're in a detached tree.
92
+ return null;
93
+ }
94
+ return inst.stateNode.containerInfo;
95
+}
96
+
97
+/**
98
+ * Allows registered plugins an opportunity to extract events from top-level
99
+ * native browser events.
100
+ *
101
+ * @return {*} An accumulation of synthetic events.
102
+ * @internal
103
+ */
104
+function extractPluginEvents(
105
+ topLevelType: TopLevelType,
106
+ targetInst: null | Fiber,
107
+ nativeEvent: AnyNativeEvent,
108
+ nativeEventTarget: null | EventTarget,
109
+ eventSystemFlags: EventSystemFlags,
110
+): Array<ReactSyntheticEvent> | ReactSyntheticEvent | null {
111
+ let events = null;
112
+ for (let i = 0; i < plugins.length; i++) {
113
+ // Not every plugin in the ordering may be loaded at runtime.
114
+ const possiblePlugin: PluginModule<AnyNativeEvent> = plugins[i];
115
+ if (possiblePlugin) {
116
+ const extractedEvents = possiblePlugin.extractEvents(
117
+ topLevelType,
118
+ targetInst,
119
+ nativeEvent,
120
+ nativeEventTarget,
121
+ eventSystemFlags,
122
+ );
123
+ if (extractedEvents) {
124
+ events = accumulateInto(events, extractedEvents);
125
+ }
126
+ }
127
+ }
128
+ return events;
129
+}
130
+
131
+function runExtractedPluginEventsInBatch(
132
+ topLevelType: TopLevelType,
133
+ targetInst: null | Fiber,
134
+ nativeEvent: AnyNativeEvent,
135
+ nativeEventTarget: null | EventTarget,
136
+ eventSystemFlags: EventSystemFlags,
137
+) {
138
+ const events = extractPluginEvents(
139
+ topLevelType,
140
+ targetInst,
141
+ nativeEvent,
142
+ nativeEventTarget,
143
+ eventSystemFlags,
144
+ );
145
+ runEventsInBatch(events);
146
+}
147
+
148
+function handleTopLevel(bookKeeping: BookKeepingInstance) {
149
+ let targetInst = bookKeeping.targetInst;
150
+
151
+ // Loop through the hierarchy, in case there's any nested components.
152
+ // It's important that we build the array of ancestors before calling any
153
+ // event handlers, because event handlers can modify the DOM, leading to
154
+ // inconsistencies with ReactMount's node cache. See #1105.
155
+ let ancestor = targetInst;
156
+ do {
157
+ if (!ancestor) {
158
+ const ancestors = bookKeeping.ancestors;
159
+ ((ancestors: any): Array<Fiber | null>).push(ancestor);
160
+ break;
161
+ }
162
+ const root = findRootContainerNode(ancestor);
163
+ if (!root) {
164
+ break;
165
+ }
166
+ const tag = ancestor.tag;
167
+ if (tag === HostComponent || tag === HostText) {
168
+ bookKeeping.ancestors.push(ancestor);
169
+ }
170
+ ancestor = getClosestInstanceFromNode(root);
171
+ } while (ancestor);
172
+
173
+ for (let i = 0; i < bookKeeping.ancestors.length; i++) {
174
+ targetInst = bookKeeping.ancestors[i];
175
+ const eventTarget = getEventTarget(bookKeeping.nativeEvent);
176
+ const topLevelType = ((bookKeeping.topLevelType: any): DOMTopLevelEventType);
177
+ const nativeEvent = ((bookKeeping.nativeEvent: any): AnyNativeEvent);
178
+ let eventSystemFlags = bookKeeping.eventSystemFlags;
179
+
180
+ // If this is the first ancestor, we mark it on the system flags
181
+ if (i === 0) {
182
+ eventSystemFlags |= IS_FIRST_ANCESTOR;
183
+ }
184
+
185
+ runExtractedPluginEventsInBatch(
186
+ topLevelType,
187
+ targetInst,
188
+ nativeEvent,
189
+ eventTarget,
190
+ eventSystemFlags,
191
+ );
192
+ }
193
+}
194
+
195
+export function dispatchEventForPluginEventSystem(
196
+ topLevelType: DOMTopLevelEventType,
197
+ eventSystemFlags: EventSystemFlags,
198
+ nativeEvent: AnyNativeEvent,
199
+ targetInst: null | Fiber,
200
+): void {
201
+ const bookKeeping = getTopLevelCallbackBookKeeping(
202
+ topLevelType,
203
+ nativeEvent,
204
+ targetInst,
205
+ eventSystemFlags,
206
+ );
207
+
208
+ try {
209
+ // Event queue being processed in the same cycle allows
210
+ // `preventDefault`.
211
+ batchedEventUpdates(handleTopLevel, bookKeeping);
212
+ } finally {
213
+ releaseTopLevelCallbackBookKeeping(bookKeeping);
214
+ }
215
+}
packages/react-dom/src/events/ReactBrowserEventEmitter.js
+4
-4
@@ -42,13 +42,13 @@ import isEventSupported from './isEventSupported';
42
* may be done in the worker thread.
43
*
44
* - Forward these native events (with the associated top-level type used to
45
- * trap it) to `EventPluginHub`, which in turn will ask plugins if they want
45
+ * trap it) to `EventPluginRegistry`, which in turn will ask plugins if they want
46
* to extract any synthetic events.
47
*
48
- * - The `EventPluginHub` will then process each event by annotating them with
48
+ * - The `EventPluginRegistry` will then process each event by annotating them with
49
* "dispatches", a sequence of listeners and IDs that care about that event.
50
*
51
- * - The `EventPluginHub` then dispatches the events.
51
+ * - The `EventPluginRegistry` then dispatches the events.
52
*
53
* Overview of React and the event system:
54
*
@@ -65,7 +65,7 @@ import isEventSupported from './isEventSupported';
65
* | . | |Plugin |
66
* +-----|------+ . v +-----------+
67
* | | | . +--------------+ +------------+
68
- * | +-----------.--->|EventPluginHub| | Event |
68
+ * | +-----------.--->|PluginRegistry| | Event |
69
* | | . | | +-----------+ | Propagators|
70
* | ReactEvent | . | | |TapEvent | |------------|
71
* | Emitter | . | |<---+|Plugin | |other plugin|
packages/react-dom/src/events/ReactDOMEventListener.js
+2
-148
@@ -8,7 +8,6 @@
8
*/
9
10
import type {AnyNativeEvent} from 'legacy-events/PluginModuleType';
11
-import type {Fiber} from 'react-reconciler/src/ReactFiber';
11
import type {FiberRoot} from 'react-reconciler/src/ReactFiberRoot';
12
import type {Container, SuspenseInstance} from '../client/ReactDOMHostConfig';
13
import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes';
@@ -18,11 +17,9 @@ import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes';
17
import * as Scheduler from 'scheduler';
18
19
import {
21
- batchedEventUpdates,
20
discreteUpdates,
21
flushDiscreteUpdatesIfNeeded,
22
} from 'legacy-events/ReactGenericBatching';
25
-import {runExtractedPluginEventsInBatch} from 'legacy-events/EventPluginHub';
23
import {DEPRECATED_dispatchEventForResponderEventSystem} from './DeprecatedDOMEventResponderSystem';
24
import {
25
isReplayableDiscreteEvent,
@@ -36,12 +33,7 @@ import {
33
getContainerFromFiber,
34
getSuspenseInstanceFromFiber,
35
} from 'react-reconciler/reflection';
39
-import {
40
- HostRoot,
41
- SuspenseComponent,
42
- HostComponent,
43
- HostText,
44
-} from 'shared/ReactWorkTags';
36
+import {HostRoot, SuspenseComponent} from 'shared/ReactWorkTags';
37
import {
38
type EventSystemFlags,
39
PLUGIN_EVENT_SYSTEM,
@@ -49,7 +41,6 @@ import {
41
IS_PASSIVE,
42
IS_ACTIVE,
43
PASSIVE_NOT_SUPPORTED,
52
- IS_FIRST_ANCESTOR,
44
} from 'legacy-events/EventSystemFlags';
45
46
import {
@@ -69,128 +60,13 @@ import {
60
DiscreteEvent,
61
} from 'shared/ReactTypes';
62
import {getEventPriorityForPluginSystem} from './DOMEventProperties';
63
+import {dispatchEventForPluginEventSystem} from './DOMEventPluginSystem';
64
65
const {
66
unstable_UserBlockingPriority: UserBlockingPriority,
67
unstable_runWithPriority: runWithPriority,
68
} = Scheduler;
69
78
-const CALLBACK_BOOKKEEPING_POOL_SIZE = 10;
79
-const callbackBookkeepingPool = [];
80
-
81
-type BookKeepingInstance = {|
82
- topLevelType: DOMTopLevelEventType | null,
83
- eventSystemFlags: EventSystemFlags,
84
- nativeEvent: AnyNativeEvent | null,
85
- targetInst: Fiber | null,
86
- ancestors: Array<Fiber | null>,
87
-|};
88
-
89
-/**
90
- * Find the deepest React component completely containing the root of the
91
- * passed-in instance (for use when entire React trees are nested within each
92
- * other). If React trees are not nested, returns null.
93
- */
94
-function findRootContainerNode(inst) {
95
- if (inst.tag === HostRoot) {
96
- return inst.stateNode.containerInfo;
97
- }
98
- // TODO: It may be a good idea to cache this to prevent unnecessary DOM
99
- // traversal, but caching is difficult to do correctly without using a
100
- // mutation observer to listen for all DOM changes.
101
- while (inst.return) {
102
- inst = inst.return;
103
- }
104
- if (inst.tag !== HostRoot) {
105
- // This can happen if we're in a detached tree.
106
- return null;
107
- }
108
- return inst.stateNode.containerInfo;
109
-}
110
-
111
-// Used to store ancestor hierarchy in top level callback
112
-function getTopLevelCallbackBookKeeping(
113
- topLevelType: DOMTopLevelEventType,
114
- nativeEvent: AnyNativeEvent,
115
- targetInst: Fiber | null,
116
- eventSystemFlags: EventSystemFlags,
117
-): BookKeepingInstance {
118
- if (callbackBookkeepingPool.length) {
119
- const instance = callbackBookkeepingPool.pop();
120
- instance.topLevelType = topLevelType;
121
- instance.eventSystemFlags = eventSystemFlags;
122
- instance.nativeEvent = nativeEvent;
123
- instance.targetInst = targetInst;
124
- return instance;
125
- }
126
- return {
127
- topLevelType,
128
- eventSystemFlags,
129
- nativeEvent,
130
- targetInst,
131
- ancestors: [],
132
- };
133
-}
134
-
135
-function releaseTopLevelCallbackBookKeeping(
136
- instance: BookKeepingInstance,
137
-): void {
138
- instance.topLevelType = null;
139
- instance.nativeEvent = null;
140
- instance.targetInst = null;
141
- instance.ancestors.length = 0;
142
- if (callbackBookkeepingPool.length < CALLBACK_BOOKKEEPING_POOL_SIZE) {
143
- callbackBookkeepingPool.push(instance);
144
- }
145
-}
146
-
147
-function handleTopLevel(bookKeeping: BookKeepingInstance) {
148
- let targetInst = bookKeeping.targetInst;
149
-
150
- // Loop through the hierarchy, in case there's any nested components.
151
- // It's important that we build the array of ancestors before calling any
152
- // event handlers, because event handlers can modify the DOM, leading to
153
- // inconsistencies with ReactMount's node cache. See #1105.
154
- let ancestor = targetInst;
155
- do {
156
- if (!ancestor) {
157
- const ancestors = bookKeeping.ancestors;
158
- ((ancestors: any): Array<Fiber | null>).push(ancestor);
159
- break;
160
- }
161
- const root = findRootContainerNode(ancestor);
162
- if (!root) {
163
- break;
164
- }
165
- const tag = ancestor.tag;
166
- if (tag === HostComponent || tag === HostText) {
167
- bookKeeping.ancestors.push(ancestor);
168
- }
169
- ancestor = getClosestInstanceFromNode(root);
170
- } while (ancestor);
171
-
172
- for (let i = 0; i < bookKeeping.ancestors.length; i++) {
173
- targetInst = bookKeeping.ancestors[i];
174
- const eventTarget = getEventTarget(bookKeeping.nativeEvent);
175
- const topLevelType = ((bookKeeping.topLevelType: any): DOMTopLevelEventType);
176
- const nativeEvent = ((bookKeeping.nativeEvent: any): AnyNativeEvent);
177
- let eventSystemFlags = bookKeeping.eventSystemFlags;
178
-
179
- // If this is the first ancestor, we mark it on the system flags
180
- if (i === 0) {
181
- eventSystemFlags |= IS_FIRST_ANCESTOR;
182
- }
183
-
184
- runExtractedPluginEventsInBatch(
185
- topLevelType,
186
- targetInst,
187
- nativeEvent,
188
- eventTarget,
189
- eventSystemFlags,
190
- );
191
- }
192
-}
193
-
70
// TODO: can we stop exporting these?
71
export let _enabled = true;
72
@@ -323,28 +199,6 @@ function dispatchUserBlockingUpdate(
199
);
200
}
201
326
-function dispatchEventForPluginEventSystem(
327
- topLevelType: DOMTopLevelEventType,
328
- eventSystemFlags: EventSystemFlags,
329
- nativeEvent: AnyNativeEvent,
330
- targetInst: null | Fiber,
331
-): void {
332
- const bookKeeping = getTopLevelCallbackBookKeeping(
333
- topLevelType,
334
- nativeEvent,
335
- targetInst,
336
- eventSystemFlags,
337
- );
338
-
339
- try {
340
- // Event queue being processed in the same cycle allows
341
- // `preventDefault`.
342
- batchedEventUpdates(handleTopLevel, bookKeeping);
343
- } finally {
344
- releaseTopLevelCallbackBookKeeping(bookKeeping);
345
- }
346
-}
347
-
202
export function dispatchEvent(
203
topLevelType: DOMTopLevelEventType,
204
eventSystemFlags: EventSystemFlags,
packages/react-native-renderer/src/ReactFabricEventEmitter.js
+60
-6
@@ -7,22 +7,76 @@
7
* @flow
8
*/
9
10
+import type {AnyNativeEvent} from 'legacy-events/PluginModuleType';
11
+import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
12
import type {Fiber} from 'react-reconciler/src/ReactFiber';
13
+import type {PluginModule} from 'legacy-events/PluginModuleType';
14
+import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType';
15
+import type {TopLevelType} from 'legacy-events/TopLevelEventTypes';
16
17
import {PLUGIN_EVENT_SYSTEM} from 'legacy-events/EventSystemFlags';
13
-import {
14
- getListener,
15
- runExtractedPluginEventsInBatch,
16
-} from 'legacy-events/EventPluginHub';
18
import {registrationNameModules} from 'legacy-events/EventPluginRegistry';
19
import {batchedUpdates} from 'legacy-events/ReactGenericBatching';
20
+import accumulateInto from 'legacy-events/accumulateInto';
21
20
-import type {AnyNativeEvent} from 'legacy-events/PluginModuleType';
22
import {enableNativeTargetAsInstance} from 'shared/ReactFeatureFlags';
22
-import type {TopLevelType} from 'legacy-events/TopLevelEventTypes';
23
+import {plugins} from 'legacy-events/EventPluginRegistry';
24
+import getListener from 'legacy-events/getListener';
25
+import {runEventsInBatch} from 'legacy-events/EventBatching';
26
27
export {getListener, registrationNameModules as registrationNames};
28
29
+/**
30
+ * Allows registered plugins an opportunity to extract events from top-level
31
+ * native browser events.
32
+ *
33
+ * @return {*} An accumulation of synthetic events.
34
+ * @internal
35
+ */
36
+function extractPluginEvents(
37
+ topLevelType: TopLevelType,
38
+ targetInst: null | Fiber,
39
+ nativeEvent: AnyNativeEvent,
40
+ nativeEventTarget: null | EventTarget,
41
+ eventSystemFlags: EventSystemFlags,
42
+): Array<ReactSyntheticEvent> | ReactSyntheticEvent | null {
43
+ let events = null;
44
+ for (let i = 0; i < plugins.length; i++) {
45
+ // Not every plugin in the ordering may be loaded at runtime.
46
+ const possiblePlugin: PluginModule<AnyNativeEvent> = plugins[i];
47
+ if (possiblePlugin) {
48
+ const extractedEvents = possiblePlugin.extractEvents(
49
+ topLevelType,
50
+ targetInst,
51
+ nativeEvent,
52
+ nativeEventTarget,
53
+ eventSystemFlags,
54
+ );
55
+ if (extractedEvents) {
56
+ events = accumulateInto(events, extractedEvents);
57
+ }
58
+ }
59
+ }
60
+ return events;
61
+}
62
+
63
+function runExtractedPluginEventsInBatch(
64
+ topLevelType: TopLevelType,
65
+ targetInst: null | Fiber,
66
+ nativeEvent: AnyNativeEvent,
67
+ nativeEventTarget: null | EventTarget,
68
+ eventSystemFlags: EventSystemFlags,
69
+) {
70
+ const events = extractPluginEvents(
71
+ topLevelType,
72
+ targetInst,
73
+ nativeEvent,
74
+ nativeEventTarget,
75
+ eventSystemFlags,
76
+ );
77
+ runEventsInBatch(events);
78
+}
79
+
80
export function dispatchEvent(
81
target: null | Object,
82
topLevelType: TopLevelType,
packages/react-native-renderer/src/ReactNativeBridgeEventPlugin.js
-3
@@ -28,9 +28,6 @@ const {
28
const ReactNativeBridgeEventPlugin = {
29
eventTypes: {},
30
31
- /**
32
- * @see {EventPluginHub.extractEvents}
33
- */
31
extractEvents: function(
32
topLevelType: TopLevelType,
33
targetInst: null | Object,
packages/react-native-renderer/src/ReactNativeEventEmitter.js
+62
-7
@@ -7,20 +7,24 @@
7
* @flow
8
*/
9
10
+import type {AnyNativeEvent} from 'legacy-events/PluginModuleType';
11
+import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
12
+import type {Fiber} from 'react-reconciler/src/ReactFiber';
13
+import type {PluginModule} from 'legacy-events/PluginModuleType';
14
+import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType';
15
+import type {TopLevelType} from 'legacy-events/TopLevelEventTypes';
16
+
17
import {PLUGIN_EVENT_SYSTEM} from 'legacy-events/EventSystemFlags';
11
-import {
12
- getListener,
13
- runExtractedPluginEventsInBatch,
14
-} from 'legacy-events/EventPluginHub';
18
import {registrationNameModules} from 'legacy-events/EventPluginRegistry';
19
import {batchedUpdates} from 'legacy-events/ReactGenericBatching';
20
+import {runEventsInBatch} from 'legacy-events/EventBatching';
21
import {enableNativeTargetAsInstance} from 'shared/ReactFeatureFlags';
22
+import {plugins} from 'legacy-events/EventPluginRegistry';
23
+import getListener from 'legacy-events/getListener';
24
+import accumulateInto from 'legacy-events/accumulateInto';
25
26
import {getInstanceFromNode} from './ReactNativeComponentTree';
27
21
-import type {AnyNativeEvent} from 'legacy-events/PluginModuleType';
22
-import type {TopLevelType} from 'legacy-events/TopLevelEventTypes';
23
-
28
export {getListener, registrationNameModules as registrationNames};
29
30
/**
@@ -121,6 +125,57 @@ function _receiveRootNodeIDEvent(
125
// where it would do it.
126
}
127
128
+/**
129
+ * Allows registered plugins an opportunity to extract events from top-level
130
+ * native browser events.
131
+ *
132
+ * @return {*} An accumulation of synthetic events.
133
+ * @internal
134
+ */
135
+function extractPluginEvents(
136
+ topLevelType: TopLevelType,
137
+ targetInst: null | Fiber,
138
+ nativeEvent: AnyNativeEvent,
139
+ nativeEventTarget: null | EventTarget,
140
+ eventSystemFlags: EventSystemFlags,
141
+): Array<ReactSyntheticEvent> | ReactSyntheticEvent | null {
142
+ let events = null;
143
+ for (let i = 0; i < plugins.length; i++) {
144
+ // Not every plugin in the ordering may be loaded at runtime.
145
+ const possiblePlugin: PluginModule<AnyNativeEvent> = plugins[i];
146
+ if (possiblePlugin) {
147
+ const extractedEvents = possiblePlugin.extractEvents(
148
+ topLevelType,
149
+ targetInst,
150
+ nativeEvent,
151
+ nativeEventTarget,
152
+ eventSystemFlags,
153
+ );
154
+ if (extractedEvents) {
155
+ events = accumulateInto(events, extractedEvents);
156
+ }
157
+ }
158
+ }
159
+ return events;
160
+}
161
+
162
+function runExtractedPluginEventsInBatch(
163
+ topLevelType: TopLevelType,
164
+ targetInst: null | Fiber,
165
+ nativeEvent: AnyNativeEvent,
166
+ nativeEventTarget: null | EventTarget,
167
+ eventSystemFlags: EventSystemFlags,
168
+) {
169
+ const events = extractPluginEvents(
170
+ topLevelType,
171
+ targetInst,
172
+ nativeEvent,
173
+ nativeEventTarget,
174
+ eventSystemFlags,
175
+ );
176
+ runEventsInBatch(events);
177
+}
178
+
179
/**
180
* Publicly exposed method on module for native objc to invoke when a top
181
* level event is extracted.
packages/react-native-renderer/src/ReactNativeInjectionShared.js
+6
-3
@@ -16,8 +16,11 @@
16
// Module provided by RN:
17
import 'react-native/Libraries/ReactPrivate/ReactNativePrivateInitializeCore';
18
19
-import {injection as EventPluginHubInjection} from 'legacy-events/EventPluginHub';
19
import ResponderEventPlugin from 'legacy-events/ResponderEventPlugin';
20
+import {
21
+ injectEventPluginOrder,
22
+ injectEventPluginsByName,
23
+} from 'legacy-events/EventPluginRegistry';
24
25
import ReactNativeBridgeEventPlugin from './ReactNativeBridgeEventPlugin';
26
import ReactNativeEventPluginOrder from './ReactNativeEventPluginOrder';
@@ -25,13 +28,13 @@ import ReactNativeEventPluginOrder from './ReactNativeEventPluginOrder';
28
/**
29
* Inject module for resolving DOM hierarchy and plugin ordering.
30
*/
28
-EventPluginHubInjection.injectEventPluginOrder(ReactNativeEventPluginOrder);
31
+injectEventPluginOrder(ReactNativeEventPluginOrder);
32
33
/**
34
* Some important event plugins included by default (without having to require
35
* them).
36
*/
34
-EventPluginHubInjection.injectEventPluginsByName({
37
+injectEventPluginsByName({
38
ResponderEventPlugin: ResponderEventPlugin,
39
ReactNativeBridgeEventPlugin: ReactNativeBridgeEventPlugin,
40
});
scripts/error-codes/codes.json
+2
-2
@@ -98,8 +98,8 @@
98
"96": "EventPluginRegistry: Cannot inject event plugins that do not exist in the plugin ordering, `%s`.",
99
"97": "EventPluginRegistry: Event plugins must implement an `extractEvents` method, but `%s` does not.",
100
"98": "EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.",
101
- "99": "EventPluginHub: More than one plugin attempted to publish the same event name, `%s`.",
102
- "100": "EventPluginHub: More than one plugin attempted to publish the same registration name, `%s`.",
101
+ "99": "EventPluginRegistry: More than one plugin attempted to publish the same event name, `%s`.",
102
+ "100": "EventPluginRegistry: More than one plugin attempted to publish the same registration name, `%s`.",
103
"101": "EventPluginRegistry: Cannot inject event plugin ordering more than once. You are likely trying to load more than one copy of React.",
104
"102": "EventPluginRegistry: Cannot inject two different event plugins using the same name, `%s`.",
105
"103": "executeDirectDispatch(...): Invalid `event`.",