Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,13 @@ internal constructor(
}

val parentView = parentViewState.view
checkNotNull(parentView) { "Unable to find parentView for tag $parentTag" }
if (parentView == null) {
ReactSoftExceptionLogger.logSoftException(
ReactSoftExceptionLogger.Categories.SURFACE_MOUNTING_MANAGER_MISSING_VIEWSTATE,
ReactNoCrashSoftException("ViewState has no view for tag: [$parentTag] for removeViewAt"),
)
return
}
if (parentView !is ViewGroup) {
ReactSoftExceptionLogger.logSoftException(
TAG,
Expand Down Expand Up @@ -886,7 +892,14 @@ internal constructor(
return
}

val viewToUpdate = checkNotNull(viewState.view) { "Unable to find View for tag: $reactTag" }
val viewToUpdate = viewState.view
if (viewToUpdate == null) {
ReactSoftExceptionLogger.logSoftException(
ReactSoftExceptionLogger.Categories.SURFACE_MOUNTING_MANAGER_MISSING_VIEWSTATE,
ReactNoCrashSoftException("ViewState has no view for tag: [$reactTag] for updatePadding"),
)
return
}
val viewManager =
checkNotNull(viewState.viewManager) { "Unable to find ViewManager for view: $viewState" }

Expand Down Expand Up @@ -922,7 +935,15 @@ internal constructor(
}

val viewToUpdate = viewState.view
checkNotNull(viewToUpdate) { "Unable to find View for tag: $reactTag" }
if (viewToUpdate == null) {
ReactSoftExceptionLogger.logSoftException(
ReactSoftExceptionLogger.Categories.SURFACE_MOUNTING_MANAGER_MISSING_VIEWSTATE,
ReactNoCrashSoftException(
"ViewState has no view for tag: [$reactTag] for updateOverflowInset",
),
)
return
}

if (viewToUpdate is ReactOverflowViewWithInset) {
(viewToUpdate as ReactOverflowViewWithInset).setOverflowInset(
Expand Down Expand Up @@ -953,9 +974,16 @@ internal constructor(
val prevStateWrapper = viewState.stateWrapper
viewState.stateWrapper = stateWrapper

val viewManager =
checkNotNull(viewState.viewManager) { "Unable to find ViewManager for tag: $reactTag" }
val view = checkNotNull(viewState.view)
val viewManager = viewState.viewManager
val view = viewState.view
if (viewManager == null || view == null) {
ReactSoftExceptionLogger.logSoftException(
ReactSoftExceptionLogger.Categories.SURFACE_MOUNTING_MANAGER_MISSING_VIEWSTATE,
ReactNoCrashSoftException("ViewState has no view for tag: [$reactTag] for updateState"),
)
prevStateWrapper?.destroyState()
return
}

val extraData = viewManager.updateState(view, viewState.currentProps, stateWrapper)
if (extraData != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.facebook.react.fabric.mounting.MountingManager
import com.facebook.react.fabric.mounting.MountingManager.MountItemExecutor
import com.facebook.react.fabric.mounting.SurfaceMountingManager
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests
import com.facebook.react.uimanager.StateWrapper
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.ViewManager
import com.facebook.react.uimanager.ViewManagerRegistry
Expand All @@ -25,6 +26,8 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

Expand Down Expand Up @@ -195,4 +198,69 @@ class SurfaceMountingManagerTest {
smm.preallocateView("RCTView", 42, JavaOnlyMap.of(), null, true)
assertThat(smm.getViewExists(42)).isTrue()
}

/**
* A ViewState can exist without an Android View (createViewUnsafe only creates one when the node
* is layoutable). updateOverflowInset on such a tag used to throw and tear down the surface; it
* should log a soft exception and return, as the missing-viewState branch already does.
*/
@Test
fun updateOverflowInset_doesNotThrowWhenViewStateHasNoView() {
val smm = startSurface()

// isLayoutable = false: a ViewState is registered, but no Android View is created for it.
smm.preallocateView("RCTView", 42, JavaOnlyMap.of(), null, false)
assertThat(smm.getViewExists(42)).isTrue()

smm.updateOverflowInset(42, 0, 0, 0, 0)
}

/** Same as above, for updatePadding. */
@Test
fun updatePadding_doesNotThrowWhenViewStateHasNoView() {
val smm = startSurface()

// isLayoutable = false: a ViewState is registered, but no Android View is created for it.
smm.preallocateView("RCTView", 42, JavaOnlyMap.of(), null, false)
assertThat(smm.getViewExists(42)).isTrue()

smm.updatePadding(42, 0, 0, 0, 0)
}

/** Same as above, for removeViewAt when the parent's ViewState has no View. */
@Test
fun removeViewAt_doesNotThrowWhenParentViewStateHasNoView() {
val smm = startSurface()

// isLayoutable = false: a ViewState is registered, but no Android View is created for it.
smm.preallocateView("RCTView", 42, JavaOnlyMap.of(), null, false)
assertThat(smm.getViewExists(42)).isTrue()

smm.removeViewAt(43, 42, 0)
}

/** Same as above, for updateState. */
@Test
fun updateState_doesNotThrowWhenViewStateHasNoView() {
val smm = startSurface()

// isLayoutable = false: a ViewState is registered, but no View or ViewManager is set on it.
smm.preallocateView("RCTView", 42, JavaOnlyMap.of(), null, false)
assertThat(smm.getViewExists(42)).isTrue()

smm.updateState(42, null)
}

/** The early return in updateState must still free the replaced state wrapper. */
@Test
fun updateState_destroysPreviousStateWhenViewStateHasNoView() {
val smm = startSurface()
smm.preallocateView("RCTView", 42, JavaOnlyMap.of(), null, false)

val first = mock<StateWrapper>()
smm.updateState(42, first)
smm.updateState(42, mock())

verify(first).destroyState()
}
}
Loading