Repository: facebook/react
Pull request: React Native raw event EventEmitter - intended for app-specific perf listeners and debugging

## Summary

This is a replacement / alternative to the Pressability-based event telemetry system that has existed since ~1 year ago. Instead of relying on the internals of the Pressability state machine, and only being able to instrument certain touch events, this system will allow us to instrument any/all events that flow through React Native into ReactJS.

## How did you test this change?

Testing manually on an internal stack, with a very small interface.

Changed files:
filename: packages/react-native-renderer/src/ReactFabricEventEmitter.js
patch: @@ -21,6 +21,8 @@ import {plugins} from './legacy-events/EventPluginRegistry';
 import getListener from './ReactNativeGetListener';
 import {runEventsInBatch} from './legacy-events/EventBatching';
 
+import {RawEventEmitter} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
+
 export {getListener, registrationNameModules as registrationNames};
 
 /**
@@ -73,7 +75,7 @@ function runExtractedPluginEventsInBatch(
 
 export function dispatchEvent(
   target: null | Object,
-  topLevelType: TopLevelType,
+  topLevelType: RNTopLevelEventType,
   nativeEvent: AnyNativeEvent,
 ) {
   const targetFiber = (target: null | Fiber);
@@ -88,6 +90,31 @@ export function dispatchEvent(
   }
 
   batchedUpdates(function() {
+    // Emit event to the RawEventEmitter. This is an unused-by-default EventEmitter
+    // that can be used to instrument event performance monitoring (primarily - could be useful
+    // for other things too).
+    //
+    // NOTE: this merely emits events into the EventEmitter below.
+    // If *you* do not add listeners to the `RawEventEmitter`,
+    // then all of these emitted events will just blackhole and are no-ops.
+    // It is available (although not officially supported... yet) if you want to collect
+    // perf data on event latency in your application, and could also be useful for debugging
+    // low-level events issues.
+    //
+    // If you do not have any event perf monitoring and are extremely concerned about event perf,
+    // it is safe to disable these "emit" statements; it will prevent checking the size of
+    // an empty array twice and prevent two no-ops. Practically the overhead is so low that
+    // we don't think it's worth thinking about in prod; your perf issues probably lie elsewhere.
+    //
+    // We emit two events here: one for listeners to this specific event,
+    // and one for the catchall listener '*', for any listeners that want
+    // to be notified for all events.
+    // Note that extracted events are *not* emitted,
+    // only events that have a 1:1 mapping with a native event, at least for now.
+    const event = {eventName: topLevelType, nativeEvent};
+    RawEventEmitter.emit(topLevelType, event);
+    RawEventEmitter.emit('*', event);
+
     // Heritage plugin event system
     runExtractedPluginEventsInBatch(
       topLevelType,

filename: packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/RawEventEmitter.js
patch: @@ -0,0 +1,16 @@
+/**
+ * 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.
+ */
+
+'use strict';
+
+// See the react-native repository for a full implementation.
+// However, this is just an EventEmitter.
+const RawEventEmitter = {
+  emit: jest.fn(),
+};
+
+module.exports = {default: RawEventEmitter};

filename: packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/ReactNativePrivateInterface.js
patch: @@ -41,4 +41,7 @@ module.exports = {
   get legacySendAccessibilityEvent() {
     return require('./legacySendAccessibilityEvent');
   },
+  get RawEventEmitter() {
+    return require('./RawEventEmitter').default;
+  },
 };

filename: scripts/flow/react-native-host-hooks.js
patch: @@ -21,6 +21,13 @@ import type {CapturedError} from 'react-reconciler/src/ReactCapturedValue';
 import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
 
 type DeepDifferOptions = {|+unsafelyIgnoreFunctions?: boolean|};
+type RawEventEmitterEvent = $ReadOnly<{|
+  eventName: string,
+  // We expect, but do not/cannot require, that nativeEvent is an object
+  // with the properties: key, elementType (string), type (string), tag (numeric),
+  // and a stateNode of the native element/Fiber the event was emitted to.
+  nativeEvent: {[string]: mixed},
+|}>;
 
 declare module 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface' {
   declare export function deepDiffer(
@@ -127,6 +134,10 @@ declare module 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'
     get: (name: string) => ReactNativeBaseComponentViewConfig,
     ...
   };
+  declare export var RawEventEmitter: {
+    emit: (channel: string, event: RawEventEmitterEvent) => string,
+    ...
+  };
 }
 
 declare module 'react-native/Libraries/ReactPrivate/ReactNativePrivateInitializeCore' {