Bug 1470786 - 2. Fix a text sync issue; r?esawin draft
authorJim Chen <nchen@mozilla.com>
Tue, 17 Jul 2018 11:22:34 -0400
changeset 819303 330b9ebdec531667423fde48348f7dfb664c11be
parent 819302 66f071146bcdad97d958250e53b62f5628e3cc8b
push id116499
push userbmo:nchen@mozilla.com
push dateTue, 17 Jul 2018 15:23:54 +0000
reviewersesawin
bugs1470786
milestone63.0a1
Bug 1470786 - 2. Fix a text sync issue; r?esawin Fix an issue in GeckoEditable where spans can be mistakenly deleted when deleting text. The new code re-copies all spans to make sure similar issues don't happen in the future. MozReview-Commit-ID: G1fWsJkeTka
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java
@@ -322,32 +322,39 @@ import android.view.inputmethod.EditorIn
             }
 
             // Copy the portion of the current text that has changed over to the shadow
             // text, with consideration for any concurrent changes in the shadow text.
             final int start = Math.min(mShadowStart, mCurrentStart);
             final int shadowEnd = mShadowNewEnd + Math.max(0, mCurrentOldEnd - mShadowOldEnd);
             final int currentEnd = mCurrentNewEnd + Math.max(0, mShadowOldEnd - mCurrentOldEnd);
 
-            // Remove identical spans that are in the new text from the old text.
-            // Otherwise the new spans won't be inserted due to the text already having
-            // the old spans.
-            Object[] spans = mCurrentText.getSpans(start, currentEnd, Object.class);
-            for (final Object span : spans) {
-                mShadowText.removeSpan(span);
-            }
-
-            // Also remove existing spans that are no longer in the new text.
-            spans = mShadowText.getSpans(start, shadowEnd, Object.class);
+            // Remove existing spans that may no longer be in the new text.
+            Object[] spans = mShadowText.getSpans(start, shadowEnd, Object.class);
             for (final Object span : spans) {
                 mShadowText.removeSpan(span);
             }
 
             mShadowText.replace(start, shadowEnd, mCurrentText, start, currentEnd);
 
+            // The replace() call may not have copied all affected spans, so we re-copy all the
+            // spans manually just in case. Expand bounds by 1 so we get all the spans.
+            spans = mCurrentText.getSpans(Math.max(start - 1, 0),
+                                          Math.min(currentEnd + 1, mCurrentText.length()),
+                                          Object.class);
+            for (final Object span : spans) {
+                if (span == Selection.SELECTION_START || span == Selection.SELECTION_END) {
+                    continue;
+                }
+                mShadowText.setSpan(span,
+                                    mCurrentText.getSpanStart(span),
+                                    mCurrentText.getSpanEnd(span),
+                                    mCurrentText.getSpanFlags(span));
+            }
+
             // SpannableStringBuilder has some internal logic to fix up selections, but we
             // don't want that, so we always fix up the selection a second time.
             final int selStart = Selection.getSelectionStart(mCurrentText);
             final int selEnd = Selection.getSelectionEnd(mCurrentText);
             Selection.setSelection(mShadowText, selStart, selEnd);
 
             if (DEBUG && !checkEqualText(mShadowText, mCurrentText)) {
                 // Sanity check.
@@ -378,16 +385,20 @@ import android.view.inputmethod.EditorIn
     private static boolean checkEqualText(final Spanned s1, final Spanned s2) {
         if (!s1.toString().equals(s2.toString())) {
             return false;
         }
 
         final Object[] o1s = s1.getSpans(0, s1.length(), Object.class);
         final Object[] o2s = s2.getSpans(0, s2.length(), Object.class);
 
+        if (o1s.length != o2s.length) {
+            return false;
+        }
+
         o1loop: for (final Object o1 : o1s) {
             for (final Object o2 : o2s)  {
                 if (o1 != o2) {
                     continue;
                 }
                 if (s1.getSpanStart(o1) != s2.getSpanStart(o2) ||
                         s1.getSpanEnd(o1) != s2.getSpanEnd(o2) ||
                         s1.getSpanFlags(o1) != s2.getSpanFlags(o2)) {