@samitouri / QOS-React-2 / commits / 9def56ec0e

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`.",