Bug 998941 - part 2-2: Make HTMLEditor set InputEvent.dataTransfer when InputEvent.inputType is "insertFromPaste", "insertFromDrop" or "insertReplacementText" r=smaug,m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 19 Feb 2019 06:33:42 +0000
changeset 517734 b259d7a34058346d36896febd5cc1c94ed63a5ca
parent 517733 5b4aa09a20df7251ea54dea5e01687d30cbf4401
child 517735 14aa082701576b9cd9b73dc527c86ffa85df752a
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [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 2-2: Make HTMLEditor set InputEvent.dataTransfer when InputEvent.inputType is "insertFromPaste", "insertFromDrop" or "insertReplacementText" r=smaug,m_kato InputEvent.dataTransfer should be set to non-null when InputEvent.inputType is "insertFromPaste", "insertFromDrop" or "insertReplacementText" and editor is an HTMLEditor instance: https://rawgit.com/w3c/input-events/v1/index.html#dfn-data https://w3c.github.io/input-events/#dfn-data ("insertTranspose" and "insertFromYank" are not currently supported on Gecko.) This patch makes nsContentUtils::DispatchInputEvent() take dataTransfer value and EditorBase set it via AutoEditActionDataSetter like data value. However, we need to create other constructors of DataTransfer to create its read-only instances initialized with nsITransferable or nsAString. This will be implemented by the following patch. Differential Revision: https://phabricator.services.mozilla.com/D19297
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/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/base/test/chrome/window_nsITextInputProcessor.xul
dom/html/test/forms/test_MozEditableElement_setUserInput.html
dom/html/test/forms/test_input_event.html
dom/tests/mochitest/general/test_clipboard_events.html
editor/libeditor/EditorBase.cpp
editor/libeditor/EditorBase.h
editor/libeditor/HTMLEditorDataTransfer.cpp
editor/libeditor/TextEditor.cpp
editor/libeditor/TextEditorDataTransfer.cpp
editor/libeditor/tests/test_abs_positioner_positioning_elements.html
editor/libeditor/tests/test_dom_input_event_on_htmleditor.html
editor/libeditor/tests/test_dom_input_event_on_texteditor.html
editor/libeditor/tests/test_dragdrop.html
editor/libeditor/tests/test_middle_click_paste.html
editor/libeditor/tests/test_nsIEditorMailSupport_insertAsCitedQuotation.html
editor/libeditor/tests/test_nsIHTMLEditor_removeInlineProperty.html
editor/libeditor/tests/test_nsIPlaintextEditor_insertLineBreak.html
editor/libeditor/tests/test_nsITableEditor_deleteTableCell.html
editor/libeditor/tests/test_nsITableEditor_deleteTableCellContents.html
editor/libeditor/tests/test_nsITableEditor_deleteTableColumn.html
editor/libeditor/tests/test_nsITableEditor_deleteTableRow.html
editor/libeditor/tests/test_nsITableEditor_insertTableCell.html
editor/libeditor/tests/test_nsITableEditor_insertTableColumn.html
editor/libeditor/tests/test_nsITableEditor_insertTableRow.html
editor/libeditor/tests/test_resizers_resizing_elements.html
editor/libeditor/tests/test_undo_after_spellchecker_replaces_word.html
editor/libeditor/tests/test_undo_redo_stack_after_setting_value.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
widget/tests/window_composition_text_querycontent.xul
--- a/browser/extensions/formautofill/test/mochitest/creditCard/test_clear_form.html
+++ b/browser/extensions/formautofill/test/mochitest/creditCard/test_clear_form.html
@@ -71,16 +71,18 @@ async function confirmClear(selector) {
       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");
+      is(event.dataTransfer, null,
+         "dataTransfer value should be null");
       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
@@ -116,16 +116,18 @@ function triggerAutofillAndCheckProfile(
       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}"`);
+          is(event.dataTransfer, null,
+             "dataTransfer should be null");
         } 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
@@ -51,16 +51,18 @@ function checkElementFilled(element, exp
         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");
+          is(event.dataTransfer, null,
+             "dataTransfer value should be null");
         } 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/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -4126,16 +4126,23 @@ nsresult nsContentUtils::DispatchEvent(D
 
 // static
 nsresult nsContentUtils::DispatchInputEvent(Element* aEventTargetElement) {
   RefPtr<TextEditor> textEditor;  // See bug 1506439
   return DispatchInputEvent(aEventTargetElement, EditorInputType::eUnknown,
                             textEditor, InputEventOptions());
 }
 
+nsContentUtils::InputEventOptions::InputEventOptions(
+    DataTransfer* aDataTransfer)
+    : mDataTransfer(aDataTransfer) {
+  MOZ_ASSERT(mDataTransfer);
+  MOZ_ASSERT(mDataTransfer->IsReadOnly());
+}
+
 // static
 nsresult nsContentUtils::DispatchInputEvent(Element* aEventTargetElement,
                                             EditorInputType aEditorInputType,
                                             TextEditor* aTextEditor,
                                             const InputEventOptions& aOptions) {
   if (NS_WARN_IF(!aEventTargetElement)) {
     return NS_ERROR_INVALID_ARG;
   }
@@ -4244,22 +4251,32 @@ nsresult nsContentUtils::DispatchInputEv
     }
 #endif  // #ifdef DEBUG
   } else {
     MOZ_ASSERT(aTextEditor->AsHTMLEditor());
     if (IsDataAvailableOnHTMLEditor(aEditorInputType)) {
       inputEvent.mData = aOptions.mData;
       MOZ_ASSERT(!inputEvent.mData.IsVoid(),
                  "inputEvent.mData shouldn't be void");
-    }
+    } else {
+      MOZ_ASSERT(inputEvent.mData.IsVoid(), "inputEvent.mData should be void");
+      if (IsDataTransferAvailableOnHTMLEditor(aEditorInputType)) {
+        inputEvent.mDataTransfer = aOptions.mDataTransfer;
+        MOZ_ASSERT(inputEvent.mDataTransfer,
+                   "inputEvent.mDataTransfer shouldn't be nullptr");
+        MOZ_ASSERT(inputEvent.mDataTransfer->IsReadOnly(),
+                   "inputEvent.mDataTransfer should be read only");
+      }
 #ifdef DEBUG
-    else {
-      MOZ_ASSERT(inputEvent.mData.IsVoid(), "inputEvent.mData should be void");
-    }
+      else {
+        MOZ_ASSERT(!inputEvent.mDataTransfer,
+                   "inputEvent.mDataTransfer should be nullptr");
+      }
 #endif  // #ifdef DEBUG
+    }
   }
 
   inputEvent.mInputType = aEditorInputType;
 
   (new AsyncEventDispatcher(aEventTargetElement, inputEvent))
       ->RunDOMEventWhenSafe();
   return NS_OK;
 }
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -127,16 +127,17 @@ class Dispatcher;
 class ErrorResult;
 class EventListenerManager;
 class HTMLEditor;
 class TextEditor;
 
 namespace dom {
 class ContentFrameMessageManager;
 struct CustomElementDefinition;
+class DataTransfer;
 class DocumentFragment;
 class Element;
 class Event;
 class EventTarget;
 class HTMLInputElement;
 class IPCDataTransfer;
 class IPCDataTransferItem;
 struct LifecycleCallbackArgs;
@@ -1418,19 +1419,22 @@ class nsContentUtils {
    * @param aOptions            Optional.  If aEditorInputType value requires
    *                            some additional data, they should be properly
    *                            set with this argument.
    */
   MOZ_CAN_RUN_SCRIPT
   static nsresult DispatchInputEvent(Element* aEventTarget);
   struct MOZ_STACK_CLASS InputEventOptions final {
     InputEventOptions() = default;
-    explicit InputEventOptions(const nsAString& aData) : mData(aData) {}
+    explicit InputEventOptions(const nsAString& aData)
+        : mData(aData), mDataTransfer(nullptr) {}
+    explicit InputEventOptions(mozilla::dom::DataTransfer* aDataTransfer);
 
     nsString mData;
+    mozilla::dom::DataTransfer* mDataTransfer;
   };
   MOZ_CAN_RUN_SCRIPT
   static nsresult DispatchInputEvent(Element* aEventTarget,
                                      mozilla::EditorInputType aEditorInputType,
                                      mozilla::TextEditor* aTextEditor,
                                      const InputEventOptions& aOptions);
 
   /**
--- a/dom/base/test/chrome/window_nsITextInputProcessor.xul
+++ b/dom/base/test/chrome/window_nsITextInputProcessor.xul
@@ -64,16 +64,17 @@ function checkInputEvent(aEvent, aIsComp
     return;
   }
   ok(aEvent instanceof InputEvent, `${aDescription}"input" event should be dispatched with InputEvent interface`);
   is(aEvent.cancelable, false, `${aDescription}"input" event should be never cancelable`);
   is(aEvent.bubbles, true, `${aDescription}"input" event should always bubble`);
   is(aEvent.isComposing, aIsComposing, `${aDescription}isComposing should be ${aIsComposing}`);
   is(aEvent.inputType, aInputType, `${aDescription}inputType should be "${aInputType}"`);
   is(aEvent.data, aData, `${aDescription}data should be "${aData}"`);
+  is(aEvent.dataTransfer, null, `${aDescription}dataTransfer should be null`);
 }
 
 const kIsMac = (navigator.platform.indexOf("Mac") == 0);
 
 var iframe = document.getElementById("iframe");
 var childWindow = iframe.contentWindow;
 var textareaInFrame;
 var input = document.getElementById("input");
--- a/dom/html/test/forms/test_MozEditableElement_setUserInput.html
+++ b/dom/html/test/forms/test_MozEditableElement_setUserInput.html
@@ -161,16 +161,18 @@ SimpleTest.waitForFocus(() => {
                `"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`);
+          is(inputEvents[0].dataTransfer, null,
+             `dataTransfer should be null 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,
@@ -221,16 +223,18 @@ SimpleTest.waitForFocus(() => {
                `"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`);
+          is(inputEvents[0].dataTransfer, null,
+             `dataTransfer should be null 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/html/test/forms/test_input_event.html
+++ b/dom/html/test/forms/test_input_event.html
@@ -50,16 +50,18 @@ https://bugzilla.mozilla.org/show_bug.cg
          `"input" event should be dispatched with InputEvent interface ${aDescription}`);
     } else {
       ok(aEvent instanceof InputEvent,
          `"input" event should be dispatched with InputEvent interface ${aDescription}`);
       is(aEvent.inputType, expectedInputType,
          `inputType should be "${expectedInputType}" ${aDescription}`);
       is(aEvent.data, expectedData,
          `data should be ${expectedData} ${aDescription}`);
+      is(aEvent.dataTransfer, null,
+         `dataTransfer should be null ${aDescription}`);
     }
     is(aEvent.cancelable, false,
        `"input" event should be never cancelable ${aDescription}`);
     is(aEvent.bubbles, true,
        `"input" event should always bubble ${aDescription}`);
   }
 
   function checkIfInputIsEvent(aEvent, aDescription) {
--- a/dom/tests/mochitest/general/test_clipboard_events.html
+++ b/dom/tests/mochitest/general/test_clipboard_events.html
@@ -213,28 +213,33 @@ add_task(async function test_input_oncut
 
   // Setup an oncut event handler, and fire cut.  Ensure that the event
   // handler was fired, the clipboard contains the INPUT TEXT, and
   // that the input itself is empty.
   selectContentInput();
   var oncut_fired = false;
   var oninput_count = 0;
   var inputType = "";
+  var data, dataTransfer;
   contentInput.oncut = function() { oncut_fired = true; };
   contentInput.oninput = function (aEvent) {
     oninput_count++;
     inputType = aEvent.inputType;
+    data = aEvent.data;
+    dataTransfer = aEvent.dataTransfer;
   };
   try {
     await putOnClipboard("INPUT TEXT", () => {
       synthesizeKey("x", {accelKey: 1});
     }, "cut on plaintext editor set clipboard correctly");
     ok(oncut_fired, "cut event firing on plaintext editor");
     is(oninput_count, 1, "input event should be fired once by cut");
     is(inputType, "deleteByCut", "inputType of the input event should be \"deleteByCut\"");
+    is(data, null, "data of the input event should be null when inputType is \"deleteByCut\"");
+    is(dataTransfer, null, "dataTransfer of the input event should be null when inputType is \"deleteByCut\"");
     is(contentInput.value, "",
       "cut on plaintext editor emptied editor");
   } finally {
     contentInput.oncut = null;
     contentInput.oninput = null;
   }
 });
 
@@ -243,32 +248,34 @@ 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;
+  var data, dataTransfer;
   contentInput.onpaste = function() { onpaste_fired = true; };
   contentInput.oninput = function(aEvent) {
     oninput_count++;
     inputType = aEvent.inputType;
     data = aEvent.data;
+    dataTransfer = aEvent.dataTransfer;
   };
 
   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(dataTransfer, null, "dataTransfer of the input event should be null even when inputType is \"insertFromPaste\"");
     is(contentInput.value, clipboardInitialValue,
       "paste on plaintext editor did modify editor value");
   } finally {
     contentInput.onpaste = null;
     contentInput.oninput = null;
   }
 });
 
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -46,16 +46,17 @@
 #include "mozilla/Services.h"           // for GetObserverService
 #include "mozilla/ServoCSSParser.h"     // for ServoCSSParser
 #include "mozilla/TextComposition.h"    // for TextComposition
 #include "mozilla/TextInputListener.h"  // for TextInputListener
 #include "mozilla/TextServicesDocument.h"  // for TextServicesDocument
 #include "mozilla/TextEvents.h"
 #include "mozilla/TransactionManager.h"  // for TransactionManager
 #include "mozilla/dom/CharacterData.h"   // for CharacterData
+#include "mozilla/dom/DataTransfer.h"    // for DataTransfer
 #include "mozilla/dom/Element.h"         // for Element, nsINode::AsElement
 #include "mozilla/dom/EventTarget.h"     // for EventTarget
 #include "mozilla/dom/HTMLBodyElement.h"
 #include "mozilla/dom/Text.h"
 #include "mozilla/dom/Event.h"
 #include "nsAString.h"                // for nsAString::Length, etc.
 #include "nsCCUncollectableMarker.h"  // for nsCCUncollectableMarker
 #include "nsCaret.h"                  // for nsCaret
@@ -86,16 +87,17 @@
 #include "nsNameSpaceManager.h"        // for kNameSpaceID_None, etc.
 #include "nsINode.h"                   // for nsINode, etc.
 #include "nsIPlaintextEditor.h"        // for nsIPlaintextEditor, etc.
 #include "nsIPresShell.h"              // for nsIPresShell
 #include "nsISelectionController.h"    // for nsISelectionController, etc.
 #include "nsISelectionDisplay.h"       // for nsISelectionDisplay, etc.
 #include "nsISupportsBase.h"           // for nsISupports
 #include "nsISupportsUtils.h"          // for NS_ADDREF, NS_IF_ADDREF
+#include "nsITransferable.h"           // for nsITransferable
 #include "nsITransaction.h"            // for nsITransaction
 #include "nsITransactionManager.h"
 #include "nsIWeakReference.h"  // for nsISupportsWeakReference
 #include "nsIWidget.h"         // for nsIWidget, IMEState, etc.
 #include "nsPIDOMWindow.h"     // for nsPIDOMWindow
 #include "nsPresContext.h"     // for nsPresContext
 #include "nsRange.h"           // for nsRange
 #include "nsReadableUtils.h"   // for EmptyString, ToNewCString
@@ -2027,18 +2029,23 @@ void EditorBase::NotifyEditorObservers(
       }
       break;
     default:
       MOZ_CRASH("Handle all notifications here");
       break;
   }
 }
 
-void EditorBase::FireInputEvent(EditAction aEditAction,
-                                const nsAString& aData) {
+void EditorBase::FireInputEvent() {
+  RefPtr<DataTransfer> dataTransfer = GetInputEventDataTransfer();
+  FireInputEvent(GetEditAction(), GetInputEventData(), dataTransfer);
+}
+
+void EditorBase::FireInputEvent(EditAction aEditAction, const nsAString& aData,
+                                DataTransfer* aDataTransfer) {
   MOZ_ASSERT(IsEditActionDataAvailable());
 
   // We don't need to dispatch multiple input events if there is a pending
   // input event.  However, it may have different event target.  If we resolved
   // this issue, we need to manage the pending events in an array.  But it's
   // overwork.  We don't need to do it for the very rare case.
   // TODO: However, we start to set InputEvent.inputType.  So, each "input"
   //       event now notifies web app each change.  So, perhaps, we should
@@ -2046,17 +2053,18 @@ void EditorBase::FireInputEvent(EditActi
 
   RefPtr<Element> targetElement = GetInputEventTargetElement();
   if (NS_WARN_IF(!targetElement)) {
     return;
   }
   RefPtr<TextEditor> textEditor = AsTextEditor();
   DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
       targetElement, ToInputType(aEditAction), textEditor,
-      nsContentUtils::InputEventOptions(aData));
+      aDataTransfer ? nsContentUtils::InputEventOptions(aDataTransfer)
+                    : nsContentUtils::InputEventOptions(aData));
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                        "Failed to dispatch input event");
 }
 
 NS_IMETHODIMP
 EditorBase::AddEditActionListener(nsIEditActionListener* aListener) {
   NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER);
 
@@ -4898,9 +4906,51 @@ void EditorBase::AutoEditActionDataSette
     return;
   }
 
   // Get serialized color value (i.e., "rgb()" or "rgba()").
   nsStyleUtil::GetSerializedColorValue(color, mData);
   MOZ_ASSERT(!mData.IsVoid());
 }
 
+void EditorBase::AutoEditActionDataSetter::InitializeDataTransfer(
+    DataTransfer* aDataTransfer) {
+  MOZ_ASSERT(aDataTransfer);
+  MOZ_ASSERT(aDataTransfer->IsReadOnly());
+  mDataTransfer = aDataTransfer;
+}
+
+void EditorBase::AutoEditActionDataSetter::InitializeDataTransfer(
+    nsITransferable* aTransferable) {
+  MOZ_ASSERT(aTransferable);
+
+  Document* document = mEditorBase.GetDocument();
+  nsIGlobalObject* scopeObject =
+      document ? document->GetScopeObject() : nullptr;
+  // TODO: Implement DataTransfer constructor which takes nsITransferable.
+  mDataTransfer =
+      new DataTransfer(scopeObject, ePaste, true /* is external */, 1);
+}
+
+void EditorBase::AutoEditActionDataSetter::InitializeDataTransfer(
+    const nsAString& aString) {
+  Document* document = mEditorBase.GetDocument();
+  nsIGlobalObject* scopeObject =
+      document ? document->GetScopeObject() : nullptr;
+  // TODO: Implement DataTransfer constructor which takes nsAString.
+  mDataTransfer =
+      new DataTransfer(scopeObject, ePaste, true /* is external */, 1);
+}
+
+void EditorBase::AutoEditActionDataSetter::InitializeDataTransferWithClipboard(
+    SettingDataTransfer aSettingDataTransfer, int32_t aClipboardType) {
+  Document* document = mEditorBase.GetDocument();
+  nsIGlobalObject* scopeObject =
+      document ? document->GetScopeObject() : nullptr;
+  mDataTransfer =
+      new DataTransfer(scopeObject,
+                       aSettingDataTransfer == SettingDataTransfer::eWithFormat
+                           ? ePaste
+                           : ePasteNoFormatting,
+                       true /* is external */, aClipboardType);
+}
+
 }  // namespace mozilla
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -12,16 +12,17 @@
 #include "mozilla/Maybe.h"               // for Maybe
 #include "mozilla/OwningNonNull.h"       // for OwningNonNull
 #include "mozilla/PresShell.h"           // for PresShell
 #include "mozilla/RangeBoundary.h"       // for RawRangeBoundary, RangeBoundary
 #include "mozilla/SelectionState.h"      // for RangeUpdater, etc.
 #include "mozilla/StyleSheet.h"          // for StyleSheet
 #include "mozilla/TransactionManager.h"  // for TransactionManager
 #include "mozilla/WeakPtr.h"             // for WeakPtr
