@samitouri / QOS-React-2 / commits / 2c6ea0b3ff

[Native] Add FeatureFlag to dispatch events with instance targets (#17323)

* [Native] Add FeatureFlag to dispatch events with instance targets * Prettier

Eli White committed Nov 11, 2019 at 11:35 UTC 2c6ea0b3ffffd1a110845327262ecea59ee48dab
13 files changed +380 -4
packages/react-native-renderer/src/ReactFabricEventEmitter.js
+18 -2
@@ -18,7 +18,10 @@ import {registrationNameModules} from 'legacy-events/EventPluginRegistry';
18 import {batchedUpdates} from 'legacy-events/ReactGenericBatching';
19
20 import type {AnyNativeEvent} from 'legacy-events/PluginModuleType';
21 -import {enableFlareAPI} from 'shared/ReactFeatureFlags';
21 +import {
22 + enableFlareAPI,
23 + enableNativeTargetAsInstance,
24 +} from 'shared/ReactFeatureFlags';
25 import type {TopLevelType} from 'legacy-events/TopLevelEventTypes';
26 import {dispatchEventForResponderEventSystem} from './ReactFabricEventResponderSystem';
27
@@ -30,6 +33,7 @@ export function dispatchEvent(
33 nativeEvent: AnyNativeEvent,
34 ) {
35 const targetFiber = (target: null | Fiber);
36 +
37 if (enableFlareAPI) {
38 // React Flare event system
39 dispatchEventForResponderEventSystem(
@@ -38,13 +42,25 @@ export function dispatchEvent(
42 (nativeEvent: any),
43 );
44 }
45 +
46 + let eventTarget;
47 + if (enableNativeTargetAsInstance) {
48 + if (targetFiber == null) {
49 + eventTarget = null;
50 + } else {
51 + eventTarget = targetFiber.stateNode.canonical;
52 + }
53 + } else {
54 + eventTarget = nativeEvent.target;
55 + }
56 +
57 batchedUpdates(function() {
58 // Heritage plugin event system
59 runExtractedPluginEventsInBatch(
60 topLevelType,
61 targetFiber,
62 nativeEvent,
47 - nativeEvent.target,
63 + eventTarget,
64 PLUGIN_EVENT_SYSTEM,
65 );
66 });
packages/react-native-renderer/src/ReactNativeEventEmitter.js
+14 -1
@@ -15,6 +15,7 @@ import {
15 import {registrationNameModules} from 'legacy-events/EventPluginRegistry';
16 import {batchedUpdates} from 'legacy-events/ReactGenericBatching';
17 import warningWithoutStack from 'shared/warningWithoutStack';
18 +import {enableNativeTargetAsInstance} from 'shared/ReactFeatureFlags';
19
20 import {getInstanceFromNode} from './ReactNativeComponentTree';
21
@@ -98,12 +99,24 @@ function _receiveRootNodeIDEvent(
99 ) {
100 const nativeEvent = nativeEventParam || EMPTY_NATIVE_EVENT;
101 const inst = getInstanceFromNode(rootNodeID);
102 +
103 + let target;
104 + if (enableNativeTargetAsInstance) {
105 + if (inst == null) {
106 + target = null;
107 + } else {
108 + target = inst.stateNode;
109 + }
110 + } else {
111 + target = nativeEvent.target;
112 + }
113 +
114 batchedUpdates(function() {
115 runExtractedPluginEventsInBatch(
116 topLevelType,
117 inst,
118 nativeEvent,
106 - nativeEvent.target,
119 + target,
120 PLUGIN_EVENT_SYSTEM,
121 );
122 });
packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js
+194
@@ -12,6 +12,7 @@
12
13 let React;
14 let ReactFabric;
15 +let ReactFeatureFlags;
16 let createReactClass;
17 let createReactNativeComponentClass;
18 let UIManager;
@@ -38,6 +39,7 @@ describe('ReactFabric', () => {
39 React = require('react');
40 StrictMode = React.StrictMode;
41 ReactFabric = require('react-native-renderer/fabric');
42 + ReactFeatureFlags = require('shared/ReactFeatureFlags');
43 UIManager = require('react-native/Libraries/ReactPrivate/ReactNativePrivateInterface')
44 .UIManager;
45 createReactClass = require('create-react-class/factory')(
@@ -779,6 +781,198 @@ describe('ReactFabric', () => {
781 expect(touchStart2).toBeCalled();
782 });
783
784 + it('dispatches event with target as reactTag', () => {
785 + ReactFeatureFlags.enableNativeTargetAsInstance = false;
786 +
787 + const View = createReactNativeComponentClass('RCTView', () => ({
788 + validAttributes: {
789 + id: true,
790 + },
791 + uiViewClassName: 'RCTView',
792 + directEventTypes: {
793 + topTouchStart: {
794 + registrationName: 'onTouchStart',
795 + },
796 + topTouchEnd: {
797 + registrationName: 'onTouchEnd',
798 + },
799 + },
800 + }));
801 +
802 + function getViewById(id) {
803 + const [
804 + reactTag,
805 + ,
806 + ,
807 + ,
808 + instanceHandle,
809 + ] = nativeFabricUIManager.createNode.mock.calls.find(
810 + args => args[3] && args[3].id === id,
811 + );
812 +
813 + return {reactTag, instanceHandle};
814 + }
815 +
816 + const ref1 = React.createRef();
817 + const ref2 = React.createRef();
818 +
819 + ReactFabric.render(
820 + <View id="parent">
821 + <View
822 + ref={ref1}
823 + id="one"
824 + onResponderStart={event => {
825 + expect(ref1.current).not.toBeNull();
826 + expect(ReactFabric.findNodeHandle(ref1.current)).toEqual(
827 + event.target,
828 + );
829 + }}
830 + onStartShouldSetResponder={() => true}
831 + />
832 + <View
833 + ref={ref2}
834 + id="two"
835 + onResponderStart={event => {
836 + expect(ref2.current).not.toBeNull();
837 + expect(ReactFabric.findNodeHandle(ref2.current)).toEqual(
838 + event.target,
839 + );
840 + }}
841 + onStartShouldSetResponder={() => true}
842 + />
843 + </View>,
844 + 1,
845 + );
846 +
847 + let [
848 + dispatchEvent,
849 + ] = nativeFabricUIManager.registerEventHandler.mock.calls[0];
850 +
851 + dispatchEvent(getViewById('one').instanceHandle, 'topTouchStart', {
852 + target: getViewById('one').reactTag,
853 + identifier: 17,
854 + touches: [],
855 + changedTouches: [],
856 + });
857 + dispatchEvent(getViewById('one').instanceHandle, 'topTouchEnd', {
858 + target: getViewById('one').reactTag,
859 + identifier: 17,
860 + touches: [],
861 + changedTouches: [],
862 + });
863 +
864 + dispatchEvent(getViewById('two').instanceHandle, 'topTouchStart', {
865 + target: getViewById('two').reactTag,
866 + identifier: 17,
867 + touches: [],
868 + changedTouches: [],
869 + });
870 +
871 + dispatchEvent(getViewById('two').instanceHandle, 'topTouchEnd', {
872 + target: getViewById('two').reactTag,
873 + identifier: 17,
874 + touches: [],
875 + changedTouches: [],
876 + });
877 +
878 + expect.assertions(4);
879 + });
880 +
881 + it('dispatches event with target as instance', () => {
882 + ReactFeatureFlags.enableNativeTargetAsInstance = true;
883 +
884 + const View = createReactNativeComponentClass('RCTView', () => ({
885 + validAttributes: {
886 + id: true,
887 + },
888 + uiViewClassName: 'RCTView',
889 + directEventTypes: {
890 + topTouchStart: {
891 + registrationName: 'onTouchStart',
892 + },
893 + topTouchEnd: {
894 + registrationName: 'onTouchEnd',
895 + },
896 + },
897 + }));
898 +
899 + function getViewById(id) {
900 + const [
901 + reactTag,
902 + ,
903 + ,
904 + ,
905 + instanceHandle,
906 + ] = nativeFabricUIManager.createNode.mock.calls.find(
907 + args => args[3] && args[3].id === id,
908 + );
909 +
910 + return {reactTag, instanceHandle};
911 + }
912 +
913 + const ref1 = React.createRef();
914 + const ref2 = React.createRef();
915 +
916 + ReactFabric.render(
917 + <View id="parent">
918 + <View
919 + ref={ref1}
920 + id="one"
921 + onResponderStart={event => {
922 + expect(ref1.current).not.toBeNull();
923 + // Check for referential equality
924 + expect(ref1.current).toBe(event.target);
925 + }}
926 + onStartShouldSetResponder={() => true}
927 + />
928 + <View
929 + ref={ref2}
930 + id="two"
931 + onResponderStart={event => {
932 + expect(ref2.current).not.toBeNull();
933 + // Check for referential equality
934 + expect(ref2.current).toBe(event.target);
935 + }}
936 + onStartShouldSetResponder={() => true}
937 + />
938 + </View>,
939 + 1,
940 + );
941 +
942 + let [
943 + dispatchEvent,
944 + ] = nativeFabricUIManager.registerEventHandler.mock.calls[0];
945 +
946 + dispatchEvent(getViewById('one').instanceHandle, 'topTouchStart', {
947 + target: getViewById('one').reactTag,
948 + identifier: 17,
949 + touches: [],
950 + changedTouches: [],
951 + });
952 + dispatchEvent(getViewById('one').instanceHandle, 'topTouchEnd', {
953 + target: getViewById('one').reactTag,
954 + identifier: 17,
955 + touches: [],
956 + changedTouches: [],
957 + });
958 +
959 + dispatchEvent(getViewById('two').instanceHandle, 'topTouchStart', {
960 + target: getViewById('two').reactTag,
961 + identifier: 17,
962 + touches: [],
963 + changedTouches: [],
964 + });
965 +
966 + dispatchEvent(getViewById('two').instanceHandle, 'topTouchEnd', {
967 + target: getViewById('two').reactTag,
968 + identifier: 17,
969 + touches: [],
970 + changedTouches: [],
971 + });
972 +
973 + expect.assertions(4);
974 + });
975 +
976 it('findHostInstance_DEPRECATED should warn if used to find a host component inside StrictMode', () => {
977 const View = createReactNativeComponentClass('RCTView', () => ({
978 validAttributes: {foo: true},
packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js
+140
@@ -14,6 +14,7 @@ let PropTypes;
14 let RCTEventEmitter;
15 let React;
16 let ReactNative;
17 +let ReactFeatureFlags;
18 let ResponderEventPlugin;
19 let UIManager;
20 let createReactNativeComponentClass;
@@ -68,6 +69,7 @@ beforeEach(() => {
69 .RCTEventEmitter;
70 React = require('react');
71 ReactNative = require('react-native-renderer');
72 + ReactFeatureFlags = require('shared/ReactFeatureFlags');
73 ResponderEventPlugin = require('legacy-events/ResponderEventPlugin').default;
74 UIManager = require('react-native/Libraries/ReactPrivate/ReactNativePrivateInterface')
75 .UIManager;
@@ -456,3 +458,141 @@ it('handles events without target', () => {
458 'two responder end',
459 ]);
460 });
461 +
462 +it('dispatches event with target as reactTag', () => {
463 + ReactFeatureFlags.enableNativeTargetAsInstance = false;
464 + const EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
465 +
466 + const View = fakeRequireNativeComponent('View', {id: true});
467 +
468 + function getViewById(id) {
469 + return UIManager.createView.mock.calls.find(
470 + args => args[3] && args[3].id === id,
471 + )[0];
472 + }
473 +
474 + const ref1 = React.createRef();
475 + const ref2 = React.createRef();
476 +
477 + ReactNative.render(
478 + <View id="parent">
479 + <View
480 + ref={ref1}
481 + id="one"
482 + onResponderStart={event => {
483 + expect(ref1.current).not.toBeNull();
484 + expect(ReactNative.findNodeHandle(ref1.current)).toEqual(
485 + event.target,
486 + );
487 + }}
488 + onStartShouldSetResponder={() => true}
489 + />
490 + <View
491 + ref={ref2}
492 + id="two"
493 + onResponderStart={event => {
494 + expect(ref2.current).not.toBeNull();
495 + expect(ReactNative.findNodeHandle(ref2.current)).toEqual(
496 + event.target,
497 + );
498 + }}
499 + onStartShouldSetResponder={() => true}
500 + />
501 + </View>,
502 + 1,
503 + );
504 +
505 + EventEmitter.receiveTouches(
506 + 'topTouchStart',
507 + [{target: getViewById('one'), identifier: 17}],
508 + [0],
509 + );
510 +
511 + EventEmitter.receiveTouches(
512 + 'topTouchEnd',
513 + [{target: getViewById('one'), identifier: 17}],
514 + [0],
515 + );
516 +
517 + EventEmitter.receiveTouches(
518 + 'topTouchStart',
519 + [{target: getViewById('two'), identifier: 18}],
520 + [0],
521 + );
522 +
523 + EventEmitter.receiveTouches(
524 + 'topTouchEnd',
525 + [{target: getViewById('two'), identifier: 18}],
526 + [0],
527 + );
528 +
529 + expect.assertions(4);
530 +});
531 +
532 +it('dispatches event with target as instance', () => {
533 + ReactFeatureFlags.enableNativeTargetAsInstance = true;
534 + const EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
535 +
536 + const View = fakeRequireNativeComponent('View', {id: true});
537 +
538 + function getViewById(id) {
539 + return UIManager.createView.mock.calls.find(
540 + args => args[3] && args[3].id === id,
541 + )[0];
542 + }
543 +
544 + const ref1 = React.createRef();
545 + const ref2 = React.createRef();
546 +
547 + ReactNative.render(
548 + <View id="parent">
549 + <View
550 + ref={ref1}
551 + id="one"
552 + onResponderStart={event => {
553 + expect(ref1.current).not.toBeNull();
554 + // Check for referential equality
555 + expect(ref1.current).toBe(event.target);
556 + }}
557 + onStartShouldSetResponder={() => true}
558 + />
559 + <View
560 + ref={ref2}
561 + id="two"
562 + onResponderStart={event => {
563 + expect(ref2.current).not.toBeNull();
564 + // Check for referential equality
565 + expect(ref2.current).toBe(event.target);
566 + }}
567 + onStartShouldSetResponder={() => true}
568 + />
569 + </View>,
570 + 1,
571 + );
572 +
573 + EventEmitter.receiveTouches(
574 + 'topTouchStart',
575 + [{target: getViewById('one'), identifier: 17}],
576 + [0],
577 + );
578 +
579 + EventEmitter.receiveTouches(
580 + 'topTouchEnd',
581 + [{target: getViewById('one'), identifier: 17}],
582 + [0],
583 + );
584 +
585 + EventEmitter.receiveTouches(
586 + 'topTouchStart',
587 + [{target: getViewById('two'), identifier: 18}],
588 + [0],
589 + );
590 +
591 + EventEmitter.receiveTouches(
592 + 'topTouchEnd',
593 + [{target: getViewById('two'), identifier: 18}],
594 + [0],
595 + );
596 +
597 + expect.assertions(4);
598 +});
packages/shared/ReactFeatureFlags.js
+3
@@ -89,3 +89,6 @@ export const disableLegacyContext = false;
89 export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
90
91 export const enableTrustedTypesIntegration = false;
92 +
93 +// Flag to turn event.target and event.currentTarget in ReactNative from a reactTag to a component instance
94 +export const enableNativeTargetAsInstance = false;
packages/shared/forks/ReactFeatureFlags.native-fb.js
+3 -1
@@ -13,7 +13,9 @@ import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags';
13 import typeof * as FeatureFlagsShimType from './ReactFeatureFlags.native-fb';
14
15 // Uncomment to re-export dynamic flags from the fbsource version.
16 -// export const {} = require('../shims/ReactFeatureFlags');
16 +export const {
17 + enableNativeTargetAsInstance,
18 +} = require('../shims/ReactFeatureFlags');
19
20 // The rest of the flags are static for better dead code elimination.
21 export const enableUserTimingAPI = __DEV__;
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -37,6 +37,7 @@ export const warnAboutStringRefs = false;
37 export const disableLegacyContext = false;
38 export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
39 export const enableTrustedTypesIntegration = false;
40 +export const enableNativeTargetAsInstance = false;
41
42 // Only used in www builds.
43 export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.persistent.js
+1
@@ -37,6 +37,7 @@ export const warnAboutStringRefs = false;
37 export const disableLegacyContext = false;
38 export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
39 export const enableTrustedTypesIntegration = false;
40 +export const enableNativeTargetAsInstance = false;
41
42 // Only used in www builds.
43 export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -37,6 +37,7 @@ export const warnAboutStringRefs = false;
37 export const disableLegacyContext = false;
38 export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
39 export const enableTrustedTypesIntegration = false;
40 +export const enableNativeTargetAsInstance = false;
41
42 // Only used in www builds.
43 export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -35,6 +35,7 @@ export const warnAboutStringRefs = false;
35 export const disableLegacyContext = false;
36 export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
37 export const enableTrustedTypesIntegration = false;
38 +export const enableNativeTargetAsInstance = false;
39
40 // Only used in www builds.
41 export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.www.js
+2
@@ -85,6 +85,8 @@ export const enableSuspenseCallback = true;
85
86 export const flushSuspenseFallbacksInTests = true;
87
88 +export const enableNativeTargetAsInstance = false;
89 +
90 // Flow magic to verify the exports of this file match the original version.
91 // eslint-disable-next-line no-unused-vars
92 type Check<_X, Y: _X, X: Y = _X> = null;
scripts/flow/react-native-host-hooks.js
+1
@@ -181,4 +181,5 @@ declare module 'RTManager' {
181 // shims/ReactFeatureFlags is generated by the packaging script
182 declare module '../shims/ReactFeatureFlags' {
183 declare export var debugRenderPhaseSideEffects: boolean;
184 + declare export var enableNativeTargetAsInstance: boolean;
185 }
scripts/rollup/shims/react-native/ReactFeatureFlags.js
+1
@@ -12,6 +12,7 @@
12
13 const ReactFeatureFlags = {
14 debugRenderPhaseSideEffects: false,
15 + enableNativeTargetAsInstance: false,
16 };
17
18 module.exports = ReactFeatureFlags;