Bug 1504911 - part 1: Make all "input" event dispatcher in C++ use new utility method r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Wed, 21 Nov 2018 03:59:02 +0000
changeset 503891 48440593d675ccfe5a6893118a7e91cc4b823c3f
parent 503890 d4a142e1648b3e341fb0f486d7b15a0bad7e0904
child 503892 110224ca92565e84894ce093dea626a62f599057
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1504911, 1506439
milestone65.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 1504911 - part 1: Make all "input" event dispatcher in C++ use new utility method r=smaug Currently, a lot of code dispatch "input" event and some of them dispatch "input" event with wrong interface and/or values. Therefore this patch creates nsContentUtils::DispatchInputEvent() to make all of them dispatch correct event. Unfortunately, due to bug 1506439, we cannot set pointer to refcountable classes of MOZ_CAN_RUN_SCRIPT method to nullptr. Therefore, this patch creates temporary RefPtr<TextEditor> a lot even though it makes damage to the performance if it's in a hot path. This patch makes eEditorInput event dispatched with InternalEditorInputEvent when "input" event should be dispatched with dom::InputEvent. However, this patch uses WidgetEvent whose message is eUnidentifiedEvent and setting WidgetEvent::mSpecifiedEventType to nsGkAtoms::oninput when "input" event should be dispatched with dom::Event because we need to keep that eEditorInput and InternalEditorInputEvent are mapped each other. Differential Revision: https://phabricator.services.mozilla.com/D12244
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/html/HTMLInputElement.cpp
dom/html/HTMLInputElement.h
dom/html/HTMLTextAreaElement.cpp
dom/html/HTMLTextAreaElement.h
dom/html/nsITextControlElement.h
dom/html/nsTextEditorState.cpp
dom/html/nsTextEditorState.h
dom/html/test/forms/test_MozEditableElement_setUserInput.html
dom/html/test/forms/test_input_event.html
editor/libeditor/EditorBase.cpp
editor/libeditor/EditorBase.h
editor/libeditor/EditorEventListener.cpp
editor/libeditor/EditorEventListener.h
editor/libeditor/HTMLEditor.cpp
editor/libeditor/HTMLEditor.h
editor/libeditor/TextEditor.cpp
editor/libeditor/TextEditor.h
layout/forms/nsComboboxControlFrame.h
layout/forms/nsFileControlFrame.cpp
layout/forms/nsFileControlFrame.h
layout/forms/nsListControlFrame.cpp
layout/forms/nsListControlFrame.h
toolkit/components/satchel/test/test_submit_on_keydown_enter.html
toolkit/content/tests/chrome/file_editor_with_autocomplete.js
widget/BasicEvents.h
--- a/browser/extensions/formautofill/test/mochitest/formautofill_common.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_common.js
@@ -110,26 +110,24 @@ function triggerAutofillAndCheckProfile(
   const promises = [];
 
   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") {
-          todo(event instanceof InputEvent,
-               `"input" event should be dispatched with InputEvent interface on ${element.tagName}`);
-          todo_is(event.cancelable, false,
-                  `"input" event should be never cancelable on ${element.tagName}`);
+          ok(event instanceof InputEvent,
+             `"input" event should be dispatched with InputEvent interface on ${element.tagName}`);
         } else {
           todo(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.cancelable, false,
+           `"input" event should be never cancelable on ${element.tagName}`);
         is(event.bubbles, true,
            `"input" event should always bubble on ${element.tagName}`);
         resolve();
       }, {once: true})),
       new Promise(resolve => element.addEventListener(expectingEvent, resolve, {once: true})),
     ]).then(() => checkFieldValue(element, value));
 
     promises.push(checkFieldAutofilled);
--- a/browser/extensions/formautofill/test/mochitest/test_clear_form.html
+++ b/browser/extensions/formautofill/test/mochitest/test_clear_form.html
@@ -63,20 +63,20 @@ function checkIsFormCleared(patch = {}) 
     checkFieldPreview(elem, "");
   }
 }
 
 async function confirmClear(selector) {
   info("Await for clearing input");
   let promise = new Promise(resolve =>
     document.querySelector(selector).addEventListener("input", (event) => {
-      todo(event instanceof InputEvent,
-           '"input" event should be dispatched with InputEvent interface');
-      todo_is(event.cancelable, false,
-              '"input" event should be never cancelable');
+      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');
       resolve();
     }, {once: true})
   );
   synthesizeKey("KEY_Enter");
   await promise;
 }
