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 @@ -7,6 +7,8 @@

package com.facebook.react.uimanager;

import static com.facebook.react.uimanager.internal.FloatPropertySanitizer.sanitizeFloatPropertyValue;

import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
Expand Down Expand Up @@ -298,7 +300,7 @@ public void setOpacity(@NonNull T view, float opacity) {

@ReactProp(name = ViewProps.ELEVATION)
public void setElevation(@NonNull T view, float elevation) {
float px = PixelUtil.toPixelFromDIP(elevation);
float px = sanitizeFloatPropertyValue(PixelUtil.toPixelFromDIP(elevation));
boolean wasElevated = view.getElevation() > 0f;
ViewCompat.setElevation(view, px);
if (wasElevated != (px > 0f)) {
Expand Down Expand Up @@ -553,13 +555,13 @@ public void setRotation(@NonNull T view, float rotation) {
@Deprecated
@ReactProp(name = ViewProps.SCALE_X, defaultFloat = 1f)
public void setScaleX(@NonNull T view, float scaleX) {
view.setScaleX(scaleX);
view.setScaleX(sanitizeFloatPropertyValue(scaleX));
}

@Deprecated
@ReactProp(name = ViewProps.SCALE_Y, defaultFloat = 1f)
public void setScaleY(@NonNull T view, float scaleY) {
view.setScaleY(scaleY);
view.setScaleY(sanitizeFloatPropertyValue(scaleY));
}

@Deprecated
Expand Down Expand Up @@ -719,29 +721,6 @@ private static <T extends View> void clearSkewAnimationMatrixIfActive(@NonNull T
view.setTag(R.id.skew_animation_matrix, null);
}

/**
* Prior to Android P things like setScaleX() allowed passing float values that were bogus such as
* Float.NaN. If the app is targeting Android P or later then passing these values will result in
* an exception being thrown. Since JS might still send Float.NaN, we want to keep the code
* backward compatible and continue using the fallback value if an invalid float is passed.
*/
private static float sanitizeFloatPropertyValue(float value) {
if (value >= -Float.MAX_VALUE && value <= Float.MAX_VALUE) {
return value;
}
if (value < -Float.MAX_VALUE || value == Float.NEGATIVE_INFINITY) {
return -Float.MAX_VALUE;
}
if (value > Float.MAX_VALUE || value == Float.POSITIVE_INFINITY) {
return Float.MAX_VALUE;
}
if (Float.isNaN(value)) {
return 0;
}
// Shouldn't be possible to reach this point.
throw new IllegalStateException("Invalid float property value: " + value);
}

protected void updateViewAccessibility(@NonNull T view) {
ReactAccessibilityDelegate.setDelegate(
view, view.isFocusable(), view.getImportantForAccessibility());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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.internal

/**
* Prior to Android P things like setScaleX() allowed passing float values that were bogus such as
* Float.NaN. If the app is targeting Android P or later then passing these values will result in an
* exception being thrown. Since JS might still send Float.NaN, we want to keep the code backward
* compatible and continue using the fallback value if an invalid float is passed.
*/
public object FloatPropertySanitizer {

@JvmStatic
public fun sanitizeFloatPropertyValue(value: Float): Float =
if (value.isNaN()) 0f else value.coerceIn(-Float.MAX_VALUE, Float.MAX_VALUE)
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.facebook.react.uimanager.ViewGroupManager
import com.facebook.react.uimanager.ViewManagerDelegate
import com.facebook.react.uimanager.annotations.ReactProp
import com.facebook.react.uimanager.events.EventDispatcher
import com.facebook.react.uimanager.internal.FloatPropertySanitizer
import com.facebook.react.viewmanagers.AndroidDrawerLayoutManagerDelegate
import com.facebook.react.viewmanagers.AndroidDrawerLayoutManagerInterface
import com.facebook.react.views.drawer.events.DrawerClosedEvent
Expand Down Expand Up @@ -142,7 +143,7 @@ public class ReactDrawerLayoutManager :
public override fun setStatusBarBackgroundColor(view: ReactDrawerLayout, value: Int?): Unit = Unit

public override fun setElevation(view: ReactDrawerLayout, elevation: Float) {
view.drawerElevation = elevation.dpToPx()
view.drawerElevation = FloatPropertySanitizer.sanitizeFloatPropertyValue(elevation.dpToPx())
}

public override fun needsCustomLayoutForChildren(): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

package com.facebook.react.uimanager

import android.util.DisplayMetrics
import android.view.View
import android.view.View.OnFocusChangeListener
import android.view.accessibility.AccessibilityNodeInfo
Expand Down Expand Up @@ -321,6 +322,78 @@ class BaseViewManagerTest {
Assertions.assertThat(recycled.getTag(R.id.original_focusability)).isNull()
}

@Test
fun testNaNElevationLeavesTheViewUnelevated() {
val unelevated = createView(JavaOnlyMap.of(ViewProps.ELEVATION, Double.NaN))

Assertions.assertThat(unelevated.elevation).isEqualTo(0f)
}

@Test
fun testInfiniteElevationTakesTheLargestFiniteElevation() {
val raised = createView(JavaOnlyMap.of(ViewProps.ELEVATION, Double.POSITIVE_INFINITY))

Assertions.assertThat(raised.elevation).isEqualTo(Float.MAX_VALUE)
}

@Test
fun testNegativeInfiniteElevationTakesTheLowestFiniteElevation() {
val lowered = createView(JavaOnlyMap.of(ViewProps.ELEVATION, Double.NEGATIVE_INFINITY))

Assertions.assertThat(lowered.elevation).isEqualTo(-Float.MAX_VALUE)
}

@Test
fun testElevationPastTheLargestFloatTakesTheLargestFiniteElevation() {
val raised = createView(JavaOnlyMap.of(ViewProps.ELEVATION, 1e39))

Assertions.assertThat(raised.elevation).isEqualTo(Float.MAX_VALUE)
}

@Test
fun testElevationPastTheLargestFloatInPixelsTakesTheLargestFiniteElevation() {
DisplayMetricsHolder.setScreenDisplayMetrics(DisplayMetrics().apply { density = 3f })

viewManager.setElevation(view, Float.MAX_VALUE)

Assertions.assertThat(view.elevation).isEqualTo(Float.MAX_VALUE)
}

@Test
fun testNaNScaleTakesTheFrameworksFallback() {
val scaled =
createView(JavaOnlyMap.of(ViewProps.SCALE_X, Double.NaN, ViewProps.SCALE_Y, Double.NaN))

Assertions.assertThat(scaled.scaleX).isEqualTo(0f)
Assertions.assertThat(scaled.scaleY).isEqualTo(0f)
}

@Test
fun testInfiniteScaleTakesTheLargestFiniteScale() {
val scaled =
createView(
JavaOnlyMap.of(
ViewProps.SCALE_X,
Double.POSITIVE_INFINITY,
ViewProps.SCALE_Y,
Double.POSITIVE_INFINITY,
)
)

Assertions.assertThat(scaled.scaleX).isEqualTo(Float.MAX_VALUE)
Assertions.assertThat(scaled.scaleY).isEqualTo(Float.MAX_VALUE)
}

@Test
fun testNaNElevationAfterAFiniteOneLeavesTheViewUnelevated() {
viewManager.setElevation(view, 4f)
Assertions.assertThat(view.elevation).isEqualTo(PixelUtil.toPixelFromDIP(4f))

viewManager.setElevation(view, Float.NaN)

Assertions.assertThat(view.elevation).isEqualTo(0f)
}

private fun recycleView(
previousProps: JavaOnlyMap,
beforeDrop: (ReactViewGroup) -> Unit = {},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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.
*/

@file:Suppress("DEPRECATION")

package com.facebook.react.views.drawer

import android.util.DisplayMetrics
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import androidx.drawerlayout.widget.DrawerLayout
import com.facebook.react.bridge.BridgeReactContext
import com.facebook.react.uimanager.DisplayMetricsHolder
import com.facebook.react.uimanager.ThemedReactContext
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment

@RunWith(RobolectricTestRunner::class)
class ReactDrawerLayoutManagerTest {
private lateinit var drawerLayout: ReactDrawerLayout

@Before
fun setUp() {
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(RuntimeEnvironment.getApplication())
val context = BridgeReactContext(RuntimeEnvironment.getApplication())
drawerLayout = ReactDrawerLayout(ThemedReactContext(context, context, null, -1))
drawerLayout.addView(
View(drawerLayout.context),
DrawerLayout.LayoutParams(100, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.START),
)
}

@After
fun tearDown() {
DisplayMetricsHolder.setScreenDisplayMetrics(null)
}

@Test
fun testNaNElevationLeavesTheDrawerUnelevated() {
ReactDrawerLayoutManager().setElevation(drawerLayout, Float.NaN)

assertThat(drawerLayout.drawerElevation).isEqualTo(0f)
}

@Test
fun testInfiniteElevationTakesTheLargestFiniteElevation() {
ReactDrawerLayoutManager().setElevation(drawerLayout, Float.POSITIVE_INFINITY)

assertThat(drawerLayout.drawerElevation).isEqualTo(Float.MAX_VALUE)
}

@Test
fun testElevationPastTheLargestFloatInPixelsTakesTheLargestFiniteElevation() {
DisplayMetricsHolder.setScreenDisplayMetrics(DisplayMetrics().apply { density = 3f })

ReactDrawerLayoutManager().setElevation(drawerLayout, Float.MAX_VALUE)

assertThat(drawerLayout.drawerElevation).isEqualTo(Float.MAX_VALUE)
}
}
Loading