From 8a0fe30591e21b90a3481c1ef3eeadd4b592f3ed Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Thu, 9 Feb 2023 09:56:05 -0800 Subject: [PATCH] Fix measurement of uncontrolled TextInput after edit Summary: D42721684 (https://github.com/facebook/react-native/commit/be69c8b5a77ae60cced1b2af64e48b90d9955be5) left a pretty bad bug when using Fabric for Android. I missed that in Fabric specifically, on edit we will cache the Spannable backing the EditText for use in future measurement. Because we've stripped the sizing spans, Spannable measurement has incorrect font size, and the TextInput size will change (collapsing) after the first edit. This effectively breaks any uncontrolled TextInput which does not have explicit dimensions set. Changelog: [Android][Fixed] - Fix measurement of uncontrolled TextInput after edit Reviewed By: sammy-SC Differential Revision: D43158407 fbshipit-source-id: 51602eab06c9a50e2b60ef0ed87bdb4df025e51e --- .../react/views/textinput/ReactEditText.java | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java index c150abc1500dca..3bcff03e5f4be4 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java @@ -587,7 +587,7 @@ public void maybeSetText(ReactTextUpdate reactTextUpdate) { manageSpans(spannableStringBuilder, reactTextUpdate.mContainsMultipleFragments); // Mitigation for https://github.com/facebook/react-native/issues/35936 (S318090) - stripAbsoluteSizeSpans(spannableStringBuilder); + stripAtributeEquivalentSpans(spannableStringBuilder); mContainsImages = reactTextUpdate.containsImages(); @@ -662,7 +662,7 @@ private void manageSpans( } } - private void stripAbsoluteSizeSpans(SpannableStringBuilder sb) { + private void stripAtributeEquivalentSpans(SpannableStringBuilder sb) { // We have already set a font size on the EditText itself. We can safely remove sizing spans // which are the same as the set font size, and not otherwise overlapped. final int effectiveFontSize = mTextAttributes.getEffectiveFontSize(); @@ -683,6 +683,31 @@ private void stripAbsoluteSizeSpans(SpannableStringBuilder sb) { } } + private void unstripAttributeEquivalentSpans( + SpannableStringBuilder workingText, Spannable originalText) { + // We must add spans back for Fabric to be able to measure, at lower precedence than any + // existing spans. Remove all spans, add the attributes, then re-add the spans over + workingText.append(originalText); + + for (Object span : workingText.getSpans(0, workingText.length(), Object.class)) { + workingText.removeSpan(span); + } + + workingText.setSpan( + new ReactAbsoluteSizeSpan(mTextAttributes.getEffectiveFontSize()), + 0, + workingText.length(), + Spanned.SPAN_INCLUSIVE_INCLUSIVE); + + for (Object span : originalText.getSpans(0, originalText.length(), Object.class)) { + workingText.setSpan( + span, + originalText.getSpanStart(span), + originalText.getSpanEnd(span), + originalText.getSpanFlags(span)); + } + } + private static boolean sameTextForSpan( final Editable oldText, final SpannableStringBuilder newText, @@ -1102,7 +1127,8 @@ private void updateCachedSpannable(boolean resetStyles) { // ... // - android.app.Activity.dispatchKeyEvent (Activity.java:3447) try { - sb.append(currentText.subSequence(0, currentText.length())); + Spannable text = (Spannable) currentText.subSequence(0, currentText.length()); + unstripAttributeEquivalentSpans(sb, text); } catch (IndexOutOfBoundsException e) { ReactSoftExceptionLogger.logSoftException(TAG, e); }