Skip to content

Commit 0873116

Browse files
sorinc03meta-codesync[bot]
authored andcommitted
Fix secure Android TextInput same-text updates (#57013)
Summary: Fixes #53696. Android secure `TextInput` has a guard that skips replacing text when JS echoes the same content, preserving Android's transient password reveal timer. During the Kotlin conversion, this guard changed from `TextUtils.equals(...)` to equality between the current `Editable` and incoming `Spanned` text. Those can contain the same characters without comparing equal, so state updates re-apply the text immediately and hide the latest revealed password character. This restores content-based equality for secure text updates and adds a regression test that same-text secure updates do not replace the existing `Editable`. ## Changelog: [ANDROID] [FIXED] - Preserve secure TextInput password character reveal timing when JS state echoes the same text. Pull Request resolved: #57013 Test Plan: - `./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest --tests com.facebook.react.views.textinput.ReactTextInputPropertyTest -Preact.internal.useHermesStable=true` - `./gradlew :packages:react-native:ReactAndroid:ktfmtCheck -Preact.internal.useHermesStable=true` Reviewed By: cortinico Differential Revision: D107094261 Pulled By: javache fbshipit-source-id: 68d087a7b4f0d6021cc31456ef50245c11552b2b
1 parent 63dec77 commit 0873116

2 files changed

Lines changed: 31 additions & 1 deletion

File tree

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import android.text.Spannable
2222
import android.text.SpannableStringBuilder
2323
import android.text.Spanned
2424
import android.text.TextPaint
25+
import android.text.TextUtils
2526
import android.text.TextWatcher
2627
import android.text.method.KeyListener
2728
import android.text.method.QwertyKeyListener
@@ -648,7 +649,7 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
648649
public fun canUpdateWithEventCount(eventCounter: Int): Boolean = eventCounter >= nativeEventCount
649650

650651
private fun maybeSetText(reactTextUpdate: ReactTextUpdate) {
651-
if (isSecureText && (text == reactTextUpdate.text)) {
652+
if (isSecureText && TextUtils.equals(text, reactTextUpdate.text)) {
652653
return
653654
}
654655

packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import android.text.InputFilter
1717
import android.text.InputFilter.AllCaps
1818
import android.text.InputType
1919
import android.text.Layout
20+
import android.text.SpannableString
21+
import android.text.Spanned
2022
import android.util.DisplayMetrics
2123
import android.view.Gravity
2224
import android.view.View
@@ -32,6 +34,7 @@ import com.facebook.react.uimanager.DisplayMetricsHolder
3234
import com.facebook.react.uimanager.ReactStylesDiffMap
3335
import com.facebook.react.uimanager.ThemedReactContext
3436
import com.facebook.react.views.text.DefaultStyleValuesUtil.getDefaultTextColorHint
37+
import com.facebook.react.views.text.ReactTextUpdate
3538
import org.assertj.core.api.Assertions.assertThat
3639
import org.junit.Before
3740
import org.junit.Test
@@ -481,7 +484,33 @@ class ReactTextInputPropertyTest {
481484
assertThat(view.filters).isEqualTo(filters)
482485
}
483486

487+
@Test
488+
fun testSecureTextDoesNotReplaceSameTextFromJS() {
489+
val markerSpan = MarkerSpan()
490+
val textUpdate =
491+
SpannableString("secret").apply {
492+
setSpan(markerSpan, 0, length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
493+
}
494+
495+
manager.updateProperties(view, buildStyles("secureTextEntry", true))
496+
view.setText("secret")
497+
498+
view.maybeSetTextFromJS(
499+
ReactTextUpdate(
500+
textUpdate,
501+
0,
502+
view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK,
503+
Layout.BREAK_STRATEGY_HIGH_QUALITY,
504+
0,
505+
)
506+
)
507+
508+
assertThat(checkNotNull(view.text).getSpans(0, view.length(), MarkerSpan::class.java)).isEmpty()
509+
}
510+
484511
private fun buildStyles(vararg keysAndValues: Any?): ReactStylesDiffMap {
485512
return ReactStylesDiffMap(JavaOnlyMap.of(*keysAndValues))
486513
}
514+
515+
private class MarkerSpan
487516
}

0 commit comments

Comments
 (0)