--- 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
@@ -45,26 +45,24 @@ 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") {
-          todo(event instanceof InputEvent,
-               `"input" event should be dispatched with InputEvent interface on ${element.name}`);
-          todo_is(event.cancelable, false,
-                  `"input" event should be never cancelable on ${element.name}`);
+          ok(event instanceof InputEvent,
+             `"input" event should be dispatched with InputEvent interface on ${element.name}`);
         } else {
           todo(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.cancelable, false,
+           `"input" event should be never cancelable on ${element.name}`);
         is(event.bubbles, true,
            `"input" event should always bubble on ${element.name}`);
         resolve();
       }, {once: true});
     }),
     new Promise(resolve => {
       element.addEventListener("change", function onChange() {
         ok(true, "Checking " + element.name + " field fires change event");
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -52,16 +52,17 @@
 #include "mozilla/dom/ElementInlines.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/FileSystemSecurity.h"
 #include "mozilla/dom/FileBlobImpl.h"
 #include "mozilla/dom/FontTableURIProtocolHandler.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "mozilla/dom/HTMLSlotElement.h"
 #include "mozilla/dom/HTMLTemplateElement.h"
+#include "mozilla/dom/HTMLTextAreaElement.h"
 #include "mozilla/dom/IDTracker.h"
 #include "mozilla/dom/MouseEventBinding.h"
 #include "mozilla/dom/KeyboardEventBinding.h"
 #include "mozilla/dom/IPCBlobUtils.h"
 #include "mozilla/dom/NodeBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/TabParent.h"
@@ -80,16 +81,17 @@
 #include "mozilla/Likely.h"
 #include "mozilla/ManualNAC.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ResultExtensions.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPrefs.h"
+#include "mozilla/TextEditor.h"
 #include "mozilla/TextEvents.h"
 #include "nsArrayUtils.h"
 #include "nsAString.h"
 #include "nsAttrName.h"
 #include "nsAttrValue.h"
 #include "nsAttrValueInlines.h"
 #include "nsBindingManager.h"
 #include "nsCanvasFrame.h"
@@ -4362,16 +4364,18 @@ nsresult GetEventAndTarget(nsIDocument* 
 nsresult
 nsContentUtils::DispatchTrustedEvent(nsIDocument* aDoc, nsISupports* aTarget,
                                      const nsAString& aEventName,
                                      CanBubble aCanBubble,
                                      Cancelable aCancelable,
                                      Composed aComposed,
                                      bool* aDefaultAction)
 {
+  MOZ_ASSERT(!aEventName.EqualsLiteral("input"),
+             "Use DispatchInputEvent() instead");
   return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable,
                        aComposed, Trusted::eYes, aDefaultAction);
 }
 
 // static
 nsresult
 nsContentUtils::DispatchUntrustedEvent(nsIDocument* aDoc, nsISupports* aTarget,
                                        const nsAString& aEventName,
@@ -4445,16 +4449,120 @@ nsContentUtils::DispatchEvent(nsIDocumen
   nsresult rv = EventDispatcher::DispatchDOMEvent(target, &aEvent, nullptr,
                                                   nullptr, &status);
   if (aDefaultAction) {
     *aDefaultAction = (status != nsEventStatus_eConsumeNoDefault);
   }
   return rv;
 }
 
+// static
+nsresult
+nsContentUtils::DispatchInputEvent(Element* aEventTargetElement)
+{
+  RefPtr<TextEditor> textEditor; // See bug 1506439
+  return DispatchInputEvent(aEventTargetElement, textEditor);
+}
+
+// static
+nsresult
+nsContentUtils::DispatchInputEvent(Element* aEventTargetElement,
+                                   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
+  // dispatch "input" event for HTMLEditor except HTMLEditor itself.
+  bool useInputEvent = false;
+  if (aTextEditor) {
+    useInputEvent = true;
+  } else if (HTMLTextAreaElement* textAreaElement=
+               HTMLTextAreaElement::FromNode(aEventTargetElement)) {
+    aTextEditor = textAreaElement->GetTextEditorWithoutCreation();
+    useInputEvent = true;
+  } else if (HTMLInputElement* inputElement =
+               HTMLInputElement::FromNode(aEventTargetElement)) {
+    if (inputElement->IsSingleLineTextControl()) {
+      aTextEditor = inputElement->GetTextEditorWithoutCreation();
+      useInputEvent = true;
+    }
+  }
+#ifdef DEBUG
+  else {
+    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) {
+    // 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))->RunDOMEventWhenSafe();
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIWidget> widget;
+  if (aTextEditor) {
+    widget = aTextEditor->GetWidget();
+    if (NS_WARN_IF(!widget)) {
+      return NS_ERROR_FAILURE;
+    }
+  } else {
+    nsIDocument* document = aEventTargetElement->OwnerDoc();
+    if (NS_WARN_IF(!document)) {
+      return NS_ERROR_FAILURE;
+    }
+    // If we're running xpcshell tests, we fail to get presShell here.
+    // Even in such case, we need to dispatch "input" event without widget.
+    nsIPresShell* presShell = document->GetShell();
+    if (presShell) {
+      nsPresContext* presContext = presShell->GetPresContext();
+      if (NS_WARN_IF(!presContext)) {
+        return NS_ERROR_FAILURE;
+      }
+      widget = presContext->GetRootWidget();
+      if (NS_WARN_IF(!widget)) {
+        return NS_ERROR_FAILURE;
+      }
+    }
+  }
+
+  // Dispatch "input" event with InputEvent instance.
+  InternalEditorInputEvent inputEvent(true, eEditorInput, widget);
+
+  // Using same time as old event dispatcher in EditorBase for backward
+  // compatibility.
+  inputEvent.mTime = static_cast<uint64_t>(PR_Now() / 1000);
+
+  // If there is an editor, set isComposing to true when it has composition.
+  // 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;
+
+  (new AsyncEventDispatcher(aEventTargetElement,
+                            inputEvent))->RunDOMEventWhenSafe();
+  return NS_OK;
+}
+
 nsresult
 nsContentUtils::DispatchChromeEvent(nsIDocument *aDoc,
                                     nsISupports *aTarget,
                                     const nsAString& aEventName,
                                     CanBubble aCanBubble,
                                     Cancelable aCancelable,
                                     bool* aDefaultAction)
 {
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -118,16 +118,17 @@ template<class K, class V> class nsDataH
 template<class K, class V> class nsRefPtrHashtable;
 template<class T> class nsReadingIterator;
 
 namespace mozilla {
 class Dispatcher;
 class ErrorResult;
 class EventListenerManager;
 class HTMLEditor;
+class TextEditor;
 
 namespace dom {
 class ContentFrameMessageManager;
 struct CustomElementDefinition;
 class DocumentFragment;
 class Element;
 class Event;
 class EventTarget;
@@ -1372,19 +1373,22 @@ public:
    * If you depend on that you need to hold references yourself.
    *
    * @param aChild    The node to fire DOMNodeRemoved at.
    * @param aParent   The parent of aChild.
    */
   static void MaybeFireNodeRemoved(nsINode* aChild, nsINode* aParent);
 
   /**
-   * This method creates and dispatches a trusted event.
+   * These methods create and dispatch a trusted event.
    * Works only with events which can be created by calling
    * nsIDocument::CreateEvent() with parameter "Events".
+   * Note that don't use these methods for "input" event.  Use
+   * DispatchInputEvent() instead.
+   *
    * @param aDoc           The document which will be used to create the event.
    * @param aTarget        The target of the event, should be QIable to
    *                       EventTarget.
    * @param aEventName     The name of the event.
    * @param aCanBubble     Whether the event can bubble.
    * @param aCancelable    Is the event cancelable.
    * @param aCopmosed      Is the event composed.
    * @param aDefaultAction Set to true if default action should be taken,
@@ -1434,16 +1438,36 @@ public:
     WidgetEventType event(true, aEventMessage);
     MOZ_ASSERT(GetEventClassIDFromMessage(aEventMessage) == event.mClass);
     return DispatchEvent(aDoc, aTarget, event, aEventMessage,
                          aCanBubble, aCancelable, Trusted::eYes,
                          aDefaultAction, aOnlyChromeDispatch);
   }
 
   /**
+   * This method dispatches "input" event with proper event class.  If it's
+   * 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 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::TextEditor* aTextEditor);
+
+  /**
    * This method creates and dispatches a untrusted event.
    * Works only with events which can be created by calling
    * nsIDocument::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
    *                       EventTarget.
    * @param aEventName     The name of the event.
    * @param aCanBubble     Whether the event can bubble.
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -246,26 +246,22 @@ public:
       OwningFileOrDirectory* element = array.AppendElement();
       element->SetAsFile() = aFiles[i];
     }
 
     mInputElement->SetFilesOrDirectories(array, true);
     Unused << NS_WARN_IF(NS_FAILED(DispatchEvents()));
   }
 
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   nsresult
   DispatchEvents()
   {
-    nsresult rv = NS_OK;
-    rv = nsContentUtils::DispatchTrustedEvent(mInputElement->OwnerDoc(),
-                                              static_cast<Element*>(mInputElement.get()),
-                                              NS_LITERAL_STRING("input"),
-                                              CanBubble::eYes,
-                                              Cancelable::eNo);
-    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "DispatchTrustedEvent failed");
+    nsresult rv = nsContentUtils::DispatchInputEvent(mInputElement);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch input event");
 
     rv = nsContentUtils::DispatchTrustedEvent(mInputElement->OwnerDoc(),
                                               static_cast<Element*>(mInputElement.get()),
                                               NS_LITERAL_STRING("change"),
                                               CanBubble::eYes,
                                               Cancelable::eNo);
 
     return rv;
@@ -590,25 +586,28 @@ public:
                              nsIColorPicker* aColorPicker)
     : mInput(aInput)
     , mColorPicker(aColorPicker)
     , mValueChanged(false)
   {}
 
   NS_DECL_ISUPPORTS
 
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   NS_IMETHOD Update(const nsAString& aColor) override;
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   NS_IMETHOD Done(const nsAString& aColor) override;
 
 private:
   /**
    * Updates the internals of the object using aColor as the new value.
    * If aTrustedUpdate is true, it will consider that aColor is a new value.
    * Otherwise, it will check that aColor is different from the current value.
    */
+  MOZ_CAN_RUN_SCRIPT
   nsresult UpdateInternal(const nsAString& aColor, bool aTrustedUpdate);
 
   RefPtr<HTMLInputElement> mInput;
   nsCOMPtr<nsIColorPicker>   mColorPicker;
   bool                       mValueChanged;
 };
 
 nsresult
@@ -629,25 +628,24 @@ nsColorPickerShownCallback::UpdateIntern
   if (!aTrustedUpdate) {
     nsAutoString newValue;
     mInput->GetValue(newValue, CallerType::System);
     if (!oldValue.Equals(newValue)) {
       valueChanged = true;
     }
   }
 
-  if (valueChanged) {
-    mValueChanged = true;
-    return nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
-                                                static_cast<Element*>(mInput.get()),
-                                                NS_LITERAL_STRING("input"),
-                                                CanBubble::eYes,
-                                                Cancelable::eNo);
-  }
-
+  if (!valueChanged) {
+    return NS_OK;
+  }
+
+  mValueChanged = true;
+  DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(mInput);
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                       "Failed to dispatch input event");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsColorPickerShownCallback::Update(const nsAString& aColor)
 {
   return UpdateInternal(aColor, true);
 }
@@ -2348,23 +2346,19 @@ HTMLInputElement::SetUserInput(const nsA
 
   nsresult rv =
     SetValueInternal(aValue,
       nsTextEditorState::eSetValue_BySetUserInput |
       nsTextEditorState::eSetValue_Notify|
       nsTextEditorState::eSetValue_MoveCursorToEndIfValueChanged);
   NS_ENSURE_SUCCESS_VOID(rv);
 
-  // FIXME: We're inconsistent about whether "input" events are cancelable or
-  // not.
-  nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
-                                       static_cast<Element*>(this),
-                                       NS_LITERAL_STRING("input"),
-                                       CanBubble::eYes,
-                                       Cancelable::eYes);
+  DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                       "Failed to dispatch input event");
 
   // If this element is not currently focused, it won't receive a change event for this
   // update through the normal channels. So fire a change event immediately, instead.
   if (!ShouldBlur(this)) {
     FireChangeEventIfNeeded();
   }
 }
 
@@ -2385,16 +2379,26 @@ HTMLInputElement::GetTextEditorFromState
 }
 
 NS_IMETHODIMP_(TextEditor*)
 HTMLInputElement::GetTextEditor()
 {
   return GetTextEditorFromState();
 }
 
+NS_IMETHODIMP_(TextEditor*)
+HTMLInputElement::GetTextEditorWithoutCreation()
+{
+  nsTextEditorState* state = GetEditorState();
+  if (!state) {
+    return nullptr;
+  }
+  return state->GetTextEditorWithoutCreation();
+}
+
 NS_IMETHODIMP_(nsISelectionController*)
 HTMLInputElement::GetSelectionController()
 {
   nsTextEditorState* state = GetEditorState();
   if (state) {
     return state->GetSelectionController();
   }
   return nullptr;
@@ -2730,17 +2734,17 @@ HTMLInputElement::SetFiles(FileList* aFi
 
   // Update |mFileData->mFileList| without copy
   mFileData->mFileList = aFiles;
 }
 
 /* static */ void
 HTMLInputElement::HandleNumberControlSpin(void* aData)
 {
-  HTMLInputElement* input = static_cast<HTMLInputElement*>(aData);
+  RefPtr<HTMLInputElement> input = static_cast<HTMLInputElement*>(aData);
 
   NS_ASSERTION(input->mNumberControlSpinnerIsSpinning,
                "Should have called nsRepeatService::Stop()");
 
   nsNumberControlFrame* numberControlFrame =
     do_QueryFrame(input->GetPrimaryFrame());
   if (input->mType != NS_FORM_INPUT_NUMBER || !numberControlFrame) {
     // Type has changed (and possibly our frame type hasn't been updated yet)
@@ -3745,22 +3749,19 @@ HTMLInputElement::CancelRangeThumbDrag(b
     // TODO: What should we do if SetValueInternal fails?  (The allocation
     // is small, so we should be fine here.)
     SetValueInternal(val, nsTextEditorState::eSetValue_BySetUserInput |
                           nsTextEditorState::eSetValue_Notify);
     nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
     if (frame) {
       frame->UpdateForValueChange();
     }
-    RefPtr<AsyncEventDispatcher> asyncDispatcher =
-      new AsyncEventDispatcher(this,
-                               NS_LITERAL_STRING("input"),
-                               CanBubble::eYes,
-                               ChromeOnlyDispatch::eNo);
-    asyncDispatcher->RunDOMEventWhenSafe();
+    DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                         "Failed to dispatch input event");
   }
 }
 
 void
 HTMLInputElement::SetValueOfRangeForUserEvent(Decimal aValue)
 {
   MOZ_ASSERT(aValue.isFinite());
 
@@ -3773,21 +3774,19 @@ HTMLInputElement::SetValueOfRangeForUser
   SetValueInternal(val, nsTextEditorState::eSetValue_BySetUserInput |
                         nsTextEditorState::eSetValue_Notify);
   nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
   if (frame) {
     frame->UpdateForValueChange();
   }
 
   if (GetValueAsDecimal() != oldValue) {
-    nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
-                                         static_cast<Element*>(this),
-                                         NS_LITERAL_STRING("input"),
-                                         CanBubble::eYes,
-                                         Cancelable::eNo);
+    DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                         "Failed to dispatch input event");
   }
 }
 
 void
 HTMLInputElement::StartNumberControlSpinnerSpin()
 {
   MOZ_ASSERT(!mNumberControlSpinnerIsSpinning);
 
@@ -3872,21 +3871,19 @@ HTMLInputElement::StepNumberControlForUs
 
   nsAutoString newVal;
   mInputType->ConvertNumberToString(newValue, newVal);
   // TODO: What should we do if SetValueInternal fails?  (The allocation
   // is small, so we should be fine here.)
   SetValueInternal(newVal, nsTextEditorState::eSetValue_BySetUserInput |
                            nsTextEditorState::eSetValue_Notify);
 
-  nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
-                                       static_cast<Element*>(this),
-                                       NS_LITERAL_STRING("input"),
-                                       CanBubble::eYes,
-                                       Cancelable::eNo);
+  DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                       "Failed to dispatch input event");
 }
 
 static bool
 SelectTextFieldOnFocus()
 {
   if (!gSelectTextFieldOnFocus) {
     int32_t selectTextfieldsOnKeyFocus = -1;
     nsresult rv =
@@ -4086,19 +4083,19 @@ HTMLInputElement::PostHandleEvent(EventC
       } else if (oldType == NS_FORM_INPUT_CHECKBOX) {
         bool originalIndeterminateValue =
           !!(aVisitor.mItemFlags & NS_ORIGINAL_INDETERMINATE_VALUE);
         SetIndeterminateInternal(originalIndeterminateValue, false);
         DoSetChecked(originalCheckedValue, true, true);
       }
     } else {
       // Fire input event and then change event.
-      nsContentUtils::DispatchTrustedEvent<InternalEditorInputEvent>
-        (OwnerDoc(), static_cast<Element*>(this),
-         eEditorInput, CanBubble::eYes, Cancelable::eNo);
+      DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
+      NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                           "Failed to dispatch input event");
 
       nsContentUtils::DispatchTrustedEvent<WidgetEvent>
         (OwnerDoc(), static_cast<Element*>(this),
          eFormChange, CanBubble::eYes, Cancelable::eNo);
 #ifdef ACCESSIBILITY
       // Fire an event to notify accessibility
       if (mType == NS_FORM_INPUT_CHECKBOX) {
         FireEventForAccessibility(this, aVisitor.mPresContext,
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -182,22 +182,28 @@ public:
                                 nsAttrValue& aResult) override;
   virtual nsChangeHint GetAttributeChangeHint(const nsAtom* aAttribute,
                                               int32_t aModType) const override;
   NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override;
   virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
 
   void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
   virtual nsresult PreHandleEvent(EventChainVisitor& aVisitor) override;
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   virtual nsresult PostHandleEvent(
                      EventChainPostVisitor& aVisitor) override;
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   void PostHandleEventForRangeThumb(EventChainPostVisitor& aVisitor);
+  MOZ_CAN_RUN_SCRIPT
   void StartRangeThumbDrag(WidgetGUIEvent* aEvent);
+  MOZ_CAN_RUN_SCRIPT
   void FinishRangeThumbDrag(WidgetGUIEvent* aEvent = nullptr);
+  MOZ_CAN_RUN_SCRIPT
   void CancelRangeThumbDrag(bool aIsForUserEvent = true);
+  MOZ_CAN_RUN_SCRIPT
   void SetValueOfRangeForUserEvent(Decimal aValue);
 
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent) override;
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true) override;
 
   virtual void DoneCreatingElement() override;
