From da23fdff40b7653453a950de70ef9cc880e4aa62 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Wed, 25 Jan 2023 10:11:35 -0800 Subject: [PATCH] Partial Mitigation for Samsung TextInput Hangs Summary: 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 the most common cases, when it is sufficient to set `android:textSize`. In simple cases, it causes typing into the TextInput to no longer hang. This does not 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 could potentially do further work to reduce the number of spans emitted in these scenarios, but this may be fighting a losing battle against the platform. Changelog: [Android][Fixed] - Partial Mitigation for Samsung TextInput Hangs (Paper) Reviewed By: cortinico Differential Revision: D42721684 fbshipit-source-id: 5599070f10a385c6063683e3ac7a5acbbdc10ae9 --- .../views/text/ReactBaseTextShadowNode.java | 26 +++++++++++++++++++ .../react/views/text/ReactTextShadowNode.java | 10 +++++++ 2 files changed, 36 insertions(+) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java index cbf2967d9755ab..bfed6e09a47aae 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java @@ -308,6 +308,9 @@ protected Spannable spannedFromShadowNode( priority++; } + // Mitigation for https://github.com/facebook/react-native/issues/35936 (S318090) + flattenAbsoluteSizeSpans(sb); + textShadowNode.mTextAttributes.setHeightOfTallestInlineViewOrImage( heightOfTallestInlineViewOrImage); @@ -318,6 +321,29 @@ protected Spannable spannedFromShadowNode( return sb; } + private void flattenAbsoluteSizeSpans(SpannableStringBuilder sb) { + // If spans cover the entire input with the same size as + // getEffectiveFontSize() we can omit them + ReactAbsoluteSizeSpan[] spans = sb.getSpans(0, sb.length(), ReactAbsoluteSizeSpan.class); + + int lastSpanIndex = -1; + final int expectedTextSize = mTextAttributes.getEffectiveFontSize(); + + for (ReactAbsoluteSizeSpan span : spans) { + if (sb.getSpanStart(span) > lastSpanIndex + 1 || span.getSize() != expectedTextSize) { + return; + } + + lastSpanIndex = sb.getSpanEnd(span) - 1; + } + + if (lastSpanIndex >= sb.length() - 1) { + for (ReactAbsoluteSizeSpan span : spans) { + sb.removeSpan(span); + } + } + } + protected TextAttributes mTextAttributes; protected boolean mIsColorSet = false; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java index 4e67070aca4bd0..36dfc6bf94f475 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java @@ -73,6 +73,16 @@ public long measure( mPreparedSpannableText, "Spannable element has not been prepared in onBeforeLayout"); + // Unflatten AbsoluteSizeSpan for measurement (see flattenAbsoluteSizeSpans() in + // ReactBaseTextShadowNode) + if (text.getSpans(0, text.length(), ReactAbsoluteSizeSpan.class).length == 0) { + text.setSpan( + new ReactAbsoluteSizeSpan(mTextAttributes.getEffectiveFontSize()), + 0, + text.length(), + Spannable.SPAN_INCLUSIVE_INCLUSIVE); + } + Layout layout = measureSpannedText(text, width, widthMode); if (mAdjustsFontSizeToFit) {