From e3dd900ef6e6eadfac5ed445942020e016018add Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Mon, 20 Mar 2023 19:28:39 -0700 Subject: [PATCH] Minimize EditText Spans 1/N: Fix precedence (#36543) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/36543 This is part of a series of changes to minimize the number of spans committed to EditText, as a mitigation for platform issues on Samsung devices. See this [GitHub thread]( https://github.com/facebook/react-native/issues/35936#issuecomment-1411437789) for greater context on the platform behavior. We cache the backing EditText span on text change to later measure. To measure outside of a TextInput we need to restore any spans we removed. Spans may overlap, so base attributes should be behind everything else. The logic here for dealing with precedence is incorrect, and we should instead accomplish this by twiddling with the `SPAN_PRIORITY` bits. Changelog: [Android][Fixed] - Minimize Spans 1/N: Fix precedence Differential Revision: D44240779 fbshipit-source-id: adabdfa204892ec05148375446a30bfaa63e8936 --- .../react/views/textinput/ReactEditText.java | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java index 07c95b243db62e..837f79702f12ce 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java @@ -683,29 +683,18 @@ private void stripAttributeEquivalentSpans(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); + private void unstripAttributeEquivalentSpans(SpannableStringBuilder workingText) { + int spanFlags = Spannable.SPAN_INCLUSIVE_INCLUSIVE; - for (Object span : workingText.getSpans(0, workingText.length(), Object.class)) { - workingText.removeSpan(span); - } + // Set all bits for SPAN_PRIORITY so that this span has the highest possible priority + // (least precedence). This ensures the span is behind any overlapping spans. + spanFlags |= Spannable.SPAN_PRIORITY; 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)); - } + spanFlags); } private static boolean sameTextForSpan( @@ -1132,8 +1121,8 @@ private void updateCachedSpannable(boolean resetStyles) { // ... // - android.app.Activity.dispatchKeyEvent (Activity.java:3447) try { - Spannable text = (Spannable) currentText.subSequence(0, currentText.length()); - unstripAttributeEquivalentSpans(sb, text); + sb.append(currentText.subSequence(0, currentText.length())); + unstripAttributeEquivalentSpans(sb); } catch (IndexOutOfBoundsException e) { ReactSoftExceptionLogger.logSoftException(TAG, e); }