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
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 19 Feb 2019 06:28:57 +0000
changeset 459854 ebd32851bb24a4f09ad7c02d5de5669e5c6ce7f5
parent 459853 7eae0724c0aa291d7439cb64dc851e0026cdebe2
child 459855 cfd766ac996375620525ee17540e80d03872cf08
push id112018
push userbtara@mozilla.com
push dateTue, 19 Feb 2019 17:39:20 +0000
treeherdermozilla-inbound@8c7b302aa952 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, m_kato
bugs998941
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
browser/extensions/formautofill/test/mochitest/creditCard/test_clear_form.html
browser/extensions/formautofill/test/mochitest/formautofill_common.js
browser/extensions/formautofill/test/mochitest/test_multi_locale_CA_address_form.html
dom/html/nsTextEditorState.cpp
dom/html/test/forms/test_MozEditableElement_setUserInput.html
dom/tests/mochitest/general/test_clipboard_events.html
editor/libeditor/EditorBase.h
editor/libeditor/HTMLEditor.h
editor/libeditor/TextEditor.cpp
editor/libeditor/TextEditor.h
editor/libeditor/TextEditorDataTransfer.cpp
editor/libeditor/tests/test_dragdrop.html
editor/libeditor/tests/test_middle_click_paste.html
editor/libeditor/tests/test_undo_after_spellchecker_replaces_word.html
toolkit/components/satchel/test/test_form_autocomplete.html
toolkit/components/satchel/test/test_form_autocomplete_with_list.html
toolkit/components/satchel/test/test_submit_on_keydown_enter.html
toolkit/content/tests/chrome/file_editor_with_autocomplete.js
widget/EventForwards.h
--- a/browser/extensions/formautofill/test/mochitest/creditCard/test_clear_form.html
+++ b/browser/extensions/formautofill/test/mochitest/creditCard/test_clear_form.html
@@ -69,16 +69,18 @@ async function confirmClear(selector) {
       ok(event instanceof InputEvent,
          '"input" event should be dispatched with InputEvent interface');
       is(event.cancelable, false,
          '"input" event should be never cancelable');
       is(event.bubbles, true,
          '"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})
   );
   synthesizeKey("KEY_Enter");
   await promise;
 }
 
 add_task(async function simple_clear() {
--- a/browser/extensions/formautofill/test/mochitest/formautofill_common.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_common.js
@@ -114,16 +114,18 @@ function triggerAutofillAndCheckProfile(
     const expectingEvent = document.activeElement == element ? "DOMAutoComplete" : "change";
     const checkFieldAutofilled = Promise.all([
       new Promise(resolve => element.addEventListener("input", (event) => {
         if (element.tagName == "INPUT" && element.type == "text") {
           ok(event instanceof InputEvent,
              `"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}`);
         }
         is(event.cancelable, false,
            `"input" event should be never cancelable on ${element.tagName}`);
         is(event.bubbles, true,
            `"input" event should always bubble on ${element.tagName}`);
--- a/browser/extensions/formautofill/test/mochitest/test_multi_locale_CA_address_form.html
+++ b/browser/extensions/formautofill/test/mochitest/test_multi_locale_CA_address_form.html
@@ -49,16 +49,18 @@ function checkElementFilled(element, exp
     new Promise(resolve => {
       element.addEventListener("input", function onInput(event) {
         ok(true, "Checking " + element.name + " field fires input event");
         if (element.tagName == "INPUT" && element.type == "text") {
           ok(event instanceof InputEvent,
              `"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}`);
         }
         is(event.cancelable, false,
            `"input" event should be never cancelable on ${element.name}`);
         is(event.bubbles, true,
            `"input" event should always bubble on ${element.name}`);
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -2427,20 +2427,21 @@ bool nsTextEditorState::SetValue(const n
 
       // If this is called as part of user input, we need to dispatch "input"
       // event with "insertReplacementText" since web apps may want to know
       // the user operation which changes editor value with a built-in function
       // like autocomplete, password manager, session restore, etc.
       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");
       }
     } else {
       // Even if our value is not actually changing, apparently we need to mark
       // our SelectionProperties dirty to make accessibility tests happy.
       // Probably because they depend on the SetSelectionRange() call we make on
       // our frame in RestoreSelectionState, but I have no idea why they do.
--- a/dom/html/test/forms/test_MozEditableElement_setUserInput.html
+++ b/dom/html/test/forms/test_MozEditableElement_setUserInput.html
@@ -159,16 +159,18 @@ SimpleTest.waitForFocus(() => {
         if (test.type === "number" || test.type === "time") {
           todo(inputEvents[0] instanceof InputEvent,
                `"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
         } else {
           ok(inputEvents[0] instanceof InputEvent,
              `"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),
            `"input" event should be dispatched with Event interface when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
       }
       is(inputEvents[0].cancelable, false,
          `"input" event should be never cancelable (${tag}, before getting focus)`);
       is(inputEvents[0].bubbles, true,
@@ -217,16 +219,18 @@ SimpleTest.waitForFocus(() => {
         if (test.type === "number" || test.type === "time") {
           todo(inputEvents[0] instanceof InputEvent,
                `"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
         } else {
           ok(inputEvents[0] instanceof InputEvent,
              `"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),
            `"input" event should be dispatched with Event interface when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
       }
       is(inputEvents[0].cancelable, false,
          `"input" event should be never cancelable (${tag}, after getting focus)`);
       is(inputEvents[0].bubbles, true,
--- a/dom/tests/mochitest/general/test_clipboard_events.html
+++ b/dom/tests/mochitest/general/test_clipboard_events.html
@@ -243,29 +243,32 @@ add_task(async function test_input_onpas
 
   // Setup an onpaste event handler, and fire paste.  Ensure that the event
   // handler was fired, the clipboard contents didn't change, and that the
   // input value did change (ie. paste succeeded).
   selectContentInput();
   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 {
     synthesizeKey("v", {accelKey: 1});
     ok(onpaste_fired, "paste event firing on plaintext editor");
     is(getClipboardText(), clipboardInitialValue,
       "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 {
     contentInput.onpaste = null;
     contentInput.oninput = null;
   }
 });
 
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -765,16 +765,20 @@ class EditorBase : public nsIEditor,
     EditAction mEditAction;
     EditSubAction mTopLevelEditSubAction;
     EDirection mDirectionOfTopLevelEditSubAction;
 
     AutoEditActionDataSetter() = delete;
     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
    * part of handling edit actions are allowed to call the following protected
    * methods.  However, those methods won't prepare caches of some objects
    * which are necessary for them.  So, if you want some following methods
    * to do that for you, you need to create a wrapper method in public scope
    * and call it.
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -1958,20 +1958,20 @@ class HTMLEditor final : public TextEdit
                                   bool havePrivateHTMLFlavor,
                                   bool aDoDeleteSelection);
 
   /**
    * InsertFromDataTransfer() is called only when user drops data into
    * 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,
                        char16_t** aCfcontext);
 
   nsresult StripFormattingNodes(nsIContent& aNode, bool aOnlyList = false);
   nsresult CreateDOMFragmentFromPaste(
       const nsAString& aInputString, const nsAString& aContextStr,
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -1110,16 +1110,19 @@ nsresult TextEditor::SetText(const nsASt
 }
 
 nsresult TextEditor::ReplaceTextAsAction(
     const nsAString& aString, nsRange* aReplaceRange /* = nullptr */) {
   AutoEditActionDataSetter editActionData(*this, EditAction::eReplaceText);
   if (NS_WARN_IF(!editActionData.CanHandle())) {
     return NS_ERROR_NOT_INITIALIZED;
   }
+  if (!AsHTMLEditor()) {
+    editActionData.SetData(aString);
+  }
 
   AutoPlaceholderBatch treatAsOneTransaction(*this);
 
   // This should emulates inserting text for better undo/redo behavior.
   AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
       *this, EditSubAction::eInsertText, nsIEditor::eNext);
 
   if (!aReplaceRange) {
@@ -2005,16 +2008,17 @@ nsresult TextEditor::PasteAsQuotationAsA
   if (!flav.EqualsLiteral(kUnicodeMime) &&
       !flav.EqualsLiteral(kMozTextInternal)) {
     return NS_OK;
   }
 
   if (nsCOMPtr<nsISupportsString> text = do_QueryInterface(genericDataObj)) {
     nsAutoString stuffToPaste;
     text->GetData(stuffToPaste);
+    editActionData.SetData(stuffToPaste);
     if (!stuffToPaste.IsEmpty()) {
       AutoPlaceholderBatch treatAsOneTransaction(*this);
       rv = InsertWithQuotationsAsSubAction(stuffToPaste);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
   }
--- a/editor/libeditor/TextEditor.h
+++ b/editor/libeditor/TextEditor.h
@@ -398,33 +398,16 @@ class TextEditor : public EditorBase, pu
    *                            content.  Otherwise, false.
    */
   MOZ_CAN_RUN_SCRIPT
   nsresult InsertTextAt(const nsAString& aStringToInsert,
                         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.
    *
    * @param aQuotedText         String to insert.  This will be quoted by ">"
    *                            automatically.
    */
   nsresult InsertWithQuotationsAsSubAction(const nsAString& aQuotedText);
 
--- a/editor/libeditor/TextEditorDataTransfer.cpp
+++ b/editor/libeditor/TextEditorDataTransfer.cpp
@@ -109,28 +109,36 @@ nsresult TextEditor::InsertTextAt(const 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
 nsresult TextEditor::InsertTextFromTransferable(
     nsITransferable* aTransferable) {
+  MOZ_ASSERT(IsEditActionDataAvailable());
+  MOZ_ASSERT(!AsHTMLEditor());
+
   nsAutoCString bestFlavor;
   nsCOMPtr<nsISupports> genericDataObj;
   if (NS_SUCCEEDED(aTransferable->GetAnyTransferData(
           bestFlavor, getter_AddRefs(genericDataObj))) &&
       (bestFlavor.EqualsLiteral(kUnicodeMime) ||
        bestFlavor.EqualsLiteral(kMozTextInternal))) {
     AutoTransactionsConserveSelection dontChangeMySelection(*this);
 
     nsAutoString stuffToPaste;
     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
       nsContentUtils::PlatformToDOMLineBreaks(stuffToPaste);
 
       AutoPlaceholderBatch treatAsOneTransaction(*this);
       nsresult rv = InsertTextAsSubAction(stuffToPaste);
       if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -140,42 +148,16 @@ nsresult TextEditor::InsertTextFromTrans
   }
 
   // Try to scroll the selection into view if the paste/drop succeeded
   ScrollSelectionIntoView(false);
 
   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;
   }
 
   CommitComposition();
 
   AutoEditActionDataSetter editActionData(*this, EditAction::eDrop);
@@ -311,21 +293,68 @@ nsresult TextEditor::OnDrop(DragEvent* a
         return NS_ERROR_EDITOR_DESTROYED;
       }
     }
 
     // XXX Now, Selection may be changed by input event listeners.  If so,
     //     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);
 
   return NS_OK;
 }
 
 nsresult TextEditor::PasteAsAction(int32_t aClipboardType,
--- a/editor/libeditor/tests/test_dragdrop.html
+++ b/editor/libeditor/tests/test_dragdrop.html
@@ -19,22 +19,23 @@
 <script type="application/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
 // This listener allows us to clear the default data for the selection added for the drag.
 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() {
   const htmlContextData = { type: "text/_moz_htmlcontext",
                             data: "<html><body></body></html>" };
   const htmlInfoData = { type: "text/_moz_htmlinfo", data: "0,0" };
   const htmlData = { type: "text/html", data: '<span id="text" style="font-size: 40px;">Some Text</span>' };
 
@@ -97,17 +98,18 @@ function doTest() {
 
   selection.selectAllChildren(text);
   input.value = "";
   inputEvents = [];
   synthesizeDrop(text, input, [], "copy");
   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>
 
   selection.selectAllChildren(text);
   input.value = "";
   input.disabled = true;
   inputEvents = [];
   synthesizeDrop(text, input, [], "copy");
@@ -145,17 +147,18 @@ function doTest() {
   selection.selectAllChildren(text);
   input.value = "";
   inputEvents = [];
   synthesizeDrop(text, input, [[{type: "text/html", data: "Some <b>Bold<b> Text"},
                                 {type: "text/plain", data: "Some Plain Text"}]], "copy");
   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>
 
 // XXXndeakin Can't test textareas due to some event handling issue
 //  selection.selectAllChildren(text);
 //  synthesizeDrop(text, textarea, [[{type: "text/plain", data: "Somewhat Longer Text"}]], "copy");
 //  is(textarea.value, "Somewhat Longer Text", "Drag text/plain onto textarea");
 
@@ -173,54 +176,58 @@ function doTest() {
   selection.selectAllChildren(text);
   inputEvents = [];
   synthesizeDrop(text, contenteditable, [[{type: "text/plain", data: "Sample Text"}]], "copy");
   is(contenteditable.childNodes.length, 3, "Drag text/plain onto contenteditable child nodes");
   is(contenteditable.textContent, "This is some editable text.Sample Text",
                                   "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
 
   selection.selectAllChildren(text);
   inputEvents = [];
   synthesizeDrop(text, contenteditable, [[{type: "text/html", data: "Sample <i>Italic</i> Text"}]], "copy");
   is(contenteditable.childNodes.length, 6, "Drag text/html onto contenteditable child nodes");
   is(contenteditable.childNodes[4].tagName, "I", "Drag text/html onto contenteditable italic");
   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>
 
   selection.selectAllChildren(document.getElementById("bold"));
   inputEvents = [];
   synthesizeDrop(bold, input, [[{type: "text/html", data: "<b>editable</b>"},
                                 {type: "text/plain", data: "editable"}]], "copy");
   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
 
   shouldClear = false;
 
   selection.selectAllChildren(contenteditable.childNodes[4]);
   inputEvents = [];
   synthesizeDrop(contenteditable.childNodes[4], contenteditable, [], "copy");
   is(contenteditable.childNodes.length, 7, "Move text/html and text/plain from contenteditable onto itself child nodes");
   is(contenteditable.childNodes[6].tagName, "I", "Move text/html and text/plain from contenteditable onto itself italic");
   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.
 
   // -------- Test dragging non-editable nested inside contenteditable to contenteditable
 
   input.focus(); // this resets some state in the selection otherwise an inexplicable error occurs calling selectAllChildren.
   input.blur();
@@ -229,17 +236,18 @@ function doTest() {
   selection.selectAllChildren(nonEditable);
   inputEvents = [];
   synthesizeDrop(nonEditable, document.getElementById("first"), [], "copy");
   is(document.getElementById("nestedce").textContent, " MiddleFirst letter Middle Last part",
      "Drag non-editable text/html onto contenteditable text");
   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);
 
   SimpleTest.finish();
 }
 
 SimpleTest.waitForFocus(doTest);
--- a/editor/libeditor/tests/test_middle_click_paste.html
+++ b/editor/libeditor/tests/test_middle_click_paste.html
@@ -71,25 +71,27 @@ async function copyHTMLContent(aInnerHTM
       () => {
         ok(false, `Failed to copy "${aInnerHTML}" to clipboard`);
         SimpleTest.finish();
       },
       "text/html");
   });
 }
 
-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,
      `"input" event should be never cancelable ${aDescription}`);
   is(aEvent.bubbles, true,
      `"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) {
   let inputEvents = [];
   function onInput(aEvent) {
     inputEvents.push(aEvent);
   }
   aTextarea.addEventListener("input", onInput);
@@ -98,53 +100,53 @@ async function doTextareaTests(aTextarea
   aTextarea.focus();
   inputEvents = [];
   synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
   is(aTextarea.value,
      "> abc\n> def\n> ghi\n\n",
      "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");
   aTextarea.focus();
   inputEvents = [];
   synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
   is(aTextarea.value,
      ">> abc\n>> def\n>> ghi\n\n",
      "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");
   aTextarea.focus();
   inputEvents = [];
   synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
   is(aTextarea.value,
      ">> abc\n>> def\n> \n> ghi\n\n",
      "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");
   aTextarea.focus();
   inputEvents = [];
   synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
   is(aTextarea.value,
      "> abc\n> def\n> \n",
      "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;
   function pasteEventLogger(event) {
     pasteEventCount++;
   }
   aTextarea.addEventListener("paste", pasteEventLogger);
 
@@ -168,32 +170,32 @@ async function doTextareaTests(aTextarea
   inputEvents = [];
   synthesizeMouseAtCenter(aTextarea, {button: 1});
   is(aTextarea.value, "abc",
      "Even if 'mouseup' event is consumed, paste should be done");
   is(pasteEventCount, 1,
      "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");
   aTextarea.focus();
   aTextarea.addEventListener("click", (event) => { event.preventDefault(); }, {once: true});
   pasteEventCount = 0;
   inputEvents = [];
   synthesizeMouseAtCenter(aTextarea, {button: 1});
   is(aTextarea.value, "abc",
      "Even if 'click' event handler is added to the <textarea>, paste should not be canceled");
   is(pasteEventCount, 1,
      "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");
   aTextarea.focus();
   aTextarea.addEventListener("auxclick", (event) => { event.preventDefault(); }, {once: true});
   pasteEventCount = 0;
   inputEvents = [];
   synthesizeMouseAtCenter(aTextarea, {button: 1});
@@ -220,17 +222,17 @@ async function doContenteditableTests(aE
   aEditableDiv.focus();
   inputEvents = [];
   synthesizeMouseAtCenter(aEditableDiv, {button: 1, ctrlKey: true});
   is(aEditableDiv.innerHTML,
      "<blockquote type=\"cite\">abc<br>def<br>ghi</blockquote>",
      "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;
   function pasteEventLogger(event) {
     pasteEventCount++;
   }
   aEditableDiv.addEventListener("paste", pasteEventLogger);
 
@@ -254,32 +256,32 @@ async function doContenteditableTests(aE
   inputEvents = [];
   synthesizeMouseAtCenter(aEditableDiv, {button: 1});
   is(aEditableDiv.innerHTML, "abc",
      "Even if 'mouseup' event is consumed, paste should be done");
   is(pasteEventCount, 1,
      "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");
   aEditableDiv.focus();
   aEditableDiv.addEventListener("click", (event) => { event.preventDefault(); }, {once: true});
   pasteEventCount = 0;
   inputEvents = [];
   synthesizeMouseAtCenter(aEditableDiv, {button: 1});
   is(aEditableDiv.innerHTML, "abc",
      "Even if 'click' event handler is added to the editing host, paste should not be canceled");
   is(pasteEventCount, 1,
      "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");
   aEditableDiv.focus();
   aEditableDiv.addEventListener("auxclick", (event) => { event.preventDefault(); }, {once: true});
   pasteEventCount = 0;
   inputEvents = [];
   synthesizeMouseAtCenter(aEditableDiv, {button: 1});
@@ -310,17 +312,17 @@ async function doContenteditableTests(aE
   } else {
     // Oddly, on Android, we use <br> elements for pasting <p> elements.
     is(aEditableDiv.innerHTML,
        "<blockquote type=\"cite\">abc<br><br>def<br><br>ghi</blockquote>",
        "Pasted HTML content should be set to the <blockquote>");
   }
   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);
 }
 
 async function doNestedEditorTests(aEditableDiv) {
   await copyPlaintext("CLIPBOARD TEXT");
   aEditableDiv.innerHTML = '<p id="p">foo</p><textarea id="textarea"></textarea>';
--- a/editor/libeditor/tests/test_undo_after_spellchecker_replaces_word.html
+++ b/editor/libeditor/tests/test_undo_after_spellchecker_replaces_word.html
@@ -16,25 +16,27 @@ SimpleTest.waitForExplicitFinish();
 SimpleTest.waitForFocus(() => {
   let textarea = document.getElementById("textarea");
   let editor = SpecialPowers.wrap(textarea).editor;
 
   let inlineSpellChecker = editor.getInlineSpellChecker(true);
 
   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,
        `"input" event should be never cancelable ${aDescription}`);
     is(aEvent.bubbles, true,
        `"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 = [];
   function onInput(aEvent) {
     inputEvents.push(aEvent);
   }
 
   SpecialPowers.Cu.import(
@@ -49,33 +51,36 @@ SimpleTest.waitForFocus(() => {
       is(misspelledWord.endOffset, 7,
          "Misspelled word should end at 7");
       inputEvents = [];
       inlineSpellChecker.replaceWord(editor.rootElement.firstChild, 5, "aux");
       is(textarea.value, "abc aux abc",
          "'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 });
       is(textarea.value, "abc abx abc",
          "'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 });
       is(textarea.value, "abc aux abc",
          "'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);
 
       SimpleTest.finish();
     });
   });
 });
 </script>
--- a/toolkit/components/satchel/test/test_form_autocomplete.html
+++ b/toolkit/components/satchel/test/test_form_autocomplete.html
@@ -996,16 +996,18 @@ function runTest() { // eslint-disable-l
       input.addEventListener("input", function(event) {
         ok(true, testNum + " oninput should have been received");
         ok(event instanceof InputEvent,
            testNum + " input event should be dispatched with InputEvent interface");
         ok(event.bubbles, testNum + " input event should bubble");
         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");
       checkForm("");
       synthesizeKey("KEY_Enter");
       checkForm("value1");
       testNum = 599;
       setTimeout(runTest, 100);
--- a/toolkit/components/satchel/test/test_form_autocomplete_with_list.html
+++ b/toolkit/components/satchel/test/test_form_autocomplete_with_list.html
@@ -462,16 +462,17 @@ function runTest() {
       // Check that the input event is fired.
       input.addEventListener("input", function(event) {
         ok(true, "oninput should have been received");
         ok(event instanceof InputEvent,
            "input event should be dispatched with InputEvent interface");
         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();
       }, {once: true});
 
       synthesizeKey("KEY_ArrowDown");
       checkForm("");
       synthesizeKey("KEY_Enter");
--- a/toolkit/components/satchel/test/test_submit_on_keydown_enter.html
+++ b/toolkit/components/satchel/test/test_submit_on_keydown_enter.html
@@ -38,16 +38,18 @@ function handleInput(aEvent) {
   ok(aEvent instanceof InputEvent,
      '"input" event should be dispatched with InputEvent interface');
   is(aEvent.cancelable, false,
      '"input" event should be never cancelable');
   is(aEvent.bubbles, true,
      '"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();
 }
 
 function runTest() {
   input.addEventListener("input", handleInput, true);
   input.addEventListener("keydown", function handleEnterDown(e) {
     if (e.keyCode != KeyEvent.DOM_VK_RETURN) {
--- a/toolkit/content/tests/chrome/file_editor_with_autocomplete.js
+++ b/toolkit/content/tests/chrome/file_editor_with_autocomplete.js
@@ -119,17 +119,17 @@ nsDoTestsForEditorWithAutoComplete.proto
     { description: "Undo/Redo behavior check when typed text exactly matches the case: select 'Mozilla' to complete the word",
       completeDefaultIndex: false,
       execute(aWindow, aTarget) {
         synthesizeKey("KEY_ArrowDown", {}, aWindow);
         synthesizeKey("KEY_Enter", {}, aWindow);
         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",
       completeDefaultIndex: false,
       execute(aWindow, aTarget) {
         synthesizeKey("z", { accelKey: true }, aWindow);
         return true;
       }, popup: true, value: "Mo", searchString: "Mo",
@@ -194,17 +194,17 @@ nsDoTestsForEditorWithAutoComplete.proto
     { description: "Undo/Redo behavior check when typed text does not match the case: select 'Mozilla' to complete the word",
       completeDefaultIndex: false,
       execute(aWindow, aTarget) {
         synthesizeKey("KEY_ArrowDown", {}, aWindow);
         synthesizeKey("KEY_Enter", {}, aWindow);
         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",
       completeDefaultIndex: false,
       execute(aWindow, aTarget) {
         synthesizeKey("z", { accelKey: true }, aWindow);
         return true;
       }, popup: true, value: "mo", searchString: "mo",
@@ -264,17 +264,17 @@ nsDoTestsForEditorWithAutoComplete.proto
         }
         synthesizeKey("M", { shiftKey: true }, aWindow);
         synthesizeKey("o", {}, aWindow);
         return true;
       }, popup: true, value: "Mozilla", searchString: "Mo",
       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",
       completeDefaultIndex: true,
       execute(aWindow, aTarget) {
         // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
         if (aTarget.tagName === "textbox") {
           return false;
@@ -321,17 +321,17 @@ nsDoTestsForEditorWithAutoComplete.proto
         if (aTarget.tagName === "textbox") {
           return false;
         }
         synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow);
         return true;
       }, 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",
       completeDefaultIndex: true,
       execute(aWindow, aTarget) {
         // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
         if (aTarget.tagName === "textbox") {
           return false;
@@ -367,32 +367,32 @@ nsDoTestsForEditorWithAutoComplete.proto
         }
         synthesizeKey("m", {}, aWindow);
         synthesizeKey("o", {}, aWindow);
         return true;
       }, popup: true, value: "mozilla", searchString: "mo",
       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",
       completeDefaultIndex: true,
       execute(aWindow, aTarget) {
         // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
         if (aTarget.tagName === "textbox") {
           return false;
         }
         synthesizeKey("KEY_ArrowDown", {}, aWindow);
         synthesizeKey("KEY_Enter", {}, aWindow);
         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.
     // Although we could make this transaction ignored.
     { description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): undo the selected word, but typed text shouldn't be canceled",
       completeDefaultIndex: true,
       execute(aWindow, aTarget) {
         // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
@@ -445,17 +445,17 @@ nsDoTestsForEditorWithAutoComplete.proto
         if (aTarget.tagName === "textbox") {
           return false;
         }
         synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow);
         return true;
       }, 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",
       completeDefaultIndex: true,
       execute(aWindow, aTarget) {
         // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
         if (aTarget.tagName === "textbox") {
           return false;
--- a/widget/EventForwards.h
+++ b/widget/EventForwards.h
@@ -134,16 +134,21 @@ enum class EditorInputType : EditorInput
  * IsDataAvailableOnTextEditor() returns true if aInputType on TextEditor
  * should have non-null InputEvent.data value.
  */
 inline bool IsDataAvailableOnTextEditor(EditorInputType aInputType) {
   switch (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;
   }
 }
 
 /**
  * IsDataAvailableOnHTMLEditor() returns true if aInputType on HTMLEditor