зеркало из 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 int mIMEFlags; // Used by IC thread.
|
||||||
|
|
||||||
private boolean mIgnoreSelectionChange; // Used by Gecko 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,
|
// Prevent showSoftInput and hideSoftInput from being called multiple times in a row,
|
||||||
// including reentrant calls on some devices. Used by UI/IC thread.
|
// 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) + ")");
|
getConstantName(Action.class, "TYPE_", action.mType) + ")");
|
||||||
}
|
}
|
||||||
switch (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:
|
case Action.TYPE_SET_SPAN:
|
||||||
final int len = mText.getCurrentText().length();
|
final int len = mText.getCurrentText().length();
|
||||||
if (action.mStart > len || action.mEnd > len ||
|
if (action.mStart > len || action.mEnd > len ||
|
||||||
|
@ -1579,6 +1621,12 @@ import android.view.inputmethod.EditorInfo;
|
||||||
mText.currentSetSelection(start, end);
|
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() {
|
mIcPostHandler.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
@ -1610,7 +1658,6 @@ import android.view.inputmethod.EditorInfo;
|
||||||
final int currentLength = mText.getCurrentText().length();
|
final int currentLength = mText.getCurrentText().length();
|
||||||
final int oldEnd = unboundedOldEnd > currentLength ? currentLength : unboundedOldEnd;
|
final int oldEnd = unboundedOldEnd > currentLength ? currentLength : unboundedOldEnd;
|
||||||
final int newEnd = start + text.length();
|
final int newEnd = start + text.length();
|
||||||
final Action action = mActions.peek();
|
|
||||||
|
|
||||||
if (start == 0 && unboundedOldEnd > currentLength) {
|
if (start == 0 && unboundedOldEnd > currentLength) {
|
||||||
// | oldEnd > currentLength | signals entire text is cleared (e.g. for
|
// | 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
|
// Don't ignore the next selection change because we are re-syncing with Gecko
|
||||||
mIgnoreSelectionChange = false;
|
mIgnoreSelectionChange = false;
|
||||||
|
|
||||||
} else if (action != null &&
|
mLastTextChangeStart = -1;
|
||||||
action.mType == Action.TYPE_REPLACE_TEXT &&
|
mLastTextChangeEnd = -1;
|
||||||
start <= action.mStart &&
|
mLastTextChangeReplacedSelection = false;
|
||||||
oldEnd >= action.mEnd &&
|
|
||||||
newEnd >= action.mStart + action.mSequence.length()) {
|
|
||||||
|
|
||||||
// Try to preserve both old spans and new spans in action.mSequence.
|
} else if (!geckoIsSameText(start, oldEnd, text)) {
|
||||||
// indexInText is where we can find waction.mSequence within the passed in text.
|
final Spanned currentText = mText.getCurrentText();
|
||||||
final int startWithinText = action.mStart - start;
|
final int selStart = Selection.getSelectionStart(currentText);
|
||||||
int indexInText = TextUtils.indexOf(text, action.mSequence, startWithinText);
|
final int selEnd = Selection.getSelectionEnd(currentText);
|
||||||
if (indexInText < 0 && startWithinText >= action.mSequence.length()) {
|
|
||||||
indexInText = text.toString().lastIndexOf(action.mSequence.toString(),
|
|
||||||
startWithinText);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (indexInText < 0) {
|
// True if the selection was in the middle of the replaced text; in that case
|
||||||
// Text was changed from under us. We are forced to discard any new spans.
|
// we don't know where to place the selection after replacement, and must rely
|
||||||
mText.currentReplace(start, oldEnd, text);
|
// 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
|
// Gecko side initiated the text change. Replace in two steps to properly
|
||||||
// clear composing spans that span the whole range.
|
// clear composing spans that span the whole range.
|
||||||
mText.currentReplace(start, oldEnd, "");
|
mText.currentReplace(start, oldEnd, "");
|
||||||
mText.currentReplace(start, start, text);
|
mText.currentReplace(start, start, text);
|
||||||
|
|
||||||
// Don't ignore the next selection change because we are forced to re-sync
|
mLastTextChangeStart = start;
|
||||||
// with Gecko here.
|
mLastTextChangeEnd = newEnd;
|
||||||
mIgnoreSelectionChange = false;
|
|
||||||
|
} 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
|
// onTextChange is always followed by onSelectionChange, so we let
|
||||||
|
@ -1790,7 +1783,7 @@ import android.view.inputmethod.EditorInfo;
|
||||||
} else if (chr == '\n') {
|
} else if (chr == '\n') {
|
||||||
return "\u21b2";
|
return "\u21b2";
|
||||||
}
|
}
|
||||||
return String.format("%04x", (int) chr);
|
return String.format("\\u%04x", (int) chr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static StringBuilder debugAppend(StringBuilder sb, Object obj) {
|
static StringBuilder debugAppend(StringBuilder sb, Object obj) {
|
||||||
|
|
Загрузка…
Ссылка в новой задаче