Make missing parent view state in updateLayout a soft error (#49951)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49951

We previously fixed Differentiator generating an incorrect parentTag (https://github.com/facebook/react-native/pull/48055), but this can lead to crashes in Android UI due to reordering that happens in the Android mounting layer. While we have an experiment to disable this reordering (https://github.com/facebook/react-native/pull/46702) this currently has a negative performance impact which needs to be addressed.

As a mitigation, we can make the lookup of parentTag's ViewManager state nullable. We only require this to support `needsCustomLayoutForChildren`, which is not commonly used, and seems acceptable to drop in this scenario.

Changelog: [Android][Changed] Do not crash when parent view state can't be found

Reviewed By: NickGerleman

Differential Revision: D70966621

fbshipit-source-id: 33d0b6a90860788a4c9a8c6cea36c2c72c1392e1
This commit is contained in:
Pieter De Baets
2025-03-11 14:11:15 -07:00
committed by Facebook GitHub Bot
parent 1482dd9e99
commit ade41c851b
2 changed files with 47 additions and 22 deletions
@@ -833,9 +833,16 @@ public class SurfaceMountingManager {
parent.requestLayout();
}
ViewState parentViewState = getViewState(parentTag);
// TODO T212247085: Make this non-nullable again after rolling out
// disableMountItemReorderingAndroid
ViewState parentViewState = getNullableViewState(parentTag);
IViewGroupManager<?> parentViewManager = null;
if (parentViewState.mViewManager != null) {
if (parentViewState == null) {
ReactSoftExceptionLogger.logSoftException(
ReactSoftExceptionLogger.Categories.SURFACE_MOUNTING_MANAGER_MISSING_VIEWSTATE,
new ReactNoCrashSoftException(
"Unable to find viewState for tag: " + parentTag + " for updateLayout"));
} else if (parentViewState.mViewManager != null) {
parentViewManager = (IViewGroupManager) parentViewState.mViewManager;
}
if (parentViewManager == null || !parentViewManager.needsCustomLayoutForChildren()) {
@@ -22,6 +22,7 @@ import com.facebook.react.fabric.mounting.SurfaceMountingManager;
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags;
import com.facebook.react.uimanager.StateWrapper;
import com.facebook.systrace.Systrace;
import java.util.Locale;
/**
* This class represents a batch of {@link MountItem}s, represented directly as int buffers to
@@ -199,7 +200,7 @@ final class IntBufferBatchMountItem implements BatchMountItem {
public String toString() {
try {
StringBuilder s = new StringBuilder();
s.append(String.format("IntBufferBatchMountItem [surface:%d]:\n", mSurfaceId));
s.append(String.format(Locale.ROOT, "IntBufferBatchMountItem [surface:%d]:\n", mSurfaceId));
int i = 0, j = 0;
while (i < mIntBufferLen) {
int rawType = mIntBuffer[i++];
@@ -211,49 +212,65 @@ final class IntBufferBatchMountItem implements BatchMountItem {
j += 3;
s.append(
String.format(
Locale.ROOT,
"CREATE [%d] - layoutable:%d - %s\n",
mIntBuffer[i++], mIntBuffer[i++], componentName));
mIntBuffer[i++],
mIntBuffer[i++],
componentName));
} else if (type == INSTRUCTION_DELETE) {
s.append(String.format("DELETE [%d]\n", mIntBuffer[i++]));
s.append(String.format(Locale.ROOT, "DELETE [%d]\n", mIntBuffer[i++]));
} else if (type == INSTRUCTION_INSERT) {
s.append(
String.format(
"INSERT [%d]->[%d] @%d\n", mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++]));
Locale.ROOT,
"INSERT [%d]->[%d] @%d\n",
mIntBuffer[i++],
mIntBuffer[i++],
mIntBuffer[i++]));
} else if (type == INSTRUCTION_REMOVE) {
s.append(
String.format(
"REMOVE [%d]->[%d] @%d\n", mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++]));
Locale.ROOT,
"REMOVE [%d]->[%d] @%d\n",
mIntBuffer[i++],
mIntBuffer[i++],
mIntBuffer[i++]));
} else if (type == INSTRUCTION_UPDATE_PROPS) {
Object props = mObjBuffer[j++];
String propsString =
IS_DEVELOPMENT_ENVIRONMENT
? (props != null ? props.toString() : "<null>")
: "<hidden>";
s.append(String.format("UPDATE PROPS [%d]: %s\n", mIntBuffer[i++], propsString));
s.append(
String.format(
Locale.ROOT, "UPDATE PROPS [%d]: %s\n", mIntBuffer[i++], propsString));
} else if (type == INSTRUCTION_UPDATE_STATE) {
StateWrapper state = (StateWrapper) mObjBuffer[j++];
String stateString =
IS_DEVELOPMENT_ENVIRONMENT
? (state != null ? state.toString() : "<null>")
: "<hidden>";
s.append(String.format("UPDATE STATE [%d]: %s\n", mIntBuffer[i++], stateString));
} else if (type == INSTRUCTION_UPDATE_LAYOUT) {
int reactTag = mIntBuffer[i++];
int parentTag = mIntBuffer[i++];
int x = mIntBuffer[i++];
int y = mIntBuffer[i++];
int w = mIntBuffer[i++];
int h = mIntBuffer[i++];
int displayType = mIntBuffer[i++];
int layoutDirection = mIntBuffer[i++];
s.append(
String.format(
"UPDATE LAYOUT [%d]->[%d]: x:%d y:%d w:%d h:%d displayType:%d layoutDirection:"
+ " %d\n",
parentTag, reactTag, x, y, w, h, displayType, layoutDirection));
Locale.ROOT, "UPDATE STATE [%d]: %s\n", mIntBuffer[i++], stateString));
} else if (type == INSTRUCTION_UPDATE_LAYOUT) {
s.append(
String.format(
Locale.ROOT,
"UPDATE LAYOUT [%d]->[%d]: x:%d y:%d w:%d h:%d displayType:%d"
+ " layoutDirection:%d\n",
mIntBuffer[i++],
mIntBuffer[i++],
mIntBuffer[i++],
mIntBuffer[i++],
mIntBuffer[i++],
mIntBuffer[i++],
mIntBuffer[i++],
mIntBuffer[i++]));
} else if (type == INSTRUCTION_UPDATE_PADDING) {
s.append(
String.format(
Locale.ROOT,
"UPDATE PADDING [%d]: top:%d right:%d bottom:%d left:%d\n",
mIntBuffer[i++],
mIntBuffer[i++],
@@ -263,6 +280,7 @@ final class IntBufferBatchMountItem implements BatchMountItem {
} else if (type == INSTRUCTION_UPDATE_OVERFLOW_INSET) {
s.append(
String.format(
Locale.ROOT,
"UPDATE OVERFLOWINSET [%d]: left:%d top:%d right:%d bottom:%d\n",
mIntBuffer[i++],
mIntBuffer[i++],
@@ -271,7 +289,7 @@ final class IntBufferBatchMountItem implements BatchMountItem {
mIntBuffer[i++]));
} else if (type == INSTRUCTION_UPDATE_EVENT_EMITTER) {
j += 1;
s.append(String.format("UPDATE EVENTEMITTER [%d]\n", mIntBuffer[i++]));
s.append(String.format(Locale.ROOT, "UPDATE EVENTEMITTER [%d]\n", mIntBuffer[i++]));
} else {
FLog.e(TAG, "String so far: " + s.toString());
throw new IllegalArgumentException(