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..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 @@ -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); } @@ -389,32 +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) { - // 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()); - } - } } } @@ -623,13 +597,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..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; @@ -56,10 +56,8 @@ 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; protected @Nullable LayoutUpdateListener mLayoutUpdateListener; /** @@ -99,8 +97,6 @@ protected UIImplementation( mReactContext = reactContext; mViewManagers = viewManagers; mOperationsQueue = operationsQueue; - mNativeViewHierarchyOptimizer = - new NativeViewHierarchyOptimizer(mOperationsQueue, mShadowNodeRegistry); mEventDispatcher = eventDispatcher; } @@ -170,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); } /** @@ -188,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 */ @@ -241,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. */ @@ -277,11 +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()) { - mNativeViewHierarchyOptimizer.handleCreateView(cssNode, cssNode.getThemedContext(), 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) { @@ -313,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()) { - mNativeViewHierarchyOptimizer.handleUpdateView(cssNode, className, styles); - } - } + ReactShadowNode cssNode, String className, ReactStylesDiffMap styles) {} /** * Invoked when there is a mutation in a node tree. @@ -345,6 +326,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,9 +417,6 @@ public void manageChildren( cssNodeToManage.addChildAt(cssNodeToAdd, viewAtIndex.mIndex); } - mNativeViewHierarchyOptimizer.handleManageChildren( - cssNodeToManage, indicesToRemove, tagsToRemove, viewsToAdd, tagsToDelete); - for (int i = 0; i < tagsToDelete.length; i++) { removeShadowNode(mShadowNodeRegistry.getNode(tagsToDelete[i])); } @@ -455,6 +437,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)); @@ -464,8 +450,6 @@ public void setChildren(int viewTag, ReadableArray childrenTags) { } cssNodeToManage.addChildAt(cssNodeToAdd, i); } - - mNativeViewHierarchyOptimizer.handleSetChildren(cssNodeToManage, childrenTags); } } @@ -516,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 @@ -545,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); } /** @@ -562,8 +538,6 @@ public void measureInWindow(int reactTag, Callback callback) { if (!mViewOperationsEnabled) { return; } - - mOperationsQueue.enqueueMeasureInWindow(reactTag, callback); } /** @@ -616,25 +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(); - mNativeViewHierarchyOptimizer.onBatchComplete(); - 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() { @@ -679,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); } } } @@ -700,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 @@ -716,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( @@ -745,8 +698,6 @@ public void dispatchViewManagerCommand( if (!viewExists) { return; } - - mOperationsQueue.enqueueDispatchCommand(reactTag, commandId, commandArgs); } public void dispatchViewManagerCommand( @@ -756,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() {} @@ -785,7 +728,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,14 +849,13 @@ 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) { 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(); @@ -926,7 +868,6 @@ protected void calculateRootLayout(ReactShadowNode cssRoot) { : MeasureSpec.getSize(heightSpec)); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT); - mLastCalculateLayoutTime = SystemClock.uptimeMillis() - startTime; } } @@ -956,19 +897,14 @@ protected void applyUpdatesRecursive( } } - cssNode.dispatchUpdates(absoluteX, absoluteY, mOperationsQueue, mNativeViewHierarchyOptimizer); + cssNode.dispatchUpdates(absoluteX, absoluteY, mOperationsQueue); cssNode.markUpdateSeen(); - mNativeViewHierarchyOptimizer.onViewUpdatesCompleted(cssNode); } - 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; 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)