From f9f1c2ffbbb623fbd0f436acda241b7dee671ae8 Mon Sep 17 00:00:00 2001 From: David Vacca Date: Fri, 20 Feb 2026 11:52:55 -0800 Subject: [PATCH 1/2] Delete NativeViewHierarchyOptimizer Summary: Remove the NativeViewHierarchyOptimizer class and all its usages. This class was part of the Legacy Architecture and had already been largely gutted (most logic removed due to NativeKind removal). Its remaining responsibilities have been inlined into the call sites: UIImplementation now directly enqueues view operations on UIViewOperationQueue, and ReactShadowNodeImpl.dispatchUpdates now directly calls enqueueUpdateLayout. The NativeViewHierarchyOptimizer parameter has been removed from the ReactShadowNode.onBeforeLayout() and dispatchUpdates() interfaces. changelog: [internal] internal Reviewed By: NickGerleman Differential Revision: D93037023 --- .../ReactAndroid/api/ReactAndroid.api | 21 +- .../NativeViewHierarchyOptimizer.java | 395 ------------------ .../react/uimanager/ReactShadowNode.java | 23 +- .../react/uimanager/ReactShadowNodeImpl.java | 46 +- .../react/uimanager/UIImplementation.java | 30 +- .../react/views/view/ReactViewManager.kt | 3 +- 6 files changed, 49 insertions(+), 469 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 012813425c85..ddb1ac1c6ac5 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -3539,19 +3539,6 @@ public class com/facebook/react/uimanager/NativeViewHierarchyManager { public fun updateViewExtraData (ILjava/lang/Object;)V } -public class com/facebook/react/uimanager/NativeViewHierarchyOptimizer { - public fun (Lcom/facebook/react/uimanager/UIViewOperationQueue;Lcom/facebook/react/uimanager/ShadowNodeRegistry;)V - public static fun assertNodeSupportedWithoutOptimizer (Lcom/facebook/react/uimanager/ReactShadowNode;)V - public fun handleCreateView (Lcom/facebook/react/uimanager/ReactShadowNode;Lcom/facebook/react/uimanager/ThemedReactContext;Lcom/facebook/react/uimanager/ReactStylesDiffMap;)V - public fun handleForceViewToBeNonLayoutOnly (Lcom/facebook/react/uimanager/ReactShadowNode;)V - public fun handleManageChildren (Lcom/facebook/react/uimanager/ReactShadowNode;[I[I[Lcom/facebook/react/uimanager/ViewAtIndex;[I)V - public static fun handleRemoveNode (Lcom/facebook/react/uimanager/ReactShadowNode;)V - public fun handleSetChildren (Lcom/facebook/react/uimanager/ReactShadowNode;Lcom/facebook/react/bridge/ReadableArray;)V - public fun handleUpdateLayout (Lcom/facebook/react/uimanager/ReactShadowNode;)V - public fun handleUpdateView (Lcom/facebook/react/uimanager/ReactShadowNode;Ljava/lang/String;Lcom/facebook/react/uimanager/ReactStylesDiffMap;)V - public fun onBatchComplete ()V -} - public final class com/facebook/react/uimanager/OnLayoutEvent : com/facebook/react/uimanager/events/Event { public static final field Companion Lcom/facebook/react/uimanager/OnLayoutEvent$Companion; public fun getEventName ()Ljava/lang/String; @@ -3852,7 +3839,7 @@ public abstract interface class com/facebook/react/uimanager/ReactShadowNode { public abstract fun calculateLayout (FF)V public abstract fun calculateLayoutOnChildren ()Ljava/lang/Iterable; public abstract fun dirty ()V - public abstract fun dispatchUpdates (FFLcom/facebook/react/uimanager/UIViewOperationQueue;Lcom/facebook/react/uimanager/NativeViewHierarchyOptimizer;)V + public abstract fun dispatchUpdates (FFLcom/facebook/react/uimanager/UIViewOperationQueue;)V public abstract fun dispatchUpdatesWillChangeLayout (FF)Z public abstract fun dispose ()V public abstract fun getChildAt (I)Lcom/facebook/react/uimanager/ReactShadowNode; @@ -3901,7 +3888,7 @@ public abstract interface class com/facebook/react/uimanager/ReactShadowNode { public abstract fun markUpdateSeen ()V public abstract fun markUpdated ()V public abstract fun onAfterUpdateTransaction ()V - public abstract fun onBeforeLayout (Lcom/facebook/react/uimanager/NativeViewHierarchyOptimizer;)V + public abstract fun onBeforeLayout ()V public abstract fun onCollectExtraUpdates (Lcom/facebook/react/uimanager/UIViewOperationQueue;)V public abstract fun removeAllNativeChildren ()V public abstract fun removeAndDisposeAllChildren ()V @@ -3978,7 +3965,7 @@ public class com/facebook/react/uimanager/ReactShadowNodeImpl : com/facebook/rea public fun calculateLayout (FF)V public fun calculateLayoutOnChildren ()Ljava/lang/Iterable; public fun dirty ()V - public fun dispatchUpdates (FFLcom/facebook/react/uimanager/UIViewOperationQueue;Lcom/facebook/react/uimanager/NativeViewHierarchyOptimizer;)V + public fun dispatchUpdates (FFLcom/facebook/react/uimanager/UIViewOperationQueue;)V public fun dispatchUpdatesWillChangeLayout (FF)Z public fun dispose ()V public synthetic fun getChildAt (I)Lcom/facebook/react/uimanager/ReactShadowNode; @@ -4035,7 +4022,7 @@ public class com/facebook/react/uimanager/ReactShadowNodeImpl : com/facebook/rea public final fun markUpdateSeen ()V public fun markUpdated ()V public fun onAfterUpdateTransaction ()V - public fun onBeforeLayout (Lcom/facebook/react/uimanager/NativeViewHierarchyOptimizer;)V + public fun onBeforeLayout ()V public fun onCollectExtraUpdates (Lcom/facebook/react/uimanager/UIViewOperationQueue;)V public final fun removeAllNativeChildren ()V public fun removeAndDisposeAllChildren ()V diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java deleted file mode 100644 index 79f3f6b5db5f..000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java +++ /dev/null @@ -1,395 +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.uimanager; - -import android.util.SparseBooleanArray; -import androidx.annotation.Nullable; -import com.facebook.common.logging.FLog; -import com.facebook.infer.annotation.Assertions; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMapKeySetIterator; -import com.facebook.react.common.annotations.internal.LegacyArchitecture; -import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel; -import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger; - -/** - * Class responsible for optimizing the native view hierarchy while still respecting the final UI - * product specified by JS. Basically, JS sends us a hierarchy of nodes that, while easy to reason - * about in JS, are very inefficient to translate directly to native views. This class sits in - * between {@link UIManagerModule}, which directly receives view commands from JS, and {@link - * UIViewOperationQueue}, which enqueues actual operations on the native view hierarchy. It is able - * to take instructions from UIManagerModule and output instructions to the native view hierarchy - * that achieve the same displayed UI but with fewer views. - * - *

Currently this class is only used to remove layout-only views, that is to say views that only - * affect the positions of their children but do not draw anything themselves. These views are - * fairly common because 1) containers are used to do layouting via flexbox and 2) the return of - * each Component#render() call in JS must be exactly one view, which means views are often wrapped - * in a unnecessary layer of hierarchy. - * - *

This optimization is implemented by keeping track of both the unoptimized JS hierarchy and the - * optimized native hierarchy in {@link ReactShadowNode}. - * - *

This optimization is important for view hierarchy depth (which can cause stack overflows - * during view traversal for complex apps), memory usage, amount of time spent during GCs, and - * time-to-display. - * - *

Some examples of the optimizations this class will do based on commands from JS: - Create a - * view with only layout props: a description of that view is created as a {@link ReactShadowNode} - * in UIManagerModule, but this class will not output any commands to create the view in the native - * view hierarchy. - Update a layout-only view to have non-layout props: before issuing the - * updateShadowNode call to the native view hierarchy, issue commands to create the view we - * optimized away move it into the view hierarchy - Manage the children of a view: multiple - * manageChildren calls for various parent views may be issued to the native view hierarchy - * depending on where the views being added/removed are attached in the optimized hierarchy - */ -@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR) -@Deprecated( - since = "This class is part of Legacy Architecture and will be removed in a future release") -public class NativeViewHierarchyOptimizer { - - static { - LegacyArchitectureLogger.assertLegacyArchitecture( - "NativeViewHierarchyOptimizer", LegacyArchitectureLogLevel.ERROR); - } - - private static final String TAG = "NativeViewHierarchyOptimizer"; - - private static class NodeIndexPair { - public final ReactShadowNode node; - public final int index; - - NodeIndexPair(ReactShadowNode node, int index) { - this.node = node; - this.index = index; - } - } - - private static final boolean ENABLED = true; - - private final UIViewOperationQueue mUIViewOperationQueue; - private final ShadowNodeRegistry mShadowNodeRegistry; - private final SparseBooleanArray mTagsWithLayoutVisited = new SparseBooleanArray(); - - public static void assertNodeSupportedWithoutOptimizer(ReactShadowNode node) { - // Assertions removed due to NativeKind removal - } - - public NativeViewHierarchyOptimizer( - UIViewOperationQueue uiViewOperationQueue, ShadowNodeRegistry shadowNodeRegistry) { - mUIViewOperationQueue = uiViewOperationQueue; - mShadowNodeRegistry = shadowNodeRegistry; - } - - /** Handles a createView call. May or may not actually create a native view. */ - public void handleCreateView( - ReactShadowNode node, - ThemedReactContext themedContext, - @Nullable ReactStylesDiffMap initialProps) { - if (!ENABLED) { - assertNodeSupportedWithoutOptimizer(node); - int tag = node.getReactTag(); - mUIViewOperationQueue.enqueueCreateView( - themedContext, tag, node.getViewClass(), initialProps); - return; - } - - boolean isLayoutOnly = - node.getViewClass().equals(ViewProps.VIEW_CLASS_NAME) - && isLayoutOnlyAndCollapsable(initialProps); - node.setIsLayoutOnly(isLayoutOnly); - - // enqueueCreateView call removed due to NativeKind removal - } - - /** Handles native children cleanup when css node is removed from hierarchy */ - public static void handleRemoveNode(ReactShadowNode node) { - node.removeAllNativeChildren(); - } - - /** - * Handles an updateView call. If a view transitions from being layout-only to not (or vice-versa) - * this could result in some number of additional createView and manageChildren calls. If the view - * is layout only, no updateView call will be dispatched to the native hierarchy. - */ - public void handleUpdateView(ReactShadowNode node, String className, ReactStylesDiffMap props) { - if (!ENABLED) { - assertNodeSupportedWithoutOptimizer(node); - mUIViewOperationQueue.enqueueUpdateProperties(node.getReactTag(), className, props); - return; - } - - boolean needsToLeaveLayoutOnly = node.isLayoutOnly() && !isLayoutOnlyAndCollapsable(props); - if (needsToLeaveLayoutOnly) { - transitionLayoutOnlyViewToNativeView(node, props); - } else if (!node.isLayoutOnly()) { - mUIViewOperationQueue.enqueueUpdateProperties(node.getReactTag(), className, props); - } - } - - /** - * Handles a manageChildren call. This may translate into multiple manageChildren calls for - * multiple other views. - * - *

NB: the assumption for calling this method is that all corresponding ReactShadowNodes have - * been updated **but tagsToDelete have NOT been deleted yet**. This is because we need to use the - * metadata from those nodes to figure out the correct commands to dispatch. This is unlike all - * other calls on this class where we assume all operations on the shadow hierarchy have already - * completed by the time a corresponding method here is called. - */ - public void handleManageChildren( - ReactShadowNode nodeToManage, - int[] indicesToRemove, - int[] tagsToRemove, - ViewAtIndex[] viewsToAdd, - int[] tagsToDelete) { - if (!ENABLED) { - assertNodeSupportedWithoutOptimizer(nodeToManage); - mUIViewOperationQueue.enqueueManageChildren( - nodeToManage.getReactTag(), indicesToRemove, viewsToAdd, tagsToDelete); - return; - } - - // We operate on tagsToRemove instead of indicesToRemove because by the time this method is - // called, these views have already been removed from the shadow hierarchy and the indices are - // no longer useful to operate on - for (int i = 0; i < tagsToRemove.length; i++) { - int tagToRemove = tagsToRemove[i]; - boolean delete = false; - for (int j = 0; j < tagsToDelete.length; j++) { - if (tagsToDelete[j] == tagToRemove) { - delete = true; - break; - } - } - ReactShadowNode nodeToRemove = mShadowNodeRegistry.getNode(tagToRemove); - removeNodeFromParent(nodeToRemove, delete); - } - - for (int i = 0; i < viewsToAdd.length; i++) { - ViewAtIndex toAdd = viewsToAdd[i]; - ReactShadowNode nodeToAdd = mShadowNodeRegistry.getNode(toAdd.mTag); - addNodeToNode(nodeToManage, nodeToAdd, toAdd.mIndex); - } - } - - /** - * Handles a setChildren call. This is a simplification of handleManagerChildren that only adds - * children in index order of the childrenTags array - */ - public void handleSetChildren(ReactShadowNode nodeToManage, ReadableArray childrenTags) { - if (!ENABLED) { - assertNodeSupportedWithoutOptimizer(nodeToManage); - mUIViewOperationQueue.enqueueSetChildren(nodeToManage.getReactTag(), childrenTags); - return; - } - - for (int i = 0; i < childrenTags.size(); i++) { - ReactShadowNode nodeToAdd = mShadowNodeRegistry.getNode(childrenTags.getInt(i)); - addNodeToNode(nodeToManage, nodeToAdd, i); - } - } - - /** - * Handles an updateLayout call. All updateLayout calls are collected and dispatched at the end of - * a batch because updateLayout calls to layout-only nodes can necessitate multiple updateLayout - * calls for all its children. - */ - public void handleUpdateLayout(ReactShadowNode node) { - if (!ENABLED) { - assertNodeSupportedWithoutOptimizer(node); - mUIViewOperationQueue.enqueueUpdateLayout( - Assertions.assertNotNull(node.getLayoutParent()).getReactTag(), - node.getReactTag(), - node.getScreenX(), - node.getScreenY(), - node.getScreenWidth(), - node.getScreenHeight(), - node.getLayoutDirection()); - return; - } - - applyLayoutBase(node); - } - - public void handleForceViewToBeNonLayoutOnly(ReactShadowNode node) { - if (node.isLayoutOnly()) { - transitionLayoutOnlyViewToNativeView(node, null); - } - } - - /** - * Processes the shadow hierarchy to dispatch all necessary updateLayout calls to the native - * hierarchy. Should be called after all updateLayout calls for a batch have been handled. - */ - public void onBatchComplete() { - mTagsWithLayoutVisited.clear(); - } - - private void addNodeToNode(ReactShadowNode parent, ReactShadowNode child, int index) { - // Logic removed due to NativeKind removal - } - - /** - * For handling node removal from manageChildren. In the case of removing a node which isn't - * hosting its own children (e.g. layout-only or NativeKind.LEAF), we need to recursively remove - * all its children from their native parents. - */ - private void removeNodeFromParent(ReactShadowNode nodeToRemove, boolean shouldDelete) { - // Recursive removal logic removed due to NativeKind removal - - ReactShadowNode nativeNodeToRemoveFrom = nodeToRemove.getNativeParent(); - if (nativeNodeToRemoveFrom != null) { - int index = nativeNodeToRemoveFrom.indexOfNativeChild(nodeToRemove); - nativeNodeToRemoveFrom.removeNativeChildAt(index); - - mUIViewOperationQueue.enqueueManageChildren( - nativeNodeToRemoveFrom.getReactTag(), - new int[] {index}, - null, - shouldDelete ? new int[] {nodeToRemove.getReactTag()} : null); - } - } - - private void addNonNativeChild( - ReactShadowNode nativeParent, ReactShadowNode nonNativeChild, int index) { - addGrandchildren(nativeParent, nonNativeChild, index); - } - - private void addNativeChild(ReactShadowNode parent, ReactShadowNode child, int index) { - parent.addNativeChildAt(child, index); - mUIViewOperationQueue.enqueueManageChildren( - parent.getReactTag(), - null, - new ViewAtIndex[] {new ViewAtIndex(child.getReactTag(), index)}, - null); - - // addGrandchildren call removed due to NativeKind removal - } - - private void addGrandchildren(ReactShadowNode nativeParent, ReactShadowNode child, int index) { - // Logic removed due to NativeKind removal - } - - private void applyLayoutBase(ReactShadowNode node) { - int tag = node.getReactTag(); - if (mTagsWithLayoutVisited.get(tag)) { - return; - } - mTagsWithLayoutVisited.put(tag, true); - - // We use screenX/screenY (which round to integer pixels) at each node in the hierarchy to - // emulate what the layout would look like if it were actually built with native views which - // have to have integral top/left/bottom/right values - int x = node.getScreenX(); - int y = node.getScreenY(); - - // Layout calculation logic removed due to NativeKind removal - - applyLayoutRecursive(node, x, y); - } - - private void applyLayoutRecursive(ReactShadowNode toUpdate, int x, int y) { - // enqueueUpdateLayout call removed due to NativeKind removal - - for (int i = 0; i < toUpdate.getChildCount(); i++) { - ReactShadowNode child = toUpdate.getChildAt(i); - int childTag = child.getReactTag(); - if (mTagsWithLayoutVisited.get(childTag)) { - continue; - } - mTagsWithLayoutVisited.put(childTag, true); - - int childX = child.getScreenX(); - int childY = child.getScreenY(); - - childX += x; - childY += y; - - applyLayoutRecursive(child, childX, childY); - } - } - - private void transitionLayoutOnlyViewToNativeView( - ReactShadowNode node, @Nullable ReactStylesDiffMap props) { - ReactShadowNode parent = node.getParent(); - if (parent == null) { - node.setIsLayoutOnly(false); - return; - } - - // First, remove the node from its parent. This causes the parent to update its native children - // count. The removeNodeFromParent call will cause all the view's children to be detached from - // their native parent. - int childIndex = parent.indexOf(node); - parent.removeChildAt(childIndex); - removeNodeFromParent(node, false); - - node.setIsLayoutOnly(false); - - // Create the view since it doesn't exist in the native hierarchy yet - mUIViewOperationQueue.enqueueCreateView( - node.getThemedContext(), node.getReactTag(), node.getViewClass(), props); - - // Add the node and all its children as if we are adding a new nodes - parent.addChildAt(node, childIndex); - addNodeToNode(parent, node, childIndex); - for (int i = 0; i < node.getChildCount(); i++) { - addNodeToNode(node, node.getChildAt(i), i); - } - - // Update layouts since the children of the node were offset by its x/y position previously. - // Bit of a hack: we need to update the layout of this node's children now that it's no longer - // layout-only, but we may still receive more layout updates at the end of this batch that we - // don't want to ignore. - FLog.i( - TAG, - "Transitioning LayoutOnlyView - tag: " - + node.getReactTag() - + " - rootTag: " - + node.getRootTag() - + " - hasProps: " - + (props != null) - + " - tagsWithLayout.size: " - + mTagsWithLayoutVisited.size()); - Assertions.assertCondition(mTagsWithLayoutVisited.size() == 0); - applyLayoutBase(node); - for (int i = 0; i < node.getChildCount(); i++) { - applyLayoutBase(node.getChildAt(i)); - } - mTagsWithLayoutVisited.clear(); - } - - private static boolean isLayoutOnlyAndCollapsable(@Nullable ReactStylesDiffMap props) { - if (props == null) { - return true; - } - - if (props.hasKey(ViewProps.COLLAPSABLE) && !props.getBoolean(ViewProps.COLLAPSABLE, true)) { - return false; - } - - ReadableMapKeySetIterator keyIterator = props.internal_backingMap().keySetIterator(); - while (keyIterator.hasNextKey()) { - if (!ViewProps.isLayoutOnly(props.internal_backingMap(), keyIterator.nextKey())) { - return false; - } - } - return true; - } - - /** - * Called when all the view updates of {@link ReactShadowNode} received as a parameter were - * processed. - */ - void onViewUpdatesCompleted(ReactShadowNode cssNode) { - // cssNode is not being used, but it is passed as a parameter in case this is required in the - // future. - mTagsWithLayoutVisited.clear(); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java index 93048ea91e77..c66166bc60fc 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java @@ -42,8 +42,7 @@ * *

This class allows for the native view hierarchy to not be an exact copy of the hierarchy * received from JS by keeping track of both JS children (e.g. {@link #getChildCount()} and - * separately native children (e.g. {@link #getNativeChildCount()}). See {@link - * NativeViewHierarchyOptimizer} for more information. + * separately native children (e.g. {@link #getNativeChildCount()}). */ @LegacyArchitecture @Deprecated( @@ -112,7 +111,7 @@ public interface ReactShadowNode { * layout. Will be only called for nodes that are marked as updated with {@link #markUpdated()} or * require layouting (marked with {@link #dirty()}). */ - void onBeforeLayout(NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer); + void onBeforeLayout(); void updateProperties(ReactStylesDiffMap props); @@ -130,10 +129,7 @@ public interface ReactShadowNode { /* package */ boolean dispatchUpdatesWillChangeLayout(float absoluteX, float absoluteY); /* package */ void dispatchUpdates( - float absoluteX, - float absoluteY, - UIViewOperationQueue uiViewOperationQueue, - NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer); + float absoluteX, float absoluteY, UIViewOperationQueue uiViewOperationQueue); int getReactTag(); @@ -227,13 +223,12 @@ public interface ReactShadowNode { * in this subtree (which means that the given child will be a sibling of theirs in the final * native hierarchy since they'll get attached to the same native parent). * - *

Basically, a view might have children that have been optimized away by {@link - * NativeViewHierarchyOptimizer}. Since those children will then add their native children to this - * view, we now have ranges of native children that correspond to single unoptimized children. The - * purpose of this method is to return the index within the native children that corresponds to - * the **start** of the native children that belong to the given child. Also, note that all of the - * children of a view might be optimized away, so this could return the same value for multiple - * different children. + *

Basically, a view might have children that have been optimized away. Since those children + * will then add their native children to this view, we now have ranges of native children that + * correspond to single unoptimized children. The purpose of this method is to return the index + * within the native children that corresponds to the **start** of the native children that belong + * to the given child. Also, note that all of the children of a view might be optimized away, so + * this could return the same value for multiple different children. * *

Example. Native children are represented by (N) where N is the no-opt child they came from. * If no children are optimized away it'd look like this: (0) (1) (2) (3) ... (n) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java index 99f82cdf2030..e40da52e30d9 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java @@ -55,8 +55,7 @@ * *

This class allows for the native view hierarchy to not be an exact copy of the hierarchy * received from JS by keeping track of both JS children (e.g. {@link #getChildCount()} and - * separately native children (e.g. {@link #getNativeChildCount()}). See {@link - * NativeViewHierarchyOptimizer} for more information. + * separately native children (e.g. {@link #getNativeChildCount()}). */ @ReactPropertyHolder @LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR) @@ -319,7 +318,7 @@ private void updateNativeChildrenCountInParent(int delta) { * require layouting (marked with {@link #dirty()}). */ @Override - public void onBeforeLayout(NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer) {} + public void onBeforeLayout() {} @Override public final void updateProperties(ReactStylesDiffMap props) { @@ -368,10 +367,7 @@ public boolean dispatchUpdatesWillChangeLayout(float absoluteX, float absoluteY) @Override public void dispatchUpdates( - float absoluteX, - float absoluteY, - UIViewOperationQueue uiViewOperationQueue, - @Nullable NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer) { + float absoluteX, float absoluteY, UIViewOperationQueue uiViewOperationQueue) { if (mNodeUpdated) { onCollectExtraUpdates(uiViewOperationQueue); } @@ -400,20 +396,15 @@ public void dispatchUpdates( mScreenWidth = newScreenWidth; mScreenHeight = newScreenHeight; - if (layoutHasChanged) { - // TODO: T26400974 ReactShadowNode should not depend on nativeViewHierarchyOptimizer - if (nativeViewHierarchyOptimizer != null) { - nativeViewHierarchyOptimizer.handleUpdateLayout(this); - } else { - uiViewOperationQueue.enqueueUpdateLayout( - getParent().getReactTag(), - getReactTag(), - getScreenX(), - getScreenY(), - getScreenWidth(), - getScreenHeight(), - getLayoutDirection()); - } + if (layoutHasChanged && getParent() != null) { + uiViewOperationQueue.enqueueUpdateLayout( + getParent().getReactTag(), + getReactTag(), + getScreenX(), + getScreenY(), + getScreenWidth(), + getScreenHeight(), + getLayoutDirection()); } } } @@ -623,13 +614,12 @@ public void setLocalData(Object data) {} * in this subtree (which means that the given child will be a sibling of theirs in the final * native hierarchy since they'll get attached to the same native parent). * - *

Basically, a view might have children that have been optimized away by {@link - * NativeViewHierarchyOptimizer}. Since those children will then add their native children to this - * view, we now have ranges of native children that correspond to single unoptimized children. The - * purpose of this method is to return the index within the native children that corresponds to - * the **start** of the native children that belong to the given child. Also, note that all of the - * children of a view might be optimized away, so this could return the same value for multiple - * different children. + *

Basically, a view might have children that have been optimized away. Since those children + * will then add their native children to this view, we now have ranges of native children that + * correspond to single unoptimized children. The purpose of this method is to return the index + * within the native children that corresponds to the **start** of the native children that belong + * to the given child. Also, note that all of the children of a view might be optimized away, so + * this could return the same value for multiple different children. * *

Example. Native children are represented by (N) where N is the no-opt child they came from. * If no children are optimized away it'd look like this: (0) (1) (2) (3) ... (n) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java index 2e7bc238553a..74e0b91597e0 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java @@ -56,7 +56,6 @@ public class UIImplementation { protected final ShadowNodeRegistry mShadowNodeRegistry = new ShadowNodeRegistry(); private final ViewManagerRegistry mViewManagers; private final UIViewOperationQueue mOperationsQueue; - private final NativeViewHierarchyOptimizer mNativeViewHierarchyOptimizer; private final int[] mMeasureBuffer = new int[4]; private long mLastCalculateLayoutTime = 0; @@ -99,8 +98,6 @@ protected UIImplementation( mReactContext = reactContext; mViewManagers = viewManagers; mOperationsQueue = operationsQueue; - mNativeViewHierarchyOptimizer = - new NativeViewHierarchyOptimizer(mOperationsQueue, mShadowNodeRegistry); mEventDispatcher = eventDispatcher; } @@ -279,7 +276,8 @@ public void createView(int tag, String className, int rootViewTag, ReadableMap p protected void handleCreateView( ReactShadowNode cssNode, int rootViewTag, @Nullable ReactStylesDiffMap styles) { if (!cssNode.isVirtual()) { - mNativeViewHierarchyOptimizer.handleCreateView(cssNode, cssNode.getThemedContext(), styles); + mOperationsQueue.enqueueCreateView( + cssNode.getThemedContext(), cssNode.getReactTag(), cssNode.getViewClass(), styles); } } @@ -319,7 +317,7 @@ public void synchronouslyUpdateViewOnUIThread(int tag, ReactStylesDiffMap props) protected void handleUpdateView( ReactShadowNode cssNode, String className, ReactStylesDiffMap styles) { if (!cssNode.isVirtual()) { - mNativeViewHierarchyOptimizer.handleUpdateView(cssNode, className, styles); + mOperationsQueue.enqueueUpdateProperties(cssNode.getReactTag(), className, styles); } } @@ -345,6 +343,10 @@ public void manageChildren( synchronized (uiImplementationThreadLock) { ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag); + if (cssNodeToManage == null) { + throw new IllegalViewOperationException( + "Trying to manage children of unknown view tag: " + viewTag); + } int numToMove = moveFrom == null ? 0 : moveFrom.size(); int numToAdd = addChildTags == null ? 0 : addChildTags.size(); @@ -432,8 +434,8 @@ public void manageChildren( cssNodeToManage.addChildAt(cssNodeToAdd, viewAtIndex.mIndex); } - mNativeViewHierarchyOptimizer.handleManageChildren( - cssNodeToManage, indicesToRemove, tagsToRemove, viewsToAdd, tagsToDelete); + mOperationsQueue.enqueueManageChildren( + cssNodeToManage.getReactTag(), indicesToRemove, viewsToAdd, tagsToDelete); for (int i = 0; i < tagsToDelete.length; i++) { removeShadowNode(mShadowNodeRegistry.getNode(tagsToDelete[i])); @@ -455,6 +457,10 @@ public void setChildren(int viewTag, ReadableArray childrenTags) { synchronized (uiImplementationThreadLock) { ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag); + if (cssNodeToManage == null) { + throw new IllegalViewOperationException( + "Trying to set children of unknown view tag: " + viewTag); + } for (int i = 0; i < childrenTags.size(); i++) { ReactShadowNode cssNodeToAdd = mShadowNodeRegistry.getNode(childrenTags.getInt(i)); @@ -465,7 +471,7 @@ public void setChildren(int viewTag, ReadableArray childrenTags) { cssNodeToManage.addChildAt(cssNodeToAdd, i); } - mNativeViewHierarchyOptimizer.handleSetChildren(cssNodeToManage, childrenTags); + mOperationsQueue.enqueueSetChildren(cssNodeToManage.getReactTag(), childrenTags); } } @@ -619,7 +625,6 @@ public void dispatchViewUpdates(int batchId) { final long commitStartTime = SystemClock.uptimeMillis(); try { updateViewHierarchy(); - mNativeViewHierarchyOptimizer.onBatchComplete(); mOperationsQueue.dispatchViewUpdates(batchId, commitStartTime, mLastCalculateLayoutTime); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT); @@ -785,7 +790,7 @@ protected final void removeShadowNode(ReactShadowNode nodeToRemove) { } private void removeShadowNodeRecursive(ReactShadowNode nodeToRemove) { - NativeViewHierarchyOptimizer.handleRemoveNode(nodeToRemove); + nodeToRemove.removeAllNativeChildren(); mShadowNodeRegistry.removeNode(nodeToRemove.getReactTag()); for (int i = nodeToRemove.getChildCount() - 1; i >= 0; i--) { removeShadowNodeRecursive(nodeToRemove.getChildAt(i)); @@ -906,7 +911,7 @@ private void notifyOnBeforeLayoutRecursive(ReactShadowNode cssNode) { for (int i = 0; i < cssNode.getChildCount(); i++) { notifyOnBeforeLayoutRecursive(cssNode.getChildAt(i)); } - cssNode.onBeforeLayout(mNativeViewHierarchyOptimizer); + cssNode.onBeforeLayout(); } protected void calculateRootLayout(ReactShadowNode cssRoot) { @@ -956,10 +961,9 @@ protected void applyUpdatesRecursive( } } - cssNode.dispatchUpdates(absoluteX, absoluteY, mOperationsQueue, mNativeViewHierarchyOptimizer); + cssNode.dispatchUpdates(absoluteX, absoluteY, mOperationsQueue); cssNode.markUpdateSeen(); - mNativeViewHierarchyOptimizer.onViewUpdatesCompleted(cssNode); } public void addUIBlock(UIBlock block) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt index 0a5f3a6d7b17..1ced2c033428 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt @@ -389,8 +389,7 @@ public open class ReactViewManager : ReactClippingViewManager() @ReactProp(name = ViewProps.COLLAPSABLE) @Suppress("UNUSED_PARAMETER") public open fun setCollapsable(view: ReactViewGroup, collapsable: Boolean) { - // no-op: it's here only so that "collapsable" property is exported to JS. The value is actually - // handled in NativeViewHierarchyOptimizer + // no-op: it's here only so that "collapsable" property is exported to JS. } @ReactProp(name = ViewProps.COLLAPSABLE_CHILDREN) From d8a129eb5a01ff267aa3e53a3e9e8ea6de412dda Mon Sep 17 00:00:00 2001 From: David Vacca Date: Fri, 20 Feb 2026 11:52:55 -0800 Subject: [PATCH 2/2] Stub UIViewOperationQueue and remove its usages Summary: UIViewOperationQueue is part of the Legacy Architecture and is no longer needed. This change stubs out all methods in the class (making them no-ops) and removes all call sites that invoked those methods across the codebase. Specifically: - UIViewOperationQueue: All method bodies replaced with empty stubs, all inner classes removed, all fields removed. The public API signatures are preserved for backward compatibility. - UIImplementation: All calls to mOperationsQueue.enqueue*, dispatchViewUpdates, resumeFrameCallback, pauseFrameCallback, etc. removed. Layout update listener is now called directly instead of through the queue. - UIManagerModule: resolveView() now returns null instead of chaining through the operation queue. - ReactShadowNodeImpl: dispatchUpdates() no longer calls enqueueUpdateLayout on the operation queue. - ARTSurfaceViewShadowNode: Deleted the onCollectExtraUpdates override that called enqueueUpdateExtraData. Reviewed By: NickGerleman Differential Revision: D93069524 --- .../react/uimanager/ReactShadowNodeImpl.java | 17 - .../react/uimanager/UIImplementation.java | 104 +- .../react/uimanager/UIManagerModule.java | 7 +- .../react/uimanager/UIViewOperationQueue.java | 994 +----------------- .../react/views/text/ReactTextView.java | 4 + 5 files changed, 60 insertions(+), 1066 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java index e40da52e30d9..fe254d5f7e20 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java @@ -385,27 +385,10 @@ public void dispatchUpdates( int newScreenWidth = newAbsoluteRight - newAbsoluteLeft; int newScreenHeight = newAbsoluteBottom - newAbsoluteTop; - boolean layoutHasChanged = - newScreenX != mScreenX - || newScreenY != mScreenY - || newScreenWidth != mScreenWidth - || newScreenHeight != mScreenHeight; - mScreenX = newScreenX; mScreenY = newScreenY; mScreenWidth = newScreenWidth; mScreenHeight = newScreenHeight; - - if (layoutHasChanged && getParent() != null) { - uiViewOperationQueue.enqueueUpdateLayout( - getParent().getReactTag(), - getReactTag(), - getScreenX(), - getScreenY(), - getScreenWidth(), - getScreenHeight(), - getLayoutDirection()); - } } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java index 74e0b91597e0..bbaa7fc909ea 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java @@ -7,7 +7,6 @@ package com.facebook.react.uimanager; -import android.os.SystemClock; import android.view.View; import android.view.View.MeasureSpec; import androidx.annotation.Nullable; @@ -33,6 +32,7 @@ import com.facebook.yoga.YogaDirection; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -58,7 +58,6 @@ public class UIImplementation { private final UIViewOperationQueue mOperationsQueue; private final int[] mMeasureBuffer = new int[4]; - private long mLastCalculateLayoutTime = 0; protected @Nullable LayoutUpdateListener mLayoutUpdateListener; /** @@ -167,16 +166,12 @@ public void run() { mShadowNodeRegistry.addRootNode(rootCSSNode); } }); - - // register it within NativeViewHierarchyManager - mOperationsQueue.addRootView(tag, rootView); } } /** Unregisters a root node with a given tag. */ public void removeRootView(int rootViewTag) { removeRootShadowNode(rootViewTag); - mOperationsQueue.enqueueRemoveRootView(rootViewTag); } /** @@ -185,7 +180,7 @@ public void removeRootView(int rootViewTag) { * @return The num of root view */ public int getRootViewNum() { - return mOperationsQueue.getNativeViewHierarchyManager().getRootViewNum(); + return 0; } /** Unregisters a root node with a given tag from the shadow node registry */ @@ -238,12 +233,10 @@ public void setViewLocalData(int tag, Object data) { dispatchViewUpdatesIfNeeded(); } - public void profileNextBatch() { - mOperationsQueue.profileNextBatch(); - } + public void profileNextBatch() {} public Map getProfiledBatchPerfCounters() { - return mOperationsQueue.getProfiledBatchPerfCounters(); + return new HashMap<>(); } /** Invoked by React to create a new node with a given tag, class name and properties. */ @@ -274,12 +267,7 @@ public void createView(int tag, String className, int rootViewTag, ReadableMap p } protected void handleCreateView( - ReactShadowNode cssNode, int rootViewTag, @Nullable ReactStylesDiffMap styles) { - if (!cssNode.isVirtual()) { - mOperationsQueue.enqueueCreateView( - cssNode.getThemedContext(), cssNode.getReactTag(), cssNode.getViewClass(), styles); - } - } + ReactShadowNode cssNode, int rootViewTag, @Nullable ReactStylesDiffMap styles) {} /** Invoked by React to create a new node with a given tag has its properties changed. */ public void updateView(int tag, String className, ReadableMap props) { @@ -311,15 +299,10 @@ public void updateView(int tag, String className, ReadableMap props) { */ public void synchronouslyUpdateViewOnUIThread(int tag, ReactStylesDiffMap props) { UiThreadUtil.assertOnUiThread(); - mOperationsQueue.getNativeViewHierarchyManager().updateProperties(tag, props); } protected void handleUpdateView( - ReactShadowNode cssNode, String className, ReactStylesDiffMap styles) { - if (!cssNode.isVirtual()) { - mOperationsQueue.enqueueUpdateProperties(cssNode.getReactTag(), className, styles); - } - } + ReactShadowNode cssNode, String className, ReactStylesDiffMap styles) {} /** * Invoked when there is a mutation in a node tree. @@ -434,9 +417,6 @@ public void manageChildren( cssNodeToManage.addChildAt(cssNodeToAdd, viewAtIndex.mIndex); } - mOperationsQueue.enqueueManageChildren( - cssNodeToManage.getReactTag(), indicesToRemove, viewsToAdd, tagsToDelete); - for (int i = 0; i < tagsToDelete.length; i++) { removeShadowNode(mShadowNodeRegistry.getNode(tagsToDelete[i])); } @@ -470,8 +450,6 @@ public void setChildren(int viewTag, ReadableArray childrenTags) { } cssNodeToManage.addChildAt(cssNodeToAdd, i); } - - mOperationsQueue.enqueueSetChildren(cssNodeToManage.getReactTag(), childrenTags); } } @@ -522,9 +500,7 @@ public void replaceExistingNonRootView(int oldTag, int newTag) { * @param callback will be called if with the identified child view react ID, and measurement * info. If no view was found, callback will be invoked with no data. */ - public void findSubviewIn(int reactTag, float targetX, float targetY, Callback callback) { - mOperationsQueue.enqueueFindTargetForTouch(reactTag, targetX, targetY, callback); - } + public void findSubviewIn(int reactTag, float targetX, float targetY, Callback callback) {} /** * Check if the first shadow node is the descendant of the second shadow node @@ -551,12 +527,6 @@ public void measure(int reactTag, Callback callback) { if (!mViewOperationsEnabled) { return; } - - // This method is called by the implementation of JS touchable interface (see Touchable.js for - // more details) at the moment of touch activation. That is after user starts the gesture from - // a touchable view with a given reactTag, or when user drag finger back into the press - // activation area of a touchable view that have been activated before. - mOperationsQueue.enqueueMeasure(reactTag, callback); } /** @@ -568,8 +538,6 @@ public void measureInWindow(int reactTag, Callback callback) { if (!mViewOperationsEnabled) { return; } - - mOperationsQueue.enqueueMeasureInWindow(reactTag, callback); } /** @@ -622,24 +590,15 @@ public void dispatchViewUpdates(int batchId) { SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT, "UIImplementation.dispatchViewUpdates") .arg("batchId", batchId) .flush(); - final long commitStartTime = SystemClock.uptimeMillis(); try { updateViewHierarchy(); - mOperationsQueue.dispatchViewUpdates(batchId, commitStartTime, mLastCalculateLayoutTime); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT); } } private void dispatchViewUpdatesIfNeeded() { - // If we are in the middle of a batch update, any additional changes - // will automatically be dispatched at the end of the batch. - // If we are not, we have to initiate new batch update. - // As all batches are executed as a single runnable on the event queue - // this should always be empty, but that calling architecture is an implementation detail. - if (mOperationsQueue.isEmpty()) { - dispatchViewUpdates(-1); // "-1" means "no associated batch id" - } + dispatchViewUpdates(-1); } protected void updateViewHierarchy() { @@ -684,8 +643,8 @@ protected void updateViewHierarchy() { Systrace.endSection(Systrace.TRACE_TAG_REACT); } - if (mLayoutUpdateListener != null) { - mOperationsQueue.enqueueLayoutUpdateFinished(cssRoot, mLayoutUpdateListener); + if (mLayoutUpdateListener != null && cssRoot != null) { + mLayoutUpdateListener.onLayoutUpdated(cssRoot); } } } @@ -705,9 +664,7 @@ protected void updateViewHierarchy() { * * @param enabled whether layout animation is enabled or not */ - public void setLayoutAnimationEnabledExperimental(boolean enabled) { - mOperationsQueue.enqueueSetLayoutAnimationEnabled(enabled); - } + public void setLayoutAnimationEnabledExperimental(boolean enabled) {} /** * Configure an animation to be used for the native layout changes, and native views creation. The @@ -721,26 +678,17 @@ public void setLayoutAnimationEnabledExperimental(boolean enabled) { * interrupted. In this case, callback parameter will be false. * @param error will be called if there was an error processing the animation */ - public void configureNextLayoutAnimation(ReadableMap config, Callback success) { - mOperationsQueue.enqueueConfigureLayoutAnimation(config, success); - } + public void configureNextLayoutAnimation(ReadableMap config, Callback success) {} public void setJSResponder(int reactTag, boolean blockNativeResponder) { ReactShadowNode node = mShadowNodeRegistry.getNode(reactTag); if (node == null) { - // TODO: this should only happen when using Fabric renderer. This is a temporary approach - // and it will be refactored when fabric supports JS Responder. return; } - - // While loop removed due to NativeKind removal - mOperationsQueue.enqueueSetJSResponder(node.getReactTag(), reactTag, blockNativeResponder); } - public void clearJSResponder() { - mOperationsQueue.enqueueClearJSResponder(); - } + public void clearJSResponder() {} @Deprecated public void dispatchViewManagerCommand( @@ -750,8 +698,6 @@ public void dispatchViewManagerCommand( if (!viewExists) { return; } - - mOperationsQueue.enqueueDispatchCommand(reactTag, commandId, commandArgs); } public void dispatchViewManagerCommand( @@ -761,21 +707,13 @@ public void dispatchViewManagerCommand( if (!viewExists) { return; } - - mOperationsQueue.enqueueDispatchCommand(reactTag, commandId, commandArgs); } - public void sendAccessibilityEvent(int tag, int eventType) { - mOperationsQueue.enqueueSendAccessibilityEvent(tag, eventType); - } + public void sendAccessibilityEvent(int tag, int eventType) {} - public void onHostResume() { - mOperationsQueue.resumeFrameCallback(); - } + public void onHostResume() {} - public void onHostPause() { - mOperationsQueue.pauseFrameCallback(); - } + public void onHostPause() {} public void onHostDestroy() {} @@ -918,7 +856,6 @@ protected void calculateRootLayout(ReactShadowNode cssRoot) { SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT, "cssRoot.calculateLayout") .arg("rootTag", cssRoot.getReactTag()) .flush(); - long startTime = SystemClock.uptimeMillis(); try { int widthSpec = cssRoot.getWidthMeasureSpec(); int heightSpec = cssRoot.getHeightMeasureSpec(); @@ -931,7 +868,6 @@ protected void calculateRootLayout(ReactShadowNode cssRoot) { : MeasureSpec.getSize(heightSpec)); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT); - mLastCalculateLayoutTime = SystemClock.uptimeMillis() - startTime; } } @@ -966,13 +902,9 @@ protected void applyUpdatesRecursive( cssNode.markUpdateSeen(); } - public void addUIBlock(UIBlock block) { - mOperationsQueue.enqueueUIBlock(block); - } + public void addUIBlock(UIBlock block) {} - public void prependUIBlock(UIBlock block) { - mOperationsQueue.prependUIBlock(block); - } + public void prependUIBlock(UIBlock block) {} public int resolveRootTagFromReactTag(int reactTag) { if (mShadowNodeRegistry.isRootNode(reactTag)) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index f0ed262acc0a..b38c683ee38f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -778,12 +778,9 @@ public void onLowMemory() {} } @Override - public View resolveView(int tag) { + public @Nullable View resolveView(int tag) { UiThreadUtil.assertOnUiThread(); - return mUIImplementation - .getUIViewOperationQueue() - .getNativeViewHierarchyManager() - .resolveView(tag); + return null; } @Override diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java index 8d46ee3eb49e..2cb81a8d1cd0 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -7,33 +7,16 @@ package com.facebook.react.uimanager; -import android.os.SystemClock; import android.view.View; -import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; -import androidx.annotation.UiThread; -import com.facebook.common.logging.FLog; import com.facebook.react.bridge.Callback; -import com.facebook.react.bridge.GuardedRunnable; import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.bridge.ReactNoCrashSoftException; -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.SoftAssertions; -import com.facebook.react.bridge.UiThreadUtil; -import com.facebook.react.common.ReactConstants; import com.facebook.react.common.annotations.internal.LegacyArchitecture; import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel; import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger; -import com.facebook.react.modules.core.ReactChoreographer; -import com.facebook.systrace.Systrace; -import com.facebook.systrace.SystraceMessage; import com.facebook.yoga.YogaDirection; -import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -46,6 +29,8 @@ * *

TODO(7135923): Pooling of operation objects TODO(5694019): Consider a better data structure * for operations queue to save on allocations + * + * @deprecated This class is stubbed out and will be removed in a future release. */ @LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR) @Deprecated( @@ -58,9 +43,6 @@ public class UIViewOperationQueue { } public static final int DEFAULT_MIN_TIME_LEFT_IN_FRAME_FOR_NONBATCHED_OPERATION_MS = 8; - private static final String TAG = UIViewOperationQueue.class.getSimpleName(); - - private final int[] mMeasureBuffer = new int[4]; /** A mutation or animation operation on the view hierarchy. */ public interface UIOperation { @@ -68,649 +50,54 @@ public interface UIOperation { void execute(); } - /** A spec for an operation on the native View hierarchy. */ - private abstract class ViewOperation implements UIOperation { - - public int mTag; - - public ViewOperation(int tag) { - mTag = tag; - } - } - - private final class RemoveRootViewOperation extends ViewOperation { - - public RemoveRootViewOperation(int tag) { - super(tag); - } - - @Override - public void execute() { - mNativeViewHierarchyManager.removeRootView(mTag); - } - } - - private final class UpdatePropertiesOperation extends ViewOperation { - - private final ReactStylesDiffMap mProps; - - private UpdatePropertiesOperation(int tag, ReactStylesDiffMap props) { - super(tag); - mProps = props; - } - - @Override - public void execute() { - mNativeViewHierarchyManager.updateProperties(mTag, mProps); - } - } - - private final class UpdateInstanceHandleOperation extends ViewOperation { - - private final long mInstanceHandle; - - private UpdateInstanceHandleOperation(int tag, long instanceHandle) { - super(tag); - mInstanceHandle = instanceHandle; - } - - @Override - public void execute() { - mNativeViewHierarchyManager.updateInstanceHandle(mTag, mInstanceHandle); - } - } - - /** - * Operation for updating native view's position and size. The operation is not created directly - * by a {@link UIManagerModule} call from JS. Instead it gets inflated using computed position and - * size values by CSSNodeDEPRECATED hierarchy. - */ - private final class UpdateLayoutOperation extends ViewOperation { - - private final int mParentTag, mX, mY, mWidth, mHeight; - private final YogaDirection mLayoutDirection; - - public UpdateLayoutOperation( - int parentTag, - int tag, - int x, - int y, - int width, - int height, - YogaDirection layoutDirection) { - super(tag); - mParentTag = parentTag; - mX = x; - mY = y; - mWidth = width; - mHeight = height; - mLayoutDirection = layoutDirection; - Systrace.startAsyncFlow(Systrace.TRACE_TAG_REACT, "updateLayout", mTag); - } - - @Override - public void execute() { - Systrace.endAsyncFlow(Systrace.TRACE_TAG_REACT, "updateLayout", mTag); - mNativeViewHierarchyManager.updateLayout( - mParentTag, mTag, mX, mY, mWidth, mHeight, mLayoutDirection); - } - } - - private final class CreateViewOperation extends ViewOperation { - - private final ThemedReactContext mThemedContext; - private final String mClassName; - private final @Nullable ReactStylesDiffMap mInitialProps; - - public CreateViewOperation( - ThemedReactContext themedContext, - int tag, - String className, - @Nullable ReactStylesDiffMap initialProps) { - super(tag); - mThemedContext = themedContext; - mClassName = className; - mInitialProps = initialProps; - Systrace.startAsyncFlow(Systrace.TRACE_TAG_REACT, "createView", mTag); - } - - @Override - public void execute() { - Systrace.endAsyncFlow(Systrace.TRACE_TAG_REACT, "createView", mTag); - mNativeViewHierarchyManager.createView(mThemedContext, mTag, mClassName, mInitialProps); - } - } - - private final class ManageChildrenOperation extends ViewOperation { - - private final @Nullable int[] mIndicesToRemove; - private final @Nullable ViewAtIndex[] mViewsToAdd; - private final @Nullable int[] mTagsToDelete; - - public ManageChildrenOperation( - int tag, - @Nullable int[] indicesToRemove, - @Nullable ViewAtIndex[] viewsToAdd, - @Nullable int[] tagsToDelete) { - super(tag); - mIndicesToRemove = indicesToRemove; - mViewsToAdd = viewsToAdd; - mTagsToDelete = tagsToDelete; - } - - @Override - public void execute() { - mNativeViewHierarchyManager.manageChildren( - mTag, mIndicesToRemove, mViewsToAdd, mTagsToDelete); - } - } - - private final class SetChildrenOperation extends ViewOperation { - - private final ReadableArray mChildrenTags; - - public SetChildrenOperation(int tag, ReadableArray childrenTags) { - super(tag); - mChildrenTags = childrenTags; - } - - @Override - public void execute() { - mNativeViewHierarchyManager.setChildren(mTag, mChildrenTags); - } - } - - private final class UpdateViewExtraData extends ViewOperation { - - private final Object mExtraData; - - public UpdateViewExtraData(int tag, Object extraData) { - super(tag); - mExtraData = extraData; - } - - @Override - public void execute() { - mNativeViewHierarchyManager.updateViewExtraData(mTag, mExtraData); - } - } - - private final class ChangeJSResponderOperation extends ViewOperation { - - private final int mInitialTag; - private final boolean mBlockNativeResponder; - private final boolean mClearResponder; - - public ChangeJSResponderOperation( - int tag, int initialTag, boolean clearResponder, boolean blockNativeResponder) { - super(tag); - mInitialTag = initialTag; - mClearResponder = clearResponder; - mBlockNativeResponder = blockNativeResponder; - } - - @Override - public void execute() { - if (!mClearResponder) { - mNativeViewHierarchyManager.setJSResponder(mTag, mInitialTag, mBlockNativeResponder); - } else { - mNativeViewHierarchyManager.clearJSResponder(); - } - } - } - - /** - * This is a common interface for View Command operations. Once we delete the deprecated {@link - * DispatchCommandOperation}, we can delete this interface too. It provides a set of common - * operations to simplify generic operations on all types of ViewCommands. - */ - private interface DispatchCommandViewOperation { - - /** - * Like the execute function, but throws real exceptions instead of logging soft errors and - * returning silently. - */ - void executeWithExceptions(); - - /** Increment retry counter. */ - void incrementRetries(); - - /** Get retry counter. */ - int getRetries(); - } - - @Deprecated - private final class DispatchCommandOperation extends ViewOperation - implements DispatchCommandViewOperation { - - private final int mCommand; - private final @Nullable ReadableArray mArgs; - - private int numRetries = 0; - - public DispatchCommandOperation(int tag, int command, @Nullable ReadableArray args) { - super(tag); - mCommand = command; - mArgs = args; - } - - @Override - public void execute() { - try { - mNativeViewHierarchyManager.dispatchCommand(mTag, mCommand, mArgs); - } catch (Throwable e) { - ReactSoftExceptionLogger.logSoftException( - TAG, new RuntimeException("Error dispatching View Command", e)); - } - } - - @Override - public void executeWithExceptions() { - mNativeViewHierarchyManager.dispatchCommand(mTag, mCommand, mArgs); - } - - @Override - @UiThread - public void incrementRetries() { - numRetries++; - } - - @Override - @UiThread - public int getRetries() { - return numRetries; - } - } - - private final class DispatchStringCommandOperation extends ViewOperation - implements DispatchCommandViewOperation { - - private final String mCommand; - private final @Nullable ReadableArray mArgs; - private int numRetries = 0; - - public DispatchStringCommandOperation(int tag, String command, @Nullable ReadableArray args) { - super(tag); - mCommand = command; - mArgs = args; - } - - @Override - public void execute() { - try { - mNativeViewHierarchyManager.dispatchCommand(mTag, mCommand, mArgs); - } catch (Throwable e) { - ReactSoftExceptionLogger.logSoftException( - TAG, new RuntimeException("Error dispatching View Command", e)); - } - } - - @Override - @UiThread - public void executeWithExceptions() { - mNativeViewHierarchyManager.dispatchCommand(mTag, mCommand, mArgs); - } - - @Override - @UiThread - public void incrementRetries() { - numRetries++; - } - - @Override - public int getRetries() { - return numRetries; - } - } - - /** A spec for animation operations (add/remove) */ - private abstract static class AnimationOperation implements UIViewOperationQueue.UIOperation { - - protected final int mAnimationID; - - public AnimationOperation(int animationID) { - mAnimationID = animationID; - } - } - - private class SetLayoutAnimationEnabledOperation implements UIOperation { - private final boolean mEnabled; - - private SetLayoutAnimationEnabledOperation(final boolean enabled) { - mEnabled = enabled; - } - - @Override - public void execute() { - mNativeViewHierarchyManager.setLayoutAnimationEnabled(mEnabled); - } - } - - private class ConfigureLayoutAnimationOperation implements UIOperation { - private final ReadableMap mConfig; - private final Callback mAnimationComplete; - - private ConfigureLayoutAnimationOperation( - final ReadableMap config, final Callback animationComplete) { - mConfig = config; - mAnimationComplete = animationComplete; - } - - @Override - public void execute() { - mNativeViewHierarchyManager.configureLayoutAnimation(mConfig, mAnimationComplete); - } - } - - private final class MeasureOperation implements UIOperation { - - private final int mReactTag; - private final Callback mCallback; - - private MeasureOperation(final int reactTag, final Callback callback) { - super(); - mReactTag = reactTag; - mCallback = callback; - } - - @Override - public void execute() { - try { - mNativeViewHierarchyManager.measure(mReactTag, mMeasureBuffer); - } catch (IllegalViewOperationException e) { - // Invoke with no args to signal failure and to allow JS to clean up the callback - // handle. - mCallback.invoke(); - return; - } - - float x = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]); - float y = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]); - float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]); - float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]); - mCallback.invoke(0, 0, width, height, x, y); - } - } - - private final class MeasureInWindowOperation implements UIOperation { - - private final int mReactTag; - private final Callback mCallback; - - private MeasureInWindowOperation(final int reactTag, final Callback callback) { - super(); - mReactTag = reactTag; - mCallback = callback; - } - - @Override - public void execute() { - try { - mNativeViewHierarchyManager.measureInWindow(mReactTag, mMeasureBuffer); - } catch (IllegalViewOperationException e) { - // Invoke with no args to signal failure and to allow JS to clean up the callback - // handle. - mCallback.invoke(); - return; - } - - float x = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]); - float y = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]); - float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]); - float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]); - mCallback.invoke(x, y, width, height); - } - } - - private final class FindTargetForTouchOperation implements UIOperation { - - private final int mReactTag; - private final float mTargetX; - private final float mTargetY; - private final Callback mCallback; - - private FindTargetForTouchOperation( - final int reactTag, final float targetX, final float targetY, final Callback callback) { - super(); - mReactTag = reactTag; - mTargetX = targetX; - mTargetY = targetY; - mCallback = callback; - } - - @Override - public void execute() { - try { - mNativeViewHierarchyManager.measure(mReactTag, mMeasureBuffer); - } catch (IllegalViewOperationException e) { - mCallback.invoke(); - return; - } - - // Because React coordinates are relative to root container, and measure() operates - // on screen coordinates, we need to offset values using root container location. - final float containerX = (float) mMeasureBuffer[0]; - final float containerY = (float) mMeasureBuffer[1]; - - final int touchTargetReactTag = - mNativeViewHierarchyManager.findTargetTagForTouch(mReactTag, mTargetX, mTargetY); - - try { - mNativeViewHierarchyManager.measure(touchTargetReactTag, mMeasureBuffer); - } catch (IllegalViewOperationException e) { - mCallback.invoke(); - return; - } - - float x = PixelUtil.toDIPFromPixel(mMeasureBuffer[0] - containerX); - float y = PixelUtil.toDIPFromPixel(mMeasureBuffer[1] - containerY); - float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]); - float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]); - mCallback.invoke(touchTargetReactTag, x, y, width, height); - } - } - - private final class LayoutUpdateFinishedOperation implements UIOperation { - - private final ReactShadowNode mNode; - private final UIImplementation.LayoutUpdateListener mListener; - - private LayoutUpdateFinishedOperation( - ReactShadowNode node, UIImplementation.LayoutUpdateListener listener) { - mNode = node; - mListener = listener; - } - - @Override - public void execute() { - mListener.onLayoutUpdated(mNode); - } - } - - private class UIBlockOperation implements UIOperation { - private final UIBlock mBlock; - - public UIBlockOperation(UIBlock block) { - mBlock = block; - } - - @Override - public void execute() { - mBlock.execute(mNativeViewHierarchyManager); - } - } - - private final class SendAccessibilityEvent extends ViewOperation { - - private final int mEventType; - - private SendAccessibilityEvent(int tag, int eventType) { - super(tag); - mEventType = eventType; - } - - @Override - public void execute() { - try { - mNativeViewHierarchyManager.sendAccessibilityEvent(mTag, mEventType); - } catch (RetryableMountingLayerException e) { - // Accessibility events are similar to commands in that they're imperative - // calls from JS, disconnected from the commit lifecycle, and therefore - // inherently unpredictable and dangerous. If we encounter a "retryable" - // error, that is, a known category of errors that this is likely to hit - // due to race conditions (like the view disappearing after the event is - // queued and before it executes), we log a soft exception and continue along. - // Other categories of errors will still cause a hard crash. - ReactSoftExceptionLogger.logSoftException(TAG, e); - } - } - } - - private final NativeViewHierarchyManager mNativeViewHierarchyManager; - private final Object mDispatchRunnablesLock = new Object(); - private final Object mNonBatchedOperationsLock = new Object(); - private final DispatchUIFrameCallback mDispatchUIFrameCallback; - private final ReactApplicationContext mReactApplicationContext; - - private ArrayList mViewCommandOperations = new ArrayList<>(); - - // Only called from the UIManager queue? - private ArrayList mOperations = new ArrayList<>(); - - @GuardedBy("mDispatchRunnablesLock") - private ArrayList mDispatchUIRunnables = new ArrayList<>(); - - @GuardedBy("mNonBatchedOperationsLock") - private ArrayDeque mNonBatchedOperations = new ArrayDeque<>(); - - private boolean mIsDispatchUIFrameCallbackEnqueued = false; - private boolean mIsInIllegalUIState = false; - private boolean mIsProfilingNextBatch = false; - private long mNonBatchedExecutionTotalTime; - private long mProfiledBatchCommitStartTime; - private long mProfiledBatchCommitEndTime; - private long mProfiledBatchLayoutTime; - private long mProfiledBatchDispatchViewUpdatesTime; - private long mProfiledBatchRunStartTime; - private long mProfiledBatchRunEndTime; - private long mProfiledBatchBatchedExecutionTime; - private long mProfiledBatchNonBatchedExecutionTime; - private long mThreadCpuTime; - private long mCreateViewCount; - private long mUpdatePropertiesOperationCount; - public UIViewOperationQueue( ReactApplicationContext reactContext, NativeViewHierarchyManager nativeViewHierarchyManager, - int minTimeLeftInFrameForNonBatchedOperationMs) { - mNativeViewHierarchyManager = nativeViewHierarchyManager; - mDispatchUIFrameCallback = - new DispatchUIFrameCallback( - reactContext, - minTimeLeftInFrameForNonBatchedOperationMs == -1 - ? DEFAULT_MIN_TIME_LEFT_IN_FRAME_FOR_NONBATCHED_OPERATION_MS - : minTimeLeftInFrameForNonBatchedOperationMs); - mReactApplicationContext = reactContext; - } + int minTimeLeftInFrameForNonBatchedOperationMs) {} - /*package*/ NativeViewHierarchyManager getNativeViewHierarchyManager() { - return mNativeViewHierarchyManager; + /*package*/ @Nullable + NativeViewHierarchyManager getNativeViewHierarchyManager() { + return null; } - public void profileNextBatch() { - mIsProfilingNextBatch = true; - mProfiledBatchCommitStartTime = 0; - mCreateViewCount = 0; - mUpdatePropertiesOperationCount = 0; - } + public void profileNextBatch() {} public Map getProfiledBatchPerfCounters() { - Map perfMap = new HashMap<>(); - perfMap.put("CommitStartTime", mProfiledBatchCommitStartTime); - perfMap.put("CommitEndTime", mProfiledBatchCommitEndTime); - perfMap.put("LayoutTime", mProfiledBatchLayoutTime); - perfMap.put("DispatchViewUpdatesTime", mProfiledBatchDispatchViewUpdatesTime); - perfMap.put("RunStartTime", mProfiledBatchRunStartTime); - perfMap.put("RunEndTime", mProfiledBatchRunEndTime); - perfMap.put("BatchedExecutionTime", mProfiledBatchBatchedExecutionTime); - perfMap.put("NonBatchedExecutionTime", mProfiledBatchNonBatchedExecutionTime); - perfMap.put("NativeModulesThreadCpuTime", mThreadCpuTime); - perfMap.put("CreateViewCount", mCreateViewCount); - perfMap.put("UpdatePropsCount", mUpdatePropertiesOperationCount); - return perfMap; + return new HashMap<>(); } public boolean isEmpty() { - return mOperations.isEmpty() && mViewCommandOperations.isEmpty(); + return true; } - public void addRootView(final int tag, final View rootView) { - mNativeViewHierarchyManager.addRootView(tag, rootView); - } + public void addRootView(final int tag, final View rootView) {} - /** - * Enqueues a UIOperation to be executed in UI thread. This method should only be used by a - * subclass to support UIOperations not provided by UIViewOperationQueue. - */ - protected void enqueueUIOperation(UIOperation operation) { - SoftAssertions.assertNotNull(operation); - mOperations.add(operation); - } + protected void enqueueUIOperation(UIOperation operation) {} - public void enqueueRemoveRootView(int rootViewTag) { - mOperations.add(new RemoveRootViewOperation(rootViewTag)); - } + public void enqueueRemoveRootView(int rootViewTag) {} - public void enqueueSetJSResponder(int tag, int initialTag, boolean blockNativeResponder) { - mOperations.add( - new ChangeJSResponderOperation( - tag, initialTag, false /*clearResponder*/, blockNativeResponder)); - } + public void enqueueSetJSResponder(int tag, int initialTag, boolean blockNativeResponder) {} - public void enqueueClearJSResponder() { - // Tag is 0 because JSResponderHandler doesn't need one in order to clear the responder. - mOperations.add(new ChangeJSResponderOperation(0, 0, true /*clearResponder*/, false)); - } + public void enqueueClearJSResponder() {} @Deprecated public void enqueueDispatchCommand( - int reactTag, int commandId, @Nullable ReadableArray commandArgs) { - final DispatchCommandOperation command = - new DispatchCommandOperation(reactTag, commandId, commandArgs); - mViewCommandOperations.add(command); - } + int reactTag, int commandId, @Nullable ReadableArray commandArgs) {} public void enqueueDispatchCommand( - int reactTag, String commandId, @Nullable ReadableArray commandArgs) { - final DispatchStringCommandOperation command = - new DispatchStringCommandOperation(reactTag, commandId, commandArgs); - mViewCommandOperations.add(command); - } + int reactTag, String commandId, @Nullable ReadableArray commandArgs) {} - public void enqueueUpdateExtraData(int reactTag, Object extraData) { - mOperations.add(new UpdateViewExtraData(reactTag, extraData)); - } + public void enqueueUpdateExtraData(int reactTag, Object extraData) {} public void enqueueCreateView( ThemedReactContext themedContext, int viewReactTag, String viewClassName, - @Nullable ReactStylesDiffMap initialProps) { - synchronized (mNonBatchedOperationsLock) { - mCreateViewCount++; - mNonBatchedOperations.addLast( - new CreateViewOperation(themedContext, viewReactTag, viewClassName, initialProps)); - } - } + @Nullable ReactStylesDiffMap initialProps) {} - public void enqueueUpdateInstanceHandle(int reactTag, long instanceHandle) { - mOperations.add(new UpdateInstanceHandleOperation(reactTag, instanceHandle)); - } + public void enqueueUpdateInstanceHandle(int reactTag, long instanceHandle) {} - public void enqueueUpdateProperties(int reactTag, String className, ReactStylesDiffMap props) { - mUpdatePropertiesOperationCount++; - mOperations.add(new UpdatePropertiesOperation(reactTag, props)); - } + public void enqueueUpdateProperties(int reactTag, String className, ReactStylesDiffMap props) {} /** * @deprecated Use {@link #enqueueUpdateLayout(int, int, int, int, int, int, YogaDirection)} @@ -718,9 +105,7 @@ public void enqueueUpdateProperties(int reactTag, String className, ReactStylesD */ @Deprecated public void enqueueUpdateLayout( - int parentTag, int reactTag, int x, int y, int width, int height) { - enqueueUpdateLayout(parentTag, reactTag, x, y, width, height, YogaDirection.INHERIT); - } + int parentTag, int reactTag, int x, int y, int width, int height) {} public void enqueueUpdateLayout( int parentTag, @@ -729,348 +114,41 @@ public void enqueueUpdateLayout( int y, int width, int height, - YogaDirection layoutDirection) { - mOperations.add( - new UpdateLayoutOperation(parentTag, reactTag, x, y, width, height, layoutDirection)); - } + YogaDirection layoutDirection) {} public void enqueueManageChildren( int reactTag, @Nullable int[] indicesToRemove, @Nullable ViewAtIndex[] viewsToAdd, - @Nullable int[] tagsToDelete) { - mOperations.add( - new ManageChildrenOperation(reactTag, indicesToRemove, viewsToAdd, tagsToDelete)); - } + @Nullable int[] tagsToDelete) {} - public void enqueueSetChildren(int reactTag, ReadableArray childrenTags) { - mOperations.add(new SetChildrenOperation(reactTag, childrenTags)); - } + public void enqueueSetChildren(int reactTag, ReadableArray childrenTags) {} - public void enqueueSetLayoutAnimationEnabled(final boolean enabled) { - mOperations.add(new SetLayoutAnimationEnabledOperation(enabled)); - } + public void enqueueSetLayoutAnimationEnabled(final boolean enabled) {} public void enqueueConfigureLayoutAnimation( - final ReadableMap config, final Callback onAnimationComplete) { - mOperations.add(new ConfigureLayoutAnimationOperation(config, onAnimationComplete)); - } + final ReadableMap config, final Callback onAnimationComplete) {} - public void enqueueMeasure(final int reactTag, final Callback callback) { - mOperations.add(new MeasureOperation(reactTag, callback)); - } + public void enqueueMeasure(final int reactTag, final Callback callback) {} - public void enqueueMeasureInWindow(final int reactTag, final Callback callback) { - mOperations.add(new MeasureInWindowOperation(reactTag, callback)); - } + public void enqueueMeasureInWindow(final int reactTag, final Callback callback) {} public void enqueueFindTargetForTouch( - final int reactTag, final float targetX, final float targetY, final Callback callback) { - mOperations.add(new FindTargetForTouchOperation(reactTag, targetX, targetY, callback)); - } + final int reactTag, final float targetX, final float targetY, final Callback callback) {} - public void enqueueSendAccessibilityEvent(int tag, int eventType) { - mOperations.add(new SendAccessibilityEvent(tag, eventType)); - } + public void enqueueSendAccessibilityEvent(int tag, int eventType) {} public void enqueueLayoutUpdateFinished( - ReactShadowNode node, UIImplementation.LayoutUpdateListener listener) { - mOperations.add(new LayoutUpdateFinishedOperation(node, listener)); - } + ReactShadowNode node, UIImplementation.LayoutUpdateListener listener) {} - public void enqueueUIBlock(UIBlock block) { - mOperations.add(new UIBlockOperation(block)); - } + public void enqueueUIBlock(UIBlock block) {} - public void prependUIBlock(UIBlock block) { - mOperations.add(0, new UIBlockOperation(block)); - } + public void prependUIBlock(UIBlock block) {} public void dispatchViewUpdates( - final int batchId, final long commitStartTime, final long layoutTime) { - SystraceMessage.beginSection( - Systrace.TRACE_TAG_REACT, "UIViewOperationQueue.dispatchViewUpdates") - .arg("batchId", batchId) - .flush(); - try { - final long dispatchViewUpdatesTime = SystemClock.uptimeMillis(); - final long nativeModulesThreadCpuTime = SystemClock.currentThreadTimeMillis(); - - // Store the current operation queues to dispatch and create new empty ones to continue - // receiving new operations - final ArrayList viewCommandOperations; - if (!mViewCommandOperations.isEmpty()) { - viewCommandOperations = mViewCommandOperations; - mViewCommandOperations = new ArrayList<>(); - } else { - viewCommandOperations = null; - } - - final ArrayList batchedOperations; - if (!mOperations.isEmpty()) { - batchedOperations = mOperations; - mOperations = new ArrayList<>(); - } else { - batchedOperations = null; - } - - final ArrayDeque nonBatchedOperations; - synchronized (mNonBatchedOperationsLock) { - if (!mNonBatchedOperations.isEmpty()) { - nonBatchedOperations = mNonBatchedOperations; - mNonBatchedOperations = new ArrayDeque<>(); - } else { - nonBatchedOperations = null; - } - } - - Runnable runOperations = - new Runnable() { - @Override - public void run() { - SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT, "DispatchUI") - .arg("BatchId", batchId) - .flush(); - try { - long runStartTime = SystemClock.uptimeMillis(); - - // All ViewCommands should be executed first as a perf optimization. - // This entire block is only executed if there's at least one ViewCommand queued. - if (viewCommandOperations != null) { - for (DispatchCommandViewOperation op : viewCommandOperations) { - try { - op.executeWithExceptions(); - } catch (RetryableMountingLayerException e) { - // Catch errors in DispatchCommands. We allow all commands to be retried - // exactly once, after the current batch of other mountitems. If the second - // attempt fails, then we log a soft error. This will still crash only in - // debug. We do this because it is a ~relatively common pattern to dispatch a - // command during render, for example, to scroll to the bottom of a ScrollView - // in render. This dispatches the command before that View is even mounted. By - // retrying once, we can still dispatch the vast majority of commands faster, - // avoid errors, and still operate correctly for most commands even when - // they're executed too soon. - if (op.getRetries() == 0) { - op.incrementRetries(); - mViewCommandOperations.add(op); - } else { - // Retryable exceptions should be logged, but never crash in debug. - ReactSoftExceptionLogger.logSoftException( - TAG, new ReactNoCrashSoftException(e)); - } - } catch (Throwable e) { - // Non-retryable exceptions should be logged in prod, and crash in Debug. - ReactSoftExceptionLogger.logSoftException(TAG, e); - } - } - } - - // All nonBatchedOperations should be executed before regular operations as - // regular operations may depend on them - if (nonBatchedOperations != null) { - for (UIOperation op : nonBatchedOperations) { - op.execute(); - } - } - - if (batchedOperations != null) { - for (UIOperation op : batchedOperations) { - op.execute(); - } - } - - if (mIsProfilingNextBatch && mProfiledBatchCommitStartTime == 0) { - mProfiledBatchCommitStartTime = commitStartTime; - mProfiledBatchCommitEndTime = SystemClock.uptimeMillis(); - mProfiledBatchLayoutTime = layoutTime; - mProfiledBatchDispatchViewUpdatesTime = dispatchViewUpdatesTime; - mProfiledBatchRunStartTime = runStartTime; - mProfiledBatchRunEndTime = mProfiledBatchCommitEndTime; - mThreadCpuTime = nativeModulesThreadCpuTime; - - Systrace.beginAsyncSection( - Systrace.TRACE_TAG_REACT, - "delayBeforeDispatchViewUpdates", - 0, - mProfiledBatchCommitStartTime * 1000000); - Systrace.endAsyncSection( - Systrace.TRACE_TAG_REACT, - "delayBeforeDispatchViewUpdates", - 0, - mProfiledBatchDispatchViewUpdatesTime * 1000000); - Systrace.beginAsyncSection( - Systrace.TRACE_TAG_REACT, - "delayBeforeBatchRunStart", - 0, - mProfiledBatchDispatchViewUpdatesTime * 1000000); - Systrace.endAsyncSection( - Systrace.TRACE_TAG_REACT, - "delayBeforeBatchRunStart", - 0, - mProfiledBatchRunStartTime * 1000000); - } - - // Clear layout animation, as animation only apply to current UI operations batch. - mNativeViewHierarchyManager.clearLayoutAnimation(); - - } catch (Exception e) { - mIsInIllegalUIState = true; - throw e; - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT); - } - } - }; - - SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT, "acquiring mDispatchRunnablesLock") - .arg("batchId", batchId) - .flush(); - synchronized (mDispatchRunnablesLock) { - Systrace.endSection(Systrace.TRACE_TAG_REACT); - mDispatchUIRunnables.add(runOperations); - } + final int batchId, final long commitStartTime, final long layoutTime) {} - // In the case where the frame callback isn't enqueued, the UI isn't being displayed or is - // being - // destroyed. In this case it's no longer important to align to frames, but it is important to - // make - // sure any late-arriving UI commands are executed. - if (!mIsDispatchUIFrameCallbackEnqueued) { - UiThreadUtil.runOnUiThread( - new GuardedRunnable(mReactApplicationContext) { - @Override - public void runGuarded() { - flushPendingBatches(); - } - }); - } - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT); - } - } - - /* package */ void resumeFrameCallback() { - mIsDispatchUIFrameCallbackEnqueued = true; - ReactChoreographer.getInstance() - .postFrameCallback(ReactChoreographer.CallbackType.DISPATCH_UI, mDispatchUIFrameCallback); - } - - /* package */ void pauseFrameCallback() { - mIsDispatchUIFrameCallbackEnqueued = false; - ReactChoreographer.getInstance() - .removeFrameCallback(ReactChoreographer.CallbackType.DISPATCH_UI, mDispatchUIFrameCallback); - flushPendingBatches(); - } - - private void flushPendingBatches() { - if (mIsInIllegalUIState) { - FLog.w( - ReactConstants.TAG, - "Not flushing pending UI operations because of previously thrown Exception"); - return; - } - - final ArrayList runnables; - synchronized (mDispatchRunnablesLock) { - if (!mDispatchUIRunnables.isEmpty()) { - runnables = mDispatchUIRunnables; - mDispatchUIRunnables = new ArrayList<>(); - } else { - return; - } - } - - final long batchedExecutionStartTime = SystemClock.uptimeMillis(); - for (Runnable runnable : runnables) { - runnable.run(); - } - - if (mIsProfilingNextBatch) { - mProfiledBatchBatchedExecutionTime = SystemClock.uptimeMillis() - batchedExecutionStartTime; - mProfiledBatchNonBatchedExecutionTime = mNonBatchedExecutionTotalTime; - mIsProfilingNextBatch = false; - - Systrace.beginAsyncSection( - Systrace.TRACE_TAG_REACT, "batchedExecutionTime", 0, batchedExecutionStartTime * 1000000); - Systrace.endAsyncSection(Systrace.TRACE_TAG_REACT, "batchedExecutionTime", 0); - } - mNonBatchedExecutionTotalTime = 0; - } - - /** - * Choreographer FrameCallback responsible for actually dispatching view updates on the UI thread - * that were enqueued via {@link #dispatchViewUpdates(int)}. The reason we don't just enqueue - * directly to the UI thread from that method is to make sure our Runnables actually run before - * the next traversals happen: - * - *

ViewRootImpl#scheduleTraversals (which is called from invalidate, requestLayout, etc) calls - * Looper#postSyncBarrier which keeps any UI thread looper messages from being processed until - * that barrier is removed during the next traversal. That means, depending on when we get updates - * from JS and what else is happening on the UI thread, we can sometimes try to post this runnable - * after ViewRootImpl has posted a barrier. - * - *

Using a Choreographer callback (which runs immediately before traversals), we guarantee we - * run before the next traversal. - */ - private class DispatchUIFrameCallback extends GuardedFrameCallback { - - private static final int FRAME_TIME_MS = 16; - private final int mMinTimeLeftInFrameForNonBatchedOperationMs; - - private DispatchUIFrameCallback( - ReactContext reactContext, int minTimeLeftInFrameForNonBatchedOperationMs) { - super(reactContext); - mMinTimeLeftInFrameForNonBatchedOperationMs = minTimeLeftInFrameForNonBatchedOperationMs; - } - - @Override - public void doFrameGuarded(long frameTimeNanos) { - if (mIsInIllegalUIState) { - FLog.w( - ReactConstants.TAG, - "Not flushing pending UI operations because of previously thrown Exception"); - return; - } - - Systrace.beginSection(Systrace.TRACE_TAG_REACT, "dispatchNonBatchedUIOperations"); - try { - dispatchPendingNonBatchedOperations(frameTimeNanos); - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT); - } + /* package */ void resumeFrameCallback() {} - flushPendingBatches(); - - ReactChoreographer.getInstance() - .postFrameCallback(ReactChoreographer.CallbackType.DISPATCH_UI, this); - } - - private void dispatchPendingNonBatchedOperations(long frameTimeNanos) { - while (true) { - long timeLeftInFrame = FRAME_TIME_MS - ((System.nanoTime() - frameTimeNanos) / 1000000); - if (timeLeftInFrame < mMinTimeLeftInFrameForNonBatchedOperationMs) { - break; - } - - UIOperation nextOperation; - synchronized (mNonBatchedOperationsLock) { - if (mNonBatchedOperations.isEmpty()) { - break; - } - - nextOperation = mNonBatchedOperations.pollFirst(); - } - - try { - long nonBatchedExecutionStartTime = SystemClock.uptimeMillis(); - nextOperation.execute(); - mNonBatchedExecutionTotalTime += - SystemClock.uptimeMillis() - nonBatchedExecutionStartTime; - } catch (Exception e) { - mIsInIllegalUIState = true; - throw e; - } - } - } - } + /* package */ void pauseFrameCallback() {} } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java index 5e3cf945fdac..80fba556da12 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java @@ -233,6 +233,10 @@ protected void onLayout( for (TextInlineViewPlaceholderSpan placeholder : placeholders) { View child = uiManager.resolveView(placeholder.getReactTag()); + if (child == null) { + continue; + } + int start = text.getSpanStart(placeholder); int line = layout.getLineForOffset(start); boolean isLineTruncated = layout.getEllipsisCount(line) > 0;