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.         const phasedRegistrationName = phasedRegistrationNames[phaseName];
    
  111.         publishRegistrationName(
    
  112.           phasedRegistrationName,
    
  113.           pluginModule,
    
  114.           eventName,
    
  115.         );
    
  116.       }
    
  117.     }
    
  118.     return true;
    
  119.   } else if (dispatchConfig.registrationName) {
    
  120.     publishRegistrationName(
    
  121.       dispatchConfig.registrationName,
    
  122.       pluginModule,
    
  123.       eventName,
    
  124.     );
    
  125.     return true;
    
  126.   }
    
  127.   return false;
    
  128. }
    
  129. 
    
  130. /**
    
  131.  * Publishes a registration name that is used to identify dispatched events.
    
  132.  *
    
  133.  * @param {string} registrationName Registration name to add.
    
  134.  * @param {object} PluginModule Plugin publishing the event.
    
  135.  * @private
    
  136.  */
    
  137. function publishRegistrationName(
    
  138.   registrationName: string,
    
  139.   pluginModule: LegacyPluginModule<AnyNativeEvent>,
    
  140.   eventName: string,
    
  141. ): void {
    
  142.   if (registrationNameModules[registrationName]) {
    
  143.     throw new Error(
    
  144.       'EventPluginRegistry: More than one plugin attempted to publish the same ' +
    
  145.         `registration name, \`${registrationName}\`.`,
    
  146.     );
    
  147.   }
    
  148. 
    
  149.   registrationNameModules[registrationName] = pluginModule;
    
  150.   registrationNameDependencies[registrationName] =
    
  151.     pluginModule.eventTypes[eventName].dependencies;
    
  152. 
    
  153.   if (__DEV__) {
    
  154.     const lowerCasedName = registrationName.toLowerCase();
    
  155.     possibleRegistrationNames[lowerCasedName] = registrationName;
    
  156. 
    
  157.     if (registrationName === 'onDoubleClick') {
    
  158.       possibleRegistrationNames.ondblclick = registrationName;
    
  159.     }
    
  160.   }
    
  161. }
    
  162. 
    
  163. /**
    
  164.  * Registers plugins so that they can extract and dispatch events.
    
  165.  */
    
  166. 
    
  167. /**
    
  168.  * Ordered list of injected plugins.
    
  169.  */
    
  170. export const plugins: Array<LegacyPluginModule<AnyNativeEvent>> = [];
    
  171. 
    
  172. /**
    
  173.  * Mapping from event name to dispatch config
    
  174.  */
    
  175. export const eventNameDispatchConfigs: {
    
  176.   [eventName: string]: DispatchConfig,
    
  177. } = {};
    
  178. 
    
  179. /**
    
  180.  * Mapping from registration name to plugin module
    
  181.  */
    
  182. export const registrationNameModules: {
    
  183.   [registrationName: string]: LegacyPluginModule<AnyNativeEvent>,
    
  184. } = {};
    
  185. 
    
  186. /**
    
  187.  * Mapping from registration name to event name
    
  188.  */
    
  189. export const registrationNameDependencies: {
    
  190.   [registrationName: string]: Array<TopLevelType> | void,
    
  191. } = {};
    
  192. 
    
  193. /**
    
  194.  * Mapping from lowercase registration names to the properly cased version,
    
  195.  * used to warn in the case of missing event handlers. Available
    
  196.  * only in __DEV__.
    
  197.  * @type {Object}
    
  198.  */
    
  199. export const possibleRegistrationNames: {
    
  200.   [lowerCasedName: string]: string,
    
  201. } = __DEV__ ? {} : (null: any);
    
  202. // Trust the developer to only use possibleRegistrationNames in __DEV__
    
  203. 
    
  204. /**
    
  205.  * Injects an ordering of plugins (by plugin name). This allows the ordering
    
  206.  * to be decoupled from injection of the actual plugins so that ordering is
    
  207.  * always deterministic regardless of packaging, on-the-fly injection, etc.
    
  208.  *
    
  209.  * @param {array} InjectedEventPluginOrder
    
  210.  * @internal
    
  211.  */
    
  212. export function injectEventPluginOrder(
    
  213.   injectedEventPluginOrder: EventPluginOrder,
    
  214. ): void {
    
  215.   if (eventPluginOrder) {
    
  216.     throw new Error(
    
  217.       'EventPluginRegistry: Cannot inject event plugin ordering more than ' +
    
  218.         'once. You are likely trying to load more than one copy of React.',
    
  219.     );
    
  220.   }
    
  221. 
    
  222.   // Clone the ordering so it cannot be dynamically mutated.
    
  223.   // $FlowFixMe[method-unbinding] found when upgrading Flow
    
  224.   eventPluginOrder = Array.prototype.slice.call(injectedEventPluginOrder);
    
  225.   recomputePluginOrdering();
    
  226. }
    
  227. 
    
  228. /**
    
  229.  * Injects plugins to be used by plugin event system. The plugin names must be
    
  230.  * in the ordering injected by `injectEventPluginOrder`.
    
  231.  *
    
  232.  * Plugins can be injected as part of page initialization or on-the-fly.
    
  233.  *
    
  234.  * @param {object} injectedNamesToPlugins Map from names to plugin modules.
    
  235.  * @internal
    
  236.  */
    
  237. export function injectEventPluginsByName(
    
  238.   injectedNamesToPlugins: NamesToPlugins,
    
  239. ): void {
    
  240.   let isOrderingDirty = false;
    
  241.   for (const pluginName in injectedNamesToPlugins) {
    
  242.     if (!injectedNamesToPlugins.hasOwnProperty(pluginName)) {
    
  243.       continue;
    
  244.     }
    
  245.     const pluginModule = injectedNamesToPlugins[pluginName];
    
  246.     if (
    
  247.       !namesToPlugins.hasOwnProperty(pluginName) ||
    
  248.       namesToPlugins[pluginName] !== pluginModule
    
  249.     ) {
    
  250.       if (namesToPlugins[pluginName]) {
    
  251.         throw new Error(
    
  252.           'EventPluginRegistry: Cannot inject two different event plugins ' +
    
  253.             `using the same name, \`${pluginName}\`.`,
    
  254.         );
    
  255.       }
    
  256. 
    
  257.       namesToPlugins[pluginName] = pluginModule;
    
  258.       isOrderingDirty = true;
    
  259.     }
    
  260.   }
    
  261.   if (isOrderingDirty) {
    
  262.     recomputePluginOrdering();
    
  263.   }
    
  264. }