main
js 265 lines 7.42 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and 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 {DispatchConfig} from './ReactSyntheticEventType';
11 import type {
12 AnyNativeEvent,
13 PluginName,
14 LegacyPluginModule,
15 } from './PluginModuleType';
16 import type {TopLevelType} from './TopLevelEventTypes';
17
18 type NamesToPlugins = {
19 [key: PluginName]: LegacyPluginModule<AnyNativeEvent>,
20 };
21 type EventPluginOrder = null | Array<PluginName>;
22
23 /**
24 * Injectable ordering of event plugins.
25 */
26 let eventPluginOrder: EventPluginOrder = null;
27
28 /**
29 * Injectable mapping from names to event plugin modules.
30 */
31 const namesToPlugins: NamesToPlugins = {};
32
33 /**
34 * Recomputes the plugin list using the injected plugins and plugin ordering.
35 *
36 * @private
37 */
38 function recomputePluginOrdering(): void {
39 if (!eventPluginOrder) {
40 // Wait until an `eventPluginOrder` is injected.
41 return;
42 }
43 for (const pluginName in namesToPlugins) {
44 const pluginModule = namesToPlugins[pluginName];
45 // $FlowFixMe[incompatible-use] found when upgrading Flow
46 const pluginIndex = eventPluginOrder.indexOf(pluginName);
47
48 if (pluginIndex <= -1) {
49 throw new Error(
50 'EventPluginRegistry: Cannot inject event plugins that do not exist in ' +
51 `the plugin ordering, \`${pluginName}\`.`,
52 );
53 }
54
55 if (plugins[pluginIndex]) {
56 continue;
57 }
58
59 if (!pluginModule.extractEvents) {
60 throw new Error(
61 'EventPluginRegistry: Event plugins must implement an `extractEvents` ' +
62 `method, but \`${pluginName}\` does not.`,
63 );
64 }
65
66 plugins[pluginIndex] = pluginModule;
67 const publishedEvents = pluginModule.eventTypes;
68 for (const eventName in publishedEvents) {
69 if (
70 !publishEventForPlugin(
71 publishedEvents[eventName],
72 pluginModule,
73 eventName,
74 )
75 ) {
76 throw new Error(
77 `EventPluginRegistry: Failed to publish event \`${eventName}\` for plugin \`${pluginName}\`.`,
78 );
79 }
80 }
81 }
82 }
83
84 /**
85 * Publishes an event so that it can be dispatched by the supplied plugin.
86 *
87 * @param {object} dispatchConfig Dispatch configuration for the event.
88 * @param {object} PluginModule Plugin publishing the event.
89 * @return {boolean} True if the event was successfully published.
90 * @private
91 */
92 function publishEventForPlugin(
93 dispatchConfig: DispatchConfig,
94 pluginModule: LegacyPluginModule<AnyNativeEvent>,
95 eventName: string,
96 ): boolean {
97 if (eventNameDispatchConfigs.hasOwnProperty(eventName)) {
98 throw new Error(
99 'EventPluginRegistry: More than one plugin attempted to publish the same ' +
100 `event name, \`${eventName}\`.`,
101 );
102 }
103
104 eventNameDispatchConfigs[eventName] = dispatchConfig;
105
106 const phasedRegistrationNames = dispatchConfig.phasedRegistrationNames;
107 if (phasedRegistrationNames) {
108 for (const phaseName in phasedRegistrationNames) {
109 if (phasedRegistrationNames.hasOwnProperty(phaseName)) {
110 // $FlowFixMe[invalid-computed-prop]
111 const phasedRegistrationName = phasedRegistrationNames[phaseName];
112 publishRegistrationName(
113 phasedRegistrationName,
114 pluginModule,
115 eventName,
116 );
117 }
118 }
119 return true;
120 } else if (dispatchConfig.registrationName) {
121 publishRegistrationName(
122 dispatchConfig.registrationName,
123 pluginModule,
124 eventName,
125 );
126 return true;
127 }
128 return false;
129 }
130
131 /**
132 * Publishes a registration name that is used to identify dispatched events.
133 *
134 * @param {string} registrationName Registration name to add.
135 * @param {object} PluginModule Plugin publishing the event.
136 * @private
137 */
138 function publishRegistrationName(
139 registrationName: string,
140 pluginModule: LegacyPluginModule<AnyNativeEvent>,
141 eventName: string,
142 ): void {
143 if (registrationNameModules[registrationName]) {
144 throw new Error(
145 'EventPluginRegistry: More than one plugin attempted to publish the same ' +
146 `registration name, \`${registrationName}\`.`,
147 );
148 }
149
150 registrationNameModules[registrationName] = pluginModule;
151 registrationNameDependencies[registrationName] =
152 pluginModule.eventTypes[eventName].dependencies;
153
154 if (__DEV__) {
155 const lowerCasedName = registrationName.toLowerCase();
156 possibleRegistrationNames[lowerCasedName] = registrationName;
157
158 if (registrationName === 'onDoubleClick') {
159 possibleRegistrationNames.ondblclick = registrationName;
160 }
161 }
162 }
163
164 /**
165 * Registers plugins so that they can extract and dispatch events.
166 */
167
168 /**
169 * Ordered list of injected plugins.
170 */
171 export const plugins: Array<LegacyPluginModule<AnyNativeEvent>> = [];
172
173 /**
174 * Mapping from event name to dispatch config
175 */
176 export const eventNameDispatchConfigs: {
177 [eventName: string]: DispatchConfig,
178 } = {};
179
180 /**
181 * Mapping from registration name to plugin module
182 */
183 export const registrationNameModules: {
184 [registrationName: string]: LegacyPluginModule<AnyNativeEvent>,
185 } = {};
186
187 /**
188 * Mapping from registration name to event name
189 */
190 export const registrationNameDependencies: {
191 [registrationName: string]: Array<TopLevelType> | void,
192 } = {};
193
194 /**
195 * Mapping from lowercase registration names to the properly cased version,
196 * used to warn in the case of missing event handlers. Available
197 * only in __DEV__.
198 * @type {Object}
199 */
200 export const possibleRegistrationNames: {
201 [lowerCasedName: string]: string,
202 } = __DEV__ ? {} : (null as any);
203 // Trust the developer to only use possibleRegistrationNames in __DEV__
204
205 /**
206 * Injects an ordering of plugins (by plugin name). This allows the ordering
207 * to be decoupled from injection of the actual plugins so that ordering is
208 * always deterministic regardless of packaging, on-the-fly injection, etc.
209 *
210 * @param {array} InjectedEventPluginOrder
211 * @internal
212 */
213 export function injectEventPluginOrder(
214 injectedEventPluginOrder: EventPluginOrder,
215 ): void {
216 if (eventPluginOrder) {
217 throw new Error(
218 'EventPluginRegistry: Cannot inject event plugin ordering more than ' +
219 'once. You are likely trying to load more than one copy of React.',
220 );
221 }
222
223 // Clone the ordering so it cannot be dynamically mutated.
224 // $FlowFixMe[method-unbinding] found when upgrading Flow
225 eventPluginOrder = Array.prototype.slice.call(injectedEventPluginOrder);
226 recomputePluginOrdering();
227 }
228
229 /**
230 * Injects plugins to be used by plugin event system. The plugin names must be
231 * in the ordering injected by `injectEventPluginOrder`.
232 *
233 * Plugins can be injected as part of page initialization or on-the-fly.
234 *
235 * @param {object} injectedNamesToPlugins Map from names to plugin modules.
236 * @internal
237 */
238 export function injectEventPluginsByName(
239 injectedNamesToPlugins: NamesToPlugins,
240 ): void {
241 let isOrderingDirty = false;
242 for (const pluginName in injectedNamesToPlugins) {
243 if (!injectedNamesToPlugins.hasOwnProperty(pluginName)) {
244 continue;
245 }
246 const pluginModule = injectedNamesToPlugins[pluginName];
247 if (
248 !namesToPlugins.hasOwnProperty(pluginName) ||
249 namesToPlugins[pluginName] !== pluginModule
250 ) {
251 if (namesToPlugins[pluginName]) {
252 throw new Error(
253 'EventPluginRegistry: Cannot inject two different event plugins ' +
254 `using the same name, \`${pluginName}\`.`,
255 );
256 }
257
258 namesToPlugins[pluginName] = pluginModule;
259 isOrderingDirty = true;
260 }
261 }
262 if (isOrderingDirty) {
263 recomputePluginOrdering();
264 }
265 }