@@ -218,16 +224,17 @@ public:
   NS_IMETHOD_(bool) IsPasswordTextControl() const override;
   NS_IMETHOD_(int32_t) GetCols() override;
   NS_IMETHOD_(int32_t) GetWrapCols() override;
   NS_IMETHOD_(int32_t) GetRows() override;
   NS_IMETHOD_(void) GetDefaultValueFromContent(nsAString& aValue) override;
   NS_IMETHOD_(bool) ValueChanged() const override;
   NS_IMETHOD_(void) GetTextEditorValue(nsAString& aValue, bool aIgnoreWrap) const override;
   NS_IMETHOD_(mozilla::TextEditor*) GetTextEditor() override;
+  NS_IMETHOD_(mozilla::TextEditor*) GetTextEditorWithoutCreation() override;
   NS_IMETHOD_(nsISelectionController*) GetSelectionController() override;
   NS_IMETHOD_(nsFrameSelection*) GetConstFrameSelection() override;
   NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame) override;
   NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override;
   NS_IMETHOD CreateEditor() override;
   NS_IMETHOD_(void) UpdateOverlayTextVisibility(bool aNotify) override;
   NS_IMETHOD_(void) SetPreviewValue(const nsAString& aValue) override;
   NS_IMETHOD_(void) GetPreviewValue(nsAString& aValue) override;