+#include "mozilla/dom/DataTransfer.h"    // for dom::DataTransfer
 #include "mozilla/dom/Selection.h"
 #include "mozilla/dom/Text.h"
 #include "nsCOMPtr.h"  // for already_AddRefed, nsCOMPtr
 #include "nsCycleCollectionParticipant.h"
 #include "nsGkAtoms.h"
 #include "mozilla/dom/Document.h"
 #include "nsIContentInlines.h"       // for nsINode::IsEditable()
 #include "nsIEditor.h"               // for nsIEditor, etc.
@@ -41,16 +42,17 @@ class mozInlineSpellChecker;
 class nsAtom;
 class nsIContent;
 class nsIDocumentStateListener;
 class nsIEditActionListener;
 class nsIEditorObserver;
 class nsINode;
 class nsIPresShell;
 class nsISupports;
+class nsITransferable;
 class nsITransaction;
 class nsITransactionListener;
 class nsIWidget;
 class nsRange;
 
 namespace mozilla {
 class AutoSelectionRestorer;
 class AutoTopLevelEditSubActionNotifier;
@@ -626,16 +628,27 @@ class EditorBase : public nsIEditor,
    * current editor state. When editor session is destroyed, it always reset
    * selection state even if this has no focus.  So if destroying editor,
    * we have to call this method for focused editor to set selection state.
    */
   void ReinitializeSelection(Element& aElement);
 
  protected:  // AutoEditActionDataSetter, this shouldn't be accessed by friends.
   /**
+   * SettingDataTransfer enum class is used to specify whether DataTransfer
+   * should be initialized with or without format.  For example, when user
+   * uses Accel + Shift + V to paste text without format, DataTransfer should
+   * have only plain/text data to make web apps treat it without format.
+   */
+  enum class SettingDataTransfer {
+    eWithFormat,
+    eWithoutFormat,
+  };
+
+  /**
    * AutoEditActionDataSetter grabs some necessary objects for handling any
    * edit actions and store the edit action what we're handling.  When this is
    * created, its pointer is set to the mEditActionData, and this guarantees
    * the lifetime of grabbing objects until it's destroyed.
    */
   class MOZ_STACK_CLASS AutoEditActionDataSetter final {
    public:
     AutoEditActionDataSetter(const EditorBase& aEditorBase,
@@ -649,16 +662,42 @@ class EditorBase : public nsIEditor,
     const RefPtr<Selection>& SelectionRefPtr() const { return mSelection; }
     EditAction GetEditAction() const { return mEditAction; }
 
     void SetData(const nsAString& aData) { mData = aData; }
     const nsString& GetData() const { return mData; }
 
     void SetColorData(const nsAString& aData);
 
+    /**
+     * InitializeDataTransfer(DataTransfer*) sets mDataTransfer to
+     * aDataTransfer.  In this case, aDataTransfer should not be read/write
+     * because it'll be set to InputEvent.dataTransfer and which should be
+     * read-only.
+     */
+    void InitializeDataTransfer(dom::DataTransfer* aDataTransfer);
+    /**
+     * InitializeDataTransfer(nsITransferable*) creates new DataTransfer
+     * instance, initializes it with aTransferable and sets mDataTransfer to
+     * it.
+     */
+    void InitializeDataTransfer(nsITransferable* aTransferable);
+    /**
+     * InitializeDataTransfer(const nsAString&) creates new DataTransfer
+     * instance, initializes it with aString and sets mDataTransfer to it.
+     */
+    void InitializeDataTransfer(const nsAString& aString);
+    /**
+     * InitializeDataTransferWithClipboard() creates new DataTransfer instance,
+     * initializes it with clipboard and sets mDataTransfer to it.
+     */
+    void InitializeDataTransferWithClipboard(
+        SettingDataTransfer aSettingDataTransfer, int32_t aClipboardType);
+    dom::DataTransfer* GetDataTransfer() const { return mDataTransfer; }
+
     void SetTopLevelEditSubAction(EditSubAction aEditSubAction,
                                   EDirection aDirection = eNone) {
       mTopLevelEditSubAction = aEditSubAction;
       switch (mTopLevelEditSubAction) {
         case EditSubAction::eInsertNode:
         case EditSubAction::eCreateNode:
         case EditSubAction::eSplitNode:
         case EditSubAction::eInsertText:
@@ -759,16 +798,19 @@ class EditorBase : public nsIEditor,
     SelectionState mSavedSelection;
 
     // Utility class object for maintaining preserved ranges.
     RangeUpdater mRangeUpdater;
 
     // The data should be set to InputEvent.data.
     nsString mData;
 
+    // The dataTransfer should be set to InputEvent.dataTransfer.
+    RefPtr<dom::DataTransfer> mDataTransfer;
+
     EditAction mEditAction;
     EditSubAction mTopLevelEditSubAction;
     EDirection mDirectionOfTopLevelEditSubAction;
 
     AutoEditActionDataSetter() = delete;
     AutoEditActionDataSetter(const AutoEditActionDataSetter& aOther) = delete;
   };
 
@@ -816,16 +858,25 @@ class EditorBase : public nsIEditor,
    * GetInputEventData() returns inserting or inserted text value with
    * current edit action.  The result is proper for InputEvent.data value.
    */
   const nsString& GetInputEventData() const {
     return mEditActionData ? mEditActionData->GetData() : VoidString();
   }
 
   /**
+   * GetInputEventDataTransfer() returns inserting or inserted transferable
+   * content with current edit action.  The result is proper for
+   * InputEvent.dataTransfer value.
+   */
+  dom::DataTransfer* GetInputEventDataTransfer() const {
+    return mEditActionData ? mEditActionData->GetDataTransfer() : nullptr;
+  }
+
+  /**
    * GetTopLevelEditSubAction() returns the top level edit sub-action.
    * For example, if selected content is being replaced with inserted text,
    * while removing selected content, the top level edit sub-action may be
    * EditSubAction::eDeleteSelectedContent.  However, while inserting new
    * text, the top level edit sub-action may be EditSubAction::eInsertText.
    * So, this result means what we are doing right now unless you're looking
    * for a case which the method is called via mutation event listener or
    * selectionchange event listener which are fired while handling the edit
@@ -1801,21 +1852,20 @@ class EditorBase : public nsIEditor,
 
   nsresult DetermineCurrentDirection();
 
   /**
    * FireInputEvent() dispatches an "input" event synchronously or
    * asynchronously if it's not safe to dispatch.
    */
   MOZ_CAN_RUN_SCRIPT
-  void FireInputEvent() {
-    FireInputEvent(GetEditAction(), GetInputEventData());
-  }
+  void FireInputEvent();
   MOZ_CAN_RUN_SCRIPT
-  void FireInputEvent(EditAction aEditAction, const nsAString& aData);
+  void FireInputEvent(EditAction aEditAction, const nsAString& aData,
+                      dom::DataTransfer* aDataTransfer);
 
   /**
    * Called after a transaction is done successfully.
    */
   void DoAfterDoTransaction(nsITransaction* aTxn);
 
   /**
    * Called after a transaction is undone successfully.
--- a/editor/libeditor/HTMLEditorDataTransfer.cpp
+++ b/editor/libeditor/HTMLEditorDataTransfer.cpp
@@ -1538,16 +1538,17 @@ nsresult HTMLEditor::PasteInternal(int32
   return NS_OK;
 }
 
 nsresult HTMLEditor::PasteTransferable(nsITransferable* aTransferable) {
   AutoEditActionDataSetter editActionData(*this, EditAction::ePaste);
   if (NS_WARN_IF(!editActionData.CanHandle())) {
     return NS_ERROR_NOT_INITIALIZED;
   }
+  editActionData.InitializeDataTransfer(aTransferable);
 
   // Use an invalid value for the clipboard type as data comes from
   // aTransferable and we don't currently implement a way to put that in the
   // data transfer yet.
   if (!FireClipboardEvent(ePaste, nsIClipboard::kGlobalClipboard)) {
     return NS_OK;
   }
 
@@ -1564,16 +1565,18 @@ nsresult HTMLEditor::PasteTransferable(n
  * HTML PasteNoFormatting. Ignore any HTML styles and formating in paste source.
  */
 NS_IMETHODIMP
 HTMLEditor::PasteNoFormatting(int32_t aSelectionType) {
   AutoEditActionDataSetter editActionData(*this, EditAction::ePaste);
   if (NS_WARN_IF(!editActionData.CanHandle())) {
     return NS_ERROR_NOT_INITIALIZED;
   }
+  editActionData.InitializeDataTransferWithClipboard(
+      SettingDataTransfer::eWithoutFormat, aSelectionType);
 
   if (!FireClipboardEvent(ePasteNoFormatting, aSelectionType)) {
     return NS_OK;
   }
 
   CommitComposition();
 
   // Get Clipboard Service
@@ -1704,16 +1707,18 @@ nsresult HTMLEditor::PasteAsQuotationAsA
                                               bool aDispatchPasteEvent) {
   MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard ||
              aClipboardType == nsIClipboard::kSelectionClipboard);
 
   AutoEditActionDataSetter editActionData(*this, EditAction::ePaste);
   if (NS_WARN_IF(!editActionData.CanHandle())) {
     return NS_ERROR_NOT_INITIALIZED;
   }
+  editActionData.InitializeDataTransferWithClipboard(
+      SettingDataTransfer::eWithFormat, aClipboardType);
 
   if (IsPlaintextEditor()) {
     // XXX In this case, we don't dispatch ePaste event.  Why?
     return PasteAsPlaintextQuotation(aClipboardType);
   }
 
   // If it's not in plain text edit mode, paste text into new
   // <blockquote type="cite"> element after removing selection.
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -1112,16 +1112,18 @@ 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);
+  } else {
+    editActionData.InitializeDataTransfer(aString);
   }
 
   AutoPlaceholderBatch treatAsOneTransaction(*this);
 
   // This should emulates inserting text for better undo/redo behavior.
   AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
       *this, EditSubAction::eInsertText, nsIEditor::eNext);
 
--- a/editor/libeditor/TextEditorDataTransfer.cpp
+++ b/editor/libeditor/TextEditorDataTransfer.cpp
@@ -283,17 +283,18 @@ nsresult TextEditor::OnDrop(DragEvent* a
     }
     droppedAt = SelectionRefPtr()->FocusRef();
     if (NS_WARN_IF(!droppedAt.IsSet())) {
       return NS_ERROR_FAILURE;
     }
 
     // Let's fire "input" event for the deletion now.
     if (mDispatchInputEvent) {
-      FireInputEvent(EditAction::eDeleteByDrag, VoidString());
+      RefPtr<DataTransfer> dataTransfer;  // Required due to bug 1506439
+      FireInputEvent(EditAction::eDeleteByDrag, VoidString(), dataTransfer);
       if (NS_WARN_IF(Destroyed())) {
         return NS_ERROR_EDITOR_DESTROYED;
       }
     }
 
     // XXX Now, Selection may be changed by input event listeners.  If so,
     //     should we update |droppedAt|?
   }
