diff --git a/React/Fabric/RCTScheduler.mm b/React/Fabric/RCTScheduler.mm index 1b6c8b797..b3a4994a2 100644 --- a/React/Fabric/RCTScheduler.mm +++ b/React/Fabric/RCTScheduler.mm @@ -7,6 +7,7 @@ #import "RCTScheduler.h" +#import #import #import #import @@ -49,7 +50,7 @@ private: { if (self = [super init]) { _delegateProxy = std::make_shared((__bridge void *)self); - _scheduler = std::make_shared(std::static_pointer_cast(contextContatiner)); + _scheduler = std::make_shared(std::static_pointer_cast(contextContatiner), getDefaultComponentRegistryFactory()); _scheduler->setDelegate(_delegateProxy.get()); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/BUCK b/ReactAndroid/src/main/java/com/facebook/react/fabric/BUCK index 62d36a016..a70e4c7c7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/BUCK @@ -4,10 +4,11 @@ rn_android_library( name = "fabric", srcs = glob([ "*.java", - "events/*.java", + "jsi/*.java", + "mounting/**/*.java", ]), provided_deps = [ - react_native_dep("third-party/android/support/v4:lib-support-v4"), + react_native_dep("third-party/android/support-new:support-v4"), ], required_for_source_only_abi = True, visibility = [ @@ -15,17 +16,23 @@ rn_android_library( ], deps = [ YOGA_TARGET, + react_native_dep("third-party/java/infer-annotations:infer-annotations"), react_native_dep("java/com/facebook/systrace:systrace"), react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"), - react_native_dep("third-party/java/infer-annotations:infer-annotations"), - react_native_dep("third-party/java/jsr-305:jsr-305"), + react_native_dep("libraries/soloader/java/com/facebook/soloader:soloader"), react_native_target("java/com/facebook/debug/tags:tags"), react_native_target("java/com/facebook/debug/holder:holder"), react_native_target("java/com/facebook/react/bridge:bridge"), - react_native_target("java/com/facebook/react/uimanager:uimanager"), - react_native_target("java/com/facebook/react/uimanager/annotations:annotations"), + react_native_target("java/com/facebook/react/fabric/jsi/jni:jni"), + react_native_target("java/com/facebook/react:react"), react_native_target("java/com/facebook/react/module/annotations:annotations"), - react_native_target("java/com/facebook/react/common:common"), + react_native_target("java/com/facebook/react/modules/core:core"), react_native_target("java/com/facebook/react/modules/i18nmanager:i18nmanager"), + react_native_target("java/com/facebook/react/common:common"), + react_native_target("java/com/facebook/react/uimanager:uimanager"), + react_native_target("java/com/facebook/react/views/view:view"), + react_native_target("java/com/facebook/react/touch:touch"), + ], + exported_deps = [ ], ) diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricBinding.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricBinding.java index 22e71c275..ce79198f5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricBinding.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricBinding.java @@ -8,6 +8,8 @@ package com.facebook.react.fabric; import com.facebook.react.bridge.JavaScriptContextHolder; import com.facebook.react.bridge.queue.MessageQueueThread; +import com.facebook.react.fabric.jsi.ComponentFactoryDelegate; +import com.facebook.react.fabric.jsi.EventBeatManager; public interface FabricBinding { @@ -15,8 +17,9 @@ public interface FabricBinding { void register( JavaScriptContextHolder jsContext, FabricBinder fabricBinder, - Object eventBeatManager, - MessageQueueThread jsMessageQueueThread); + EventBeatManager eventBeatManager, + MessageQueueThread jsMessageQueueThread, + ComponentFactoryDelegate componentFactoryDelegate); void unregister(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricEventEmitter.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricEventEmitter.java new file mode 100644 index 000000000..f54f96ce2 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricEventEmitter.java @@ -0,0 +1,155 @@ +/** + * 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. + */ +package com.facebook.react.fabric; + +import static com.facebook.react.uimanager.events.TouchesHelper.CHANGED_TOUCHES_KEY; +import static com.facebook.react.uimanager.events.TouchesHelper.TARGET_KEY; +import static com.facebook.react.uimanager.events.TouchesHelper.TOP_TOUCH_CANCEL_KEY; +import static com.facebook.react.uimanager.events.TouchesHelper.TOP_TOUCH_END_KEY; +import static com.facebook.react.uimanager.events.TouchesHelper.TOUCHES_KEY; + +import android.annotation.TargetApi; +import android.os.Build; +import android.util.Pair; +import com.facebook.common.logging.FLog; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableArray; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.WritableNativeArray; +import com.facebook.react.bridge.WritableNativeMap; +import com.facebook.react.uimanager.events.RCTEventEmitter; +import com.facebook.systrace.Systrace; +import java.util.HashSet; +import java.util.Set; +import javax.annotation.Nullable; + +@TargetApi(Build.VERSION_CODES.ECLAIR) +public class FabricEventEmitter implements RCTEventEmitter { + + private static final String TAG = FabricEventEmitter.class.getSimpleName(); + + private final FabricUIManager mUIManager; + + public FabricEventEmitter(FabricUIManager uiManager) { + mUIManager = uiManager; + } + + @Override + public void receiveEvent(int reactTag, String eventName, @Nullable WritableMap params) { + Systrace.beginSection( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "FabricEventEmitter.receiveEvent('" + eventName + "')"); + mUIManager.receiveEvent(reactTag, eventName, params); + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + + @Override + public void receiveTouches( + String eventTopLevelType, WritableArray touches, WritableArray changedIndices) { + Pair result = + TOP_TOUCH_END_KEY.equalsIgnoreCase(eventTopLevelType) + || TOP_TOUCH_CANCEL_KEY.equalsIgnoreCase(eventTopLevelType) + ? removeTouchesAtIndices(touches, changedIndices) + : touchSubsequence(touches, changedIndices); + + WritableArray changedTouches = result.first; + touches = result.second; + + for (int jj = 0; jj < changedTouches.size(); jj++) { + WritableMap touch = getWritableMap(changedTouches.getMap(jj)); + // Touch objects can fulfill the role of `DOM` `Event` objects if we set + // the `changedTouches`/`touches`. This saves allocations. + + touch.putArray(CHANGED_TOUCHES_KEY, copyWritableArray(changedTouches)); + touch.putArray(TOUCHES_KEY, copyWritableArray(touches)); + WritableMap nativeEvent = touch; + int rootNodeID = 0; + int target = nativeEvent.getInt(TARGET_KEY); + if (target < 1) { + FLog.e(TAG, "A view is reporting that a touch occurred on tag zero."); + } else { + rootNodeID = target; + } + + receiveEvent(rootNodeID, eventTopLevelType, touch); + } + } + + /** TODO T31905686 optimize this to avoid copying arrays */ + private WritableArray copyWritableArray(WritableArray array) { + WritableNativeArray ret = new WritableNativeArray(); + for (int i = 0; i < array.size(); i++) { + ret.pushMap(getWritableMap(array.getMap(i))); + } + return ret; + } + + /** + * Destroys `touches` by removing touch objects at indices `indices`. This is to maintain + * compatibility with W3C touch "end" events, where the active touches don't include the set that + * has just been "ended". + * + *

This method was originally in ReactNativeRenderer.js + * + *

