mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 15:23:51 +00:00
Bug 1462257 - TextComposition should dispatch eCompositionChange event when eCompositionCommit is being fired immediately after eCompositionStart r=m_kato
TextEditor modifies composition string or selected string when first eCompositionChange event is received. However, TextComposition dispatches eCompositionChange event ("text" event of DOM) only when composition string becomes non-empty if current composition string is empty. So, when IME dispatches only eCompositionStart and eCompositionCommit events for removing selected text, TextEditor does nothing. This hacky behavior is used by MS Pinyin on Windows 10 at least. For supporting this behavior, we need to make TextComposition dispatch eCompositionChange event when eCompositionChange(AsIs) event is fired even before dispatching eCompositionChange event. Although from point of view of web apps, the hacky composition should be merged into the previous composition if it's possible but it's out of scope of this bug. MozReview-Commit-ID: 7QfeBJamGTU --HG-- extra : rebase_source : 8de1353021f2961ae9f8bdf17ddded1058175339
This commit is contained in:
parent
e5a1c32097
commit
b98777fe41
@ -67,6 +67,7 @@ TextComposition::TextComposition(nsPresContext* aPresContext,
|
||||
, mIsRequestingCommit(false)
|
||||
, mIsRequestingCancel(false)
|
||||
, mRequestedToCommitOrCancel(false)
|
||||
, mHasDispatchedDOMTextEvent(false)
|
||||
, mHasReceivedCommitEvent(false)
|
||||
, mWasNativeCompositionEndEventDiscarded(false)
|
||||
, mAllowControlCharacters(
|
||||
@ -108,6 +109,13 @@ TextComposition::MaybeDispatchCompositionUpdate(
|
||||
return false;
|
||||
}
|
||||
|
||||
// Note that we don't need to dispatch eCompositionUpdate event even if
|
||||
// mHasDispatchedDOMTextEvent is false and eCompositionCommit event is
|
||||
// dispatched with empty string immediately after eCompositionStart
|
||||
// because composition string has never been changed from empty string to
|
||||
// non-empty string in such composition even if selected string was not
|
||||
// empty string (mLastData isn't set to selected text when this receives
|
||||
// eCompositionStart).
|
||||
if (mLastData == aCompositionEvent->mData) {
|
||||
return true;
|
||||
}
|
||||
@ -356,10 +364,15 @@ TextComposition::DispatchCompositionEvent(
|
||||
// When mIsComposing is false but the committing string is different from
|
||||
// the last data (E.g., previous eCompositionChange event made the
|
||||
// composition string empty or didn't have clause information), we don't
|
||||
// need to dispatch redundant DOM text event.
|
||||
// need to dispatch redundant DOM text event. (But note that we need to
|
||||
// dispatch eCompositionChange event if we have not dispatched
|
||||
// eCompositionChange event yet and commit string replaces selected string
|
||||
// with empty string since selected string hasn't been replaced with empty
|
||||
// string yet.)
|
||||
if (dispatchDOMTextEvent &&
|
||||
aCompositionEvent->mMessage != eCompositionChange &&
|
||||
!mIsComposing && mLastData == aCompositionEvent->mData) {
|
||||
!mIsComposing && mHasDispatchedDOMTextEvent &&
|
||||
mLastData == aCompositionEvent->mData) {
|
||||
dispatchEvent = dispatchDOMTextEvent = false;
|
||||
}
|
||||
|
||||
@ -387,10 +400,14 @@ TextComposition::DispatchCompositionEvent(
|
||||
// we cannot map multiple event messages to a DOM event type.
|
||||
if (dispatchDOMTextEvent &&
|
||||
aCompositionEvent->mMessage != eCompositionChange) {
|
||||
mHasDispatchedDOMTextEvent = true;
|
||||
aCompositionEvent->mFlags =
|
||||
CloneAndDispatchAs(aCompositionEvent, eCompositionChange,
|
||||
aStatus, aCallBack);
|
||||
} else {
|
||||
if (aCompositionEvent->mMessage == eCompositionChange) {
|
||||
mHasDispatchedDOMTextEvent = true;
|
||||
}
|
||||
DispatchEvent(aCompositionEvent, aStatus, aCallBack);
|
||||
}
|
||||
} else {
|
||||
|
@ -369,6 +369,9 @@ private:
|
||||
// mIsRequestingCancel are set to false.
|
||||
bool mRequestedToCommitOrCancel;
|
||||
|
||||
// Set to true if the instance dispatches an eCompositionChange event.
|
||||
bool mHasDispatchedDOMTextEvent;
|
||||
|
||||
// Before this dispatches commit event into the tree, this is set to true.
|
||||
// So, this means if native IME already commits the composition.
|
||||
bool mHasReceivedCommitEvent;
|
||||
|
@ -51,6 +51,15 @@ SimpleTest.waitForFocus(()=>{
|
||||
|
||||
clear();
|
||||
|
||||
// FYI: Chrome commits composition if blur() and focus() are called during
|
||||
// composition. But note that if they are called by compositionupdate
|
||||
// listener, the behavior is unstable. On Windows, composition is
|
||||
// canceled. On Linux and macOS, the composition is committed
|
||||
// internally but the string keeps underlined. If they are called
|
||||
// by input event listener, committed on any platforms though.
|
||||
// On the other hand, Edge and Safari keeps composition even with
|
||||
// calling both blur() and focus().
|
||||
|
||||
// Committing at compositionstart
|
||||
aEditor.focus();
|
||||
aEditor.addEventListener("compositionstart", committer, true);
|
||||
@ -68,7 +77,7 @@ SimpleTest.waitForFocus(()=>{
|
||||
caret: { start: 1, length: 0 }, key: { key: "a" }});
|
||||
aEditor.removeEventListener("compositionupdate", committer, true);
|
||||
ok(!isComposing(), "composition in " + aEditor.id + " should be committed by compositionupdate event handler");
|
||||
is(value(), "", "composition in " + aEditor.id + " shouldn't have inserted any text since it's committed at first compositionupdate");
|
||||
is(value(), "a", "composition in " + aEditor.id + " should have \"a\" since IME committed with it");
|
||||
clear();
|
||||
|
||||
// Committing at first text (eCompositionChange)
|
||||
@ -93,7 +102,7 @@ SimpleTest.waitForFocus(()=>{
|
||||
caret: { start: 2, length: 0 }, key: { key: "b" }});
|
||||
aEditor.removeEventListener("compositionupdate", committer, true);
|
||||
ok(!isComposing(), "composition in " + aEditor.id + " should be committed by compositionupdate event handler");
|
||||
todo_is(value(), "a", "composition in " + aEditor.id + " shouldn't have been modified since it's committed at second compositionupdate");
|
||||
is(value(), "ab", "composition in " + aEditor.id + " should have \"ab\" since IME committed with it");
|
||||
clear();
|
||||
|
||||
// Committing at second text (eCompositionChange)
|
||||
@ -108,7 +117,7 @@ SimpleTest.waitForFocus(()=>{
|
||||
caret: { start: 2, length: 0 }, key: { key: "b" }});
|
||||
aEditor.removeEventListener("text", committer, true);
|
||||
ok(!isComposing(), "composition in " + aEditor.id + " should be committed by text event handler");
|
||||
todo_is(value(), "a", "composition in " + aEditor.id + " shouldn't have been modified since it's committed at second text");
|
||||
is(value(), "ab", "composition in " + aEditor.id + " should have \"ab\" since IME committed with it");
|
||||
clear();
|
||||
}
|
||||
runTest(document.getElementById("input"));
|
||||
|
@ -884,6 +884,63 @@ function runCompositionCommitTest()
|
||||
is(result.input, true, "runCompositionCommitTest: input should be fired after dispatching compositioncommit #3");
|
||||
is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #3");
|
||||
|
||||
// inserting empty string with simple composition.
|
||||
textarea.value = "abc";
|
||||
textarea.setSelectionRange(3, 3);
|
||||
synthesizeComposition({ type: "compositionstart" });
|
||||
|
||||
clearResult();
|
||||
synthesizeComposition({ type: "compositioncommit", data: "" });
|
||||
|
||||
is(result.compositionupdate, false,
|
||||
"runCompositionCommitTest: compositionupdate should not be fired when interting empty string with composition");
|
||||
is(result.compositionend, true,
|
||||
"runCompositionCommitTest: compositionend should be fired when interting empty string with composition");
|
||||
is(result.text, true,
|
||||
"runCompositionCommitTest: text should be fired when interting empty string with composition");
|
||||
is(result.input, true,
|
||||
"runCompositionCommitTest: input should be fired when interting empty string with composition");
|
||||
is(textarea.value, "abc",
|
||||
"runCompositionCommitTest: textarea should keep original value when interting empty string with composition");
|
||||
|
||||
// replacing selection with empty string with simple composition.
|
||||
textarea.value = "abc";
|
||||
textarea.setSelectionRange(0, 3);
|
||||
synthesizeComposition({ type: "compositionstart" });
|
||||
|
||||
clearResult();
|
||||
synthesizeComposition({ type: "compositioncommit", data: "" });
|
||||
|
||||
is(result.compositionupdate, false,
|
||||
"runCompositionCommitTest: compositionupdate should not be fired when replacing selection with empty string with composition");
|
||||
is(result.compositionend, true,
|
||||
"runCompositionCommitTest: compositionend should be fired when replacing selection with empty string with composition");
|
||||
is(result.text, true,
|
||||
"runCompositionCommitTest: text should be fired when replacing selection with empty string with composition");
|
||||
is(result.input, true,
|
||||
"runCompositionCommitTest: input should be fired when replacing selection with empty string with composition");
|
||||
is(textarea.value, "",
|
||||
"runCompositionCommitTest: textarea should become empty when replacing selection with empty string with composition");
|
||||
|
||||
// replacing selection with same string with simple composition.
|
||||
textarea.value = "abc";
|
||||
textarea.setSelectionRange(0, 3);
|
||||
synthesizeComposition({ type: "compositionstart" });
|
||||
|
||||
clearResult();
|
||||
synthesizeComposition({ type: "compositioncommit", data: "abc" });
|
||||
|
||||
is(result.compositionupdate, true,
|
||||
"runCompositionCommitTest: compositionupdate should be fired when replacing selection with same string with composition");
|
||||
is(result.compositionend, true,
|
||||
"runCompositionCommitTest: compositionend should be fired when replacing selection with same string with composition");
|
||||
is(result.text, true,
|
||||
"runCompositionCommitTest: text should be fired when replacing selection with same string with composition");
|
||||
is(result.input, true,
|
||||
"runCompositionCommitTest: input should be fired when replacing selection with same string with composition");
|
||||
is(textarea.value, "abc",
|
||||
"runCompositionCommitTest: textarea should keep same value when replacing selection with same string with composition");
|
||||
|
||||
// compositioncommit with non-empty composition string.
|
||||
textarea.value = "";
|
||||
synthesizeCompositionChange(
|
||||
@ -6421,13 +6478,15 @@ function runRemoveContentTest()
|
||||
|
||||
hitEventLoop(function () {
|
||||
// XXX Currently, "input" event isn't fired on removed content.
|
||||
is(events.length, 1,
|
||||
is(events.length, 2,
|
||||
"runRemoveContentTest: wrong event count #2");
|
||||
is(events[0].type, "compositionend",
|
||||
is(events[0].type, "text",
|
||||
"runRemoveContentTest: the 1st event must be text #2");
|
||||
is(events[1].type, "compositionend",
|
||||
"runRemoveContentTest: the 1st event must be compositionend #2");
|
||||
is(events[0].data, "",
|
||||
is(events[1].data, "",
|
||||
"runRemoveContentTest: compositionupdate has wrong data #2");
|
||||
is(events[0].target, textarea,
|
||||
is(events[1].target, textarea,
|
||||
"runRemoveContentTest: The 1st event was fired on wrong event target #2");
|
||||
ok(!getEditor(textarea).isComposing,
|
||||
"runRemoveContentTest: the textarea still has composition #2");
|
||||
|
Loading…
Reference in New Issue
Block a user