@@ -886,22 +893,24 @@ public:
 
   void StartNumberControlSpinnerSpin();
   enum SpinnerStopState {
     eAllowDispatchingEvents,
     eDisallowDispatchingEvents
   };
   void StopNumberControlSpinnerSpin(SpinnerStopState aState =
                                       eAllowDispatchingEvents);
+  MOZ_CAN_RUN_SCRIPT
   void StepNumberControlForUserEvent(int32_t aDirection);
 
   /**
    * The callback function used by the nsRepeatService that we use to spin the
    * spinner for <input type=number>.
    */
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   static void HandleNumberControlSpin(void* aData);
 
   bool NumberSpinnerUpButtonIsDepressed() const
   {
     return mNumberControlSpinnerIsSpinning && mNumberControlSpinnerSpinsUp;
   }
 
   bool NumberSpinnerDownButtonIsDepressed() const
@@ -911,16 +920,17 @@ public:
 
   bool MozIsTextField(bool aExcludePassword);
 
   /**
    * GetEditor() is for webidl bindings.
    */
   nsIEditor* GetEditor();
 
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   void SetUserInput(const nsAString& aInput,
                     nsIPrincipal& aSubjectPrincipal);
 
   /**
    * If aValue contains a valid floating-point number in the format specified
    * by the HTML 5 spec:
    *
    *   http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#floating-point-numbers
@@ -1049,16 +1059,17 @@ protected:
    * Called when an attribute is about to be changed
    */
   virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
                                  const nsAttrValueOrString* aValue,
                                  bool aNotify) override;
   /**
    * Called when an attribute has just been changed
    */
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
                                 const nsAttrValue* aValue,
                                 const nsAttrValue* aOldValue,
                                 nsIPrincipal* aSubjectPrincipal,
                                 bool aNotify) override;
 
   virtual void BeforeSetForm(bool aBindToTree) override;
 
@@ -1175,16 +1186,17 @@ protected:
   void FreeData();
   nsTextEditorState *GetEditorState() const;
 
   mozilla::TextEditor* GetTextEditorFromState();
 
   /**
    * Manages the internal data storage across type changes.
    */
+  MOZ_CAN_RUN_SCRIPT
   void HandleTypeChange(uint8_t aNewType, bool aNotify);
 
   /**
    * Sanitize the value of the element depending of its current type.
    * See: http://www.whatwg.org/specs/web-apps/current-work/#value-sanitization-algorithm
    */
   void SanitizeValue(nsAString& aValue);
 
--- a/dom/html/HTMLTextAreaElement.cpp
+++ b/dom/html/HTMLTextAreaElement.cpp
@@ -219,16 +219,22 @@ HTMLTextAreaElement::GetValueInternal(ns
 }
 
 NS_IMETHODIMP_(TextEditor*)
 HTMLTextAreaElement::GetTextEditor()
 {
   return mState.GetTextEditor();
 }
 
+NS_IMETHODIMP_(TextEditor*)
+HTMLTextAreaElement::GetTextEditorWithoutCreation()
+{
+  return mState.GetTextEditorWithoutCreation();
+}
+
 NS_IMETHODIMP_(nsISelectionController*)
 HTMLTextAreaElement::GetSelectionController()
 {
   return mState.GetSelectionController();
 }
 
 NS_IMETHODIMP_(nsFrameSelection*)
 HTMLTextAreaElement::GetConstFrameSelection()
--- a/dom/html/HTMLTextAreaElement.h
+++ b/dom/html/HTMLTextAreaElement.h
@@ -78,16 +78,17 @@ public:
   NS_IMETHOD_(bool) IsPasswordTextControl() const override;
   NS_IMETHOD_(int32_t) GetCols() override;
   NS_IMETHOD_(int32_t) GetWrapCols() override;
   NS_IMETHOD_(int32_t) GetRows() override;
   NS_IMETHOD_(void) GetDefaultValueFromContent(nsAString& aValue) override;
   NS_IMETHOD_(bool) ValueChanged() const override;
   NS_IMETHOD_(void) GetTextEditorValue(nsAString& aValue, bool aIgnoreWrap) const override;
   NS_IMETHOD_(mozilla::TextEditor*) GetTextEditor() override;
+  NS_IMETHOD_(mozilla::TextEditor*) GetTextEditorWithoutCreation() override;
   NS_IMETHOD_(nsISelectionController*) GetSelectionController() override;
   NS_IMETHOD_(nsFrameSelection*) GetConstFrameSelection() override;
   NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame) override;
   NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override;
   NS_IMETHOD CreateEditor() override;
   NS_IMETHOD_(void) UpdateOverlayTextVisibility(bool aNotify) override;
   NS_IMETHOD_(bool) GetPlaceholderVisibility() override;
   NS_IMETHOD_(bool) GetPreviewVisibility() override;
--- a/dom/html/nsITextControlElement.h
+++ b/dom/html/nsITextControlElement.h
@@ -97,18 +97,22 @@ public:
    * for the element.
    */
   NS_IMETHOD_(void) GetTextEditorValue(nsAString& aValue, bool aIgnoreWrap) const = 0;
 
   /**
    * Get the editor object associated with the text editor.
    * The return value is null if the control does not support an editor
    * (for example, if it is a checkbox.)
+   * Note that GetTextEditor() creates editor if it hasn't been created yet.
+   * If you need editor only when the editor is there, you should use
+   * GetTextEditorWithoutCreation().
    */
   NS_IMETHOD_(mozilla::TextEditor*) GetTextEditor() = 0;
+  NS_IMETHOD_(mozilla::TextEditor*) GetTextEditorWithoutCreation() = 0;
 
   /**
    * Get the selection controller object associated with the text editor.
    * The return value is null if the control does not support an editor
    * (for example, if it is a checkbox.)
    */
   NS_IMETHOD_(nsISelectionController*) GetSelectionController() = 0;
 
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -1218,16 +1218,22 @@ nsTextEditorState::GetTextEditor()
 {
   if (!mTextEditor) {
     nsresult rv = PrepareEditor();
     NS_ENSURE_SUCCESS(rv, nullptr);
   }
   return mTextEditor;
 }
 
