Bug 998941 - part 1-3: Make TextEditor (only when not HTMLEditor instance) set InputEvent.data to inserting string when InputEvent.inputType is "insertFromPaste", "insertFromDrop" or "insertReplacementText" r=smaug,m_kato

https://rawgit.com/w3c/input-events/v1/index.html#dfn-data
https://w3c.github.io/input-events/#dfn-data

Both Input Events Level 1 and Level 2 declare that InputEvent.data should be
set to inserting string only on TextEditor when InputEvent.inputType is
"insertFromPaste", "insertFromPasteAsQuotation", "insertFromDrop",
"insertTranspose", "insertReplacementText" or "insertFromYank".

Currently, we support only "insertFromPaste", "insertFromDrop",
"insertReplacementText".  Therefore, this patch makes TextEditor set
EditorBase::mEditActionData::mData only for them (and the instance is not
HTMLEditor's).

Differential Revision: https://phabricator.services.mozilla.com/D19287

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Masayuki Nakano 2019-02-19 06:28:57 +00:00
parent a3484e40c0
commit 2eaf64e594
19 changed files with 139 additions and 80 deletions

View File

@ -74,6 +74,8 @@ async function confirmClear(selector) {
'"input" event should always bubble');
is(event.inputType, "insertReplacementText",
'inputType value should be "insertReplacementText"');
is(event.data, "",
"data value should be empty string");
resolve();
}, {once: true})
);

View File

@ -119,6 +119,8 @@ function triggerAutofillAndCheckProfile(profile) {
`"input" event should be dispatched with InputEvent interface on ${element.tagName}`);
is(event.inputType, "insertReplacementText",
"inputType value should be \"insertReplacementText\"");
is(event.data, String(value),
`data value should be "${value}"`);
} else {
ok(event instanceof Event && !(event instanceof UIEvent),
`"input" event should be dispatched with Event interface on ${element.tagName}`);

View File

@ -54,6 +54,8 @@ function checkElementFilled(element, expectedvalue) {
`"input" event should be dispatched with InputEvent interface on ${element.name}`);
is(event.inputType, "insertReplacementText",
"inputType value should be \"insertReplacementText\"");
is(event.data, element.value,
"data value should be same as value of the input element");
} else {
ok(event instanceof Event && !(event instanceof UIEvent),
`"input" event should be dispatched with Event interface on ${element.name}`);

View File

@ -2432,10 +2432,11 @@ bool nsTextEditorState::SetValue(const nsAString& aValue,
if (aFlags & eSetValue_BySetUserInput) {
nsCOMPtr<Element> element = do_QueryInterface(textControlElement);
MOZ_ASSERT(element);
MOZ_ASSERT(!newValue.IsVoid());
RefPtr<TextEditor> textEditor; // See bug 1506439
DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
element, EditorInputType::eInsertReplacementText, textEditor,
nsContentUtils::InputEventOptions());
nsContentUtils::InputEventOptions(newValue));
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"Failed to dispatch input event");
}

View File

