/** * 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 {AnyNativeEvent} from 'legacy-events/PluginModuleType'; import type {EventSystemFlags} from 'legacy-events/EventSystemFlags'; import type {TopLevelType} from 'legacy-events/TopLevelEventTypes'; import SyntheticEvent from 'legacy-events/SyntheticEvent'; import invariant from 'shared/invariant'; // Module provided by RN: import {ReactNativeViewConfigRegistry} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'; import accumulateInto from 'legacy-events/accumulateInto'; import getListener from 'legacy-events/getListener'; import forEachAccumulated from 'legacy-events/forEachAccumulated'; import {HostComponent} from 'react-reconciler/src/ReactWorkTags'; const { customBubblingEventTypes, customDirectEventTypes, } = ReactNativeViewConfigRegistry; // Start of inline: the below functions were inlined from // EventPropagator.js, as they deviated from ReactDOM's newer // implementations. function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) { const registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase]; return getListener(inst, registrationName); } function accumulateDirectionalDispatches(inst, phase, event) { if (__DEV__) { if (!inst) { console.error('Dispatching inst must not be null'); } } const listener = listenerAtPhase(inst, event, phase); if (listener) { event._dispatchListeners = accumulateInto( event._dispatchListeners, listener, ); event._dispatchInstances = accumulateInto(event._dispatchInstances, inst); } } function getParent(inst) { do { inst = inst.return; // TODO: If this is a HostRoot we might want to bail out. // That is depending on if we want nested subtrees (layers) to bubble // events to their parent. We could also go through parentNode on the // host node but that wouldn't work for React Native and doesn't let us // do the portal feature. } while (inst && inst.tag !== HostComponent); if (inst) { return inst; } return null; } /** * Simulates the traversal of a two-phase, capture/bubble event dispatch. */ export function traverseTwoPhase(inst: Object, fn: Function, arg: Function) { const path = []; while (inst) { path.push(inst); inst = getParent(inst); } let i; for (i = path.length; i-- > 0; ) { fn(path[i], 'captured', arg); } for (i = 0; i < path.length; i++) { fn(path[i], 'bubbled', arg); } } function accumulateTwoPhaseDispatchesSingle(event) { if (event && event.dispatchConfig.phasedRegistrationNames) { traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event); } } function accumulateTwoPhaseDispatches(events) { forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle); } /** * Accumulates without regard to direction, does not look for phased * registration names. Same as `accumulateDirectDispatchesSingle` but without * requiring that the `dispatchMarker` be the same as the dispatched ID. */ function accumulateDispatches( inst: Object, ignoredDirection: ?boolean, event: Object, ): void { if (inst && event && event.dispatchConfig.registrationName) { const registrationName = event.dispatchConfig.registrationName; const listener = getListener(inst, registrationName); if (listener) { event._dispatchListeners = accumulateInto( event._dispatchListeners, listener, ); event._dispatchInstances = accumulateInto(event._dispatchInstances, inst); } } } /** * Accumulates dispatches on an `SyntheticEvent`, but only for the * `dispatchMarker`. * @param {SyntheticEvent} event */ function accumulateDirectDispatchesSingle(event: Object) { if (event && event.dispatchConfig.registrationName) { accumulateDispatches(event._targetInst, null, event); } } function accumulateDirectDispatches(events: ?(Array | Object)) { forEachAccumulated(events, accumulateDirectDispatchesSingle); } // End of inline type PropagationPhases = 'bubbled' | 'captured'; const ReactNativeBridgeEventPlugin = { eventTypes: {}, extractEvents: function( topLevelType: TopLevelType, targetInst: null | Object, nativeEvent: AnyNativeEvent, nativeEventTarget: null | Object, eventSystemFlags: EventSystemFlags, ): ?Object { if (targetInst == null) { // Probably a node belonging to another renderer's tree. return null; } const bubbleDispatchConfig = customBubblingEventTypes[topLevelType]; const directDispatchConfig = customDirectEventTypes[topLevelType]; invariant( bubbleDispatchConfig || directDispatchConfig, 'Unsupported top level event type "%s" dispatched', topLevelType, ); const event = SyntheticEvent.getPooled( bubbleDispatchConfig || directDispatchConfig, targetInst, nativeEvent, nativeEventTarget, ); if (bubbleDispatchConfig) { accumulateTwoPhaseDispatches(event); } else if (directDispatchConfig) { accumulateDirectDispatches(event); } else { return null; } return event; }, }; export default ReactNativeBridgeEventPlugin;