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:
Nicola Corti
2025-06-09 08:44:02 -07:00
committed by Facebook GitHub Bot
parent a67d2c5e44
commit f33fdca876
3 changed files with 373 additions and 458 deletions
@@ -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
@@ -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));
}
}
@@ -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
}
}