Bug 1125934 - Discard redundant NS_COMPOSITION_CHANGE event which is send just before NS_COMPOSITION_END on TSF. r=masayuki

This commit is contained in:
Tooru Fujisawa 2015-02-11 12:20:02 +09:00
parent d8f563fcbd
commit 6558751505
3 changed files with 210 additions and 3 deletions

View File

@ -224,6 +224,17 @@ TextComposition::DispatchCompositionEvent(
dispatchEvent = dispatchDOMTextEvent = false;
}
// widget may dispatch redundant NS_COMPOSITION_CHANGE event
// which modifies neither composition string, clauses nor caret
// position. In such case, we shouldn't dispatch DOM events.
if (dispatchDOMTextEvent &&
aCompositionEvent->message == NS_COMPOSITION_CHANGE &&
mLastData == aCompositionEvent->mData &&
mRanges && aCompositionEvent->mRanges &&
mRanges->Equals(*aCompositionEvent->mRanges)) {
dispatchEvent = dispatchDOMTextEvent = false;
}
if (dispatchDOMTextEvent) {
if (!MaybeDispatchCompositionUpdate(aCompositionEvent)) {
return;

View File

@ -84,7 +84,7 @@ struct TextRangeStyle
IsLineStyleDefined() && mLineStyle == LINESTYLE_NONE;
}
bool Equals(const TextRangeStyle& aOther)
bool Equals(const TextRangeStyle& aOther) const
{
if (mDefinedStyles != aOther.mDefinedStyles)
return false;
@ -103,12 +103,12 @@ struct TextRangeStyle
return true;
}
bool operator !=(const TextRangeStyle &aOther)
bool operator !=(const TextRangeStyle &aOther) const
{
return !Equals(aOther);
}
bool operator ==(const TextRangeStyle &aOther)
bool operator ==(const TextRangeStyle &aOther) const
{
return Equals(aOther);
}
@ -166,6 +166,14 @@ struct TextRange
"Invalid range type");
return mRangeType != NS_TEXTRANGE_CARETPOSITION;
}
bool Equals(const TextRange& aOther) const
{
return mStartOffset == aOther.mStartOffset &&
mEndOffset == aOther.mEndOffset &&
mRangeType == aOther.mRangeType &&
mRangeStyle == aOther.mRangeStyle;
}
};
/******************************************************************************
@ -201,6 +209,20 @@ public:
}
return 0;
}
bool Equals(const TextRangeArray& aOther) const
{
size_t len = Length();
if (len != aOther.Length()) {
return false;
}
for (size_t i = 0; i < len; i++) {
if (!ElementAt(i).Equals(aOther.ElementAt(i))) {
return false;
}
}
return true;
}
};
} // namespace mozilla

View File

@ -2978,6 +2978,178 @@ function runIsComposingTest()
textarea.value = "";
}
function runRedundantChangeTest()
{
textarea.focus();
var result = {};
function clearResult()
{
result = { compositionupdate: false, compositionend: false, text: false, input: false };
}
function handler(aEvent)
{
result[aEvent.type] = true;
}
textarea.addEventListener("compositionupdate", handler, true);
textarea.addEventListener("compositionend", handler, true);
textarea.addEventListener("input", handler, true);
textarea.addEventListener("text", handler, true);
textarea.value = "";
// synthesize change event
clearResult();
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3042",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 }
});
is(result.compositionupdate, true, "runRedundantChangeTest: compositionupdate should be fired after synthesizing composition change #1");
is(result.compositionend, false, "runRedundantChangeTest: compositionend shouldn't be fired after synthesizing composition change #1");
is(result.text, true, "runRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string #1");
is(result.input, true, "runRedundantChangeTest: input should be fired after synthesizing composition change #1");
is(textarea.value, "\u3042", "runRedundantChangeTest: textarea has uncommitted string #1");
// synthesize another change event
clearResult();
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3042\u3044",
"clauses":
[
{ "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
]
},
"caret": { "start": 2, "length": 0 }
});
is(result.compositionupdate, true, "runRedundantChangeTest: compositionupdate should be fired after synthesizing composition change #2");
is(result.compositionend, false, "runRedundantChangeTest: compositionend shouldn't be fired after synthesizing composition change #2");
is(result.text, true, "runRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string #2");
is(result.input, true, "runRedundantChangeTest: input should be fired after synthesizing composition change #2");
is(textarea.value, "\u3042\u3044", "runRedundantChangeTest: textarea has uncommitted string #2");
// synthesize same change event again
clearResult();
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3042\u3044",
"clauses":
[
{ "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
]
},
"caret": { "start": 2, "length": 0 }
});
is(result.compositionupdate, false, "runRedundantChangeTest: compositionupdate shouldn't be fired after synthesizing composition change again");
is(result.compositionend, false, "runRedundantChangeTest: compositionend shouldn't be fired after synthesizing composition change again");
is(result.text, false, "runRedundantChangeTest: text shouldn't be fired after synthesizing composition change again because it's dispatched when there is composing string");
is(result.input, false, "runRedundantChangeTest: input shouldn't be fired after synthesizing composition change again");
is(textarea.value, "\u3042\u3044", "runRedundantChangeTest: textarea has uncommitted string #3");
synthesizeComposition({ type: "compositioncommit" });
textarea.removeEventListener("compositionupdate", handler, true);
textarea.removeEventListener("compositionend", handler, true);
textarea.removeEventListener("input", handler, true);
textarea.removeEventListener("text", handler, true);
}
function runNotRedundantChangeTest()
{
textarea.focus();
var result = {};
function clearResult()
{
result = { compositionupdate: false, compositionend: false, text: false, input: false };
}
function handler(aEvent)
{
result[aEvent.type] = true;
}
textarea.addEventListener("compositionupdate", handler, true);
textarea.addEventListener("compositionend", handler, true);
textarea.addEventListener("input", handler, true);
textarea.addEventListener("text", handler, true);
textarea.value = "abcde";
// synthesize change event with non-null ranges
clearResult();
synthesizeCompositionChange(
{ "composition":
{ "string": "ABCDE",
"clauses":
[
{ "length": 5, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
]
},
"caret": { "start": 5, "length": 0 }
});
is(result.compositionupdate, true, "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with non-null ranges");
is(result.compositionend, false, "runNotRedundantChangeTest: compositionend shouldn't be fired after synthesizing composition change with non-null ranges");
is(result.text, true, "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with non-null ranges");
is(result.input, true, "runNotRedundantChangeTest: input should be fired after synthesizing composition change with non-null ranges");
is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #1");
// synthesize change event with null ranges
clearResult();
synthesizeCompositionChange(
{ "composition":
{ "string": "",
"clauses":
[
{ "length": 0, "attr": 0 }
]
},
});
is(result.compositionupdate, true, "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with null ranges after non-null ranges");
is(result.compositionend, false, "runNotRedundantChangeTest: compositionend shouldn't be fired after synthesizing composition change with null ranges after non-null ranges");
is(result.text, true, "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with null ranges after non-null ranges");
is(result.input, false, "runNotRedundantChangeTest: input shouldn't be fired after synthesizing composition change with null ranges after non-null ranges");
is(textarea.value, "abcde", "runNotRedundantChangeTest: textarea doesn't have uncommitted string");
// synthesize change event with non-null ranges
clearResult();
synthesizeCompositionChange(
{ "composition":
{ "string": "ABCDE",
"clauses":
[
{ "length": 5, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
]
},
"caret": { "start": 5, "length": 0 }
});
is(result.compositionupdate, true, "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with non-null ranges after null ranges");
is(result.compositionend, false, "runNotRedundantChangeTest: compositionend shouldn't be fired after synthesizing composition change with non-null ranges after null ranges");
is(result.text, true, "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with non-null ranges after null ranges");
is(result.input, true, "runNotRedundantChangeTest: input should be fired after synthesizing composition change with non-null ranges after null ranges");
is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #2");
synthesizeComposition({ type: "compositioncommit", data: "" });
textarea.removeEventListener("compositionupdate", handler, true);
textarea.removeEventListener("compositionend", handler, true);
textarea.removeEventListener("input", handler, true);
textarea.removeEventListener("text", handler, true);
}
function runRemoveContentTest(aCallback)
{
var events = [];
@ -3550,6 +3722,8 @@ function runTest()
runForceCommitTest();
runBug811755Test();
runIsComposingTest();
runRedundantChangeTest();
runNotRedundantChangeTest();
runAsyncForceCommitTest(function () {
runRemoveContentTest(function () {
runFrameTest();