+TextEditor*
+nsTextEditorState::GetTextEditorWithoutCreation()
+{
+  return mTextEditor;
+}
+
 nsISelectionController*
 nsTextEditorState::GetSelectionController() const
 {
   return mSelCon;
 }
 
 // Helper class, used below in BindToFrame().
 class PrepareEditorEvent : public Runnable {
--- a/dom/html/nsTextEditorState.h
+++ b/dom/html/nsTextEditorState.h
@@ -147,16 +147,17 @@ public:
   {
     Unlink();
     mValue.reset();
     mValueBeingSet.Truncate();
     mTextCtrlElement = nullptr;
   }
 
   mozilla::TextEditor* GetTextEditor();
+  mozilla::TextEditor* GetTextEditorWithoutCreation();
   nsISelectionController* GetSelectionController() const;
   nsFrameSelection* GetConstFrameSelection();
   nsresult BindToFrame(nsTextControlFrame* aFrame);
   void UnbindFromFrame(nsTextControlFrame* aFrame);
   nsresult PrepareEditor(const nsAString *aValue = nullptr);
   void InitializeKeyboardEventListeners();
 
   enum SetValueFlags
--- a/dom/html/test/forms/test_MozEditableElement_setUserInput.html
+++ b/dom/html/test/forms/test_MozEditableElement_setUserInput.html
@@ -159,24 +159,29 @@ SimpleTest.waitForFocus(() => {
            `No "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
       }
     } else {
       is(inputEvents.length, 1,
          `Only one "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
     }
     if (inputEvents.length > 0) {
       if (test.result.useInputEvent) {
-        todo(inputEvents[0] instanceof InputEvent,
+        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`);
+        }
       } 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`);
       }
-      todo_is(inputEvents[0].cancelable, false,
-              `"input" event should be never cancelable (${tag}, before getting focus)`);
+      is(inputEvents[0].cancelable, false,
+         `"input" event should be never cancelable (${tag}, before getting focus)`);
       is(inputEvents[0].bubbles, true,
          `"input" event should always bubble (${tag}, before getting focus)`);
     }
 
     inputEvents = [];
     target.focus();
     previousValue = target.value;
     SpecialPowers.wrap(target).setUserInput(test.input.after);
@@ -217,24 +222,29 @@ SimpleTest.waitForFocus(() => {
            `No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
       }
     } else {
       is(inputEvents.length, 1,
          `Only one "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
     }
     if (inputEvents.length > 0) {
       if (test.result.useInputEvent) {
-        todo(inputEvents[0] instanceof InputEvent,
+        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`);
+        }
       } 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`);
       }
-      todo_is(inputEvents[0].cancelable, false,
-              `"input" event should be never cancelable (${tag}, after getting focus)`);
+      is(inputEvents[0].cancelable, false,
+         `"input" event should be never cancelable (${tag}, after getting focus)`);
       is(inputEvents[0].bubbles, true,
          `"input" event should always bubble (${tag}, after getting focus)`);
     }
 
     target.removeEventListener("input", onInput);
   }
 
   SimpleTest.finish();
--- a/dom/html/test/forms/test_input_event.html
+++ b/dom/html/test/forms/test_input_event.html
@@ -52,23 +52,18 @@ https://bugzilla.mozilla.org/show_bug.cg
     }
     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) {
-    if (event.target.type === "checkbox" || event.target.type === "radio") {
-      todo(event instanceof Event && !(event instanceof UIEvent),
-           `"input" event should be dispatched with InputEvent interface ${aDescription}`);
-    } else {
-      ok(event instanceof Event && !(event instanceof UIEvent),
-         `"input" event should be dispatched with InputEvent interface ${aDescription}`);
-    }
+    ok(event instanceof Event && !(event instanceof UIEvent),
+       `"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}`);
   }
 
   var textareaInput = 0;
   document.getElementById("textarea").oninput = (aEvent) => {
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -2137,66 +2137,16 @@ EditorBase::NotifySelectionChanged(nsIDo
   if (mIMEContentObserver) {
     RefPtr<IMEContentObserver> observer = mIMEContentObserver;
     observer->OnSelectionChange(*aSelection);
   }
 
   return NS_OK;
 }
 
-class EditorInputEventDispatcher final : public Runnable
-{
-public:
-  EditorInputEventDispatcher(EditorBase* aEditorBase,
-                             nsIContent* aTarget,
-                             bool aIsComposing)
-    : Runnable("EditorInputEventDispatcher")
-    , mEditorBase(aEditorBase)
-    , mTarget(aTarget)
-    , mIsComposing(aIsComposing)
-  {
-  }
-
-  NS_IMETHOD Run() override
-  {
-    // Note that we don't need to check mDispatchInputEvent here.  We need
-    // to check it only when the editor requests to dispatch the input event.
-
-    if (!mTarget->IsInComposedDoc()) {
-      return NS_OK;
-    }
-
-    nsCOMPtr<nsIPresShell> ps = mEditorBase->GetPresShell();
-    if (!ps) {
-      return NS_OK;
-    }
-
-    nsCOMPtr<nsIWidget> widget = mEditorBase->GetWidget();
-    if (!widget) {
-      return NS_OK;
-    }
-
-    // Even if the change is caused by untrusted event, we need to dispatch
-    // trusted input event since it's a fact.
-    InternalEditorInputEvent inputEvent(true, eEditorInput, widget);
-    inputEvent.mTime = static_cast<uint64_t>(PR_Now() / 1000);
-    inputEvent.mIsComposing = mIsComposing;
-    nsEventStatus status = nsEventStatus_eIgnore;
-    nsresult rv =
-      ps->HandleEventWithTarget(&inputEvent, nullptr, mTarget, &status);
-    NS_ENSURE_SUCCESS(rv, NS_OK); // print the warning if error
-    return NS_OK;
-  }
-
-private:
-  RefPtr<EditorBase> mEditorBase;
-  nsCOMPtr<nsIContent> mTarget;
-  bool mIsComposing;
-};
-
 void
 EditorBase::NotifyEditorObservers(NotificationForEditorObservers aNotification)
 {
   switch (aNotification) {
     case eNotifyEditorObserversOfEnd:
       mIsInEditSubAction = false;
 
       if (mTextInputListener) {
@@ -2247,29 +2197,25 @@ EditorBase::NotifyEditorObservers(Notifi
       MOZ_CRASH("Handle all notifications here");
       break;
   }
 }
 
 void
 EditorBase::FireInputEvent()
 {
-  // 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.
-
-  nsCOMPtr<nsIContent> target = GetInputEventTargetContent();
-  NS_ENSURE_TRUE_VOID(target);
-
-  // NOTE: Don't refer IsIMEComposing() because it returns false even before
-  //       compositionend.  However, DOM Level 3 Events defines it should be
-  //       true after compositionstart and before compositionend.
-  nsContentUtils::AddScriptRunner(
-    new EditorInputEventDispatcher(this, target, !!GetComposition()));
+  RefPtr<Element> targetElement = GetInputEventTargetElement();
+  if (NS_WARN_IF(!targetElement)) {
+    return;
+  }
+  RefPtr<TextEditor> textEditor = AsTextEditor();
+  DebugOnly<nsresult> rvIgnored =
+    nsContentUtils::DispatchInputEvent(targetElement, 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);
 
   // If given edit action listener is text services document for the inline
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -351,27 +351,29 @@ public:
    * For example, the editor instance, the widget or the process itself may
    * be destroyed.
    */
   nsresult CommitComposition();
 
   /**
    * ToggleTextDirection() toggles text-direction of the root element.
    */
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   nsresult ToggleTextDirection();
 
   /**
    * SwitchTextDirectionTo() sets the text-direction of the root element to
    * LTR or RTL.
    */
   enum class TextDirection
   {
     eLTR,
     eRTL,
   };
+  MOZ_CAN_RUN_SCRIPT
   void SwitchTextDirectionTo(TextDirection aTextDirection);
 
   /**
    * Finalizes selection and caret for the editor.
    */
   nsresult FinalizeSelection();
 
   /**
@@ -1727,17 +1729,19 @@ protected: // Called by helper classes.
    * (Begin|End)PlaceholderTransaction() are called by AutoPlaceholderBatch.
    * This set of methods are similar to the (Begin|End)Transaction(), but do
    * not use the transaction managers batching feature.  Instead we use a
    * placeholder transaction to wrap up any further transaction while the
    * batch is open.  The advantage of this is that placeholder transactions
    * can later merge, if needed.  Merging is unavailable between transaction
    * manager batches.
    */
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   void BeginPlaceholderTransaction(nsAtom* aTransactionName);
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   void EndPlaceholderTransaction();
 
   void BeginUpdateViewBatch();
   void EndUpdateViewBatch();
 
   /**
    * Used by AutoTransactionBatch.  After calling BeginTransactionInternal(),
    * all transactions will be treated as an atomic transaction.  I.e.,
@@ -1763,16 +1767,18 @@ protected: // Shouldn't be used by frien
   /**
    * 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();
+
+  MOZ_CAN_RUN_SCRIPT
   void FireInputEvent();
 
   /**
    * Called after a transaction is done successfully.
    */
   void DoAfterDoTransaction(nsITransaction *aTxn);
 
   /**
@@ -1883,17 +1889,17 @@ protected: // Shouldn't be used by frien
 
   virtual nsresult InstallEventListeners();
   virtual void CreateEventListeners();
   virtual void RemoveEventListeners();
 
   /**
    * Get the input event target. This might return null.
    */
-  virtual already_AddRefed<nsIContent> GetInputEventTargetContent() = 0;
+  virtual already_AddRefed<Element> GetInputEventTargetElement() = 0;
 
   /**
    * Return true if spellchecking should be enabled for this editor.
    */
   bool GetDesiredSpellCheckState();
 
   bool CanEnableSpellCheck()
   {
@@ -1945,16 +1951,17 @@ protected: // Shouldn't be used by frien
   nsresult InitializeSelection(dom::EventTarget* aFocusEventTarget);
 
   enum NotificationForEditorObservers
   {
     eNotifyEditorObserversOfEnd,
     eNotifyEditorObserversOfBefore,
     eNotifyEditorObserversOfCancel
   };
+  MOZ_CAN_RUN_SCRIPT
   void NotifyEditorObservers(NotificationForEditorObservers aNotification);
 
 private:
   nsCOMPtr<nsISelectionController> mSelectionController;
   nsCOMPtr<nsIDocument> mDocument;
 
   AutoEditActionDataSetter* mEditActionData;
 
--- a/editor/libeditor/EditorEventListener.cpp
+++ b/editor/libeditor/EditorEventListener.cpp
@@ -1026,17 +1026,17 @@ EditorEventListener::HandleChangeComposi
     return NS_OK;
   }
 
   // if we are readonly or disabled, then do nothing.
   if (editorBase->IsReadonly() || editorBase->IsDisabled()) {
     return NS_OK;
   }
 
-  TextEditor* textEditor = editorBase->AsTextEditor();
+  RefPtr<TextEditor> textEditor = editorBase->AsTextEditor();
   return textEditor->OnCompositionChange(*aCompositionChangeEvent);
 }
 
 void
 EditorEventListener::HandleEndComposition(
                        WidgetCompositionEvent* aCompositionEndEvent)
 {
   if (NS_WARN_IF(!aCompositionEndEvent)) {
@@ -1045,17 +1045,17 @@ EditorEventListener::HandleEndCompositio
   RefPtr<EditorBase> editorBase(mEditorBase);
   if (DetachedFromEditor() ||
       !editorBase->IsAcceptableInputEvent(aCompositionEndEvent)) {
     return;
   }
   MOZ_ASSERT(!aCompositionEndEvent->DefaultPrevented(),
              "eCompositionEnd shouldn't be cancelable");
 
-  TextEditor* textEditor = editorBase->AsTextEditor();
+  RefPtr<TextEditor> textEditor = editorBase->AsTextEditor();
   textEditor->OnCompositionEnd(*aCompositionEndEvent);
 }
 
 nsresult
 EditorEventListener::Focus(InternalFocusEvent* aFocusEvent)
 {
   if (NS_WARN_IF(!aFocusEvent) || DetachedFromEditor()) {
     return NS_OK;
--- a/editor/libeditor/EditorEventListener.h
+++ b/editor/libeditor/EditorEventListener.h
@@ -58,21 +58,24 @@ public:
 protected:
   virtual ~EditorEventListener();
 
   nsresult InstallToEditor();
   void UninstallFromEditor();
 
 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
   nsresult KeyDown(const WidgetKeyboardEvent* aKeyboardEvent);
+  MOZ_CAN_RUN_SCRIPT
   nsresult KeyUp(const WidgetKeyboardEvent* aKeyboardEvent);
 #endif
   nsresult KeyPress(WidgetKeyboardEvent* aKeyboardEvent);
+  MOZ_CAN_RUN_SCRIPT
   nsresult HandleChangeComposition(WidgetCompositionEvent* aCompositionEvent);
   nsresult HandleStartComposition(WidgetCompositionEvent* aCompositionEvent);
+  MOZ_CAN_RUN_SCRIPT
   void HandleEndComposition(WidgetCompositionEvent* aCompositionEvent);
   MOZ_CAN_RUN_SCRIPT
   virtual nsresult MouseDown(dom::MouseEvent* aMouseEvent);
   virtual nsresult MouseUp(dom::MouseEvent* aMouseEvent) { return NS_OK; }
   MOZ_CAN_RUN_SCRIPT
   virtual nsresult MouseClick(WidgetMouseEvent* aMouseClickEvent);
   nsresult Focus(InternalFocusEvent* aFocusEvent);
   nsresult Blur(InternalFocusEvent* aBlurEvent);
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -5449,20 +5449,20 @@ HTMLEditor::GetPreferredIMEState(IMEStat
   if (IsReadonly() || IsDisabled()) {
     aState->mEnabled = IMEState::DISABLED;
   } else {
     aState->mEnabled = IMEState::ENABLED;
   }
   return NS_OK;
 }
 
-already_AddRefed<nsIContent>
-HTMLEditor::GetInputEventTargetContent()
+already_AddRefed<Element>
+HTMLEditor::GetInputEventTargetElement()
 {
-  nsCOMPtr<nsIContent> target = GetActiveEditingHost();
+  RefPtr<Element> target = GetActiveEditingHost();
   return target.forget();
 }
 
 Element*
 HTMLEditor::GetEditorRoot() const
 {
   return GetActiveEditingHost();
 }
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -1773,17 +1773,17 @@ protected: // Shouldn't be used by frien
 
   /**
    * Get the focused node of this editor.
    * @return    If the editor has focus, this returns the focused node.
    *            Otherwise, returns null.
    */
   already_AddRefed<nsINode> GetFocusedNode();
 
-  virtual already_AddRefed<nsIContent> GetInputEventTargetContent() override;
+  virtual already_AddRefed<Element> GetInputEventTargetElement() override;
 
   /**
    * Return TRUE if aElement is a table-related elemet and caret was set.
    */
   bool SetCaretInTableCell(dom::Element* aElement);
 
   nsresult TabInTable(bool inIsShift, bool* outHandled);
 
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -1455,20 +1455,20 @@ TextEditor::OnCompositionEnd(WidgetCompo
   //      soon since its Destroy() will be called by IMEStateManager.
   mComposition->EndHandlingComposition(this);
   mComposition = nullptr;
 
   // notify editor observers of action
   NotifyEditorObservers(eNotifyEditorObserversOfEnd);
 }
 
-already_AddRefed<nsIContent>
-TextEditor::GetInputEventTargetContent()
+already_AddRefed<Element>
+TextEditor::GetInputEventTargetElement()
 {
-  nsCOMPtr<nsIContent> target = do_QueryInterface(mEventTarget);
+  nsCOMPtr<Element> target = do_QueryInterface(mEventTarget);
   return target.forget();
 }
 
 nsresult
 TextEditor::IsEmpty(bool* aIsEmpty) const
 {
   if (NS_WARN_IF(!mRules)) {
     return NS_ERROR_NOT_INITIALIZED;
--- a/editor/libeditor/TextEditor.h
+++ b/editor/libeditor/TextEditor.h
@@ -59,17 +59,19 @@ public:
 
   NS_IMETHOD DeleteSelection(EDirection aAction,
                              EStripWrappers aStripWrappers) override;
 
   NS_IMETHOD SetDocumentCharacterSet(const nsACString& characterSet) override;
 
   // If there are some good name to create non-virtual Undo()/Redo() methods,
   // we should create them and those methods should just run them.
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   NS_IMETHOD Undo(uint32_t aCount) final;
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   NS_IMETHOD Redo(uint32_t aCount) final;
 
   NS_IMETHOD Cut() override;
   NS_IMETHOD CanCut(bool* aCanCut) override;
   NS_IMETHOD Copy() override;
   NS_IMETHOD CanCopy(bool* aCanCopy) override;
   NS_IMETHOD CanDelete(bool* aCanDelete) override;
   NS_IMETHOD CanPaste(int32_t aSelectionType, bool* aCanPaste) override;
@@ -200,24 +202,26 @@ public:
 
   /**
    * OnCompositionChange() is called when editor receives an eCompositioChange
    * event which should be handled in this editor.
    *
    * @param aCompositionChangeEvent     eCompositionChange event which should
    *                                    be handled in this editor.
    */
+  MOZ_CAN_RUN_SCRIPT
   nsresult
   OnCompositionChange(WidgetCompositionEvent& aCompositionChangeEvent);
 
   /**
    * OnCompositionEnd() is called when editor receives an eCompositionChange
    * event and it's followed by eCompositionEnd event and after
    * OnCompositionChange() is called.
    */
+  MOZ_CAN_RUN_SCRIPT
   void OnCompositionEnd(WidgetCompositionEvent& aCompositionEndEvent);
 
   /**
    * OnDrop() is called from EditorEventListener::Drop that is handler of drop
    * event.
    */
   MOZ_CAN_RUN_SCRIPT
   nsresult OnDrop(dom::DragEvent* aDropEvent);
@@ -525,17 +529,17 @@ protected: // Shouldn't be used by frien
    * the event comes to the editor.
    *
    * @return            true if there is a composition.  Otherwise, for example,
    *                    a composition event handler in web contents moved focus
    *                    for committing the composition, returns false.
    */
   bool EnsureComposition(WidgetCompositionEvent& aCompositionEvent);
 
-  virtual already_AddRefed<nsIContent> GetInputEventTargetContent() override;
+  virtual already_AddRefed<Element> GetInputEventTargetElement() override;
 
 protected:
   mutable nsCOMPtr<nsIDocumentEncoder> mCachedDocumentEncoder;
   mutable nsString mCachedDocumentEncoderType;
   int32_t mWrapColumn;
   int32_t mMaxTextLength;
   int32_t mInitTriggerCounter;
   int32_t mNewlineHandling;
--- a/layout/forms/nsComboboxControlFrame.h
+++ b/layout/forms/nsComboboxControlFrame.h
@@ -123,16 +123,17 @@ public:
   /**
    * Inform the control that it got (or lost) focus.
    * If it lost focus, the dropdown menu will be rolled up if needed,
    * and FireOnChange() will be called.
    * @param aOn true if got focus, false if lost focus.
    * @param aRepaint if true then force repaint (NOTE: we always force repaint currently)
    * @note This method might destroy |this|.
    */
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   virtual void SetFocus(bool aOn, bool aRepaint) override;
 
   bool IsDroppedDown() { return mDroppedDown; }
   /**
    * @note This method might destroy |this|.
    */
   void ShowDropDown(bool aDoDropDown);
   nsIFrame* GetDropDown();
--- a/layout/forms/nsFileControlFrame.cpp
+++ b/layout/forms/nsFileControlFrame.cpp
@@ -454,21 +454,21 @@ nsFileControlFrame::DnDListener::HandleE
       else if (dirPickerEnabled) {
         inputElement->SetFilesOrDirectories(array, true);
       }
       // Normal DnD
       else {
         inputElement->SetFiles(fileList, true);
       }
 
-      nsContentUtils::DispatchTrustedEvent(inputElement->OwnerDoc(),
-                                           static_cast<nsINode*>(inputElement),
-                                           NS_LITERAL_STRING("input"),
-                                           CanBubble::eYes,
-                                           Cancelable::eNo);
+      RefPtr<TextEditor> textEditor;
+      DebugOnly<nsresult> rvIgnored =
+        nsContentUtils::DispatchInputEvent(inputElement);
+      NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                           "Failed to dispatch input event");
       nsContentUtils::DispatchTrustedEvent(inputElement->OwnerDoc(),
                                            static_cast<nsINode*>(inputElement),
                                            NS_LITERAL_STRING("change"),
                                            CanBubble::eYes,
                                            Cancelable::eNo);
     }
   }
 
--- a/layout/forms/nsFileControlFrame.h
+++ b/layout/forms/nsFileControlFrame.h
@@ -118,17 +118,19 @@ protected:
   };
 
   class DnDListener: public MouseListener {
   public:
     explicit DnDListener(nsFileControlFrame* aFrame)
       : MouseListener(aFrame)
     {}
 
-    NS_DECL_NSIDOMEVENTLISTENER
+    // nsIDOMEventListener
+    MOZ_CAN_RUN_SCRIPT_BOUNDARY
+    NS_IMETHOD HandleEvent(mozilla::dom::Event* aEvent) override;
 
     nsresult GetBlobImplForWebkitDirectory(mozilla::dom::FileList* aFileList,
                                            mozilla::dom::BlobImpl** aBlobImpl);
 
     bool IsValidDropData(mozilla::dom::DataTransfer* aDataTransfer);
     bool CanDropTheseFiles(mozilla::dom::DataTransfer* aDataTransfer,
                            bool aSupportsMultiple);
   };
--- a/layout/forms/nsListControlFrame.cpp
+++ b/layout/forms/nsListControlFrame.cpp
@@ -65,17 +65,20 @@ class nsListEventListener final : public
 {
 public:
   explicit nsListEventListener(nsListControlFrame *aFrame)
     : mFrame(aFrame) { }
 
   void SetFrame(nsListControlFrame *aFrame) { mFrame = aFrame; }
 
   NS_DECL_ISUPPORTS
-  NS_DECL_NSIDOMEVENTLISTENER
+
+  // nsIDOMEventListener
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
+  NS_IMETHOD HandleEvent(Event* aEvent) override;
 
 private:
   ~nsListEventListener() {}
 
   nsListControlFrame  *mFrame;
 };
 
 //---------------------------------------------------------
@@ -1385,24 +1388,27 @@ nsListControlFrame::FireOnInputAndOnChan
     }
 
     // See if the selection actually changed
     if (index == GetSelectedIndex()) {
       return;
     }
   }
 
-  nsCOMPtr<nsIContent> content = mContent;
+  RefPtr<Element> element = Element::FromNodeOrNull(mContent);
+  if (NS_WARN_IF(!element)) {
+    return;
+  }
   // Dispatch the input event.
-  nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
-                                       NS_LITERAL_STRING("input"),
-                                       CanBubble::eYes,
-                                       Cancelable::eNo);
+  DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(element);
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                       "Failed to dispatch input event");
+
   // Dispatch the change event.