TODO: this method is a copy from ReactNativeRenderer.removeTouchesAtIndices and it needs to + * be rewritten in a more efficient way, + * + * @param touches {@link WritableArray} Deserialized touch objects. + * @param indices {WritableArray} Indices to remove from `touches`. + * @return {Array} Subsequence of removed touch objects. + */ + private Pair removeTouchesAtIndices( + WritableArray touches, WritableArray indices) { + WritableArray rippedOut = new WritableNativeArray(); + // use an unsafe downcast to alias to nullable elements, + // so we can delete and then compact. + WritableArray tempTouches = new WritableNativeArray(); + Set rippedOutIndices = new HashSet<>(); + for (int i = 0; i < indices.size(); i++) { + int index = indices.getInt(i); + rippedOut.pushMap(getWritableMap(touches.getMap(index))); + rippedOutIndices.add(index); + } + for (int j = 0; j < touches.size(); j++) { + if (!rippedOutIndices.contains(j)) { + tempTouches.pushMap(getWritableMap(touches.getMap(j))); + } + } + + return new Pair<>(rippedOut, tempTouches); + } + + /** + * Selects a subsequence of `Touch`es, without destroying `touches`. + * + *

This method was originally in ReactNativeRenderer.js + * + * @param touches {@link WritableArray} Deserialized touch objects. + * @param changedIndices {@link WritableArray} Indices by which to pull subsequence. + * @return {Array} Subsequence of touch objects. + */ + private Pair touchSubsequence( + WritableArray touches, WritableArray changedIndices) { + WritableArray result = new WritableNativeArray(); + for (int i = 0; i < changedIndices.size(); i++) { + result.pushMap(getWritableMap(touches.getMap(changedIndices.getInt(i)))); + } + return new Pair<>(result, touches); + } + + /** + * TODO: this is required because the WritableNativeArray.getMap() returns a ReadableMap instead + * of the original writableMap. this will change in the near future. + * + * @param readableMap {@link ReadableMap} source map + */ + private WritableMap getWritableMap(ReadableMap readableMap) { + WritableNativeMap map = new WritableNativeMap(); + map.merge(readableMap); + return map; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricJSIModuleProvider.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricJSIModuleProvider.java new file mode 100644 index 000000000..4b484ac25 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricJSIModuleProvider.java @@ -0,0 +1,69 @@ +// Copyright 2004-present Facebook. All Rights Reserved. +package com.facebook.react.fabric; + +import com.facebook.react.fabric.jsi.Binding; +import com.facebook.react.fabric.jsi.ComponentFactoryDelegate; +import com.facebook.react.fabric.jsi.EventBeatManager; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.bridge.JSIModuleProvider; +import com.facebook.react.bridge.JavaScriptContextHolder; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.UIManager; +import com.facebook.react.bridge.queue.MessageQueueThread; +import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.events.EventDispatcher; +import com.facebook.systrace.Systrace; + +public class FabricJSIModuleProvider implements JSIModuleProvider { + + private final ReactInstanceManager mReactInstanceManager; + private final JavaScriptContextHolder mJSContext; + private final ReactApplicationContext mReactApplicationContext; + private final ComponentFactoryDelegate mComponentFactoryDelegate; + + public FabricJSIModuleProvider( + ReactInstanceManager reactInstanceManager, + ReactApplicationContext reactApplicationContext, + JavaScriptContextHolder jsContext, + ComponentFactoryDelegate componentFactoryDelegate) { + mReactInstanceManager = reactInstanceManager; + mReactApplicationContext = reactApplicationContext; + mJSContext = jsContext; + mComponentFactoryDelegate = componentFactoryDelegate; + } + + @Override + public UIManager get() { + final EventBeatManager eventBeatManager = + new EventBeatManager(mJSContext, mReactApplicationContext); + final UIManager uiManager = createUIManager(eventBeatManager); + Systrace.beginSection( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "FabricJSIModuleProvider.registerBinding"); + final FabricBinding binding = new Binding(); + MessageQueueThread jsMessageQueueThread = + mReactApplicationContext + .getCatalystInstance() + .getReactQueueConfiguration() + .getJSQueueThread(); + binding.register(mJSContext, (FabricBinder) uiManager, eventBeatManager, jsMessageQueueThread, + mComponentFactoryDelegate); + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + return uiManager; + } + + private UIManager createUIManager(EventBeatManager eventBeatManager) { + Systrace.beginSection( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "FabricJSIModuleProvider.createUIManager"); + UIManagerModule nativeModule = mReactApplicationContext.getNativeModule(UIManagerModule.class); + EventDispatcher eventDispatcher = nativeModule.getEventDispatcher(); + FabricUIManager fabricUIManager = + new FabricUIManager( + mReactApplicationContext, + nativeModule.getViewManagerRegistry_DO_NOT_USE(), + eventDispatcher, + eventBeatManager); + + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + return fabricUIManager; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java new file mode 100644 index 000000000..d68555969 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -0,0 +1,422 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.facebook.react.fabric; + +import static com.facebook.react.fabric.mounting.LayoutMetricsConversions.getMaxSize; +import static com.facebook.react.fabric.mounting.LayoutMetricsConversions.getMinSize; +import static com.facebook.react.fabric.mounting.LayoutMetricsConversions.getYogaMeasureMode; +import static com.facebook.react.fabric.mounting.LayoutMetricsConversions.getYogaSize; +import static com.facebook.infer.annotation.ThreadConfined.UI; +import static com.facebook.react.uimanager.common.UIManagerType.FABRIC; + +import android.annotation.SuppressLint; +import android.support.annotation.GuardedBy; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import com.facebook.common.logging.FLog; +import com.facebook.react.fabric.jsi.Binding; +import com.facebook.react.fabric.jsi.EventBeatManager; +import com.facebook.react.fabric.jsi.EventEmitterWrapper; +import com.facebook.react.fabric.jsi.FabricSoLoader; +import com.facebook.react.fabric.mounting.MountingManager; +import com.facebook.react.fabric.mounting.mountitems.BatchMountItem; +import com.facebook.react.fabric.mounting.mountitems.CreateMountItem; +import com.facebook.react.fabric.mounting.mountitems.DeleteMountItem; +import com.facebook.react.fabric.mounting.mountitems.DispatchCommandMountItem; +import com.facebook.react.fabric.mounting.mountitems.InsertMountItem; +import com.facebook.react.fabric.mounting.mountitems.MountItem; +import com.facebook.react.fabric.mounting.mountitems.RemoveMountItem; +import com.facebook.react.fabric.mounting.mountitems.UpdateEventEmitterMountItem; +import com.facebook.react.fabric.mounting.mountitems.UpdateLayoutMountItem; +import com.facebook.react.fabric.mounting.mountitems.UpdateLocalDataMountItem; +import com.facebook.react.fabric.mounting.mountitems.UpdatePropsMountItem; +import com.facebook.infer.annotation.Assertions; +import com.facebook.infer.annotation.ThreadConfined; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.bridge.GuardedRunnable; +import com.facebook.react.bridge.LifecycleEventListener; +import com.facebook.react.bridge.NativeMap; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableNativeMap; +import com.facebook.react.bridge.UIManager; +import com.facebook.react.bridge.UiThreadUtil; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.common.ReactConstants; +import com.facebook.react.modules.core.ReactChoreographer; +import com.facebook.react.uimanager.ReactRootViewTagGenerator; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.ViewManagerPropertyUpdater; +import com.facebook.react.uimanager.ViewManagerRegistry; +import com.facebook.react.uimanager.common.MeasureSpecProvider; +import com.facebook.react.uimanager.common.SizeMonitoringFrameLayout; +import com.facebook.react.uimanager.events.EventDispatcher; +import com.facebook.systrace.Systrace; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@SuppressLint("MissingNativeLoadLibrary") +public class FabricUIManager implements UIManager, FabricBinder, LifecycleEventListener { + + private static final String TAG = FabricUIManager.class.getSimpleName(); + + private static final Map sComponentNames = new HashMap<>(); + + static { + FabricSoLoader.staticInit(); + + // TODO T31905686: unify component names between JS - Android - iOS - C++ + sComponentNames.put("View", "RCTView"); + sComponentNames.put("Image", "RCTImageView"); + sComponentNames.put("ScrollView", "RCTScrollView"); + sComponentNames.put("ReactPerformanceLoggerFlag", "ReactPerformanceLoggerFlag"); + sComponentNames.put("Paragraph", "RCTText"); + sComponentNames.put("Text", "RCText"); + sComponentNames.put("RawText", "RCTRawText"); + sComponentNames.put("ActivityIndicatorView", "AndroidProgressBar"); + sComponentNames.put("ShimmeringView", "RKShimmeringView"); + sComponentNames.put("TemplateView", "RCTTemplateView"); + } + + private Binding mBinding; + private final ReactApplicationContext mReactApplicationContext; + private final MountingManager mMountingManager; + private final EventDispatcher mEventDispatcher; + private final ConcurrentHashMap mReactContextForRootTag = + new ConcurrentHashMap<>(); + private final EventBeatManager mEventBeatManager; + private final Object mMountItemsLock = new Object(); + + @GuardedBy("mMountItemsLock") + private List mMountItems = new ArrayList<>(); + + @ThreadConfined(UI) + private final DispatchUIFrameCallback mDispatchUIFrameCallback; + + @ThreadConfined(UI) + private boolean mIsMountingEnabled = true; + + public FabricUIManager( + ReactApplicationContext reactContext, + ViewManagerRegistry viewManagerRegistry, + EventDispatcher eventDispatcher, + EventBeatManager eventBeatManager) { + mDispatchUIFrameCallback = new DispatchUIFrameCallback(reactContext); + mReactApplicationContext = reactContext; + mMountingManager = new MountingManager(viewManagerRegistry); + mEventDispatcher = eventDispatcher; + mEventBeatManager = eventBeatManager; + mReactApplicationContext.addLifecycleEventListener(this); + } + + @Override + public int addRootView( + final T rootView, final WritableMap initialProps, final @Nullable String initialUITemplate) { + final int rootTag = ReactRootViewTagGenerator.getNextRootViewTag(); + ThemedReactContext reactContext = + new ThemedReactContext(mReactApplicationContext, rootView.getContext()); + mMountingManager.addRootView(rootTag, rootView); + mReactContextForRootTag.put(rootTag, reactContext); + mBinding.startSurface(rootTag, (NativeMap) initialProps); + updateRootLayoutSpecs(rootTag, rootView.getWidthMeasureSpec(), rootView.getHeightMeasureSpec()); + if (initialUITemplate != null) { + mBinding.renderTemplateToSurface(rootTag, initialUITemplate); + } + return rootTag; + } + + /** Method called when an event has been dispatched on the C++ side. */ + @DoNotStrip + public void onRequestEventBeat() { + mEventDispatcher.dispatchAllEvents(); + } + + @Override + public void removeRootView(int reactRootTag) { + // TODO T31905686: integrate with the unmounting of Fabric React Renderer. + mMountingManager.removeRootView(reactRootTag); + mReactContextForRootTag.remove(reactRootTag); + } + + @DoNotStrip + @SuppressWarnings("unused") + private MountItem createMountItem( + String componentName, int reactRootTag, int reactTag, boolean isVirtual) { + String component = sComponentNames.get(componentName); + if (component == null) { + throw new IllegalArgumentException("Unable to find component with name " + componentName); + } + ThemedReactContext reactContext = mReactContextForRootTag.get(reactRootTag); + if (reactContext == null) { + throw new IllegalArgumentException("Unable to find ReactContext for root: " + reactRootTag); + } + return new CreateMountItem(reactContext, component, reactTag, isVirtual); + } + + @Override + public void initialize() { + mEventDispatcher.registerEventEmitter(FABRIC, new FabricEventEmitter(this)); + mEventDispatcher.addBatchEventDispatchedListener(mEventBeatManager); + } + + @Override + public void onCatalystInstanceDestroy() { + mEventDispatcher.removeBatchEventDispatchedListener(mEventBeatManager); + mEventDispatcher.unregisterEventEmitter(FABRIC); + mBinding.unregister(); + ViewManagerPropertyUpdater.clear(); + } + + @DoNotStrip + private void preallocateView(final int rootTag, final String componentName) { + UiThreadUtil.runOnUiThread( + new GuardedRunnable(mReactApplicationContext) { + @Override + public void runGuarded() { + ThemedReactContext context = + Assertions.assertNotNull(mReactContextForRootTag.get(rootTag)); + String component = sComponentNames.get(componentName); + Assertions.assertNotNull(component); + mMountingManager.preallocateView(context, component); + } + }); + } + + @DoNotStrip + @SuppressWarnings("unused") + private MountItem removeMountItem(int reactTag, int parentReactTag, int index) { + return new RemoveMountItem(reactTag, parentReactTag, index); + } + + @DoNotStrip + @SuppressWarnings("unused") + private MountItem insertMountItem(int reactTag, int parentReactTag, int index) { + return new InsertMountItem(reactTag, parentReactTag, index); + } + + @DoNotStrip + @SuppressWarnings("unused") + private MountItem deleteMountItem(int reactTag) { + return new DeleteMountItem(reactTag); + } + + @DoNotStrip + @SuppressWarnings("unused") + private MountItem updateLayoutMountItem(int reactTag, int x, int y, int width, int height) { + return new UpdateLayoutMountItem(reactTag, x, y, width, height); + } + + @DoNotStrip + @SuppressWarnings("unused") + private MountItem updatePropsMountItem(int reactTag, ReadableNativeMap map) { + return new UpdatePropsMountItem(reactTag, map); + } + + @DoNotStrip + @SuppressWarnings("unused") + private MountItem updateLocalDataMountItem(int reactTag, ReadableNativeMap newLocalData) { + return new UpdateLocalDataMountItem(reactTag, newLocalData); + } + + @DoNotStrip + @SuppressWarnings("unused") + private MountItem updateEventEmitterMountItem(int reactTag, Object eventEmitter) { + return new UpdateEventEmitterMountItem(reactTag, (EventEmitterWrapper) eventEmitter); + } + + @DoNotStrip + @SuppressWarnings("unused") + private MountItem createBatchMountItem(MountItem[] items, int size) { + return new BatchMountItem(items, size); + } + + @DoNotStrip + @SuppressWarnings("unused") + private long measure( + String componentName, + ReadableNativeMap localData, + ReadableNativeMap props, + int minWidth, + int maxWidth, + int minHeight, + int maxHeight) { + + return mMountingManager.measure( + mReactApplicationContext, + componentName, + localData, + props, + getYogaSize(minWidth, maxWidth), + getYogaMeasureMode(minWidth, maxWidth), + getYogaSize(minHeight, maxHeight), + getYogaMeasureMode(minHeight, maxHeight)); + } + + /** + * This method enqueues UI operations directly to the UI thread. This might change in the future + * to enforce execution order using {@link ReactChoreographer#CallbackType}. + */ + @DoNotStrip + @SuppressWarnings("unused") + private void scheduleMountItems(final MountItem mountItems) { + synchronized (mMountItemsLock) { + mMountItems.add(mountItems); + } + + if (UiThreadUtil.isOnUiThread()) { + flushMountItems(); + } + } + + @UiThread + private void flushMountItems() { + if (!mIsMountingEnabled) { + FLog.w( + ReactConstants.TAG, + "Not flushing pending UI operations because of previously thrown Exception"); + return; + } + + try { + List mountItemsToDispatch; + synchronized (mMountItemsLock) { + if (mMountItems.isEmpty()) { + return; + } + mountItemsToDispatch = mMountItems; + mMountItems = new ArrayList<>(); + } + + Systrace.beginSection( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "FabricUIManager::mountViews (" + mountItemsToDispatch.size() + " batches)"); + for (MountItem mountItem : mountItemsToDispatch) { + mountItem.execute(mMountingManager); + } + + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } catch (Exception ex) { + FLog.i(ReactConstants.TAG, "Exception thrown when executing UIFrameGuarded", ex); + mIsMountingEnabled = false; + throw ex; + } + } + + @Override + public void setBinding(Binding binding) { + mBinding = binding; + } + + /** + * Updates the layout metrics of the root view based on the Measure specs received by parameters. + */ + @Override + public void updateRootLayoutSpecs( + final int rootTag, final int widthMeasureSpec, final int heightMeasureSpec) { + + // TODO T31905686: this should not run in a different thread. + // This is a workaround because a race condition that happens in core of RN. + // We are analyzing this and fixing it as part of another diff. + mReactApplicationContext.runOnJSQueueThread( + new GuardedRunnable(mReactApplicationContext) { + @Override + public void runGuarded() { + mBinding.setConstraints( + rootTag, + getMinSize(widthMeasureSpec), + getMaxSize(widthMeasureSpec), + getMinSize(heightMeasureSpec), + getMaxSize(heightMeasureSpec)); + } + }); + } + + public void receiveEvent(int reactTag, String eventName, @Nullable WritableMap params) { + EventEmitterWrapper eventEmitter = mMountingManager.getEventEmitter(reactTag); + if (eventEmitter == null) { + // This can happen if the view has disappeared from the screen (because of async events) + FLog.d(TAG, "Unable to invoke event: " + eventName + " for reactTag: " + reactTag); + return; + } + + eventEmitter.invoke(eventName, params); + } + + @Override + public void onHostResume() { + ReactChoreographer.getInstance() + .postFrameCallback(ReactChoreographer.CallbackType.DISPATCH_UI, mDispatchUIFrameCallback); + } + + @Override + public void onHostPause() { + ReactChoreographer.getInstance() + .removeFrameCallback(ReactChoreographer.CallbackType.DISPATCH_UI, mDispatchUIFrameCallback); + } + + @Override + public void onHostDestroy() {} + + @Override + public void dispatchCommand( + final int reactTag, final int commandId, final ReadableArray commandArgs) { + scheduleMountItems(new DispatchCommandMountItem(reactTag, commandId, commandArgs)); + } + + @Override + public void setJSResponder(int reactTag, boolean blockNativeResponder) { + // do nothing for now. + } + + @Override + public void clearJSResponder() { + // do nothing for now. + } + + @Override + public void profileNextBatch() { + // do nothing for now. + } + + @Override + public Map getPerformanceCounters() { + return new HashMap<>(); + } + + private class DispatchUIFrameCallback extends GuardedFrameCallback { + + private DispatchUIFrameCallback(ReactContext reactContext) { + super(reactContext); + } + + @Override + public void doFrameGuarded(long frameTimeNanos) { + if (!mIsMountingEnabled) { + FLog.w( + ReactConstants.TAG, + "Not flushing pending UI operations because of previously thrown Exception"); + return; + } + + try { + flushMountItems(); + } catch (Exception ex) { + FLog.i(ReactConstants.TAG, "Exception thrown when executing UIFrameGuarded", ex); + mIsMountingEnabled = false; + throw ex; + } finally { + ReactChoreographer.getInstance() + .postFrameCallback( + ReactChoreographer.CallbackType.DISPATCH_UI, mDispatchUIFrameCallback); + } + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/GuardedFrameCallback.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/GuardedFrameCallback.java new file mode 100644 index 000000000..2982bd472 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/GuardedFrameCallback.java @@ -0,0 +1,34 @@ +/** + * 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. + */ +package com.facebook.react.fabric; + +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.modules.core.ChoreographerCompat; + +public abstract class GuardedFrameCallback extends ChoreographerCompat.FrameCallback { + + private final ReactContext mReactContext; + + protected GuardedFrameCallback(ReactContext reactContext) { + mReactContext = reactContext; + } + + @Override + public final void doFrame(long frameTimeNanos) { + try { + doFrameGuarded(frameTimeNanos); + } catch (RuntimeException e) { + mReactContext.handleException(e); + } + } + + /** + * Like the standard doFrame but RuntimeExceptions will be caught and passed to {@link + * com.facebook.react.bridge.ReactContext#handleException(RuntimeException)}. + */ + protected abstract void doFrameGuarded(long frameTimeNanos); +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/Binding.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/Binding.java new file mode 100644 index 000000000..19ac38fcc --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/Binding.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.facebook.react.fabric.jsi; + +import android.annotation.SuppressLint; +import com.facebook.jni.HybridData; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.bridge.JavaScriptContextHolder; +import com.facebook.react.bridge.NativeMap; +import com.facebook.react.bridge.queue.MessageQueueThread; +import com.facebook.react.fabric.FabricBinder; +import com.facebook.react.fabric.FabricBinding; +import com.facebook.react.uimanager.PixelUtil; + +@DoNotStrip +@SuppressLint("MissingNativeLoadLibrary") +public class Binding implements FabricBinding { + + static { + FabricSoLoader.staticInit(); + } + + @DoNotStrip private final HybridData mHybridData; + + private static native HybridData initHybrid(); + + public Binding() { + mHybridData = initHybrid(); + } + + private native void installFabricUIManager( + long jsContextNativePointer, + Object uiManager, + EventBeatManager eventBeatManager, + MessageQueueThread jsMessageQueueThread, + ComponentFactoryDelegate componentsRegistry); + + public native void startSurface(int surfaceId, NativeMap initialProps); + + public native void renderTemplateToSurface(int surfaceId, String uiTemplate); + + public native void stopSurface(int surfaceId); + + public native void setPixelDensity(float pointScaleFactor); + + public native void setConstraints( + int rootTag, float minWidth, float maxWidth, float minHeight, float maxHeight); + + @Override + public void register( + JavaScriptContextHolder jsContext, + FabricBinder fabricModule, + EventBeatManager eventBeatManager, + MessageQueueThread jsMessageQueueThread, + ComponentFactoryDelegate componentFactoryDelegate) { + fabricModule.setBinding(this); + installFabricUIManager( + jsContext.get(), fabricModule, eventBeatManager, jsMessageQueueThread, componentFactoryDelegate); + setPixelDensity(PixelUtil.getDisplayMetricDensity()); + } + + private native void uninstallFabricUIManager(); + + @Override + public void unregister() { + uninstallFabricUIManager(); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/ComponentFactoryDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/ComponentFactoryDelegate.java new file mode 100644 index 000000000..b16d2038e --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/ComponentFactoryDelegate.java @@ -0,0 +1,27 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.fabric.jsi; + +import com.facebook.jni.HybridData; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.soloader.SoLoader; +import com.facebook.react.fabric.jsi.FabricSoLoader; + +@DoNotStrip +public class ComponentFactoryDelegate { + + static { + FabricSoLoader.staticInit(); + } + + @DoNotStrip + private final HybridData mHybridData; + + @DoNotStrip + private static native HybridData initHybrid(); + + public ComponentFactoryDelegate() { + mHybridData = initHybrid(); + } + +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/ComponentRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/ComponentRegistry.java new file mode 100644 index 000000000..b942af8dc --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/ComponentRegistry.java @@ -0,0 +1,25 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.fabric.jsi; + +import com.facebook.jni.HybridData; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.soloader.SoLoader; + +@DoNotStrip +public class ComponentRegistry { + + static { + FabricSoLoader.staticInit(); + } + + private final HybridData mHybridData; + + @DoNotStrip + private static native HybridData initHybrid(); + + public ComponentRegistry() { + mHybridData = initHybrid(); + } + +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/EventBeatManager.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/EventBeatManager.java new file mode 100644 index 000000000..c6b1c3336 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/EventBeatManager.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.facebook.react.fabric.jsi; + +import android.annotation.SuppressLint; +import com.facebook.jni.HybridData; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.bridge.JavaScriptContextHolder; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.events.BatchEventDispatchedListener; +import com.facebook.react.fabric.jsi.FabricSoLoader; + +/** + * Class that acts as a proxy between the list of EventBeats registered in C++ and the Android side. + */ +@SuppressLint("MissingNativeLoadLibrary") +public class EventBeatManager implements BatchEventDispatchedListener { + + static { + FabricSoLoader.staticInit(); + } + + @DoNotStrip private final HybridData mHybridData; + private final ReactApplicationContext mReactApplicationContext; + + private static native HybridData initHybrid(long jsContext); + + private native void beat(); + + public EventBeatManager( + JavaScriptContextHolder jsContext, ReactApplicationContext reactApplicationContext) { + mHybridData = initHybrid(jsContext.get()); + mReactApplicationContext = reactApplicationContext; + } + + @Override + public void onBatchEventDispatched() { + dispatchEventsAsync(); + } + + /** + * Induce a beat in the AsyncEventBeat, calling the JNI method {@link #beat()} in the JS thread. + */ + private void dispatchEventsAsync() { + if (mReactApplicationContext.isOnJSQueueThread()) { + beat(); + } else { + mReactApplicationContext.runOnJSQueueThread( + new Runnable() { + @Override + public void run() { + beat(); + } + }); + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/EventEmitterWrapper.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/EventEmitterWrapper.java new file mode 100644 index 000000000..11c78aec4 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/EventEmitterWrapper.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.facebook.react.fabric.jsi; + +import android.annotation.SuppressLint; +import android.support.annotation.Nullable; +import com.facebook.jni.HybridData; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.bridge.NativeMap; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.WritableNativeMap; +import com.facebook.react.fabric.jsi.FabricSoLoader; + +/** + * This class holds reference to the C++ EventEmitter object. Instances of this class are created on + * the Bindings.cpp, where the pointer to the C++ event emitter is set. + */ +@SuppressLint("MissingNativeLoadLibrary") +public class EventEmitterWrapper { + + static { + FabricSoLoader.staticInit(); + } + + @DoNotStrip private final HybridData mHybridData; + + private static native HybridData initHybrid(); + + private EventEmitterWrapper() { + mHybridData = initHybrid(); + } + + private native void invokeEvent(String eventName, NativeMap params); + + /** + * Invokes the execution of the C++ EventEmitter. + * + * @param eventName {@link String} name of the event to execute. + * @param params {@link WritableMap} payload of the event + */ + public void invoke(String eventName, @Nullable WritableMap params) { + NativeMap payload = params == null ? new WritableNativeMap() : (NativeMap) params; + invokeEvent(eventName, payload); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/FabricSoLoader.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/FabricSoLoader.java new file mode 100644 index 000000000..600cec050 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/FabricSoLoader.java @@ -0,0 +1,28 @@ +/** + * 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. + */ +package com.facebook.react.fabric.jsi; + +import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE; + +import com.facebook.soloader.SoLoader; +import com.facebook.systrace.Systrace; + +public class FabricSoLoader { + private static boolean sDidInit = false; + + public static synchronized void staticInit() { + if (sDidInit) { + return; + } + sDidInit = true; + + Systrace.beginSection( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "FabricSoLoader.staticInit::load:fabricjni"); + SoLoader.loadLibrary("fabricjni"); + Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/AsyncEventBeat.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/AsyncEventBeat.h new file mode 100644 index 000000000..ffffe5cae --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/AsyncEventBeat.h @@ -0,0 +1,59 @@ +// Copyright 2004-present Facebook. All Rights Reserved. +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +#pragma once + +#include "EventBeatManager.h" +#include +#include + +namespace facebook { +namespace react { + +namespace { + + class AsyncEventBeat: + public EventBeat { + + private: + EventBeatManager *eventBeatManager_; + jsi::Runtime *runtime_; + jni::global_ref javaUIManager_; + + public: + + friend class EventBeatManager; + + AsyncEventBeat(EventBeatManager* eventBeatManager, jsi::Runtime *runtime, jni::global_ref javaUIManager) { + eventBeatManager_ = eventBeatManager; + runtime_ = runtime; + javaUIManager_ = javaUIManager; + eventBeatManager->registerEventBeat(this); + } + + ~AsyncEventBeat() { + eventBeatManager_->unregisterEventBeat(this); + } + + void induce() const override { + beat(*runtime_); + } + + void request() const override { + bool alreadyRequested = isRequested_; + EventBeat::request(); + if (!alreadyRequested) { + // Notifies java side that an event will be dispatched (e.g. LayoutEvent) + static auto onRequestEventBeat = + jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") + ->getMethod("onRequestEventBeat"); + onRequestEventBeat(javaUIManager_); + } + } + }; + +} + +} +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/BUCK b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/BUCK new file mode 100644 index 000000000..2b35c23d0 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/BUCK @@ -0,0 +1,47 @@ +load("@fbsource//tools/build_defs:glob_defs.bzl", "subdir_glob") +load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "FBJNI_TARGET", "react_native_target", "react_native_xplat_target", "rn_xplat_cxx_library") + +rn_xplat_cxx_library( + name = "jni", + srcs = glob(["*.cpp"]), + headers = glob(["*.h"]), + header_namespace = "", + exported_headers = subdir_glob( + [ + ("", "**/*.h"), + ], + prefix = "react/fabric", + ), + compiler_flags = [ + "-Wall", + "-fexceptions", + "-std=gnu++1y", + "-frtti", + ], + fbandroid_allow_jni_merging = True, + platforms = (ANDROID), + preprocessor_flags = [ + "-DLOG_TAG=\"ReactNative\"", + "-DWITH_FBSYSTRACE=1", + ], + soname = "libfabricjni.$(ext)", + visibility = ["PUBLIC"], + deps = [ + react_native_xplat_target("config:config"), + react_native_xplat_target("fabric/uimanager:uimanager"), + react_native_xplat_target("fabric/components/activityindicator:activityindicator"), + react_native_xplat_target("fabric/components/scrollview:scrollview"), + react_native_xplat_target("fabric/components/image:image"), + react_native_xplat_target("fabric/components/text:text"), + react_native_target("jni/react/jni:jni"), + "xplat//ReactNative/fabric/components/ReactPerformanceLogger:ReactPerformanceLogger", + "xplat//ReactNative/fabric/components/ShimmeringView:ShimmeringView", + "xplat//ReactNative/fabric/components/TemplateView:TemplateView", + "xplat//fbsystrace:fbsystrace", + "xplat//folly:molly", + "xplat//jsi:JSIDynamic", + "xplat//jsi:jsi", + "xplat//third-party/linker_lib:atomic", + FBJNI_TARGET, + ], +) diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/Binding.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/Binding.cpp new file mode 100644 index 000000000..5229202f1 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/Binding.cpp @@ -0,0 +1,376 @@ +// Copyright 2004-present Facebook. All Rights Reserved. +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +#include "AsyncEventBeat.h" +#include "Binding.h" +#include "EventEmitterWrapper.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace facebook::jni; +using namespace facebook::jsi; + +namespace facebook { +namespace react { + +namespace { + + struct JMountItem : public JavaClass { + static constexpr auto kJavaDescriptor = "Lcom/facebook/react/fabric/mounting/mountitems/MountItem;"; + }; + + static constexpr auto UIManagerJavaDescriptor = "com/facebook/react/fabric/FabricUIManager"; + +} + +jni::local_ref Binding::initHybrid( + jni::alias_ref) { + return makeCxxInstance(); +} + +void Binding::startSurface(jint surfaceId, NativeMap *initialProps) { + if (scheduler_) { + scheduler_->startSurface(surfaceId, "", initialProps->consume()); + } +} + +void Binding::renderTemplateToSurface(jint surfaceId, jstring uiTemplate) { + if (scheduler_) { + auto env = Environment::current(); + const char *nativeString = env->GetStringUTFChars(uiTemplate, JNI_FALSE); + scheduler_->renderTemplateToSurface(surfaceId, nativeString); + env->ReleaseStringUTFChars(uiTemplate, nativeString); + } +} + +void Binding::stopSurface(jint surfaceId){ + if (scheduler_) { + scheduler_->stopSurface(surfaceId); + } +} + +void Binding::setConstraints(jint rootTag, jfloat minWidth, jfloat maxWidth, jfloat minHeight, jfloat maxHeight) { + if (scheduler_) { + auto minimumSize = Size {minWidth / pointScaleFactor_, minHeight / pointScaleFactor_}; + auto maximumSize = Size {maxWidth / pointScaleFactor_, maxHeight / pointScaleFactor_}; + + LayoutContext context; + context.pointScaleFactor = { pointScaleFactor_ }; + LayoutConstraints constraints = {}; + constraints.minimumSize = minimumSize; + constraints.maximumSize = maximumSize; + + scheduler_->constraintSurfaceLayout(rootTag, constraints, context); + } +} + +void Binding::installFabricUIManager(jlong jsContextNativePointer, jni::alias_ref javaUIManager, EventBeatManager* eventBeatManager, jni::alias_ref jsMessageQueueThread, ComponentFactoryDelegate* componentsRegistry) { + Runtime* runtime = (Runtime*) jsContextNativePointer; + + javaUIManager_ = make_global(javaUIManager); + + SharedContextContainer contextContainer = std::make_shared(); + + auto sharedJSMessageQueueThread = std::make_shared (jsMessageQueueThread); + RuntimeExecutor runtimeExecutor = [runtime, sharedJSMessageQueueThread](std::function &&callback) { + sharedJSMessageQueueThread->runOnQueue([runtime, callback = std::move(callback)]() { + callback(*runtime); + }); + }; + + // TODO: T31905686 Create synchronous Event Beat + jni::global_ref localJavaUIManager = javaUIManager_; + EventBeatFactory synchronousBeatFactory = [eventBeatManager, runtime, localJavaUIManager]() mutable { + return std::make_unique(eventBeatManager, runtime, localJavaUIManager); + }; + + EventBeatFactory asynchronousBeatFactory = [eventBeatManager, runtime, localJavaUIManager]() mutable { + return std::make_unique(eventBeatManager, runtime, localJavaUIManager); + }; + + // TODO: Provide non-empty impl for ReactNativeConfig. + std::shared_ptr config = std::make_shared(); + contextContainer->registerInstance(config, "ReactNativeConfig"); + contextContainer->registerInstance(synchronousBeatFactory, "synchronous"); + contextContainer->registerInstance(asynchronousBeatFactory, "asynchronous"); + contextContainer->registerInstance(javaUIManager_, "FabricUIManager"); + contextContainer->registerInstance(runtimeExecutor, "runtime-executor"); + + scheduler_ = std::make_shared(contextContainer, componentsRegistry->buildRegistryFunction); + + scheduler_->setDelegate(this); +} + +void Binding::uninstallFabricUIManager() { + scheduler_ = nullptr; + javaUIManager_ = nullptr; +} + +local_ref getPlatformComponentName(const ShadowView &shadowView) { + local_ref componentName; + auto newViewProps = std::dynamic_pointer_cast(shadowView.props); + + if (newViewProps && newViewProps->yogaStyle.flexDirection == YGFlexDirectionRow) { + componentName = make_jstring("AndroidHorizontalScrollView"); + } else { + componentName = make_jstring(shadowView.componentName); + } + return componentName; +} + +local_ref createCreateMountItem(const jni::global_ref &javaUIManager, const ShadowViewMutation &mutation, const Tag rootTag) { + static auto createJavaInstruction = + jni::findClassStatic(UIManagerJavaDescriptor) + ->getMethod(jstring,jint,jint,jboolean)>("createMountItem"); + + auto newChildShadowView = mutation.newChildShadowView; + + local_ref componentName = getPlatformComponentName(newChildShadowView); + + jboolean isVirtual = newChildShadowView.layoutMetrics == EmptyLayoutMetrics; + + return createJavaInstruction(javaUIManager, componentName.get(), rootTag, newChildShadowView.tag, isVirtual); +} + +local_ref createUpdateEventEmitterMountItem(const jni::global_ref &javaUIManager, const ShadowViewMutation &mutation) { + if (!mutation.newChildShadowView.eventEmitter) { + return nullptr; + } + SharedEventEmitter eventEmitter = mutation.newChildShadowView.eventEmitter; + + // Do not hold a reference to javaEventEmitter from the C++ side. + auto javaEventEmitter = EventEmitterWrapper::newObjectJavaArgs(); + EventEmitterWrapper* cEventEmitter = cthis(javaEventEmitter); + cEventEmitter->eventEmitter = eventEmitter; + + static auto updateEventEmitterInstruction = + jni::findClassStatic(UIManagerJavaDescriptor) + ->getMethod(jint, jobject)>("updateEventEmitterMountItem"); + + return updateEventEmitterInstruction(javaUIManager, mutation.newChildShadowView.tag, javaEventEmitter.get()); +} + +local_ref createUpdatePropsMountItem(const jni::global_ref &javaUIManager, const ShadowViewMutation &mutation) { + auto shadowView = mutation.newChildShadowView; + auto newViewProps = *std::dynamic_pointer_cast(shadowView.props); + + // TODO: move props from map to a typed object. + auto rawProps = shadowView.props->rawProps; + folly::dynamic newProps = folly::dynamic::object(); + for (auto element : rawProps) { + newProps[element.first] = element.second; + } + + local_ref readableMap = ReadableNativeMap::newObjectCxxArgs(newProps); + + static auto updatePropsInstruction = + jni::findClassStatic(UIManagerJavaDescriptor) + ->getMethod(jint,ReadableNativeMap::javaobject)>("updatePropsMountItem"); + + return updatePropsInstruction(javaUIManager, + mutation.newChildShadowView.tag, + readableMap.get()); +} + +local_ref createUpdateLayoutMountItem(const jni::global_ref &javaUIManager, const ShadowViewMutation &mutation) { + auto oldChildShadowView = mutation.oldChildShadowView; + auto newChildShadowView = mutation.newChildShadowView; + + if (newChildShadowView.layoutMetrics != EmptyLayoutMetrics && oldChildShadowView.layoutMetrics != newChildShadowView.layoutMetrics) { + static auto updateLayoutInstruction = + jni::findClassStatic(UIManagerJavaDescriptor) + ->getMethod(jint, jint, jint, jint, jint)>("updateLayoutMountItem"); + auto layoutMetrics = newChildShadowView.layoutMetrics; + auto pointScaleFactor = layoutMetrics.pointScaleFactor; + auto frame = layoutMetrics.frame; + + int x = round(frame.origin.x * pointScaleFactor); + int y = round(frame.origin.y * pointScaleFactor); + int w = round(frame.size.width * pointScaleFactor); + int h = round(frame.size.height * pointScaleFactor); + return updateLayoutInstruction(javaUIManager, newChildShadowView.tag, x, y, w, h); + } + + return nullptr; +} + +local_ref createInsertMountItem(const jni::global_ref &javaUIManager, const ShadowViewMutation &mutation) { + static auto insertInstruction = + jni::findClassStatic(UIManagerJavaDescriptor) + ->getMethod(jint,jint,jint)>("insertMountItem"); + + return insertInstruction(javaUIManager, mutation.newChildShadowView.tag, mutation.parentShadowView.tag, mutation.index); +} + +local_ref createUpdateLocalData(const jni::global_ref &javaUIManager, const ShadowViewMutation &mutation) { + static auto updateLocalDataInstruction = + jni::findClassStatic(UIManagerJavaDescriptor) + ->getMethod(jint, ReadableNativeMap::javaobject)>("updateLocalDataMountItem"); + + auto localData = mutation.newChildShadowView.localData; + + folly::dynamic newLocalData = folly::dynamic::object(); + if (localData) { + newLocalData = localData->getDynamic(); + } + local_ref readableMap = ReadableNativeMap::newObjectCxxArgs(newLocalData); + + return updateLocalDataInstruction(javaUIManager, mutation.newChildShadowView.tag, readableMap.get()); +} + +local_ref createRemoveMountItem(const jni::global_ref &javaUIManager, const ShadowViewMutation &mutation) { + static auto removeInstruction = + jni::findClassStatic(UIManagerJavaDescriptor) + ->getMethod(jint,jint,jint)>("removeMountItem"); + + return removeInstruction(javaUIManager, mutation.oldChildShadowView.tag, mutation.parentShadowView.tag, mutation.index); +} + +local_ref createDeleteMountItem(const jni::global_ref &javaUIManager, const ShadowViewMutation &mutation) { + static auto deleteInstruction = + jni::findClassStatic(UIManagerJavaDescriptor) + ->getMethod(jint)>("deleteMountItem"); + + return deleteInstruction(javaUIManager, mutation.oldChildShadowView.tag); +} + +void Binding::schedulerDidFinishTransaction(const Tag rootTag, const ShadowViewMutationList &mutations) { + SystraceSection s("FabricUIManager::schedulerDidFinishTransaction"); + std::vector> queue; + // Upper bound estimation of mount items to be delivered to Java side. + int size = mutations.size() * 3 + 42; + + local_ref> mountItemsArray = JArrayClass::newArray(size); + + auto mountItems = *(mountItemsArray); + + int position = 0; + for (const auto &mutation : mutations) { + auto oldChildShadowView = mutation.oldChildShadowView; + auto newChildShadowView = mutation.newChildShadowView; + + bool isVirtual = newChildShadowView.layoutMetrics == EmptyLayoutMetrics && + oldChildShadowView.layoutMetrics == EmptyLayoutMetrics; + + switch (mutation.type) { + case ShadowViewMutation::Create: { + mountItems[position++] = createCreateMountItem(javaUIManager_, mutation, rootTag); + break; + } + case ShadowViewMutation::Remove: { + if (!isVirtual) { + mountItems[position++] = createRemoveMountItem(javaUIManager_, mutation); + } + break; + } + case ShadowViewMutation::Delete: { + mountItems[position++] = createDeleteMountItem(javaUIManager_, mutation); + break; + } + case ShadowViewMutation::Update: { + if (!isVirtual) { + if (mutation.oldChildShadowView.props != mutation.newChildShadowView.props) { + mountItems[position++] = createUpdatePropsMountItem(javaUIManager_, mutation); + } + if (mutation.oldChildShadowView.localData != mutation.newChildShadowView.localData) { + mountItems[position++] = createUpdateLocalData(javaUIManager_, mutation); + } + + auto updateLayoutMountItem = createUpdateLayoutMountItem(javaUIManager_, mutation); + if (updateLayoutMountItem) { + mountItems[position++] = updateLayoutMountItem; + } + } + + if (mutation.oldChildShadowView.eventEmitter != mutation.newChildShadowView.eventEmitter) { + auto updateEventEmitterMountItem = createUpdateEventEmitterMountItem(javaUIManager_, mutation); + if (updateEventEmitterMountItem) { + mountItems[position++] = updateEventEmitterMountItem; + } + } + break; + } + case ShadowViewMutation::Insert: { + if (!isVirtual) { + mountItems[position++] = createInsertMountItem(javaUIManager_, mutation); + + mountItems[position++] = createUpdatePropsMountItem(javaUIManager_, mutation); + + auto updateLayoutMountItem = createUpdateLayoutMountItem(javaUIManager_, mutation); + if (updateLayoutMountItem) { + mountItems[position++] = updateLayoutMountItem; + } + + if (mutation.newChildShadowView.localData) { + mountItems[position++] = createUpdateLocalData(javaUIManager_, mutation); + } + } + + auto updateEventEmitterMountItem = createUpdateEventEmitterMountItem(javaUIManager_, mutation); + if (updateEventEmitterMountItem) { + mountItems[position++] = updateEventEmitterMountItem; + } + break; + } + default: { + break; + } + } + } + + static auto createMountItemsBatchContainer = + jni::findClassStatic(UIManagerJavaDescriptor) + ->getMethod(jtypeArray,jint)>("createBatchMountItem"); + + auto batch = createMountItemsBatchContainer(javaUIManager_, mountItemsArray.get(), position); + + static auto scheduleMountItems = + jni::findClassStatic(UIManagerJavaDescriptor) + ->getMethod("scheduleMountItems"); + + scheduleMountItems(javaUIManager_, batch.get()); +} + +void Binding::setPixelDensity(float pointScaleFactor) { + pointScaleFactor_ = pointScaleFactor; +} + +void Binding::schedulerDidRequestPreliminaryViewAllocation(const SurfaceId surfaceId, const ComponentName componentName, bool isLayoutable, const ComponentHandle componentHandle) { + if (isLayoutable) { + static auto preallocateView = + jni::findClassStatic(UIManagerJavaDescriptor) + ->getMethod("preallocateView"); + + preallocateView(javaUIManager_, surfaceId, make_jstring(componentName).get()); + } +} + +void Binding::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", Binding::initHybrid), + makeNativeMethod("installFabricUIManager", Binding::installFabricUIManager), + makeNativeMethod("startSurface", Binding::startSurface), + makeNativeMethod("renderTemplateToSurface", Binding::renderTemplateToSurface), + makeNativeMethod("stopSurface", Binding::stopSurface), + makeNativeMethod("setConstraints", Binding::setConstraints), + makeNativeMethod("setPixelDensity", Binding::setPixelDensity), + makeNativeMethod("uninstallFabricUIManager", Binding::uninstallFabricUIManager) + }); +} + +} +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/Binding.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/Binding.h new file mode 100644 index 000000000..1d854196c --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/Binding.h @@ -0,0 +1,60 @@ +// Copyright 2004-present Facebook. All Rights Reserved. +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +#pragma once + +#include "ComponentFactoryDelegate.h" +#include "EventBeatManager.h" +#include +#include +#include +#include +#include +#include +#include + +namespace facebook { +namespace react { + +class Instance; + +class Binding : public jni::HybridClass, public SchedulerDelegate { +public: + constexpr static const char *const kJavaDescriptor = + "Lcom/facebook/react/fabric/jsi/Binding;"; + + static void registerNatives(); + + jni::global_ref javaUIManager_; + + std::shared_ptr scheduler_; + + float pointScaleFactor_ = 1; + +private: + + void setConstraints(jint rootTag, jfloat minWidth, jfloat maxWidth, jfloat minHeight, jfloat maxHeight); + + static jni::local_ref initHybrid(jni::alias_ref); + + void installFabricUIManager(jlong jsContextNativePointer, jni::alias_ref javaUIManager, EventBeatManager* eventBeatManager, jni::alias_ref jsMessageQueueThread, ComponentFactoryDelegate* componentsRegistry); + + void startSurface(jint surfaceId, NativeMap *initialProps); + + void renderTemplateToSurface(jint surfaceId, jstring uiTemplate); + + void stopSurface(jint surfaceId); + + void schedulerDidFinishTransaction(const Tag rootTag, const ShadowViewMutationList &mutations); + + void schedulerDidRequestPreliminaryViewAllocation(const SurfaceId surfaceId, const ComponentName componentName, bool isLayoutable, const ComponentHandle componentHandle); + + void setPixelDensity(float pointScaleFactor); + + void uninstallFabricUIManager(); + +}; + +} +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/ComponentFactoryDelegate.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/ComponentFactoryDelegate.cpp new file mode 100644 index 000000000..f9448bb4c --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/ComponentFactoryDelegate.cpp @@ -0,0 +1,27 @@ +// Copyright 2004-present Facebook. All Rights Reserved. +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +#include "ComponentFactoryDelegate.h" +#include +#include +#include +#include + +using namespace facebook::jsi; + +namespace facebook { +namespace react { + +jni::local_ref ComponentFactoryDelegate::initHybrid( + jni::alias_ref) { + return makeCxxInstance(); +} + +void ComponentFactoryDelegate::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", ComponentFactoryDelegate::initHybrid), + }); +} + +}} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/ComponentFactoryDelegate.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/ComponentFactoryDelegate.h new file mode 100644 index 000000000..389b94aaf --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/ComponentFactoryDelegate.h @@ -0,0 +1,38 @@ +// Copyright 2004-present Facebook. All Rights Reserved. +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +using namespace facebook::jsi; + +namespace facebook { +namespace react { + +class Instance; + +class ComponentFactoryDelegate : public jni::HybridClass { +public: + constexpr static const char *const kJavaDescriptor = + "Lcom/facebook/react/fabric/jsi/ComponentFactoryDelegate;"; + + static void registerNatives(); + + ComponentRegistryFactory buildRegistryFunction; + +private: + + static jni::local_ref initHybrid(jni::alias_ref); + +}; + +} +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/EventBeatManager.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/EventBeatManager.cpp new file mode 100644 index 000000000..b72e9c0ae --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/EventBeatManager.cpp @@ -0,0 +1,47 @@ +// Copyright 2004-present Facebook. All Rights Reserved. +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +#include "EventBeatManager.h" +#include +using namespace facebook::jni; + +namespace facebook { +namespace react { + +EventBeatManager::EventBeatManager(Runtime* runtime, jni::alias_ref jhybridobject) : runtime_(runtime), jhybridobject_(jhybridobject) { } + +jni::local_ref EventBeatManager::initHybrid( + jni::alias_ref jhybridobject, jlong jsContext) { + return makeCxxInstance((Runtime *) jsContext, jhybridobject); +} + +void EventBeatManager::registerEventBeat(EventBeat *eventBeat) const { + std::lock_guard lock(mutex_); + + registeredEventBeats_.insert(eventBeat); +} + +void EventBeatManager::unregisterEventBeat(EventBeat *eventBeat) const { + std::lock_guard lock(mutex_); + + registeredEventBeats_.erase(eventBeat); +} + +void EventBeatManager::beat() { + std::lock_guard lock(mutex_); + + for (const auto eventBeat : registeredEventBeats_) { + eventBeat->beat(*runtime_); + } +} + +void EventBeatManager::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", EventBeatManager::initHybrid), + makeNativeMethod("beat", EventBeatManager::beat), + }); +} + +} +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/EventBeatManager.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/EventBeatManager.h new file mode 100644 index 000000000..ee2d60319 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/EventBeatManager.h @@ -0,0 +1,50 @@ +// Copyright 2004-present Facebook. All Rights Reserved. +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +#pragma once + +#include +#include +#include +#include +#include + +using namespace facebook::jsi; + +namespace facebook { +namespace react { + +class Instance; + +class EventBeatManager : public jni::HybridClass { +public: + constexpr static const char *const kJavaDescriptor = + "Lcom/facebook/react/fabric/jsi/EventBeatManager;"; + + static void registerNatives(); + + void registerEventBeat(EventBeat *eventBeat) const; + + void unregisterEventBeat(EventBeat *eventBeat) const; + + void beat(); + + EventBeatManager(Runtime* runtime, jni::alias_ref jhybridobject); + +private: + + Runtime* runtime_; + + jni::alias_ref jhybridobject_; + + mutable std::unordered_set registeredEventBeats_ {}; // Protected by `mutex_` + + mutable std::mutex mutex_; + + static jni::local_ref initHybrid(jni::alias_ref jhybridobject, jlong jsContext); + +}; + +} +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/EventEmitterWrapper.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/EventEmitterWrapper.cpp new file mode 100644 index 000000000..81f56fc63 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/EventEmitterWrapper.cpp @@ -0,0 +1,30 @@ +// Copyright 2004-present Facebook. All Rights Reserved. +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +#include "EventEmitterWrapper.h" +#include + +using namespace facebook::jni; + +namespace facebook { +namespace react { + +jni::local_ref EventEmitterWrapper::initHybrid( + jni::alias_ref) { + return makeCxxInstance(); +} + +void EventEmitterWrapper::invokeEvent(std::string eventName, NativeMap *payload) { + eventEmitter->dispatchEvent(eventName, payload->consume(), EventPriority::AsynchronousBatched); +} + +void EventEmitterWrapper::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", EventEmitterWrapper::initHybrid), + makeNativeMethod("invokeEvent", EventEmitterWrapper::invokeEvent), + }); +} + +} +} \ No newline at end of file diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/EventEmitterWrapper.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/EventEmitterWrapper.h new file mode 100644 index 000000000..ec32f73db --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/EventEmitterWrapper.h @@ -0,0 +1,34 @@ +// Copyright 2004-present Facebook. All Rights Reserved. +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +#pragma once + +#include +#include +#include + +namespace facebook { +namespace react { + +class Instance; + +class EventEmitterWrapper : public jni::HybridClass { +public: + constexpr static const char *const kJavaDescriptor = + "Lcom/facebook/react/fabric/jsi/EventEmitterWrapper;"; + + static void registerNatives(); + + SharedEventEmitter eventEmitter; + + void invokeEvent(std::string eventName, NativeMap *params); + +private: + + static jni::local_ref initHybrid(jni::alias_ref); + +}; + +} +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/OnLoad.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/OnLoad.cpp new file mode 100644 index 000000000..63f4184cf --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/OnLoad.cpp @@ -0,0 +1,20 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +#include +#include + +#include "Binding.h" +#include "EventBeatManager.h" +#include "EventEmitterWrapper.h" + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { + return facebook::xplat::initialize(vm, [] { + facebook::react::Binding::registerNatives(); + facebook::react::EventBeatManager::registerNatives(); + facebook::react::EventEmitterWrapper::registerNatives(); + facebook::react::ComponentFactoryDelegate::registerNatives(); + }); +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ContextBasedViewPool.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ContextBasedViewPool.java new file mode 100644 index 000000000..ed2a54927 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ContextBasedViewPool.java @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.facebook.react.fabric.mounting; + +import android.support.annotation.UiThread; +import android.view.View; +import com.facebook.react.bridge.UiThreadUtil; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.ViewManagerRegistry; +import java.util.WeakHashMap; + +/** Class that provides pool for views based on {@link ThemedReactContext}. */ +final class ContextBasedViewPool { + private final WeakHashMap mContextViewPoolHashMap = + new WeakHashMap<>(); + private final ViewManagerRegistry mViewManagerRegistry; + + ContextBasedViewPool(ViewManagerRegistry viewManagerRegistry) { + mViewManagerRegistry = viewManagerRegistry; + } + + @UiThread + void createView(ThemedReactContext context, String componentName) { + UiThreadUtil.assertOnUiThread(); + getViewPool(context).createView(componentName, context); + } + + @UiThread + View getOrCreateView(String componentName, ThemedReactContext context) { + UiThreadUtil.assertOnUiThread(); + return getViewPool(context).getOrCreateView(componentName, context); + } + + @UiThread + void returnToPool(ThemedReactContext context, String componentName, View view) { + UiThreadUtil.assertOnUiThread(); + getViewPool(context).returnToPool(componentName, view); + } + + @UiThread + private ViewPool getViewPool(ThemedReactContext context) { + ViewPool pool = mContextViewPoolHashMap.get(context); + if (pool == null) { + pool = new ViewPool(mViewManagerRegistry); + mContextViewPoolHashMap.put(context, pool); + } + return pool; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/LayoutMetricsConversions.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/LayoutMetricsConversions.java new file mode 100644 index 000000000..01365f878 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/LayoutMetricsConversions.java @@ -0,0 +1,57 @@ +/** + * 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. + */ +package com.facebook.react.fabric.mounting; + +import static android.view.View.MeasureSpec.EXACTLY; + +import android.view.View; +import com.facebook.react.uimanager.PixelUtil; +import com.facebook.yoga.YogaMeasureMode; + +public class LayoutMetricsConversions { + + // Represents the Layout constraint mode "undefined" from React side. + public static final int REACT_CONSTRAINT_UNDEFINED = -2147483648; + + public static float getMinSize(int viewMeasureSpec) { + int mode = View.MeasureSpec.getMode(viewMeasureSpec); + int size = View.MeasureSpec.getSize(viewMeasureSpec); + + return mode == EXACTLY ? size : 0f; + } + + public static float getMaxSize(int viewMeasureSpec) { + int mode = View.MeasureSpec.getMode(viewMeasureSpec); + int size = View.MeasureSpec.getSize(viewMeasureSpec); + + return mode == View.MeasureSpec.UNSPECIFIED ? REACT_CONSTRAINT_UNDEFINED : size; + } + + public static float getYogaSize(float minSize, float maxSize) { + float yogaSize; + if (minSize == maxSize) { + yogaSize = PixelUtil.toPixelFromDIP(maxSize); + } else if (maxSize == REACT_CONSTRAINT_UNDEFINED) { + yogaSize = 0; + } else { + yogaSize = PixelUtil.toPixelFromDIP(maxSize); + } + return yogaSize; + } + + public static YogaMeasureMode getYogaMeasureMode(float minSize, float maxSize) { + YogaMeasureMode yogaMeasureMode; + if (minSize == maxSize) { + yogaMeasureMode = YogaMeasureMode.EXACTLY; + } else if (maxSize == REACT_CONSTRAINT_UNDEFINED) { + yogaMeasureMode = YogaMeasureMode.UNDEFINED; + } else { + yogaMeasureMode = YogaMeasureMode.AT_MOST; + } + return yogaMeasureMode; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java new file mode 100644 index 000000000..0e3092173 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java @@ -0,0 +1,334 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.facebook.react.fabric.mounting; + +import android.content.Context; +import android.support.annotation.AnyThread; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import com.facebook.react.fabric.FabricUIManager; +import com.facebook.react.fabric.jsi.EventEmitterWrapper; +import com.facebook.react.fabric.mounting.mountitems.MountItem; +import com.facebook.infer.annotation.Assertions; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableNativeMap; +import com.facebook.react.bridge.SoftAssertions; +import com.facebook.react.bridge.UiThreadUtil; +import com.facebook.react.uimanager.IllegalViewOperationException; +import com.facebook.react.uimanager.ReactStylesDiffMap; +import com.facebook.react.uimanager.RootView; +import com.facebook.react.uimanager.RootViewManager; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.ViewGroupManager; +import com.facebook.react.uimanager.ViewManager; +import com.facebook.react.uimanager.ViewManagerRegistry; +import com.facebook.react.uimanager.common.SizeMonitoringFrameLayout; +import com.facebook.yoga.YogaMeasureMode; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Class responsible for actually dispatching view updates enqueued via {@link + * FabricUIManager#scheduleMountItems(int, MountItem[])} on the UI thread. + */ +public class MountingManager { + + private final ConcurrentHashMap mTagToViewState; + private final ViewManagerRegistry mViewManagerRegistry; + private final RootViewManager mRootViewManager = new RootViewManager(); + private final ContextBasedViewPool mViewPool; + + public MountingManager(ViewManagerRegistry viewManagerRegistry) { + mTagToViewState = new ConcurrentHashMap<>(); + mViewManagerRegistry = viewManagerRegistry; + mViewPool = new ContextBasedViewPool(viewManagerRegistry); + } + + @UiThread + public void addRootView(int reactRootTag, SizeMonitoringFrameLayout rootView) { + if (rootView.getId() != View.NO_ID) { + throw new IllegalViewOperationException( + "Trying to add a root view with an explicit id already set. React Native uses " + + "the id field to track react tags and will overwrite this field. If that is fine, " + + "explicitly overwrite the id field to View.NO_ID before calling addRootView."); + } + + mTagToViewState.put( + reactRootTag, new ViewState(reactRootTag, rootView, mRootViewManager, true)); + rootView.setId(reactRootTag); + } + + /** Releases all references to given native View. */ + @UiThread + private void dropView(View view) { + UiThreadUtil.assertOnUiThread(); + + int reactTag = view.getId(); + ViewState state = getViewState(reactTag); + ViewManager viewManager = state.mViewManager; + + if (!state.mIsRoot && viewManager != null) { + // For non-root views we notify viewmanager with {@link ViewManager#onDropInstance} + viewManager.onDropViewInstance(view); + } + if (view instanceof ViewGroup && viewManager instanceof ViewGroupManager) { + ViewGroup viewGroup = (ViewGroup) view; + ViewGroupManager viewGroupManager = getViewGroupManager(state); + for (int i = viewGroupManager.getChildCount(viewGroup) - 1; i >= 0; i--) { + View child = viewGroupManager.getChildAt(viewGroup, i); + if (mTagToViewState.get(child.getId()) != null) { + dropView(child); + } + viewGroupManager.removeViewAt(viewGroup, i); + } + } + + mTagToViewState.remove(reactTag); + Context context = view.getContext(); + mViewPool.returnToPool( + (ThemedReactContext) context, Assertions.assertNotNull(viewManager).getName(), view); + } + + /** Releases all references to react root tag. */ + @UiThread + public void removeRootView(int reactRootTag) { + UiThreadUtil.assertOnUiThread(); + ViewState viewState = mTagToViewState.get(reactRootTag); + if (viewState == null || !viewState.mIsRoot) { + SoftAssertions.assertUnreachable( + "View with tag " + reactRootTag + " is not registered as a root view"); + } + if (viewState.mView != null) { + dropView(viewState.mView); + } + } + + @UiThread + public void addViewAt(int parentTag, int tag, int index) { + UiThreadUtil.assertOnUiThread(); + ViewState parentViewState = getViewState(parentTag); + final ViewGroup parentView = (ViewGroup) parentViewState.mView; + final View view = getViewState(tag).mView; + if (view == null) { + throw new IllegalStateException("Unable to find view for tag " + tag); + } + getViewGroupManager(parentViewState).addView(parentView, view, index); + } + + private ViewState getViewState(int tag) { + ViewState viewState = mTagToViewState.get(tag); + if (viewState == null) { + throw new IllegalStateException("Unable to find viewState for tag " + tag); + } + return viewState; + } + + public void receiveCommand(int reactTag, int commandId, @Nullable ReadableArray commandArgs) { + ViewState viewState = getViewState(reactTag); + + if (viewState.mViewManager == null) { + throw new IllegalStateException("Unable to find viewState manager for tag " + reactTag); + } + + if (viewState.mView == null) { + throw new IllegalStateException("Unable to find viewState view for tag " + reactTag); + } + + viewState.mViewManager.receiveCommand(viewState.mView, commandId, commandArgs); + } + + @SuppressWarnings("unchecked") // prevents unchecked conversion warn of the type + private static ViewGroupManager getViewGroupManager(ViewState viewState) { + if (viewState.mViewManager == null) { + throw new IllegalStateException("Unable to find ViewManager"); + } + return (ViewGroupManager) viewState.mViewManager; + } + + @UiThread + public void removeViewAt(int parentTag, int index) { + UiThreadUtil.assertOnUiThread(); + ViewState viewState = getViewState(parentTag); + final ViewGroup parentView = (ViewGroup) viewState.mView; + if (parentView == null) { + throw new IllegalStateException("Unable to find view for tag " + parentTag); + } + + getViewGroupManager(viewState).removeViewAt(parentView, index); + } + + @UiThread + public void createView( + ThemedReactContext themedReactContext, + String componentName, + int reactTag, + boolean isVirtual) { + UiThreadUtil.assertOnUiThread(); + View view = null; + ViewManager viewManager = null; + if (!isVirtual) { + viewManager = mViewManagerRegistry.get(componentName); + view = mViewPool.getOrCreateView(componentName, themedReactContext); + view.setId(reactTag); + } + + mTagToViewState.put(reactTag, new ViewState(reactTag, view, viewManager)); + } + + @UiThread + public void updateProps(int reactTag, ReadableMap props) { + if (props == null) { + return; + } + UiThreadUtil.assertOnUiThread(); + ViewState viewState = getViewState(reactTag); + viewState.mCurrentProps = new ReactStylesDiffMap(props); + View view = viewState.mView; + + if (view == null) { + throw new IllegalStateException("Unable to find view for tag " + reactTag); + } + + Assertions.assertNotNull(viewState.mViewManager) + .updateProperties(view, viewState.mCurrentProps); + } + + @UiThread + public void updateLayout(int reactTag, int x, int y, int width, int height) { + UiThreadUtil.assertOnUiThread(); + + ViewState viewState = getViewState(reactTag); + // Do not layout Root Views + if (viewState.mIsRoot) { + return; + } + + View viewToUpdate = viewState.mView; + if (viewToUpdate == null) { + throw new IllegalStateException("Unable to find View for tag: " + reactTag); + } + + viewToUpdate.measure( + View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)); + + ViewParent parent = viewToUpdate.getParent(); + if (parent instanceof RootView) { + parent.requestLayout(); + } + + // TODO: T31905686 Check if the parent of the view has to layout the view, or the child has + // to lay itself out. see NativeViewHierarchyManager.updateLayout + viewToUpdate.layout(x, y, x + width, y + height); + } + + @UiThread + public void deleteView(int reactTag) { + UiThreadUtil.assertOnUiThread(); + View view = getViewState(reactTag).mView; + if (view != null) { + dropView(view); + } else { + mTagToViewState.remove(reactTag); + } + } + + @UiThread + public void updateLocalData(int reactTag, ReadableMap newLocalData) { + UiThreadUtil.assertOnUiThread(); + ViewState viewState = getViewState(reactTag); + if (viewState.mCurrentProps == null) { + throw new IllegalStateException( + "Can not update local data to view without props: " + reactTag); + } + if (viewState.mCurrentLocalData != null + && newLocalData.hasKey("hash") + && viewState.mCurrentLocalData.getDouble("hash") == newLocalData.getDouble("hash") + && viewState.mCurrentLocalData.toString().equals(newLocalData.toString())) { + // TODO: T31905686 implement a proper equality method + return; + } + viewState.mCurrentLocalData = newLocalData; + + ViewManager viewManager = viewState.mViewManager; + + if (viewManager == null) { + throw new IllegalStateException("Unable to find ViewManager for tag: " + reactTag); + } + Object extraData = + viewManager.updateLocalData( + viewState.mView, + viewState.mCurrentProps, + new ReactStylesDiffMap(viewState.mCurrentLocalData)); + if (extraData != null) { + viewManager.updateExtraData(viewState.mView, extraData); + } + } + + @UiThread + public void preallocateView(ThemedReactContext reactContext, String componentName) { + mViewPool.createView(reactContext, componentName); + } + + @UiThread + public void updateEventEmitter(int reactTag, EventEmitterWrapper eventEmitter) { + UiThreadUtil.assertOnUiThread(); + ViewState viewState = getViewState(reactTag); + viewState.mEventEmitter = eventEmitter; + } + + @AnyThread + public long measure( + ReactContext context, + String componentName, + ReadableNativeMap localData, + ReadableNativeMap props, + float width, + YogaMeasureMode widthMode, + float height, + YogaMeasureMode heightMode) { + + return mViewManagerRegistry + .get(componentName) + .measure(context, localData, props, width, widthMode, height, heightMode); + } + + @AnyThread + public @Nullable EventEmitterWrapper getEventEmitter(int reactTag) { + ViewState viewState = mTagToViewState.get(reactTag); + return viewState == null ? null : viewState.mEventEmitter; + } + + /** + * This class holds view state for react tags. Objects of this class are stored into the {@link + * #mTagToViewState}, and they should be updated in the same thread. + */ + private static class ViewState { + @Nullable final View mView; + final int mReactTag; + final boolean mIsRoot; + @Nullable final ViewManager mViewManager; + public ReactStylesDiffMap mCurrentProps; + public ReadableMap mCurrentLocalData; + public EventEmitterWrapper mEventEmitter; + + private ViewState(int reactTag, @Nullable View view, @Nullable ViewManager viewManager) { + this(reactTag, view, viewManager, false); + } + + private ViewState(int reactTag, @Nullable View view, ViewManager viewManager, boolean isRoot) { + mReactTag = reactTag; + mView = view; + mIsRoot = isRoot; + mViewManager = viewManager; + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ViewPool.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ViewPool.java new file mode 100644 index 000000000..5cf492a1a --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ViewPool.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.facebook.react.fabric.mounting; + +import android.support.annotation.UiThread; +import android.view.View; +import com.facebook.react.common.ClearableSynchronizedPool; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.ViewManager; +import com.facebook.react.uimanager.ViewManagerRegistry; +import java.util.HashMap; +import java.util.Map; + +final class ViewPool { + private static final int POOL_SIZE = 512; + private final Map> mViewPool = new HashMap<>(); + private final ViewManagerRegistry mViewManagerRegistry; + + ViewPool(ViewManagerRegistry viewManagerRegistry) { + mViewManagerRegistry = viewManagerRegistry; + } + + @UiThread + void createView(String componentName, ThemedReactContext context) { + ClearableSynchronizedPool viewPool = getViewPoolForComponent(componentName); + ViewManager viewManager = mViewManagerRegistry.get(componentName); + // TODO: T31905686 Integrate / re-implement jsResponder + View view = viewManager.createView(context, null); + viewPool.release(view); + } + + @UiThread + View getOrCreateView(String componentName, ThemedReactContext context) { + ClearableSynchronizedPool viewPool = getViewPoolForComponent(componentName); + View view = viewPool.acquire(); + if (view == null) { + createView(componentName, context); + view = viewPool.acquire(); + } + return view; + } + + @UiThread + void returnToPool(String componentName, View view) { + ClearableSynchronizedPool viewPool = mViewPool.get(componentName); + if (viewPool != null) { + viewPool.release(view); + } + } + + private ClearableSynchronizedPool getViewPoolForComponent(String componentName) { + ClearableSynchronizedPool viewPool = mViewPool.get(componentName); + if (viewPool == null) { + viewPool = new ClearableSynchronizedPool<>(POOL_SIZE); + mViewPool.put(componentName, viewPool); + } + return viewPool; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/BatchMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/BatchMountItem.java new file mode 100644 index 000000000..0e02ebe8d --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/BatchMountItem.java @@ -0,0 +1,52 @@ +/** + * 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. + */ +package com.facebook.react.fabric.mounting.mountitems; + +import com.facebook.react.fabric.mounting.MountingManager; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.systrace.Systrace; + +/** + * This class represents a batch of {@link MountItem}s + * + *

A MountItem batch contains an array of {@link MountItem} and a size. The size determines the + * amount of items that needs to be processed in the array. + * + *

The purpose of encapsulating the array of MountItems this way, is to reduce the amount of + * allocations in C++ + */ +@DoNotStrip +public class BatchMountItem implements MountItem { + + private final MountItem[] mMountItems; + private final int mSize; + + public BatchMountItem(MountItem[] items, int size) { + if (items == null) { + throw new NullPointerException(); + } + if (size < 0 || size > items.length) { + throw new IllegalArgumentException( + "Invalid size received by parameter size: " + size + " items.size = " + items.length); + } + mMountItems = items; + mSize = size; + } + + @Override + public void execute(MountingManager mountingManager) { + Systrace.beginSection( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "FabricUIManager::mountViews (" + mSize + " items)"); + + for (int mountItemIndex = 0; mountItemIndex < mSize; mountItemIndex++) { + MountItem mountItem = mMountItems[mountItemIndex]; + mountItem.execute(mountingManager); + } + + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/CreateMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/CreateMountItem.java new file mode 100644 index 000000000..58e77d8b0 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/CreateMountItem.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.facebook.react.fabric.mounting.mountitems; + +import com.facebook.react.fabric.mounting.MountingManager; +import com.facebook.react.uimanager.ThemedReactContext; + +public class CreateMountItem implements MountItem { + + private final String mComponentName; + private final int mReactTag; + private final ThemedReactContext mThemedReactContext; + private final boolean mIsVirtual; + + public CreateMountItem( + ThemedReactContext themedReactContext, + String componentName, + int reactTag, + boolean isVirtual) { + mReactTag = reactTag; + mThemedReactContext = themedReactContext; + mComponentName = componentName; + mIsVirtual = isVirtual; + } + + @Override + public void execute(MountingManager mountingManager) { + mountingManager.createView(mThemedReactContext, mComponentName, mReactTag, mIsVirtual); + } + + public String getComponentName() { + return mComponentName; + } + + public ThemedReactContext getThemedReactContext() { + return mThemedReactContext; + } + + @Override + public String toString() { + return "CreateMountItem [" + mReactTag + "] " + mComponentName; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/DeleteMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/DeleteMountItem.java new file mode 100644 index 000000000..b33a6ab67 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/DeleteMountItem.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.facebook.react.fabric.mounting.mountitems; + +import com.facebook.react.fabric.mounting.MountingManager; + +public class DeleteMountItem implements MountItem { + + private int mReactTag; + + public DeleteMountItem(int reactTag) { + mReactTag = reactTag; + } + + @Override + public void execute(MountingManager mountingManager) { + mountingManager.deleteView(mReactTag); + } + + @Override + public String toString() { + return "DeleteMountItem [" + mReactTag + "]"; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/DispatchCommandMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/DispatchCommandMountItem.java new file mode 100644 index 000000000..e07ceab98 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/DispatchCommandMountItem.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.facebook.react.fabric.mounting.mountitems; + +import android.support.annotation.Nullable; +import com.facebook.react.fabric.mounting.MountingManager; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.UiThreadUtil; + +public class DispatchCommandMountItem implements MountItem { + + private final int mReactTag; + private final int mCommandId; + private final @Nullable ReadableArray mCommandArgs; + + public DispatchCommandMountItem( + int reactTag, int commandId, @Nullable ReadableArray commandArgs) { + mReactTag = reactTag; + mCommandId = commandId; + mCommandArgs = commandArgs; + } + + @Override + public void execute(MountingManager mountingManager) { + UiThreadUtil.assertOnUiThread(); + mountingManager.receiveCommand(mReactTag, mCommandId, mCommandArgs); + } + + @Override + public String toString() { + return "DispatchCommandMountItem [" + mReactTag + "] " + mCommandId; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/InsertMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/InsertMountItem.java new file mode 100644 index 000000000..c0b18ff2e --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/InsertMountItem.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.facebook.react.fabric.mounting.mountitems; + +import com.facebook.react.fabric.mounting.MountingManager; + +public class InsertMountItem implements MountItem { + + private int mReactTag; + private int mParentReactTag; + private int mIndex; + + public InsertMountItem(int reactTag, int parentReactTag, int index) { + mReactTag = reactTag; + mParentReactTag = parentReactTag; + mIndex = index; + } + + @Override + public void execute(MountingManager mountingManager) { + mountingManager.addViewAt(mParentReactTag, mReactTag, mIndex); + } + + public int getParentReactTag() { + return mParentReactTag; + } + + public int getIndex() { + return mIndex; + } + + @Override + public String toString() { + return "InsertMountItem [" + + mReactTag + + "] - parentTag: " + + mParentReactTag + + " - index: " + + mIndex; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/MountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/MountItem.java new file mode 100644 index 000000000..7cf7d2576 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/MountItem.java @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.facebook.react.fabric.mounting.mountitems; + +import android.support.annotation.UiThread; +import com.facebook.react.fabric.mounting.MountingManager; + +public interface MountItem { + + /** Execute this {@link MountItem} into the operation queue received by parameter. */ + @UiThread + void execute(MountingManager mountingManager); +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/RemoveMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/RemoveMountItem.java new file mode 100644 index 000000000..342e31ca2 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/RemoveMountItem.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.facebook.react.fabric.mounting.mountitems; + +import com.facebook.react.fabric.mounting.MountingManager; + +public class RemoveMountItem implements MountItem { + + private int mReactTag; + private int mParentReactTag; + private int mIndex; + + public RemoveMountItem(int reactTag, int parentReactTag, int index) { + mReactTag = reactTag; + mParentReactTag = parentReactTag; + mIndex = index; + } + + @Override + public void execute(MountingManager mountingManager) { + mountingManager.removeViewAt(mParentReactTag, mIndex); + } + + public int getParentReactTag() { + return mParentReactTag; + } + + public int getIndex() { + return mIndex; + } + + @Override + public String toString() { + return "RemoveMountItem [" + + mReactTag + + "] - parentTag: " + + mParentReactTag + + " - index: " + + mIndex; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateEventEmitterMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateEventEmitterMountItem.java new file mode 100644 index 000000000..24dd1b64f --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateEventEmitterMountItem.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.facebook.react.fabric.mounting.mountitems; + +import com.facebook.react.fabric.jsi.EventEmitterWrapper; +import com.facebook.react.fabric.mounting.MountingManager; + +public class UpdateEventEmitterMountItem implements MountItem { + + private final EventEmitterWrapper mEventHandler; + private final int mReactTag; + + public UpdateEventEmitterMountItem(int reactTag, EventEmitterWrapper EventHandler) { + mReactTag = reactTag; + mEventHandler = EventHandler; + } + + @Override + public void execute(MountingManager mountingManager) { + mountingManager.updateEventEmitter(mReactTag, mEventHandler); + } + + @Override + public String toString() { + return "UpdateEventEmitterMountItem [" + mReactTag + "]"; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateLayoutMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateLayoutMountItem.java new file mode 100644 index 000000000..2beee834c --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateLayoutMountItem.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.facebook.react.fabric.mounting.mountitems; + +import com.facebook.react.fabric.mounting.MountingManager; + +public class UpdateLayoutMountItem implements MountItem { + + private final int mReactTag; + private final int mX; + private final int mY; + private final int mWidth; + private final int mHeight; + + public UpdateLayoutMountItem(int reactTag, int x, int y, int width, int height) { + mReactTag = reactTag; + mX = x; + mY = y; + mWidth = width; + mHeight = height; + } + + @Override + public void execute(MountingManager mountingManager) { + mountingManager.updateLayout(mReactTag, mX, mY, mWidth, mHeight); + } + + public int getX() { + return mX; + } + + public int getY() { + return mY; + } + + public int getHeight() { + return mHeight; + } + + public int getWidth() { + return mWidth; + } + + @Override + public String toString() { + return "UpdateLayoutMountItem [" + + mReactTag + + "] - x: " + + mX + + " - y: " + + mY + + " - height: " + + mHeight + + " - width: " + + mWidth; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateLocalDataMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateLocalDataMountItem.java new file mode 100644 index 000000000..c54bfba8f --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateLocalDataMountItem.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.facebook.react.fabric.mounting.mountitems; + +import com.facebook.react.fabric.mounting.MountingManager; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableNativeMap; + +public class UpdateLocalDataMountItem implements MountItem { + + private final int mReactTag; + private final ReadableMap mNewLocalData; + + public UpdateLocalDataMountItem(int reactTag, ReadableNativeMap newLocalData) { + mReactTag = reactTag; + mNewLocalData = newLocalData; + } + + @Override + public void execute(MountingManager mountingManager) { + mountingManager.updateLocalData(mReactTag, mNewLocalData); + } + + public ReadableMap getNewLocalData() { + return mNewLocalData; + } + + @Override + public String toString() { + return "UpdateLocalDataMountItem [" + mReactTag + "] - localData: " + mNewLocalData; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdatePropsMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdatePropsMountItem.java new file mode 100644 index 000000000..62a64342e --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdatePropsMountItem.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.facebook.react.fabric.mounting.mountitems; + +import com.facebook.react.fabric.mounting.MountingManager; +import com.facebook.react.bridge.ReadableMap; + +public class UpdatePropsMountItem implements MountItem { + + private final int mReactTag; + private final ReadableMap mUpdatedProps; + + public UpdatePropsMountItem(int reactTag, ReadableMap updatedProps) { + mReactTag = reactTag; + mUpdatedProps = updatedProps; + } + + @Override + public void execute(MountingManager mountingManager) { + mountingManager.updateProps(mReactTag, mUpdatedProps); + } + + @Override + public String toString() { + return "UpdatePropsMountItem [" + mReactTag + "] - props: " + mUpdatedProps; + } +} diff --git a/ReactCommon/fabric/sample/SampleComponentDescriptorFactor.cpp b/ReactCommon/fabric/sample/SampleComponentDescriptorFactor.cpp index 4f15775f5..9cd6ffa2d 100644 --- a/ReactCommon/fabric/sample/SampleComponentDescriptorFactor.cpp +++ b/ReactCommon/fabric/sample/SampleComponentDescriptorFactor.cpp @@ -15,11 +15,12 @@ namespace react { /** * This is a sample implementation. Each app should provide its own. */ -SharedComponentDescriptorRegistry ComponentDescriptorFactory::buildRegistry( - const SharedEventDispatcher &eventDispatcher, - const SharedContextContainer &contextContainer) { - auto registry = std::make_shared(); - return registry; +ComponentRegistryFactory getDefaultComponentRegistryFactory() { + return [](const SharedEventDispatcher &eventDispatcher, + const SharedContextContainer &contextContainer) { + auto registry = std::make_shared(); + return registry; + } } } // namespace react diff --git a/ReactCommon/fabric/textlayoutmanager/platform/android/TextLayoutManager.cpp b/ReactCommon/fabric/textlayoutmanager/platform/android/TextLayoutManager.cpp index 35cb5e435..fd18e4769 100644 --- a/ReactCommon/fabric/textlayoutmanager/platform/android/TextLayoutManager.cpp +++ b/ReactCommon/fabric/textlayoutmanager/platform/android/TextLayoutManager.cpp @@ -31,7 +31,7 @@ Size TextLayoutManager::measure( "FabricUIManager"); static auto measure = - jni::findClassStatic("com/facebook/fbreact/fabric/FabricUIManager") + jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") ->getMethod; + +ComponentRegistryFactory getDefaultComponentRegistryFactory(); } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/uimanager/Scheduler.cpp b/ReactCommon/fabric/uimanager/Scheduler.cpp index 88ab7e7f3..3415dfa98 100644 --- a/ReactCommon/fabric/uimanager/Scheduler.cpp +++ b/ReactCommon/fabric/uimanager/Scheduler.cpp @@ -13,12 +13,12 @@ #include #include -#include "ComponentDescriptorFactory.h" - namespace facebook { namespace react { -Scheduler::Scheduler(const SharedContextContainer &contextContainer) { +Scheduler::Scheduler( + const SharedContextContainer &contextContainer, + ComponentRegistryFactory buildRegistryFunction) { const auto asynchronousEventBeatFactory = contextContainer->getInstance("asynchronous"); const auto synchronousEventBeatFactory = @@ -46,8 +46,8 @@ Scheduler::Scheduler(const SharedContextContainer &contextContainer) { auto eventDispatcher = std::make_shared( eventPipe, synchronousEventBeatFactory, asynchronousEventBeatFactory); - componentDescriptorRegistry_ = ComponentDescriptorFactory::buildRegistry( - eventDispatcher, contextContainer); + componentDescriptorRegistry_ = + buildRegistryFunction(eventDispatcher, contextContainer); uiManagerRef.setDelegate(this); uiManagerRef.setShadowTreeRegistry(&shadowTreeRegistry_); diff --git a/ReactCommon/fabric/uimanager/Scheduler.h b/ReactCommon/fabric/uimanager/Scheduler.h index 7402b32c2..839b88f5b 100644 --- a/ReactCommon/fabric/uimanager/Scheduler.h +++ b/ReactCommon/fabric/uimanager/Scheduler.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -29,7 +30,9 @@ namespace react { */ class Scheduler final : public UIManagerDelegate, public ShadowTreeDelegate { public: - Scheduler(const SharedContextContainer &contextContainer); + Scheduler( + const SharedContextContainer &contextContainer, + ComponentRegistryFactory buildRegistryFunction); ~Scheduler(); #pragma mark - Surface Management diff --git a/ReactCommon/fabric/uimanager/tests/UITemplateProcessorTest.cpp b/ReactCommon/fabric/uimanager/tests/UITemplateProcessorTest.cpp index bd0176947..a546f7660 100644 --- a/ReactCommon/fabric/uimanager/tests/UITemplateProcessorTest.cpp +++ b/ReactCommon/fabric/uimanager/tests/UITemplateProcessorTest.cpp @@ -21,33 +21,37 @@ using namespace facebook::react; #include #include #include +#include #include +#include namespace facebook { namespace react { -SharedComponentDescriptorRegistry ComponentDescriptorFactory::buildRegistry( - const SharedEventDispatcher &eventDispatcher, - const SharedContextContainer &contextContainer) { - auto registry = std::make_shared(); - registry->registerComponentDescriptor( - std::make_shared(eventDispatcher)); - registry->registerComponentDescriptor( - std::make_shared( - eventDispatcher, contextContainer)); - registry->registerComponentDescriptor( - std::make_shared(eventDispatcher)); - registry->registerComponentDescriptor( - std::make_shared( - eventDispatcher, contextContainer)); - registry->registerComponentDescriptor( - std::make_shared(eventDispatcher)); - registry->registerComponentDescriptor( - std::make_shared(eventDispatcher)); - registry->registerComponentDescriptor( - std::make_shared( - eventDispatcher)); - return registry; +// TODO (T29441913): Codegen this app-specific implementation. +ComponentRegistryFactory getDefaultComponentRegistryFactory() { + return [](const SharedEventDispatcher &eventDispatcher, + const SharedContextContainer &contextContainer) { + auto registry = std::make_shared(); + registry->registerComponentDescriptor( + std::make_shared(eventDispatcher)); + registry->registerComponentDescriptor( + std::make_shared( + eventDispatcher, contextContainer)); + registry->registerComponentDescriptor( + std::make_shared(eventDispatcher)); + registry->registerComponentDescriptor( + std::make_shared( + eventDispatcher, contextContainer)); + registry->registerComponentDescriptor( + std::make_shared(eventDispatcher)); + registry->registerComponentDescriptor( + std::make_shared(eventDispatcher)); + registry->registerComponentDescriptor( + std::make_shared( + eventDispatcher)); + return registry; + }; } bool mockSimpleTestValue_; @@ -93,7 +97,7 @@ std::shared_ptr mockReactNativeConfig_ = TEST(UITemplateProcessorTest, testSimpleBytecode) { auto surfaceId = 11; auto componentDescriptorRegistry = - ComponentDescriptorFactory::buildRegistry(nullptr, nullptr); + getDefaultComponentRegistryFactory()(nullptr, nullptr); auto nativeModuleRegistry = buildNativeModuleRegistry(); auto bytecode = R"delim({"version":0.1,"commands":[ @@ -127,7 +131,7 @@ TEST(UITemplateProcessorTest, testSimpleBytecode) { TEST(UITemplateProcessorTest, testConditionalBytecode) { auto surfaceId = 11; auto componentDescriptorRegistry = - ComponentDescriptorFactory::buildRegistry(nullptr, nullptr); + getDefaultComponentRegistryFactory()(nullptr, nullptr); auto nativeModuleRegistry = buildNativeModuleRegistry(); auto bytecode = R"delim({"version":0.1,"commands":[