зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1490391 - 3. Process replace-text event on reply; r=esawin
Currently, we process replace-text events during `onTextChange` calls, but we get confused if one `onTextChange` call corresponds to two or more replace-text events. This patch makes us do minimal processing during `onTextChange`, and perform the bulk of the processing during each individual replace-text reply. Differential Revision: https://phabricator.services.mozilla.com/D9851
This commit is contained in:
Родитель
841b637299
Коммит
f564f7b1a6
|
@ -105,6 +105,9 @@ import android.view.inputmethod.EditorInfo;
|
|||
private int mIMEFlags; // Used by IC thread.
|
||||
|
||||
private boolean mIgnoreSelectionChange; // Used by Gecko thread
|
||||
private int mLastTextChangeStart = -1; // Used by Gecko thread
|
||||
private int mLastTextChangeEnd = -1; // Used by Gecko thread
|
||||
private boolean mLastTextChangeReplacedSelection; // Used by Gecko thread
|
||||
|
||||
// Prevent showSoftInput and hideSoftInput from being called multiple times in a row,
|
||||
// including reentrant calls on some devices. Used by UI/IC thread.
|
||||
|
@ -1126,6 +1129,45 @@ import android.view.inputmethod.EditorInfo;
|
|||
getConstantName(Action.class, "TYPE_", action.mType) + ")");
|
||||
}
|
||||
switch (action.mType) {
|
||||
case Action.TYPE_REPLACE_TEXT: {
|
||||
final Spanned currentText = mText.getCurrentText();
|
||||
final int actionNewEnd = action.mStart + action.mSequence.length();
|
||||
if (mLastTextChangeStart < 0 || mLastTextChangeEnd > currentText.length() ||
|
||||
action.mStart < mLastTextChangeStart || actionNewEnd > mLastTextChangeEnd) {
|
||||
// Replace-text action doesn't match our text change.
|
||||
break;
|
||||
}
|
||||
|
||||
int indexInText = TextUtils.indexOf(currentText, action.mSequence,
|
||||
action.mStart, mLastTextChangeEnd);
|
||||
if (indexInText < 0 && action.mStart != mLastTextChangeStart) {
|
||||
final String changedText = TextUtils.substring(
|
||||
currentText, mLastTextChangeStart, actionNewEnd);
|
||||
indexInText = changedText.lastIndexOf(action.mSequence.toString());
|
||||
if (indexInText >= 0) {
|
||||
indexInText += mLastTextChangeStart;
|
||||
}
|
||||
}
|
||||
if (indexInText < 0) {
|
||||
// Replace-text action doesn't match our current text.
|
||||
break;
|
||||
}
|
||||
|
||||
// Replace-text action matches our current text; copy the new spans to the
|
||||
// current text.
|
||||
mText.currentReplace(indexInText,
|
||||
indexInText + action.mSequence.length(),
|
||||
action.mSequence);
|
||||
|
||||
// The text change is caused by the replace-text event. If the text change
|
||||
// replaced the previous selection, we need to rely on Gecko for an updated
|
||||
// selection, so don't ignore selection change. However, if the text change
|
||||
// did not replace the previous selection, we can ignore the Gecko selection
|
||||
// in favor of the Java selection.
|
||||
mIgnoreSelectionChange = !mLastTextChangeReplacedSelection;
|
||||
break;
|
||||
}
|
||||
|
||||
case Action.TYPE_SET_SPAN:
|
||||
final int len = mText.getCurrentText().length();
|
||||
if (action.mStart > len || action.mEnd > len ||
|
||||
|
@ -1579,6 +1621,12 @@ import android.view.inputmethod.EditorInfo;
|
|||
mText.currentSetSelection(start, end);
|
||||
}
|
||||
|
||||
// We receive selection change notification after receiving replies for pending
|
||||
// events, so we can reset text change bounds at this point.
|
||||
mLastTextChangeStart = -1;
|
||||
mLastTextChangeEnd = -1;
|
||||
mLastTextChangeReplacedSelection = false;
|
||||
|
||||
mIcPostHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -1610,7 +1658,6 @@ import android.view.inputmethod.EditorInfo;
|
|||
final int currentLength = mText.getCurrentText().length();
|
||||
final int oldEnd = unboundedOldEnd > currentLength ? currentLength : unboundedOldEnd;
|
||||
final int newEnd = start + text.length();
|
||||
final Action action = mActions.peek();
|
||||
|
||||
if (start == 0 && unboundedOldEnd > currentLength) {
|
||||
// | oldEnd > currentLength | signals entire text is cleared (e.g. for
|
||||
|
@ -1622,96 +1669,42 @@ import android.view.inputmethod.EditorInfo;
|
|||
// Don't ignore the next selection change because we are re-syncing with Gecko
|
||||
mIgnoreSelectionChange = false;
|
||||
|
||||
} else if (action != null &&
|
||||
action.mType == Action.TYPE_REPLACE_TEXT &&
|
||||
start <= action.mStart &&
|
||||
oldEnd >= action.mEnd &&
|
||||
newEnd >= action.mStart + action.mSequence.length()) {
|
||||
mLastTextChangeStart = -1;
|
||||
mLastTextChangeEnd = -1;
|
||||
mLastTextChangeReplacedSelection = false;
|
||||
|
||||
// Try to preserve both old spans and new spans in action.mSequence.
|
||||
// indexInText is where we can find waction.mSequence within the passed in text.
|
||||
final int startWithinText = action.mStart - start;
|
||||
int indexInText = TextUtils.indexOf(text, action.mSequence, startWithinText);
|
||||
if (indexInText < 0 && startWithinText >= action.mSequence.length()) {
|
||||
indexInText = text.toString().lastIndexOf(action.mSequence.toString(),
|
||||
startWithinText);
|
||||
}
|
||||
} else if (!geckoIsSameText(start, oldEnd, text)) {
|
||||
final Spanned currentText = mText.getCurrentText();
|
||||
final int selStart = Selection.getSelectionStart(currentText);
|
||||
final int selEnd = Selection.getSelectionEnd(currentText);
|
||||
|
||||
if (indexInText < 0) {
|
||||
// Text was changed from under us. We are forced to discard any new spans.
|
||||
mText.currentReplace(start, oldEnd, text);
|
||||
// True if the selection was in the middle of the replaced text; in that case
|
||||
// we don't know where to place the selection after replacement, and must rely
|
||||
// on the Gecko selection.
|
||||
mLastTextChangeReplacedSelection |=
|
||||
(selStart >= start && selStart <= oldEnd) ||
|
||||
(selEnd >= start && selEnd <= oldEnd);
|
||||
|
||||
// Don't ignore the next selection change because we are forced to re-sync
|
||||
// with Gecko here.
|
||||
mIgnoreSelectionChange = false;
|
||||
|
||||
} else if (indexInText == 0 && text.length() == action.mSequence.length() &&
|
||||
oldEnd - start == action.mEnd - action.mStart) {
|
||||
// The new change exactly matches our saved change, so do a direct replace.
|
||||
mText.currentReplace(start, oldEnd, action.mSequence);
|
||||
|
||||
// Ignore the next selection change because the selection change is a
|
||||
// side-effect of the replace-text event we sent.
|
||||
mIgnoreSelectionChange = true;
|
||||
|
||||
} else {
|
||||
// The sequence is embedded within the changed text, so we have to perform
|
||||
// replacement in parts. First replace part of text before the sequence.
|
||||
mText.currentReplace(start, action.mStart, text.subSequence(0, indexInText));
|
||||
|
||||
// Then replace part of the text after the sequence.
|
||||
final int actionStart = indexInText + start;
|
||||
final int delta = actionStart - action.mStart;
|
||||
final int actionEnd = delta + action.mEnd;
|
||||
|
||||
final Spanned currentText = mText.getCurrentText();
|
||||
final boolean resetSelStart = Selection.getSelectionStart(currentText) == actionEnd;
|
||||
final boolean resetSelEnd = Selection.getSelectionEnd(currentText) == actionEnd;
|
||||
|
||||
mText.currentReplace(actionEnd, delta + oldEnd, text.subSequence(
|
||||
indexInText + action.mSequence.length(), text.length()));
|
||||
|
||||
// The replacement above may have shifted our selection, if the selection
|
||||
// was at the start of the replacement range. If so, we need to reset
|
||||
// our selection to the previous position.
|
||||
if (resetSelStart || resetSelEnd) {
|
||||
mText.currentSetSelection(
|
||||
resetSelStart ? actionEnd : Selection.getSelectionStart(currentText),
|
||||
resetSelEnd ? actionEnd : Selection.getSelectionEnd(currentText));
|
||||
}
|
||||
|
||||
// Finally replace the sequence itself to preserve new spans.
|
||||
mText.currentReplace(actionStart, actionEnd, action.mSequence);
|
||||
|
||||
// If one of the Java selection ends is not at the end of the replaced
|
||||
// text, we want to preserve that selection, so we ignore the Gecko
|
||||
// selection change notification. On the other hand, if the Java selection
|
||||
// is normal, we want to try syncing the Java selection to the Gecko
|
||||
// selection, because this text change could have changed the Gecko
|
||||
// selection to elsewhere; so in that case, don't ignore the Gecko
|
||||
// selection change notification.
|
||||
mIgnoreSelectionChange = !resetSelStart || !resetSelEnd;
|
||||
}
|
||||
|
||||
} else if (geckoIsSameText(start, oldEnd, text)) {
|
||||
// Nothing to do because the text is the same. This could happen when
|
||||
// the composition is updated for example, in which case we want to keep the
|
||||
// Java selection.
|
||||
mIgnoreSelectionChange = mIgnoreSelectionChange || (action != null &&
|
||||
(action.mType == Action.TYPE_REPLACE_TEXT ||
|
||||
action.mType == Action.TYPE_SET_SPAN ||
|
||||
action.mType == Action.TYPE_REMOVE_SPAN));
|
||||
return;
|
||||
|
||||
} else {
|
||||
// Gecko side initiated the text change. Replace in two steps to properly
|
||||
// clear composing spans that span the whole range.
|
||||
mText.currentReplace(start, oldEnd, "");
|
||||
mText.currentReplace(start, start, text);
|
||||
|
||||
// Don't ignore the next selection change because we are forced to re-sync
|
||||
// with Gecko here.
|
||||
mIgnoreSelectionChange = false;
|
||||
mLastTextChangeStart = start;
|
||||
mLastTextChangeEnd = newEnd;
|
||||
|
||||
} else {
|
||||
// Nothing to do because the text is the same. This could happen when
|
||||
// the composition is updated for example, in which case we want to keep the
|
||||
// Java selection.
|
||||
final Action action = mActions.peek();
|
||||
mIgnoreSelectionChange = mIgnoreSelectionChange || (action != null &&
|
||||
(action.mType == Action.TYPE_REPLACE_TEXT ||
|
||||
action.mType == Action.TYPE_SET_SPAN ||
|
||||
action.mType == Action.TYPE_REMOVE_SPAN));
|
||||
|
||||
mLastTextChangeStart = start;
|
||||
mLastTextChangeEnd = newEnd;
|
||||
}
|
||||
|
||||
// onTextChange is always followed by onSelectionChange, so we let
|
||||
|
@ -1790,7 +1783,7 @@ import android.view.inputmethod.EditorInfo;
|
|||
} else if (chr == '\n') {
|
||||
return "\u21b2";
|
||||
}
|
||||
return String.format("%04x", (int) chr);
|
||||
return String.format("\\u%04x", (int) chr);
|
||||
}
|
||||
|
||||
static StringBuilder debugAppend(StringBuilder sb, Object obj) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче