Mitigation for Samsung TextInput Hangs (#35967)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/35967

In https://github.com/facebook/react-native/issues/35936 we observed that the presence of AbsoluteSizeSpan may lead to hangs when using the Grammarly keyboard on Samsung.

This mitigation makes it so that we do not emit this span in any case where it is sufficient to rely on already set EditText textSize. In simple cases, tested on two devices, it causes typing into the TextInput to no longer hang.

This does not fully resolve the issue for TextInputs which meaningfully use layout-effecting spans (or at least font size), such as non-uniform text size within the input. We instead just try to reduce to minimum AbsoluteSizeSpan possible.

Testing the first commit was able to resolve hangs in some simpler inputs tested, by me and cortinico.

Changelog:
[Android][Fixed] - Mitigation for Samsung TextInput Hangs

Reviewed By: cortinico

Differential Revision: D42721684

fbshipit-source-id: e0388dfb4617f0217bc1d0b71752c733e10261dd
This commit is contained in:
Nick Gerleman
2023-01-25 21:21:51 -08:00
committed by Riccardo Cipolleschi
parent 279fb52e03
commit 616fca2fd2
@@ -555,6 +555,10 @@ public class ReactEditText extends AppCompatEditText
new SpannableStringBuilder(reactTextUpdate.getText());
manageSpans(spannableStringBuilder, reactTextUpdate.mContainsMultipleFragments);
// Mitigation for https://github.com/facebook/react-native/issues/35936 (S318090)
stripAbsoluteSizeSpans(spannableStringBuilder);
mContainsImages = reactTextUpdate.containsImages();
// When we update text, we trigger onChangeText code that will
@@ -628,6 +632,27 @@ public class ReactEditText extends AppCompatEditText
}
}
private void stripAbsoluteSizeSpans(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();
ReactAbsoluteSizeSpan[] spans = sb.getSpans(0, sb.length(), ReactAbsoluteSizeSpan.class);
outerLoop:
for (ReactAbsoluteSizeSpan span : spans) {
ReactAbsoluteSizeSpan[] overlappingSpans =
sb.getSpans(sb.getSpanStart(span), sb.getSpanEnd(span), ReactAbsoluteSizeSpan.class);
for (ReactAbsoluteSizeSpan overlappingSpan : overlappingSpans) {
if (span.getSize() != effectiveFontSize) {
continue outerLoop;
}
}
sb.removeSpan(span);
}
}
private static boolean sameTextForSpan(
final Editable oldText,
final SpannableStringBuilder newText,