Bug 1051556 - Re-flush IME changes when querying text triggers more changes; r=esawin

We send query text events when flushing IME changes, and sometimes these
events make Gecko commit more pending changes. In that case, we should
try flushing again, so we pick up the new changes.

This patch also makes the process of flushing text changes
transactional, so that if we have to bail due to more pending changes,
nothing will be committed.
This commit is contained in:
Jim Chen 2015-12-09 17:46:45 -05:00
parent aa9db9aeed
commit 899201cf4f
2 changed files with 56 additions and 13 deletions

View File

@ -1005,7 +1005,8 @@ final class GeckoEditable extends JNIObject
number to denote "end of the text". Fix that here */ number to denote "end of the text". Fix that here */
final int oldEnd = unboundedOldEnd > mText.length() ? mText.length() : unboundedOldEnd; final int oldEnd = unboundedOldEnd > mText.length() ? mText.length() : unboundedOldEnd;
// new end should always match text // new end should always match text
if (start != 0 && unboundedNewEnd != (start + text.length())) { if (unboundedOldEnd < Integer.MAX_VALUE / 2 &&
unboundedNewEnd != (start + text.length())) {
Log.e(LOGTAG, "newEnd does not match text: " + unboundedNewEnd + " vs " + Log.e(LOGTAG, "newEnd does not match text: " + unboundedNewEnd + " vs " +
(start + text.length())); (start + text.length()));
throw new IllegalArgumentException("newEnd does not match text"); throw new IllegalArgumentException("newEnd does not match text");

View File

@ -241,6 +241,7 @@ public:
, mIMEMaskEventsCount(1) // Mask IME events since there's no focus yet , mIMEMaskEventsCount(1) // Mask IME events since there's no focus yet
, mIMEUpdatingContext(false) , mIMEUpdatingContext(false)
, mIMESelectionChanged(false) , mIMESelectionChanged(false)
, mIMETextChangedDuringFlush(false)
{} {}
~Natives(); ~Natives();
@ -316,11 +317,17 @@ private:
int32_t mIMEMaskEventsCount; // Mask events when > 0 int32_t mIMEMaskEventsCount; // Mask events when > 0
bool mIMEUpdatingContext; bool mIMEUpdatingContext;
bool mIMESelectionChanged; bool mIMESelectionChanged;
bool mIMETextChangedDuringFlush;
void SendIMEDummyKeyEvents(); void SendIMEDummyKeyEvents();
void AddIMETextChange(const IMETextChange& aChange); void AddIMETextChange(const IMETextChange& aChange);
void PostFlushIMEChanges();
void FlushIMEChanges(); enum FlushChangesFlag {
FLUSH_FLAG_NONE,
FLUSH_FLAG_RETRY
};
void PostFlushIMEChanges(FlushChangesFlag aFlags = FLUSH_FLAG_NONE);
void FlushIMEChanges(FlushChangesFlag aFlags = FLUSH_FLAG_NONE);
public: public:
bool NotifyIME(const IMENotification& aIMENotification); bool NotifyIME(const IMENotification& aIMENotification);
@ -1976,6 +1983,10 @@ nsWindow::Natives::AddIMETextChange(const IMETextChange& aChange)
{ {
mIMETextChanges.AppendElement(aChange); mIMETextChanges.AppendElement(aChange);
// We may not be in the middle of flushing,
// in which case this flag is meaningless.
mIMETextChangedDuringFlush = true;
// Now that we added a new range we need to go back and // Now that we added a new range we need to go back and
// update all the ranges before that. // update all the ranges before that.
// Ranges that have offsets which follow this new range // Ranges that have offsets which follow this new range
@ -2034,9 +2045,10 @@ nsWindow::Natives::AddIMETextChange(const IMETextChange& aChange)
} }
void void
nsWindow::Natives::PostFlushIMEChanges() nsWindow::Natives::PostFlushIMEChanges(FlushChangesFlag aFlags)
{ {
if (!mIMETextChanges.IsEmpty() || mIMESelectionChanged) { if (aFlags != FLUSH_FLAG_RETRY &&
(!mIMETextChanges.IsEmpty() || mIMESelectionChanged)) {
// Already posted // Already posted
return; return;
} }
@ -2044,15 +2056,15 @@ nsWindow::Natives::PostFlushIMEChanges()
// Keep a strong reference to the window to keep 'this' alive. // Keep a strong reference to the window to keep 'this' alive.
RefPtr<nsWindow> window(&this->window); RefPtr<nsWindow> window(&this->window);
nsAppShell::gAppShell->PostEvent([this, window] { nsAppShell::gAppShell->PostEvent([this, window, aFlags] {
if (!window->Destroyed()) { if (!window->Destroyed()) {
FlushIMEChanges(); FlushIMEChanges(aFlags);
} }
}); });
} }
void void
nsWindow::Natives::FlushIMEChanges() nsWindow::Natives::FlushIMEChanges(FlushChangesFlag aFlags)
{ {
// Only send change notifications if we are *not* masking events, // Only send change notifications if we are *not* masking events,
// i.e. if we have a focused editor, // i.e. if we have a focused editor,
@ -2068,9 +2080,20 @@ nsWindow::Natives::FlushIMEChanges()
RefPtr<nsWindow> kungFuDeathGrip(&window); RefPtr<nsWindow> kungFuDeathGrip(&window);
window.UserActivity(); window.UserActivity();
for (uint32_t i = 0; i < mIMETextChanges.Length(); i++) { struct TextRecord {
IMETextChange &change = mIMETextChanges[i]; nsString text;
int32_t start;
int32_t oldEnd;
int32_t newEnd;
};
nsAutoTArray<TextRecord, 4> textTransaction;
if (mIMETextChanges.Length() > textTransaction.Capacity()) {
textTransaction.SetCapacity(mIMETextChanges.Length());
}
mIMETextChangedDuringFlush = false;
for (const IMETextChange &change : mIMETextChanges) {
if (change.mStart == change.mOldEnd && if (change.mStart == change.mOldEnd &&
change.mStart == change.mNewEnd) { change.mStart == change.mNewEnd) {
continue; continue;
@ -2087,12 +2110,32 @@ nsWindow::Natives::FlushIMEChanges()
NS_ENSURE_TRUE_VOID(event.mReply.mContentsRoot == imeRoot.get()); NS_ENSURE_TRUE_VOID(event.mReply.mContentsRoot == imeRoot.get());
} }
mEditable->OnTextChange(event.mReply.mString, change.mStart, if (mIMETextChangedDuringFlush) {
change.mOldEnd, change.mNewEnd); // The query event above could have triggered more text changes to
// come in, as indicated by our flag. If that happens, try flushing
// IME changes again later.
if (!NS_WARN_IF(aFlags == FLUSH_FLAG_RETRY)) {
// Don't retry if already retrying, to avoid infinite loops.
PostFlushIMEChanges(FLUSH_FLAG_RETRY);
}
return;
}
textTransaction.AppendElement(
TextRecord{event.mReply.mString, change.mStart,
change.mOldEnd, change.mNewEnd});
} }
mIMETextChanges.Clear(); mIMETextChanges.Clear();
for (const TextRecord& record : textTransaction) {
mEditable->OnTextChange(record.text, record.start,
record.oldEnd, record.newEnd);
}
if (mIMESelectionChanged) { if (mIMESelectionChanged) {
mIMESelectionChanged = false;
WidgetQueryContentEvent event(true, eQuerySelectedText, &window); WidgetQueryContentEvent event(true, eQuerySelectedText, &window);
window.InitEvent(event, nullptr); window.InitEvent(event, nullptr);
window.DispatchEvent(&event); window.DispatchEvent(&event);
@ -2102,7 +2145,6 @@ nsWindow::Natives::FlushIMEChanges()
mEditable->OnSelectionChange(int32_t(event.GetSelectionStart()), mEditable->OnSelectionChange(int32_t(event.GetSelectionStart()),
int32_t(event.GetSelectionEnd())); int32_t(event.GetSelectionEnd()));
mIMESelectionChanged = false;
} }
} }