Bug 1447239 - Implement InputEvent.inputType r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Mon, 07 Jan 2019 10:10:57 +0000
changeset 509796 cd0f006ea4b311f6223c6d151f09aa9758c7ad92
parent 509795 f8237a1e70b899fb91131718054f2d9e53bc082c
child 509797 b97e2750407e3e98d7165c5605732ce2a7b0e68c
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1447239
milestone66.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 1447239 - Implement InputEvent.inputType r=smaug This patch implements InputType.inputType which is declared by Input Events. The attribute has already been implemented by Chrome and Safari. Chrome implements Input Events Level 1, but Safari implements Input Events Level 2. Difference between them is only whether it supports "insertFromComposition", "deleteByComposition" and "deleteCompositionText". This patch makes the level switchable with pref and takes Level 1 by default because Level 2 is still unstable around event order with composition events. For reducing string copy cost at dispatching "input" event, this patch makes EditorInternalInputEvent store valid input-type as enum class, EditorInputType and resolves it to string value when dom::InputEvent::GetInputType() is called. Note that the reason why this patch names the enum class as EditorInputType is, there is InputType enum class already for avoiding conflict the name, this appends "Editor" prefix because "input" and "beforeinput" events are fired only when an editor has focus. Differential Revision: https://phabricator.services.mozilla.com/D14128
browser/extensions/formautofill/test/mochitest/formautofill_common.js
browser/extensions/formautofill/test/mochitest/test_clear_form.html
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/events/InputEvent.cpp
dom/events/InputEvent.h
dom/events/InputTypeList.h
dom/events/moz.build
dom/events/test/test_eventctors.html
dom/html/nsTextEditorState.cpp
dom/html/test/forms/test_MozEditableElement_setUserInput.html
dom/html/test/forms/test_input_event.html
dom/tests/mochitest/general/test_clipboard_events.html
dom/webidl/InputEvent.webidl
editor/libeditor/EditAction.h
editor/libeditor/EditorBase.cpp
editor/libeditor/EditorBase.h
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
modules/libpref/init/StaticPrefList.h
modules/libpref/init/all.js
testing/web-platform/meta/input-events/idlharness.window.js.ini
testing/web-platform/meta/input-events/input-events-exec-command.html.ini
testing/web-platform/meta/uievents/idlharness.window.js.ini
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/modules/sessionstore/FormData.jsm
widget/EventForwards.h
widget/SharedWidgetUtils.cpp
widget/TextEvents.h
widget/WidgetEventImpl.cpp
widget/tests/window_composition_text_querycontent.xul
--- a/browser/extensions/formautofill/test/mochitest/formautofill_common.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_common.js
@@ -112,16 +112,18 @@ function triggerAutofillAndCheckProfile(
   for (const [fieldName, value] of Object.entries(adaptedProfile)) {
     const element = document.getElementById(fieldName);
     const expectingEvent = document.activeElement == element ? "DOMAutoComplete" : "change";
     const checkFieldAutofilled = Promise.all([
       new Promise(resolve => element.addEventListener("input", (event) => {
         if (element.tagName == "INPUT" && element.type == "text") {
           ok(event instanceof InputEvent,
              `"input" event should be dispatched with InputEvent interface on ${element.tagName}`);
+          is(event.inputType, "insertReplacementText",
+             "inputType value should be \"insertReplacementText\"");
         } 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_clear_form.html
+++ b/browser/extensions/formautofill/test/mochitest/test_clear_form.html
@@ -69,16 +69,18 @@ async function confirmClear(selector) {
   let promise = new Promise(resolve =>
     document.querySelector(selector).addEventListener("input", (event) => {
       ok(event instanceof InputEvent,
          '"input" event should be dispatched with InputEvent interface');
       is(event.cancelable, false,
          '"input" event should be never cancelable');
       is(event.bubbles, true,
          '"input" event should always bubble');
+      is(event.inputType, "insertReplacementText",
+         'inputType value should be "insertReplacementText"');
       resolve();
     }, {once: true})
   );
   synthesizeKey("KEY_Enter");
   await promise;
 }
 
 add_task(async function simple_clear() {
--- 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
@@ -47,16 +47,18 @@ let MOCK_STORAGE = [{
 function checkElementFilled(element, expectedvalue) {
   return [
     new Promise(resolve => {
       element.addEventListener("input", function onInput(event) {
         ok(true, "Checking " + element.name + " field fires input event");
         if (element.tagName == "INPUT" && element.type == "text") {
           ok(event instanceof InputEvent,
              `"input" event should be dispatched with InputEvent interface on ${element.name}`);
+          is(event.inputType, "insertReplacementText",
+             "inputType value should be \"insertReplacementText\"");
         } 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
@@ -4099,21 +4099,23 @@ nsresult nsContentUtils::DispatchEvent(D
     *aDefaultAction = (status != nsEventStatus_eConsumeNoDefault);
   }
   return rv;
 }
 
 // static
 nsresult nsContentUtils::DispatchInputEvent(Element* aEventTargetElement) {
   RefPtr<TextEditor> textEditor;  // See bug 1506439
-  return DispatchInputEvent(aEventTargetElement, textEditor);
+  return DispatchInputEvent(aEventTargetElement, EditorInputType::eUnknown,
+                            textEditor);
 }
 
 // static
 nsresult nsContentUtils::DispatchInputEvent(Element* aEventTargetElement,
+                                            EditorInputType aEditorInputType,
                                             TextEditor* aTextEditor) {
   if (NS_WARN_IF(!aEventTargetElement)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   // If this is called from editor, the instance should be set to aTextEditor.
   // Otherwise, we need to look for an editor for aEventTargetElement.
   // However, we don't need to do it for HTMLEditor since nobody shouldn't
@@ -4137,16 +4139,17 @@ nsresult nsContentUtils::DispatchInputEv
     nsCOMPtr<nsITextControlElement> textControlElement =
         do_QueryInterface(aEventTargetElement);
     MOZ_ASSERT(!textControlElement,
                "The event target may have editor, but we've not known it yet.");
   }
 #endif  // #ifdef DEBUG
 
   if (!useInputEvent) {
+    MOZ_ASSERT(aEditorInputType == EditorInputType::eUnknown);
     // Dispatch "input" event with Event instance.
     WidgetEvent widgetEvent(true, eUnidentifiedEvent);
     widgetEvent.mSpecifiedEventType = nsGkAtoms::oninput;
     widgetEvent.mFlags.mCancelable = false;
     // Using same time as nsContentUtils::DispatchEvent() for backward
     // compatibility.
     widgetEvent.mTime = PR_Now();
     (new AsyncEventDispatcher(aEventTargetElement, widgetEvent))
@@ -4191,16 +4194,18 @@ nsresult nsContentUtils::DispatchInputEv
   // Note that EditorBase::IsIMEComposing() may return false even when we
   // need to set it to true.
   // Otherwise, i.e., editor hasn't been created for the element yet,
   // we should set isComposing to false since the element can never has
   // composition without editor.
   inputEvent.mIsComposing =
       aTextEditor ? !!aTextEditor->GetComposition() : false;
 
+  inputEvent.mInputType = aEditorInputType;
+
   (new AsyncEventDispatcher(aEventTargetElement, inputEvent))
       ->RunDOMEventWhenSafe();
   return NS_OK;
 }
 
 nsresult nsContentUtils::DispatchChromeEvent(
     Document* aDoc, nsISupports* aTarget, const nsAString& aEventName,
     CanBubble aCanBubble, Cancelable aCancelable, bool* aDefaultAction) {
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1392,24 +1392,28 @@ class nsContentUtils {
    * unsafe to dispatch, this put the event into the script runner queue.
    * Input Events spec defines as:
    *   Input events are dispatched on elements that act as editing hosts,
    *   including elements with the contenteditable attribute set, textarea
    *   elements, and input elements that permit text input.
    *
    * @param aEventTarget        The event target element of the "input" event.
    *                            Must not be nullptr.
+   * @param aEditorInputType    The inputType value of InputEvent.
+   *                            If aEventTarget won't dispatch "input" event
+   *                            with InputEvent, set EditorInputType::eUnknown.
    * @param aTextEditor         Optional.  If this is called by editor,
    *                            editor should set this.  Otherwise, leave
    *                            nullptr.
    */
   MOZ_CAN_RUN_SCRIPT
   static nsresult DispatchInputEvent(Element* aEventTarget);
   MOZ_CAN_RUN_SCRIPT
   static nsresult DispatchInputEvent(Element* aEventTarget,
+                                     mozilla::EditorInputType aEditorInputType,
                                      mozilla::TextEditor* aTextEditor);
 
   /**
    * This method creates and dispatches a untrusted event.
    * Works only with events which can be created by calling
    * Document::CreateEvent() with parameter "Events".
    * @param aDoc           The document which will be used to create the event.
    * @param aTarget        The target of the event, should be QIable to
--- a/dom/base/test/chrome/window_nsITextInputProcessor.xul
+++ b/dom/base/test/chrome/window_nsITextInputProcessor.xul
@@ -54,24 +54,25 @@ function finish()
   window.close();
 }
 
 function onunload()
 {
   SimpleTest.finish();
 }
 
-function checkInputEvent(aEvent, aIsComposing, aDescription) {
+function checkInputEvent(aEvent, aIsComposing, 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.isComposing, aIsComposing, `${aDescription}isComposing should be ${aIsComposing}`);
+  is(aEvent.inputType, aInputType, `${aDescription}inputType should be "${aInputType}"`);
 }
 
 const kIsMac = (navigator.platform.indexOf("Mac") == 0);
 
 var iframe = document.getElementById("iframe");
 var childWindow = iframe.contentWindow;
 var textareaInFrame;
 var input = document.getElementById("input");
@@ -227,17 +228,17 @@ function runBeginInputTransactionMethodT
   is(events[0].type, "compositionstart",
      description + "events[0] should be compositionstart");
   is(events[1].type, "compositionupdate",
      description + "events[1] should be compositionupdate");
   is(events[2].type, "text",
      description + "events[2] should be text");
   is(events[3].type, "input",
      description + "events[3] should be input");
-  checkInputEvent(events[3], true, description);
+  checkInputEvent(events[3], true, "insertCompositionText", description);
   TIP1.cancelComposition();
 
   // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitComposition().
   events = [];
   TIP1.beginInputTransaction(window, simpleCallback);
   TIP1.setPendingCompositionString(composingStr);
   TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
   TIP1.flushPendingComposition();
@@ -263,17 +264,17 @@ function runBeginInputTransactionMethodT
   is(events.length, 3,
      description + "text, compositionend and input events should be fired by TIP1.commitComposition()");
   is(events[0].type, "text",
      description + "events[0] should be text");
   is(events[1].type, "compositionend",
      description + "events[1] should be compositionend");
   is(events[2].type, "input",
      description + "events[2] should be input");
-  checkInputEvent(events[2], false, description);
+  checkInputEvent(events[2], false, "insertCompositionText", description);
 
   // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar").
   events = [];
   input.addEventListener("compositionstart", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
     ok(!TIP2.beginInputTransaction(window, simpleCallback),
        description + "TIP2 shouldn't be able to begin input transaction from compositionstart event handler during TIP1.commitCompositionWith(\"bar\");");
@@ -311,17 +312,17 @@ function runBeginInputTransactionMethodT
   is(events[1].type, "compositionupdate",
      description + "events[1] should be compositionupdate");
   is(events[2].type, "text",
      description + "events[2] should be text");
   is(events[3].type, "compositionend",
      description + "events[3] should be compositionend");
   is(events[4].type, "input",
      description + "events[4] should be input");
-  checkInputEvent(events[4], false, description);
+  checkInputEvent(events[4], false, "insertCompositionText", description);
 
   // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during cancelComposition().
   events = [];
   TIP1.beginInputTransaction(window, simpleCallback);
   TIP1.setPendingCompositionString(composingStr);
   TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
   TIP1.flushPendingComposition();
   input.addEventListener("compositionupdate", function (aEvent) {
@@ -354,17 +355,17 @@ function runBeginInputTransactionMethodT
   is(events[0].type, "compositionupdate",
      description + "events[0] should be compositionupdate");
   is(events[1].type, "text",
      description + "events[1] should be text");
   is(events[2].type, "compositionend",
      description + "events[2] should be compositionend");
   is(events[3].type, "input",
      description + "events[3] should be input");
-  checkInputEvent(events[3], false, description);
+  checkInputEvent(events[3], false, "insertCompositionText", description);
 
   // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during keydown() and keyup().
   events = [];
   TIP1.beginInputTransaction(window, simpleCallback);
   input.addEventListener("keydown", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
     ok(!TIP2.beginInputTransaction(window, simpleCallback),
@@ -394,17 +395,17 @@ function runBeginInputTransactionMethodT
   is(events.length, 4,
      description + "keydown, keypress, input, keyup events should be fired by TIP1.keydown() and TIP1.keyup()");
   is(events[0].type, "keydown",
      description + "events[0] should be keydown");
   is(events[1].type, "keypress",
      description + "events[1] should be keypress");
   is(events[2].type, "input",
      description + "events[2] should be input");
-  checkInputEvent(events[2], false, description);
+  checkInputEvent(events[2], false, "insertText", description);
   is(events[3].type, "keyup",
      description + "events[3] should be keyup");
 
   // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during startComposition().
   var events = [];
   input.addEventListener("compositionstart", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
@@ -452,17 +453,17 @@ function runBeginInputTransactionMethodT
   is(events[0].type, "compositionstart",
      description + "events[0] should be compositionstart");
   is(events[1].type, "compositionupdate",
      description + "events[1] should be compositionupdate");
   is(events[2].type, "text",
      description + "events[2] should be text");
   is(events[3].type, "input",
      description + "events[3] should be input");
-  checkInputEvent(events[3], true, description);
+  checkInputEvent(events[3], true, "insertCompositionText", description);
   TIP1.cancelComposition();
 
   // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitComposition().
   events = [];
   TIP1.beginInputTransactionForTests(window, simpleCallback);
   TIP1.setPendingCompositionString(composingStr);
   TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
   TIP1.flushPendingComposition();
@@ -488,17 +489,17 @@ function runBeginInputTransactionMethodT
   is(events.length, 3,
      description + "text, compositionend and input events should be fired by TIP1.commitComposition()");
   is(events[0].type, "text",
      description + "events[0] should be text");
   is(events[1].type, "compositionend",
      description + "events[1] should be compositionend");
   is(events[2].type, "input",
      description + "events[2] should be input");
-  checkInputEvent(events[2], false, description);
+  checkInputEvent(events[2], false, "insertCompositionText", description);
 
   // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar").
   events = [];
   input.addEventListener("compositionstart", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
     ok(!TIP2.beginInputTransactionForTests(window),
        description + "TIP2 shouldn't be able to begin input transaction for tests from compositionstart event handler during TIP1.commitCompositionWith(\"bar\");");
@@ -536,17 +537,17 @@ function runBeginInputTransactionMethodT
   is(events[1].type, "compositionupdate",
      description + "events[1] should be compositionupdate");
   is(events[2].type, "text",
      description + "events[2] should be text");
   is(events[3].type, "compositionend",
      description + "events[3] should be compositionend");
   is(events[4].type, "input",
      description + "events[4] should be input");
-  checkInputEvent(events[4], false, description);
+  checkInputEvent(events[4], false, "insertCompositionText", description);
 
   // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during cancelComposition().
   events = [];
   TIP1.beginInputTransactionForTests(window, simpleCallback);
   TIP1.setPendingCompositionString(composingStr);
   TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
   TIP1.flushPendingComposition();
   input.addEventListener("compositionupdate", function (aEvent) {
@@ -579,17 +580,17 @@ function runBeginInputTransactionMethodT
   is(events[0].type, "compositionupdate",
      description + "events[0] should be compositionupdate");
   is(events[1].type, "text",
      description + "events[1] should be text");
   is(events[2].type, "compositionend",
      description + "events[2] should be compositionend");
   is(events[3].type, "input",
      description + "events[3] should be input");
-  checkInputEvent(events[3], false, description);
+  checkInputEvent(events[3], false, "insertCompositionText", description);
 
   // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during keydown() and keyup().
   events = [];
   TIP1.beginInputTransactionForTests(window);
   input.addEventListener("keydown", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
     ok(!TIP2.beginInputTransactionForTests(window),
@@ -619,17 +620,17 @@ function runBeginInputTransactionMethodT
   is(events.length, 4,
      description + "keydown, keypress, input, keyup events should be fired by TIP1.keydown() and TIP1.keyup()");
   is(events[0].type, "keydown",
      description + "events[0] should be keydown");
   is(events[1].type, "keypress",
      description + "events[1] should be keypress");
   is(events[2].type, "input",
      description + "events[2] should be input");
-  checkInputEvent(events[2], false, description);
+  checkInputEvent(events[2], false, "insertText", description);
   is(events[3].type, "keyup",
      description + "events[3] should be keyup");
 
   // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during startComposition().
   var events = [];
   input.addEventListener("compositionstart", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
@@ -707,17 +708,17 @@ function runBeginInputTransactionMethodT
   is(events[0].type, "compositionstart",
      description + "events[0] should be compositionstart");
   is(events[1].type, "compositionupdate",
      description + "events[1] should be compositionupdate");
   is(events[2].type, "text",
      description + "events[2] should be text");
   is(events[3].type, "input",
      description + "events[3] should be input");
-  checkInputEvent(events[3], true, description);
+  checkInputEvent(events[3], true, "insertCompositionText", description);
   TIP1.cancelComposition();
 
   // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during commitComposition().
   events = [];
   TIP1.beginInputTransaction(window, simpleCallback);
   TIP1.setPendingCompositionString(composingStr);
   TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
   TIP1.flushPendingComposition();
@@ -761,17 +762,17 @@ function runBeginInputTransactionMethodT
   is(events.length, 3,
      description + "text, compositionend and input events should be fired by TIP1.commitComposition()");
   is(events[0].type, "text",
      description + "events[0] should be text");
   is(events[1].type, "compositionend",
      description + "events[1] should be compositionend");
   is(events[2].type, "input",
      description + "events[2] should be input");
-  checkInputEvent(events[2], false, description);
+  checkInputEvent(events[2], false, "insertCompositionText", description);
 
   // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during commitCompositionWith("bar");.
   events = [];
   input.addEventListener("compositionstart", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
     try {
       TIP1.beginInputTransaction(otherWindow, simpleCallback);
@@ -839,17 +840,17 @@ function runBeginInputTransactionMethodT
   is(events[1].type, "compositionupdate",
      description + "events[1] should be compositionupdate");
   is(events[2].type, "text",
      description + "events[2] should be text");
   is(events[3].type, "compositionend",
      description + "events[3] should be compositionend");
   is(events[4].type, "input",
      description + "events[4] should be input");
-  checkInputEvent(events[4], false, description);
+  checkInputEvent(events[4], false, "insertCompositionText", description);
 
   // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during cancelComposition();.
   events = [];
   TIP1.beginInputTransaction(window, simpleCallback);
   TIP1.setPendingCompositionString(composingStr);
   TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
   TIP1.flushPendingComposition();
   input.addEventListener("compositionupdate", function (aEvent) {
@@ -906,17 +907,17 @@ function runBeginInputTransactionMethodT
   is(events[0].type, "compositionupdate",
      description + "events[0] should be compositionupdate");
   is(events[1].type, "text",
      description + "events[1] should be text");
   is(events[2].type, "compositionend",
      description + "events[2] should be compositionend");
   is(events[3].type, "input",
      description + "events[3] should be input");
-  checkInputEvent(events[3], false, description);
+  checkInputEvent(events[3], false, "insertCompositionText", description);
 
   // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during keydown() and keyup();.
   events = [];
   TIP1.beginInputTransaction(window, simpleCallback);
   input.addEventListener("keydown", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
     try {
@@ -970,17 +971,17 @@ function runBeginInputTransactionMethodT
   is(events.length, 4,
      description + "keydown, keypress, input, keyup events should be fired by TIP1.keydown() and TIP1.keyup()");
   is(events[0].type, "keydown",
      description + "events[0] should be keydown");
   is(events[1].type, "keypress",
      description + "events[1] should be keypress");
   is(events[2].type, "input",
      description + "events[2] should be input");
-  checkInputEvent(events[2], false, description);
+  checkInputEvent(events[2], false, "insertText", description);
   is(events[3].type, "keyup",
      description + "events[3] should be keyup");
 
   // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during startComposition().
   var events = [];
   input.addEventListener("compositionstart", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
@@ -1058,17 +1059,17 @@ function runBeginInputTransactionMethodT
   is(events[0].type, "compositionstart",
      description + "events[0] should be compositionstart");
   is(events[1].type, "compositionupdate",
      description + "events[1] should be compositionupdate");
   is(events[2].type, "text",
      description + "events[2] should be text");
   is(events[3].type, "input",
      description + "events[3] should be input");
-  checkInputEvent(events[3], true, description);
+  checkInputEvent(events[3], true, "insertCompositionText", description);
   TIP1.cancelComposition();
 
   // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during commitComposition().
   events = [];
   TIP1.beginInputTransactionForTests(window, simpleCallback);
   TIP1.setPendingCompositionString(composingStr);
   TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
   TIP1.flushPendingComposition();
@@ -1112,17 +1113,17 @@ function runBeginInputTransactionMethodT
   is(events.length, 3,
      description + "text, compositionend and input events should be fired by TIP1.commitComposition()");
   is(events[0].type, "text",
      description + "events[0] should be text");
   is(events[1].type, "compositionend",
      description + "events[1] should be compositionend");
   is(events[2].type, "input",
      description + "events[2] should be input");
-  checkInputEvent(events[2], false, description);
+  checkInputEvent(events[2], false, "insertCompositionText", description);
 
   // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during commitCompositionWith("bar");.
   events = [];
   input.addEventListener("compositionstart", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
     try {
       TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
@@ -1190,17 +1191,17 @@ function runBeginInputTransactionMethodT
   is(events[1].type, "compositionupdate",
      description + "events[1] should be compositionupdate");
   is(events[2].type, "text",
      description + "events[2] should be text");
   is(events[3].type, "compositionend",
      description + "events[3] should be compositionend");
   is(events[4].type, "input",
      description + "events[4] should be input");
-  checkInputEvent(events[4], false, description);
+  checkInputEvent(events[4], false, "insertCompositionText", description);
 
   // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during cancelComposition();.
   events = [];
   TIP1.beginInputTransactionForTests(window, simpleCallback);
   TIP1.setPendingCompositionString(composingStr);
   TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
   TIP1.flushPendingComposition();
   input.addEventListener("compositionupdate", function (aEvent) {
@@ -1257,17 +1258,17 @@ function runBeginInputTransactionMethodT
   is(events[0].type, "compositionupdate",
      description + "events[0] should be compositionupdate");
   is(events[1].type, "text",
      description + "events[1] should be text");
   is(events[2].type, "compositionend",
      description + "events[2] should be compositionend");
   is(events[3].type, "input",
      description + "events[3] should be input");
-  checkInputEvent(events[3], false, description);
+  checkInputEvent(events[3], false, "insertCompositionText", description);
 
   // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during keydown() and keyup();.
   events = [];
   TIP1.beginInputTransactionForTests(window, simpleCallback);
   input.addEventListener("keydown", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
     try {
@@ -1321,17 +1322,17 @@ function runBeginInputTransactionMethodT
   is(events.length, 4,
      description + "keydown, keypress, input, keyup events should be fired by TIP1.keydown() and TIP1.keyup()");
   is(events[0].type, "keydown",
      description + "events[0] should be keydown");
   is(events[1].type, "keypress",
      description + "events[1] should be keypress");
   is(events[2].type, "input",
      description + "events[2] should be input");
-  checkInputEvent(events[2], false, description);
+  checkInputEvent(events[2], false, "insertText", description);
   is(events[3].type, "keyup",
      description + "events[3] should be keyup");
 
   // Let's check if startComposition() throws an exception after ownership is stolen.
   input.value = "";
   ok(TIP1.beginInputTransactionForTests(window),
      description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
   ok(TIP2.beginInputTransactionForTests(window),
--- a/dom/events/InputEvent.cpp
+++ b/dom/events/InputEvent.cpp
@@ -22,29 +22,44 @@ InputEvent::InputEvent(EventTarget* aOwn
   if (aEvent) {
     mEventIsInternal = false;
   } else {
     mEventIsInternal = true;
     mEvent->mTime = PR_Now();
   }
 }
 
+void InputEvent::GetInputType(nsAString& aInputType) {
+  InternalEditorInputEvent* editorInputEvent = mEvent->AsEditorInputEvent();
+  MOZ_ASSERT(editorInputEvent);
+  if (editorInputEvent->mInputType == EditorInputType::eUnknown) {
+    aInputType = mInputTypeValue;
+  } else {
+    editorInputEvent->GetDOMInputTypeName(aInputType);
+  }
+}
+
 bool InputEvent::IsComposing() {
   return mEvent->AsEditorInputEvent()->mIsComposing;
 }
 
 already_AddRefed<InputEvent> InputEvent::Constructor(
     const GlobalObject& aGlobal, const nsAString& aType,
     const InputEventInit& aParam, ErrorResult& aRv) {
   nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports());
   RefPtr<InputEvent> e = new InputEvent(t, nullptr, nullptr);
   bool trusted = e->Init(t);
   e->InitUIEvent(aType, aParam.mBubbles, aParam.mCancelable, aParam.mView,
                  aParam.mDetail);
   InternalEditorInputEvent* internalEvent = e->mEvent->AsEditorInputEvent();
+  internalEvent->mInputType =
+      InternalEditorInputEvent::GetEditorInputType(aParam.mInputType);
+  if (internalEvent->mInputType == EditorInputType::eUnknown) {
+    e->mInputTypeValue = aParam.mInputType;
+  }
   internalEvent->mIsComposing = aParam.mIsComposing;
   e->SetTrusted(trusted);
   e->SetComposed(aParam.mComposed);
   return e.forget();
 }
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/events/InputEvent.h
+++ b/dom/events/InputEvent.h
@@ -26,20 +26,25 @@ class InputEvent : public UIEvent {
                                                   const InputEventInit& aParam,
                                                   ErrorResult& aRv);
 
   virtual JSObject* WrapObjectInternal(
       JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override {
     return InputEvent_Binding::Wrap(aCx, this, aGivenProto);
   }
 
+  void GetInputType(nsAString& aInputType);
   bool IsComposing();
 
  protected:
   ~InputEvent() {}
+
+  // mInputTypeValue stores inputType attribute value if the instance is
+  // created by script and not initialized with known inputType value.
+  nsString mInputTypeValue;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 already_AddRefed<mozilla::dom::InputEvent> NS_NewDOMInputEvent(
     mozilla::dom::EventTarget* aOwner, nsPresContext* aPresContext,
     mozilla::InternalEditorInputEvent* aEvent);
new file mode 100644
--- /dev/null
+++ b/dom/events/InputTypeList.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This header file defines all inputType values which are used for DOM
+ * InputEvent.inputType.
+ * You must define NS_DEFINE_INPUTTYPE macro before including this.
+ *
+ * It must have two arguments, (aCPPName, aDOMName)
+ * aCPPName is usable name for a part of C++ constants.
+ * aDOMName is the actual value declared by the specs:
+ * Level 1:
+ *   https://rawgit.com/w3c/input-events/v1/index.html#interface-InputEvent-Attributes
+ * Level 2:
+ *   https://w3c.github.io/input-events/index.html#interface-InputEvent-Attributes
+ */
+
+NS_DEFINE_INPUTTYPE(InsertText, "insertText")
+NS_DEFINE_INPUTTYPE(InsertReplacementText, "insertReplacementText")
+NS_DEFINE_INPUTTYPE(InsertLineBreak, "insertLineBreak")
+NS_DEFINE_INPUTTYPE(InsertParagraph, "insertParagraph")
+NS_DEFINE_INPUTTYPE(InsertOrderedList, "insertOrderedList")
+NS_DEFINE_INPUTTYPE(InsertUnorderedList, "insertUnorderedList")
+NS_DEFINE_INPUTTYPE(InsertHorizontalRule, "insertHorizontalRule")
+NS_DEFINE_INPUTTYPE(InsertFromYank, "insertFromYank")
+NS_DEFINE_INPUTTYPE(InsertFromDrop, "insertFromDrop")
+NS_DEFINE_INPUTTYPE(InsertFromPaste, "insertFromPaste")
+NS_DEFINE_INPUTTYPE(InsertTranspose, "insertTranspose")
+NS_DEFINE_INPUTTYPE(InsertCompositionText, "insertCompositionText")
+NS_DEFINE_INPUTTYPE(InsertFromComposition,
+                    "insertFromComposition")  // Level 2
+NS_DEFINE_INPUTTYPE(InsertLink, "insertLink")
+NS_DEFINE_INPUTTYPE(DeleteByComposition,
+                    "deleteByComposition")  // Level 2
+NS_DEFINE_INPUTTYPE(DeleteCompositionText,
+                    "deleteCompositionText")  // Level 2
+NS_DEFINE_INPUTTYPE(DeleteWordBackward, "deleteWordBackward")
+NS_DEFINE_INPUTTYPE(DeleteWordForward, "deleteWordForward")
+NS_DEFINE_INPUTTYPE(DeleteSoftLineBackward, "deleteSoftLineBackward")
+NS_DEFINE_INPUTTYPE(DeleteSoftLineForward, "deleteSoftLineForward")
+NS_DEFINE_INPUTTYPE(DeleteEntireSoftLine, "deleteEntireSoftLine")
+NS_DEFINE_INPUTTYPE(DeleteHardLineBackward, "deleteHardLineBackward")
+NS_DEFINE_INPUTTYPE(DeleteHardLineForward, "deleteHardLineForward")
+NS_DEFINE_INPUTTYPE(DeleteByDrag, "deleteByDrag")
+NS_DEFINE_INPUTTYPE(DeleteByCut, "deleteByCut")
+NS_DEFINE_INPUTTYPE(DeleteContent, "deleteContent")
+NS_DEFINE_INPUTTYPE(DeleteContentBackward, "deleteContentBackward")
+NS_DEFINE_INPUTTYPE(DeleteContentForward, "deleteContentForward")
+NS_DEFINE_INPUTTYPE(HistoryUndo, "historyUndo")
+NS_DEFINE_INPUTTYPE(HistoryRedo, "historyRedo")
+NS_DEFINE_INPUTTYPE(FormatBold, "formatBold")
+NS_DEFINE_INPUTTYPE(FormatItalic, "formatItalic")
+NS_DEFINE_INPUTTYPE(FormatUnderline, "formatUnderline")
+NS_DEFINE_INPUTTYPE(FormatStrikeThrough, "formatStrikeThrough")
+NS_DEFINE_INPUTTYPE(FormatSuperscript, "formatSuperscript")
+NS_DEFINE_INPUTTYPE(FormatSubscript, "formatSubscript")
+NS_DEFINE_INPUTTYPE(FormatJustifyFull, "formatJustifyFull")
+NS_DEFINE_INPUTTYPE(FormatJustifyCenter, "formatJustifyCenter")
+NS_DEFINE_INPUTTYPE(FormatJustifyRight, "formatJustifyRight")
+NS_DEFINE_INPUTTYPE(FormatJustifyLeft, "formatJustifyLeft")
+NS_DEFINE_INPUTTYPE(FormatIndent, "formatIndent")
+NS_DEFINE_INPUTTYPE(FormatOutdent, "formatOutdent")
+NS_DEFINE_INPUTTYPE(FormatRemove, "formatRemove")
+NS_DEFINE_INPUTTYPE(FormatSetBlockTextDirection, "formatSetBlockTextDirection")
+NS_DEFINE_INPUTTYPE(FormatSetInlineTextDirection,
+                    "formatSetInlineTextDirection")
+NS_DEFINE_INPUTTYPE(FormatBackColor, "formatBackColor")
+NS_DEFINE_INPUTTYPE(FormatFontColor, "formatFontColor")
+NS_DEFINE_INPUTTYPE(FormatFontName, "formatFontName")
--- a/dom/events/moz.build
+++ b/dom/events/moz.build
@@ -30,16 +30,17 @@ EXPORTS.mozilla += [
     'DOMEventTargetHelper.h',
     'EventDispatcher.h',
     'EventListenerManager.h',
     'EventNameList.h',
     'EventStateManager.h',
     'EventStates.h',
     'IMEContentObserver.h',
     'IMEStateManager.h',
+    'InputTypeList.h',
     'InternalMutationEvent.h',
     'JSEventHandler.h',
     'KeyNameList.h',
     'PendingFullscreenEvent.h',
     'PhysicalKeyCodeNameList.h',
     'TextComposition.h',
     'VirtualKeyCodeList.h',
     'WheelHandlingHelper.h',
--- a/dom/events/test/test_eventctors.html
+++ b/dom/events/test/test_eventctors.html
@@ -880,12 +880,31 @@ is(e.animationName, "bounce3", "Animatio
 is(e.elapsedTime, 3.5, "Animation event copies elapsedTime from AnimationEventInit");
 is(e.pseudoElement, "", "Animation event copies pseudoElement from AnimationEventInit");
 is(e.bubbles, false, "Lack of bubbles property in AnimationEventInit");
 is(e.cancelable, false, "Lack of cancelable property in AnimationEventInit");
 is(e.type, "hello", "Wrong event type!");
 is(e.isTrusted, false, "Event shouldn't be trusted!");
 is(e.eventPhase, Event.NONE, "Wrong event phase");
 
+// InputEvent
+e = new InputEvent("hello", {data: "something data", inputType: "invalid input type", isComposing: true});
+is(e.type, "hello", "InputEvent should set type attribute");
+todo_is(e.data, "something data", "InputEvent should have data attribute");
+is(e.inputType, "invalid input type", "InputEvent should have inputType attribute");
+is(e.isComposing, true, "InputEvent should have isComposing attribute");
+
+e = new InputEvent("hello", {inputType: "insertText"});
+is(e.inputType, "insertText", "InputEvent.inputType should return valid inputType from EditorInputType enum");
+e = new InputEvent("hello", {inputType: "deleteWordBackward"});
+is(e.inputType, "deleteWordBackward", "InputEvent.inputType should return valid inputType from EditorInputType enum");
+e = new InputEvent("hello", {inputType: "formatFontName"});
+is(e.inputType, "formatFontName", "InputEvent.inputType should return valid inputType from EditorInputType enum");
+
+e = new InputEvent("input", {});
+todo_is(e.data, "", "InputEvent.data should be empty string in default");
+is(e.inputType, "", "InputEvent.inputType should be empty string in default");
+is(e.isComposing, false, "InputEvent.isComposing should be false in default");
+
 </script>
 </pre>
 </body>
 </html>
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -2421,23 +2421,25 @@ bool nsTextEditorState::SetValue(const n
       }
 
       // Update the frame display if needed
       if (mBoundFrame) {
         mBoundFrame->UpdateValueDisplay(true);
       }
 
       // If this is called as part of user input, we need to dispatch "input"
-      // event since web apps may want to know the user operation.
+      // event with "insertReplacementText" since web apps may want to know
+      // the user operation which changes editor value with a built-in function
+      // like autocomplete, password manager, session restore, etc.
       if (aFlags & eSetValue_BySetUserInput) {
         nsCOMPtr<Element> element = do_QueryInterface(textControlElement);
         MOZ_ASSERT(element);
-        RefPtr<TextEditor> textEditor;
-        DebugOnly<nsresult> rvIgnored =
-            nsContentUtils::DispatchInputEvent(element, textEditor);
+        RefPtr<TextEditor> textEditor;  // See bug 1506439
+        DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
+            element, EditorInputType::eInsertReplacementText, textEditor);
         NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                              "Failed to dispatch input event");
       }
     } else {
       // Even if our value is not actually changing, apparently we need to mark
       // our SelectionProperties dirty to make accessibility tests happy.
       // Probably because they depend on the SetSelectionRange() call we make on
       // our frame in RestoreSelectionState, but I have no idea why they do.
--- a/dom/html/test/forms/test_MozEditableElement_setUserInput.html
+++ b/dom/html/test/forms/test_MozEditableElement_setUserInput.html
@@ -157,16 +157,18 @@ SimpleTest.waitForFocus(() => {
     if (inputEvents.length > 0) {
       if (SpecialPowers.wrap(target).isInputEventTarget) {
         if (test.type === "number" || test.type === "time") {
           todo(inputEvents[0] instanceof InputEvent,
                `"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
         } else {
           ok(inputEvents[0] instanceof InputEvent,
              `"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
+          is(inputEvents[0].inputType, "insertReplacementText",
+             `inputType should be "insertReplacementText" when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
         }
       } 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,
@@ -213,16 +215,18 @@ SimpleTest.waitForFocus(() => {
     if (inputEvents.length > 0) {
       if (SpecialPowers.wrap(target).isInputEventTarget) {
         if (test.type === "number" || test.type === "time") {
           todo(inputEvents[0] instanceof InputEvent,
                `"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
         } else {
           ok(inputEvents[0] instanceof InputEvent,
              `"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
+          is(inputEvents[0].inputType, "insertReplacementText",
+             `inputType should be "insertReplacementText" when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
         }
       } 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
@@ -35,25 +35,28 @@ https://bugzilla.mozilla.org/show_bug.cg
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
   /** Test for input event. This is highly based on test_change_event.html **/
 
   const isDesktop = !/Mobile|Tablet/.test(navigator.userAgent);
 
+  let expectedInputType = "";
   function checkIfInputIsInputEvent(aEvent, aToDo, aDescription) {
     if (aToDo) {
       // Probably, key operation should fire "input" event with InputEvent interface.
       // See https://github.com/w3c/input-events/issues/88
       todo(aEvent instanceof InputEvent,
          `"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.cancelable, false,
        `"input" event should be never cancelable ${aDescription}`);
     is(aEvent.bubbles, true,
        `"input" event should always bubble ${aDescription}`);
   }
 
   function checkIfInputIsEvent(aEvent, aDescription) {
@@ -123,69 +126,79 @@ https://bugzilla.mozilla.org/show_bug.cg
     setTimeout(testUserInput2, 0);
   }
 
   function testUserInput2() {
     // Some generic checks for types that support the input event.
     for (var i = 0; i < textTypes.length; ++i) {
       input = document.getElementById("input_" + textTypes[i]);
       input.focus();
+      expectedInputType = "insertLineBreak";
       synthesizeKey("KEY_Enter");
       is(textInput[i], 0, "input event shouldn't be dispatched on " + textTypes[i] + " input element");
 
+      expectedInputType = "insertText";
       sendString("m");
       is(textInput[i], 1, textTypes[i] + " input element should have dispatched input event.");
+      expectedInputType = "insertLineBreak";
       synthesizeKey("KEY_Enter");
       is(textInput[i], 1, "input event shouldn't be dispatched on " + textTypes[i] + " input element");
 
+      expectedInputType = "deleteContentBackward";
       synthesizeKey("KEY_Backspace");
       is(textInput[i], 2, textTypes[i] + " input element should have dispatched input event.");
     }
 
     // Some scenarios of value changing from script and from user input.
     input = document.getElementById("input_text");
     input.focus();
+    expectedInputType = "insertText";
     sendString("f");
     is(textInput[0], 3, "input event should have been dispatched");
     input.blur();
     is(textInput[0], 3, "input event should not have been dispatched");
 
     input.focus();
+    expectedInputType = "insertText";
     input.value = 'foo';
     is(textInput[0], 3, "input event should not have been dispatched");
     input.blur();
     is(textInput[0], 3, "input event should not have been dispatched");
 
     input.focus();
+    expectedInputType = "insertText";
     sendString("f");
     is(textInput[0], 4, "input event should have been dispatched");
     input.value = 'bar';
     is(textInput[0], 4, "input event should not have been dispatched");
     input.blur();
     is(textInput[0], 4, "input event should not have been dispatched");
 
     // Same for textarea.
     var textarea = document.getElementById("textarea");
     textarea.focus();
+    expectedInputType = "insertText";
     sendString("f");
     is(textareaInput, 1, "input event should have been dispatched");
     textarea.blur();
     is(textareaInput, 1, "input event should not have been dispatched");
 
     textarea.focus();
     textarea.value = 'foo';
     is(textareaInput, 1, "input event should not have been dispatched");
     textarea.blur();
     is(textareaInput, 1, "input event should not have been dispatched");
 
     textarea.focus();
+    expectedInputType = "insertText";
     sendString("f");
     is(textareaInput, 2, "input event should have been dispatched");
     textarea.value = 'bar';
     is(textareaInput, 2, "input event should not have been dispatched");
+    expectedInputType = "deleteContentBackward";
     synthesizeKey("KEY_Backspace");
     is(textareaInput, 3, "input event should have been dispatched");
     textarea.blur();
     is(textareaInput, 3, "input event should not have been dispatched");
 
     // Non-text input tests:
     for (var i = 0; i < NonTextTypes.length; ++i) {
       // Button, submit, image and reset input type tests.
@@ -256,16 +269,19 @@ https://bugzilla.mozilla.org/show_bug.cg
     // Tests for type='number'.
     // We only test key events here since input events for mouse event changes
     // are tested in test_input_number_mouse_events.html
     var number = document.getElementById("input_number");
 
     if (isDesktop) { // up/down arrow keys not supported on android/b2g
       number.value = "";
       number.focus();
+      // <input type="number">'s inputType value hasn't been decided, see
+      // https://github.com/w3c/input-events/issues/88
+      expectedInputType = "";
       synthesizeKey("KEY_ArrowUp");
       is(numberInput, 1, "input event should be dispatched for up/down arrow key keypress");
       is(number.value, "1", "sanity check value of number control after keypress");
 
       synthesizeKey("KEY_ArrowDown", {repeat: 3});
       is(numberInput, 4, "input event should be dispatched for each up/down arrow key keypress event, even when rapidly repeated");
       is(number.value, "-2", "sanity check value of number control after multiple keydown events");
 
--- a/dom/tests/mochitest/general/test_clipboard_events.html
+++ b/dom/tests/mochitest/general/test_clipboard_events.html
@@ -188,127 +188,162 @@ add_task(async function test_input_oncop
   // Setup an oncopy event handler, fire copy.  Ensure that the event
   // handler was called, and the clipboard contents have been set to 'PUT TE',
   // which is the part that is selected below.
   selectContentInput();
   contentInput.focus();
   contentInput.setSelectionRange(2, 8);
 
   var oncopy_fired = false;
+  var oninput_fired = false;
   contentInput.oncopy = function() { oncopy_fired = true; };
+  contentInput.oninput = function () { oninput_fired = true; };
   try {
     await putOnClipboard("PUT TE", () => {
       synthesizeKey("c", {accelKey: 1});
     }, "copy on plaintext editor set clipboard correctly");
     ok(oncopy_fired, "copy event firing on plaintext editor");
+    ok(!oninput_fired, "input event shouldn't be fired on plaintext editor by copy");
   } finally {
     contentInput.oncopy = null;
+    contentInput.oninput = null;
   }
 });
 
 add_task(async function test_input_oncut() {
   await reset();
 
   // 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 = "";
   contentInput.oncut = function() { oncut_fired = true; };
+  contentInput.oninput = function (aEvent) {
+    oninput_count++;
+    inputType = aEvent.inputType;
+  };
   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(contentInput.value, "",
       "cut on plaintext editor emptied editor");
   } finally {
     contentInput.oncut = null;
+    contentInput.oninput = null;
   }
 });
 
 add_task(async function test_input_onpaste() {
   await reset();
 
   // 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 = "";
   contentInput.onpaste = function() { onpaste_fired = true; };
+  contentInput.oninput = function(aEvent) {
+    oninput_count++;
+    inputType = aEvent.inputType;
+  };
+
   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(contentInput.value, clipboardInitialValue,
       "paste on plaintext editor did modify editor value");
   } finally {
     contentInput.onpaste = null;
+    contentInput.oninput = null;
   }
 });
 
 add_task(async function test_input_oncopy_abort() {
   await reset();
 
   // Setup an oncopy event handler, fire copy.  Ensure that the event
   // handler was called, and that the clipboard value did NOT change.
   selectContentInput();
   var oncopy_fired = false;
   contentInput.oncopy = function() { oncopy_fired = true; return false; };
+  contentInput.oninput = function() {
+    ok(false, "input event shouldn't be fired by copy but canceled");
+  };
   try {
     await wontPutOnClipboard(clipboardInitialValue, () => {
       synthesizeKey("c", {accelKey: 1});
     }, "aborted copy on plaintext editor did not modify clipboard");
     ok(oncopy_fired, "copy event (to-be-cancelled) firing on plaintext editor");
   } finally {
     contentInput.oncopy = null;
+    contentInput.oninput = null;
   }
 });
 
 add_task(async function test_input_oncut_abort() {
   await reset();
 
   // 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;
   contentInput.oncut = function() { oncut_fired = true; return false; };
+  contentInput.oninput = function() {
+    ok(false, "input event shouldn't be fired by cut but canceled");
+  };
   try {
     await wontPutOnClipboard(clipboardInitialValue, () => {
       synthesizeKey("x", {accelKey: 1});
     }, "aborted cut on plaintext editor did not modify clipboard");
     ok(oncut_fired, "cut event (to-be-cancelled) firing on plaintext editor");
     is(contentInput.value, "INPUT TEXT",
       "aborted cut on plaintext editor did not modify editor contents");
   } finally {
     contentInput.oncut = null;
+    contentInput.oninput = null;
   }
 });
 
 add_task(async function test_input_onpaste_abort() {
   await reset();
 
   // 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;
   contentInput.onpaste = function() { onpaste_fired = true; return false; };
+  contentInput.oninput = function() {
+    ok(false, "input event shouldn't be fired by paste but canceled");
+  };
   try {
     synthesizeKey("v", {accelKey: 1});
     ok(onpaste_fired,
       "paste event (to-be-cancelled) firing on plaintext editor");
     is(getClipboardText(), clipboardInitialValue,
       "aborted paste on plaintext editor did not modify clipboard");
     is(contentInput.value, "INPUT TEXT",
       "aborted paste on plaintext editor did not modify modified editor value");
   } finally {
     contentInput.onpaste = null;
+    contentInput.oninput = null;
   }
 });
 
 add_task(async function test_input_cut_dataTransfer() {
   await reset();
 
   // Cut using event.dataTransfer. The event is not cancelled so the default
   // cut should occur
--- a/dom/webidl/InputEvent.webidl
+++ b/dom/webidl/InputEvent.webidl
@@ -3,14 +3,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
 [Constructor(DOMString type, optional InputEventInit eventInitDict)]
 interface InputEvent : UIEvent
 {
   readonly attribute boolean       isComposing;
+
+  [Pref="dom.inputevent.inputtype.enabled"]
+  readonly attribute DOMString inputType;
 };
 
 dictionary InputEventInit : UIEventInit
 {
   boolean isComposing = false;
+  DOMString inputType = "";
 };
--- a/editor/libeditor/EditAction.h
+++ b/editor/libeditor/EditAction.h
@@ -1,16 +1,19 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_EditAction_h
 #define mozilla_EditAction_h
 
+#include "mozilla/EventForwards.h"
+#include "mozilla/StaticPrefs.h"
+
 namespace mozilla {
 
 /**
  * EditAction indicates which operation or command causes running the methods
  * of editors.
  */
 enum class EditAction {
   // eNone indicates no edit action is being handled.
@@ -59,16 +62,20 @@ enum class EditAction {
   // This may be set even when Selection is not collapsed.
   eDeleteToBeginningOfSoftLine,
 
   // eDeleteToEndOfSoftLine indicates to remove characters between caret and
   // next visual line break.
   // This may be set even when Selection is not collapsed.
   eDeleteToEndOfSoftLine,
 
+  // eDeleteByDrag indicates to remove selection by dragging the content
+  // to different place.
+  eDeleteByDrag,
+
   // eStartComposition indicates that user starts composition.
   eStartComposition,
 
   // eUpdateComposition indicates that user updates composition with
   // new non-empty composition string and IME selections.
   eUpdateComposition,
 
   // eCommitComposition indicates that user commits composition.
@@ -458,15 +465,133 @@ enum class EditSubAction : int32_t {
   // z-index value.
   eDecreaseZIndex,
   eIncreaseZIndex,
 
   // eCreateBogusNode indicates to create a bogus <br> node.
   eCreateBogusNode,
 };
 
+inline EditorInputType ToInputType(EditAction aEditAction) {
+  switch (aEditAction) {
+    case EditAction::eInsertText:
+      return EditorInputType::eInsertText;
+    case EditAction::eReplaceText:
+      return EditorInputType::eInsertReplacementText;
+    case EditAction::eInsertLineBreak:
+      return EditorInputType::eInsertLineBreak;
+    case EditAction::eInsertParagraphSeparator:
+      return EditorInputType::eInsertParagraph;
+    case EditAction::eInsertOrderedListElement:
+    case EditAction::eRemoveOrderedListElement:
+      return EditorInputType::eInsertOrderedList;
+    case EditAction::eInsertUnorderedListElement:
+    case EditAction::eRemoveUnorderedListElement:
+      return EditorInputType::eInsertUnorderedList;
+    case EditAction::eInsertHorizontalRuleElement:
+      return EditorInputType::eInsertHorizontalRule;
+    case EditAction::eDrop:
+      return EditorInputType::eInsertFromDrop;
+    case EditAction::ePaste:
+      return EditorInputType::eInsertFromPaste;
+    case EditAction::eUpdateComposition:
+      return EditorInputType::eInsertCompositionText;
+    case EditAction::eCommitComposition:
+      if (StaticPrefs::dom_input_events_conform_to_level_1()) {
+        return EditorInputType::eInsertCompositionText;
+      }
+      return EditorInputType::eInsertFromComposition;
+    case EditAction::eCancelComposition:
+      if (StaticPrefs::dom_input_events_conform_to_level_1()) {
+        return EditorInputType::eInsertCompositionText;
+      }
+      return EditorInputType::eDeleteCompositionText;
+    case EditAction::eDeleteByComposition:
+      if (StaticPrefs::dom_input_events_conform_to_level_1()) {
+        // XXX Or EditorInputType::eDeleteContent?  I don't know which IME may
+        //     causes this situation.
+        return EditorInputType::eInsertCompositionText;
+      }
+      return EditorInputType::eDeleteByComposition;
+    case EditAction::eInsertLinkElement:
+      return EditorInputType::eInsertLink;
+    case EditAction::eDeleteWordBackward:
+      return EditorInputType::eDeleteWordBackward;
+    case EditAction::eDeleteWordForward:
+      return EditorInputType::eDeleteWordForward;
+    case EditAction::eDeleteToBeginningOfSoftLine:
+      return EditorInputType::eDeleteSoftLineBackward;
+    case EditAction::eDeleteToEndOfSoftLine:
+      return EditorInputType::eDeleteSoftLineForward;
+    case EditAction::eDeleteByDrag:
+      return EditorInputType::eDeleteByDrag;
+    case EditAction::eCut:
+      return EditorInputType::eDeleteByCut;
+    case EditAction::eDeleteSelection:
+    case EditAction::eRemoveTableRowElement:
+    case EditAction::eRemoveTableColumn:
+    case EditAction::eRemoveTableElement:
+    case EditAction::eDeleteTableCellContents:
+    case EditAction::eRemoveTableCellElement:
+      return EditorInputType::eDeleteContent;
+    case EditAction::eDeleteBackward:
+      return EditorInputType::eDeleteContentBackward;
+    case EditAction::eDeleteForward:
+      return EditorInputType::eDeleteContentForward;
+    case EditAction::eUndo:
+      return EditorInputType::eHistoryUndo;
+    case EditAction::eRedo:
+      return EditorInputType::eHistoryRedo;
+    case EditAction::eSetFontWeightProperty:
+    case EditAction::eRemoveFontWeightProperty:
+      return EditorInputType::eFormatBold;
+    case EditAction::eSetTextStyleProperty:
+    case EditAction::eRemoveTextStyleProperty:
+      return EditorInputType::eFormatItalic;
+    case EditAction::eSetTextDecorationPropertyUnderline:
+    case EditAction::eRemoveTextDecorationPropertyUnderline:
+      return EditorInputType::eFormatUnderline;
+    case EditAction::eSetTextDecorationPropertyLineThrough:
+    case EditAction::eRemoveTextDecorationPropertyLineThrough:
+      return EditorInputType::eFormatStrikeThrough;
+    case EditAction::eSetVerticalAlignPropertySuper:
+    case EditAction::eRemoveVerticalAlignPropertySuper:
+      return EditorInputType::eFormatSuperscript;
+    case EditAction::eSetVerticalAlignPropertySub:
+    case EditAction::eRemoveVerticalAlignPropertySub:
+      return EditorInputType::eFormatSubscript;
+    case EditAction::eJustify:
+      return EditorInputType::eFormatJustifyFull;
+    case EditAction::eAlignCenter:
+      return EditorInputType::eFormatJustifyCenter;
+    case EditAction::eAlignRight:
+      return EditorInputType::eFormatJustifyRight;
+    case EditAction::eAlignLeft:
+      return EditorInputType::eFormatJustifyLeft;
+    case EditAction::eIndent:
+      return EditorInputType::eFormatIndent;
+    case EditAction::eOutdent:
+      return EditorInputType::eFormatOutdent;
+    case EditAction::eRemoveAllInlineStyleProperties:
+      return EditorInputType::eFormatRemove;
+    case EditAction::eSetTextDirection:
+      return EditorInputType::eFormatSetBlockTextDirection;
+    case EditAction::eSetBackgroundColorPropertyInline:
+    case EditAction::eRemoveBackgroundColorPropertyInline:
+      return EditorInputType::eFormatBackColor;
+    case EditAction::eSetColorProperty:
+    case EditAction::eRemoveColorProperty:
+      return EditorInputType::eFormatFontColor;
+    case EditAction::eSetFontFamilyProperty:
+    case EditAction::eRemoveFontFamilyProperty:
+      return EditorInputType::eFormatFontName;
+    default:
+      return EditorInputType::eUnknown;
+  }
+}
+
 }  // namespace mozilla
 
 inline bool operator!(const mozilla::EditSubAction& aEditSubAction) {
   return aEditSubAction == mozilla::EditSubAction::eNone;
 }
 
 #endif  // #ifdef mozilla_EditAction_h
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -2012,24 +2012,34 @@ void EditorBase::NotifyEditorObservers(
       }
       break;
     default:
       MOZ_CRASH("Handle all notifications here");
       break;
   }
 }
 
-void EditorBase::FireInputEvent() {
+void EditorBase::FireInputEvent(EditAction aEditAction) {
+  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
+  //       not omit input events.
+
   RefPtr<Element> targetElement = GetInputEventTargetElement();
   if (NS_WARN_IF(!targetElement)) {
     return;
   }
   RefPtr<TextEditor> textEditor = AsTextEditor();
-  DebugOnly<nsresult> rvIgnored =
-      nsContentUtils::DispatchInputEvent(targetElement, textEditor);
+  DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
+      targetElement, ToInputType(aEditAction), textEditor);
   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);
 
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -1766,18 +1766,24 @@ class EditorBase : public nsIEditor,
    * SelectAllInternal() should be used instead of SelectAll() in editor
    * because SelectAll() creates AutoEditActionSetter but we should avoid
    * to create it as far as possible.
    */
   virtual nsresult SelectAllInternal();
 
   nsresult DetermineCurrentDirection();
 
+  /**
+   * FireInputEvent() dispatches an "input" event synchronously or
+   * asynchronously if it's not safe to dispatch.
+   */
   MOZ_CAN_RUN_SCRIPT
-  void FireInputEvent();
+  void FireInputEvent() { FireInputEvent(GetEditAction()); }
+  MOZ_CAN_RUN_SCRIPT
+  void FireInputEvent(EditAction aEditAction);
 
   /**
    * Called after a transaction is done successfully.
    */
   void DoAfterDoTransaction(nsITransaction* aTxn);
 
   /**
    * Called after a transaction is undone successfully.
--- a/editor/libeditor/TextEditorDataTransfer.cpp
+++ b/editor/libeditor/TextEditorDataTransfer.cpp
@@ -301,17 +301,17 @@ 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();
+      FireInputEvent(EditAction::eDeleteByDrag);
       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|?
   }
--- a/editor/libeditor/tests/test_abs_positioner_positioning_elements.html
+++ b/editor/libeditor/tests/test_abs_positioner_positioning_elements.html
@@ -79,16 +79,18 @@ SimpleTest.waitForFocus(async function()
           return;
         }
         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");
       }
 
       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
@@ -8,38 +8,40 @@
   <link rel="stylesheet" type="text/css"
           href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <div id="display">
   <iframe id="editor1" srcdoc="<html><body contenteditable id='eventTarget'></body></html>"></iframe>
   <iframe id="editor2" srcdoc="<html contenteditable id='eventTarget'><body></body></html>"></iframe>
   <iframe id="editor3" srcdoc="<html><body><div contenteditable id='eventTarget'></div></body></html>"></iframe>
-  <iframe id="editor4" srcdoc="<html contenteditable id='eventTarget'><body><div contenteditable id='editTarget'></div></body></html>"></iframe>
+  <iframe id="editor4" srcdoc="<html contenteditable id='eventTarget'><body><div contenteditable></div></body></html>"></iframe>
   <iframe id="editor5" srcdoc="<html><body id='eventTarget'></body><script>document.designMode='on';</script></html>"></iframe>
 </div>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 </pre>
 
 <script class="testbody" type="application/javascript">
 
 SimpleTest.waitForExplicitFinish();
 SimpleTest.waitForFocus(runTests, window);
 
+const kIsWin = navigator.platform.indexOf("Win") == 0;
 const kIsMac = navigator.platform.indexOf("Mac") == 0;
 
 function runTests() {
   function doTests(aDocument, aWindow, aDescription) {
     aDescription += ": ";
     aWindow.focus();
 
     var body = aDocument.body;
+    var selection = aWindow.getSelection();
 
     var eventTarget = aDocument.getElementById("eventTarget");
     // The event target must be focusable because it's the editing host.
     eventTarget.focus();
 
     var editTarget = aDocument.getElementById("editTarget");
     if (!editTarget) {
       editTarget = eventTarget;
@@ -87,77 +89,257 @@ function runTests() {
 
     aWindow.addEventListener("input", handler, true);
 
     inputEvent = null;
     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"');
 
     inputEvent = null;
-    synthesizeKey("VK_BACK_SPACE", { }, aWindow);
+    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');
 
     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"');
 
     inputEvent = null;
-    synthesizeKey("VK_RETURN", { }, aWindow);
+    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"');
 
     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"');
 
     inputEvent = null;
-    synthesizeKey("VK_RETURN", { }, aWindow);
+    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');
 
     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");
 
     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 " "');
 
     inputEvent = null;
-    synthesizeKey("VK_DELETE", { }, aWindow);
+    synthesizeKey("KEY_Delete", { }, aWindow);
     ok(!inputEvent, aDescription + "input event was fired by Delete key at the end");
 
     inputEvent = null;
-    synthesizeKey("VK_LEFT", { }, aWindow);
+    synthesizeKey("KEY_ArrowLeft", { }, aWindow);
     ok(!inputEvent, aDescription + "input event was fired by Left key");
 
     inputEvent = null;
-    synthesizeKey("VK_DELETE", { }, aWindow);
+    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');
 
     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"');
 
     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"');
+
+    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"');
+
+    // 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');
+
+    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');
+
+    // 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');
+
+    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');
+
+    // 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,
+       aDescription + "input event should be fired by deleting to previous word boundary with non-collapsed selection");
+    ok(inputEvent.isTrusted,
+       aDescription + "input event should be trusted when deleting to previous word boundary with non-collapsed selection");
+    if (kIsWin) {
+      // Only on Windows, we collapse selection to start before handling this command.
+      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');
+    }
+
+    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");
+    ok(inputEvent.isTrusted,
+       aDescription + "input event should be trusted when deleting to next word boundary with non-collapsed selection");
+    if (kIsWin) {
+      // Only on Windows, we collapse selection to start before handling this command.
+      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');
+    }
+
+    // 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');
+
+    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');
+
+    // 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,
+       aDescription + "input event should be fired by deleting to previous visual line boundary with non-collapsed selection");
+    ok(inputEvent.isTrusted,
+       aDescription + "input event should be trusted when deleting to previous visual line boundary with non-collapsed selection");
+    if (kIsWin) {
+      // Only on Windows, we collapse selection to start before handling this command.
+      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');
+    }
+
+    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");
+    ok(inputEvent.isTrusted,
+       aDescription + "input event should be trusted when deleting to next visual line boundary with non-collapsed selection");
+    if (kIsWin) {
+      // Only on Windows, we collapse selection to start before handling this command.
+      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');
+    }
 
     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
@@ -61,29 +61,35 @@ function runTests() {
 
     aElement.addEventListener("input", handler, true);
 
     inputEvent = null;
     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"');
 
     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');
 
     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"');
     }
 
     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;
@@ -91,44 +97,75 @@ function runTests() {
     is(aElement.value, "", aDescription + "value wasn't set (empty)");
     ok(!inputEvent, aDescription + "input event was fired by setting empty value");
 
     inputEvent = null;
     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 " "');
 
     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");
     is(aElement.value, " ", aDescription + "Left key removed the value");
     ok(!inputEvent, aDescription + "input event was fired by Left key");
 
     inputEvent = null;
     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');
 
     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"');
 
     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"');
+
+    // 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');
+
+    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');
 
     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
@@ -24,16 +24,17 @@ SimpleTest.waitForExplicitFinish();
 var shouldClear = false;
 window.addEventListener("dragstart", function(event) { if (shouldClear) event.dataTransfer.clearData(); }, true);
 
 function checkInputEvent(aEvent, aExpectedTarget, 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}`);
 }
 
 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>' };
 
--- a/editor/libeditor/tests/test_middle_click_paste.html
+++ b/editor/libeditor/tests/test_middle_click_paste.html
@@ -78,16 +78,18 @@ async function copyHTMLContent(aInnerHTM
 
 function checkInputEvent(aEvent, 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}`);
 }
 
 async function doTextareaTests(aTextarea) {
   let inputEvents = [];
   function onInput(aEvent) {
     inputEvents.push(aEvent);
   }
   aTextarea.addEventListener("input", onInput);
--- a/editor/libeditor/tests/test_nsIEditorMailSupport_insertAsCitedQuotation.html
+++ b/editor/libeditor/tests/test_nsIEditorMailSupport_insertAsCitedQuotation.html
@@ -46,16 +46,18 @@ SimpleTest.waitForFocus(function() {
   is(inputEvents.length, 1,
      'One "input" event should be fired on the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor');
   ok(inputEvents[0] instanceof InputEvent,
      '"input" event should be dispatched with InputEvent interface after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor');
   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');
 
   // 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);
@@ -71,16 +73,18 @@ SimpleTest.waitForFocus(function() {
   is(inputEvents.length, 1,
      'One "input" event should be fired on the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)');
   ok(inputEvents[0] instanceof InputEvent,
      '"input" event should be dispatched with InputEvent interface after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)');
   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)");
 
   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)");
@@ -93,16 +97,18 @@ SimpleTest.waitForFocus(function() {
   is(inputEvents.length, 1,
      'One "input" event should be fired on the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)');
   ok(inputEvents[0] instanceof InputEvent,
      '"input" event should be dispatched with InputEvent interface after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)');
   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)");
 
   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
@@ -20,23 +20,24 @@ SimpleTest.waitForFocus(function() {
   let selection = window.getSelection();
   let description, condition;
   let inputEvents = [];
   function onInput(aEvent) {
     inputEvents.push(aEvent);
   }
   editor.addEventListener("input", onInput);
 
-  function checkInputEvent(aEvent, aDescription) {
+  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}`);
   }
 
   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) {
@@ -65,178 +66,178 @@ SimpleTest.waitForFocus(function() {
     inputEvents = [];
     prepare(editor.firstChild.firstChild.nextSibling);
     getHTMLEditor().removeInlineProperty("b", "");
     is(editor.innerHTML, "<p>test: here is bolden text</p>",
       description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should remove the <b> element');
     is(inputEvents.length, 1,
       description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
     if (inputEvents.length > 0) {
-      checkInputEvent(inputEvents[0], description);
+      checkInputEvent(inputEvents[0], "formatBold", description);
     }
   }
 
   description = "When there is a <b> element which has style attribute and ";
   for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
     editor.innerHTML = '<p>test: <b style="font-style: italic">here</b> is bolden text</p>';
     editor.focus();
     inputEvents = [];
     prepare(editor.firstChild.firstChild.nextSibling);
     getHTMLEditor().removeInlineProperty("b", "");
     is(editor.innerHTML, '<p>test: <span style="font-style: italic">here</span> is bolden text</p>',
       description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should replace the <b> element with <span> element to keep the style');
     is(inputEvents.length, 1,
       description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
     if (inputEvents.length > 0) {
-      checkInputEvent(inputEvents[0], description);
+      checkInputEvent(inputEvents[0], "formatBold", description);
     }
   }
 
   description = "When there is a <b> element which has class attribute and ";
   for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
     editor.innerHTML = '<p>test: <b class="foo">here</b> is bolden text</p>';
     editor.focus();
     inputEvents = [];
     prepare(editor.firstChild.firstChild.nextSibling);
     getHTMLEditor().removeInlineProperty("b", "");
     is(editor.innerHTML, '<p>test: <span class="foo">here</span> is bolden text</p>',
       description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should replace the <b> element with <span> element to keep the class');
     is(inputEvents.length, 1,
       description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
     if (inputEvents.length > 0) {
-      checkInputEvent(inputEvents[0], description);
+      checkInputEvent(inputEvents[0], "formatBold", description);
     }
   }
 
   description = "When there is a <b> element which has an <i> element and ";
   for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
     editor.innerHTML = "<p>test: <b><i>here</i></b> is bolden and italic text</p>";
     editor.focus();
     inputEvents = [];
     prepare(editor.firstChild.firstChild.nextSibling);
     getHTMLEditor().removeInlineProperty("b", "");
     is(editor.innerHTML, "<p>test: <i>here</i> is bolden and italic text</p>",
       description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should remove only the <b> element');
     is(inputEvents.length, 1,
       description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
     if (inputEvents.length > 0) {
-      checkInputEvent(inputEvents[0], description);
+      checkInputEvent(inputEvents[0], "formatBold", description);
     }
   }
 
   description = "When there is a <b> element which has an <i> element and ";
   for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
     editor.innerHTML = "<p>test: <b><i>here</i></b> is bolden and italic text</p>";
     editor.focus();
     inputEvents = [];
     prepare(editor.firstChild.firstChild.nextSibling);
     getHTMLEditor().removeInlineProperty("i", "");
     is(editor.innerHTML, "<p>test: <b>here</b> is bolden and italic text</p>",
       description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should remove only the <i> element');
     is(inputEvents.length, 1,
       description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should cause an "input" event');
     if (inputEvents.length > 0) {
-      checkInputEvent(inputEvents[0], description);
+      checkInputEvent(inputEvents[0], "formatItalic", description);
     }
   }
 
   description = "When there is an <i> element in a <b> element and ";
   for (let prepare of [selectNode, selectAllChildren, selectChildContents]) {
     editor.innerHTML = "<p>test: <b><i>here</i></b> is bolden and italic text</p>";
     editor.focus();
     inputEvents = [];
     prepare(editor.firstChild.firstChild.nextSibling.firstChild);
     getHTMLEditor().removeInlineProperty("b", "");
     is(editor.innerHTML, "<p>test: <i>here</i> is bolden and italic text</p>",
       description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should remove only the <b> element');
     is(inputEvents.length, 1,
       description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
     if (inputEvents.length > 0) {
-      checkInputEvent(inputEvents[0], description);
+      checkInputEvent(inputEvents[0], "formatBold", description);
     }
   }
 
   description = "When there is an <i> element in a <b> element and ";
   for (let prepare of [selectNode, selectAllChildren, selectChildContents]) {
     editor.innerHTML = "<p>test: <b><i>here</i></b> is bolden and italic text</p>";
     editor.focus();
     inputEvents = [];
     prepare(editor.firstChild.firstChild.nextSibling.firstChild);
     getHTMLEditor().removeInlineProperty("i", "");
     is(editor.innerHTML, "<p>test: <b>here</b> is bolden and italic text</p>",
       description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should remove only the <i> element');
     is(inputEvents.length, 1,
       description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should cause an "input" event');
     if (inputEvents.length > 0) {
-      checkInputEvent(inputEvents[0], description);
+      checkInputEvent(inputEvents[0], "formatItalic", description);
     }
   }
 
   description = "When there is an <i> element between text nodes in a <b> element and ";
   for (let prepare of [selectNode, selectAllChildren, selectChildContents]) {
     editor.innerHTML = "<p>test: <b>h<i>e</i>re</b> is bolden and italic text</p>";
     editor.focus();
     inputEvents = [];
     prepare(editor.firstChild.firstChild.nextSibling.firstChild.nextSibling);
     getHTMLEditor().removeInlineProperty("i", "");
     is(editor.innerHTML, "<p>test: <b>here</b> is bolden and italic text</p>",
       description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should remove only the <i> element');
     is(inputEvents.length, 1,
       description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should cause an "input" event');
     if (inputEvents.length > 0) {
-      checkInputEvent(inputEvents[0], description);
+      checkInputEvent(inputEvents[0], "formatItalic", description);
     }
   }
 
   description = "When there is an <i> element between text nodes in a <b> element and ";
   for (let prepare of [selectNode, selectAllChildren, selectChildContents]) {
     editor.innerHTML = "<p>test: <b>h<i>e</i>re</b> is bolden and italic text</p>";
     editor.focus();
     inputEvents = [];
     prepare(editor.firstChild.firstChild.nextSibling.firstChild.nextSibling);
     getHTMLEditor().removeInlineProperty("b", "");
     is(editor.innerHTML, "<p>test: <b>h</b><i>e</i><b>re</b> is bolden and italic text</p>",
       description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should split the <b> element');
     is(inputEvents.length, 1,
       description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
     if (inputEvents.length > 0) {
-      checkInputEvent(inputEvents[0], description);
+      checkInputEvent(inputEvents[0], "formatBold", description);
     }
   }
 
   description = "When there is an <a> element whose href attribute is not empty and ";
   for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
     editor.innerHTML = '<p>test: <a href="about:blank">here</a> is a link</p>';
     editor.focus();
     inputEvents = [];
     prepare(editor.firstChild.firstChild.nextSibling);
     getHTMLEditor().removeInlineProperty("href", "");
     is(editor.innerHTML, "<p>test: here is a link</p>",
       description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should remove the <a> element');
     is(inputEvents.length, 1,
       description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should cause an "input" event');
     if (inputEvents.length > 0) {
-      checkInputEvent(inputEvents[0], description);
+      checkInputEvent(inputEvents[0], "", description);
     }
   }
 
   // XXX In the case of "name", removeInlineProperty() does not the <a> element when name attribute is empty.
   description = "When there is an <a> element whose href attribute is empty and ";
   for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
     editor.innerHTML = '<p>test: <a href="">here</a> is a link</p>';
     editor.focus();
     inputEvents = [];
     prepare(editor.firstChild.firstChild.nextSibling);
     getHTMLEditor().removeInlineProperty("href", "");
     is(editor.innerHTML, "<p>test: here is a link</p>",
       description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should remove the <a> element');
     is(inputEvents.length, 1,
       description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should cause an "input" event');
     if (inputEvents.length > 0) {
-      checkInputEvent(inputEvents[0], description);
+      checkInputEvent(inputEvents[0], "", description);
     }
   }
 
   description = "When there is an <a> element which does not have href attribute and ";
   for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
     editor.innerHTML = "<p>test: <a>here</a> is an anchor</p>";
     editor.focus();
     inputEvents = [];
@@ -268,17 +269,17 @@ SimpleTest.waitForFocus(function() {
     inputEvents = [];
     prepare(editor.firstChild.firstChild.nextSibling);
     getHTMLEditor().removeInlineProperty("name", "");
     is(editor.innerHTML, "<p>test: here is a named anchor</p>",
       description + condition + ': nsIHTMLEditor.removeInlineProperty("name", "") should remove the <a> element');
     is(inputEvents.length, 1,
       description + condition + ': nsIHTMLEditor.removeInlineProperty("name", "") should cause an "input" event');
     if (inputEvents.length > 0) {
-      checkInputEvent(inputEvents[0], description);
+      checkInputEvent(inputEvents[0], "", description);
     }
   }
 
   // XXX In the case of "href", removeInlineProperty() removes the <a> element when href attribute is empty.
   description = "When there is an <a> element whose name attribute is empty and ";
   for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
     editor.innerHTML = '<p>test: <a name="">here</a> is a named anchor</p>';
     editor.focus();
--- a/editor/libeditor/tests/test_nsIPlaintextEditor_insertLineBreak.html
+++ b/editor/libeditor/tests/test_nsIPlaintextEditor_insertLineBreak.html
@@ -48,16 +48,18 @@ SimpleTest.waitForFocus(function() {
   is(textarea.value, "abc\ndef", "nsIPlaintextEditor.insertLineBreak() should insert \n into multi-line editor");
   is(inputEvents.length, 1, "nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on multi-line editor");
   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');
 
   // 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;
@@ -71,16 +73,18 @@ SimpleTest.waitForFocus(function() {
   is(inputEvents.length, 1,
      "nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has only text node when defaultParagraphSeparator is \"br\"");
   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');
 
   contenteditable.innerHTML = "<p>abcdef</p>";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild.firstChild, 3);
   inputEvents = [];
   contenteditable.addEventListener("input", onInput);
   getPlaintextEditor(contenteditable).insertLineBreak();
@@ -90,16 +94,18 @@ SimpleTest.waitForFocus(function() {
   is(inputEvents.length, 1,
      "nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <p> element when defaultParagraphSeparator is \"br\"");
   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');
 
   contenteditable.innerHTML = "<div>abcdef</div>";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild.firstChild, 3);
   inputEvents = [];
   contenteditable.addEventListener("input", onInput);
   getPlaintextEditor(contenteditable).insertLineBreak();
@@ -109,16 +115,18 @@ SimpleTest.waitForFocus(function() {
   is(inputEvents.length, 1,
      "nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <div> element when defaultParagraphSeparator is \"br\"");
   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');
 
   contenteditable.innerHTML = "<pre>abcdef</pre>";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild.firstChild, 3);
   inputEvents = [];
   contenteditable.addEventListener("input", onInput);
   getPlaintextEditor(contenteditable).insertLineBreak();
@@ -128,16 +136,18 @@ SimpleTest.waitForFocus(function() {
   is(inputEvents.length, 1,
      "nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <pre> element when defaultParagraphSeparator is \"br\"");
   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');
 
   document.execCommand("defaultParagraphSeparator", false, "p");
 
   contenteditable.innerHTML = "abcdef";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild, 3);
   inputEvents = [];
@@ -149,16 +159,18 @@ SimpleTest.waitForFocus(function() {
   is(inputEvents.length, 1,
      "nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has only text node when defaultParagraphSeparator is \"p\"");
   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');
 
   contenteditable.innerHTML = "<p>abcdef</p>";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild.firstChild, 3);
   inputEvents = [];
   contenteditable.addEventListener("input", onInput);
   getPlaintextEditor(contenteditable).insertLineBreak();
@@ -168,16 +180,18 @@ SimpleTest.waitForFocus(function() {
   is(inputEvents.length, 1,
      "nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <p> element when defaultParagraphSeparator is \"p\"");
   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');
 
   contenteditable.innerHTML = "<div>abcdef</div>";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild.firstChild, 3);
   inputEvents = [];
   contenteditable.addEventListener("input", onInput);
   getPlaintextEditor(contenteditable).insertLineBreak();
@@ -187,16 +201,18 @@ SimpleTest.waitForFocus(function() {
   is(inputEvents.length, 1,
      "nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <div> element when defaultParagraphSeparator is \"p\"");
   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');
 
   contenteditable.innerHTML = "<pre>abcdef</pre>";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild.firstChild, 3);
   inputEvents = [];
   contenteditable.addEventListener("input", onInput);
   getPlaintextEditor(contenteditable).insertLineBreak();
@@ -206,16 +222,18 @@ SimpleTest.waitForFocus(function() {
   is(inputEvents.length, 1,
      "nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <pre> element when defaultParagraphSeparator is \"p\"");
   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');
 
   document.execCommand("defaultParagraphSeparator", false, "div");
 
   contenteditable.innerHTML = "abcdef";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild, 3);
   inputEvents = [];
@@ -227,16 +245,18 @@ SimpleTest.waitForFocus(function() {
   is(inputEvents.length, 1,
      "nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has only text node when defaultParagraphSeparator is \"div\"");
   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');
 
   contenteditable.innerHTML = "<p>abcdef</p>";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild.firstChild, 3);
   inputEvents = [];
   contenteditable.addEventListener("input", onInput);
   getPlaintextEditor(contenteditable).insertLineBreak();
@@ -246,16 +266,18 @@ SimpleTest.waitForFocus(function() {
   is(inputEvents.length, 1,
      "nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <p> element when defaultParagraphSeparator is \"div\"");
   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');
 
   contenteditable.innerHTML = "<div>abcdef</div>";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild.firstChild, 3);
   inputEvents = [];
   contenteditable.addEventListener("input", onInput);
   getPlaintextEditor(contenteditable).insertLineBreak();
@@ -265,16 +287,18 @@ SimpleTest.waitForFocus(function() {
   is(inputEvents.length, 1,
      "nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <div> element when defaultParagraphSeparator is \"div\"");
   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');
 
   contenteditable.innerHTML = "<pre>abcdef</pre>";
   contenteditable.focus();
   contenteditable.scrollTop;
   selection.collapse(contenteditable.firstChild.firstChild, 3);
   inputEvents = [];
   contenteditable.addEventListener("input", onInput);
   getPlaintextEditor(contenteditable).insertLineBreak();
@@ -284,16 +308,18 @@ SimpleTest.waitForFocus(function() {
   is(inputEvents.length, 1,
      "nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <pre> element when defaultParagraphSeparator is \"div\"");
   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');
 
   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
@@ -21,16 +21,18 @@ SimpleTest.waitForFocus(function() {
 
   function checkInputEvent(aEvent, 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, "deleteContent",
+       `inputType should be "deleteContent" ${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
@@ -21,16 +21,18 @@ SimpleTest.waitForFocus(function() {
 
   function checkInputEvent(aEvent, 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, "deleteContent",
+       `inputType should be "deleteContent" ${aDescription}`);
   }
 
   let inputEvents = [];
   function onInput(aEvent) {
     inputEvents.push(aEvent);
   }
   editor.addEventListener("input", onInput);
 
--- a/editor/libeditor/tests/test_nsITableEditor_deleteTableColumn.html
+++ b/editor/libeditor/tests/test_nsITableEditor_deleteTableColumn.html
@@ -14,267 +14,374 @@
 
 <script class="testbody" type="application/javascript">
 
 SimpleTest.waitForExplicitFinish();
 SimpleTest.waitForFocus(function() {
   let editor = document.getElementById("content");
   let selection = document.getSelection();
 
+  function checkInputEvent(aEvent, 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, "deleteContent",
+       `inputType should be "deleteContent" ${aDescription}`);
+  }
+
+  let inputEvents = [];
+  function onInput(aEvent) {
+    inputEvents.push(aEvent);
+  }
+  editor.addEventListener("input", onInput);
+
+  inputEvents = [];
   selection.collapse(editor.firstChild, 0);
   getTableEditor().deleteTableColumn(1);
   is(editor.innerHTML, "out of table<table><tbody><tr><td>default content</td></tr></tbody></table>",
      "nsITableEditor.deleteTableColumn(1) should do nothing if selection is not in <table>");
+  is(inputEvents.length, 0,
+     'No "input" event should be fired when a call of nsITableEditor.deleteTableColumn(1) does nothing');
 
   selection.removeAllRanges();
   try {
+    inputEvents = [];
     getTableEditor().deleteTableColumn(1);
     ok(false, "getTableEditor().deleteTableColumn(1) without selection ranges should throw exception");
   } catch (e) {
     ok(true, "getTableEditor().deleteTableColumn(1) without selection ranges should throw exception");
+    is(inputEvents.length, 0,
+       'No "input" event should be fired when nsITableEditor.deleteTableColumn(1) causes exception due to no selection range');
   }
 
   // If a cell is selected and the argument is less than number of rows,
   // specified number of rows should be removed starting from the row
   // containing the selected cell.  But if the argument is same or
   // larger than actual number of rows, the <table> should be removed.
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
+  inputEvents = [];
   let range = document.createRange();
   range.selectNode(document.getElementById("select"));
   selection.addRange(range);
   getTableEditor().deleteTableColumn(1);
   is(editor.innerHTML, "<table><tbody><tr><td>cell1-2</td></tr><tr><td>cell2-2</td></tr></tbody></table>",
      "nsITableEditor.deleteTableColumn(1) should delete the first column when a cell in the first column is selected");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when a cell in the first column is selected');
+  checkInputEvent(inputEvents[0], "when a cell in the first column is selected");
 
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td>cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td id="select">cell2-2</td></tr></table>';
+  inputEvents = [];
   range = document.createRange();
   range.selectNode(document.getElementById("select"));
   selection.addRange(range);
   getTableEditor().deleteTableColumn(1);
   is(editor.innerHTML, "<table><tbody><tr><td>cell1-1</td></tr><tr><td>cell2-1</td></tr></tbody></table>",
      "nsITableEditor.deleteTableColumn(1) should delete the second column when a cell in the second column is selected");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when a cell in the second column is selected');
+  checkInputEvent(inputEvents[0], "when a cell in the second column is selected");
 
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
+  inputEvents = [];
   range = document.createRange();
   range.selectNode(document.getElementById("select"));
   selection.addRange(range);
   getTableEditor().deleteTableColumn(2);
   is(editor.innerHTML, "",
      "nsITableEditor.deleteTableColumn(2) should delete the <table> since there is only 2 columns");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when a cell in first column is selected and argument is same as number of rows');
+  checkInputEvent(inputEvents[0], "when a cell in first column is selected and argument is same as number of rows");
 
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
+  inputEvents = [];
   range = document.createRange();
   range.selectNode(document.getElementById("select"));
   selection.addRange(range);
   getTableEditor().deleteTableColumn(3);
   is(editor.innerHTML, "",
      "nsITableEditor.deleteTableColumn(3) should delete the <table> when argument is larger than actual number of columns");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when argument is larger than actual number of columns');
+  checkInputEvent(inputEvents[0], "when argument is larger than actual number of columns");
 
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td>cell1-1</td><td id="select">cell1-2</td><td>cell1-3</td></tr><tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr></table>';
+  inputEvents = [];
   range = document.createRange();
   range.selectNode(document.getElementById("select"));
   selection.addRange(range);
   getTableEditor().deleteTableColumn(2);
   is(editor.innerHTML, "<table><tbody><tr><td>cell1-1</td></tr><tr><td>cell2-1</td></tr></tbody></table>",
      "nsITableEditor.deleteTableColumn(2) should delete the second column containing selected cell and next column");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when a cell in second column and argument is same as the remaining columns');
+  checkInputEvent(inputEvents[0], "when a cell in second column and argument is same as the remaining columns");
 
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td>cell1-1</td><td>cell1-2</td></tr><tr><td id="select">cell2-1</td><td>cell2-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></table>';
+  inputEvents = [];
   range = document.createRange();
   range.selectNode(document.getElementById("select"));
   selection.addRange(range);
   getTableEditor().deleteTableColumn(3);
   is(editor.innerHTML, "",
      "nsITableEditor.deleteTableColumn(3) should delete the <table> since the argument equals actual number of columns");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when a cell in first column and argument is larger than the remaining columns');
+  checkInputEvent(inputEvents[0], "when a cell in first column and argument is larger than the remaining columns");
 
   // Similar to selected a cell, when selection is in a cell, the cell should
   // treated as selected.
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
+  inputEvents = [];
   editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
   range = document.createRange();
   range.selectNode(document.getElementById("select").firstChild);
   selection.addRange(range);
   getTableEditor().deleteTableColumn(1);
   is(editor.innerHTML, "<table><tbody><tr><td>cell1-2</td></tr><tr><td>cell2-2</td></tr></tbody></table>",
      "nsITableEditor.deleteTableColumn(1) should delete the first column when a cell in the first column contains selection range");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when a cell in the first column contains selection range');
+  checkInputEvent(inputEvents[0], "when a cell in the first column contains selection range");
 
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td>cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td id="select">cell2-2</td></tr></table>';
+  inputEvents = [];
   editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
   range = document.createRange();
   range.selectNode(document.getElementById("select").firstChild);
   selection.addRange(range);
   getTableEditor().deleteTableColumn(1);
   is(editor.innerHTML, "<table><tbody><tr><td>cell1-1</td></tr><tr><td>cell2-1</td></tr></tbody></table>",
      "nsITableEditor.deleteTableColumn(1) should delete the second column when a cell in the second column contains selection range");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when a cell in the second column contains selection range');
+  checkInputEvent(inputEvents[0], "when a cell in the second column contains selection range");
 
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
+  inputEvents = [];
   editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
   range = document.createRange();
   range.selectNode(document.getElementById("select").firstChild);
   selection.addRange(range);
   getTableEditor().deleteTableColumn(2);
   is(editor.innerHTML, "",
      "nsITableEditor.deleteTableColumn(2) should delete the <table> since there is only 2 columns");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when all text in a cell in first column is selected and argument includes next row');
+  checkInputEvent(inputEvents[0], "when all text in a cell in first column is selected and argument includes next row");
 
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
+  inputEvents = [];
   editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
   range = document.createRange();
   range.selectNode(document.getElementById("select").firstChild);
   selection.addRange(range);
   getTableEditor().deleteTableColumn(3);
   is(editor.innerHTML, "",
      "nsITableEditor.deleteTableColumn(3) should delete the <table> when argument is larger than actual number of columns");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when all text in a cell in first column is selected and argument is same as number of all rows');
+  checkInputEvent(inputEvents[0], "when all text in a cell in first column is selected and argument is same as number of all rows");
 
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td>cell1-1</td><td id="select">cell1-2</td><td>cell1-3</td></tr><tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr></table>';
+  inputEvents = [];
   editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
   range = document.createRange();
   range.selectNode(document.getElementById("select").firstChild);
   selection.addRange(range);
   getTableEditor().deleteTableColumn(2);
   is(editor.innerHTML, "<table><tbody><tr><td>cell1-1</td></tr><tr><td>cell2-1</td></tr></tbody></table>",
      "nsITableEditor.deleteTableColumn(2) should delete the second column containing a cell containing selection range and next column");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when all text in a cell is selected and argument is same than renaming number of columns');
+  checkInputEvent(inputEvents[0], "when all text in a cell is selected and argument is same than renaming number of columns");
 
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td>cell1-1</td><td>cell1-2</td></tr><tr><td id="select">cell2-1</td><td>cell2-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></table>';
+  inputEvents = [];
   editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
   range = document.createRange();
   range.selectNode(document.getElementById("select").firstChild);
   selection.addRange(range);
   getTableEditor().deleteTableColumn(3);
   is(editor.innerHTML, "",
      "nsITableEditor.deleteTableColumn(3) should delete the <table> since the argument equals actual number of columns");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when all text in a cell in the first column and argument is larger than renaming number of columns');
+  checkInputEvent(inputEvents[0], "when all text in a cell in the first column and argument is larger than renaming number of columns");
 
   // The argument should be ignored when 2 or more cells are selected.
   // XXX Different from deleteTableRow(), this removes the <table> completely.
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td id="select1">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td id="select2">cell2-2</td></tr></table>';
+  inputEvents = [];
   range = document.createRange();
   range.selectNode(document.getElementById("select1"));
   selection.addRange(range);
   range = document.createRange();
   range.selectNode(document.getElementById("select2"));
   selection.addRange(range);
   getTableEditor().deleteTableColumn(1);
   is(editor.innerHTML, "",
      "nsITableEditor.deleteTableColumn(1) should delete the <table> when both columns have selected cell");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when both columns have selected cell');
+  checkInputEvent(inputEvents[0], "when both columns have selected cell");
 
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td id="select1">cell1-1</td><td>cell1-2</td></tr><tr><td id="select2">cell2-1</td><td>cell2-2</td></tr></table>';
+  inputEvents = [];
   range = document.createRange();
   range.selectNode(document.getElementById("select1"));
   selection.addRange(range);
   range = document.createRange();
   range.selectNode(document.getElementById("select2"));
   selection.addRange(range);
   getTableEditor().deleteTableColumn(2);
   is(editor.innerHTML, "",
      "nsITableEditor.deleteTableColumn(2) should delete the <table> since 2 is number of columns of the <table>");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when cells in every column are selected #2');
+  checkInputEvent(inputEvents[0], "when cells in every column are selected #2");
 
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td id="select1">cell1-1</td><td>cell1-2</td></tr><tr><td id="select2">cell2-1</td><td>cell2-2</td></tr></table>';
+  inputEvents = [];
   range = document.createRange();
   range.selectNode(document.getElementById("select1"));
   selection.addRange(range);
   range = document.createRange();
   range.selectNode(document.getElementById("select2"));
   selection.addRange(range);
   getTableEditor().deleteTableColumn(2);
   is(editor.innerHTML, "",
      "nsITableEditor.deleteTableColumn(2) should delete the <table> since 2 is number of columns of the <table>");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when 2 cells in same column are selected');
+  checkInputEvent(inputEvents[0], "when 2 cells in same column are selected");
 
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td id="select1">cell1-1</td><td id="select2">cell1-2</td><td>cell1-3</td></tr><tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr></table>';
+  inputEvents = [];
   range = document.createRange();
   range.selectNode(document.getElementById("select1"));
   selection.addRange(range);
   range = document.createRange();
   range.selectNode(document.getElementById("select2"));
   selection.addRange(range);
   getTableEditor().deleteTableColumn(1);
   is(editor.innerHTML, "<table><tbody><tr><td>cell1-3</td></tr><tr><td>cell2-3</td></tr></tbody></table>",
      "nsITableEditor.deleteTableColumn(1) should delete first 2 columns because cells in the both columns are selected");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when 2 cell elements in different columns are selected #1');
+  checkInputEvent(inputEvents[0], "when 2 cell elements in different columns are selected #1");
 
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td id="select1">cell1-1</td><td>cell1-2</td><td id="select2">cell1-3</td></tr><tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr></table>';
+  inputEvents = [];
   range = document.createRange();
   range.selectNode(document.getElementById("select1"));
   selection.addRange(range);
   range = document.createRange();
   range.selectNode(document.getElementById("select2"));
   selection.addRange(range);
   getTableEditor().deleteTableColumn(1);
   is(editor.innerHTML, "<table><tbody><tr><td>cell1-2</td></tr><tr><td>cell2-2</td></tr></tbody></table>",
      "nsITableEditor.deleteTableColumn(1) should delete the first and the last columns because cells in the both columns are selected");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when 2 cell elements in different columns are selected #2');
+  checkInputEvent(inputEvents[0], "when 2 cell elements in different columns are selected #2");
 
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td id="select" colspan="2">cell1-1</td><td>cell1-3</td></tr><tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr></table>';
+  inputEvents = [];
   range = document.createRange();
   range.selectNode(document.getElementById("select"));
   selection.addRange(range);
   getTableEditor().deleteTableColumn(1);
   is(editor.innerHTML, '<table><tbody><tr><td id="select" colspan="1"><br></td><td>cell1-3</td></tr><tr><td>cell2-2</td><td>cell2-3</td></tr></tbody></table>',
      "nsITableEditor.deleteTableColumn(1) with a selected cell is colspan=\"2\" should delete the first column and add empty cell to the second column");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when a cell is selected and its colspan is 2');
+  checkInputEvent(inputEvents[0], "when a cell is selected and its colspan is 2");
 
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td id="select" colspan="3">cell1-1</td></tr><tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr></table>';
+  inputEvents = [];
   range = document.createRange();
   range.selectNode(document.getElementById("select"));
   selection.addRange(range);
   getTableEditor().deleteTableColumn(1);
   is(editor.innerHTML, '<table><tbody><tr><td id="select" colspan="2"><br></td></tr><tr><td>cell2-2</td><td>cell2-3</td></tr></tbody></table>',
      "nsITableEditor.deleteTableColumn(1) with a selected cell is colspan=\"3\" should delete the first column and add empty cell whose colspan is 2 to the second column");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when a cell is selected and its colspan is 3');
+  checkInputEvent(inputEvents[0], "when a cell is selected and its colspan is 3");
 
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td colspan="3">cell1-1</td></tr><tr><td>cell2-1</td><td id="select">cell2-2</td><td>cell2-3</td></tr></table>';
+  inputEvents = [];
   range = document.createRange();
   range.selectNode(document.getElementById("select"));
   selection.addRange(range);
   getTableEditor().deleteTableColumn(1);
   is(editor.innerHTML, '<table><tbody><tr><td colspan="2">cell1-1</td></tr><tr><td>cell2-1</td><td>cell2-3</td></tr></tbody></table>',
      "nsITableEditor.deleteTableColumn(1) with selected cell in the second column should delete the second column and the colspan in the first row should be adjusted");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when a cell in 2nd column is only cell defined by the column #1');
+  checkInputEvent(inputEvents[0], "when a cell in 2nd column is only cell defined by the column #1");
 
   selection.removeAllRanges();
   editor.innerHTML =
     '<table><tr><td colspan="2">cell1-1</td><td>cell1-3</td></tr><tr><td>cell2-1</td><td id="select">cell2-2</td><td>cell2-3</td></tr></table>';
+  inputEvents = [];
   range = document.createRange();
   range.selectNode(document.getElementById("select"));
   selection.addRange(range);
   getTableEditor().deleteTableColumn(1);
   is(editor.innerHTML, '<table><tbody><tr><td colspan="1">cell1-1</td><td>cell1-3</td></tr><tr><td>cell2-1</td><td>cell2-3</td></tr></tbody></table>',
      "nsITableEditor.deleteTableColumn(1) with selected cell in the second column should delete the second column and the colspan should be adjusted");
+  is(inputEvents.length, 1,
+     'Only one "input" event should be fired when a cell in 2nd column is only cell defined by the column #2');
+  checkInputEvent(inputEvents[0], "when a cell in 2nd column is only cell defined by the column #2");
 
   SimpleTest.finish();
 });
 
 function getTableEditor() {
   var editingSession = SpecialPowers.wrap(window).docShell.editingSession;
   return editingSession.getEditorForWindow(window).QueryInterface(SpecialPowers.Ci.nsITableEditor);
 }
--- a/editor/libeditor/tests/test_nsITableEditor_deleteTableRow.html
+++ b/editor/libeditor/tests/test_nsITableEditor_deleteTableRow.html
@@ -21,16 +21,18 @@ SimpleTest.waitForFocus(function() {
 
   function checkInputEvent(aEvent, 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, "deleteContent",
+       `inputType should be "deleteContent" ${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
@@ -21,16 +21,18 @@ SimpleTest.waitForFocus(function() {
 
   function checkInputEvent(aEvent, 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, "",
+       `inputType should be empty string ${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
@@ -21,16 +21,18 @@ SimpleTest.waitForFocus(function() {
 
   function checkInputEvent(aEvent, 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, "",
+       `inputType should be empty string ${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
@@ -21,16 +21,18 @@ SimpleTest.waitForFocus(function() {
 
   function checkInputEvent(aEvent, 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, "",
+       `inputType should be empty string ${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
@@ -92,16 +92,18 @@ SimpleTest.waitForFocus(async function()
           return;
         }
         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`);
       }
 
       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
@@ -16,23 +16,25 @@ SimpleTest.waitForExplicitFinish();
 SimpleTest.waitForFocus(() => {
   let textarea = document.getElementById("textarea");
   let editor = SpecialPowers.wrap(textarea).editor;
 
   let inlineSpellChecker = editor.getInlineSpellChecker(true);
 
   textarea.focus();
 
-  function checkInputEvent(aEvent, aDescription) {
+  function checkInputEvent(aEvent, aInputType, aDescription) {
     ok(aEvent instanceof InputEvent,
        `"input" event should be dispatched with InputEvent interface ${aDescription}`);
     is(aEvent.cancelable, false,
        `"input" event should be never cancelable ${aDescription}`);
     is(aEvent.bubbles, true,
        `"input" event should always bubble ${aDescription}`);
+    is(aEvent.inputType, aInputType,
+       `inputType should be "${aInputType}" ${aDescription}`);
   }
 
   let inputEvents = [];
   function onInput(aEvent) {
     inputEvents.push(aEvent);
   }
 
   SpecialPowers.Cu.import(
@@ -47,33 +49,33 @@ SimpleTest.waitForFocus(() => {
       is(misspelledWord.endOffset, 7,
          "Misspelled word should end at 7");
       inputEvents = [];
       inlineSpellChecker.replaceWord(editor.rootElement.firstChild, 5, "aux");
       is(textarea.value, "abc aux abc",
          "'abx' should be replaced with 'aux'");
       is(inputEvents.length, 1,
          'Only one "input" event should be fired when replacing a word with spellchecker');
-      checkInputEvent(inputEvents[0], "when replacing a word with spellchecker");
+      checkInputEvent(inputEvents[0], "insertReplacementText", "when replacing a word with spellchecker");
 
       inputEvents = [];
       synthesizeKey("z", { accelKey: true });
       is(textarea.value, "abc abx abc",
          "'abx' should be restored by undo");
       is(inputEvents.length, 1,
          'Only one "input" event should be fired when undoing the replacing word');
-      checkInputEvent(inputEvents[0], "when undoing the replacing word");
+      checkInputEvent(inputEvents[0], "historyUndo", "when undoing the replacing word");
 
       inputEvents = [];
       synthesizeKey("z", { accelKey: true, shiftKey: true });
       is(textarea.value, "abc aux abc",
          "'aux' should be restored by redo");
       is(inputEvents.length, 1,
          'Only one "input" event should be fired when redoing the replacing word');
-      checkInputEvent(inputEvents[0], "when redoing the replacing word");
+      checkInputEvent(inputEvents[0], "historyRedo", "when redoing the replacing word");
 
       textarea.removeEventListener("input", onInput);
 
       SimpleTest.finish();
     });
   });
 });
 </script>
--- a/editor/libeditor/tests/test_undo_redo_stack_after_setting_value.html
+++ b/editor/libeditor/tests/test_undo_redo_stack_after_setting_value.html
@@ -24,55 +24,57 @@ https://bugzilla.mozilla.org/show_bug.cg
 <script class="testbody" type="application/javascript">
 SimpleTest.waitForExplicitFinish();
 SimpleTest.waitForFocus(function() {
   let editableElements = [
     document.getElementById("input"),
     document.getElementById("textarea"),
   ];
   for (let editableElement of editableElements) {
-    function checkInputEvent(aEvent, aDescription) {
+    function checkInputEvent(aEvent, aInputType, aDescription) {
       ok(aEvent instanceof InputEvent,
          `"input" event should be dispatched with InputEvent interface ${aDescription}`);
       is(aEvent.cancelable, false,
          `"input" event should be never cancelable ${aDescription}`);
       is(aEvent.bubbles, true,
          `"input" event should always bubble ${aDescription}`);
+      is(aEvent.inputType, aInputType,
+         `inputType should be "${aInputType}" ${aDescription}`);
     }
 
     let inputEvents = [];
     function onInput(aEvent) {
       inputEvents.push(aEvent);
     }
     editableElement.addEventListener("input", onInput);
 
     editableElement.focus();
 
     inputEvents = [];
     synthesizeKey("a");
     is(inputEvents.length, 1,
        `Only one "input" event should be fired when inserting "a" with key on <${editableElement.tagName.toLowerCase()}> element`);
-    checkInputEvent(inputEvents[0], `when inserting "a" with key on <${editableElement.tagName.toLowerCase()}> element`);
+    checkInputEvent(inputEvents[0], "insertText", `when inserting "a" with key on <${editableElement.tagName.toLowerCase()}> element`);
 
     inputEvents = [];
     synthesizeKey("c");
     is(inputEvents.length, 1,
        `Only one "input" event should be fired when inserting "c" with key on <${editableElement.tagName.toLowerCase()}> element`);
-    checkInputEvent(inputEvents[0], `when inserting "c" with key on <${editableElement.tagName.toLowerCase()}> element`);
+    checkInputEvent(inputEvents[0], "insertText", `when inserting "c" with key on <${editableElement.tagName.toLowerCase()}> element`);
 
     inputEvents = [];
     synthesizeKey("KEY_ArrowLeft");
     is(inputEvents.length, 0,
        `No "input" event should be fired when pressing "ArrowLeft" key on <${editableElement.tagName.toLowerCase()}> element`);
 
     inputEvents = [];
     synthesizeKey("b");
     is(inputEvents.length, 1,
        `Only one "input" event should be fired when inserting "b" with key on <${editableElement.tagName.toLowerCase()}> element`);
-    checkInputEvent(inputEvents[0], `when inserting "b" with key on <${editableElement.tagName.toLowerCase()}> element`);
+    checkInputEvent(inputEvents[0], "insertText", `when inserting "b" with key on <${editableElement.tagName.toLowerCase()}> element`);
 
     let editor = SpecialPowers.wrap(editableElement).editor;
     let transactionManager = editor.transactionManager;
     is(transactionManager.numberOfUndoItems, 2,
        editableElement.tagName + ": Initially, there should be 2 undo items");
     // Defined as nsITextControlElement::DEFAULT_UNDO_CAP
     is(transactionManager.maxTransactionCount, 1000,
        editableElement.tagName + ": Initially, transaction manager should be able to have 1,000 undo items");
@@ -86,25 +88,25 @@ SimpleTest.waitForFocus(function() {
        editableElement.tagName + ": After setting value, all undo items must be deleted");
     is(transactionManager.maxTransactionCount, 1000,
        editableElement.tagName + ": After setting value, maximum transaction count should be restored to the previous value");
 
     inputEvents = [];
     synthesizeKey("a");
     is(inputEvents.length, 1,
        `Only one "input" event should be fired when inserting "a" with key again on <${editableElement.tagName.toLowerCase()}> element`);
-    checkInputEvent(inputEvents[0], `when inserting "a" with key again on <${editableElement.tagName.toLowerCase()}> element`);
+    checkInputEvent(inputEvents[0], "insertText", `when inserting "a" with key again on <${editableElement.tagName.toLowerCase()}> element`);
 
     inputEvents = [];
     synthesizeKey("z", { accelKey: true });
     is(editableElement.value, "def",
        editableElement.tagName + ": undo should work after setting value");
     is(inputEvents.length, 1,
        `Only one "input" event should be fired when undoing on <${editableElement.tagName.toLowerCase()}> element`);
-    checkInputEvent(inputEvents[0], `when undoing on <${editableElement.tagName.toLowerCase()}> element`);
+    checkInputEvent(inputEvents[0], "historyUndo", `when undoing on <${editableElement.tagName.toLowerCase()}> element`);
 
     // Disable undo/redo.
     editor.enableUndo(0);
     is(transactionManager.maxTransactionCount, 0,
        editableElement.tagName + ": Transaction manager should not be able to have undo items");
     editableElement.value = "hij";
     is(transactionManager.maxTransactionCount, 0,
        editableElement.tagName + ": Transaction manager should not be able to have undo items after setting value");
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -206,16 +206,25 @@ VARCACHE_PREF(
 // If this is true, "keypress" event's keyCode value and charCode value always
 // become same if the event is not created/initialized by JS.
 VARCACHE_PREF(
   "dom.keyboardevent.keypress.set_keycode_and_charcode_to_same_value",
    dom_keyboardevent_keypress_set_keycode_and_charcode_to_same_value,
   bool, true
 )
 
+// Whether we conform to Input Events Level 1 or Input Events Level 2.
+// true:  conforming to Level 1
+// false: conforming to Level 2
+VARCACHE_PREF(
+  "dom.input_events.conform_to_level_1",
+   dom_input_events_conform_to_level_1,
+  bool, true
+)
+
 // NOTE: This preference is used in unit tests. If it is removed or its default
 // value changes, please update test_sharedMap_var_caches.js accordingly.
 VARCACHE_PREF(
   "dom.webcomponents.shadowdom.report_usage",
    dom_webcomponents_shadowdom_report_usage,
   bool, false
 )
 
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -228,16 +228,19 @@ pref("dom.keyboardevent.keypress.hack.di
 
 // Blacklist of domains of web apps which handle keyCode and charCode of
 // keypress events with a path only for Firefox (i.e., broken if we set
 // non-zero keyCode or charCode value to the other).  The format is exactly
 // same as "dom.keyboardevent.keypress.hack.dispatch_non_printable_keys". So,
 // check its explanation for the detail.
 pref("dom.keyboardevent.keypress.hack.use_legacy_keycode_and_charcode", "");
 
+// Whether InputEvent.inputType is enabled.
+pref("dom.inputevent.inputtype.enabled", true);
+
 // Whether the WebMIDI API is enabled
 pref("dom.webmidi.enabled", false);
 
 // Whether to enable the JavaScript start-up cache. This causes one of the first
 // execution to record the bytecode of the JavaScript function used, and save it
 // in the existing cache entry. On the following loads of the same script, the
 // bytecode would be loaded from the cache instead of being generated once more.
 pref("dom.script_loader.bytecode_cache.enabled", true);
--- a/testing/web-platform/meta/input-events/idlharness.window.js.ini
+++ b/testing/web-platform/meta/input-events/idlharness.window.js.ini
@@ -1,15 +1,9 @@
 [idlharness.window.html]
-  [InputEvent interface: attribute inputType]
-    expected: FAIL
-
-  [InputEvent interface: new InputEvent("foo") must inherit property "inputType" with the proper type]
-    expected: FAIL
-
   [InputEvent interface: new InputEvent("foo") must inherit property "dataTransfer" with the proper type]
     expected: FAIL
 
   [InputEvent interface: attribute dataTransfer]
     expected: FAIL
 
   [InputEvent interface: new InputEvent("foo") must inherit property "getTargetRanges()" with the proper type]
     expected: FAIL
--- a/testing/web-platform/meta/input-events/input-events-exec-command.html.ini
+++ b/testing/web-platform/meta/input-events/input-events-exec-command.html.ini
@@ -1,117 +1,42 @@
 [input-events-exec-command.html]
-  [Calling execCommand("insertText", false, a)]
-    expected: FAIL
-
-  [Calling execCommand("insertText", false, bc)]
-    expected: FAIL
-
-  [Calling execCommand("insertOrderedList", false, null)]
-    expected: FAIL
-
-  [Calling execCommand("insertUnorderedList", false, null)]
-    expected: FAIL
-
-  [Calling execCommand("insertLineBreak", false, null)]
-    expected: FAIL
-
-  [Calling execCommand("insertParagraph", false, null)]
-    expected: FAIL
-
-  [Calling execCommand("insertHorizontalRule", false, null)]
-    expected: FAIL
-
-  [Calling execCommand("bold", false, null)]
-    expected: FAIL
-
-  [Calling execCommand("italic", false, null)]
-    expected: FAIL
-
   [execCommand("italic") should wrap selected text with <i> element]
     expected: FAIL
 
-  [Calling execCommand("underline", false, null)]
-    expected: FAIL
-
   [execCommand("underline") should wrap selected text with <u> element]
     expected: FAIL
 
-  [Calling execCommand("strikeThrough", false, null)]
-    expected: FAIL
-
   [execCommand("strikeThrough") should wrap selected text with <strike> element]
     expected: FAIL
 
-  [Calling execCommand("superscript", false, null)]
-    expected: FAIL
-
   [execCommand("superscript") should wrap selected text with <sup> element]
     expected: FAIL
 
-  [Calling execCommand("subscript", false, null)]
-    expected: FAIL
-
   [execCommand("subscript") should wrap selected text with <sub> element]
     expected: FAIL
 
-  [Calling execCommand("backColor", false, #000000)]
-    expected: FAIL
-
-  [Calling execCommand("foreColor", false, #FFFFFF)]
-    expected: FAIL
-
-  [Calling execCommand("hiliteColor", false, #FFFF00)]
-    expected: FAIL
-
-  [Calling execCommand("fontName", false, monospace)]
-    expected: FAIL
-
-  [Calling execCommand("justifyCenter", false, null)]
-    expected: FAIL
-
   [execCommand("justifyCenter") should wrap the text with <div> element whose text-align is center]
     expected: FAIL
 
-  [Calling execCommand("justifyFull", false, null)]
-    expected: FAIL
-
   [execCommand("justifyFull") should wrap the text with <div> element whose text-align is justify]
     expected: FAIL
 
-  [Calling execCommand("justifyRight", false, null)]
-    expected: FAIL
-
   [execCommand("justifyRight") should wrap the text with <div> element whose text-align is right]
     expected: FAIL
 
-  [Calling execCommand("justifyLeft", false, null)]
-    expected: FAIL
-
   [execCommand("justifyLeft") should wrap the text with <div> element whose text-align is left]
     expected: FAIL
 
   [Calling execCommand("removeFormat", false, null)]
     expected: FAIL
 
   [execCommand("removeFormat") should remove the style of current block]
     expected: FAIL
 
-  [Calling execCommand("indent", false, null)]
-    expected: FAIL
-
-  [Calling execCommand("outdent", false, null)]
-    expected: FAIL
-
   [Set of execCommand("indent") and execCommand("outdent") should keep same DOM tree]
     expected: FAIL
 
   [Calling execCommand("cut", false, null)]
     expected: FAIL
 
   [Calling execCommand("paste", false, null)]
     expected: FAIL
-
-  [Calling execCommand("createLink", false, https://example.com/)]
-    expected: FAIL
-
-  [Calling execCommand("unlink", false, null)]
-    expected: FAIL
--- a/testing/web-platform/meta/uievents/idlharness.window.js.ini
+++ b/testing/web-platform/meta/uievents/idlharness.window.js.ini
@@ -1,13 +1,6 @@
 [idlharness.window.html]
   [InputEvent interface: attribute data]
     expected: FAIL
 
   [InputEvent interface: new InputEvent("event") must inherit property "data" with the proper type]
     expected: FAIL
-
-  [InputEvent interface: new InputEvent("event") must inherit property "inputType" with the proper type]
-    expected: FAIL
-
-  [InputEvent interface: attribute inputType]
-    expected: FAIL
-
--- a/toolkit/components/satchel/test/test_form_autocomplete.html
+++ b/toolkit/components/satchel/test/test_form_autocomplete.html
@@ -992,16 +992,18 @@ function runTest() { // eslint-disable-l
     // Check that the input event is fired.
     case 500:
       input.addEventListener("input", function(event) {
         ok(true, testNum + " oninput should have been received");
         ok(event instanceof InputEvent,
            testNum + " input event should be dispatched with InputEvent interface");
         ok(event.bubbles, testNum + " input event should bubble");
         ok(!event.cancelable, testNum + " input event shouldn't be cancelable");
+        is(event.inputType, "insertReplacementText",
+           testNum + ' inputType should be "insertReplacementText"');
       }, {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
@@ -461,16 +461,17 @@ function runTest() {
     case 400:
       // Check that the input event is fired.
       input.addEventListener("input", function(event) {
         ok(true, "oninput should have been received");
         ok(event instanceof InputEvent,
            "input event should be dispatched with InputEvent interface");
         ok(event.bubbles, "input event should bubble");
         ok(!event.cancelable, "input event should be cancelable");
+        is(event.inputType, "insertReplacementText", 'inputType should be "insertReplacementText"');
         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
@@ -36,16 +36,18 @@ function handleInput(aEvent) {
   info("Input");
   is(input.value, expectedValue, "Check input value");
   ok(aEvent instanceof InputEvent,
      '"input" event should be dispatched with InputEvent interface');
   is(aEvent.cancelable, false,
      '"input" event should be never cancelable');
   is(aEvent.bubbles, true,
      '"input" event should always bubble');
+  is(aEvent.inputType, "insertReplacementText",
+     'inputType should be "insertReplacementText"');
   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/modules/sessionstore/FormData.jsm
+++ b/toolkit/modules/sessionstore/FormData.jsm
@@ -380,18 +380,20 @@ var FormDataInternal = {
   },
 
   /**
    * Dispatches an event of type "input" to the given |node|.
    *
    * @param node (DOMNode)
    */
   fireInputEvent(node) {
+    // "inputType" value hasn't been decided for session restor:
+    // https://github.com/w3c/input-events/issues/30#issuecomment-438693664
     let event = node.isInputEventTarget ?
-      new node.ownerGlobal.InputEvent("input", {bubbles: true}) :
+      new node.ownerGlobal.InputEvent("input", {bubbles: true, inputType: ""}) :
       new node.ownerGlobal.Event("input", {bubbles: true});
     node.dispatchEvent(event);
   },
 
   /**
    * Restores form data for the current frame hierarchy starting at |root|
    * using the given form |data|.
    *
--- a/widget/EventForwards.h
+++ b/widget/EventForwards.h
@@ -112,16 +112,29 @@ enum CodeNameIndex : CodeNameIndexType {
   // specified code name should be stored and use it as .code value.
   CODE_NAME_INDEX_USE_STRING
 };
 
 #undef NS_DEFINE_PHYSICAL_KEY_CODE_NAME
 
 const nsCString ToString(CodeNameIndex aCodeNameIndex);
 
+#define NS_DEFINE_INPUTTYPE(aCPPName, aDOMName) e##aCPPName,
+
+typedef uint8_t EditorInputTypeType;
+enum class EditorInputType : EditorInputTypeType {
+#include "mozilla/InputTypeList.h"
+  // If a DOM input event is synthesized by script, this is used.  Then,
+  // specified input type should be stored as string and use it as .inputType
+  // value.
+  eUnknown,
+};
+
+#undef NS_DEFINE_INPUTTYPE
+
 #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/SharedWidgetUtils.cpp
+++ b/widget/SharedWidgetUtils.cpp
@@ -13,17 +13,20 @@
 #include "nsIDocShell.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsPIDOMWindow.h"
 
 namespace mozilla {
 namespace widget {
 
 // static
-void WidgetUtils::Shutdown() { WidgetKeyboardEvent::Shutdown(); }
+void WidgetUtils::Shutdown() {
+  WidgetKeyboardEvent::Shutdown();
+  InternalEditorInputEvent::Shutdown();
+}
 
 // static
 already_AddRefed<nsIWidget> WidgetUtils::DOMWindowToWidget(
     nsPIDOMWindowOuter* aDOMWindow) {
   nsCOMPtr<nsIWidget> widget;
   nsCOMPtr<nsPIDOMWindowOuter> window = aDOMWindow;
 
   if (window) {
--- a/widget/TextEvents.h
+++ b/widget/TextEvents.h
@@ -1134,44 +1134,62 @@ class WidgetSelectionEvent : public Widg
 };
 
 /******************************************************************************
  * mozilla::InternalEditorInputEvent
  ******************************************************************************/
 
 class InternalEditorInputEvent : public InternalUIEvent {
  private:
-  InternalEditorInputEvent() : mIsComposing(false) {}
+  InternalEditorInputEvent()
+      : mInputType(EditorInputType::eUnknown), mIsComposing(false) {}
 
  public:
   virtual InternalEditorInputEvent* AsEditorInputEvent() override {
     return this;
   }
 
   InternalEditorInputEvent(bool aIsTrusted, EventMessage aMessage,
                            nsIWidget* aWidget = nullptr)
       : InternalUIEvent(aIsTrusted, aMessage, aWidget, eEditorInputEventClass),
-        mIsComposing(false) {}
+        mInputType(EditorInputType::eUnknown) {}
 
   virtual WidgetEvent* Duplicate() const override {
     MOZ_ASSERT(mClass == eEditorInputEventClass,
                "Duplicate() must be overridden by sub class");
     // Not copying widget, it is a weak reference.
     InternalEditorInputEvent* result =
         new InternalEditorInputEvent(false, mMessage, nullptr);
     result->AssignEditorInputEventData(*this, true);
     result->mFlags = mFlags;
     return result;
   }
 
+  EditorInputType mInputType;
+
   bool mIsComposing;
 
   void AssignEditorInputEventData(const InternalEditorInputEvent& aEvent,
                                   bool aCopyTargets) {
     AssignUIEventData(aEvent, aCopyTargets);
 
+    mInputType = aEvent.mInputType;
     mIsComposing = aEvent.mIsComposing;
   }
+
+  void GetDOMInputTypeName(nsAString& aInputTypeName) {
+    GetDOMInputTypeName(mInputType, aInputTypeName);
+  }
+  static void GetDOMInputTypeName(EditorInputType aInputType,
+                                  nsAString& aInputTypeName);
+  static EditorInputType GetEditorInputType(const nsAString& aInputType);
+
+  static void Shutdown();
+
+ private:
+  static const char16_t* const kInputTypeNames[];
+  typedef nsDataHashtable<nsStringHashKey, EditorInputType> InputTypeHashtable;
+  static InputTypeHashtable* sInputTypeHashtable;
 };
 
 }  // namespace mozilla
 
 #endif  // mozilla_TextEvents_h__
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -1691,9 +1691,59 @@ WidgetKeyboardEvent::ComputeCodeNameInde
     case KEY_NAME_INDEX_ScrollLock:
     case KEY_NAME_INDEX_SymbolLock:
       return true;
     default:
       return false;
   }
 }
 
+/******************************************************************************
+ * mozilla::InternalEditorInputEvent (TextEvents.h)
+ ******************************************************************************/
+
+#define NS_DEFINE_INPUTTYPE(aCPPName, aDOMName) (u"" aDOMName),
+const char16_t* const InternalEditorInputEvent::kInputTypeNames[] = {
+#include "mozilla/InputTypeList.h"
+};
+#undef NS_DEFINE_INPUTTYPE
+
+InternalEditorInputEvent::InputTypeHashtable*
+    InternalEditorInputEvent::sInputTypeHashtable = nullptr;
+
+/* static */ void InternalEditorInputEvent::Shutdown() {
+  delete sInputTypeHashtable;
+  sInputTypeHashtable = nullptr;
+}
+
+/* static */ void InternalEditorInputEvent::GetDOMInputTypeName(
+    EditorInputType aInputType, nsAString& aInputTypeName) {
+  if (static_cast<size_t>(aInputType) >=
+      static_cast<size_t>(EditorInputType::eUnknown)) {
+    aInputTypeName.Truncate();
+    return;
+  }
+
+  MOZ_RELEASE_ASSERT(
+      static_cast<size_t>(aInputType) < ArrayLength(kInputTypeNames),
+      "Illegal input type enumeration value");
+  aInputTypeName.Assign(kInputTypeNames[static_cast<size_t>(aInputType)]);
+}
+
+/* static */ EditorInputType InternalEditorInputEvent::GetEditorInputType(
+    const nsAString& aInputType) {
+  if (aInputType.IsEmpty()) {
+    return EditorInputType::eUnknown;
+  }
+
+  if (!sInputTypeHashtable) {
+    sInputTypeHashtable = new InputTypeHashtable(ArrayLength(kInputTypeNames));
+    for (size_t i = 0; i < ArrayLength(kInputTypeNames); i++) {
+      sInputTypeHashtable->Put(nsDependentString(kInputTypeNames[i]),
+                               static_cast<EditorInputType>(i));
+    }
+  }
+  EditorInputType result = EditorInputType::eUnknown;
+  sInputTypeHashtable->Get(aInputType, &result);
+  return result;
+}
+
 }  // namespace mozilla
--- a/widget/tests/window_composition_text_querycontent.xul
+++ b/widget/tests/window_composition_text_querycontent.xul
@@ -638,24 +638,25 @@ function runUndoRedoTest()
 
   if (!checkContent("\u732B\u5A18 means \"cat-girl\".",
                     "runUndoRedoTest", "#" + ++i) ||
       !checkSelection(20, "", "runUndoRedoTest", "#" + i)) {
     return;
   }
 }
 
-function checkInputEvent(aEvent, aIsComposing, aDescription) {
+function checkInputEvent(aEvent, aIsComposing, aInputType, aDescription) {
   if (aEvent.type != "input") {
     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}`);
 }
 
 function runCompositionCommitAsIsTest()
 {
   textarea.focus();
 
   var result = [];
   function clearResult()
@@ -694,17 +695,17 @@ function runCompositionCommitAsIsTest()
   is(result.length, 3,
      "runCompositionCommitAsIsTest: 3 events should be fired after dispatching compositioncommitasis #1");
   is(result[0].type, "text",
      "runCompositionCommitAsIsTest: text should be fired after dispatching compositioncommitasis because it's dispatched when there is composing string #1");
   is(result[1].type, "compositionend",
      "runCompositionCommitAsIsTest: compositionend should be fired after dispatching compositioncommitasis #1");
   is(result[2].type, "input",
      "runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #1");
-  checkInputEvent(result[2], false,
+  checkInputEvent(result[2], false, "insertCompositionText",
                   "runCompositionCommitAsIsTest: after dispatching compositioncommitasis #1");
   is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #1");
 
   // compositioncommitasis with committed string.
   textarea.value = "";
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3042",
@@ -734,17 +735,17 @@ function runCompositionCommitAsIsTest()
   synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter", type: "keyup" } });
 
   is(result.length, 2,
      "runCompositionCommitAsIsTest: 2 events should be fired after dispatching compositioncommitasis #2");
   is(result[0].type, "compositionend",
      "runCompositionCommitAsIsTest: compositionend should be fired after dispatching compositioncommitasis #2");
   is(result[1].type, "input",
      "runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #2");
-  checkInputEvent(result[1], false,
+  checkInputEvent(result[1], false, "insertCompositionText",
                   "runCompositionCommitAsIsTest: after dispatching compositioncommitasis #2");
   is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #2");
 
   // compositioncommitasis with committed string.
   textarea.value = "";
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3042",
@@ -774,17 +775,17 @@ function runCompositionCommitAsIsTest()
   synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Escape", type: "keyup" } });
 
   is(result.length, 2,
      "runCompositionCommitAsIsTest: 2 events should be fired after dispatching compositioncommitasis #3");
   is(result[0].type, "compositionend",
      "runCompositionCommitAsIsTest: compositionend shouldn't be fired after dispatching compositioncommitasis #3");
   is(result[1].type, "input",
      "runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #3");
-  checkInputEvent(result[1], false,
+  checkInputEvent(result[1], false, "insertCompositionText",
                   "runCompositionCommitAsIsTest: after dispatching compositioncommitasis #3");
   is(textarea.value, "", "runCompositionCommitAsIsTest: textarea doesn't have committed string #3");
 
   textarea.removeEventListener("compositionupdate", handler, true);
   textarea.removeEventListener("compositionend", handler, true);
   textarea.removeEventListener("input", handler, true);
   textarea.removeEventListener("text", handler, true);
 }
@@ -832,17 +833,17 @@ function runCompositionCommitTest()
   is(result[0].type, "compositionupdate",
      "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit because it's dispatched when there is composing string #1");
   is(result[1].type, "text",
      "runCompositionCommitTest: text should be fired after dispatching compositioncommit #1");
   is(result[2].type, "compositionend",
      "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #1");
   is(result[3].type, "input",
      "runCompositionCommitTest: input should be fired after dispatching compositioncommit #1");
-  checkInputEvent(result[3], false,
+  checkInputEvent(result[3], false, "insertCompositionText",
                   "runCompositionCommitTest: after dispatching compositioncommit #1");
   is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #1");
 
   // compositioncommit with different committed string when there is already committed string
   textarea.value = "";
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3042",
@@ -876,17 +877,17 @@ function runCompositionCommitTest()
   is(result[0].type, "compositionupdate",
      "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #2");
   is(result[1].type, "text",
      "runCompositionCommitTest: text should be fired after dispatching compositioncommit #2");
   is(result[2].type, "compositionend",
      "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #2");
   is(result[3].type, "input",
      "runCompositionCommitTest: input should be fired after dispatching compositioncommit #2");
-  checkInputEvent(result[3], false,
+  checkInputEvent(result[3], false, "insertCompositionText",
                   "runCompositionCommitTest: after dispatching compositioncommit #2");
   is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #2");
 
   // compositioncommit with empty composition string.
   textarea.value = "";
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3042",
@@ -920,17 +921,17 @@ function runCompositionCommitTest()
   is(result[0].type, "compositionupdate",
      "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #3");
   is(result[1].type, "text",
      "runCompositionCommitTest: text should be fired after dispatching compositioncommit #3");
   is(result[2].type, "compositionend",
      "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #3");
   is(result[3].type, "input",
      "runCompositionCommitTest: input should be fired after dispatching compositioncommit #3");
-  checkInputEvent(result[3], false,
+  checkInputEvent(result[3], false, "insertCompositionText",
                   "runCompositionCommitTest: after dispatching compositioncommit #3");
   is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #3");
 
   // inserting empty string with simple composition.
   textarea.value = "abc";
   textarea.setSelectionRange(3, 3);
   synthesizeComposition({ type: "compositionstart" });
 
@@ -940,17 +941,17 @@ function runCompositionCommitTest()
   is(result.length, 3,
      "runCompositionCommitTest: 3 events should be fired when inserting empty string with composition");
   is(result[0].type, "text",
      "runCompositionCommitTest: text should be fired when inserting empty string with composition");
   is(result[1].type, "compositionend",
      "runCompositionCommitTest: compositionend should be fired when inserting empty string with composition");
   is(result[2].type, "input",
      "runCompositionCommitTest: input should be fired when inserting empty string with composition");
-  checkInputEvent(result[2], false,
+  checkInputEvent(result[2], false, "insertCompositionText",
                   "runCompositionCommitTest: when inserting empty string with composition");
   is(textarea.value, "abc",
      "runCompositionCommitTest: textarea should keep original value when inserting empty string with composition");
 
   // replacing selection with empty string with simple composition.
   textarea.value = "abc";
   textarea.setSelectionRange(0, 3);
   synthesizeComposition({ type: "compositionstart" });
@@ -961,17 +962,17 @@ function runCompositionCommitTest()
   is(result.length, 3,
      "runCompositionCommitTest: 3 events should be fired when replacing with empty string with composition");
   is(result[0].type, "text",
      "runCompositionCommitTest: text should be fired when replacing with empty string with composition");
   is(result[1].type, "compositionend",
      "runCompositionCommitTest: compositionend should be fired when replacing with empty string with composition");
   is(result[2].type, "input",
      "runCompositionCommitTest: input should be fired when replacing with empty string with composition");
-  checkInputEvent(result[2], false,
+  checkInputEvent(result[2], false, "insertCompositionText",
                   "runCompositionCommitTest: when replacing with empty string with composition");
   is(textarea.value, "",
      "runCompositionCommitTest: textarea should become empty when replacing selection with empty string with composition");
 
   // replacing selection with same string with simple composition.
   textarea.value = "abc";
   textarea.setSelectionRange(0, 3);
   synthesizeComposition({ type: "compositionstart" });
@@ -984,17 +985,17 @@ function runCompositionCommitTest()
   is(result[0].type, "compositionupdate",
      "runCompositionCommitTest: compositionupdate should be fired when replacing selection with same string with composition");
   is(result[1].type, "text",
      "runCompositionCommitTest: text should be fired when replacing selection with same string with composition");
   is(result[2].type, "compositionend",
      "runCompositionCommitTest: compositionend should be fired when replacing selection with same string with composition");
   is(result[3].type, "input",
      "runCompositionCommitTest: input should be fired when replacing selection with same string with composition");
-  checkInputEvent(result[3], false,
+  checkInputEvent(result[3], false, "insertCompositionText",
                   "runCompositionCommitTest: when replacing selection with same string with composition");
   is(textarea.value, "abc",
      "runCompositionCommitTest: textarea should keep same value when replacing selection with same string with composition");
 
   // compositioncommit with non-empty composition string.
   textarea.value = "";
   synthesizeCompositionChange(
     { "composition":
@@ -1017,17 +1018,17 @@ function runCompositionCommitTest()
   is(result[0].type, "compositionupdate",
      "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #4");
   is(result[1].type, "text",
      "runCompositionCommitTest: text should be fired after dispatching compositioncommit #4");
   is(result[2].type, "compositionend",
      "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #4");
   is(result[3].type, "input",
      "runCompositionCommitTest: input should be fired after dispatching compositioncommit #4");
-  checkInputEvent(result[3], false,
+  checkInputEvent(result[3], false, "insertCompositionText",
                   "runCompositionCommitTest: after dispatching compositioncommit #4");
   is(textarea.value, "", "runCompositionCommitTest: textarea should be empty #4");
 
   // compositioncommit immediately without compositionstart
   textarea.value = "";
 
   clearResult();
   synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "a" } });
@@ -1037,17 +1038,17 @@ function runCompositionCommitTest()
   is(result[0].type, "compositionupdate",
      "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #5");
   is(result[1].type, "text",
      "runCompositionCommitTest: text should be fired after dispatching compositioncommit #5");
   is(result[2].type, "compositionend",
      "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #5");
   is(result[3].type, "input",
      "runCompositionCommitTest: input should be fired after dispatching compositioncommit #5");
-  checkInputEvent(result[3], false,
+  checkInputEvent(result[3], false, "insertCompositionText",
                   "runCompositionCommitTest: after dispatching compositioncommit #5");
   is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should be empty #5");
 
   // compositioncommit with same composition string.
   textarea.value = "";
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3042",
@@ -1067,17 +1068,17 @@ function runCompositionCommitTest()
   is(result.length, 3,
      "runCompositionCommitTest: 3 events should be fired after dispatching compositioncommit #6");
   is(result[0].type, "text",
      "runCompositionCommitTest: text should be fired after dispatching compositioncommit #6");
   is(result[1].type, "compositionend",
      "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #6");
   is(result[2].type, "input",
      "runCompositionCommitTest: input should be fired after dispatching compositioncommit #6");
-  checkInputEvent(result[2], false,
+  checkInputEvent(result[2], false, "insertCompositionText",
                   "runCompositionCommitTest: after dispatching compositioncommit #6");
   is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should have committed string #6");
 
   // compositioncommit with same composition string when there is committed string
   textarea.value = "";
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3042",
@@ -1108,17 +1109,17 @@ function runCompositionCommitTest()
   synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "KEY_Enter", type: "keyup" } });
 
   is(result.length, 2,
      "runCompositionCommitTest: 2 events should be fired after dispatching compositioncommit #7");
   is(result[0].type, "compositionend",
      "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #7");
   is(result[1].type, "input",
      "runCompositionCommitTest: input should be fired after dispatching compositioncommit #7");
-  checkInputEvent(result[1], false,
+  checkInputEvent(result[1], false, "insertCompositionText",
                   "runCompositionCommitTest: after dispatching compositioncommit #7");
   is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should have committed string #6");
 
   textarea.removeEventListener("compositionupdate", handler, true);
   textarea.removeEventListener("compositionend", handler, true);
   textarea.removeEventListener("input", handler, true);
   textarea.removeEventListener("text", handler, true);
 }
@@ -5049,30 +5050,30 @@ function runForceCommitTest()
   is(events[0].type, "compositionstart",
      "runForceCommitTest: the 1st event must be compositionstart #1");
   is(events[1].type, "compositionupdate",
      "runForceCommitTest: the 2nd event must be compositionupdate #1");
   is(events[2].type, "text",
      "runForceCommitTest: the 3rd event must be text #1");
   is(events[3].type, "input",
      "runForceCommitTest: the 4th event must be input #1");
-  checkInputEvent(events[3], true, "runForceCommitTest #1");
+  checkInputEvent(events[3], true, "insertCompositionText", "runForceCommitTest #1");
 
   events = [];
   synthesizeMouseAtCenter(textarea, {});
 
   is(events.length, 3,
      "runForceCommitTest: wrong event count #2");
   is(events[0].type, "text",
      "runForceCommitTest: the 1st event must be text #2");
   is(events[1].type, "compositionend",
      "runForceCommitTest: the 2nd event must be compositionend #2");
   is(events[2].type, "input",
      "runForceCommitTest: the 3rd event must be input #2");
-  checkInputEvent(events[2], false, "runForceCommitTest #2");
+  checkInputEvent(events[2], false, "insertCompositionText", "runForceCommitTest #2");
   is(events[1].data, "\u306E",
      "runForceCommitTest: compositionend has wrong data #2");
   is(events[0].target, textarea,
      "runForceCommitTest: The 1st event was fired on wrong event target #2");
   is(events[1].target, textarea,
      "runForceCommitTest: The 2nd event was fired on wrong event target #2");
   is(events[2].target, textarea,
      "runForceCommitTest: The 3rd event was fired on wrong event target #2");
@@ -5103,17 +5104,17 @@ function runForceCommitTest()
   is(events.length, 3,
      "runForceCommitTest: wrong event count #3");
   is(events[0].type, "text",
      "runForceCommitTest: the 1st event must be text #3");
   is(events[1].type, "compositionend",
      "runForceCommitTest: the 2nd event must be compositionend #3");
   is(events[2].type, "input",
      "runForceCommitTest: the 3rd event must be input #3");
-  checkInputEvent(events[2], false, "runForceCommitTest #3");
+  checkInputEvent(events[2], false, "insertCompositionText", "runForceCommitTest #3");
   is(events[1].data, "\u306E",
      "runForceCommitTest: compositionend has wrong data #3");
   is(events[0].target, textarea,
      "runForceCommitTest: The 1st event was fired on wrong event target #3");
   is(events[1].target, textarea,
      "runForceCommitTest: The 2nd event was fired on wrong event target #3");
   is(events[2].target, textarea,
      "runForceCommitTest: The 3rd event was fired on wrong event target #3");
@@ -5147,17 +5148,17 @@ function runForceCommitTest()
   is(events.length, 3,
      "runForceCommitTest: wrong event count #4");
   is(events[0].type, "text",
      "runForceCommitTest: the 1st event must be text #4");
   is(events[1].type, "compositionend",
      "runForceCommitTest: the 2nd event must be compositionend #4");
   is(events[2].type, "input",
      "runForceCommitTest: the 3rd event must be input #4");
-  checkInputEvent(events[2], false, "runForceCommitTest #4");
+  checkInputEvent(events[2], false, "insertCompositionText", "runForceCommitTest #4");
   is(events[1].data, "\u306E",
      "runForceCommitTest: compositionend has wrong data #4");
   is(events[0].target, textarea,
      "runForceCommitTest: The 1st event was fired on wrong event target #4");
   is(events[1].target, textarea,
      "runForceCommitTest: The 2nd event was fired on wrong event target #4");
   is(events[2].target, textarea,
      "runForceCommitTest: The 3rd event was fired on wrong event target #4");
@@ -5188,17 +5189,17 @@ function runForceCommitTest()
   is(events.length, 3,
      "runForceCommitTest: wrong event count #5");
   is(events[0].type, "text",
      "runForceCommitTest: the 1st event must be text #5");
   is(events[1].type, "compositionend",
      "runForceCommitTest: the 2nd event must be compositionend #5");
   is(events[2].type, "input",
      "runForceCommitTest: the 3rd event must be input #5");
-  checkInputEvent(events[2], false, "runForceCommitTest #5");
+  checkInputEvent(events[2], false, "insertCompositionText", "runForceCommitTest #5");
   is(events[1].data, "\u306E",
      "runForceCommitTest: compositionend has wrong data #5");
   is(events[0].target, textarea,
      "runForceCommitTest: The 1st event was fired on wrong event target #5");
   is(events[1].target, textarea,
      "runForceCommitTest: The 2nd event was fired on wrong event target #5");
   is(events[2].target, textarea,
      "runForceCommitTest: The 3rd event was fired on wrong event target #5");
@@ -5233,17 +5234,17 @@ function runForceCommitTest()
   is(events.length, 3,
      "runForceCommitTest: wrong event count #6");
   is(events[0].type, "text",
      "runForceCommitTest: the 1st event must be text #6");
   is(events[1].type, "compositionend",
      "runForceCommitTest: the 2nd event must be compositionend #6");
   is(events[2].type, "input",
      "runForceCommitTest: the 3rd event must be input #6");
-  checkInputEvent(events[2], false, "runForceCommitTest #6");
+  checkInputEvent(events[2], false, "insertCompositionText", "runForceCommitTest #6");
   is(events[1].data, "\u306E",
      "runForceCommitTest: compositionend has wrong data #6");
   is(events[0].target, textarea,
      "runForceCommitTest: The 1st event was fired on wrong event target #6");
   is(events[1].target, textarea,
      "runForceCommitTest: The 2nd event was fired on wrong event target #6");
   is(events[2].target, textarea,
      "runForceCommitTest: The 3rd event was fired on wrong event target #6");
@@ -5278,17 +5279,17 @@ function runForceCommitTest()
   is(events.length, 3,
      "runForceCommitTest: wrong event count #7");
   is(events[0].type, "text",
      "runForceCommitTest: the 1st event must be text #7");
   is(events[1].type, "compositionend",
      "runForceCommitTest: the 2nd event must be compositionend #7");
   is(events[2].type, "input",
      "runForceCommitTest: the 3rd event must be input #7");
-  checkInputEvent(events[2], false, "runForceCommitTest #7");
+  checkInputEvent(events[2], false, "insertCompositionText", "runForceCommitTest #7");
   is(events[1].data, "\u306E",
      "runForceCommitTest: compositionend has wrong data #7");
   is(events[0].target, textarea,
      "runForceCommitTest: The 1st event was fired on wrong event target #7");
   is(events[1].target, textarea,
      "runForceCommitTest: The 2nd event was fired on wrong event target #7");
   is(events[2].target, textarea,
      "runForceCommitTest: The 3rd event was fired on wrong event target #7");
@@ -5324,17 +5325,17 @@ function runForceCommitTest()
   is(events.length, 3,
      "runForceCommitTest: wrong event count #8");
   is(events[0].type, "text",
      "runForceCommitTest: the 1st event must be text #8");
   is(events[1].type, "compositionend",
      "runForceCommitTest: the 2nd event must be compositionend #8");
   is(events[2].type, "input",
      "runForceCommitTest: the 3rd event must be input #8");
-  checkInputEvent(events[2], false, "runForceCommitTest #8");
+  checkInputEvent(events[2], false, "insertCompositionText", "runForceCommitTest #8");
   is(events[1].data, "\u306E",
      "runForceCommitTest: compositionend has wrong data #8");
   is(events[0].target, textarea,
      "runForceCommitTest: The 1st event was fired on wrong event target #8");
   is(events[1].target, textarea,
      "runForceCommitTest: The 2nd event was fired on wrong event target #8");
   is(events[2].target, textarea,
      "runForceCommitTest: The 3rd event was fired on wrong event target #8");
@@ -5369,17 +5370,17 @@ function runForceCommitTest()
   is(events.length, 3,
      "runForceCommitTest: wrong event count #9");
   is(events[0].type, "text",
      "runForceCommitTest: the 1st event must be text #9");
   is(events[1].type, "compositionend",
      "runForceCommitTest: the 2nd event must be compositionend #9");
   is(events[2].type, "input",
      "runForceCommitTest: the 3rd event must be input #9");
-  checkInputEvent(events[2], false, "runForceCommitTest #9");
+  checkInputEvent(events[2], false, "insertCompositionText", "runForceCommitTest #9");
   is(events[1].data, "\u306E",
      "runForceCommitTest: compositionend has wrong data #9");
   is(events[0].target, iframe2.contentDocument.body,
      "runForceCommitTest: The 1st event was fired on wrong event target #9");
   is(events[1].target, iframe2.contentDocument.body,
      "runForceCommitTest: The 2nd event was fired on wrong event target #9");
   is(events[2].target, iframe2.contentDocument.body,
      "runForceCommitTest: The 3rd event was fired on wrong event target #9");
@@ -5412,17 +5413,17 @@ function runForceCommitTest()
   is(events.length, 3,
      "runForceCommitTest: wrong event count #10");
   is(events[0].type, "text",
      "runForceCommitTest: the 1st event must be text #10");
   is(events[1].type, "compositionend",
      "runForceCommitTest: the 2nd event must be compositionend #10");
   is(events[2].type, "input",
      "runForceCommitTest: the 3rd event must be input #10");
-  checkInputEvent(events[2], false, "runForceCommitTest #10");
+  checkInputEvent(events[2], false, "insertCompositionText", "runForceCommitTest #10");
   is(events[1].data, "\u306E",
      "runForceCommitTest: compositionend has wrong data #10");
   is(events[0].target, iframe2.contentDocument.body,
      "runForceCommitTest: The 1st event was fired on wrong event target #10");
   is(events[1].target, iframe2.contentDocument.body,
      "runForceCommitTest: The 2nd event was fired on wrong event target #10");
   is(events[2].target, iframe2.contentDocument.body,
      "runForceCommitTest: The 3rd event was fired on wrong event target #10");
@@ -5460,17 +5461,17 @@ function runForceCommitTest()
   is(events.length, 3,
      "runForceCommitTest: wrong event count #11");
   is(events[0].type, "text",
      "runForceCommitTest: the 1st event must be text #11");
   is(events[1].type, "compositionend",
      "runForceCommitTest: the 2nd event must be compositionend #11");
   is(events[2].type, "input",
      "runForceCommitTest: the 3rd event must be input #11");
-  checkInputEvent(events[2], false, "runForceCommitTest #11");
+  checkInputEvent(events[2], false, "insertCompositionText", "runForceCommitTest #11");
   is(events[1].data, "\u306E",
      "runForceCommitTest: compositionend has wrong data #11");
   is(events[0].target, iframe2.contentDocument.body,
      "runForceCommitTest: The 1st event was fired on wrong event target #11");
   is(events[1].target, iframe2.contentDocument.body,
      "runForceCommitTest: The 2nd event was fired on wrong event target #11");
   is(events[2].target, iframe2.contentDocument.body,
      "runForceCommitTest: The 3rd event was fired on wrong event target #11");
@@ -5504,17 +5505,17 @@ function runForceCommitTest()
   is(events.length, 3,
      "runForceCommitTest: wrong event count #12");
   is(events[0].type, "text",
      "runForceCommitTest: the 1st event must be text #12");
   is(events[1].type, "compositionend",
      "runForceCommitTest: the 2nd event must be compositionend #12");
   is(events[2].type, "input",
      "runForceCommitTest: the 3rd event must be input #12");
-  checkInputEvent(events[2], false, "runForceCommitTest #12");
+  checkInputEvent(events[2], false, "insertCompositionText", "runForceCommitTest #12");
   is(events[1].data, "\u306E",
      "runForceCommitTest: compositionend has wrong data #12");
   is(events[0].target, input,
      "runForceCommitTest: The 1st event was fired on wrong event target #12");
   is(events[1].target, input,
      "runForceCommitTest: The 2nd event was fired on wrong event target #12");
   is(events[2].target, input,
      "runForceCommitTest: The 3rd event was fired on wrong event target #12");
@@ -5543,17 +5544,17 @@ function runForceCommitTest()
   is(events.length, 3,
      "runForceCommitTest: wrong event count #13");
   is(events[0].type, "text",
      "runForceCommitTest: the 1st event must be text #13");
   is(events[1].type, "compositionend",
      "runForceCommitTest: the 2nd event must be compositionend #13");
   is(events[2].type, "input",
      "runForceCommitTest: the 3rd event must be input #13");
-  checkInputEvent(events[2], false, "runForceCommitTest #13");
+  checkInputEvent(events[2], false, "insertCompositionText", "runForceCommitTest #13");
   is(events[1].data, "\u306E",
      "runForceCommitTest: compositionend has wrong data #13");
   is(events[0].target, textarea,
      "runForceCommitTest: The 1st event was fired on wrong event target #13");
   is(events[1].target, textarea,
      "runForceCommitTest: The 2nd event was fired on wrong event target #13");
   is(events[2].target, textarea,
      "runForceCommitTest: The 3rd event was fired on wrong event target #13");
@@ -5582,17 +5583,17 @@ function runForceCommitTest()
   is(events.length, 3,
      "runForceCommitTest: wrong event count #14");
   is(events[0].type, "text",
      "runForceCommitTest: the 1st event must be text #14");
   is(events[1].type, "compositionend",
      "runForceCommitTest: the 2nd event must be compositionend #14");
   is(events[2].type, "input",
      "runForceCommitTest: the 3rd event must be input #14");
-  checkInputEvent(events[2], false, "runForceCommitTest #14");
+  checkInputEvent(events[2], false, "insertCompositionText", "runForceCommitTest #14");
   is(events[1].data, "\u306E",
      "runForceCommitTest: compositionend has wrong data #14");
   is(events[0].target, input,
      "runForceCommitTest: The 1st event was fired on wrong event target #14");
   is(events[1].target, input,
      "runForceCommitTest: The 2nd event was fired on wrong event target #14");
   is(events[2].target, input,
      "runForceCommitTest: The 3rd event was fired on wrong event target #14");
@@ -5695,17 +5696,17 @@ function runNestedSettingValue()
   is(events.length, 3,
      "runNestedSettingValue: wrong event count #1");
   is(events[0].type, "text",
      "runNestedSettingValue: the 1st event must be text #1");
   is(events[1].type, "compositionend",
      "runNestedSettingValue: the 2nd event must be compositionend #1");
   is(events[2].type, "input",
      "runNestedSettingValue: the 3rd event must be input #1");
-  checkInputEvent(events[2], false, "runNestedSettingValue #1");
+  checkInputEvent(events[2], false, "insertCompositionText", "runNestedSettingValue #1");
   is(events[1].data, "\u306E",
      "runNestedSettingValue: compositionend has wrong data #1");
   is(events[0].target, textarea,
      "runNestedSettingValue: The 1st event was fired on wrong event target #1");
   is(events[1].target, textarea,
      "runNestedSettingValue: The 2nd event was fired on wrong event target #1");
   is(events[2].target, textarea,
      "runNestedSettingValue: The 3rd event was fired on wrong event target #1");
@@ -5736,17 +5737,17 @@ function runNestedSettingValue()
   is(events.length, 3,
      "runNestedSettingValue: wrong event count #2");
   is(events[0].type, "text",
      "runNestedSettingValue: the 1st event must be text #2");
   is(events[1].type, "compositionend",
      "runNestedSettingValue: the 2nd event must be compositionend #2");
   is(events[2].type, "input",
      "runNestedSettingValue: the 3rd event must be input #2");
-  checkInputEvent(events[2], false, "runNestedSettingValue #2");
+  checkInputEvent(events[2], false, "insertCompositionText", "runNestedSettingValue #2");
   is(events[1].data, "\u306E",
      "runNestedSettingValue: compositionend has wrong data #2");
   is(events[0].target, input,
      "runNestedSettingValue: The 1st event was fired on wrong event target #2");
   is(events[1].target, input,
      "runNestedSettingValue: The 2nd event was fired on wrong event target #2");
   is(events[2].target, input,
      "runNestedSettingValue: The 3rd event was fired on wrong event target #2");
@@ -5777,17 +5778,17 @@ function runNestedSettingValue()
   is(events.length, 3,
      "runNestedSettingValue: wrong event count #3");
   is(events[0].type, "text",
      "runNestedSettingValue: the 1st event must be text #3");
   is(events[1].type, "compositionend",
      "runNestedSettingValue: the 2nd event must be compositionend #3");
   is(events[2].type, "input",
      "runNestedSettingValue: the 3rd event must be input #3");
-  checkInputEvent(events[2], false, "runNestedSettingValue #3");
+  checkInputEvent(events[2], false, "insertCompositionText", "runNestedSettingValue #3");
   is(events[1].data, "\u306E",
      "runNestedSettingValue: compositionend has wrong data #3");
   is(events[0].target, textarea,
      "runNestedSettingValue: The 1st event was fired on wrong event target #3");
   is(events[1].target, textarea,
      "runNestedSettingValue: The 2nd event was fired on wrong event target #3");
   is(events[2].target, textarea,
      "runNestedSettingValue: The 3rd event was fired on wrong event target #3");
@@ -5818,17 +5819,17 @@ function runNestedSettingValue()
   is(events.length, 3,
      "runNestedSettingValue: wrong event count #4");
   is(events[0].type, "text",
      "runNestedSettingValue: the 1st event must be text #4");
   is(events[1].type, "compositionend",
      "runNestedSettingValue: the 2nd event must be compositionend #4");
   is(events[2].type, "input",
      "runNestedSettingValue: the 3rd event must be input #4");
-  checkInputEvent(events[2], false, "runNestedSettingValue #4");
+  checkInputEvent(events[2], false, "insertCompositionText", "runNestedSettingValue #4");
   is(events[1].data, "\u306E",
      "runNestedSettingValue: compositionend has wrong data #4");
   is(events[0].target, input,
      "runNestedSettingValue: The 1st event was fired on wrong event target #4");
   is(events[1].target, input,
      "runNestedSettingValue: The 2nd event was fired on wrong event target #4");
   is(events[2].target, input,
      "runNestedSettingValue: The 3rd event was fired on wrong event target #4");
@@ -5911,33 +5912,33 @@ function runAsyncForceCommitTest()
   is(events[0].type, "compositionstart",
      "runAsyncForceCommitTest: the 1st event must be compositionstart #1");
   is(events[1].type, "compositionupdate",
      "runAsyncForceCommitTest: the 2nd event must be compositionupdate #1");
   is(events[2].type, "text",
      "runAsyncForceCommitTest: the 3rd event must be text #1");
   is(events[3].type, "input",
      "runAsyncForceCommitTest: the 4th event must be input #1");
-  checkInputEvent(events[3], true, "runAsyncForceCommitTest #1");
+  checkInputEvent(events[3], true, "insertCompositionText", "runAsyncForceCommitTest #1");
 
   events = [];
   commitRequested = false;
   synthesizeMouseAtCenter(textarea, {});
 
   ok(commitRequested,
      "runAsyncForceCommitTest: \"request-to-commit\" should've been notified");
   is(events.length, 3,
      "runAsyncForceCommitTest: wrong event count #2");
   is(events[0].type, "text",
      "runAsyncForceCommitTest: the 1st event must be text #2");
   is(events[1].type, "compositionend",
      "runAsyncForceCommitTest: the 2nd event must be compositionend #2");
   is(events[2].type, "input",
      "runAsyncForceCommitTest: the 3rd event must be input #2");
-  checkInputEvent(events[2], false, "runAsyncForceCommitTest #2");
+  checkInputEvent(events[2], false, "insertCompositionText", "runAsyncForceCommitTest #2");
   is(events[1].data, "\u306E",
      "runAsyncForceCommitTest: compositionend has wrong data #2");
   is(events[0].target, textarea,
      "runAsyncForceCommitTest: The 1st event was fired on wrong event target #2");
   is(events[1].target, textarea,
      "runAsyncForceCommitTest: The 2nd event was fired on wrong event target #2");
   is(events[2].target, textarea,
      "runAsyncForceCommitTest: The 3rd event was fired on wrong event target #2");
@@ -5979,17 +5980,17 @@ function runIsComposingTest()
     if (aEvent.type == "keydown" || aEvent.type == "keyup") {
       is(aEvent.isComposing, expectedIsComposing,
          "runIsComposingTest: " + description + " (type=" + aEvent.type + ", key=" + aEvent.key + ")");
     } else if (aEvent.type == "keypress") {
       // keypress event shouldn't be fired during composition so that isComposing should be always false.
       is(aEvent.isComposing, false,
          "runIsComposingTest: " + description + " (type=" + aEvent.type + ")");
     } else {
-      checkInputEvent(aEvent, expectedIsComposing, `runIsComposingTest: ${description}`);
+      checkInputEvent(aEvent, expectedIsComposing, "insertCompositionText", `runIsComposingTest: ${description}`);
     }
   }
 
   function onComposition(aEvent)
   {
     if (aEvent.type == "compositionstart") {
       expectedIsComposing = true;
     } else if (aEvent.type == "compositionend") {
@@ -6089,17 +6090,17 @@ function runRedundantChangeTest()
   is(result.length, 3,
      "runRedundantChangeTest: 3 events should be fired after synthesizing composition change #1");
   is(result[0].type, "compositionupdate",
      "runRedundantChangeTest: compositionupdate should be fired after synthesizing composition change #1");
   is(result[1].type, "text",
      "runRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string #1");
   is(result[2].type, "input",
      "runRedundantChangeTest: input should be fired after synthesizing composition change #1");
-  checkInputEvent(result[2], true,
+  checkInputEvent(result[2], true, "insertCompositionText",
                   "runRedundantChangeTest: after synthesizing composition change #1");
   is(textarea.value, "\u3042", "runRedundantChangeTest: textarea has uncommitted string #1");
 
   // synthesize another change event
   clearResult();
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3042\u3044",
@@ -6114,17 +6115,17 @@ function runRedundantChangeTest()
   is(result.length, 3,
      "runRedundantChangeTest: 3 events should be fired after synthesizing composition change #2");
   is(result[0].type, "compositionupdate",
      "runRedundantChangeTest: compositionupdate should be fired after synthesizing composition change #2");
   is(result[1].type, "text",
      "runRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string #2");
   is(result[2].type, "input",
      "runRedundantChangeTest: input should be fired after synthesizing composition change #2");
-  checkInputEvent(result[2], true,
+  checkInputEvent(result[2], true, "insertCompositionText",
                   "runRedundantChangeTest: after synthesizing composition change #2");
   is(textarea.value, "\u3042\u3044", "runRedundantChangeTest: textarea has uncommitted string #2");
 
   // synthesize same change event again
   clearResult();
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3042\u3044",
@@ -6145,17 +6146,17 @@ function runRedundantChangeTest()
   is(result.length, 3,
      "runRedundantChangeTest: 3 events be fired after synthesizing composition commit-as-is");
   is(result[0].type, "text",
      "runRedundantChangeTest: text shouldn't be fired after synthesizing composition commit-as-is for removing the ranges");
   is(result[1].type, "compositionend",
      "runRedundantChangeTest: compositionend should be fired after synthesizing composition commit-as-is");
   is(result[2].type, "input",
      "runRedundantChangeTest: input shouldn't be fired before compositionend at synthesizing commit-as-is");
-  checkInputEvent(result[2], false,
+  checkInputEvent(result[2], false, "insertCompositionText",
                   "runRedundantChangeTest: at synthesizing commit-as-is");
   is(textarea.value, "\u3042\u3044", "runRedundantChangeTest: textarea has the commit string");
 
   textarea.removeEventListener("compositionupdate", handler, true);
   textarea.removeEventListener("compositionend", handler, true);
   textarea.removeEventListener("input", handler, true);
   textarea.removeEventListener("text", handler, true);
 }
@@ -6198,17 +6199,17 @@ function runNotRedundantChangeTest()
   is(result.length, 3,
      "runNotRedundantChangeTest: 3 events should be fired after synthesizing composition change with non-null ranges");
   is(result[0].type, "compositionupdate",
      "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with non-null ranges");
   is(result[1].type, "text",
      "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with non-null ranges");
   is(result[2].type, "input",
      "runNotRedundantChangeTest: input should be fired after synthesizing composition change with non-null ranges");
-  checkInputEvent(result[2], true,
+  checkInputEvent(result[2], true, "insertCompositionText",
                   "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges");
   is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #1");
 
   // synthesize change event with null ranges
   clearResult();
   synthesizeCompositionChange(
     { "composition":
       { "string": "ABCDE",
@@ -6219,17 +6220,17 @@ function runNotRedundantChangeTest()
       },
     });
   is(result.length, 2,
      "runNotRedundantChangeTest: 2 events should be fired after synthesizing composition change with null ranges after non-null ranges");
   is(result[0].type, "text",
      "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with null ranges after non-null ranges");
   is(result[1].type, "input",
      "runNotRedundantChangeTest: input should be fired after synthesizing composition change with null ranges after non-null ranges");
-  checkInputEvent(result[1], true,
+  checkInputEvent(result[1], true, "insertCompositionText",
                   "runNotRedundantChangeTest: after synthesizing composition change with null ranges after non-null ranges");
   is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #2");
 
   // synthesize change event with non-null ranges
   clearResult();
   synthesizeCompositionChange(
     { "composition":
       { "string": "ABCDE",
@@ -6242,17 +6243,17 @@ function runNotRedundantChangeTest()
     });
 
   is(result.length, 2,
      "runNotRedundantChangeTest: 2 events should be fired after synthesizing composition change with null ranges after non-null ranges");
   is(result[0].type, "text",
      "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with non-null ranges after null ranges");
   is(result[1].type, "input",
      "runNotRedundantChangeTest: input should be fired after synthesizing composition change with non-null ranges after null ranges");
-  checkInputEvent(result[1], true,
+  checkInputEvent(result[1], true, "insertCompositionText",
                   "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges after null ranges");
   is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #3");
 
   // synthesize change event with empty data and null ranges
   clearResult();
   synthesizeCompositionChange(
     { "composition":
       { "string": "",
@@ -6265,17 +6266,17 @@ function runNotRedundantChangeTest()
   is(result.length, 3,
      "runNotRedundantChangeTest: 3 events should be fired after synthesizing composition change with empty data and null ranges after non-null ranges");
   is(result[0].type, "compositionupdate",
      "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with empty data and null ranges after non-null ranges");
   is(result[1].type, "text",
      "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with empty data and null ranges after non-null ranges");
   is(result[2].type, "input",
      "runNotRedundantChangeTest: input should be fired after synthesizing composition change with empty data and null ranges after non-null ranges");
-  checkInputEvent(result[2], true,
+  checkInputEvent(result[2], true, "insertCompositionText",
                   "runNotRedundantChangeTest: after synthesizing composition change with empty data and null ranges after non-null ranges");
   is(textarea.value, "abcde", "runNotRedundantChangeTest: textarea doesn't have uncommitted string #1");
 
   // synthesize change event with non-null ranges
   clearResult();
   synthesizeCompositionChange(
     { "composition":
       { "string": "ABCDE",
@@ -6290,34 +6291,34 @@ function runNotRedundantChangeTest()
   is(result.length, 3,
      "runNotRedundantChangeTest: 3 events should be fired after synthesizing composition change with non-null ranges after empty data and null ranges");
   is(result[0].type, "compositionupdate",
      "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with non-null ranges after empty data and null ranges");
   is(result[1].type, "text",
      "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with non-null ranges after empty data and null ranges");
   is(result[2].type, "input",
      "runNotRedundantChangeTest: input should be fired after synthesizing composition change with non-null ranges after empty data and null ranges");
-  checkInputEvent(result[2], true,
+  checkInputEvent(result[2], true, "insertCompositionText",
                   "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges after empty data and null ranges");
   is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #4");
 
   clearResult();
   synthesizeComposition({ type: "compositioncommit", data: "" });
 
   is(result.length, 4,
      "runNotRedundantChangeTest: 4 events should be fired after synthesizing composition commit with empty data after non-empty data");
   is(result[0].type, "compositionupdate",
      "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition commit with empty data after non-empty data");
   is(result[1].type, "text",
      "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with empty data after non-empty data");
   is(result[2].type, "compositionend",
      "runNotRedundantChangeTest: compositionend should be fired after synthesizing composition commit with empty data after non-empty data");
   is(result[3].type, "input",
      "runNotRedundantChangeTest: input should be fired after compositionend after synthesizing composition change with empty data after non-empty data");
-  checkInputEvent(result[3], false,
+  checkInputEvent(result[3], false, "insertCompositionText",
                   "runNotRedundantChangeTest: after synthesizing composition change with empty data after non-empty data");
   is(textarea.value, "abcde", "runNotRedundantChangeTest: textarea doesn't have uncommitted string #2");
 
   textarea.removeEventListener("compositionupdate", handler, true);
   textarea.removeEventListener("compositionend", handler, true);
   textarea.removeEventListener("input", handler, true);
   textarea.removeEventListener("text", handler, true);
 }