@ -164,6 +164,8 @@ SimpleTest.waitForFocus(() => {
`"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
is(inputEvents[0].inputType, "insertReplacementText",
`inputType should be "insertReplacementText" when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
is(inputEvents[0].data, test.input.before,
`data should be "${test.input.before}" when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
}
} else {
ok(inputEvents[0] instanceof Event && !(inputEvents[0] instanceof UIEvent),
@ -222,6 +224,8 @@ SimpleTest.waitForFocus(() => {
`"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
is(inputEvents[0].inputType, "insertReplacementText",
`inputType should be "insertReplacementText" when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
is(inputEvents[0].data, test.input.after,
`data should be "${test.input.after}" when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
}
} else {
ok(inputEvents[0] instanceof Event && !(inputEvents[0] instanceof UIEvent),

View File

@ -248,10 +248,12 @@ add_task(async function test_input_onpaste() {
var onpaste_fired = false;
var oninput_count = 0;
var inputType = "";
var data;
contentInput.onpaste = function() { onpaste_fired = true; };
contentInput.oninput = function(aEvent) {
oninput_count++;
inputType = aEvent.inputType;
data = aEvent.data;
};
try {
@ -261,6 +263,7 @@ add_task(async function test_input_onpaste() {
"paste on plaintext editor did not modify clipboard contents");
is(oninput_count, 1, "input event should be fired once by cut");
is(inputType, "insertFromPaste", "inputType of the input event should be \"insertFromPaste\"");
is(data, clipboardInitialValue, `data of the input event should be ${clipboardInitialValue}`);
is(contentInput.value, clipboardInitialValue,
"paste on plaintext editor did modify editor value");
} finally {

View File

@ -770,6 +770,10 @@ class EditorBase : public nsIEditor,
AutoEditActionDataSetter(const AutoEditActionDataSetter& aOther) = delete;
};
void UpdateEditActionData(const nsAString& aData) {
mEditActionData->SetData(aData);
}
protected: // May be called by friends.
/****************************************************************************
* Some classes like TextEditRules, HTMLEditRules, WSRunObject which are

View File

@ -1963,10 +1963,10 @@ class HTMLEditor final : public TextEditor,
* this editor. Don't use this method for other purposes.
*/
MOZ_CAN_RUN_SCRIPT
virtual nsresult InsertFromDataTransfer(dom::DataTransfer* aDataTransfer,
int32_t aIndex, Document* aSourceDoc,
const EditorDOMPoint& aDroppedAt,
bool aDoDeleteSelection) override;
nsresult InsertFromDataTransfer(dom::DataTransfer* aDataTransfer,
int32_t aIndex, Document* aSourceDoc,
const EditorDOMPoint& aDroppedAt,
bool aDoDeleteSelection);
bool HavePrivateHTMLFlavor(nsIClipboard* clipboard);
nsresult ParseCFHTML(nsCString& aCfhtml, char16_t** aStuffToPaste,

View File

@ -1115,6 +1115,9 @@ nsresult TextEditor::ReplaceTextAsAction(
if (NS_WARN_IF(!editActionData.CanHandle())) {
return NS_ERROR_NOT_INITIALIZED;
}
if (!AsHTMLEditor()) {
editActionData.SetData(aString);
}
AutoPlaceholderBatch treatAsOneTransaction(*this);
@ -2010,6 +2013,7 @@ nsresult TextEditor::PasteAsQuotationAsAction(int32_t aClipboardType,
if (nsCOMPtr<nsISupportsString> text = do_QueryInterface(genericDataObj)) {
nsAutoString stuffToPaste;
text->GetData(stuffToPaste);
editActionData.SetData(stuffToPaste);
if (!stuffToPaste.IsEmpty()) {
AutoPlaceholderBatch treatAsOneTransaction(*this);
rv = InsertWithQuotationsAsSubAction(stuffToPaste);

View File

@ -402,23 +402,6 @@ class TextEditor : public EditorBase, public nsIPlaintextEditor {
const EditorDOMPoint& aPointToInsert,
bool aDoDeleteSelection);
/**
* InsertFromDataTransfer() inserts the data in aDataTransfer at aIndex.
* This is intended to handle "drop" event.
*
* @param aDataTransfer Dropped data transfer.
* @param aIndex Index of the data which should be inserted.
* @param aSourceDoc The document which the source comes from.
* @param aDroppedAt The dropped position.
* @param aDoDeleteSelection true if this should delete selected content.
* false otherwise.
*/
MOZ_CAN_RUN_SCRIPT
virtual nsresult InsertFromDataTransfer(dom::DataTransfer* aDataTransfer,
int32_t aIndex, Document* aSourceDoc,
const EditorDOMPoint& aDroppedAt,
bool aDoDeleteSelection);
/**
* InsertWithQuotationsAsSubAction() inserts aQuotedText with appending ">"
* to start of every line.

View File

@ -114,6 +114,9 @@ nsresult TextEditor::InsertTextAt(const nsAString& aStringToInsert,
nsresult TextEditor::InsertTextFromTransferable(
nsITransferable* aTransferable) {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(!AsHTMLEditor());
nsAutoCString bestFlavor;
nsCOMPtr<nsISupports> genericDataObj;
if (NS_SUCCEEDED(aTransferable->GetAnyTransferData(
@ -126,6 +129,11 @@ nsresult TextEditor::InsertTextFromTransferable(
if (nsCOMPtr<nsISupportsString> text = do_QueryInterface(genericDataObj)) {
text->GetData(stuffToPaste);
}
MOZ_ASSERT(GetEditAction() == EditAction::ePaste);
// Use native line breaks for compatibility with Chrome.
// XXX Although, somebody has already converted native line breaks to
// XP line breaks.
UpdateEditActionData(stuffToPaste);
if (!stuffToPaste.IsEmpty()) {
// Sanitize possible carriage returns in the string to be inserted
@ -145,32 +153,6 @@ nsresult TextEditor::InsertTextFromTransferable(
return NS_OK;
}
nsresult TextEditor::InsertFromDataTransfer(DataTransfer* aDataTransfer,
int32_t aIndex,
Document* aSourceDoc,
const EditorDOMPoint& aDroppedAt,
bool aDoDeleteSelection) {
MOZ_ASSERT(GetEditAction() == EditAction::eDrop);
MOZ_ASSERT(
mPlaceholderBatch,
"TextEditor::InsertFromDataTransfer() should be called only by OnDrop() "
"and there should've already been placeholder transaction");
MOZ_ASSERT(aDroppedAt.IsSet());
nsCOMPtr<nsIVariant> data;
aDataTransfer->GetDataAtNoSecurityCheck(NS_LITERAL_STRING("text/plain"),
aIndex, getter_AddRefs(data));
if (!data) {
return NS_OK;
}
nsAutoString insertText;
data->GetAsAString(insertText);
nsContentUtils::PlatformToDOMLineBreaks(insertText);
return InsertTextAt(insertText, aDroppedAt, aDoDeleteSelection);
}
nsresult TextEditor::OnDrop(DragEvent* aDropEvent) {
if (NS_WARN_IF(!aDropEvent)) {
return NS_ERROR_INVALID_ARG;
@ -316,11 +298,58 @@ nsresult TextEditor::OnDrop(DragEvent* aDropEvent) {
// should we update |droppedAt|?
}
for (uint32_t i = 0; i < numItems; ++i) {
InsertFromDataTransfer(dataTransfer, i, srcdoc, droppedAt, false);
if (!AsHTMLEditor()) {
// For "beforeinput", we need to create data first.
AutoTArray<nsString, 5> textArray;
textArray.SetCapacity(numItems);
uint32_t textLength = 0;
for (uint32_t i = 0; i < numItems; ++i) {
nsCOMPtr<nsIVariant> data;
dataTransfer->GetDataAtNoSecurityCheck(NS_LITERAL_STRING("text/plain"), i,
getter_AddRefs(data));
if (!data) {
continue;
}
// Use nsString to avoid copying its storage to textArray.
nsString insertText;
data->GetAsAString(insertText);
if (insertText.IsEmpty()) {
continue;
}
textArray.AppendElement(insertText);
textLength += insertText.Length();
}
// Use nsString to avoid copying its storage to editActionData.
nsString data;
data.SetCapacity(textLength);
// Join the text array from end to start because we insert each items
// in the dataTransfer at same point from start to end. Although I
// don't know whether this is intentional behavior.
for (nsString& text : Reversed(textArray)) {
data.Append(text);
}
// Use native line breaks for compatibility with Chrome.
// XXX Although, somebody has already converted native line breaks to
// XP line breaks.
editActionData.SetData(data);
// Then, insert the text. Note that we shouldn't need to walk the array
// anymore because nobody should listen to mutation events of anonymous
// text node in <input>/<textarea>.
nsContentUtils::PlatformToDOMLineBreaks(data);
InsertTextAt(data, droppedAt, false);
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
} else {
RefPtr<HTMLEditor> htmlEditor(AsHTMLEditor());
for (uint32_t i = 0; i < numItems; ++i) {
htmlEditor->InsertFromDataTransfer(dataTransfer, i, srcdoc, droppedAt,
false);
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
}
}
ScrollSelectionIntoView(false);

View File

@ -24,12 +24,13 @@ SimpleTest.waitForExplicitFinish();
var shouldClear = false;
window.addEventListener("dragstart", function(event) { if (shouldClear) event.dataTransfer.clearData(); }, true);
function checkInputEvent(aEvent, aExpectedTarget, aDescription) {
function checkInputEvent(aEvent, aExpectedTarget, aData, aDescription) {
ok(aEvent instanceof InputEvent, `"input" event should be dispatched with InputEvent interface ${aDescription}`);
is(aEvent.cancelable, false, `"input" event should be never cancelable ${aDescription}`);
is(aEvent.bubbles, true, `"input" event should always bubble ${aDescription}`);
is(aEvent.target, aExpectedTarget, `"input" event should be fired on the <${aExpectedTarget.tagName.toLowerCase()}> element ${aDescription}`);
is(aEvent.inputType, "insertFromDrop", `inputType should be "insertFromDrop" on the <${aExpectedTarget.tagName.toLowerCase()}> element ${aDescription}`);
is(aEvent.data, aData, `data should be ${aData} on the <${aExpectedTarget.tagName.toLowerCase()}> element ${aDescription}`);
}
function doTest() {
@ -102,7 +103,8 @@ function doTest() {
is(input.value, "Some Text", "Drag text/html onto input");
is(inputEvents.length, 1,
'Only one "input" events should be fired when dropping text/html into empty <input> element');
checkInputEvent(inputEvents[0], input, "when dropping text/html into empty <input> element");
checkInputEvent(inputEvents[0], input, "Some Text",
"when dropping text/html into empty <input> element");
// -------- Test dragging regular text of text/html to disabled <input>
@ -150,7 +152,8 @@ function doTest() {
is(input.value, "Some Plain Text", "Drag text/html and text/plain onto input");
is(inputEvents.length, 1,
'Only one "input" events should be fired when dropping text/plain into empty <input> element');
checkInputEvent(inputEvents[0], input, "when dropping text/plain into empty <input> element");
checkInputEvent(inputEvents[0], input, "Some Plain Text",
"when dropping text/plain into empty <input> element");
// -------- Test dragging regular text of text/plain to <textarea>
@ -178,7 +181,8 @@ function doTest() {
"Drag text/plain onto contenteditable text");
is(inputEvents.length, 1,
'Only one "input" events should be fired when dropping text/plain into contenteditable element');
checkInputEvent(inputEvents[0], contenteditable, "when dropping text/plain into contenteditable element");
checkInputEvent(inputEvents[0], contenteditable, null,
"when dropping text/plain into contenteditable element");
// -------- Test dragging regular text of text/html to contenteditable
@ -190,7 +194,8 @@ function doTest() {
is(contenteditable.childNodes[4].textContent, "Italic", "Drag text/html onto contenteditable italic text");
is(inputEvents.length, 1,
'Only one "input" events should be fired when dropping text/html into contenteditable element');
checkInputEvent(inputEvents[0], contenteditable, "when dropping text/html into contenteditable element");
checkInputEvent(inputEvents[0], contenteditable, null,
"when dropping text/html into contenteditable element");
// -------- Test dragging contenteditable to <input>
@ -201,7 +206,8 @@ function doTest() {
is(input.value, "Some Plain Texteditable", "Copy text/html and text/plain from contenteditable onto input");
is(inputEvents.length, 1,
'Only one "input" events should be fired when dragging from contenteditable to <input> element');
checkInputEvent(inputEvents[0], input, "when dragging from contenteditable to <input> element");
checkInputEvent(inputEvents[0], input, "editable",
"when dragging from contenteditable to <input> element");
// -------- Test dragging contenteditable to contenteditable
@ -215,7 +221,8 @@ function doTest() {
is(contenteditable.childNodes[6].textContent, "Italic", "Move text/html and text/plain from contenteditable onto itself text");
is(inputEvents.length, 1,
'Only one "input" events should be fired when dragging (copy) in a contentediable element');
checkInputEvent(inputEvents[0], contenteditable, "when dragging (copy) in a contentediable element");
checkInputEvent(inputEvents[0], contenteditable, null,
"when dragging (copy) in a contentediable element");
// We'd test 'move' here as well as 'copy', but that requires knowledge of
// the source of the drag which drag simulation doesn't provide.
@ -234,7 +241,8 @@ function doTest() {
todo_is(inputEvents.length, 1,
'Only one "input" events should be fired when dragging from inner non-editable element to a contentediable element');
if (inputEvents.length > 0) {
checkInputEvent(inputEvents[0], document.getElementById("nestedce"), "when dragging from inner non-editable element to a contentediable element");
checkInputEvent(inputEvents[0], document.getElementById("nestedce"), null,
"when dragging from inner non-editable element to a contentediable element");
}
document.removeEventListener("input", onInput);

View File

@ -76,7 +76,7 @@ async function copyHTMLContent(aInnerHTML) {
});
}
function checkInputEvent(aEvent, aDescription) {
function checkInputEvent(aEvent, aData, aDescription) {
ok(aEvent instanceof InputEvent,
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
is(aEvent.cancelable, false,
@ -85,6 +85,8 @@ function checkInputEvent(aEvent, aDescription) {
`"input" event should always bubble ${aDescription}`);
is(aEvent.inputType, "insertFromPaste",
`inputType should be "insertFromPaste" ${aDescription}`);
is(aEvent.data, aData,
`data should be ${aData} ${aDescription}`);
}
async function doTextareaTests(aTextarea) {
@ -103,7 +105,7 @@ async function doTextareaTests(aTextarea) {
"Pasted each line should start with \"> \"");
is(inputEvents.length, 1,
'One "input" event should be fired #1');
checkInputEvent(inputEvents[0], "#1");
checkInputEvent(inputEvents[0], "abc\ndef\nghi", "#1");
aTextarea.value = "";
await copyPlaintext("> abc\n> def\n> ghi");
@ -115,7 +117,7 @@ async function doTextareaTests(aTextarea) {
"Pasted each line should be start with \">> \" when already quoted one level");
is(inputEvents.length, 1,
'One "input" event should be fired #2');
checkInputEvent(inputEvents[0], "#2");
checkInputEvent(inputEvents[0], "> abc\n> def\n> ghi", "#2");
aTextarea.value = "";
await copyPlaintext("> abc\n> def\n\nghi");
@ -127,7 +129,7 @@ async function doTextareaTests(aTextarea) {
"Pasted each line should be start with \">> \" when already quoted one level");
is(inputEvents.length, 1,
'One "input" event should be fired #3');
checkInputEvent(inputEvents[0], "#3");
checkInputEvent(inputEvents[0], "> abc\n> def\n\nghi", "#3");
aTextarea.value = "";
await copyPlaintext("abc\ndef\n\n");
@ -139,7 +141,7 @@ async function doTextareaTests(aTextarea) {
"If pasted text ends with \"\\n\", only the last line should not started with \">\"");
is(inputEvents.length, 1,
'One "input" event should be fired #4');
checkInputEvent(inputEvents[0], "#4");
checkInputEvent(inputEvents[0], "abc\ndef\n\n", "#4");
aTextarea.value = "";
let pasteEventCount = 0;
@ -173,7 +175,7 @@ async function doTextareaTests(aTextarea) {
"Even if 'mouseup' event is consumed, 'paste' event should be fired once");
is(inputEvents.length, 1,
'One "input" event should be fired even if "mouseup" event is canceled');
checkInputEvent(inputEvents[0], 'even if "mouseup" event is canceled');
checkInputEvent(inputEvents[0], "abc", 'even if "mouseup" event is canceled');
aTextarea.value = "";
await copyPlaintext("abc");
@ -188,7 +190,7 @@ async function doTextareaTests(aTextarea) {
"Even if 'click' event handler is added to the <textarea>, 'paste' event should be fired once");
is(inputEvents.length, 1,
'One "input" event should be fired even if "click" event is canceled in bubbling phase');
checkInputEvent(inputEvents[0], 'even if "click" event is canceled in bubbling phase');
checkInputEvent(inputEvents[0], "abc", 'even if "click" event is canceled in bubbling phase');
aTextarea.value = "";
await copyPlaintext("abc");
@ -225,7 +227,7 @@ async function doContenteditableTests(aEditableDiv) {
"Pasted plaintext should be in <blockquote> element and each linebreaker should be <br> element");
is(inputEvents.length, 1,
'One "input" event should be fired on the editing host');
checkInputEvent(inputEvents[0], "(contenteditable)");
checkInputEvent(inputEvents[0], null, "(contenteditable)");
aEditableDiv.innerHTML = "";
let pasteEventCount = 0;
@ -259,7 +261,7 @@ async function doContenteditableTests(aEditableDiv) {
"Even if 'mouseup' event is consumed, 'paste' event should be fired once");
is(inputEvents.length, 1,
'One "input" event should be fired even if "mouseup" event is canceled (contenteditable)');
checkInputEvent(inputEvents[0], 'even if "mouseup" event is canceled (contenteditable)');
checkInputEvent(inputEvents[0], null, 'even if "mouseup" event is canceled (contenteditable)');
aEditableDiv.innerHTML = "";
await copyPlaintext("abc");
@ -274,7 +276,7 @@ async function doContenteditableTests(aEditableDiv) {
"Even if 'click' event handler is added to the editing host, 'paste' event should be fired");
is(inputEvents.length, 1,
'One "input" event should be fired even if "click" event is canceled in bubbling phase (contenteditable)');
checkInputEvent(inputEvents[0], 'even if "click" event is canceled in bubbling phase (contenteditable)');
checkInputEvent(inputEvents[0], null, 'even if "click" event is canceled in bubbling phase (contenteditable)');
aEditableDiv.innerHTML = "";
await copyPlaintext("abc");
@ -315,7 +317,7 @@ async function doContenteditableTests(aEditableDiv) {
}
is(inputEvents.length, 1,
'One "input" event should be fired when pasting HTML');
checkInputEvent(inputEvents[0], "when pasting HTML");
checkInputEvent(inputEvents[0], null, "when pasting HTML");
aEditableDiv.innerHTML = "";
aEditableDiv.removeEventListener("input", onInput);

View File

@ -21,7 +21,7 @@ SimpleTest.waitForFocus(() => {
textarea.focus();
function checkInputEvent(aEvent, aInputType, aDescription) {
function checkInputEvent(aEvent, aInputType, aData, aDescription) {
ok(aEvent instanceof InputEvent,
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
is(aEvent.cancelable, false,
@ -30,6 +30,8 @@ SimpleTest.waitForFocus(() => {
`"input" event should always bubble ${aDescription}`);
is(aEvent.inputType, aInputType,
`inputType should be "${aInputType}" ${aDescription}`);
is(aEvent.data, aData,
`data should be ${aData} ${aDescription}`);
}
let inputEvents = [];
@ -54,7 +56,8 @@ SimpleTest.waitForFocus(() => {
"'abx' should be replaced with 'aux'");
is(inputEvents.length, 1,
'Only one "input" event should be fired when replacing a word with spellchecker');
checkInputEvent(inputEvents[0], "insertReplacementText", "when replacing a word with spellchecker");
checkInputEvent(inputEvents[0], "insertReplacementText", "aux",
"when replacing a word with spellchecker");
inputEvents = [];
synthesizeKey("z", { accelKey: true });
@ -62,7 +65,8 @@ SimpleTest.waitForFocus(() => {
"'abx' should be restored by undo");
is(inputEvents.length, 1,
'Only one "input" event should be fired when undoing the replacing word');
checkInputEvent(inputEvents[0], "historyUndo", "when undoing the replacing word");
checkInputEvent(inputEvents[0], "historyUndo", null,
"when undoing the replacing word");
inputEvents = [];
synthesizeKey("z", { accelKey: true, shiftKey: true });
@ -70,7 +74,8 @@ SimpleTest.waitForFocus(() => {
"'aux' should be restored by redo");
is(inputEvents.length, 1,
'Only one "input" event should be fired when redoing the replacing word');
checkInputEvent(inputEvents[0], "historyRedo", "when redoing the replacing word");
checkInputEvent(inputEvents[0], "historyRedo", null,
"when redoing the replacing word");
textarea.removeEventListener("input", onInput);

View File

@ -1001,6 +1001,8 @@ function runTest() { // eslint-disable-line complexity
ok(!event.cancelable, testNum + " input event shouldn't be cancelable");
is(event.inputType, "insertReplacementText",
testNum + ' inputType should be "insertReplacementText"');
is(event.data, "value1",
testNum + ' data should be "value1"');
}, {once: true});
synthesizeKey("KEY_ArrowDown");

View File

@ -467,6 +467,7 @@ function runTest() {
ok(event.bubbles, "input event should bubble");
ok(!event.cancelable, "input event should be cancelable");
is(event.inputType, "insertReplacementText", 'inputType should be "insertReplacementText"');
is(event.data, "Google", 'data should be "Google"');
checkForm("Google");
input.blur();
SimpleTest.finish();

View File

@ -43,6 +43,8 @@ function handleInput(aEvent) {
'"input" event should always bubble');
is(aEvent.inputType, "insertReplacementText",
'inputType should be "insertReplacementText"');
is(aEvent.data, expectedValue,
`data should be "${expectedValue}"`);
input.removeEventListener("input", handleInput, true);
SimpleTest.finish();
}

View File

@ -124,7 +124,7 @@ nsDoTestsForEditorWithAutoComplete.prototype = {
return true;
}, popup: false, value: "Mozilla", searchString: "Mozilla",
inputEvents: [
{inputType: "insertReplacementText", data: null},
{inputType: "insertReplacementText", data: "Mozilla"},
],
},
{ description: "Undo/Redo behavior check when typed text exactly matches the case: undo the word, but typed text shouldn't be canceled",
@ -199,7 +199,7 @@ nsDoTestsForEditorWithAutoComplete.prototype = {
return true;
}, popup: false, value: "Mozilla", searchString: "Mozilla",
inputEvents: [
{inputType: "insertReplacementText", data: null},
{inputType: "insertReplacementText", data: "Mozilla"},
],
},
{ description: "Undo/Redo behavior check when typed text does not match the case: undo the word, but typed text shouldn't be canceled",
@ -269,7 +269,7 @@ nsDoTestsForEditorWithAutoComplete.prototype = {
inputEvents: [
{inputType: "insertText", data: "M"},
{inputType: "insertText", data: "o"},
{inputType: "insertReplacementText", data: null},
{inputType: "insertReplacementText", data: "Mozilla"},
],
},
{ description: "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): select 'Mozilla' to complete the word",
@ -326,7 +326,7 @@ nsDoTestsForEditorWithAutoComplete.prototype = {
}, popup: true, value: "Mozilla", searchString: "Mo",
inputEvents: [
{inputType: "historyRedo", data: null},
{inputType: "insertReplacementText", data: null},
{inputType: "insertReplacementText", data: "Mozilla"},
],
},
{ description: "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): redo the word",
@ -372,7 +372,7 @@ nsDoTestsForEditorWithAutoComplete.prototype = {
inputEvents: [
{inputType: "insertText", data: "m"},
{inputType: "insertText", data: "o"},
{inputType: "insertReplacementText", data: null},
{inputType: "insertReplacementText", data: "mozilla"},
],
},
{ description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): select 'Mozilla' to complete the word",
@ -387,7 +387,7 @@ nsDoTestsForEditorWithAutoComplete.prototype = {
return true;
}, popup: false, value: "Mozilla", searchString: "Mozilla",
inputEvents: [
{inputType: "insertReplacementText", data: null},
{inputType: "insertReplacementText", data: "Mozilla"},
],
},
// Different from "exactly matches the case" case, modifying the case causes one additional transaction.
@ -450,7 +450,7 @@ nsDoTestsForEditorWithAutoComplete.prototype = {
}, popup: true, value: "mozilla", searchString: "mo",
inputEvents: [
{inputType: "historyRedo", data: null},
{inputType: "insertReplacementText", data: null},
{inputType: "insertReplacementText", data: "mozilla"},
],
},
{ description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): redo the default index word",

View File

@ -139,6 +139,11 @@ inline bool IsDataAvailableOnTextEditor(EditorInputType aInputType) {
case EditorInputType::eInsertText:
case EditorInputType::eInsertCompositionText:
case EditorInputType::eInsertFromComposition: // Only level 2
case EditorInputType::eInsertFromPaste:
case EditorInputType::eInsertTranspose:
case EditorInputType::eInsertFromDrop:
case EditorInputType::eInsertReplacementText:
case EditorInputType::eInsertFromYank:
return true;
default:
return false;