-  nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
+  nsContentUtils::DispatchTrustedEvent(element->OwnerDoc(), element,
                                        NS_LITERAL_STRING("change"),
                                        CanBubble::eYes,
                                        Cancelable::eNo);
 }
 
 NS_IMETHODIMP
 nsListControlFrame::OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex)
 {
--- a/layout/forms/nsListControlFrame.h
+++ b/layout/forms/nsListControlFrame.h
@@ -129,16 +129,17 @@ public:
    * @note This method might destroy the frame, pres shell and other objects.
    */
   void AboutToRollup();
 
   /**
    * Dispatch a DOM oninput and onchange event synchroniously.
    * @note This method might destroy the frame, pres shell and other objects.
    */
+  MOZ_CAN_RUN_SCRIPT
   void FireOnInputAndOnChange();
 
   /**
    * Makes aIndex the selected option of a combobox list.
    * @note This method might destroy the frame, pres shell and other objects.
    */
   void ComboboxFinish(int32_t aIndex);
   void OnContentReset();
@@ -155,20 +156,23 @@ public:
   NS_IMETHOD OnOptionSelected(int32_t aIndex, bool aSelected) override;
   NS_IMETHOD OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex) override;
 
   /**
    * Mouse event listeners.
    * @note These methods might destroy the frame, pres shell and other objects.
    */
   nsresult MouseDown(mozilla::dom::Event* aMouseEvent);
