Bug 1904192 - Make TextControlState::SetValue suppress TextEditor to dispatch input events even after dispatching input event during composition r=m_kato,geckoview-reviewers

`input` event listeners may be async functions and may set the text control
element value after the editor ends dispatching an `input` event.  Even in this
case, the `input` event listener should not be called recursively by committing
composition which is caused by setting the value because the value is
overwritten by the setting value which was intended by the web app.

Unfortunately, we cannot check whether the value setter is an async `input`
event listener.  Therefore, we need to suppress `input` events even after the
editor ends dispatching `input` event.

On the other hand, we should not suppress `input` event if the value is set
by a `compositionupdate` event listener which runs before the editor starts
handling the latest composition change because once we do that, the app won't
receive `input` event for the composition.  Therefore, we should not suppress
the editor starts handling the composition change (including during
`beforeinput` event dispatching, but the `beforeinput` is not cancelable, so,
the result will be odd, therefore, we have no tests for the cases).

Therefore, this patch adds a new method to `TextComposition` to make
`TextControlState::SetValue` can check whether the editor is handling the
latest composition change or after that.  So, during composition, setting
value should cause `input` events between `TextComposition` starts dispatching a
`compositionupdate` event and `EditorBase` starts handling `eCompositionChange`
event which is dispatched after `compositionupdate`.

Differential Revision: https://phabricator.services.mozilla.com/D214676
This commit is contained in:
Masayuki Nakano
2024-06-25 03:52:54 +00:00
parent 1ab75bd748
commit 7310f1cf4d
9 changed files with 192 additions and 144 deletions

View File

