From 346af557c3275fe5fbfd06f0dc1acf752752b2bf Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Tue, 3 Oct 2017 05:24:32 -0700 Subject: [PATCH] Fix regression in Java->C++->JS ViewManagers interaction. Reviewed By: bvaughn Differential Revision: D5953937 fbshipit-source-id: 8bc5dd8a483054ab9830ab653f2a3b41cad6c791 --- .../ReactNative/requireNativeComponent.js | 58 +++++++++++++++++ .../react/uimanager/UIManagerModule.java | 30 ++++++--- .../UIManagerModuleConstantsHelper.java | 62 ++++++++++--------- 3 files changed, 112 insertions(+), 38 deletions(-) diff --git a/Libraries/ReactNative/requireNativeComponent.js b/Libraries/ReactNative/requireNativeComponent.js index b04749ed5..7034d871a 100644 --- a/Libraries/ReactNative/requireNativeComponent.js +++ b/Libraries/ReactNative/requireNativeComponent.js @@ -52,6 +52,61 @@ function requireNativeComponent( componentInterface?: ?ComponentInterface, extraConfig?: ?{nativeOnly?: Object}, ): React$ComponentType | string { + function attachBubblingEventTypes(viewConfig) { + if (UIManager.genericBubblingEventTypes) { + viewConfig.bubblingEventTypes = merge( + viewConfig.bubblingEventTypes, + UIManager.genericBubblingEventTypes, + ); + // As genericBubblingEventTypes do not change over time, and there's + // merge of all the events happening in Fiber, we need to pass + // genericBubblingEventTypes to Fiber only once. Therefore, we can delete + // it and forget about it. + delete UIManager.genericBubblingEventTypes; + } + } + + function attachDirectEventTypes(viewConfig) { + if (UIManager.genericDirectEventTypes) { + viewConfig.directEventTypes = merge( + viewConfig.directEventTypes, + UIManager.genericDirectEventTypes, + ); + // As genericDirectEventTypes do not change over time, and there's merge + // of all the events happening in Fiber, we need to pass genericDirectEventTypes + // to Fiber only once. Therefore, we can delete it and forget about it. + delete UIManager.genericDirectEventTypes; + } + } + + function merge(destination: ?Object, source: ?Object): ?Object { + if (!source) { + return destination; + } + if (!destination) { + return source; + } + + for (const key in source) { + if (!source.hasOwnProperty(key)) { + continue; + } + + var sourceValue = source[key]; + if (destination.hasOwnProperty(key)) { + const destinationValue = destination[key]; + if ( + typeof sourceValue === 'object' && + typeof destinationValue === 'object' + ) { + sourceValue = merge(destinationValue, sourceValue); + } + } + destination[key] = sourceValue; + } + return destination; + } + // Don't load the ViewConfig from UIManager until it's needed for rendering. // Lazy-loading this can help avoid Prepack deopts. function getViewConfig() { @@ -129,6 +184,9 @@ function requireNativeComponent( ); } + attachBubblingEventTypes(viewConfig); + attachDirectEventTypes(viewConfig); + // Register this view's event types with the ReactNative renderer. // This enables view managers to be initialized lazily, improving perf, // While also enabling 3rd party components to define custom event types. diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index f2aba52c5..8988f597f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -32,6 +32,7 @@ import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; +import com.facebook.react.common.MapBuilder; import com.facebook.react.common.ReactConstants; import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener; @@ -114,6 +115,11 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements private int mBatchId = 0; + // Defines if events were already exported to JS. We do not send them more + // than once as they are stored and mixed in with Fiber for every ViewManager + // on JS side. + private boolean mEventsWereSentToJS = false; + public UIManagerModule( ReactApplicationContext reactContext, ViewManagerResolver viewManagerResolver, @@ -142,10 +148,8 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements super(reactContext); DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext); mEventDispatcher = new EventDispatcher(reactContext); - mModuleConstants = createConstants(viewManagersList); - mCustomDirectEvents = - (Map) mModuleConstants.get( - UIManagerModuleConstantsHelper.CUSTOM_DIRECT_EVENTS_KEY); + mCustomDirectEvents = MapBuilder.newHashMap(); + mModuleConstants = createConstants(viewManagersList, null, mCustomDirectEvents); mUIImplementation = uiImplementationProvider.createUIImplementation( reactContext, @@ -214,11 +218,15 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements } } - private static Map createConstants(List viewManagers) { + private static Map createConstants( + List viewManagers, + @Nullable Map customBubblingEvents, + @Nullable Map customDirectEvents) { ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_CONSTANTS_START); Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "CreateUIManagerConstants"); try { - return UIManagerModuleConstantsHelper.createConstants(viewManagers); + return UIManagerModuleConstantsHelper.createConstants( + viewManagers, customBubblingEvents, customDirectEvents); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_CONSTANTS_END); @@ -242,11 +250,15 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements Map viewManagerConstants = UIManagerModuleConstantsHelper.createConstantsForViewManager( targetView, - UIManagerModuleConstants.getBubblingEventTypeConstants(), - UIManagerModuleConstants.getDirectEventTypeConstants(), + mEventsWereSentToJS ? null : UIManagerModuleConstants.getBubblingEventTypeConstants(), + mEventsWereSentToJS ? null : UIManagerModuleConstants.getDirectEventTypeConstants(), null, mCustomDirectEvents); - return viewManagerConstants != null ? Arguments.makeNativeMap(viewManagerConstants) : null; + if (viewManagerConstants != null) { + mEventsWereSentToJS = true; + return Arguments.makeNativeMap(viewManagerConstants); + } + return null; } finally { SystraceMessage.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelper.java index d9b742da0..ad456d655 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelper.java @@ -24,9 +24,6 @@ import javax.annotation.Nullable; */ /* package */ class UIManagerModuleConstantsHelper { - /* package */ static final String CUSTOM_BUBBLING_EVENTS_KEY = "customBubblingEventTypes"; - /* package */ static final String CUSTOM_DIRECT_EVENTS_KEY = "customDirectEventTypes"; - /** * Generates a lazy discovery enabled version of {@link UIManagerModule} constants. It only * contains a list of view manager names, so that JS side is aware of the managers there are. @@ -53,22 +50,27 @@ import javax.annotation.Nullable; * {@link UIManagerModuleConstants}. * TODO(6845124): Create a test for this */ - /* package */ static Map createConstants(List viewManagers) { + /* package */ static Map createConstants( + List viewManagers, + @Nullable Map allBubblingEventTypes, + @Nullable Map allDirectEventTypes) { Map constants = UIManagerModuleConstants.getConstants(); // Generic/default event types: // All view managers are capable of dispatching these events. - // They will be automatically registered for each view type. + // They will be automatically registered with React Fiber. Map genericBubblingEventTypes = UIManagerModuleConstants.getBubblingEventTypeConstants(); Map genericDirectEventTypes = UIManagerModuleConstants.getDirectEventTypeConstants(); // Cumulative event types: // View manager specific event types are collected as views are loaded. // This information is used later when events are dispatched. - Map allBubblingEventTypes = MapBuilder.newHashMap(); - allBubblingEventTypes.putAll(genericBubblingEventTypes); - Map allDirectEventTypes = MapBuilder.newHashMap(); - allDirectEventTypes.putAll(genericDirectEventTypes); + if (allBubblingEventTypes != null) { + allBubblingEventTypes.putAll(genericBubblingEventTypes); + } + if (allDirectEventTypes != null) { + allDirectEventTypes.putAll(genericDirectEventTypes); + } for (ViewManager viewManager : viewManagers) { final String viewManagerName = viewManager.getName(); @@ -81,8 +83,8 @@ import javax.annotation.Nullable; try { Map viewManagerConstants = createConstantsForViewManager( viewManager, - genericBubblingEventTypes, - genericDirectEventTypes, + null, + null, allBubblingEventTypes, allDirectEventTypes); if (!viewManagerConstants.isEmpty()) { @@ -93,41 +95,39 @@ import javax.annotation.Nullable; } } - // Used by https://fburl.com/6nskr82o - constants.put(CUSTOM_BUBBLING_EVENTS_KEY, allBubblingEventTypes); - constants.put(CUSTOM_DIRECT_EVENTS_KEY, allDirectEventTypes); + constants.put("genericBubblingEventTypes", genericBubblingEventTypes); + constants.put("genericDirectEventTypes", genericDirectEventTypes); return constants; } /* package */ static Map createConstantsForViewManager( ViewManager viewManager, - Map defaultBubblingEvents, - Map defaultDirectEvents, + @Nullable Map defaultBubblingEvents, + @Nullable Map defaultDirectEvents, @Nullable Map cumulativeBubblingEventTypes, @Nullable Map cumulativeDirectEventTypes) { + final String BUBBLING_EVENTS_KEY = "bubblingEventTypes"; + final String DIRECT_EVENTS_KEY = "directEventTypes"; + Map viewManagerConstants = MapBuilder.newHashMap(); Map viewManagerBubblingEvents = viewManager.getExportedCustomBubblingEventTypeConstants(); if (viewManagerBubblingEvents != null) { - if (cumulativeBubblingEventTypes != null) { - recursiveMerge(cumulativeBubblingEventTypes, viewManagerBubblingEvents); - } + recursiveMerge(cumulativeBubblingEventTypes, viewManagerBubblingEvents); recursiveMerge(viewManagerBubblingEvents, defaultBubblingEvents); - } else { - viewManagerBubblingEvents = defaultBubblingEvents; + viewManagerConstants.put(BUBBLING_EVENTS_KEY, viewManagerBubblingEvents); + } else if (defaultBubblingEvents != null) { + viewManagerConstants.put(BUBBLING_EVENTS_KEY, defaultBubblingEvents); } - viewManagerConstants.put("bubblingEventTypes", viewManagerBubblingEvents); Map viewManagerDirectEvents = viewManager.getExportedCustomDirectEventTypeConstants(); if (viewManagerDirectEvents != null) { - if (cumulativeDirectEventTypes != null) { - recursiveMerge(cumulativeDirectEventTypes, viewManagerBubblingEvents); - } + recursiveMerge(cumulativeDirectEventTypes, viewManagerDirectEvents); recursiveMerge(viewManagerDirectEvents, defaultDirectEvents); - } else { - viewManagerDirectEvents = defaultDirectEvents; + viewManagerConstants.put(DIRECT_EVENTS_KEY, viewManagerDirectEvents); + } else if (defaultDirectEvents != null) { + viewManagerConstants.put(DIRECT_EVENTS_KEY, defaultDirectEvents); } - viewManagerConstants.put("directEventTypes", viewManagerDirectEvents); Map customViewConstants = viewManager.getExportedViewConstants(); if (customViewConstants != null) { @@ -148,7 +148,11 @@ import javax.annotation.Nullable; /** * Merges {@param source} map into {@param dest} map recursively */ - private static void recursiveMerge(Map dest, Map source) { + private static void recursiveMerge(@Nullable Map dest, @Nullable Map source) { + if (dest == null || source == null || source.isEmpty()) { + return; + } + for (Object key : source.keySet()) { Object sourceValue = source.get(key); Object destValue = dest.get(key);