Skip to content
Open
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 @@ -626,13 +626,14 @@ internal constructor(
public fun storeSynchronousMountPropsOverride(reactTag: Int, props: ReadableMap): Unit {
if (ReactNativeFeatureFlags.overrideBySynchronousMountPropsAtMountingAndroid()) {
val propsMap = getMapFromPropsReadableMap(props)
var synchronousMountProps = tagToSynchronousMountProps[reactTag]
if (synchronousMountProps != null) {
synchronousMountProps.putAll(propsMap)
val synchronousMountProps = tagToSynchronousMountProps[reactTag] ?: mutableMapOf()
removeNullTransformAndOpacityPropsFromPropsReadableMap(props, synchronousMountProps)
synchronousMountProps.putAll(propsMap)
if (synchronousMountProps.isEmpty()) {
tagToSynchronousMountProps.remove(reactTag)
} else {
synchronousMountProps = propsMap
tagToSynchronousMountProps[reactTag] = synchronousMountProps
}
tagToSynchronousMountProps[reactTag] = synchronousMountProps
}
}

Expand Down Expand Up @@ -1332,7 +1333,10 @@ internal constructor(
for ((propKey, propValue) in patchMap) {
if (outputReadableMap.hasKey(propKey)) {
if (propKey == PROP_TRANSFORM) {
assert(outputReadableMap.getType(propKey) == ReadableType.Array && propValue is List<*>)
val outputType = outputReadableMap.getType(propKey)
assert(
(outputType == ReadableType.Array || outputType == ReadableType.Null) &&
propValue is List<*>)
Comment on lines +1336 to +1339
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

val array = WritableNativeArray()
for (item in propValue as List<*>) {
if (item is Map<*, *>) {
Expand All @@ -1350,7 +1354,10 @@ internal constructor(
}
outputReadableMap.putArray(propKey, array)
} else if (propKey == PROP_OPACITY) {
assert(outputReadableMap.getType(propKey) == ReadableType.Number && propValue is Number)
val outputType = outputReadableMap.getType(propKey)
assert(
(outputType == ReadableType.Number || outputType == ReadableType.Null) &&
propValue is Number)
outputReadableMap.putDouble(propKey, (propValue as Number).toDouble())
}
}
Expand Down Expand Up @@ -1387,6 +1394,22 @@ internal constructor(
return outputMap
}

private fun removeNullTransformAndOpacityPropsFromPropsReadableMap(
readableMap: ReadableMap,
outputMap: MutableMap<String, Any>,
) {
// Native Animated uses synchronous null updates to restore animated-managed props.
// Keep this scoped to props stored by this override path today.
if (readableMap.hasKey(PROP_TRANSFORM) &&
readableMap.getType(PROP_TRANSFORM) == ReadableType.Null) {
outputMap.remove(PROP_TRANSFORM)
}
if (readableMap.hasKey(PROP_OPACITY) &&
readableMap.getType(PROP_OPACITY) == ReadableType.Null) {
outputMap.remove(PROP_OPACITY)
}
}

// prevents unchecked conversion warn of the <ViewGroup> type
private fun getViewGroupManager(viewState: ViewState): IViewGroupManager<View> {
val viewManager =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@
package com.facebook.react.fabric

import com.facebook.react.ReactRootView
import com.facebook.react.bridge.JavaOnlyArray
import com.facebook.react.bridge.JavaOnlyMap
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReactTestHelper
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.ThemedReactContext
import com.facebook.react.uimanager.ViewManager
import com.facebook.react.uimanager.ViewManagerRegistry
import com.facebook.react.views.view.ReactViewGroup
import com.facebook.react.views.view.ReactViewManager
import com.facebook.testutils.shadows.ShadowNativeLoader
import com.facebook.testutils.shadows.ShadowNativeMap
Expand Down Expand Up @@ -67,8 +69,8 @@ class SurfaceMountingManagerSynchronousMountPropsTest {
themedReactContext = ThemedReactContext(reactContext, reactContext, null, -1)
mountingManager =
MountingManager(
ViewManagerRegistry(listOf<ViewManager<*, *>>(ReactViewManager())),
MountItemExecutor {},
ViewManagerRegistry(listOf<ViewManager<*, *>>(TestReactViewManager())),
{},
)
}

Expand All @@ -83,6 +85,25 @@ class SurfaceMountingManagerSynchronousMountPropsTest {
smm.addViewAt(surfaceId, tag, 0)
}

private class TestReactViewManager : ReactViewManager() {
override fun setTransformProperty(
view: ReactViewGroup,
transforms: ReadableArray?,
transformOrigin: ReadableArray?,
) {
view.translationY = 0.0f
if (transforms == null) {
return
}
for (i in 0..<transforms.size()) {
val transform = transforms.getMap(i) ?: continue
if (transform.hasKey("translateY")) {
view.translationY = transform.getDouble("translateY").toFloat()
}
}
}
}

/** Stored synchronous opacity should override a stale Fabric mount update. */
@Test
fun storeSynchronousProps_overridesStaleOpacityInUpdateProps() {
Expand All @@ -100,7 +121,7 @@ class SurfaceMountingManagerSynchronousMountPropsTest {
assertThat(smm.getView(tag).alpha).isEqualTo(0.3f)
}

/** Multiple storeSynchronousMountPropsOverride calls should merge later values win. */
/** Multiple storeSynchronousMountPropsOverride calls should merge; later values win. */
@Test
fun storeSynchronousProps_mergesMultipleCalls() {
val smm = startSurface()
Expand Down Expand Up @@ -137,6 +158,80 @@ class SurfaceMountingManagerSynchronousMountPropsTest {
assertThat(smm.getView(tag).alpha).isEqualTo(0.2f)
}

/** A null Fabric prop update should not clear the stored synchronous opacity override. */
@Test
fun updateProps_withNullOpacity_keepsStoredSynchronousProp() {
val smm = startSurface()
val tag = 42
createAndAttachView(smm, tag)

smm.storeSynchronousMountPropsOverride(tag, JavaOnlyMap.of("opacity", 0.3))
smm.updatePropsSynchronously(tag, JavaOnlyMap.of("opacity", 0.3))
assertThat(smm.getView(tag).alpha).isEqualTo(0.3f)

smm.updateProps(tag, JavaOnlyMap.of("opacity", null))

assertThat(smm.getView(tag).alpha).isEqualTo(0.3f)
}

/** A synchronous null update should clear the stored synchronous opacity override. */
@Test
fun updatePropsSynchronously_withNullOpacity_removesStoredSynchronousProp() {
val smm = startSurface()
val tag = 42
createAndAttachView(smm, tag)

smm.storeSynchronousMountPropsOverride(tag, JavaOnlyMap.of("opacity", 0.3))
smm.updatePropsSynchronously(tag, JavaOnlyMap.of("opacity", 0.3))
assertThat(smm.getView(tag).alpha).isEqualTo(0.3f)

smm.storeSynchronousMountPropsOverride(tag, JavaOnlyMap.of("opacity", null))
smm.updatePropsSynchronously(tag, JavaOnlyMap.of("opacity", null))
assertThat(smm.getView(tag).alpha).isEqualTo(1.0f)

smm.updateProps(tag, JavaOnlyMap.of("opacity", null))

assertThat(smm.getView(tag).alpha).isEqualTo(1.0f)
}

/** A null Fabric prop update should not clear the stored synchronous transform override. */
@Test
fun updateProps_withNullTransform_keepsStoredSynchronousProp() {
val smm = startSurface()
val tag = 42
val transform = JavaOnlyArray.of(JavaOnlyMap.of("translateY", 40.0))
createAndAttachView(smm, tag)

smm.storeSynchronousMountPropsOverride(tag, JavaOnlyMap.of("transform", transform))
smm.updatePropsSynchronously(tag, JavaOnlyMap.of("transform", transform))
assertThat(smm.getView(tag).translationY).isEqualTo(40.0f)

smm.updateProps(tag, JavaOnlyMap.of("transform", null))

assertThat(smm.getView(tag).translationY).isEqualTo(40.0f)
}

/** A synchronous null update should clear the stored synchronous transform override. */
@Test
fun updatePropsSynchronously_withNullTransform_removesStoredSynchronousProp() {
val smm = startSurface()
val tag = 42
val transform = JavaOnlyArray.of(JavaOnlyMap.of("translateY", 40.0))
createAndAttachView(smm, tag)

smm.storeSynchronousMountPropsOverride(tag, JavaOnlyMap.of("transform", transform))
smm.updatePropsSynchronously(tag, JavaOnlyMap.of("transform", transform))
assertThat(smm.getView(tag).translationY).isEqualTo(40.0f)

smm.storeSynchronousMountPropsOverride(tag, JavaOnlyMap.of("transform", null))
smm.updatePropsSynchronously(tag, JavaOnlyMap.of("transform", null))
assertThat(smm.getView(tag).translationY).isEqualTo(0.0f)

smm.updateProps(tag, JavaOnlyMap.of("transform", null))

assertThat(smm.getView(tag).translationY).isEqualTo(0.0f)
}

/**
* When a view is deleted, stored synchronous props should be cleaned up. A recreated view with
* the same tag should not be affected by the old stored props.
Expand Down
Loading