mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Convert and internalize MountingManager (#51872)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/51872 This converts to Kotlin and internaline MountingManager. The only usage in OSS is react-natve-live-markdown: https://github.com/Expensify/react-native-live-markdown/issues/693 They're using reflection to access Mounting Manager, which they shouldn't. Other than them, I wasn't able to find meaningful usages of `MountingManager` Changelog: [Android] [Breaking] - Convert to Kotlin and internalize MountingManager Reviewed By: rshest Differential Revision: D76126338 fbshipit-source-id: 5ab491f86d697a82b8e5b02b031877020dfa3e9e
This commit is contained in:
committed by
Facebook GitHub Bot
parent
a67d2c5e44
commit
f33fdca876
@@ -2389,33 +2389,6 @@ public abstract interface class com/facebook/react/fabric/mounting/MountItemDisp
|
||||
public abstract fun willMountItems (Ljava/util/List;)V
|
||||
}
|
||||
|
||||
public class com/facebook/react/fabric/mounting/MountingManager {
|
||||
public static final field TAG Ljava/lang/String;
|
||||
public fun <init> (Lcom/facebook/react/uimanager/ViewManagerRegistry;Lcom/facebook/react/fabric/mounting/MountingManager$MountItemExecutor;)V
|
||||
public fun attachRootView (ILandroid/view/View;Lcom/facebook/react/uimanager/ThemedReactContext;)V
|
||||
public fun clearJSResponder ()V
|
||||
public fun enqueuePendingEvent (IILjava/lang/String;ZLcom/facebook/react/bridge/WritableMap;I)V
|
||||
public fun getEventEmitter (II)Lcom/facebook/react/fabric/events/EventEmitterWrapper;
|
||||
public fun getSurfaceManager (I)Lcom/facebook/react/fabric/mounting/SurfaceMountingManager;
|
||||
public fun getSurfaceManagerEnforced (ILjava/lang/String;)Lcom/facebook/react/fabric/mounting/SurfaceMountingManager;
|
||||
public fun getSurfaceManagerForView (I)Lcom/facebook/react/fabric/mounting/SurfaceMountingManager;
|
||||
public fun getSurfaceManagerForViewEnforced (I)Lcom/facebook/react/fabric/mounting/SurfaceMountingManager;
|
||||
public fun getViewExists (I)Z
|
||||
public fun isWaitingForViewAttach (I)Z
|
||||
public fun measure (Lcom/facebook/react/bridge/ReactContext;Ljava/lang/String;Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/ReadableMap;FLcom/facebook/yoga/YogaMeasureMode;FLcom/facebook/yoga/YogaMeasureMode;[F)J
|
||||
public fun receiveCommand (IIILcom/facebook/react/bridge/ReadableArray;)V
|
||||
public fun receiveCommand (IILjava/lang/String;Lcom/facebook/react/bridge/ReadableArray;)V
|
||||
public fun sendAccessibilityEvent (III)V
|
||||
public fun startSurface (ILcom/facebook/react/uimanager/ThemedReactContext;Landroid/view/View;)Lcom/facebook/react/fabric/mounting/SurfaceMountingManager;
|
||||
public fun stopSurface (I)V
|
||||
public fun surfaceIsStopped (I)Z
|
||||
public fun updateProps (ILcom/facebook/react/bridge/ReadableMap;)V
|
||||
}
|
||||
|
||||
public abstract interface class com/facebook/react/fabric/mounting/MountingManager$MountItemExecutor {
|
||||
public abstract fun executeItems (Ljava/util/Queue;)V
|
||||
}
|
||||
|
||||
public class com/facebook/react/fabric/mounting/SurfaceMountingManager {
|
||||
public static final field TAG Ljava/lang/String;
|
||||
public fun <init> (ILcom/facebook/react/touch/JSResponderHandler;Lcom/facebook/react/uimanager/ViewManagerRegistry;Lcom/facebook/react/uimanager/RootViewManager;Lcom/facebook/react/fabric/mounting/MountingManager$MountItemExecutor;Lcom/facebook/react/uimanager/ThemedReactContext;)V
|
||||
|
||||
-431
@@ -1,431 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 com.facebook.infer.annotation.ThreadConfined.ANY;
|
||||
import static com.facebook.infer.annotation.ThreadConfined.UI;
|
||||
|
||||
import android.view.View;
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.infer.annotation.Nullsafe;
|
||||
import com.facebook.infer.annotation.ThreadConfined;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReactSoftExceptionLogger;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.RetryableMountingLayerException;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.common.annotations.UnstableReactNativeAPI;
|
||||
import com.facebook.react.common.mapbuffer.MapBuffer;
|
||||
import com.facebook.react.fabric.FabricUIManager;
|
||||
import com.facebook.react.fabric.events.EventEmitterWrapper;
|
||||
import com.facebook.react.fabric.mounting.mountitems.MountItem;
|
||||
import com.facebook.react.touch.JSResponderHandler;
|
||||
import com.facebook.react.uimanager.RootViewManager;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.ViewManagerRegistry;
|
||||
import com.facebook.react.uimanager.common.ViewUtil;
|
||||
import com.facebook.react.uimanager.events.EventCategoryDef;
|
||||
import com.facebook.yoga.YogaMeasureMode;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* Class responsible for actually dispatching view updates enqueued via {@link
|
||||
* FabricUIManager#scheduleMountItem} on the UI thread.
|
||||
*/
|
||||
@Nullsafe(Nullsafe.Mode.LOCAL)
|
||||
public class MountingManager {
|
||||
public static final String TAG = MountingManager.class.getSimpleName();
|
||||
private static final int MAX_STOPPED_SURFACE_IDS_LENGTH = 15;
|
||||
|
||||
private final ConcurrentHashMap<Integer, SurfaceMountingManager> mSurfaceIdToManager =
|
||||
new ConcurrentHashMap<>(); // any thread
|
||||
|
||||
private final CopyOnWriteArrayList<Integer> mStoppedSurfaceIds = new CopyOnWriteArrayList<>();
|
||||
|
||||
@Nullable private SurfaceMountingManager mMostRecentSurfaceMountingManager;
|
||||
@Nullable private SurfaceMountingManager mLastQueriedSurfaceMountingManager;
|
||||
|
||||
private final JSResponderHandler mJSResponderHandler = new JSResponderHandler();
|
||||
private final ViewManagerRegistry mViewManagerRegistry;
|
||||
private final MountItemExecutor mMountItemExecutor;
|
||||
private final RootViewManager mRootViewManager = new RootViewManager();
|
||||
|
||||
public interface MountItemExecutor {
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
void executeItems(Queue<MountItem> items);
|
||||
}
|
||||
|
||||
public MountingManager(
|
||||
ViewManagerRegistry viewManagerRegistry, MountItemExecutor mountItemExecutor) {
|
||||
mViewManagerRegistry = viewManagerRegistry;
|
||||
mMountItemExecutor = mountItemExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts surface without attaching the view. All view operations executed against that surface
|
||||
* will be queued until the view is attached.
|
||||
*/
|
||||
@AnyThread
|
||||
public SurfaceMountingManager startSurface(
|
||||
final int surfaceId, ThemedReactContext reactContext, @Nullable View rootView) {
|
||||
SurfaceMountingManager surfaceMountingManager =
|
||||
new SurfaceMountingManager(
|
||||
surfaceId,
|
||||
mJSResponderHandler,
|
||||
mViewManagerRegistry,
|
||||
mRootViewManager,
|
||||
mMountItemExecutor,
|
||||
reactContext);
|
||||
|
||||
// There could technically be a race condition here if addRootView is called twice from
|
||||
// different threads, though this is (probably) extremely unlikely, and likely an error.
|
||||
// This logic to protect against race conditions is a holdover from older code, and we don't
|
||||
// know if it actually happens in practice - so, we're logging soft exceptions for now.
|
||||
// This *will* crash in Debug mode, but not in production.
|
||||
mSurfaceIdToManager.putIfAbsent(surfaceId, surfaceMountingManager);
|
||||
if (mSurfaceIdToManager.get(surfaceId) != surfaceMountingManager) {
|
||||
ReactSoftExceptionLogger.logSoftException(
|
||||
TAG,
|
||||
new IllegalStateException(
|
||||
"Called startSurface more than once for the SurfaceId [" + surfaceId + "]"));
|
||||
}
|
||||
|
||||
mMostRecentSurfaceMountingManager = mSurfaceIdToManager.get(surfaceId);
|
||||
|
||||
if (rootView != null) {
|
||||
surfaceMountingManager.attachRootView(rootView, reactContext);
|
||||
}
|
||||
|
||||
return surfaceMountingManager;
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public void attachRootView(
|
||||
final int surfaceId, final View rootView, ThemedReactContext themedReactContext) {
|
||||
SurfaceMountingManager surfaceMountingManager =
|
||||
getSurfaceManagerEnforced(surfaceId, "attachView");
|
||||
|
||||
if (surfaceMountingManager.isStopped()) {
|
||||
ReactSoftExceptionLogger.logSoftException(
|
||||
TAG, new IllegalStateException("Trying to attach a view to a stopped surface"));
|
||||
return;
|
||||
}
|
||||
|
||||
surfaceMountingManager.attachRootView(rootView, themedReactContext);
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public void stopSurface(final int surfaceId) {
|
||||
SurfaceMountingManager surfaceMountingManager = mSurfaceIdToManager.get(surfaceId);
|
||||
if (surfaceMountingManager != null) {
|
||||
// Maximum number of stopped surfaces to keep track of
|
||||
while (mStoppedSurfaceIds.size() >= MAX_STOPPED_SURFACE_IDS_LENGTH) {
|
||||
Integer staleStoppedId = mStoppedSurfaceIds.get(0);
|
||||
Assertions.assertNotNull(staleStoppedId);
|
||||
mSurfaceIdToManager.remove(staleStoppedId.intValue());
|
||||
mStoppedSurfaceIds.remove(staleStoppedId);
|
||||
FLog.d(TAG, "Removing stale SurfaceMountingManager: [%d]", staleStoppedId.intValue());
|
||||
}
|
||||
mStoppedSurfaceIds.add(surfaceId);
|
||||
|
||||
surfaceMountingManager.stopSurface();
|
||||
|
||||
if (mMostRecentSurfaceMountingManager == surfaceMountingManager) {
|
||||
mMostRecentSurfaceMountingManager = null;
|
||||
}
|
||||
if (mLastQueriedSurfaceMountingManager == surfaceMountingManager) {
|
||||
mLastQueriedSurfaceMountingManager = null;
|
||||
}
|
||||
} else {
|
||||
ReactSoftExceptionLogger.logSoftException(
|
||||
TAG,
|
||||
new IllegalStateException(
|
||||
"Cannot call stopSurface on non-existent surface: [" + surfaceId + "]"));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public SurfaceMountingManager getSurfaceManager(int surfaceId) {
|
||||
if (mLastQueriedSurfaceMountingManager != null
|
||||
&& mLastQueriedSurfaceMountingManager.getSurfaceId() == surfaceId) {
|
||||
return mLastQueriedSurfaceMountingManager;
|
||||
}
|
||||
|
||||
if (mMostRecentSurfaceMountingManager != null
|
||||
&& mMostRecentSurfaceMountingManager.getSurfaceId() == surfaceId) {
|
||||
return mMostRecentSurfaceMountingManager;
|
||||
}
|
||||
|
||||
SurfaceMountingManager surfaceMountingManager = mSurfaceIdToManager.get(surfaceId);
|
||||
mLastQueriedSurfaceMountingManager = surfaceMountingManager;
|
||||
return surfaceMountingManager;
|
||||
}
|
||||
|
||||
public SurfaceMountingManager getSurfaceManagerEnforced(int surfaceId, String context) {
|
||||
SurfaceMountingManager surfaceMountingManager = getSurfaceManager(surfaceId);
|
||||
|
||||
if (surfaceMountingManager == null) {
|
||||
throw new RetryableMountingLayerException(
|
||||
"Unable to find SurfaceMountingManager for surfaceId: ["
|
||||
+ surfaceId
|
||||
+ "]. Context: "
|
||||
+ context);
|
||||
}
|
||||
|
||||
return surfaceMountingManager;
|
||||
}
|
||||
|
||||
public boolean surfaceIsStopped(int surfaceId) {
|
||||
if (mStoppedSurfaceIds.contains(surfaceId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
SurfaceMountingManager surfaceMountingManager = getSurfaceManager(surfaceId);
|
||||
if (surfaceMountingManager != null && surfaceMountingManager.isStopped()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isWaitingForViewAttach(int surfaceId) {
|
||||
SurfaceMountingManager mountingManager = getSurfaceManager(surfaceId);
|
||||
if (mountingManager == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mountingManager.isStopped()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !mountingManager.isRootViewAttached();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SurfaceMountingManager associated with a ReactTag. Unfortunately, this requires lookups
|
||||
* over N maps, where N is the number of active or recently-stopped Surfaces. Each lookup will
|
||||
* cost `log(M)` operations where M is the number of reactTags in the surface, so the total cost
|
||||
* per lookup is `O(N * log(M))`.
|
||||
*
|
||||
* <p>To mitigate this cost, we attempt to keep track of the "most recent" SurfaceMountingManager
|
||||
* and do lookups in it first. For the vast majority of use-cases, except for events or operations
|
||||
* sent to off-screen surfaces, or use-cases where multiple surfaces are visible and interactable,
|
||||
* this will reduce the lookup time to `O(log(M))`. Someone smarter than me could probably figure
|
||||
* out an amortized time.
|
||||
*
|
||||
* @param reactTag
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
public SurfaceMountingManager getSurfaceManagerForView(int reactTag) {
|
||||
if (mMostRecentSurfaceMountingManager != null
|
||||
&& mMostRecentSurfaceMountingManager.getViewExists(reactTag)) {
|
||||
return mMostRecentSurfaceMountingManager;
|
||||
}
|
||||
|
||||
for (Map.Entry<Integer, SurfaceMountingManager> entry : mSurfaceIdToManager.entrySet()) {
|
||||
SurfaceMountingManager smm = entry.getValue();
|
||||
if (smm != mMostRecentSurfaceMountingManager && smm.getViewExists(reactTag)) {
|
||||
if (mMostRecentSurfaceMountingManager == null) {
|
||||
mMostRecentSurfaceMountingManager = smm;
|
||||
}
|
||||
return smm;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public SurfaceMountingManager getSurfaceManagerForViewEnforced(int reactTag) {
|
||||
SurfaceMountingManager surfaceMountingManager = getSurfaceManagerForView(reactTag);
|
||||
|
||||
if (surfaceMountingManager == null) {
|
||||
throw new RetryableMountingLayerException(
|
||||
"Unable to find SurfaceMountingManager for tag: [" + reactTag + "]");
|
||||
}
|
||||
|
||||
return surfaceMountingManager;
|
||||
}
|
||||
|
||||
public boolean getViewExists(int reactTag) {
|
||||
return getSurfaceManagerForView(reactTag) != null;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void receiveCommand(
|
||||
int surfaceId, int reactTag, int commandId, ReadableArray commandArgs) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
getSurfaceManagerEnforced(surfaceId, "receiveCommand:int")
|
||||
.receiveCommand(reactTag, commandId, commandArgs);
|
||||
}
|
||||
|
||||
public void receiveCommand(
|
||||
int surfaceId, int reactTag, String commandId, ReadableArray commandArgs) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
getSurfaceManagerEnforced(surfaceId, "receiveCommand:string")
|
||||
.receiveCommand(reactTag, commandId, commandArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an accessibility eventType to a Native View. eventType is any valid `AccessibilityEvent.X`
|
||||
* value.
|
||||
*
|
||||
* <p>Why accept {@ViewUtil.NO_SURFACE_ID}(-1) SurfaceId? Currently there are calls to
|
||||
* UIManager.sendAccessibilityEvent which is a legacy API and accepts only reactTag. We will have
|
||||
* to investigate and migrate away from those calls over time.
|
||||
*
|
||||
* @param surfaceId {@link int} that identifies the surface or {@ViewUtil.NO_SURFACE_ID}(-1) to
|
||||
* temporarily support backward compatibility.
|
||||
* @param reactTag {@link int} that identifies the react Tag of the view.
|
||||
* @param eventType {@link int} that identifies Android eventType. see {@link
|
||||
* View#sendAccessibilityEvent}
|
||||
*/
|
||||
public void sendAccessibilityEvent(int surfaceId, int reactTag, int eventType) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
if (surfaceId == View.NO_ID) {
|
||||
getSurfaceManagerForViewEnforced(reactTag).sendAccessibilityEvent(reactTag, eventType);
|
||||
} else {
|
||||
getSurfaceManagerEnforced(surfaceId, "sendAccessibilityEvent")
|
||||
.sendAccessibilityEvent(reactTag, eventType);
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void updateProps(int reactTag, @Nullable ReadableMap props) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
if (props == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
getSurfaceManagerForViewEnforced(reactTag).updateProps(reactTag, props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the JS Responder specified by {@link SurfaceMountingManager#setJSResponder}. After this
|
||||
* method is called, all the touch events are going to be handled by JS.
|
||||
*/
|
||||
@UiThread
|
||||
public void clearJSResponder() {
|
||||
// MountingManager and SurfaceMountingManagers all share the same JSResponderHandler.
|
||||
// Must be called on MountingManager instead of SurfaceMountingManager, because we don't
|
||||
// know what surfaceId it's being called for.
|
||||
mJSResponderHandler.clearJSResponder();
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
@ThreadConfined(ANY)
|
||||
public @Nullable EventEmitterWrapper getEventEmitter(int surfaceId, int reactTag) {
|
||||
SurfaceMountingManager smm = getSurfaceMountingManager(surfaceId, reactTag);
|
||||
if (smm == null) {
|
||||
return null;
|
||||
}
|
||||
return smm.getEventEmitter(reactTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure a component, given localData, props, state, and measurement information. This needs to
|
||||
* remain here for now - and not in SurfaceMountingManager - because sometimes measures are made
|
||||
* outside of the context of a Surface; especially from C++ before StartSurface is called.
|
||||
*
|
||||
* @param context
|
||||
* @param componentName
|
||||
* @param localData
|
||||
* @param props
|
||||
* @param state
|
||||
* @param width
|
||||
* @param widthMode
|
||||
* @param height
|
||||
* @param heightMode
|
||||
* @param attachmentsPositions
|
||||
* @return
|
||||
*/
|
||||
@AnyThread
|
||||
public long measure(
|
||||
ReactContext context,
|
||||
String componentName,
|
||||
ReadableMap localData,
|
||||
ReadableMap props,
|
||||
ReadableMap state,
|
||||
float width,
|
||||
YogaMeasureMode widthMode,
|
||||
float height,
|
||||
YogaMeasureMode heightMode,
|
||||
@Nullable float[] attachmentsPositions) {
|
||||
|
||||
return mViewManagerRegistry
|
||||
.get(componentName)
|
||||
.measure(
|
||||
context,
|
||||
localData,
|
||||
props,
|
||||
state,
|
||||
width,
|
||||
widthMode,
|
||||
height,
|
||||
heightMode,
|
||||
attachmentsPositions);
|
||||
}
|
||||
|
||||
/**
|
||||
* THIS PREFETCH METHOD IS EXPERIMENTAL, DO NOT USE IT FOR PRODUCTION CODE. IT WILL MOST LIKELY
|
||||
* CHANGE OR BE REMOVED IN THE FUTURE.
|
||||
*
|
||||
* @param reactContext
|
||||
* @param componentName
|
||||
* @param surfaceId {@link int} surface ID
|
||||
* @param reactTag reactTag that should be set as ID of the view instance
|
||||
* @param params {@link MapBuffer} prefetch request params defined in C++
|
||||
*/
|
||||
@AnyThread
|
||||
@UnstableReactNativeAPI
|
||||
public void experimental_prefetchResource(
|
||||
ReactContext reactContext,
|
||||
String componentName,
|
||||
int surfaceId,
|
||||
int reactTag,
|
||||
MapBuffer params) {
|
||||
mViewManagerRegistry
|
||||
.get(componentName)
|
||||
.experimental_prefetchResource(reactContext, surfaceId, reactTag, params);
|
||||
}
|
||||
|
||||
public void enqueuePendingEvent(
|
||||
int surfaceId,
|
||||
int reactTag,
|
||||
String eventName,
|
||||
boolean canCoalesceEvent,
|
||||
@Nullable WritableMap params,
|
||||
@EventCategoryDef int eventCategory) {
|
||||
SurfaceMountingManager smm = getSurfaceMountingManager(surfaceId, reactTag);
|
||||
if (smm == null) {
|
||||
FLog.d(
|
||||
TAG,
|
||||
"Cannot queue event without valid surface mounting manager for tag: %d, surfaceId: %d",
|
||||
reactTag,
|
||||
surfaceId);
|
||||
return;
|
||||
}
|
||||
smm.enqueuePendingEvent(reactTag, eventName, canCoalesceEvent, params, eventCategory);
|
||||
}
|
||||
|
||||
private @Nullable SurfaceMountingManager getSurfaceMountingManager(int surfaceId, int reactTag) {
|
||||
return (surfaceId == ViewUtil.NO_SURFACE_ID
|
||||
? getSurfaceManagerForView(reactTag)
|
||||
: getSurfaceManager(surfaceId));
|
||||
}
|
||||
}
|
||||
+373
@@ -0,0 +1,373 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 android.view.View
|
||||
import androidx.annotation.AnyThread
|
||||
import androidx.annotation.UiThread
|
||||
import com.facebook.common.logging.FLog
|
||||
import com.facebook.infer.annotation.ThreadConfined
|
||||
import com.facebook.react.bridge.ReactContext
|
||||
import com.facebook.react.bridge.ReactSoftExceptionLogger.logSoftException
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.facebook.react.bridge.RetryableMountingLayerException
|
||||
import com.facebook.react.bridge.UiThreadUtil.assertOnUiThread
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
||||
import com.facebook.react.common.mapbuffer.MapBuffer
|
||||
import com.facebook.react.fabric.events.EventEmitterWrapper
|
||||
import com.facebook.react.fabric.mounting.mountitems.MountItem
|
||||
import com.facebook.react.touch.JSResponderHandler
|
||||
import com.facebook.react.uimanager.RootViewManager
|
||||
import com.facebook.react.uimanager.ThemedReactContext
|
||||
import com.facebook.react.uimanager.ViewManagerRegistry
|
||||
import com.facebook.react.uimanager.common.ViewUtil
|
||||
import com.facebook.react.uimanager.events.EventCategoryDef
|
||||
import com.facebook.yoga.YogaMeasureMode
|
||||
import java.util.Queue
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
/**
|
||||
* Class responsible for actually dispatching view updates enqueued via
|
||||
* [FabricUIManager.scheduleMountItem] on the UI thread.
|
||||
*/
|
||||
internal class MountingManager(
|
||||
private val viewManagerRegistry: ViewManagerRegistry,
|
||||
private val mountItemExecutor: MountItemExecutor
|
||||
) {
|
||||
private val surfaceIdToManager = ConcurrentHashMap<Int, SurfaceMountingManager>() // any thread
|
||||
|
||||
private val stoppedSurfaceIds = CopyOnWriteArrayList<Int>()
|
||||
|
||||
private var mostRecentSurfaceMountingManager: SurfaceMountingManager? = null
|
||||
private var lastQueriedSurfaceMountingManager: SurfaceMountingManager? = null
|
||||
|
||||
private val jsResponderHandler = JSResponderHandler()
|
||||
private val rootViewManager = RootViewManager()
|
||||
|
||||
internal fun interface MountItemExecutor {
|
||||
@UiThread @ThreadConfined(ThreadConfined.UI) fun executeItems(items: Queue<MountItem?>?)
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts surface without attaching the view. All view operations executed against that surface
|
||||
* will be queued until the view is attached.
|
||||
*/
|
||||
@AnyThread
|
||||
fun startSurface(
|
||||
surfaceId: Int,
|
||||
reactContext: ThemedReactContext?,
|
||||
rootView: View?
|
||||
): SurfaceMountingManager {
|
||||
val surfaceMountingManager =
|
||||
SurfaceMountingManager(
|
||||
surfaceId,
|
||||
jsResponderHandler,
|
||||
viewManagerRegistry,
|
||||
rootViewManager,
|
||||
mountItemExecutor,
|
||||
checkNotNull(reactContext))
|
||||
|
||||
// There could technically be a race condition here if addRootView is called twice from
|
||||
// different threads, though this is (probably) extremely unlikely, and likely an error.
|
||||
// This logic to protect against race conditions is a holdover from older code, and we don't
|
||||
// know if it actually happens in practice - so, we're logging soft exceptions for now.
|
||||
// This *will* crash in Debug mode, but not in production.
|
||||
surfaceIdToManager.putIfAbsent(surfaceId, surfaceMountingManager)
|
||||
if (surfaceIdToManager[surfaceId] !== surfaceMountingManager) {
|
||||
logSoftException(
|
||||
TAG,
|
||||
IllegalStateException(
|
||||
"Called startSurface more than once for the SurfaceId [$surfaceId]"))
|
||||
}
|
||||
|
||||
mostRecentSurfaceMountingManager = surfaceIdToManager[surfaceId]
|
||||
|
||||
if (rootView != null) {
|
||||
surfaceMountingManager.attachRootView(rootView, reactContext)
|
||||
}
|
||||
|
||||
return surfaceMountingManager
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
fun attachRootView(surfaceId: Int, rootView: View?, themedReactContext: ThemedReactContext?) {
|
||||
val surfaceMountingManager = getSurfaceManagerEnforced(surfaceId, "attachView")
|
||||
|
||||
if (surfaceMountingManager.isStopped) {
|
||||
logSoftException(TAG, IllegalStateException("Trying to attach a view to a stopped surface"))
|
||||
return
|
||||
}
|
||||
|
||||
surfaceMountingManager.attachRootView(rootView, themedReactContext)
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
fun stopSurface(surfaceId: Int) {
|
||||
val surfaceMountingManager = surfaceIdToManager[surfaceId]
|
||||
if (surfaceMountingManager != null) {
|
||||
// Maximum number of stopped surfaces to keep track of
|
||||
while (stoppedSurfaceIds.size >= MAX_STOPPED_SURFACE_IDS_LENGTH) {
|
||||
val staleStoppedId = stoppedSurfaceIds[0]
|
||||
checkNotNull(staleStoppedId)
|
||||
surfaceIdToManager.remove(staleStoppedId)
|
||||
stoppedSurfaceIds.remove(staleStoppedId)
|
||||
FLog.d(TAG, "Removing stale SurfaceMountingManager: [%d]", staleStoppedId)
|
||||
}
|
||||
stoppedSurfaceIds.add(surfaceId)
|
||||
|
||||
surfaceMountingManager.stopSurface()
|
||||
|
||||
if (mostRecentSurfaceMountingManager === surfaceMountingManager) {
|
||||
mostRecentSurfaceMountingManager = null
|
||||
}
|
||||
if (lastQueriedSurfaceMountingManager === surfaceMountingManager) {
|
||||
lastQueriedSurfaceMountingManager = null
|
||||
}
|
||||
} else {
|
||||
logSoftException(
|
||||
TAG,
|
||||
IllegalStateException("Cannot call stopSurface on non-existent surface: [$surfaceId]"))
|
||||
}
|
||||
}
|
||||
|
||||
fun getSurfaceManager(surfaceId: Int): SurfaceMountingManager? {
|
||||
if (lastQueriedSurfaceMountingManager?.surfaceId == surfaceId) {
|
||||
return lastQueriedSurfaceMountingManager
|
||||
}
|
||||
|
||||
if (mostRecentSurfaceMountingManager?.surfaceId == surfaceId) {
|
||||
return mostRecentSurfaceMountingManager
|
||||
}
|
||||
|
||||
val surfaceMountingManager = surfaceIdToManager[surfaceId]
|
||||
lastQueriedSurfaceMountingManager = surfaceMountingManager
|
||||
return surfaceMountingManager
|
||||
}
|
||||
|
||||
fun getSurfaceManagerEnforced(surfaceId: Int, context: String): SurfaceMountingManager =
|
||||
getSurfaceManager(surfaceId)
|
||||
?: throw RetryableMountingLayerException(
|
||||
("Unable to find SurfaceMountingManager for surfaceId: [$surfaceId]. Context: $context"))
|
||||
|
||||
fun surfaceIsStopped(surfaceId: Int): Boolean {
|
||||
if (stoppedSurfaceIds.contains(surfaceId)) {
|
||||
return true
|
||||
}
|
||||
|
||||
val surfaceMountingManager = getSurfaceManager(surfaceId)
|
||||
return surfaceMountingManager != null && surfaceMountingManager.isStopped
|
||||
}
|
||||
|
||||
fun isWaitingForViewAttach(surfaceId: Int): Boolean {
|
||||
val mountingManager = getSurfaceManager(surfaceId) ?: return false
|
||||
|
||||
if (mountingManager.isStopped) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !mountingManager.isRootViewAttached
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SurfaceMountingManager associated with a ReactTag. Unfortunately, this requires lookups
|
||||
* over N maps, where N is the number of active or recently-stopped Surfaces. Each lookup will
|
||||
* cost `log(M)` operations where M is the number of reactTags in the surface, so the total cost
|
||||
* per lookup is `O(N * log(M))`.
|
||||
*
|
||||
* To mitigate this cost, we attempt to keep track of the "most recent" SurfaceMountingManager and
|
||||
* do lookups in it first. For the vast majority of use-cases, except for events or operations
|
||||
* sent to off-screen surfaces, or use-cases where multiple surfaces are visible and interactable,
|
||||
* this will reduce the lookup time to `O(log(M))`. Someone smarter than me could probably figure
|
||||
* out an amortized time.
|
||||
*
|
||||
* @param reactTag
|
||||
* @return
|
||||
*/
|
||||
fun getSurfaceManagerForView(reactTag: Int): SurfaceMountingManager? {
|
||||
if (mostRecentSurfaceMountingManager?.getViewExists(reactTag) == true) {
|
||||
return mostRecentSurfaceMountingManager
|
||||
}
|
||||
|
||||
for ((_, smm) in surfaceIdToManager) {
|
||||
if (smm !== mostRecentSurfaceMountingManager && smm.getViewExists(reactTag)) {
|
||||
if (mostRecentSurfaceMountingManager == null) {
|
||||
mostRecentSurfaceMountingManager = smm
|
||||
}
|
||||
return smm
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
fun getSurfaceManagerForViewEnforced(reactTag: Int): SurfaceMountingManager =
|
||||
getSurfaceManagerForView(reactTag)
|
||||
?: throw RetryableMountingLayerException(
|
||||
"Unable to find SurfaceMountingManager for tag: [$reactTag]")
|
||||
|
||||
fun getViewExists(reactTag: Int): Boolean = getSurfaceManagerForView(reactTag) != null
|
||||
|
||||
@Deprecated(
|
||||
"receiveCommand with Int is deprecated, you should use receiveCommand with commandId:String",
|
||||
ReplaceWith("receiveCommand(Int,Int,String,ReadableArray)"))
|
||||
fun receiveCommand(surfaceId: Int, reactTag: Int, commandId: Int, commandArgs: ReadableArray) {
|
||||
assertOnUiThread()
|
||||
@Suppress("DEPRECATION")
|
||||
getSurfaceManagerEnforced(surfaceId, "receiveCommand:int")
|
||||
.receiveCommand(reactTag, commandId, commandArgs)
|
||||
}
|
||||
|
||||
fun receiveCommand(
|
||||
surfaceId: Int,
|
||||
reactTag: Int,
|
||||
commandId: String?,
|
||||
commandArgs: ReadableArray
|
||||
) {
|
||||
assertOnUiThread()
|
||||
getSurfaceManagerEnforced(surfaceId, "receiveCommand:string")
|
||||
.receiveCommand(reactTag, checkNotNull(commandId), commandArgs)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an accessibility eventType to a Native View. eventType is any valid `AccessibilityEvent.X`
|
||||
* value.
|
||||
*
|
||||
* Why accept {@ViewUtil.NO_SURFACE_ID}(-1) SurfaceId? Currently there are calls to
|
||||
* UIManager.sendAccessibilityEvent which is a legacy API and accepts only reactTag. We will have
|
||||
* to investigate and migrate away from those calls over time.
|
||||
*
|
||||
* @param surfaceId that identifies the surface or {@ViewUtil.NO_SURFACE_ID}(-1) to temporarily
|
||||
* support backward compatibility.
|
||||
* @param reactTag that identifies the react Tag of the view.
|
||||
* @param eventType that identifies Android eventType. see [View.sendAccessibilityEvent]
|
||||
*/
|
||||
fun sendAccessibilityEvent(surfaceId: Int, reactTag: Int, eventType: Int) {
|
||||
assertOnUiThread()
|
||||
if (surfaceId == View.NO_ID) {
|
||||
getSurfaceManagerForViewEnforced(reactTag).sendAccessibilityEvent(reactTag, eventType)
|
||||
} else {
|
||||
getSurfaceManagerEnforced(surfaceId, "sendAccessibilityEvent")
|
||||
.sendAccessibilityEvent(reactTag, eventType)
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun updateProps(reactTag: Int, props: ReadableMap?) {
|
||||
assertOnUiThread()
|
||||
if (props == null) {
|
||||
return
|
||||
}
|
||||
|
||||
getSurfaceManagerForViewEnforced(reactTag).updateProps(reactTag, props)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the JS Responder specified by [SurfaceMountingManager.setJSResponder]. After this method
|
||||
* is called, all the touch events are going to be handled by JS.
|
||||
*/
|
||||
@UiThread
|
||||
fun clearJSResponder() {
|
||||
// MountingManager and SurfaceMountingManagers all share the same JSResponderHandler.
|
||||
// Must be called on MountingManager instead of SurfaceMountingManager, because we don't
|
||||
// know what surfaceId it's being called for.
|
||||
jsResponderHandler.clearJSResponder()
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
@ThreadConfined(ThreadConfined.ANY)
|
||||
fun getEventEmitter(surfaceId: Int, reactTag: Int): EventEmitterWrapper? =
|
||||
getSurfaceMountingManager(surfaceId, reactTag)?.getEventEmitter(reactTag)
|
||||
|
||||
/**
|
||||
* Measure a component, given localData, props, state, and measurement information. This needs to
|
||||
* remain here for now - and not in SurfaceMountingManager - because sometimes measures are made
|
||||
* outside of the context of a Surface; especially from C++ before StartSurface is called.
|
||||
*/
|
||||
@AnyThread
|
||||
fun measure(
|
||||
context: ReactContext?,
|
||||
componentName: String?,
|
||||
localData: ReadableMap?,
|
||||
props: ReadableMap?,
|
||||
state: ReadableMap?,
|
||||
width: Float,
|
||||
widthMode: YogaMeasureMode?,
|
||||
height: Float,
|
||||
heightMode: YogaMeasureMode?,
|
||||
attachmentsPositions: FloatArray?
|
||||
): Long =
|
||||
viewManagerRegistry
|
||||
.get(checkNotNull(componentName))
|
||||
.measure(
|
||||
context,
|
||||
localData,
|
||||
props,
|
||||
state,
|
||||
width,
|
||||
widthMode,
|
||||
height,
|
||||
heightMode,
|
||||
attachmentsPositions)
|
||||
|
||||
/**
|
||||
* This prefetch method is experimental, do not use it for production code. it will most likely
|
||||
* change or be removed in the future.
|
||||
*
|
||||
* @param reactContext
|
||||
* @param componentName
|
||||
* @param surfaceId surface ID
|
||||
* @param reactTag reactTag that should be set as ID of the view instance
|
||||
* @param params prefetch request params defined in C++
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
@AnyThread
|
||||
@UnstableReactNativeAPI
|
||||
fun experimental_prefetchResource(
|
||||
reactContext: ReactContext?,
|
||||
componentName: String?,
|
||||
surfaceId: Int,
|
||||
reactTag: Int,
|
||||
params: MapBuffer?
|
||||
) {
|
||||
viewManagerRegistry
|
||||
.get(checkNotNull(componentName))
|
||||
.experimental_prefetchResource(reactContext, surfaceId, reactTag, params)
|
||||
}
|
||||
|
||||
fun enqueuePendingEvent(
|
||||
surfaceId: Int,
|
||||
reactTag: Int,
|
||||
eventName: String?,
|
||||
canCoalesceEvent: Boolean,
|
||||
params: WritableMap?,
|
||||
@EventCategoryDef eventCategory: Int
|
||||
) {
|
||||
val smm = getSurfaceMountingManager(surfaceId, reactTag)
|
||||
if (smm == null) {
|
||||
FLog.d(
|
||||
TAG,
|
||||
"Cannot queue event without valid surface mounting manager for tag: %d, surfaceId: %d",
|
||||
reactTag,
|
||||
surfaceId)
|
||||
return
|
||||
}
|
||||
smm.enqueuePendingEvent(reactTag, eventName, canCoalesceEvent, params, eventCategory)
|
||||
}
|
||||
|
||||
private fun getSurfaceMountingManager(surfaceId: Int, reactTag: Int): SurfaceMountingManager? =
|
||||
if (surfaceId == ViewUtil.NO_SURFACE_ID) getSurfaceManagerForView(reactTag)
|
||||
else getSurfaceManager(surfaceId)
|
||||
|
||||
companion object {
|
||||
val TAG: String = MountingManager::class.java.simpleName
|
||||
private const val MAX_STOPPED_SURFACE_IDS_LENGTH = 15
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user