@@ -568,7 +568,7 @@ bool IMEContentObserver::IsEditorHandlingEventForComposition() const {
if (!composition) {
return false;
}
return composition->IsEditorHandlingEvent();
return composition->EditorIsHandlingLatestChange();
}
bool IMEContentObserver::IsEditorComposing() const {

View File

@@ -4,13 +4,11 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "TextComposition.h"
#include "ContentEventHandler.h"
#include "IMEContentObserver.h"
#include "IMEStateManager.h"
#include "nsContentUtils.h"
#include "nsIContent.h"
#include "nsIMutationObserver.h"
#include "nsPresContext.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/EditorBase.h"
#include "mozilla/EventDispatcher.h"
@@ -21,10 +19,13 @@
#include "mozilla/RangeBoundary.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_intl.h"
#include "mozilla/TextComposition.h"
#include "mozilla/TextEvents.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/BrowserParent.h"
#include "nsContentUtils.h"
#include "nsIContent.h"
#include "nsIMutationObserver.h"
#include "nsPresContext.h"
#ifdef XP_MACOSX
// Some defiens will be conflict with OSX SDK
@@ -85,7 +86,6 @@ TextComposition::TextComposition(nsPresContext* aPresContext, nsINode* aNode,
mCompositionLengthInTextNode(UINT32_MAX),
mIsSynthesizedForTests(aCompositionEvent->mFlags.mIsSynthesizedForTests),
mIsComposing(false),
mIsEditorHandlingEvent(false),
mIsRequestingCommit(false),
mIsRequestingCancel(false),
mRequestedToCommitOrCancel(false),
@@ -207,6 +207,9 @@ bool TextComposition::MaybeDispatchCompositionUpdate(
// empty string (mLastData isn't set to selected text when this receives
// eCompositionStart).
if (mLastData == aCompositionEvent->mData) {
// Even if the new composition event does not update the composition string,
// it may change IME selection.
mLastRanges = aCompositionEvent->mRanges;
return true;
}
CloneAndDispatchAs(aCompositionEvent, eCompositionUpdate);
@@ -726,7 +729,7 @@ void TextComposition::EditorWillHandleCompositionChangeEvent(
const WidgetCompositionEvent* aCompositionChangeEvent) {
mIsComposing = aCompositionChangeEvent->IsComposing();
mRanges = aCompositionChangeEvent->mRanges;
mIsEditorHandlingEvent = true;
mEditorIsHandlingEvent = true;
MOZ_ASSERT(
mLastData == aCompositionChangeEvent->mData,
@@ -737,7 +740,7 @@ void TextComposition::EditorWillHandleCompositionChangeEvent(
void TextComposition::OnEditorDestroyed() {
MOZ_RELEASE_ASSERT(!mBrowserParent);
MOZ_ASSERT(!mIsEditorHandlingEvent,
MOZ_ASSERT(!mEditorIsHandlingEvent,
"The editor should have stopped listening events");
nsCOMPtr<nsIWidget> widget = GetWidget();
if (NS_WARN_IF(!widget)) {
@@ -751,7 +754,7 @@ void TextComposition::OnEditorDestroyed() {
void TextComposition::EditorDidHandleCompositionChangeEvent() {
mString = mLastData;
mIsEditorHandlingEvent = false;
mEditorIsHandlingEvent = false;
}
void TextComposition::StartHandlingComposition(EditorBase* aEditorBase) {

View File

@@ -14,6 +14,7 @@
#include "nsThreadUtils.h"
#include "nsPresContext.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/EventForwards.h"
#include "mozilla/RangeBoundary.h"
@@ -188,10 +189,21 @@ class TextComposition final {
bool IsComposing() const { return mIsComposing; }
/**
* Returns true while editor is handling an event which is modifying the
* composition string.
* Returns true if editor has started or already ended handling an event which
* is modifying the composition string and/or IME selections.
*/
bool IsEditorHandlingEvent() const { return mIsEditorHandlingEvent; }
[[nodiscard]] bool EditorHasHandledLatestChange() const {
return EditorIsHandlingLatestChange() ||
(mLastRanges == mRanges && mLastData == mString);
}
/**
* Returns true while editor is handling an event which is modifying the
* composition string and/or IME selections.
*/
[[nodiscard]] bool EditorIsHandlingLatestChange() const {
return mEditorIsHandlingEvent;
}
/**
* IsMovingToNewTextNode() returns true if editor detects the text node
@@ -309,8 +321,10 @@ class TextComposition final {
// This is the clause and caret range information which is managed by
// the focused editor. This may be null if there is no clauses or caret.
RefPtr<TextRangeArray> mRanges;
// Same as mRange, but mRange will have old data during compositionupdate.
// So this will be valied during compositionupdate.
// Same as mRange, but mRange will have old ranges before editor starts
// handling the latest eCompositionChange. Therefore, this stores the latest
// ranges which is introduced by the latest eCompositionChange. So this may
// be useful during dispatching eCompositionUpdate or eCompositionChange.
RefPtr<TextRangeArray> mLastRanges;
// mNativeContext stores a opaque pointer. This works as the "ID" for this
@@ -358,9 +372,9 @@ class TextComposition final {
// See the comment for IsComposing().
bool mIsComposing;
// mIsEditorHandlingEvent is true while editor is modifying the composition
// mEditorIsHandlingEvent is true while editor is modifying the composition
// string.
bool mIsEditorHandlingEvent;
bool mEditorIsHandlingEvent = false;
// mIsRequestingCommit or mIsRequestingCancel is true *only* while we're
// requesting commit or canceling the composition. In other words, while
@@ -564,7 +578,7 @@ class TextComposition final {
CompositionEventDispatcher()
: Runnable("TextComposition::CompositionEventDispatcher"),
mEventMessage(eVoidEvent),
mIsSynthesizedEvent(false){};
mIsSynthesizedEvent(false) {};
};
/**

View File

@@ -9,6 +9,7 @@
#include "mozilla/CaretAssociationHint.h"
#include "mozilla/IMEContentObserver.h"
#include "mozilla/IMEStateManager.h"
#include "mozilla/TextComposition.h"
#include "mozilla/TextInputListener.h"
#include "nsCOMPtr.h"
@@ -2640,7 +2641,9 @@ bool TextControlState::SetValue(const nsAString& aValue,
// bug must not be reproducible actually.
if (aOptions.contains(ValueSetterOption::BySetUserInputAPI) ||
aOptions.contains(ValueSetterOption::ByContentAPI)) {
if (EditorHasComposition()) {
RefPtr<TextComposition> compositionInEditor =
mTextEditor ? mTextEditor->GetComposition() : nullptr;
if (compositionInEditor && compositionInEditor->IsComposing()) {
// When this is called recursively, there shouldn't be composition.
if (handlingSetValue.IsHandling(TextControlAction::CommitComposition)) {
// Don't request to commit composition again. But if it occurs,
@@ -2679,13 +2682,27 @@ bool TextControlState::SetValue(const nsAString& aValue,
// dispatches nested `beforeinput`/`input` events if this is called by a
// `beforeinput`/`input` event listener since the commit value will be
// completely overwritten by the new value soon and the web app do not
// need to handle the committing composition which is caused by updating
// the value.
// Note that `compositionupdate` and `compositionend` will be fired.
// Therefore, everything could occur during a the following call.
// I.e., the document may be unloaded by the web app itself.
// need to handle the temporary input caused by committing composition
// which is caused by updating the value by the web app itself Note
// that `input` event listener may be async function and setting value
// may occur after the editor ends dispatching `input` event. Even in
// this case, to avoid nest call of the async `input` event listener, we
// need to suppress `input` events caused by committing composition. On
// the other hand, we need to dispatch `input` event when the value is
// set by a `compositionupdate` event listener because once we suppress
// `input` event for it, the composition change won't cause dispatching
// `input` event. Therefore, we should not suppress `input` events
// before the editor starts handling the composition change, but we need
// to suppress `input` events even after the editor ends handling the
// change.
// FYI: Even if we suppress `input` event dispatching,
// `compositionupdate` and `compositionend` caused by the committing
// composition will be fired. Therefore, everything could occur during
// a the following call. I.e., the document may be unloaded by the web
// app itself.
Maybe<AutoInputEventSuppresser> preventInputEventsDuringCommit;
if (mTextEditor->IsDispatchingInputEvent()) {
if (mTextEditor->IsDispatchingInputEvent() ||
compositionInEditor->EditorHasHandledLatestChange()) {
preventInputEventsDuringCommit.emplace(mTextEditor);
}
OwningNonNull<TextEditor> textEditor(*mTextEditor);

View File

@@ -627,4 +627,6 @@ skip-if = ["os == 'android'"]
["test_undo_with_editingui.html"]
["test_updating_input_value_during_composition.html"]
["test_updating_input_value_during_composition_from_async_input.html"]
["test_updating_input_value_during_composition_from_input.html"]

View File

@@ -0,0 +1,75 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script>
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(async () => {
const input = document.querySelector("input");
input.focus();
const promiseInputEventListener = new Promise(resolve => {
let lastValue = input.value;
input.addEventListener("input", async () => {
let selStart = input.selectionStart || 0;
const formattedValue = (() => {
const rawNumber = input.value.replace(/-/g, "");
const tel1 = rawNumber.substring(0, 3);
const tel2 = rawNumber.substring(3, 7);
const tel3 = rawNumber.substring(7, 11);
return [tel1, tel2, tel3].filter((t => Boolean(t))).join("-");
})();
let compositionEnded = false;
input.addEventListener("compositionend", () => {
ok(
!document.execCommand("insertText", false, "5"),
"execCommand call from compositionend listener during setting value should be ignored"
);
compositionEnded = true;
}, {once: true});
await new Promise(r => requestAnimationFrame(r));
input.value = formattedValue;
// compositionend event should be fired to notify web app of ending the composition.
ok(compositionEnded, "Setting value during composition should cause a compositionend event");
const prevChar = formattedValue[selStart - 1];
if (formattedValue.length > lastValue.length && prevChar === "-") {
selStart++;
}
input.setSelectionRange(selStart, selStart);
lastValue = formattedValue;
resolve();
});
});
synthesizeCompositionChange({
composition: {
string: "1234",
clauses: [
{length: 4, attr: COMPOSITION_ATTR_RAW_CLAUSE },
]
},
caret: {
start: 4,
length: 0,
},
key: {
key: "4",
}
});
await promiseInputEventListener;
is(input.value, "123-4", "text should be written in the phone number format");
is(input.selectionStart, 5, "selection should be collapsed at end of the formatted value");
SimpleTest.finish();
});
</script>
</head>
<body>
<input>
</body>
</html>

View File

@@ -890,7 +890,7 @@ bool GeckoEditableSupport::DoReplaceText(int32_t aStart, int32_t aEnd,
NS_ENSURE_SUCCESS(BeginInputTransaction(mDispatcher), false);
RefPtr<TextComposition> composition(GetComposition());
MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
MOZ_ASSERT(!composition || !composition->EditorIsHandlingLatestChange());
nsString string(aText->ToString());
const bool composing = !mIMERanges->IsEmpty();
@@ -1134,7 +1134,7 @@ bool GeckoEditableSupport::DoUpdateComposition(int32_t aStart, int32_t aEnd,
*/
nsString string;
RefPtr<TextComposition> composition(GetComposition());
MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
MOZ_ASSERT(!composition || !composition->EditorIsHandlingLatestChange());
if (!composition || !mDispatcher->IsComposing() ||
uint32_t(aStart) != composition->NativeOffsetOfStartComposition() ||

View File

@@ -7583,30 +7583,21 @@ function runForceCommitTest()
events = [];
input.value = "set value";
is(events.length, 4,
// In this case, the result of committing composition won't appear since
// the new value overwrites it. Therefore, neither `beforeinput` nor `input`
// event is fired.
is(events.length, 2,
"runForceCommitTest: wrong event count #12");
is(events[0].type, "text",
"runForceCommitTest: the 1st event must be text #12");
is(events[0].target, input,
`runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #12`);
is(events[1].type, "beforeinput",
"runForceCommitTest: the 2nd event must be beforeinput #12");
is(events[1].type, "compositionend",
"runForceCommitTest: the 2nd event must be compositionend #12");
is(events[1].target, input,
`runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #12`);
checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
"runForceCommitTest #12");
is(events[2].type, "compositionend",
"runForceCommitTest: the 3rd event must be compositionend #12");
is(events[2].target, input,
`runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #12`);
is(events[2].data, "\u306E",
is(events[1].data, "\u306E",
"runForceCommitTest: compositionend has wrong data #12");
is(events[3].type, "input",
"runForceCommitTest: the 4th event must be input #12");
is(events[3].target, input,
`runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #12`);
checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
"runForceCommitTest #12");
ok(!getEditor(input).isComposing,
"runForceCommitTest: the input still has composition #12");
is(input.value, "set value",
@@ -7629,30 +7620,21 @@ function runForceCommitTest()
events = [];
textarea.value = "set value";
is(events.length, 4,
// In this case, the result of committing composition won't appear since
// the new value overwrites it. Therefore, neither `beforeinput` nor `input`
// event is fired.
is(events.length, 2,
"runForceCommitTest: wrong event count #13");
is(events[0].type, "text",
"runForceCommitTest: the 1st event must be text #13");
is(events[0].target, textarea,
`runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #13`);
is(events[1].type, "beforeinput",
"runForceCommitTest: the 2nd event must be beforeinput #13");
is(events[1].type, "compositionend",
"runForceCommitTest: the 2nd event must be compositionend #13");
is(events[1].target, textarea,
`runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #13`);
checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
"runForceCommitTest #13");
is(events[2].type, "compositionend",
"runForceCommitTest: the 3rd event must be compositionend #13");
is(events[2].target, textarea,
`runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #13`);
is(events[2].data, "\u306E",
is(events[1].data, "\u306E",
"runForceCommitTest: compositionend has wrong data #13");
is(events[3].type, "input",
"runForceCommitTest: the 4th event must be input #13");
is(events[3].target, textarea,
`runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #13`);
checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
"runForceCommitTest #13");
ok(!getEditor(textarea).isComposing,
"runForceCommitTest: the textarea still has composition #13");
is(textarea.value, "set value",
@@ -7675,30 +7657,21 @@ function runForceCommitTest()
events = [];
input.value += " appended value";
is(events.length, 4,
// In this case, the result of committing composition won't appear since
// the new value overwrites it. Therefore, neither `beforeinput` nor `input`
// event is fired.
is(events.length, 2,
"runForceCommitTest: wrong event count #14");
is(events[0].type, "text",
"runForceCommitTest: the 1st event must be text #14");
is(events[0].target, input,
`runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #14`);
is(events[1].type, "beforeinput",
"runForceCommitTest: the 2nd event must be beforeinput #14");
is(events[1].type, "compositionend",
"runForceCommitTest: the 2nd event must be compositionend #14");
is(events[1].target, input,
`runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #14`);
checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
"runForceCommitTest #14");
is(events[2].type, "compositionend",
"runForceCommitTest: the 3rd event must be compositionend #14");
is(events[2].target, input,
`runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #14`);
is(events[2].data, "\u306E",
is(events[1].data, "\u306E",
"runForceCommitTest: compositionend has wrong data #14");
is(events[3].type, "input",
"runForceCommitTest: the 4th event must be input #14");
is(events[3].target, input,
`runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #14`);
checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
"runForceCommitTest #14");
ok(!getEditor(input).isComposing,
"runForceCommitTest: the input still has composition #14");
is(input.value, "\u306E appended value",
@@ -7797,33 +7770,24 @@ function runNestedSettingValue()
textarea.value = "first setting value, ";
isTesting = false;
is(events.length, 4,
// In this case, the result of committing composition won't appear since
// the new value overwrites it. Therefore, neither `beforeinput` nor `input`
// event is fired.
is(events.length, 2,
"runNestedSettingValue: wrong event count #1");
is(events[0].type, "text",
"runNestedSettingValue: the 1st event must be text #1");
is(events[0].target, textarea,
`runNestedSettingValue: The "${events[0].type}" event was fired on wrong event target #1`);
is(events[1].type, "beforeinput",
"runNestedSettingValue: the 2nd event must be beforeinput #1");
is(events[1].type, "compositionend",
"runNestedSettingValue: the 2nd event must be compositionend #1");
is(events[1].target, textarea,
`runNestedSettingValue: The "${events[1].type}" event was fired on wrong event target #1`);
checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
"runNestedSettingValue #1");
is(events[2].type, "compositionend",
"runNestedSettingValue: the 3rd event must be compositionend #1");
is(events[2].target, textarea,
`runNestedSettingValue: The "${events[2].type}" event was fired on wrong event target #1`);
is(events[2].data, "\u306E",
is(events[1].data, "\u306E",
"runNestedSettingValue: compositionend has wrong data #1");
is(events[3].type, "input",
"runNestedSettingValue: the 4th event must be input #1");
is(events[3].target, textarea,
`runNestedSettingValue: The "${events[3].type}" event was fired on wrong event target #1`);
checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
"runNestedSettingValue #1");
ok(!getEditor(textarea).isComposing,
"runNestedSettingValue: the textarea still has composition #1");
is(textarea.value, "first setting value, text, beforeinput, compositionend, input, ",
is(textarea.value, "first setting value, text, compositionend, ",
"runNestedSettingValue: the textarea should have all string set to value attribute");
input.focus();
@@ -7845,33 +7809,24 @@ function runNestedSettingValue()
input.value = "first setting value, ";
isTesting = false;
is(events.length, 4,
// In this case, the result of committing composition won't appear since
// the new value overwrites it. Therefore, neither `beforeinput` nor `input`
// event is fired.
is(events.length, 2,
"runNestedSettingValue: wrong event count #2");
is(events[0].type, "text",
"runNestedSettingValue: the 1st event must be text #2");
is(events[0].target, input,
`runNestedSettingValue: The "${events[0].type}" event was fired on wrong event target #2`);
is(events[1].type, "beforeinput",
"runNestedSettingValue: the 2nd event must be beforeinput #2");
is(events[1].type, "compositionend",
"runNestedSettingValue: the 2nd event must be compositionend #2");
is(events[1].target, input,
`runNestedSettingValue: The "${events[1].type}" event was fired on wrong event target #2`);
checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
"runNestedSettingValue #2");
is(events[2].type, "compositionend",
"runNestedSettingValue: the 3rd event must be compositionend #2");
is(events[2].target, input,
`runNestedSettingValue: The "${events[2].type}" event was fired on wrong event target #2`);
is(events[2].data, "\u306E",
is(events[1].data, "\u306E",
"runNestedSettingValue: compositionend has wrong data #2");
is(events[3].type, "input",
"runNestedSettingValue: the 4th event must be input #2");
is(events[3].target, input,
`runNestedSettingValue: The "${events[3].type}" event was fired on wrong event target #2`);
checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
"runNestedSettingValue #2");
ok(!getEditor(input).isComposing,
"runNestedSettingValue: the input still has composition #2");
is(textarea.value, "first setting value, text, beforeinput, compositionend, input, ",
is(textarea.value, "first setting value, text, compositionend, ",
"runNestedSettingValue: the input should have all string set to value attribute #2");
textarea.focus();
@@ -7893,33 +7848,24 @@ function runNestedSettingValue()
textarea.setRangeText("first setting value, ");
isTesting = false;
is(events.length, 4,
// In this case, the result of committing composition won't appear since
// the new value overwrites it. Therefore, neither `beforeinput` nor `input`
// event is fired.
is(events.length, 2,
"runNestedSettingValue: wrong event count #3");
is(events[0].type, "text",
"runNestedSettingValue: the 1st event must be text #3");
is(events[0].target, textarea,
`runNestedSettingValue: The ${events[0].type} event was fired on wrong event target #3`);
is(events[1].type, "beforeinput",
"runNestedSettingValue: the 2nd event must be beforeinput #3");
is(events[1].type, "compositionend",
"runNestedSettingValue: the 2nd event must be compositionend #3");
is(events[1].target, textarea,
`runNestedSettingValue: The ${events[1].type} event was fired on wrong event target #3`);
checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
"runNestedSettingValue #3");
is(events[2].type, "compositionend",
"runNestedSettingValue: the 3rd event must be compositionend #3");
is(events[2].target, textarea,
`runNestedSettingValue: The ${events[2].type} event was fired on wrong event target #3`);
is(events[2].data, "\u306E",
is(events[1].data, "\u306E",
"runNestedSettingValue: compositionend has wrong data #3");
is(events[3].type, "input",
"runNestedSettingValue: the 4th event must be input #3");
is(events[3].target, textarea,
`runNestedSettingValue: The ${events[3].type} event was fired on wrong event target #3`);
checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
"runNestedSettingValue #3");
ok(!getEditor(textarea).isComposing,
"runNestedSettingValue: the textarea still has composition #3");
is(textarea.value, "\u306Efirst setting value, text, beforeinput, compositionend, input, ",
is(textarea.value, "\u306Efirst setting value, text, compositionend, ",
"runNestedSettingValue: the textarea should have appended by setRangeText() and all string set to value attribute #3");
input.focus();
@@ -7941,33 +7887,24 @@ function runNestedSettingValue()
input.setRangeText("first setting value, ");
isTesting = false;
is(events.length, 4,
// In this case, the result of committing composition won't appear since
// the new value overwrites it. Therefore, neither `beforeinput` nor `input`
// event is fired.
is(events.length, 2,
"runNestedSettingValue: wrong event count #4");
is(events[0].type, "text",
"runNestedSettingValue: the 1st event must be text #4");
is(events[0].target, input,
`runNestedSettingValue: The "${events[0].type}" event was fired on wrong event target #4`);
is(events[1].type, "beforeinput",
"runNestedSettingValue: the 2nd event must be beforeinput #4");
is(events[1].type, "compositionend",
"runNestedSettingValue: the 2nd event must be compositionend #4");
is(events[1].target, input,
`runNestedSettingValue: The "${events[1].type}" event was fired on wrong event target #4`);
checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
"runNestedSettingValue #4");
is(events[2].type, "compositionend",
"runNestedSettingValue: the 3rd event must be compositionend #4");
is(events[2].target, input,
`runNestedSettingValue: The "${events[2].type}" event was fired on wrong event target #4`);
is(events[2].data, "\u306E",
is(events[1].data, "\u306E",
"runNestedSettingValue: compositionend has wrong data #4");
is(events[3].type, "input",
"runNestedSettingValue: the 4th event must be input #4");
is(events[3].target, input,
`runNestedSettingValue: The "${events[3].type}" event was fired on wrong event target #4`);
checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
"runNestedSettingValue #4");
ok(!getEditor(input).isComposing,
"runNestedSettingValue: the input still has composition #4");
is(textarea.value, "\u306Efirst setting value, text, beforeinput, compositionend, input, ",
is(textarea.value, "\u306Efirst setting value, text, compositionend, ",
"runNestedSettingValue: the input should have all string appended by setRangeText() and set to value attribute #4");
window.removeEventListener("compositionstart", eventHandler, true);