+  MOZ_CAN_RUN_SCRIPT
   nsresult MouseUp(mozilla::dom::Event* aMouseEvent);
   nsresult MouseMove(mozilla::dom::Event* aMouseEvent);
   nsresult DragMove(mozilla::dom::Event* aMouseEvent);
+  MOZ_CAN_RUN_SCRIPT
   nsresult KeyDown(mozilla::dom::Event* aKeyEvent);
+  MOZ_CAN_RUN_SCRIPT
   nsresult KeyPress(mozilla::dom::Event* aKeyEvent);
 
   /**
    * Returns the options collection for mContent, if any.
    */
   mozilla::dom::HTMLOptionsCollection* GetOptions() const;
   /**
    * Returns the HTMLOptionElement for a given index in mContent's collection.
@@ -252,30 +256,32 @@ protected:
   HTMLOptionElement* GetNonDisabledOptionFrom(int32_t aFromIndex,
                                               int32_t* aFoundIndex = nullptr);
 
   /**
    * Updates the selected text in a combobox and then calls FireOnChange().
    * @note This method might destroy the frame, pres shell and other objects.
    * Returns false if calling it destroyed |this|.
    */
+  MOZ_CAN_RUN_SCRIPT
   bool UpdateSelection();
 
   /**
    * Returns whether mContent supports multiple selection.
    */
   bool GetMultiple() const {
     return mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple);
   }
 
 
   /**
    * Toggles (show/hide) the combobox dropdown menu.
    * @note This method might destroy the frame, pres shell and other objects.
    */
