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
This commit is contained in:
Nick Gerleman
2023-02-09 09:56:05 -08:00
committed by Lorenzo Sciandra
parent f4f3aa31f3
commit 0bcf29372a
@@ -552,7 +552,7 @@ public class ReactEditText extends AppCompatEditText
manageSpans(spannableStringBuilder, reactTextUpdate.mContainsMultipleFragments);
// Mitigation for https://github.com/facebook/react-native/issues/35936 (S318090)
stripAbsoluteSizeSpans(spannableStringBuilder);
stripAtributeEquivalentSpans(spannableStringBuilder);
mContainsImages = reactTextUpdate.containsImages();
@@ -627,7 +627,7 @@ public class ReactEditText extends AppCompatEditText
}
}
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();
@@ -648,6 +648,31 @@ public class ReactEditText extends AppCompatEditText
}
}
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,
@@ -1066,7 +1091,8 @@ public class ReactEditText extends AppCompatEditText
// ...
// - 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);
}