Permalink
Browse files

Fixed crash in ReactTextInputLocalData

Summary:
Motivation:

(SUDDENLY) There is a thing on Android called SpanWather, and their purpose is to notify "the watcher" about span-related changes in SpannableString. The idea is: some special kind of span can have some logic to prevent or tweak interleaving with some another kind of spans. To do so, it has to implement SpanWather interface.
So, EditText uses this to control internal spannable object (!) and SUDDENLY (#><) calls internal "layout" method as a reaction to adding new spans. So, when we are cloning SpannableString, we are (re)applying same span objects to a new spannable instance, and it causes notifying other spans in the string, and they notify EditText, and the EditText does relayout and... BOOM!
So, the solution is, easy, we should use SpannableStringBuilder instead of SpannableString because it does not notify SpanWather during cloning.

See:
https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/text/SpannableStringBuilder.java#101
(the first argument is `false`).
https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/text/SpannableStringBuilder.java#678
Compare with:
https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/text/SpannableStringInternal.java#43

Why? I believe because SpannableStringBuilder objects are "unfinished" by design, and documentation said: "it is the caller's responsibility to restore invariants [among spans]". As we do an exact clone of the string, that's perfectly okay to assume that all invariants were already satisfied for original string.

Reviewed By: achen1

Differential Revision: D5970940

fbshipit-source-id: 590ca0e3aede4470b809c7db527c5d55ddf5edb4
  • Loading branch information...
shergin authored and facebook-github-bot committed Oct 4, 2017
1 parent e1fb6ff commit cdea3c574ba698b5111ac3c78c0b0984e20b4df2
@@ -10,22 +10,22 @@
package com.facebook.react.views.textinput;
import android.os.Build;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.util.TypedValue;
import android.widget.EditText;
/** Local state bearer for EditText instance. */
public final class ReactTextInputLocalData {
private final SpannableString mText;
private final SpannableStringBuilder mText;
private final float mTextSize;
private final int mMinLines;
private final int mMaxLines;
private final int mInputType;
private final int mBreakStrategy;
public ReactTextInputLocalData(EditText editText) {
mText = new SpannableString(editText.getText());
mText = new SpannableStringBuilder(editText.getText());
mTextSize = editText.getTextSize();
mMinLines = editText.getMinLines();
mMaxLines = editText.getMaxLines();

0 comments on commit cdea3c5

Please sign in to comment.