+  MOZ_CAN_RUN_SCRIPT
   void DropDownToggleKey(mozilla::dom::Event* aKeyEvent);
 
   /**
    * @return true if the <option> at aIndex is selectable by the user.
    */
   bool IsOptionInteractivelySelectable(int32_t aIndex) const;
   /**
    * @return true if aOption in aSelect is selectable by the user.
@@ -373,16 +379,17 @@ protected:
   bool     PerformSelection(int32_t aClickedIndex, bool aIsShift,
                             bool aIsControl);
   /**
    * @note This method might destroy the frame, pres shell and other objects.
    */
   bool     HandleListSelection(mozilla::dom::Event * aDOMEvent,
                                int32_t selectedIndex);
   void     InitSelectionRange(int32_t aClickedIndex);
+  MOZ_CAN_RUN_SCRIPT
   void     PostHandleKeyEvent(int32_t aNewIndex, uint32_t aCharCode,
                               bool aIsShift, bool aIsControlOrMeta);
 
 public:
   nsSelectsAreaFrame* GetOptionsContainer() const {
     return static_cast<nsSelectsAreaFrame*>(GetScrolledFrame());
   }
 
--- a/toolkit/components/satchel/test/test_submit_on_keydown_enter.html
+++ b/toolkit/components/satchel/test/test_submit_on_keydown_enter.html
@@ -30,20 +30,20 @@ function handleSubmit() { // eslint-disa
   ok(false, "The form should not be submitted");
   input.removeEventListener("input", handleInput, true);
   SimpleTest.finish();
 }
 
 function handleInput(aEvent) {
   info("Input");
   is(input.value, expectedValue, "Check input value");
-  todo(aEvent instanceof InputEvent,
-       '"input" event should be dispatched with InputEvent interface');
-  todo_is(aEvent.cancelable, false,
-          '"input" event should be never cancelable');
+  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');
   input.removeEventListener("input", handleInput, true);
   SimpleTest.finish();
 }
 
 function runTest() {
   input.addEventListener("input", handleInput, true);
--- a/toolkit/content/tests/chrome/file_editor_with_autocomplete.js
+++ b/toolkit/content/tests/chrome/file_editor_with_autocomplete.js
@@ -90,29 +90,22 @@ nsDoTestsForEditorWithAutoComplete.proto
       if (aTest.inputEvents[i] === undefined) {
         this._is(true, false,
                  this._description + ", " + aTest.description + ": \"input\" event shouldn't be dispatched anymore");
         return;
       }
       if (aTest.inputEvents[i].todoInterfaceOnXUL && aInputEvents[i].target.tagName === "textbox") {
         this._todo_is(aInputEvents[i] instanceof this._window.InputEvent, true,
                       this._description + ", " + aTest.description + ': "input" event should be dispatched with InputEvent interface');
-        this._is(aInputEvents[i].cancelable, false,
-                 this._description + ", " + aTest.description + ': "input" event should be never cancelable');
-      } else if (aTest.inputEvents[i].inputType === "insertReplacementText") {
-        this._todo_is(aInputEvents[i] instanceof this._window.InputEvent, true,
-                      this._description + ", " + aTest.description + ': "input" event should be dispatched with InputEvent interface');
-        this._todo_is(aInputEvents[i].cancelable, false,
-                      this._description + ", " + aTest.description + ': "input" event should be never cancelable');
       } else {
         this._is(aInputEvents[i] instanceof this._window.InputEvent, true,
                  this._description + ", " + aTest.description + ': "input" event should be dispatched with InputEvent interface');
-        this._is(aInputEvents[i].cancelable, false,
-                 this._description + ", " + aTest.description + ': "input" event should be never cancelable');
       }
+      this._is(aInputEvents[i].cancelable, false,
+               this._description + ", " + aTest.description + ': "input" event should be never cancelable');
       this._is(aInputEvents[i].bubbles, true,
                this._description + ", " + aTest.description + ': "input" event should always bubble');
     }
   },
 
   _tests: [
     { description: "Undo/Redo behavior check when typed text exactly matches the case: type 'Mo'",
       completeDefaultIndex: false,
--- a/widget/BasicEvents.h
+++ b/widget/BasicEvents.h
@@ -505,17 +505,18 @@ private:
       case eCompositionEventClass:
         // XXX compositionstart is cancelable in draft of DOM3 Events.
         //     However, it doesn't make sense for us, we cannot cancel
         //     composition when we send compositionstart event.
         mFlags.mCancelable = false;
         mFlags.mBubbles = true;
         break;
       default:
-        if (mMessage == eResize) {
+        if (mMessage == eResize ||
+            mMessage == eEditorInput) {
           mFlags.mCancelable = false;
         } else {
           mFlags.mCancelable = true;
         }
         mFlags.mBubbles = true;
         break;
     }
   }
@@ -609,17 +610,18 @@ public:
   // by APZ. This is used to track when that event has been processed by content,
   // and focus can be reconfirmed for async keyboard scrolling.
   uint64_t mFocusSequenceNumber;
   // See BaseEventFlags definition for the detail.
   BaseEventFlags mFlags;
 
   // If JS creates an event with unknown event type or known event type but
   // for different event interface, the event type is stored to this.
-  // NOTE: This is always used if the instance is a WidgetCommandEvent instance.
+  // NOTE: This is always used if the instance is a WidgetCommandEvent instance
+  //       or "input" event is dispatched with dom::Event class.
   RefPtr<nsAtom> mSpecifiedEventType;
 
   // nsAtom isn't available on non-main thread due to unsafe.  Therefore,
   // mSpecifiedEventTypeString is used instead of mSpecifiedEventType if
   // the event is created in non-main thread.
   nsString mSpecifiedEventTypeString;
 
   // Event targets, needed by DOM Events