/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow */ import type {DispatchConfig} from './ReactSyntheticEventType'; import type { AnyNativeEvent, PluginName, PluginModule, } from './PluginModuleType'; import invariant from 'shared/invariant'; type NamesToPlugins = {[key: PluginName]: PluginModule}; type EventPluginOrder = null | Array; /** * Injectable ordering of event plugins. */ let eventPluginOrder: EventPluginOrder = null; /** * Injectable mapping from names to event plugin modules. */ const namesToPlugins: NamesToPlugins = {}; /** * Recomputes the plugin list using the injected plugins and plugin ordering. * * @private */ function recomputePluginOrdering(): void { if (!eventPluginOrder) { // Wait until an `eventPluginOrder` is injected. return; } for (const pluginName in namesToPlugins) { const pluginModule = namesToPlugins[pluginName]; const pluginIndex = eventPluginOrder.indexOf(pluginName); invariant( pluginIndex > -1, 'EventPluginRegistry: Cannot inject event plugins that do not exist in ' + 'the plugin ordering, `%s`.', pluginName, ); if (plugins[pluginIndex]) { continue; } invariant( pluginModule.extractEvents, 'EventPluginRegistry: Event plugins must implement an `extractEvents` ' + 'method, but `%s` does not.', pluginName, ); plugins[pluginIndex] = pluginModule; const publishedEvents = pluginModule.eventTypes; for (const eventName in publishedEvents) { invariant( publishEventForPlugin( publishedEvents[eventName], pluginModule, eventName, ), 'EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.', eventName, pluginName, ); } } } /** * Publishes an event so that it can be dispatched by the supplied plugin. * * @param {object} dispatchConfig Dispatch configuration for the event. * @param {object} PluginModule Plugin publishing the event. * @return {boolean} True if the event was successfully published. * @private */ function publishEventForPlugin( dispatchConfig: DispatchConfig, pluginModule: PluginModule, eventName: string, ): boolean { invariant( !eventNameDispatchConfigs.hasOwnProperty(eventName), 'EventPluginHub: More than one plugin attempted to publish the same ' + 'event name, `%s`.', eventName, ); eventNameDispatchConfigs[eventName] = dispatchConfig; const phasedRegistrationNames = dispatchConfig.phasedRegistrationNames; if (phasedRegistrationNames) { for (const phaseName in phasedRegistrationNames) { if (phasedRegistrationNames.hasOwnProperty(phaseName)) { const phasedRegistrationName = phasedRegistrationNames[phaseName]; publishRegistrationName( phasedRegistrationName, pluginModule, eventName, ); } } return true; } else if (dispatchConfig.registrationName) { publishRegistrationName( dispatchConfig.registrationName, pluginModule, eventName, ); return true; } return false; } /** * Publishes a registration name that is used to identify dispatched events. * * @param {string} registrationName Registration name to add. * @param {object} PluginModule Plugin publishing the event. * @private */ function publishRegistrationName( registrationName: string, pluginModule: PluginModule, eventName: string, ): void { invariant( !registrationNameModules[registrationName], 'EventPluginHub: More than one plugin attempted to publish the same ' + 'registration name, `%s`.', registrationName, ); registrationNameModules[registrationName] = pluginModule; registrationNameDependencies[registrationName] = pluginModule.eventTypes[eventName].dependencies; if (__DEV__) { const lowerCasedName = registrationName.toLowerCase(); possibleRegistrationNames[lowerCasedName] = registrationName; if (registrationName === 'onDoubleClick') { possibleRegistrationNames.ondblclick = registrationName; } } } /** * Registers plugins so that they can extract and dispatch events. * * @see {EventPluginHub} */ /** * Ordered list of injected plugins. */ export const plugins = []; /** * Mapping from event name to dispatch config */ export const eventNameDispatchConfigs = {}; /** * Mapping from registration name to plugin module */ export const registrationNameModules = {}; /** * Mapping from registration name to event name */ export const registrationNameDependencies = {}; /** * Mapping from lowercase registration names to the properly cased version, * used to warn in the case of missing event handlers. Available * only in __DEV__. * @type {Object} */ export const possibleRegistrationNames = __DEV__ ? {} : (null: any); // Trust the developer to only use possibleRegistrationNames in __DEV__ /** * Injects an ordering of plugins (by plugin name). This allows the ordering * to be decoupled from injection of the actual plugins so that ordering is * always deterministic regardless of packaging, on-the-fly injection, etc. * * @param {array} InjectedEventPluginOrder * @internal * @see {EventPluginHub.injection.injectEventPluginOrder} */ export function injectEventPluginOrder( injectedEventPluginOrder: EventPluginOrder, ): void { invariant( !eventPluginOrder, 'EventPluginRegistry: Cannot inject event plugin ordering more than ' + 'once. You are likely trying to load more than one copy of React.', ); // Clone the ordering so it cannot be dynamically mutated. eventPluginOrder = Array.prototype.slice.call(injectedEventPluginOrder); recomputePluginOrdering(); } /** * Injects plugins to be used by `EventPluginHub`. The plugin names must be * in the ordering injected by `injectEventPluginOrder`. * * Plugins can be injected as part of page initialization or on-the-fly. * * @param {object} injectedNamesToPlugins Map from names to plugin modules. * @internal * @see {EventPluginHub.injection.injectEventPluginsByName} */ export function injectEventPluginsByName( injectedNamesToPlugins: NamesToPlugins, ): void { let isOrderingDirty = false; for (const pluginName in injectedNamesToPlugins) { if (!injectedNamesToPlugins.hasOwnProperty(pluginName)) { continue; } const pluginModule = injectedNamesToPlugins[pluginName]; if ( !namesToPlugins.hasOwnProperty(pluginName) || namesToPlugins[pluginName] !== pluginModule ) { invariant( !namesToPlugins[pluginName], 'EventPluginRegistry: Cannot inject two different event plugins ' + 'using the same name, `%s`.', pluginName, ); namesToPlugins[pluginName] = pluginModule; isOrderingDirty = true; } } if (isOrderingDirty) { recomputePluginOrdering(); } }