@@ -337,16 +338,17 @@ nsresult TextEditor::OnDrop(DragEvent* a
     // 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 {
+    editActionData.InitializeDataTransfer(dataTransfer);
     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;
       }
     }
@@ -360,16 +362,18 @@ nsresult TextEditor::OnDrop(DragEvent* a
 nsresult TextEditor::PasteAsAction(int32_t aClipboardType,
                                    bool aDispatchPasteEvent) {
   AutoEditActionDataSetter editActionData(*this, EditAction::ePaste);
   if (NS_WARN_IF(!editActionData.CanHandle())) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (AsHTMLEditor()) {
+    editActionData.InitializeDataTransferWithClipboard(
+        SettingDataTransfer::eWithFormat, aClipboardType);
     nsresult rv =
         AsHTMLEditor()->PasteInternal(aClipboardType, aDispatchPasteEvent);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     return NS_OK;
   }
 
--- a/editor/libeditor/tests/test_abs_positioner_positioning_elements.html
+++ b/editor/libeditor/tests/test_abs_positioner_positioning_elements.html
@@ -81,16 +81,20 @@ SimpleTest.waitForFocus(async function()
         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, "",
            "inputType should be empty string when an element is moved");
+        is(aEvent.data, null,
+           "data should be null when an element is moved");
+        is(aEvent.dataTransfer, null,
+           "dataTransfer should be null when an element is moved");
       }
 
       content.addEventListener("input", onInput);
 
       // Click on the positioner.
       synthesizeMouse(target, kPositionerX, kPositionerY, {type: "mousedown"});
       // Drag it delta pixels.
       synthesizeMouse(target, kPositionerX + aDeltaX, kPositionerY + aDeltaY, {type: "mousemove"});
--- a/editor/libeditor/tests/test_dom_input_event_on_htmleditor.html
+++ b/editor/libeditor/tests/test_dom_input_event_on_htmleditor.html
@@ -93,61 +93,73 @@ function runTests() {
     synthesizeKey("a", { }, aWindow);
     is(editTarget.innerHTML, "a", aDescription + "wrong element was edited");
     ok(inputEvent, aDescription + "input event wasn't fired by 'a' key");
     ok(inputEvent.isTrusted, aDescription + "input event by 'a' key wasn't trusted event");
     is(inputEvent.inputType, "insertText",
        aDescription + 'inputType should be "insertText" when typing "a"');
     is(inputEvent.data, "a",
        aDescription + 'data should be "a" when typing "a"');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when typing "a"');
 
     inputEvent = null;
     synthesizeKey("KEY_Backspace", { }, aWindow);
     ok(inputEvent, aDescription + "input event wasn't fired by BackSpace key");
     ok(inputEvent.isTrusted, aDescription + "input event by BackSpace key wasn't trusted event");
     is(inputEvent.inputType, "deleteContentBackward",
        aDescription + 'inputType should be "deleteContentBackward" when pressing "Backspace" with collapsed selection');
     is(inputEvent.data, null,
        aDescription + 'data should be null when pressing "Backspace" with collapsed selection');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when pressing "Backspace" with collapsed selection');
 
     inputEvent = null;
     synthesizeKey("B", { shiftKey: true }, aWindow);
     ok(inputEvent, aDescription + "input event wasn't fired by 'B' key");
     ok(inputEvent.isTrusted, aDescription + "input event by 'B' key wasn't trusted event");
     is(inputEvent.inputType, "insertText",
        aDescription + 'inputType should be "insertText" when typing "B"');
     is(inputEvent.data, "B",
        aDescription + 'data should be "B" when typing "B"');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when typing "B"');
 
     inputEvent = null;
     synthesizeKey("KEY_Enter", { }, aWindow);
     ok(inputEvent, aDescription + "input event wasn't fired by Enter key");
     ok(inputEvent.isTrusted, aDescription + "input event by Enter key wasn't trusted event");
     is(inputEvent.inputType, "insertParagraph",
        aDescription + 'inputType should be "insertParagraph" when pressing "Enter"');
     is(inputEvent.data, null,
        aDescription + 'data should be null when pressing "Enter"');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when pressing "Enter"');
 
     inputEvent = null;
     synthesizeKey("C", { shiftKey: true }, aWindow);
     ok(inputEvent, aDescription + "input event wasn't fired by 'C' key");
     ok(inputEvent.isTrusted, aDescription + "input event by 'C' key wasn't trusted event");
     is(inputEvent.inputType, "insertText",
        aDescription + 'inputType should be "insertText" when typing "C"');
     is(inputEvent.data, "C",
        aDescription + 'data should be "C" when typing "C"');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when typing "C"');
 
     inputEvent = null;
     synthesizeKey("KEY_Enter", { }, aWindow);
     ok(inputEvent, aDescription + "input event wasn't fired by Enter key (again)");
     ok(inputEvent.isTrusted, aDescription + "input event by Enter key (again) wasn't trusted event");
     is(inputEvent.inputType, "insertParagraph",
        aDescription + 'inputType should be "insertParagraph" when pressing "Enter" again');
     is(inputEvent.data, null,
        aDescription + 'data should be null when pressing "Enter" again');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when pressing "Enter" again');
 
     inputEvent = null;
     editTarget.innerHTML = "foo-bar";
     ok(!inputEvent, aDescription + "input event was fired by setting value");
 
     inputEvent = null;
     editTarget.innerHTML = "";
     ok(!inputEvent, aDescription + "input event was fired by setting empty value");
@@ -155,16 +167,18 @@ function runTests() {
     inputEvent = null;
     synthesizeKey(" ", { }, aWindow);
     ok(inputEvent, aDescription + "input event wasn't fired by Space key");
     ok(inputEvent.isTrusted, aDescription + "input event by Space key wasn't trusted event");
     is(inputEvent.inputType, "insertText",
        aDescription + 'inputType should be "insertText" when typing " "');
     is(inputEvent.data, " ",
        aDescription + 'data should be " " when typing " "');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when typing " "');
 
     inputEvent = null;
     synthesizeKey("KEY_Delete", { }, aWindow);
     ok(!inputEvent, aDescription + "input event was fired by Delete key at the end");
 
     inputEvent = null;
     synthesizeKey("KEY_ArrowLeft", { }, aWindow);
     ok(!inputEvent, aDescription + "input event was fired by Left key");
@@ -172,103 +186,119 @@ function runTests() {
     inputEvent = null;
     synthesizeKey("KEY_Delete", { }, aWindow);
     ok(inputEvent, aDescription + "input event wasn't fired by Delete key at the start");
     ok(inputEvent.isTrusted, aDescription + "input event by Delete key wasn't trusted event");
     is(inputEvent.inputType, "deleteContentForward",
        aDescription + 'inputType should be "deleteContentForward" when pressing "Delete" with collapsed selection');
     is(inputEvent.data, null,
        aDescription + 'data should be null when pressing "Delete" with collapsed selection');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when pressing "Delete" with collapsed selection');
 
     inputEvent = null;
     synthesizeKey("z", { accelKey: true }, aWindow);
     ok(inputEvent, aDescription + "input event wasn't fired by Undo");
     ok(inputEvent.isTrusted, aDescription + "input event by Undo wasn't trusted event");
     is(inputEvent.inputType, "historyUndo",
        aDescription + 'inputType should be "historyUndo" when doing "Undo"');
     is(inputEvent.data, null,
        aDescription + 'data should be null when doing "Undo"');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when doing "Undo"');
 
     inputEvent = null;
     synthesizeKey("z", { accelKey: true, shiftKey: true }, aWindow);
     ok(inputEvent, aDescription + "input event wasn't fired by Redo");
     ok(inputEvent.isTrusted, aDescription + "input event by Redo wasn't trusted event");
     is(inputEvent.inputType, "historyRedo",
        aDescription + 'inputType should be "historyRedo" when doing "Redo"');
     is(inputEvent.data, null,
        aDescription + 'data should be null when doing "Redo"');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when doing "Redo"');
 
     inputEvent = null;
     synthesizeKey("KEY_Enter", {shiftKey: true}, aWindow);
     ok(inputEvent, aDescription + "input event wasn't fired by Shift + Enter key");
     ok(inputEvent.isTrusted, aDescription + "input event by Shift + Enter key wasn't trusted event");
     is(inputEvent.inputType, "insertLineBreak",
        aDescription + 'inputType should be "insertLineBreak" when pressing Shift + "Enter"');
     is(inputEvent.data, null,
        aDescription + 'data should be null when pressing Shift + "Enter"');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when pressing Shift + "Enter"');
 
     // Backspace/Delete with non-collapsed selection.
     editTarget.innerHTML = "a";
     editTarget.focus();
     selection.selectAllChildren(editTarget);
     inputEvent = null;
     synthesizeKey("KEY_Backspace", {}, aWindow);
     ok(inputEvent,
        aDescription + 'input event should be fired by pressing "Backspace" with non-collapsed selection');
     ok(inputEvent.isTrusted,
        aDescription + 'input event should be trusted when pressing "Backspace" with non-collapsed selection');
     is(inputEvent.inputType, "deleteContentBackward",
        aDescription + 'inputType should be "deleteContentBackward" when pressing "Backspace" with non-collapsed selection');
     is(inputEvent.data, null,
        aDescription + 'data should be null when pressing "Backspace" with non-collapsed selection');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when pressing "Backspace" with non-collapsed selection');
 
     editTarget.innerHTML = "a";
     editTarget.focus();
     selection.selectAllChildren(editTarget);
     inputEvent = null;
     synthesizeKey("KEY_Delete", {}, aWindow);
     ok(inputEvent,
        aDescription + 'input event should be fired by pressing "Delete" with non-collapsed selection');
     ok(inputEvent.isTrusted,
        aDescription + 'input event should be trusted when pressing "Delete" with non-collapsed selection');
     is(inputEvent.inputType, "deleteContentForward",
        aDescription + 'inputType should be "deleteContentBackward" when Delete "Backspace" with non-collapsed selection');
     is(inputEvent.data, null,
        aDescription + 'data should be null when Delete "Backspace" with non-collapsed selection');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when Delete "Backspace" with non-collapsed selection');
 
     // Delete to previous/next word boundary with collapsed selection.
     editTarget.innerHTML = "a";
     editTarget.focus();
     selection.selectAllChildren(editTarget);
     selection.collapseToEnd();
     inputEvent = null;
     SpecialPowers.doCommand(aWindow, "cmd_deleteWordBackward");
     ok(inputEvent,
        aDescription + "input event should be fired by deleting to previous word boundary with collapsed selection");
     ok(inputEvent.isTrusted,
        aDescription + "input event should be trusted when deleting to previous word boundary with collapsed selection");
     is(inputEvent.inputType, "deleteWordBackward",
        aDescription + 'inputType should be "deleteWordBackward" when deleting to previous word boundary with collapsed selection');
     is(inputEvent.data, null,
        aDescription + "data should be null when deleting to previous word boundary with collapsed selection");
+    is(inputEvent.dataTransfer, null,
+       aDescription + "dataTransfer should be null when deleting to previous word boundary with collapsed selection");
 
     editTarget.innerHTML = "a";
     editTarget.focus();
     selection.selectAllChildren(editTarget);
     selection.collapseToStart();
     inputEvent = null;
     SpecialPowers.doCommand(aWindow, "cmd_deleteWordForward");
     ok(inputEvent,
        aDescription + "input event should be fired by deleting to next word boundary with collapsed selection");
     ok(inputEvent.isTrusted,
        aDescription + "input event should be trusted when deleting to next word boundary with collapsed selection");
     is(inputEvent.inputType, "deleteWordForward",
        aDescription + 'inputType should be "deleteWordForward" when deleting to next word boundary with collapsed selection');
     is(inputEvent.data, null,
        aDescription + "data should be null when deleting to next word boundary with collapsed selection");
+    is(inputEvent.dataTransfer, null,
+       aDescription + "dataTransfer should be null when deleting to next word boundary with collapsed selection");
 
     // Delete to previous/next word boundary with non-collapsed selection.
     editTarget.innerHTML = "abc";
     editTarget.focus();
     selection.setBaseAndExtent(editTarget.firstChild, 1, editTarget.firstChild, 2);
     inputEvent = null;
     SpecialPowers.doCommand(aWindow, "cmd_deleteWordBackward");
     ok(inputEvent,
@@ -280,16 +310,18 @@ function runTests() {
       is(inputEvent.inputType, "deleteWordBackward",
          aDescription + 'inputType should be "deleteWordBackward" when deleting to previous word boundary with non-collapsed selection');
     } else {
       is(inputEvent.inputType, "deleteContentBackward",
          aDescription + 'inputType should be "deleteContentBackward" when deleting to previous word boundary with non-collapsed selection');
     }
     is(inputEvent.data, null,
        aDescription + "data should be null when deleting to previous word boundary with non-collapsed selection");
+    is(inputEvent.dataTransfer, null,
+       aDescription + "dataTransfer should be null when deleting to previous word boundary with non-collapsed selection");
 
     editTarget.innerHTML = "abc";
     editTarget.focus();
     selection.setBaseAndExtent(editTarget.firstChild, 1, editTarget.firstChild, 2);
     inputEvent = null;
     SpecialPowers.doCommand(aWindow, "cmd_deleteWordForward");
     ok(inputEvent,
        aDescription + "input event should be fired by deleting to next word boundary with non-collapsed selection");
@@ -300,47 +332,53 @@ function runTests() {
       is(inputEvent.inputType, "deleteWordForward",
          aDescription + 'inputType should be "deleteWordForward" when deleting to next word boundary with non-collapsed selection');
     } else {
       is(inputEvent.inputType, "deleteContentForward",
          aDescription + 'inputType should be "deleteContentForward" when deleting to next word boundary with non-collapsed selection');
     }
     is(inputEvent.data, null,
        aDescription + "data should be null when deleting to next word boundary with non-collapsed selection");
+    is(inputEvent.dataTransfer, null,
+       aDescription + "dataTransfer should be null when deleting to next word boundary with non-collapsed selection");
 
     // Delete to previous/next visual line boundary with collapsed selection.
     editTarget.innerHTML = "a";
     editTarget.focus();
     selection.selectAllChildren(editTarget);
     selection.collapseToEnd();
     inputEvent = null;
     SpecialPowers.doCommand(aWindow, "cmd_deleteToBeginningOfLine");
     ok(inputEvent,
        aDescription + "input event should be fired by deleting to previous visual line boundary with collapsed selection");
     ok(inputEvent.isTrusted,
        aDescription + "input event should be trusted when deleting to previous visual line boundary with collapsed selection");
     is(inputEvent.inputType, "deleteSoftLineBackward",
        aDescription + 'inputType should be "deleteSoftLineBackward" when deleting to previous visual line boundary with collapsed selection');
     is(inputEvent.data, null,
        aDescription + "data should be null when deleting to previous visual line boundary with collapsed selection");
+    is(inputEvent.dataTransfer, null,
+       aDescription + "dataTransfer should be null when deleting to previous visual line boundary with collapsed selection");
 
     editTarget.innerHTML = "a";
     editTarget.focus();
     selection.selectAllChildren(editTarget);
     selection.collapseToStart();
     inputEvent = null;
     SpecialPowers.doCommand(aWindow, "cmd_deleteToEndOfLine");
     ok(inputEvent,
        aDescription + "input event should be fired by deleting to next visual line boundary with collapsed selection");
     ok(inputEvent.isTrusted,
        aDescription + "input event should be trusted when deleting to next visual line boundary with collapsed selection");
     is(inputEvent.inputType, "deleteSoftLineForward",
        aDescription + 'inputType should be "deleteSoftLineForward" when deleting to visual line boundary with collapsed selection');
     is(inputEvent.data, null,
        aDescription + "data should be null when deleting to visual line boundary with collapsed selection");
+    is(inputEvent.dataTransfer, null,
+       aDescription + "dataTransfer should be null when deleting to visual line boundary with collapsed selection");
 
     // Delete to previous/next visual line boundary with non-collapsed selection.
     editTarget.innerHTML = "abc";
     editTarget.focus();
     selection.setBaseAndExtent(editTarget.firstChild, 1, editTarget.firstChild, 2);
     inputEvent = null;
     SpecialPowers.doCommand(aWindow, "cmd_deleteToBeginningOfLine");
     ok(inputEvent,
@@ -352,16 +390,18 @@ function runTests() {
       is(inputEvent.inputType, "deleteSoftLineBackward",
          aDescription + 'inputType should be "deleteSoftLineBackward" when deleting to next visual line boundary with non-collapsed selection');
     } else {
       is(inputEvent.inputType, "deleteContentBackward",
          aDescription + 'inputType should be "deleteContentBackward" when deleting to previous visual line boundary with non-collapsed selection');
     }
     is(inputEvent.data, null,
        aDescription + "data should be null when deleting to previous visual line boundary with non-collapsed selection");
+    is(inputEvent.dataTransfer, null,
+       aDescription + "dataTransfer should be null when deleting to previous visual line boundary with non-collapsed selection");
 
     editTarget.innerHTML = "abc";
     editTarget.focus();
     selection.setBaseAndExtent(editTarget.firstChild, 1, editTarget.firstChild, 2);
     inputEvent = null;
     SpecialPowers.doCommand(aWindow, "cmd_deleteToEndOfLine");
     ok(inputEvent,
        aDescription + "input event should be fired by deleting to next visual line boundary with non-collapsed selection");
@@ -372,66 +412,76 @@ function runTests() {
       is(inputEvent.inputType, "deleteSoftLineForward",
          aDescription + 'inputType should be "deleteSoftLineForward" when deleting to next visual line boundary with non-collapsed selection');
     } else {
       is(inputEvent.inputType, "deleteContentForward",
          aDescription + 'inputType should be "deleteContentForward" when deleting to next visual line boundary with non-collapsed selection');
     }
     is(inputEvent.data, null,
        aDescription + "data should be null when deleting to next visual line boundary with non-collapsed selection");
+    is(inputEvent.dataTransfer, null,
+       aDescription + "dataTransfer should be null when deleting to next visual line boundary with non-collapsed selection");
 
     // Toggling text direction
     editTarget.focus();
     inputEvent = null;
     SpecialPowers.doCommand(window, "cmd_switchTextDirection");
     ok(inputEvent,
        aDescription + "input event should be fired by dispatching cmd_switchTextDirection command #1");
     ok(inputEvent.isTrusted,
        aDescription + "input event should be trusted when dispatching cmd_switchTextDirection command #1");
     is(inputEvent.inputType, "formatSetBlockTextDirection",
        aDescription + 'inputType should be "formatSetBlockTextDirection" when dispatching cmd_switchTextDirection command #1');
     is(inputEvent.data, "rtl",
        aDescription + 'data should be "rtl" when dispatching cmd_switchTextDirection command #1');
+    is(inputEvent.dataTransfer, null,
+       aDescription + "dataTransfer should be null when dispatching cmd_switchTextDirection command #1");
 
     inputEvent = null;
     SpecialPowers.doCommand(window, "cmd_switchTextDirection");
     ok(inputEvent,
        aDescription + "input event should be fired by dispatching cmd_switchTextDirection command #2");
     ok(inputEvent.isTrusted,
        aDescription + "input event should be trusted when dispatching cmd_switchTextDirection command #2");
     is(inputEvent.inputType, "formatSetBlockTextDirection",
        aDescription + 'inputType should be "formatSetBlockTextDirection" when dispatching cmd_switchTextDirection command #2');
     is(inputEvent.data, "ltr",
        aDescription + 'data should be "ltr" when dispatching cmd_switchTextDirection command #2');
+    is(inputEvent.dataTransfer, null,
+       aDescription + "dataTransfer should be null when dispatching cmd_switchTextDirection command #2");
 
     // Inserting link
     editTarget.innerHTML = "link";
     editTarget.focus();
     selection.selectAllChildren(editTarget);
     inputEvent = null;
     aDocument.execCommand("createLink", false, "https://example.com/foo/bar.html");
     ok(inputEvent,
        aDescription + 'input event should be fired by execCommand("createLink", false, "https://example.com/foo/bar.html")');
     ok(inputEvent.isTrusted,
        aDescription + 'input event should be trusted when execCommand("createLink", false, "https://example.com/foo/bar.html")');
     is(inputEvent.inputType, "insertLink",
        aDescription + 'inputType should be "insertLink" when execCommand("createLink", false, "https://example.com/foo/bar.html")');
     is(inputEvent.data, "https://example.com/foo/bar.html",
        aDescription + 'data should be "https://example.com/foo/bar.html" when execCommand("createLink", false, "https://example.com/foo/bar.html")');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when execCommand("createLink", false, "https://example.com/foo/bar.html")');
 
     selection.selectAllChildren(editTarget);
     aDocument.execCommand("createLink", false, "foo/bar.html");
     ok(inputEvent,
        aDescription + 'input event should be fired by execCommand("createLink", false, "foo/bar.html")');
     ok(inputEvent.isTrusted,
        aDescription + 'input event should be trusted when execCommand("createLink", false, "foo/bar.html")');
     is(inputEvent.inputType, "insertLink",
        aDescription + 'inputType should be "insertLink" when execCommand("createLink", false, "foo/bar.html")');
     is(inputEvent.data, "foo/bar.html",
        aDescription + 'data should be "foo/bar.html" when execCommand("createLink", false, "foo/bar.html")');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when execCommand("createLink", false, "foo/bar.html")');
 
     aWindow.removeEventListener("input", handler, true);
   }
 
   doTests(document.getElementById("editor1").contentDocument,
           document.getElementById("editor1").contentWindow,
           "Editor1, body has contenteditable attribute");
   doTests(document.getElementById("editor2").contentDocument,
--- a/editor/libeditor/tests/test_dom_input_event_on_texteditor.html
+++ b/editor/libeditor/tests/test_dom_input_event_on_texteditor.html
@@ -65,37 +65,43 @@ function runTests() {
     sendString("a");
     is(aElement.value, "a", aDescription + "'a' key didn't change the value");
     ok(inputEvent, aDescription + "input event wasn't fired by 'a' key");
     ok(inputEvent.isTrusted, aDescription + "input event by 'a' key wasn't trusted event");
     is(inputEvent.inputType, "insertText",
        aDescription + 'inputType should be "insertText" when typing "a"');
     is(inputEvent.data, "a",
        aDescription + 'data should be "a" when typing "a"');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when typing "a"');
 
     inputEvent = null;
     synthesizeKey("KEY_Backspace");
     is(aElement.value, "", aDescription + "BackSpace key didn't remove the value");
     ok(inputEvent, aDescription + "input event wasn't fired by BackSpace key");
     ok(inputEvent.isTrusted, aDescription + "input event by BackSpace key wasn't trusted event");
     is(inputEvent.inputType, "deleteContentBackward",
        aDescription + 'inputType should be "deleteContentBackward" when pressing "Backspace" with collapsed selection');
     is(inputEvent.data, null,
        aDescription + 'data should be null when pressing "Backspace" with collapsed selection');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when pressing "Backspace" with collapsed selection');
 
     if (aIsTextarea) {
       inputEvent = null;
       synthesizeKey("KEY_Enter");
       is(aElement.value, "\n", aDescription + "Enter key didn't change the value");
       ok(inputEvent, aDescription + "input event wasn't fired by Enter key");
       ok(inputEvent.isTrusted, aDescription + "input event by Enter key wasn't trusted event");
       is(inputEvent.inputType, "insertLineBreak",
          aDescription + 'inputType should be "insertLineBreak" when pressing "Enter"');
       is(inputEvent.data, null,
          aDescription + 'data should be null when pressing "Enter"');
+      is(inputEvent.dataTransfer, null,
+         aDescription + 'dataTransfer should be null when pressing "Enter"');
     }
 
     inputEvent = null;
     aElement.value = "foo-bar";
     is(aElement.value, "foo-bar", aDescription + "value wasn't set");
     ok(!inputEvent, aDescription + "input event was fired by setting value");
 
     inputEvent = null;
@@ -107,16 +113,18 @@ function runTests() {
     sendString(" ");
     is(aElement.value, " ", aDescription + "Space key didn't change the value");
     ok(inputEvent, aDescription + "input event wasn't fired by Space key");
     ok(inputEvent.isTrusted, aDescription + "input event by Space key wasn't trusted event");
     is(inputEvent.inputType, "insertText",
        aDescription + 'inputType should be "insertText" when typing " "');
     is(inputEvent.data, " ",
        aDescription + 'data should be " " when typing " "');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when typing " "');
 
     inputEvent = null;
     synthesizeKey("KEY_Delete");
     is(aElement.value, " ", aDescription + "Delete key removed the value");
     ok(!inputEvent, aDescription + "input event was fired by Delete key at the end");
 
     inputEvent = null;
     synthesizeKey("KEY_ArrowLeft");
@@ -127,87 +135,101 @@ function runTests() {
     synthesizeKey("KEY_Delete");
     is(aElement.value, "", aDescription + "Delete key didn't remove the value");
     ok(inputEvent, aDescription + "input event wasn't fired by Delete key at the start");
     ok(inputEvent.isTrusted, aDescription + "input event by Delete key wasn't trusted event");
     is(inputEvent.inputType, "deleteContentForward",
        aDescription + 'inputType should be "deleteContentForward" when pressing "Delete" with collapsed selection');
     is(inputEvent.data, null,
        aDescription + 'data should be null when pressing "Delete" with collapsed selection');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when pressing "Delete" with collapsed selection');
 
     inputEvent = null;
     synthesizeKey("z", {accelKey: true});
     is(aElement.value, " ", aDescription + "Accel+Z key didn't undo the value");
     ok(inputEvent, aDescription + "input event wasn't fired by Undo");
     ok(inputEvent.isTrusted, aDescription + "input event by Undo wasn't trusted event");
     is(inputEvent.inputType, "historyUndo",
        aDescription + 'inputType should be "historyUndo" when doing "Undo"');
     is(inputEvent.data, null,
        aDescription + 'data should be null when doing "Undo"');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when doing "Undo"');
 
     inputEvent = null;
     synthesizeKey("Z", {accelKey: true, shiftKey: true});
     is(aElement.value, "", aDescription + "Accel+Y key didn't redo the value");
     ok(inputEvent, aDescription + "input event wasn't fired by Redo");
     ok(inputEvent.isTrusted, aDescription + "input event by Redo wasn't trusted event");
     is(inputEvent.inputType, "historyRedo",
        aDescription + 'inputType should be "historyRedo" when doing "Redo"');
     is(inputEvent.data, null,
        aDescription + 'data should be null when doing "Redo"');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when doing "Redo"');
 
     // Backspace/Delete with non-collapsed selection.
     aElement.value = "a";
     aElement.select();
     inputEvent = null;
     synthesizeKey("KEY_Backspace");
     ok(inputEvent,
        aDescription + 'input event should be fired by pressing "Backspace" with non-collapsed selection');
     ok(inputEvent.isTrusted,
        aDescription + 'input event should be trusted when pressing "Backspace" with non-collapsed selection');
     is(inputEvent.inputType, "deleteContentBackward",
        aDescription + 'inputType should be "deleteContentBackward" when pressing "Backspace" with non-collapsed selection');
     is(inputEvent.data, null,
        aDescription + 'data should be null when pressing "Backspace" with non-collapsed selection');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when pressing "Backspace" with non-collapsed selection');
 
     aElement.value = "a";
     aElement.select();
     inputEvent = null;
     synthesizeKey("KEY_Delete");
     ok(inputEvent,
        aDescription + 'input event should be fired by pressing "Delete" with non-collapsed selection');
     ok(inputEvent.isTrusted,
        aDescription + 'input event should be trusted when pressing "Delete" with non-collapsed selection');
     is(inputEvent.inputType, "deleteContentForward",
        aDescription + 'inputType should be "deleteContentBackward" when Delete "Backspace" with non-collapsed selection');
     is(inputEvent.data, null,
        aDescription + 'data should be null when Delete "Backspace" with non-collapsed selection');
+    is(inputEvent.dataTransfer, null,
+       aDescription + 'dataTransfer should be null when Delete "Backspace" with non-collapsed selection');
 
     // Toggling text direction
     aElement.focus();
     inputEvent = null;
     SpecialPowers.doCommand(window, "cmd_switchTextDirection");
     ok(inputEvent,
        aDescription + "input event should be fired by dispatching cmd_switchTextDirection command #1");
     ok(inputEvent.isTrusted,
        aDescription + "input event should be trusted when dispatching cmd_switchTextDirection command #1");
     is(inputEvent.inputType, "formatSetBlockTextDirection",
        aDescription + 'inputType should be "formatSetBlockTextDirection" when dispatching cmd_switchTextDirection command #1');
     is(inputEvent.data, "rtl",
        aDescription + 'data should be "rtl" when dispatching cmd_switchTextDirection command #1');
+    is(inputEvent.dataTransfer, null,
+       aDescription + "dataTransfer should be null when dispatching cmd_switchTextDirection command #1");
 
     inputEvent = null;
     SpecialPowers.doCommand(window, "cmd_switchTextDirection");
     ok(inputEvent,
        aDescription + "input event should be fired by dispatching cmd_switchTextDirection command #2");
     ok(inputEvent.isTrusted,
        aDescription + "input event should be trusted when dispatching cmd_switchTextDirection command #2");
     is(inputEvent.inputType, "formatSetBlockTextDirection",
        aDescription + 'inputType should be "formatSetBlockTextDirection" when dispatching cmd_switchTextDirection command #2');
     is(inputEvent.data, "ltr",
        aDescription + 'data should be "ltr" when dispatching cmd_switchTextDirection command #2');
+    is(inputEvent.dataTransfer, null,
+       aDescription + "dataTransfer should be null when dispatching cmd_switchTextDirection command #2");
 
     aElement.removeEventListener("input", handler, true);
   }
 
   doTests(document.getElementById("input"), "<input type=\"text\">", false);
   doTests(document.getElementById("textarea"), "<textarea>", true);
 
   SimpleTest.finish();
--- a/editor/libeditor/tests/test_dragdrop.html
+++ b/editor/libeditor/tests/test_dragdrop.html
@@ -19,23 +19,38 @@
 <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, aData, aDescription) {
+function checkInputEvent(aEvent, aExpectedTarget, aData, aDataTransfer, 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}`);
+  if (aDataTransfer === null) {
+    is(aEvent.dataTransfer, null, `dataTransfer should be null on the <${aExpectedTarget.tagName.toLowerCase()}> element ${aDescription}`);
+  } else {
+    for (let dataTransfer of aDataTransfer) {
+      let description = `on the <${aExpectedTarget.tagName.toLowerCase()}> element ${aDescription}`;
+      if (dataTransfer.todo) {
+        // XXX It seems that synthesizeDrop() don't emulate perfectly if caller specifies the data directly.
+        todo_is(aEvent.dataTransfer.getData(dataTransfer.type), dataTransfer.data,
+                `dataTransfer should have "${dataTransfer.data}" whose type is "${dataTransfer.type}" ${description}`);
+      } else {
+        is(aEvent.dataTransfer.getData(dataTransfer.type), dataTransfer.data,
+           `dataTransfer should have "${dataTransfer.data}" whose type is "${dataTransfer.type}" ${description}`);
+      }
+    }
+  }
 }
 
 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>' };
 
@@ -98,17 +113,17 @@ 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, "Some Text",
+  checkInputEvent(inputEvents[0], input, "Some Text", null,
                   "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 = [];
@@ -147,17 +162,17 @@ 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, "Some Plain Text",
+  checkInputEvent(inputEvents[0], input, "Some Plain Text", null,
                   "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");
@@ -177,56 +192,60 @@ function doTest() {
   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, null,
+                  [{todo: true, type: "text/plain", data: "Sample Text"}],
                   "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, null,
+                  [{todo: true, type: "text/html", data: "Sample <i>Italic</i> Text"}],
                   "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, "editable",
+  checkInputEvent(inputEvents[0], input, "editable", null,
                   "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, null,
+                  [{type: "text/html", data: "<i>Italic</i>"},
+                   {type: "text/plain", data: "Italic"}],
                   "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.
@@ -236,17 +255,20 @@ 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) {
+    // XXX I'm not sure about dataTransfer value of text/html.
     checkInputEvent(inputEvents[0], document.getElementById("nestedce"), null,
+                    [{type: "text/html", data: "Middle"},
+                     {type: "text/plain", data: "Middle"}],
                     "when dragging from inner non-editable element to a contentediable element");
   }
 
   document.removeEventListener("input", onInput);
 
   SimpleTest.finish();
 }
 
--- a/editor/libeditor/tests/test_middle_click_paste.html
+++ b/editor/libeditor/tests/test_middle_click_paste.html
@@ -71,27 +71,36 @@ async function copyHTMLContent(aInnerHTM
       () => {
         ok(false, `Failed to copy "${aInnerHTML}" to clipboard`);
         SimpleTest.finish();
       },
       "text/html");
   });
 }
 
-function checkInputEvent(aEvent, aData, aDescription) {
+function checkInputEvent(aEvent, aData, aDataTransfer, 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}`);
+  if (aDataTransfer === null) {
+    is(aEvent.dataTransfer, null,
+       `dataTransfer should be null ${aDescription}`);
+  } else {
+    for (let dataTransfer of aDataTransfer) {
+      is(aEvent.dataTransfer.getData(dataTransfer.type), dataTransfer.data,
+         `dataTransfer should have "${dataTransfer.data}" whose type is "${dataTransfer.type}" ${aDescription}`);
+    }
+  }
 }
 
 async function doTextareaTests(aTextarea) {
   let inputEvents = [];
   function onInput(aEvent) {
     inputEvents.push(aEvent);
   }
   aTextarea.addEventListener("input", onInput);
@@ -100,53 +109,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], "abc\ndef\nghi", "#1");
+  checkInputEvent(inputEvents[0], "abc\ndef\nghi", null, "#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], "> abc\n> def\n> ghi", "#2");
+  checkInputEvent(inputEvents[0], "> abc\n> def\n> ghi", null, "#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], "> abc\n> def\n\nghi", "#3");
+  checkInputEvent(inputEvents[0], "> abc\n> def\n\nghi", null, "#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], "abc\ndef\n\n", "#4");
+  checkInputEvent(inputEvents[0], "abc\ndef\n\n", null, "#4");
   aTextarea.value = "";
 
   let pasteEventCount = 0;
   function pasteEventLogger(event) {
     pasteEventCount++;
   }
   aTextarea.addEventListener("paste", pasteEventLogger);
 
@@ -170,32 +179,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], "abc", 'even if "mouseup" event is canceled');
+  checkInputEvent(inputEvents[0], "abc", null, '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], "abc", 'even if "click" event is canceled in bubbling phase');
+  checkInputEvent(inputEvents[0], "abc", null, '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});
@@ -222,17 +231,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], null, "(contenteditable)");
+  checkInputEvent(inputEvents[0], null, [{type: "text/plain", data: "abc\ndef\nghi"}], "(contenteditable)");
   aEditableDiv.innerHTML = "";
 
   let pasteEventCount = 0;
   function pasteEventLogger(event) {
     pasteEventCount++;
   }
   aEditableDiv.addEventListener("paste", pasteEventLogger);
 
@@ -256,32 +265,34 @@ 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], null, 'even if "mouseup" event is canceled (contenteditable)');
+  checkInputEvent(inputEvents[0], null, [{type: "text/plain", data: "abc"}],
+                  '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], null, 'even if "click" event is canceled in bubbling phase (contenteditable)');
+  checkInputEvent(inputEvents[0], null, [{type: "text/plain", data: "abc"}],
+                  '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});
@@ -312,17 +323,24 @@ 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], null, "when pasting HTML");
+  // On windows, HTML clipboard includes extra data.
+  // The values are from widget/windows/nsDataObj.cpp.
+  const kHTMLPrefix = (navigator.platform.includes("Win")) ? "<html><body>\n<!--StartFragment-->" : "";
+  const kHTMLPostfix = (navigator.platform.includes("Win")) ? "<!--EndFragment-->\n</body>\n</html>" : "";
+  checkInputEvent(inputEvents[0], null,
+                  [{type: "text/html",
+                    data: `${kHTMLPrefix}<p>abc</p><p>def</p><p>ghi</p>${kHTMLPostfix}`}],
+                  "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_nsIEditorMailSupport_insertAsCitedQuotation.html
+++ b/editor/libeditor/tests/test_nsIEditorMailSupport_insertAsCitedQuotation.html
@@ -50,16 +50,18 @@ SimpleTest.waitForFocus(function() {
   is(inputEvents[0].cancelable, false,
      '"input" event should be never cancelable even if "click" event after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor');
   is(inputEvents[0].bubbles, true,
      '"input" event should always bubble after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor');
   is(inputEvents[0].inputType, "insertText",
      'inputType should be "insertText" after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor');
   is(inputEvents[0].data, "this is quoted text\nAnd here is second line.",
      "data should be the quoted text after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor");
+  is(inputEvents[0].dataTransfer, null,
+     "dataTransfer should be null after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor");
 
   // Tests when the editor is in HTML editor mode.
   getEditor().flags &= ~SpecialPowers.Ci.nsIPlaintextEditor.eEditorPlaintextMask;
 
   editor.innerHTML = "";
 
   inputEvents = [];
   getEditorMailSupport().insertAsCitedQuotation("this is quoted text<br>", "this is cited text", false);
@@ -79,16 +81,18 @@ SimpleTest.waitForFocus(function() {
   is(inputEvents[0].cancelable, false,
      '"input" event should be never cancelable even if "click" event after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)');
   is(inputEvents[0].bubbles, true,
      '"input" event should always bubble after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)');
   is(inputEvents[0].inputType, "",
      "inputType should be empty string after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)");
   is(inputEvents[0].data, null,
      "data should be null after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)");
+  is(inputEvents[0].dataTransfer, null,
+     "dataTransfer should be null after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)");
 
   editor.innerHTML = "";
 
   inputEvents = [];
   getEditorMailSupport().insertAsCitedQuotation("this is quoted text<br>And here is second line.", "this is cited text", true);
 
   ok(selection.isCollapsed,
      "Selection should be collapsed after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)");
@@ -105,16 +109,18 @@ SimpleTest.waitForFocus(function() {
   is(inputEvents[0].cancelable, false,
      '"input" event should be never cancelable even if "click" event after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)');
   is(inputEvents[0].bubbles, true,
      '"input" event should always bubble after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)');
   is(inputEvents[0].inputType, "",
      "inputType should be empty string after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)");
   is(inputEvents[0].data, null,
      "data should be null after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)");
+  is(inputEvents[0].dataTransfer, null,
+     "dataTransfer should be null after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)");
 
   SimpleTest.finish();
 });
 
 function getEditor() {
   var editingSession = SpecialPowers.wrap(window).docShell.editingSession;
   return editingSession.getEditorForWindow(window);
 }
--- a/editor/libeditor/tests/test_nsIHTMLEditor_removeInlineProperty.html
+++ b/editor/libeditor/tests/test_nsIHTMLEditor_removeInlineProperty.html
@@ -28,16 +28,18 @@ SimpleTest.waitForFocus(function() {
   function checkInputEvent(aEvent, aInputType, aDescription) {
     if (aEvent.type != "input") {
       return;
     }
     ok(aEvent instanceof InputEvent, `${aDescription}"input" event should be dispatched with InputEvent interface`);
     is(aEvent.cancelable, false, `${aDescription}"input" event should be never cancelable`);
     is(aEvent.bubbles, true, `${aDescription}"input" event should always bubble`);
     is(aEvent.inputType, aInputType, `${aDescription}inputType should be ${aInputType}`);
+    is(aEvent.data, null, `${aDescription}data should be null`);
+    is(aEvent.dataTransfer, null, `${aDescription}dataTransfer should be null`);
   }
 
   function selectFromTextSiblings(aNode) {
     condition = "selecting the node from end of previous text to start of next text node";
     selection.setBaseAndExtent(aNode.previousSibling, aNode.previousSibling.length,
                                aNode.nextSibling, 0);
   }
   function selectNode(aNode) {
--- a/editor/libeditor/tests/test_nsIPlaintextEditor_insertLineBreak.html
+++ b/editor/libeditor/tests/test_nsIPlaintextEditor_insertLineBreak.html
@@ -50,16 +50,20 @@ SimpleTest.waitForFocus(function() {
   ok(inputEvents[0] instanceof InputEvent,
      '"input" event should be dispatched with InputEvent interface (on multi-line editor)');
   is(inputEvents[0].cancelable, false,
      '"input" event should be never cancelable even if "click" event (on multi-line editor)');
   is(inputEvents[0].bubbles, true,
      '"input" event should always bubble (on multi-line editor)');
   is(inputEvents[0].inputType, "insertLineBreak",
      'inputType should be "insertLineBreak" on multi-line editor');
+  is(inputEvents[0].data, null,
+     'data should be null if inputType is "insertLineBreak" on multi-line editor');
+  is(inputEvents[0].dataTransfer, null,
+     'dataTransfer should be null if inputType is "insertLineBreak" on multi-line editor');
 
   // Note that despite of the name, insertLineBreak() should insert paragraph separator in HTMLEditor.
 
   document.execCommand("defaultParagraphSeparator", false, "br");
 
   contenteditable.innerHTML = "abcdef";
   contenteditable.focus();
   contenteditable.scrollTop;
@@ -75,16 +79,20 @@ SimpleTest.waitForFocus(function() {
   ok(inputEvents[0] instanceof InputEvent,
      '"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "br") #1');
   is(inputEvents[0].cancelable, false,
      '"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "br") #1');
   is(inputEvents[0].bubbles, true,
      '"input" event should always bubble (when defaultParagraphSeparator is "br") #1');
   is(inputEvents[0].inputType, "insertParagraph",
      'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "br") #1');
+  is(inputEvents[0].data, null,
+     'data should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "br") #1');
+  is(inputEvents[0].dataTransfer, null,
+     'dataTransfer should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "br") #1');
 
   contenteditable.innerHTML = "<p>abcdef</p>";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild.firstChild, 3);
   inputEvents = [];
   contenteditable.addEventListener("input", onInput);
   getPlaintextEditor(contenteditable).insertLineBreak();
@@ -96,16 +104,20 @@ SimpleTest.waitForFocus(function() {
   ok(inputEvents[0] instanceof InputEvent,
      '"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "br") #2');
   is(inputEvents[0].cancelable, false,
      '"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "br") #2');
   is(inputEvents[0].bubbles, true,
      '"input" event should always bubble (when defaultParagraphSeparator is "br") #2');
   is(inputEvents[0].inputType, "insertParagraph",
      'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "br") #2');
+  is(inputEvents[0].data, null,
+     'data should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "br") #2');
+  is(inputEvents[0].dataTransfer, null,
+     'dataTransfer should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "br") #2');
 
   contenteditable.innerHTML = "<div>abcdef</div>";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild.firstChild, 3);
   inputEvents = [];
   contenteditable.addEventListener("input", onInput);
   getPlaintextEditor(contenteditable).insertLineBreak();
@@ -117,16 +129,20 @@ SimpleTest.waitForFocus(function() {
   ok(inputEvents[0] instanceof InputEvent,
      '"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "br") #3');
   is(inputEvents[0].cancelable, false,
      '"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "br") #3');
   is(inputEvents[0].bubbles, true,
      '"input" event should always bubble (when defaultParagraphSeparator is "br") #3');
   is(inputEvents[0].inputType, "insertParagraph",
      'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "br") #3');
+  is(inputEvents[0].data, null,
+     'data should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "br") #3');
+  is(inputEvents[0].dataTransfer, null,
+     'dataTransfer should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "br") #3');
 
   contenteditable.innerHTML = "<pre>abcdef</pre>";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild.firstChild, 3);
   inputEvents = [];
   contenteditable.addEventListener("input", onInput);
   getPlaintextEditor(contenteditable).insertLineBreak();
@@ -138,16 +154,20 @@ SimpleTest.waitForFocus(function() {
   ok(inputEvents[0] instanceof InputEvent,
      '"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "br") #4');
   is(inputEvents[0].cancelable, false,
      '"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "br") #4');
   is(inputEvents[0].bubbles, true,
      '"input" event should always bubble (when defaultParagraphSeparator is "br") #4');
   is(inputEvents[0].inputType, "insertParagraph",
      'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "br") #4');
+  is(inputEvents[0].data, null,
+     'data should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "br") #4');
+  is(inputEvents[0].dataTransfer, null,
+     'dataTransfer should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "br") #4');
 
   document.execCommand("defaultParagraphSeparator", false, "p");
 
   contenteditable.innerHTML = "abcdef";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild, 3);
   inputEvents = [];
@@ -161,16 +181,20 @@ SimpleTest.waitForFocus(function() {
   ok(inputEvents[0] instanceof InputEvent,
      '"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "p") #1');
   is(inputEvents[0].cancelable, false,
      '"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "p") #1');
   is(inputEvents[0].bubbles, true,
      '"input" event should always bubble (when defaultParagraphSeparator is "p") #1');
   is(inputEvents[0].inputType, "insertParagraph",
      'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "p") #1');
+  is(inputEvents[0].data, null,
+     'data should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "p") #1');
+  is(inputEvents[0].dataTransfer, null,
+     'dataTransfer should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "p") #1');
 
   contenteditable.innerHTML = "<p>abcdef</p>";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild.firstChild, 3);
   inputEvents = [];
   contenteditable.addEventListener("input", onInput);
   getPlaintextEditor(contenteditable).insertLineBreak();
@@ -182,16 +206,20 @@ SimpleTest.waitForFocus(function() {
   ok(inputEvents[0] instanceof InputEvent,
      '"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "p") #2');
   is(inputEvents[0].cancelable, false,
      '"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "p") #2');
   is(inputEvents[0].bubbles, true,
      '"input" event should always bubble (when defaultParagraphSeparator is "p") #2');
   is(inputEvents[0].inputType, "insertParagraph",
      'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "p") #2');
+  is(inputEvents[0].data, null,
+     'data should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "p") #2');
+  is(inputEvents[0].dataTransfer, null,
+     'dataTransfer should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "p") #2');
 
   contenteditable.innerHTML = "<div>abcdef</div>";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild.firstChild, 3);
   inputEvents = [];
   contenteditable.addEventListener("input", onInput);
   getPlaintextEditor(contenteditable).insertLineBreak();
@@ -203,16 +231,20 @@ SimpleTest.waitForFocus(function() {
   ok(inputEvents[0] instanceof InputEvent,
      '"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "p") #3');
   is(inputEvents[0].cancelable, false,
      '"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "p") #3');
   is(inputEvents[0].bubbles, true,
      '"input" event should always bubble (when defaultParagraphSeparator is "p") #3');
   is(inputEvents[0].inputType, "insertParagraph",
      'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "p") #3');
+  is(inputEvents[0].data, null,
+     'data should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "p") #3');
+  is(inputEvents[0].dataTransfer, null,
+     'dataTransfer should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "p") #3');
 
   contenteditable.innerHTML = "<pre>abcdef</pre>";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild.firstChild, 3);
   inputEvents = [];
   contenteditable.addEventListener("input", onInput);
   getPlaintextEditor(contenteditable).insertLineBreak();
@@ -224,16 +256,20 @@ SimpleTest.waitForFocus(function() {
   ok(inputEvents[0] instanceof InputEvent,
      '"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "p") #4');
   is(inputEvents[0].cancelable, false,
      '"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "p") #4');
   is(inputEvents[0].bubbles, true,
      '"input" event should always bubble (when defaultParagraphSeparator is "p") #4');
   is(inputEvents[0].inputType, "insertParagraph",
      'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "p") #4');
+  is(inputEvents[0].data, null,
+     'data should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "p") #4');
+  is(inputEvents[0].dataTransfer, null,
+     'dataTransfer should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "p") #4');
 
   document.execCommand("defaultParagraphSeparator", false, "div");
 
   contenteditable.innerHTML = "abcdef";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild, 3);
   inputEvents = [];
@@ -247,16 +283,20 @@ SimpleTest.waitForFocus(function() {
   ok(inputEvents[0] instanceof InputEvent,
      '"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "div") #1');
   is(inputEvents[0].cancelable, false,
      '"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "div") #1');
   is(inputEvents[0].bubbles, true,
      '"input" event should always bubble (when defaultParagraphSeparator is "div") #1');
   is(inputEvents[0].inputType, "insertParagraph",
      'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "div") #1');
+  is(inputEvents[0].data, null,
+     'data should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "div") #1');
+  is(inputEvents[0].dataTransfer, null,
+     'dataTransfer should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "div") #1');
 
   contenteditable.innerHTML = "<p>abcdef</p>";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild.firstChild, 3);
   inputEvents = [];
   contenteditable.addEventListener("input", onInput);
   getPlaintextEditor(contenteditable).insertLineBreak();
@@ -268,16 +308,20 @@ SimpleTest.waitForFocus(function() {
   ok(inputEvents[0] instanceof InputEvent,
      '"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "div") #2');
   is(inputEvents[0].cancelable, false,
      '"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "div") #2');
   is(inputEvents[0].bubbles, true,
      '"input" event should always bubble (when defaultParagraphSeparator is "div") #2');
   is(inputEvents[0].inputType, "insertParagraph",
      'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "div") #2');
+  is(inputEvents[0].data, null,
+     'data should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "div") #2');
+  is(inputEvents[0].dataTransfer, null,
+     'dataTransfer should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "div") #2');
 
   contenteditable.innerHTML = "<div>abcdef</div>";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild.firstChild, 3);
   inputEvents = [];
   contenteditable.addEventListener("input", onInput);
   getPlaintextEditor(contenteditable).insertLineBreak();
@@ -289,16 +333,20 @@ SimpleTest.waitForFocus(function() {
   ok(inputEvents[0] instanceof InputEvent,
      '"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "div") #3');
   is(inputEvents[0].cancelable, false,
      '"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "div") #3');
   is(inputEvents[0].bubbles, true,
      '"input" event should always bubble (when defaultParagraphSeparator is "div") #3');
   is(inputEvents[0].inputType, "insertParagraph",
      'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "div") #3');
+  is(inputEvents[0].data, null,
+     'data should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "div") #3');
+  is(inputEvents[0].dataTransfer, null,
+     'dataTransfer should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "div") #3');
 
   contenteditable.innerHTML = "<pre>abcdef</pre>";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild.firstChild, 3);
   inputEvents = [];
   contenteditable.addEventListener("input", onInput);
   getPlaintextEditor(contenteditable).insertLineBreak();
@@ -310,16 +358,20 @@ SimpleTest.waitForFocus(function() {
   ok(inputEvents[0] instanceof InputEvent,
      '"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "div") #4');
   is(inputEvents[0].cancelable, false,
      '"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "div") #4');
   is(inputEvents[0].bubbles, true,
      '"input" event should always bubble (when defaultParagraphSeparator is "div") #4');
   is(inputEvents[0].inputType, "insertParagraph",
      'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "div") #4');
+  is(inputEvents[0].data, null,
+     'data should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "div") #4');
+  is(inputEvents[0].dataTransfer, null,
+     'dataTransfer should be null if inputType is "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "div") #4');
 
   SimpleTest.finish();
 });
 
 function getPlaintextEditor(aEditorElement) {
   let editor = aEditorElement ? SpecialPowers.wrap(aEditorElement).editor : null;
   if (!editor) {
     editor = SpecialPowers.wrap(window).docShell.editingSession.getEditorForWindow(window);
--- a/editor/libeditor/tests/test_nsITableEditor_deleteTableCell.html
+++ b/editor/libeditor/tests/test_nsITableEditor_deleteTableCell.html
@@ -23,16 +23,20 @@ SimpleTest.waitForFocus(function() {
     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, "deleteContent",
        `inputType should be "deleteContent" ${aDescription}`);
+    is(aEvent.data, null,
+       `data should be null ${aDescription}`);
+    is(aEvent.dataTransfer, null,
+       `data should be null ${aDescription}`);
   }
 
   let inputEvents = [];
   function onInput(aEvent) {
     inputEvents.push(aEvent);
   }
   editor.addEventListener("input", onInput);
 
--- a/editor/libeditor/tests/test_nsITableEditor_deleteTableCellContents.html
+++ b/editor/libeditor/tests/test_nsITableEditor_deleteTableCellContents.html
@@ -23,17 +23,21 @@ SimpleTest.waitForFocus(function() {
     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, "deleteContent",
        `inputType should be "deleteContent" ${aDescription}`);
-  }
+     is(aEvent.data, null,
+       `data should be null ${aDescription}`);
+    is(aEvent.dataTransfer, null,
+       `data should be null ${aDescription}`);
+ }
 
   let inputEvents = [];
   function onInput(aEvent) {
     inputEvents.push(aEvent);
   }
   editor.addEventListener("input", onInput);
 
   selection.collapse(editor.firstChild, 0);
--- a/editor/libeditor/tests/test_nsITableEditor_deleteTableColumn.html
+++ b/editor/libeditor/tests/test_nsITableEditor_deleteTableColumn.html
@@ -23,16 +23,20 @@ SimpleTest.waitForFocus(function() {
     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, "deleteContent",
        `inputType should be "deleteContent" ${aDescription}`);
+    is(aEvent.data, null,
+       `data should be null ${aDescription}`);
+    is(aEvent.dataTransfer, null,
+       `data should be null ${aDescription}`);
   }
 
   let inputEvents = [];
   function onInput(aEvent) {
     inputEvents.push(aEvent);
   }
   editor.addEventListener("input", onInput);
 
--- a/editor/libeditor/tests/test_nsITableEditor_deleteTableRow.html
+++ b/editor/libeditor/tests/test_nsITableEditor_deleteTableRow.html
@@ -23,16 +23,20 @@ SimpleTest.waitForFocus(function() {
     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, "deleteContent",
        `inputType should be "deleteContent" ${aDescription}`);
+    is(aEvent.data, null,
+       `data should be null ${aDescription}`);
+    is(aEvent.dataTransfer, null,
+       `data should be null ${aDescription}`);
   }
 
   let inputEvents = [];
   function onInput(aEvent) {
     inputEvents.push(aEvent);
   }
   editor.addEventListener("input", onInput);
 
--- a/editor/libeditor/tests/test_nsITableEditor_insertTableCell.html
+++ b/editor/libeditor/tests/test_nsITableEditor_insertTableCell.html
@@ -23,16 +23,20 @@ SimpleTest.waitForFocus(function() {
     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, "",
        `inputType should be empty string ${aDescription}`);
+    is(aEvent.data, null,
+       `data should be null ${aDescription}`);
+    is(aEvent.dataTransfer, null,
+       `data should be null ${aDescription}`);
   }
 
   let inputEvents = [];
   function onInput(aEvent) {
     inputEvents.push(aEvent);
   }
   editor.addEventListener("input", onInput);
 
--- a/editor/libeditor/tests/test_nsITableEditor_insertTableColumn.html
+++ b/editor/libeditor/tests/test_nsITableEditor_insertTableColumn.html
@@ -23,16 +23,20 @@ SimpleTest.waitForFocus(function() {
     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, "",
        `inputType should be empty string ${aDescription}`);
+    is(aEvent.data, null,
+       `data should be null ${aDescription}`);
+    is(aEvent.dataTransfer, null,
+       `data should be null ${aDescription}`);
   }
 
   let inputEvents = [];
   function onInput(aEvent) {
     inputEvents.push(aEvent);
   }
   editor.addEventListener("input", onInput);
 
--- a/editor/libeditor/tests/test_nsITableEditor_insertTableRow.html
+++ b/editor/libeditor/tests/test_nsITableEditor_insertTableRow.html
@@ -23,16 +23,20 @@ SimpleTest.waitForFocus(function() {
     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, "",
        `inputType should be empty string ${aDescription}`);
+    is(aEvent.data, null,
+       `data should be null ${aDescription}`);
+    is(aEvent.dataTransfer, null,
+       `data should be null ${aDescription}`);
   }
 
   let inputEvents = [];
   function onInput(aEvent) {
     inputEvents.push(aEvent);
   }
   editor.addEventListener("input", onInput);
 
--- a/editor/libeditor/tests/test_resizers_resizing_elements.html
+++ b/editor/libeditor/tests/test_resizers_resizing_elements.html
@@ -94,16 +94,20 @@ SimpleTest.waitForFocus(async function()
         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, "",
            `inputType should be empty string when an element is resized`);
+        is(aEvent.data, null,
+           `data should be null ${aDescription}`);
+        is(aEvent.dataTransfer, null,
+           `data should be null ${aDescription}`);
       }
 
       content.addEventListener("input", onInput);
 
       // Click on the correct resizer
       synthesizeMouse(target, basePosX, basePosY, {type: "mousedown"});
       // Drag it delta pixels to the right and bottom (or maybe left and top!)
       synthesizeMouse(target, basePosX + deltaX, basePosY + deltaY, {type: "mousemove"});
--- a/editor/libeditor/tests/test_undo_after_spellchecker_replaces_word.html
+++ b/editor/libeditor/tests/test_undo_after_spellchecker_replaces_word.html
@@ -27,16 +27,18 @@ SimpleTest.waitForFocus(() => {
     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}`);
+    is(aEvent.dataTransfer, null,
+       `dataTransfer should be null ${aDescription}`);
   }
 
   let inputEvents = [];
   function onInput(aEvent) {
     inputEvents.push(aEvent);
   }
 
   SpecialPowers.Cu.import(
--- a/editor/libeditor/tests/test_undo_redo_stack_after_setting_value.html
+++ b/editor/libeditor/tests/test_undo_redo_stack_after_setting_value.html
@@ -35,16 +35,18 @@ SimpleTest.waitForFocus(function() {
       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}`);
+      is(aEvent.dataTransfer, null,
+         `dataTransfer should be null ${aDescription}`);
     }
 
     let inputEvents = [];
     function onInput(aEvent) {
       inputEvents.push(aEvent);
     }
     editableElement.addEventListener("input", onInput);
 
--- a/toolkit/components/satchel/test/test_form_autocomplete.html
+++ b/toolkit/components/satchel/test/test_form_autocomplete.html
@@ -998,16 +998,18 @@ function runTest() { // eslint-disable-l
         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"');
+        is(event.dataTransfer, null,
+           testNum + " dataTransfer should be null");
       }, {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
@@ -463,16 +463,17 @@ function runTest() {
       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"');
+        is(event.dataTransfer, null, "dataTransfer should be null");
         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
@@ -40,16 +40,18 @@ function handleInput(aEvent) {
   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}"`);
+  is(aEvent.dataTransfer, null,
+     "dataTransfer should be null");
   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
@@ -95,16 +95,18 @@ nsDoTestsForEditorWithAutoComplete.proto
       this._is(aInputEvents[i].cancelable, false,
                this._description + ", " + aTest.description + ': "input" event should be never cancelable');
       this._is(aInputEvents[i].bubbles, true,
                this._description + ", " + aTest.description + ': "input" event should always bubble');
       this._is(aInputEvents[i].inputType, aTest.inputEvents[i].inputType,
                this._description + ", " + aTest.description + ': inputType of "input" event should be "${aTest.inputEvents[i].inputType}"');
       this._is(aInputEvents[i].data, aTest.inputEvents[i].data,
                this._description + ", " + aTest.description + ': data of "input" event should be ${aTest.inputEvents[i].data}');
+      this._is(aInputEvents[i].dataTransfer, null,
+               this._description + ", " + aTest.description + ': dataTransfer of "input" event should be null');
     }
   },
 
   _tests: [
     { description: "Undo/Redo behavior check when typed text exactly matches the case: type 'Mo'",
       completeDefaultIndex: false,
       execute(aWindow, aTarget) {
         synthesizeKey("M", { shiftKey: true }, aWindow);
--- a/widget/EventForwards.h
+++ b/widget/EventForwards.h
@@ -168,16 +168,33 @@ inline bool IsDataAvailableOnHTMLEditor(
     case EditorInputType::eFormatFontColor:
     case EditorInputType::eFormatFontName:
       return true;
     default:
       return false;
   }
 }
 
+/**
+ * IsDataTransferAvailableOnHTMLEditor() returns true if aInputType on
+ * HTMLEditor should have non-null InputEvent.dataTransfer value.
+ */
+inline bool IsDataTransferAvailableOnHTMLEditor(EditorInputType aInputType) {
+  switch (aInputType) {
+    case EditorInputType::eInsertFromPaste:
+    case EditorInputType::eInsertFromDrop:
+    case EditorInputType::eInsertTranspose:
+    case EditorInputType::eInsertReplacementText:
+    case EditorInputType::eInsertFromYank:
+      return true;
+    default:
+      return false;
+  }
+}
+
 #define NS_DEFINE_COMMAND(aName, aCommandStr) , Command##aName
 #define NS_DEFINE_COMMAND_NO_EXEC_COMMAND(aName) , Command##aName
 
 typedef int8_t CommandInt;
 enum Command : CommandInt {
   CommandDoNothing
 
 #include "mozilla/CommandList.h"
--- a/widget/tests/window_composition_text_querycontent.xul
+++ b/widget/tests/window_composition_text_querycontent.xul
@@ -648,16 +648,17 @@ function checkInputEvent(aEvent, aIsComp
     return;
   }
   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.isComposing, aIsComposing, `isComposing should be ${aIsComposing}: ${aDescription}`);
   is(aEvent.inputType, aInputType, `inputType should be "${aInputType}": ${aDescription}`);
   is(aEvent.data, aData, `data should be ${aData}: ${aDescription}`);
+  is(aEvent.dataTransfer, null, `dataTransfer should be null: ${aDescription}`);
 }
 
 function runCompositionCommitAsIsTest()
 {
   textarea.focus();
 
   var result = [];
   function clearResult()