Bug 903746 - part 2: Add `textInput` event and make `EditorBase` dispatch it as a default action of `beforeinput` r=smaug

Differential Revision: https://phabricator.services.mozilla.com/D200121
This commit is contained in:
Masayuki Nakano 2024-04-08 12:30:00 +00:00
Родитель fe2b9f4618
Коммит 7cdc773578
12 изменённых файлов: 304 добавлений и 101 удалений

Просмотреть файл

@ -225,6 +225,8 @@ EVENT(gotpointercapture, ePointerGotCapture, EventNameType_All,
EVENT(lostpointercapture, ePointerLostCapture, EventNameType_All,
ePointerEventClass)
EVENT(selectstart, eSelectStart, EventNameType_HTMLXUL, eBasicEventClass)
NON_IDL_EVENT(textInput, eLegacyTextInput, EventNameType_None,
eLegacyTextEventClass)
EVENT(contextlost, eContextLost, EventNameType_HTML, eBasicEventClass)
EVENT(contextrestored, eContextRestored, EventNameType_HTML, eBasicEventClass)

Просмотреть файл

@ -4,9 +4,13 @@
* 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 "mozilla/BasePrincipal.h" // for nsIPrincipal::IsSystemPrincipal()
#include "mozilla/EventForwards.h"
#include "mozilla/TextEvents.h"
#include "mozilla/dom/DataTransfer.h"
#include "mozilla/dom/TextEvent.h"
#include "nsGlobalWindowInner.h"
#include "nsIPrincipal.h"
#include "nsPresContext.h"
namespace mozilla::dom {
@ -33,9 +37,22 @@ void TextEvent::InitTextEvent(const nsAString& typeArg, bool canBubbleArg,
static_cast<InternalLegacyTextEvent*>(mEvent)->mData = dataArg;
}
void TextEvent::GetData(nsAString& aData) const {
// TODO: Add a security check like InputEvent::GetData().
aData = static_cast<InternalLegacyTextEvent*>(mEvent)->mData;
void TextEvent::GetData(nsAString& aData,
nsIPrincipal& aSubjectPrincipal) const {
InternalLegacyTextEvent* textEvent = mEvent->AsLegacyTextEvent();
MOZ_ASSERT(textEvent);
if (mEvent->IsTrusted() && !aSubjectPrincipal.IsSystemPrincipal() &&
!StaticPrefs::dom_event_clipboardevents_enabled() &&
ExposesClipboardDataOrDataTransfer(textEvent->mInputType)) {
aData.Truncate();
return;
}
if (!textEvent->mDataTransfer) {
aData = textEvent->mData;
return;
}
textEvent->mDataTransfer->GetData(u"text/plain"_ns, aData, aSubjectPrincipal,
IgnoreErrors());
}
} // namespace mozilla::dom

Просмотреть файл

@ -12,6 +12,8 @@
#include "mozilla/dom/TextEventBinding.h"
#include "mozilla/EventForwards.h"
class nsIPrincipal;
namespace mozilla::dom {
class TextEvent : public UIEvent {
@ -26,7 +28,7 @@ class TextEvent : public UIEvent {
return TextEvent_Binding::Wrap(aCx, this, aGivenProto);
}
void GetData(nsAString& aData) const;
void GetData(nsAString& aData, nsIPrincipal& aSubjectPrincipal) const;
void InitTextEvent(const nsAString& typeArg, bool canBubbleArg,
bool cancelableArg, nsGlobalWindowInner* viewArg,
const nsAString& dataArg);

Просмотреть файл

@ -10,6 +10,7 @@
[Pref="dom.events.textevent.enabled", Exposed=Window]
interface TextEvent : UIEvent
{
[NeedsSubjectPrincipal]
readonly attribute DOMString data;
undefined initTextEvent(DOMString type,

Просмотреть файл

@ -81,16 +81,19 @@ enum class EditAction {
// new non-empty composition string and IME selections.
eUpdateComposition,
// eCommitComposition indicates that user commits composition.
// eUpdateCompositionToCommit indicates that user commits composition with
// the new data. That means that there will be no IME selections, but the
// composition continues until the following eCompositionEnd event.
eUpdateCompositionToCommit,
// eCommitComposition indicates that user commits composition and ends the
// composition.
eCommitComposition,
// eCancelComposition indicates that user cancels composition.
// eCancelComposition indicates that user cancels composition and ends the
// composition with empty string.
eCancelComposition,
// eDeleteByComposition indicates that user starts composition with
// empty string and there was selected content.
eDeleteByComposition,
// eUndo/eRedo indicate to undo/redo a transaction.
eUndo,
eRedo,
@ -547,6 +550,7 @@ inline EditorInputType ToInputType(EditAction aEditAction) {
case EditAction::ePasteAsQuotation:
return EditorInputType::eInsertFromPasteAsQuotation;
case EditAction::eUpdateComposition:
case EditAction::eUpdateCompositionToCommit:
return EditorInputType::eInsertCompositionText;
case EditAction::eCommitComposition:
if (StaticPrefs::dom_input_events_conform_to_level_1()) {
@ -558,13 +562,6 @@ inline EditorInputType ToInputType(EditAction aEditAction) {
return EditorInputType::eInsertCompositionText;
}
return EditorInputType::eDeleteCompositionText;
case EditAction::eDeleteByComposition:
if (StaticPrefs::dom_input_events_conform_to_level_1()) {
// XXX Or EditorInputType::eDeleteContent? I don't know which IME may
// causes this situation.
return EditorInputType::eInsertCompositionText;
}
return EditorInputType::eDeleteByComposition;
case EditAction::eInsertLinkElement:
return EditorInputType::eInsertLink;
case EditAction::eDeleteWordBackward:
@ -713,9 +710,9 @@ inline bool MayEditActionDeleteSelection(const EditAction aEditAction) {
return false;
case EditAction::eUpdateComposition:
case EditAction::eUpdateCompositionToCommit:
case EditAction::eCommitComposition:
case EditAction::eCancelComposition:
case EditAction::eDeleteByComposition:
return true;
case EditAction::eUndo:

Просмотреть файл

@ -34,6 +34,7 @@
#include "ErrorList.h"
#include "gfxFontUtils.h" // for gfxFontUtils
#include "mozilla/Assertions.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/intl/BidiEmbeddingLevel.h"
#include "mozilla/BasePrincipal.h" // for BasePrincipal
#include "mozilla/CheckedInt.h" // for CheckedInt
@ -3714,32 +3715,20 @@ nsresult EditorBase::OnCompositionChange(
return NS_ERROR_FAILURE;
}
AutoEditActionDataSetter editActionData(*this,
EditAction::eUpdateComposition);
AutoEditActionDataSetter editActionData(
*this,
// We need to distinguish whether the composition change is followed by
// compositionend or not (i.e., wether IME has already ended the
// composition or still has the composition) because we need to dispatch
// `textInput` event only for the last composition change.
aCompositionChangeEvent.IsFollowedByCompositionEnd()
? EditAction::eUpdateCompositionToCommit
: EditAction::eUpdateComposition);
if (NS_WARN_IF(!editActionData.CanHandle())) {
return NS_ERROR_NOT_INITIALIZED;
}
// If:
// - new composition string is not empty,
// - there is no composition string in the DOM tree,
// - and there is non-collapsed Selection,
// the selected content will be removed by this composition.
if (aCompositionChangeEvent.mData.IsEmpty() &&
mComposition->String().IsEmpty() && !SelectionRef().IsCollapsed()) {
editActionData.UpdateEditAction(EditAction::eDeleteByComposition);
}
// If Input Events Level 2 is enabled, EditAction::eDeleteByComposition is
// mapped to EditorInputType::eDeleteByComposition and it requires null
// for InputEvent.data. Therefore, only otherwise, we should set data.
if (ToInputType(editActionData.GetEditAction()) !=
EditorInputType::eDeleteByComposition) {
MOZ_ASSERT(ToInputType(editActionData.GetEditAction()) ==
EditorInputType::eInsertCompositionText);
MOZ_ASSERT(!aCompositionChangeEvent.mData.IsVoid());
editActionData.SetData(aCompositionChangeEvent.mData);
}
MOZ_ASSERT(!aCompositionChangeEvent.mData.IsVoid());
editActionData.SetData(aCompositionChangeEvent.mData);
// If we're an `HTMLEditor` and this is second or later composition change,
// we should set target range to the range of composition string.
@ -6513,6 +6502,13 @@ nsresult EditorBase::AutoEditActionDataSetter::MaybeFlushPendingNotifications()
return NS_OK;
}
void EditorBase::AutoEditActionDataSetter::MarkEditActionCanceled() {
mBeforeInputEventCanceled = true;
if (mEditorBase.IsHTMLEditor()) {
mEditorBase.AsHTMLEditor()->mHasBeforeInputBeenCanceled = true;
}
}
nsresult EditorBase::AutoEditActionDataSetter::MaybeDispatchBeforeInputEvent(
nsIEditor::EDirection aDeleteDirectionAndAmount /* = nsIEditor::eNone */) {
MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(),
@ -6637,11 +6633,79 @@ nsresult EditorBase::AutoEditActionDataSetter::MaybeDispatchBeforeInputEvent(
NS_WARNING("nsContentUtils::DispatchInputEvent() failed");
return rv;
}
mBeforeInputEventCanceled = status == nsEventStatus_eConsumeNoDefault;
if (mBeforeInputEventCanceled && mEditorBase.IsHTMLEditor()) {
mEditorBase.AsHTMLEditor()->mHasBeforeInputBeenCanceled = true;
if (status == nsEventStatus_eConsumeNoDefault) {
MarkEditActionCanceled();
return NS_ERROR_EDITOR_ACTION_CANCELED;
}
return mBeforeInputEventCanceled ? NS_ERROR_EDITOR_ACTION_CANCELED : NS_OK;
nsCOMPtr<nsIWidget> widget = editorBase->GetWidget();
if (!StaticPrefs::dom_events_textevent_enabled() ||
!targetElement->IsInComposedDoc() || !widget) {
return NS_OK;
}
nsString textInputData;
RefPtr<DataTransfer> textInputDataTransfer;
switch (inputType) {
case EditorInputType::eInsertCompositionText:
// If the composition is still being composed, we should not dispatch
// textInput event, but we need to dispatch it for the last composition
// change because web apps should know the inserting commit string as
// same as input from keyboard.
if (mEditAction == EditAction::eUpdateComposition) {
return NS_OK;
}
[[fallthrough]];
case EditorInputType::eInsertText:
textInputData = mData;
break;
case EditorInputType::eInsertFromDrop:
case EditorInputType::eInsertFromPaste:
case EditorInputType::eInsertFromPasteAsQuotation:
if (mDataTransfer) {
textInputDataTransfer = mDataTransfer;
} else {
textInputData = mData;
}
break;
case EditorInputType::eInsertLineBreak:
case EditorInputType::eInsertParagraph:
// Don't dispatch `textInput` on <input> because Chrome does not do it.
// On the other hand, we need to dispatch it on <textarea> and
// contenteditable.
if (mEditorBase.IsTextEditor() && mEditorBase.IsSingleLineEditor()) {
return NS_OK;
}
textInputData.Assign(u'\n');
break;
default:
return NS_OK;
}
InternalLegacyTextEvent textEvent(true, eLegacyTextInput, widget);
textEvent.mData = std::move(textInputData);
textEvent.mDataTransfer = std::move(textInputDataTransfer);
textEvent.mInputType = inputType;
// Make it always cancelable even though we ignore it when inserting or
// deleting composition. This is compatible with Chrome.
// However, if and only if it's unsafe, let's set it not cancelable because of
// asynchronous dispatching.
textEvent.mFlags.mCancelable = nsContentUtils::IsSafeToRunScript();
status = nsEventStatus_eIgnore;
rv = AsyncEventDispatcher::RunDOMEventWhenSafe(*targetElement, textEvent,
&status);
if (NS_WARN_IF(mEditorBase.Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
if (NS_FAILED(rv)) {
NS_WARNING("AsyncEventDispatcher::RunDOMEventWhenSafe() failed");
return rv;
}
if (status == nsEventStatus_eConsumeNoDefault) {
MarkEditActionCanceled();
return NS_ERROR_EDITOR_ACTION_CANCELED;
}
return NS_OK;
}
/*****************************************************************************

Просмотреть файл

@ -1361,6 +1361,8 @@ class EditorBase : public nsIEditor,
}
}
void MarkEditActionCanceled();
EditorBase& mEditorBase;
RefPtr<Selection> mSelection;
nsTArray<OwningNonNull<Selection>> mRetiredSelections;

Просмотреть файл

@ -270,9 +270,6 @@ inline bool IsCancelableBeforeInputEvent(EditorInputType aInputType) {
return true;
case EditorInputType::eInsertLink:
return true;
case EditorInputType::eDeleteByComposition:
MOZ_ASSERT(!StaticPrefs::dom_input_events_conform_to_level_1());
return true;
case EditorInputType::eDeleteCompositionText:
MOZ_ASSERT(!StaticPrefs::dom_input_events_conform_to_level_1());
return false;

Просмотреть файл

@ -1527,12 +1527,16 @@ class InternalLegacyTextEvent : public InternalUIEvent {
}
nsString mData;
RefPtr<dom::DataTransfer> mDataTransfer;
EditorInputType mInputType = EditorInputType::eUnknown;
void AssignLegacyTextEventData(const InternalLegacyTextEvent& aEvent,
bool aCopyTargets) {
AssignUIEventData(aEvent, aCopyTargets);
mData = aEvent.mData;
mDataTransfer = aEvent.mDataTransfer;
mInputType = aEvent.mInputType;
}
};

Просмотреть файл

@ -59,6 +59,7 @@
<body>
<div id="display">
<input id="input-text">
<div contenteditable id="contenteditable"><br></div>
<button id="button">button</button>
<a id="a" href="about:blank">hyper link</a>
<span id="pointer-target">span</span>
@ -352,6 +353,44 @@ const kTests = [
},
todoMismatch: [ ],
},
{ description: "InternalLegacyTextEvent (input at key input)",
targetID: "input-text", eventType: "textInput",
dispatchEvent() {
const input = document.getElementById(this.targetID);
input.value = "";
input.focus();
synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, kIsWin ? WIN_VK_B : MAC_VK_ANSI_B,
{ shiftKey: true }, "B", "B");
observeKeyUpOnContent(KeyboardEvent.DOM_VK_B, runNextTest);
return true;
},
canRun() {
return (kIsMac || kIsWin);
},
todoMismatch: [],
},
{ description: "InternalLegacyTextEvent (paste)",
targetID: "contenteditable", eventType: "textInput",
async dispatchEvent() {
const editingHost = document.getElementById(this.targetID);
editingHost.innerHTML = "abc";
editingHost.focus();
getSelection().selectAllChildren(editingHost);
synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, kIsWin ? WIN_VK_C : MAC_VK_ANSI_C,
{ accelKey: true }, "", "c");
const waitForInput = new Promise(resolve => {
editingHost.addEventListener("input", resolve, {once: true});
});
// In this case, TextEvent.data is stored with a dataTransfer.
synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, kIsWin ? WIN_VK_V : MAC_VK_ANSI_V,
{ accelKey: true }, "", "v");
await waitForInput;
},
canRun() {
return (kIsMac || kIsWin);
},
todoMismatch: [],
},
{ description: "WidgetMouseScrollEvent (DOMMouseScroll, vertical)",
targetID: "input-text", eventType: "DOMMouseScroll",
dispatchEvent() {
@ -615,7 +654,7 @@ const kTests = [
},
];
function doTest(aTest) {
async function doTest(aTest) {
if (!aTest.canRun()) {
SimpleTest.executeSoon(runNextTest);
return;
@ -650,7 +689,7 @@ function doTest(aTest) {
runNextTest();
}
};
var testWillCallRunNextTest = aTest.dispatchEvent();
var testWillCallRunNextTest = await aTest.dispatchEvent();
}
var gIndex = -1;
@ -695,7 +734,8 @@ function init() {
["mousewheel.with_alt.action", 0],
["mousewheel.with_alt.action.override_x", -1],
["mousewheel.with_meta.action", 0],
["mousewheel.with_meta.action.override_x", -1]]}, runNextTest);
["mousewheel.with_meta.action.override_x", -1],
["dom.events.textevent.enabled", true]]}, runNextTest);
}
function finish() {

Просмотреть файл

@ -806,6 +806,16 @@ function checkInputEvent(aEvent, aIsComposing, aInputType, aData, aTargetRanges,
}
}
function checkTextInputEvent(aEvent, aData, aDescription) {
if (aEvent.type !== "textInput") {
throw new Error(`${aDescription}: "${aEvent.type}" is not TextEvent`);
}
ok(TextEvent.isInstance(aEvent), `"${aEvent.type}" event should be dispatched with TextEvent interface: ${aDescription}`);
is(aEvent.cancelable, true, `"${aEvent.type}" event should be cancelable: ${aDescription}`);
is(aEvent.bubbles, true, `"${aEvent.type}" event should always bubble: ${aDescription}`);
is(aEvent.data, aData, `data of "${aEvent.type}" event should be ${aData}: ${aDescription}`);
}
function runCompositionCommitAsIsTest()
{
textarea.focus();
@ -824,6 +834,7 @@ function runCompositionCommitAsIsTest()
textarea.addEventListener("compositionupdate", handler, true);
textarea.addEventListener("compositionend", handler, true);
textarea.addEventListener("beforeinput", handler, true);
textarea.addEventListener("textInput", handler, true);
textarea.addEventListener("input", handler, true);
textarea.addEventListener("text", handler, true);
@ -842,22 +853,29 @@ function runCompositionCommitAsIsTest()
});
is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have composition string #1");
is(result.findIndex(value => value.type == "textInput"), -1,
"runCompositionCommitAsIsTest: no textInput event should be fired before commit #1");
clearResult();
synthesizeComposition({ type: "compositioncommitasis", key: { key: "Enter" } });
is(result.length, 4,
"runCompositionCommitAsIsTest: 4 events should be fired after dispatching compositioncommitasis #1");
is(result.length, 5,
"runCompositionCommitAsIsTest: 5 events should be fired after dispatching compositioncommitasis #1");
is(result[0].type, "text",
"runCompositionCommitAsIsTest: text should be fired after dispatching compositioncommitasis because it's dispatched when there is composing string #1");
is(result[1].type, "beforeinput",
"runCompositionCommitAsIsTest: beforeinput should be fired after dispatching compositioncommitasis because it's dispatched when there is composing string #1");
checkInputEvent(result[1], true, "insertCompositionText", "\u3042", [],
"runCompositionCommitAsIsTest: after dispatching compositioncommitasis #1");
is(result[2].type, "compositionend",
is(result[2].type, "textInput",
"runCompositionCommitAsIsText: textInput should be fired after dispatching compositioncommitasis because it's after the last beforeinput for the composition #1");
checkTextInputEvent(result[2], "\u3042",
"runCompositionCommitAsIsText: after dispatching compositioncommitasis #1");
is(result[3].type, "compositionend",
"runCompositionCommitAsIsTest: compositionend should be fired after dispatching compositioncommitasis #1");
is(result[3].type, "input",
is(result[4].type, "input",
"runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #1");
checkInputEvent(result[3], false, "insertCompositionText", "\u3042", [],
checkInputEvent(result[4], false, "insertCompositionText", "\u3042", [],
"runCompositionCommitAsIsTest: after dispatching compositioncommitasis #1");
is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #1");
@ -887,6 +905,8 @@ function runCompositionCommitAsIsTest()
"key": { key: "KEY_Enter", type: "keydown" },
});
is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #2");
isnot(result.findIndex(value => value.type == "textInput"), -1,
"runCompositionCommitAsIsTest: a textInput event should be fired before commit #2");
clearResult();
synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter", type: "keyup" } });
@ -928,6 +948,8 @@ function runCompositionCommitAsIsTest()
"key": { key: "KEY_Escape", type: "keydown" },
});
is(textarea.value, "", "runCompositionCommitAsIsTest: textarea has non-empty composition string #3");
todo_isnot(result.findIndex(value => value.type == "textInput"), -1,
"runCompositionCommitAsIsTest: a textInput event should be fired immediately before commit #3");
clearResult();
synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Escape", type: "keyup" } });
@ -946,6 +968,7 @@ function runCompositionCommitAsIsTest()
textarea.removeEventListener("compositionupdate", handler, true);
textarea.removeEventListener("compositionend", handler, true);
textarea.removeEventListener("beforeinput", handler, true);
textarea.removeEventListener("textInput", handler, true);
textarea.removeEventListener("input", handler, true);
textarea.removeEventListener("text", handler, true);
}
@ -968,6 +991,7 @@ function runCompositionCommitTest()
textarea.addEventListener("compositionupdate", handler, true);
textarea.addEventListener("compositionend", handler, true);
textarea.addEventListener("beforeinput", handler, true);
textarea.addEventListener("textInput", handler, true);
textarea.addEventListener("input", handler, true);
textarea.addEventListener("text", handler, true);
@ -985,12 +1009,14 @@ function runCompositionCommitTest()
"key": { key: "a", type: "keydown" },
});
is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #1");
is(result.findIndex(value => value.type == "textInput"), -1,
"runCompositionCommitTest: no textInput event should be fired before commit #1");
clearResult();
synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "a", type: "keyup" } });
is(result.length, 5,
"runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #1");
is(result.length, 6,
"runCompositionCommitTest: 6 events should be fired after dispatching compositioncommit #1");
is(result[0].type, "compositionupdate",
"runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit because it's dispatched when there is composing string #1");
is(result[1].type, "text",
@ -999,16 +1025,21 @@ function runCompositionCommitTest()
"runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit because it's dispatched when there is composing string #1");
checkInputEvent(result[2], true, "insertCompositionText", "\u3043", [],
"runCompositionCommitTest: after dispatching compositioncommit #1");
is(result[3].type, "compositionend",
is(result[3].type, "textInput",
"runCompositionCommitTest: textInput should be fired after dispatching compositioncommit because the preceding beforeinput is last one for the composition #1");
checkTextInputEvent(result[3], "\u3043",
"runCompositionCommitTest: after dispatching compositioncommit #1");
is(result[4].type, "compositionend",
"runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #1");
is(result[4].type, "input",
is(result[5].type, "input",
"runCompositionCommitTest: input should be fired after dispatching compositioncommit #1");
checkInputEvent(result[4], false, "insertCompositionText", "\u3043", [],
checkInputEvent(result[5], false, "insertCompositionText", "\u3043", [],
"runCompositionCommitTest: after dispatching compositioncommit #1");
is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #1");
// compositioncommit with different committed string when there is already committed string
textarea.value = "";
clearResult();
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3042",
@ -1033,12 +1064,14 @@ function runCompositionCommitTest()
"key": { key: "KEY_Enter", type: "keydown" },
});
is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have committed string #2");
is(result.findIndex(value => value.type == "textInput"), -1,
"runCompositionCommitTest: no textInput event should be fired before commit #2");
clearResult();
synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "KEY_Enter", type: "keyup" } });
is(result.length, 5,
"runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #2");
is(result.length, 6,
"runCompositionCommitTest: 6 events should be fired after dispatching compositioncommit #2");
is(result[0].type, "compositionupdate",
"runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #2");
is(result[1].type, "text",
@ -1047,16 +1080,21 @@ function runCompositionCommitTest()
"runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #2");
checkInputEvent(result[2], true, "insertCompositionText", "\u3043", [],
"runCompositionCommitTest: after dispatching compositioncommit #2");
is(result[3].type, "compositionend",
is(result[3].type, "textInput",
"runCompositionCommitTest: textInput should be fired after dispatching compositioncommit #2");
checkTextInputEvent(result[3], "\u3043",
"runCompositionCommitTest: after dispatching compositioncommit #2");
is(result[4].type, "compositionend",
"runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #2");
is(result[4].type, "input",
is(result[5].type, "input",
"runCompositionCommitTest: input should be fired after dispatching compositioncommit #2");
checkInputEvent(result[4], false, "insertCompositionText", "\u3043", [],
checkInputEvent(result[5], false, "insertCompositionText", "\u3043", [],
"runCompositionCommitTest: after dispatching compositioncommit #2");
is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #2");
// compositioncommit with empty composition string.
textarea.value = "";
clearResult();
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3042",
@ -1081,12 +1119,14 @@ function runCompositionCommitTest()
"key": { key: "KEY_Enter", type: "keydown" },
});
is(textarea.value, "", "runCompositionCommitTest: textarea has non-empty composition string #3");
is(result.findIndex(value => value.type == "textInput"), -1,
"runCompositionCommitTest: no textInput event should be fired before commit #3");
clearResult();
synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "KEY_Enter", type: "keyup" } });
is(result.length, 5,
"runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #3");
is(result.length, 6,
"runCompositionCommitTest: 6 events should be fired after dispatching compositioncommit #3");
is(result[0].type, "compositionupdate",
"runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #3");
is(result[1].type, "text",
@ -1095,11 +1135,15 @@ function runCompositionCommitTest()
"runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #3");
checkInputEvent(result[2], true, "insertCompositionText", "\u3043", [],
"runCompositionCommitTest: after dispatching compositioncommit #3");
is(result[3].type, "compositionend",
is(result[3].type, "textInput",
"runCompositionCommitTest: textInput should be fired after dispatching compositioncommit #3");
checkTextInputEvent(result[3], "\u3043",
"runCompositionCommitTest: after dispatching compositioncommit #3");
is(result[4].type, "compositionend",
"runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #3");
is(result[4].type, "input",
is(result[5].type, "input",
"runCompositionCommitTest: input should be fired after dispatching compositioncommit #3");
checkInputEvent(result[4], false, "insertCompositionText", "\u3043", [],
checkInputEvent(result[5], false, "insertCompositionText", "\u3043", [],
"runCompositionCommitTest: after dispatching compositioncommit #3");
is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #3");
@ -1111,19 +1155,23 @@ function runCompositionCommitTest()
clearResult();
synthesizeComposition({ type: "compositioncommit", data: "" });
is(result.length, 4,
"runCompositionCommitTest: 4 events should be fired when inserting empty string with composition");
is(result.length, 5,
"runCompositionCommitTest: 5 events should be fired when inserting empty string with composition");
is(result[0].type, "text",
"runCompositionCommitTest: text should be fired when inserting empty string with composition");
is(result[1].type, "beforeinput",
"runCompositionCommitTest: beforeinput should be fired when inserting empty string with composition");
checkInputEvent(result[1], true, "insertCompositionText", "", [],
"runCompositionCommitTest: when inserting empty string with composition");
is(result[2].type, "compositionend",
is(result[2].type, "textInput",
"runCompositionCommitTest: textInput should be fired when inserting empty string with composition");
checkTextInputEvent(result[2], "",
"runCompositionCommitTest: when inserting empty string with composition");
is(result[3].type, "compositionend",
"runCompositionCommitTest: compositionend should be fired when inserting empty string with composition");
is(result[3].type, "input",
is(result[4].type, "input",
"runCompositionCommitTest: input should be fired when inserting empty string with composition");
checkInputEvent(result[3], false, "insertCompositionText", "", [],
checkInputEvent(result[4], false, "insertCompositionText", "", [],
"runCompositionCommitTest: when inserting empty string with composition");
is(textarea.value, "abc",
"runCompositionCommitTest: textarea should keep original value when inserting empty string with composition");
@ -1136,19 +1184,23 @@ function runCompositionCommitTest()
clearResult();
synthesizeComposition({ type: "compositioncommit", data: "" });
is(result.length, 4,
"runCompositionCommitTest: 4 events should be fired when replacing with empty string with composition");
is(result.length, 5,
"runCompositionCommitTest: 5 events should be fired when replacing with empty string with composition");
is(result[0].type, "text",
"runCompositionCommitTest: text should be fired when replacing with empty string with composition");
is(result[1].type, "beforeinput",
"runCompositionCommitTest: beforeinput should be fired when replacing with empty string with composition");
checkInputEvent(result[1], true, "insertCompositionText", "", [],
"runCompositionCommitTest: when replacing with empty string with composition");
is(result[2].type, "compositionend",
is(result[2].type, "textInput",
"runCompositionCommitTest: textInput should be fired when replacing with empty string with composition");
checkTextInputEvent(result[2], "",
"runCompositionCommitTest: when replacing with empty string with composition");
is(result[3].type, "compositionend",
"runCompositionCommitTest: compositionend should be fired when replacing with empty string with composition");
is(result[3].type, "input",
is(result[4].type, "input",
"runCompositionCommitTest: input should be fired when replacing with empty string with composition");
checkInputEvent(result[3], false, "insertCompositionText", "", [],
checkInputEvent(result[4], false, "insertCompositionText", "", [],
"runCompositionCommitTest: when replacing with empty string with composition");
is(textarea.value, "",
"runCompositionCommitTest: textarea should become empty when replacing selection with empty string with composition");
@ -1161,8 +1213,8 @@ function runCompositionCommitTest()
clearResult();
synthesizeComposition({ type: "compositioncommit", data: "abc" });
is(result.length, 5,
"runCompositionCommitTest: 5 events should be fired when replacing selection with same string with composition");
is(result.length, 6,
"runCompositionCommitTest: 6 events should be fired when replacing selection with same string with composition");
is(result[0].type, "compositionupdate",
"runCompositionCommitTest: compositionupdate should be fired when replacing selection with same string with composition");
is(result[1].type, "text",
@ -1171,11 +1223,15 @@ function runCompositionCommitTest()
"runCompositionCommitTest: beforeinput should be fired when replacing selection with same string with composition");
checkInputEvent(result[2], true, "insertCompositionText", "abc", [],
"runCompositionCommitTest: when replacing selection with same string with composition");
is(result[3].type, "compositionend",
is(result[3].type, "textInput",
"runCompositionCommitTest: textInput should be fired when replacing selection with same string with composition");
checkTextInputEvent(result[3], "abc",
"runCompositionCommitTest: when replacing selection with same string with composition");
is(result[4].type, "compositionend",
"runCompositionCommitTest: compositionend should be fired when replacing selection with same string with composition");
is(result[4].type, "input",
is(result[5].type, "input",
"runCompositionCommitTest: input should be fired when replacing selection with same string with composition");
checkInputEvent(result[4], false, "insertCompositionText", "abc", [],
checkInputEvent(result[5], false, "insertCompositionText", "abc", [],
"runCompositionCommitTest: 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");
@ -1198,8 +1254,8 @@ function runCompositionCommitTest()
clearResult();
synthesizeComposition({ type: "compositioncommit", data: "", key: { key: "KEY_Enter" } });
is(result.length, 5,
"runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #4");
is(result.length, 6,
"runCompositionCommitTest: 6 events should be fired after dispatching compositioncommit #4");
is(result[0].type, "compositionupdate",
"runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #4");
is(result[1].type, "text",
@ -1208,11 +1264,15 @@ function runCompositionCommitTest()
"runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #4");
checkInputEvent(result[2], true, "insertCompositionText", "", [],
"runCompositionCommitTest: after dispatching compositioncommit #4");
is(result[3].type, "compositionend",
is(result[3].type, "textInput",
"runCompositionCommitTest: textInput should be fired after dispatching compositioncommit #4");
checkTextInputEvent(result[3], "",
"runCompositionCommitTest: after dispatching compositioncommit #4");
is(result[4].type, "compositionend",
"runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #4");
is(result[4].type, "input",
is(result[5].type, "input",
"runCompositionCommitTest: input should be fired after dispatching compositioncommit #4");
checkInputEvent(result[4], false, "insertCompositionText", "", [],
checkInputEvent(result[5], false, "insertCompositionText", "", [],
"runCompositionCommitTest: after dispatching compositioncommit #4");
is(textarea.value, "", "runCompositionCommitTest: textarea should be empty #4");
@ -1222,8 +1282,8 @@ function runCompositionCommitTest()
clearResult();
synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "a" } });
is(result.length, 5,
"runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #5");
is(result.length, 6,
"runCompositionCommitTest: 6 events should be fired after dispatching compositioncommit #5");
is(result[0].type, "compositionupdate",
"runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #5");
is(result[1].type, "text",
@ -1232,11 +1292,15 @@ function runCompositionCommitTest()
"runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #5");
checkInputEvent(result[2], true, "insertCompositionText", "\u3042", [],
"runCompositionCommitTest: after dispatching compositioncommit #5");
is(result[3].type, "compositionend",
is(result[3].type, "textInput",
"runCompositionCommitTest: textInput should be fired after dispatching compositioncommit #5");
checkTextInputEvent(result[3], "\u3042",
"runCompositionCommitTest: after dispatching compositioncommit #5");
is(result[4].type, "compositionend",
"runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #5");
is(result[4].type, "input",
is(result[5].type, "input",
"runCompositionCommitTest: input should be fired after dispatching compositioncommit #5");
checkInputEvent(result[4], false, "insertCompositionText", "\u3042", [],
checkInputEvent(result[5], false, "insertCompositionText", "\u3042", [],
"runCompositionCommitTest: after dispatching compositioncommit #5");
is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should be empty #5");
@ -1258,24 +1322,29 @@ function runCompositionCommitTest()
clearResult();
synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "KEY_Enter" } });
is(result.length, 4,
"runCompositionCommitTest: 4 events should be fired after dispatching compositioncommit #6");
is(result.length, 5,
"runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #6");
is(result[0].type, "text",
"runCompositionCommitTest: text should be fired after dispatching compositioncommit #6");
is(result[1].type, "beforeinput",
"runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #6");
checkInputEvent(result[1], true, "insertCompositionText", "\u3042", [],
"runCompositionCommitTest: after dispatching compositioncommit #6");
is(result[2].type, "compositionend",
is(result[2].type, "textInput",
"runCompositionCommitTest: textInput should be fired after dispatching compositioncommit #6");
checkTextInputEvent(result[2], "\u3042",
"runCompositionCommitTest: after dispatching compositioncommit #6");
is(result[3].type, "compositionend",
"runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #6");
is(result[3].type, "input",
is(result[4].type, "input",
"runCompositionCommitTest: input should be fired after dispatching compositioncommit #6");
checkInputEvent(result[3], false, "insertCompositionText", "\u3042", [],
checkInputEvent(result[4], false, "insertCompositionText", "\u3042", [],
"runCompositionCommitTest: after dispatching compositioncommit #6");
is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should have committed string #6");
// compositioncommit with same composition string when there is committed string
textarea.value = "";
clearResult();
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3042",
@ -1301,6 +1370,8 @@ function runCompositionCommitTest()
"key": { key: "KEY_Enter", type: "keydown" },
});
is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #6");
todo_isnot(result.findIndex(value => value.type == "textInput"), -1,
"runCompositionCommitTest: a textInput event should be fired before immediately commit #6");
clearResult();
synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "KEY_Enter", type: "keyup" } });
@ -1319,6 +1390,7 @@ function runCompositionCommitTest()
textarea.removeEventListener("compositionupdate", handler, true);
textarea.removeEventListener("compositionend", handler, true);
textarea.removeEventListener("beforeinput", handler, true);
textarea.removeEventListener("textInput", handler, true);
textarea.removeEventListener("input", handler, true);
textarea.removeEventListener("text", handler, true);
}
@ -10860,6 +10932,10 @@ async function runInputModeTest()
async function runTest()
{
await SpecialPowers.pushPrefEnv({
set: [["dom.events.textevent.enabled", true]],
});
window.addEventListener("unload", window.arguments[0].SimpleTest.finish, {once: true, capture: true});
contenteditable = document.getElementById("iframe4").contentDocument.getElementById("contenteditable");

Просмотреть файл

@ -927,6 +927,7 @@ STATIC_ATOMS = [
Atom("ontypechange", "ontypechange"),
Atom("onterminate", "onterminate"),
Atom("ontext", "ontext"),
Atom("ontextInput", "ontextInput"),
Atom("ontoggle", "ontoggle"),
Atom("ontonechange", "ontonechange"),
Atom("ontouchstart", "ontouchstart"),