Bug 1504911 - part 2: Make nsTextEditorState::SetValue() dispatch "input" event if it's called for handling part of user input r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 20 Nov 2018 22:06:37 +0000
changeset 503892 110224ca92565e84894ce093dea626a62f599057
parent 503891 48440593d675ccfe5a6893118a7e91cc4b823c3f
child 503893 7f09364736a089ada819bd73b5e7e28a4f048b3b
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
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 2: Make nsTextEditorState::SetValue() dispatch "input" event if it's called for handling part of user input r=smaug When editor is modified as part of user action, aFlags of nsTextEditorState::SetValue() includes eSetValue_BySetUserInput. In this case, TextEditor (if there is) or the method itself (if there is no editor yet) should dispatch "input" event by themselves because we will need to initialize InputEvents more since we're going to implement Input Event specs. Note that even with this patch, password field stops dispatching "input" event with call of HTMLInputElement::SetUserInput(). This is caused by a hidden bug of TextEditRules. This will be fixed in a following patch. Differential Revision: https://phabricator.services.mozilla.com/D12245
dom/html/HTMLInputElement.cpp
dom/html/HTMLInputElement.h
dom/html/HTMLTextAreaElement.h
dom/html/input/InputType.cpp
dom/html/input/InputType.h
dom/html/input/NumericInputTypes.h
dom/html/nsTextEditorState.cpp
dom/html/nsTextEditorState.h
dom/html/test/forms/test_MozEditableElement_setUserInput.html
toolkit/content/tests/chrome/file_editor_with_autocomplete.js
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -2339,26 +2339,32 @@ HTMLInputElement::SetUserInput(const nsA
     if (!list.AppendElement(aValue, fallible)) {
       return;
     }
 
     MozSetFileNameArray(list, IgnoreErrors());
     return;
   }
 
+  bool isInputEventDispatchedByTextEditorState =
+    GetValueMode() == VALUE_MODE_VALUE &&
+    IsSingleLineTextControl(false);
+
   nsresult rv =
     SetValueInternal(aValue,
       nsTextEditorState::eSetValue_BySetUserInput |
       nsTextEditorState::eSetValue_Notify|
       nsTextEditorState::eSetValue_MoveCursorToEndIfValueChanged);
   NS_ENSURE_SUCCESS_VOID(rv);
 
-  DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
-  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
-                       "Failed to dispatch input event");
+  if (!isInputEventDispatchedByTextEditorState) {
+    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();
   }
 }
 
@@ -2805,16 +2811,21 @@ HTMLInputElement::SetValueInternal(const
       // else DoneCreatingElement calls us again once mDoneCreating is true
 
       bool setValueChanged = !!(aFlags & nsTextEditorState::eSetValue_Notify);
       if (setValueChanged) {
         SetValueChanged(true);
       }
 
       if (IsSingleLineTextControl(false)) {
+        // Note that if aFlags includes
+        // nsTextEditorState::eSetValue_BySetUserInput, "input" event is
+        // automatically dispatched by nsTextEditorState::SetValue().
+        // If you'd change condition of calling this method, you need to
+        // maintain SetUserInput() too.
         if (!mInputData.mState->SetValue(value, aOldValue, aFlags)) {
           return NS_ERROR_OUT_OF_MEMORY;
         }
         if (mType == NS_FORM_INPUT_EMAIL) {
           UpdateAllValidityStates(!mDoneCreating);
         }
       } else {
         free(mInputData.mValue);
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -158,19 +158,21 @@ public:
 
   // Element
   virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override;
 
   // EventTarget
   virtual void AsyncEventRunning(AsyncEventDispatcher* aEvent) override;
 
   // Overriden nsIFormControl methods
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   NS_IMETHOD Reset() override;
   NS_IMETHOD SubmitNamesValues(HTMLFormSubmission* aFormSubmission) override;
   NS_IMETHOD SaveState() override;
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   virtual bool RestoreState(PresState* aState) override;
   virtual bool AllowDrop() override;
   virtual bool IsDisabledForEvents(WidgetEvent* aEvent) override;
 
   virtual void FieldSetDisabledChanged(bool aNotify) override;
 
   // nsIContent
   virtual bool IsHTMLFocusable(bool aWithMouse, bool *aIsFocusable, int32_t *aTabIndex) override;
@@ -181,16 +183,17 @@ public:
                                 nsIPrincipal* aMaybeScriptedPrincipal,
                                 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;
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   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);
@@ -201,16 +204,17 @@ public:
   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;
 
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   virtual void DoneCreatingElement() override;
 
   virtual EventStates IntrinsicState() const override;
 
   // Element
 private:
   virtual void AddStates(EventStates aStates) override;
   virtual void RemoveStates(EventStates aStates) override;
@@ -228,28 +232,31 @@ public:
   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;
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override;
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   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;
   NS_IMETHOD_(void) EnablePreview() override;
   NS_IMETHOD_(bool) IsPreviewEnabled() override;
   NS_IMETHOD_(bool) GetPlaceholderVisibility() override;
   NS_IMETHOD_(bool) GetPreviewVisibility() override;
   NS_IMETHOD_(void) InitializeKeyboardEventListeners() override;
   NS_IMETHOD_(void) OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) override;
   virtual void GetValueFromSetRangeText(nsAString& aValue) override;
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   virtual nsresult SetValueFromSetRangeText(const nsAString& aValue) override;
   NS_IMETHOD_(bool) HasCachedSelection() override;
 
   // Methods for nsFormFillController so it can do selection operations on input
   // types the HTML spec doesn't support them on, like "email".
   uint32_t GetSelectionStartIgnoringType(ErrorResult& aRv);
   uint32_t GetSelectionEndIgnoringType(ErrorResult& aRv);
 
@@ -279,16 +286,17 @@ public:
    * Helper function returning the currently selected button in the radio group.
    * Returning null if the element is not a button or if there is no selectied
    * button in the group.
    *
    * @return the selected button (or null).
    */
   HTMLInputElement* GetSelectedRadioButton() const;
 
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   virtual nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLInputElement,
                                            nsGenericHTMLFormElementWithState)
 
   static UploadLastDir* gUploadLastDir;
   // create and destroy the static UploadLastDir object for remembering
   // which directory was last used on a site-by-site basis
@@ -719,16 +727,17 @@ public:
   {
     GetHTMLAttr(nsGkAtoms::value, aValue);
   }
   void SetDefaultValue(const nsAString& aValue, ErrorResult& aRv)
   {
     SetHTMLAttr(nsGkAtoms::value, aValue, aRv);
   }
 
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   void SetValue(const nsAString& aValue, CallerType aCallerType,
                 ErrorResult& aRv);
   void GetValue(nsAString& aValue, CallerType aCallerType);
 
   Nullable<Date> GetValueAsDate(ErrorResult& aRv);
 
   void SetValueAsDate(const Nullable<Date>& aDate, ErrorResult& aRv);
 
@@ -1014,20 +1023,22 @@ protected:
   /**
    * Setting the value.
    *
    * @param aValue      String to set.
    * @param aOldValue   Previous value before setting aValue.
                         If previous value is unknown, aOldValue can be nullptr.
    * @param aFlags      See nsTextEditorState::SetValueFlags.
    */
+  MOZ_CAN_RUN_SCRIPT
   nsresult SetValueInternal(const nsAString& aValue,
                             const nsAString* aOldValue,
                             uint32_t aFlags);
 
+  MOZ_CAN_RUN_SCRIPT
   nsresult SetValueInternal(const nsAString& aValue,
                             uint32_t aFlags)
   {
     return SetValueInternal(aValue, nullptr, aFlags);
   }
 
   // Generic getter for the value that doesn't do experimental control type
   // sanitization.
@@ -1205,16 +1216,17 @@ protected:
    */
   bool PlaceholderApplies() const;
 
   /**
    * Set the current default value to the value of the input element.
    * @note You should not call this method if GetValueMode() doesn't return
    * VALUE_MODE_VALUE.
    */
+  MOZ_CAN_RUN_SCRIPT
   nsresult SetDefaultValueAsValue();
 
   void SetDirectionFromValue(bool aNotify);
 
   /**
    * Return if an element should have a specific validity UI
    * (with :-moz-ui-invalid and :-moz-ui-valid pseudo-classes).
    *
--- a/dom/html/HTMLTextAreaElement.h
+++ b/dom/html/HTMLTextAreaElement.h
@@ -56,16 +56,17 @@ public:
 
   // Element
   virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override
   {
     return true;
   }
 
   // nsIFormControl
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   NS_IMETHOD Reset() override;
   NS_IMETHOD SubmitNamesValues(HTMLFormSubmission* aFormSubmission) override;
   NS_IMETHOD SaveState() override;
   virtual bool RestoreState(PresState* aState) override;
   virtual bool IsDisabledForEvents(WidgetEvent* aEvent) override;
 
   virtual void FieldSetDisabledChanged(bool aNotify) override;
 
@@ -82,28 +83,31 @@ public:
   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;
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override;
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   NS_IMETHOD CreateEditor() override;
   NS_IMETHOD_(void) UpdateOverlayTextVisibility(bool aNotify) override;
   NS_IMETHOD_(bool) GetPlaceholderVisibility() override;
   NS_IMETHOD_(bool) GetPreviewVisibility() override;
   NS_IMETHOD_(void) SetPreviewValue(const nsAString& aValue) override;
   NS_IMETHOD_(void) GetPreviewValue(nsAString& aValue) override;
   NS_IMETHOD_(void) EnablePreview() override;
   NS_IMETHOD_(bool) IsPreviewEnabled() override;
   NS_IMETHOD_(void) InitializeKeyboardEventListeners() override;
   NS_IMETHOD_(void) OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) override;
   virtual void GetValueFromSetRangeText(nsAString& aValue) override;
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   virtual nsresult SetValueFromSetRangeText(const nsAString& aValue) override;
   NS_IMETHOD_(bool) HasCachedSelection() override;
 
 
   // nsIContent
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                                nsIContent* aBindingParent) override;
   virtual void UnbindFromTree(bool aDeep = true,
@@ -123,16 +127,17 @@ public:
   virtual nsresult PostHandleEvent(
                      EventChainPostVisitor& aVisitor) override;
 
   virtual bool IsHTMLFocusable(bool aWithMouse, bool *aIsFocusable, int32_t *aTabIndex) override;
 
   virtual void DoneAddingChildren(bool aHaveNotified) override;
   virtual bool IsDoneAddingChildren() override;
 
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   virtual nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
 
   nsresult CopyInnerTo(Element* aDest);
 
   /**
    * Called when an attribute is about to be changed
    */
   virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
@@ -273,16 +278,17 @@ public:
   void SetWrap(const nsAString& aWrap, ErrorResult& aError)
   {
     SetHTMLAttr(nsGkAtoms::wrap, aWrap, aError);
   }
   void GetType(nsAString& aType);
   void GetDefaultValue(nsAString& aDefaultValue, ErrorResult& aError);
   void SetDefaultValue(const nsAString& aDefaultValue, ErrorResult& aError);
   void GetValue(nsAString& aValue);
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   void SetValue(const nsAString& aValue, ErrorResult& aError);
 
   uint32_t GetTextLength();
 
   // Override SetCustomValidity so we update our state properly when it's called
   // via bindings.
   void SetCustomValidity(const nsAString& aError);
 
@@ -298,16 +304,17 @@ public:
   // XPCOM adapter function widely used throughout code, leaving it as is.
   nsresult GetControllers(nsIControllers** aResult);
 
   nsIEditor* GetEditor()
   {
     return mState.GetTextEditor();
   }
 
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   void SetUserInput(const nsAString& aValue,
                     nsIPrincipal& aSubjectPrincipal);
 
 protected:
   virtual ~HTMLTextAreaElement() {}
 
   // get rid of the compiler warning
   using nsGenericHTMLFormElementWithState::IsSingleLineTextControl;
@@ -354,16 +361,17 @@ protected:
   void GetValueInternal(nsAString& aValue, bool aIgnoreWrap) const;
 
   /**
    * Setting the value.
    *
    * @param aValue      String to set.
    * @param aFlags      See nsTextEditorState::SetValueFlags.
    */
+  MOZ_CAN_RUN_SCRIPT
   nsresult SetValueInternal(const nsAString& aValue, uint32_t aFlags);
 
   /**
    * Common method to call from the various mutation observer methods.
    * aContent is a content node that's either the one that changed or its
    * parent; we should only respond to the change if aContent is non-anonymous.
    */
   void ContentChanged(nsIContent* aContent);
--- a/dom/html/input/InputType.cpp
+++ b/dom/html/input/InputType.cpp
@@ -121,17 +121,18 @@ void
 InputType::GetNonFileValueInternal(nsAString& aValue) const
 {
   return mInputElement->GetNonFileValueInternal(aValue);
 }
 
 nsresult
 InputType::SetValueInternal(const nsAString& aValue, uint32_t aFlags)
 {
-  return mInputElement->SetValueInternal(aValue, aFlags);
+  RefPtr<mozilla::dom::HTMLInputElement> inputElement(mInputElement);
+  return inputElement->SetValueInternal(aValue, aFlags);
 }
 
 mozilla::Decimal
 InputType::GetStepBase() const
 {
   return mInputElement->GetStepBase();
 }
 
--- a/dom/html/input/InputType.h
+++ b/dom/html/input/InputType.h
@@ -125,16 +125,17 @@ protected:
   void GetNonFileValueInternal(nsAString& aValue) const;
 
   /**
    * Setting the input element's value.
    *
    * @param aValue      String to set.
    * @param aFlags      See nsTextEditorState::SetValueFlags.
    */
+  MOZ_CAN_RUN_SCRIPT
   nsresult SetValueInternal(const nsAString& aValue, uint32_t aFlags);
 
   /**
    * Return the base used to compute if a value matches step.
    * Basically, it's the min attribute if present and a default value otherwise.
    *
    * @return The step base.
    */
--- a/dom/html/input/NumericInputTypes.h
+++ b/dom/html/input/NumericInputTypes.h
@@ -62,16 +62,17 @@ class RangeInputType : public NumericInp
 {
 public:
   static InputType*
   Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
   {
     return new (aMemory) RangeInputType(aInputElement);
   }
 
+  MOZ_CAN_RUN_SCRIPT
   nsresult MinMaxStepAttrChanged() override;
 
 private:
   explicit RangeInputType(mozilla::dom::HTMLInputElement* aInputElement)
     : NumericInputTypeBase(aInputElement)
   {}
 };
 
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -56,29 +56,29 @@ inline nsresult
 SetEditorFlagsIfNecessary(EditorBase& aEditorBase, uint32_t aFlags)
 {
   if (aEditorBase.Flags() == aFlags) {
     return NS_OK;
   }
   return aEditorBase.SetFlags(aFlags);
 }
 
-class MOZ_STACK_CLASS ValueSetter
+class MOZ_STACK_CLASS AutoInputEventSuppresser final
 {
 public:
-  explicit ValueSetter(TextEditor* aTextEditor)
+  explicit AutoInputEventSuppresser(TextEditor* aTextEditor)
     : mTextEditor(aTextEditor)
     // To protect against a reentrant call to SetValue, we check whether
     // another SetValue is already happening for this editor.  If it is,
     // we must wait until we unwind to re-enable oninput events.
     , mOuterTransaction(aTextEditor->IsSuppressingDispatchingInputEvent())
   {
     MOZ_ASSERT(aTextEditor);
   }
-  ~ValueSetter()
+  ~AutoInputEventSuppresser()
   {
     mTextEditor->SuppressDispatchingInputEvent(mOuterTransaction);
   }
   void Init()
   {
     mTextEditor->SuppressDispatchingInputEvent(true);
   }
 
@@ -2395,16 +2395,20 @@ nsTextEditorState::SetValue(const nsAStr
   }
 
   // \r is an illegal character in the dom, but people use them,
   // so convert windows and mac platform linebreaks to \n:
   if (!nsContentUtils::PlatformToDOMLineBreaks(newValue, fallible)) {
     return false;
   }
 
+  // mTextCtrlElement may be cleared when we dispatch an event so that
+  // we should keep grabbing it with local variable.
+  nsCOMPtr<nsITextControlElement> textControlElement(mTextCtrlElement);
+
   if (mTextEditor && mBoundFrame) {
     // The InsertText call below might flush pending notifications, which
     // could lead into a scheduled PrepareEditor to be called.  That will
     // lead to crashes (or worse) because we'd be initializing the editor
     // before InsertText returns.  This script blocker makes sure that
     // PrepareEditor cannot be called prematurely.
     nsAutoScriptBlocker scriptBlocker;
 
@@ -2426,17 +2430,17 @@ nsTextEditorState::SetValue(const nsAStr
       mBoundFrame->GetText(currentValue);
     }
 
     AutoWeakFrame weakFrame(mBoundFrame);
 
     // this is necessary to avoid infinite recursion
     if (!currentValue.Equals(newValue)) {
       RefPtr<TextEditor> textEditor = mTextEditor;
-      ValueSetter valueSetter(textEditor);
+      AutoInputEventSuppresser suppressInputEventDispatching(textEditor);
 
       nsCOMPtr<nsIDocument> document = textEditor->GetDocument();
       if (NS_WARN_IF(!document)) {
         return true;
       }
 
       // Time to mess with our security context... See comments in GetValue()
       // for why this is needed.  Note that we have to do this up here, because
@@ -2449,36 +2453,40 @@ nsTextEditorState::SetValue(const nsAStr
         Selection* selection =
           mSelCon->GetSelection(SelectionType::eNormal);
         SelectionBatcher selectionBatcher(selection);
 
         if (NS_WARN_IF(!weakFrame.IsAlive())) {
           return true;
         }
 
-        valueSetter.Init();
-
         // get the flags, remove readonly, disabled and max-length,
         // set the value, restore flags
         {
           AutoRestoreEditorState restoreState(textEditor);
 
           mTextListener->SettingValue(true);
           bool notifyValueChanged = !!(aFlags & eSetValue_Notify);
           mTextListener->SetValueChanged(notifyValueChanged);
 
           if (aFlags & eSetValue_BySetUserInput) {
             // If the caller inserts text as part of user input, for example,
             // autocomplete, we need to replace the text as "insert string"
             // because undo should cancel only this operation (i.e., previous
             // transactions typed by user shouldn't be merged with this).
+            // In this case, we need to dispatch "input" event because
+            // web apps may need to know the user's operation.
             DebugOnly<nsresult> rv = textEditor->ReplaceTextAsAction(newValue);
             NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
               "Failed to set the new value");
           } else if (aFlags & eSetValue_ForXUL) {
+            // When setting value of XUL <textbox>, we shouldn't dispatch
+            // "input" event.
+            suppressInputEventDispatching.Init();
+
             // On XUL <textbox> element, we need to preserve existing undo
             // transactions.
             // XXX Do we really need to do such complicated optimization?
             //     This was landed for web pages which set <textarea> value
             //     per line (bug 518122).  For example:
             //       for (;;) {
             //         textarea.value += oneLineText + "\n";
             //       }
@@ -2506,16 +2514,20 @@ nsTextEditorState::SetValue(const nsAStr
                 "Failed to remove the text");
             } else {
               DebugOnly<nsresult> rv =
                 textEditor->InsertTextAsAction(insertValue);
               NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                 "Failed to insert the new value");
             }
           } else {
+            // When setting value of <input>, we shouldn't dispatch "input"
+            // event.
+            suppressInputEventDispatching.Init();
+
             // On <input> or <textarea>, we shouldn't preserve existing undo
             // transactions because other browsers do not preserve them too
             // and not preserving transactions makes setting value faster.
             AutoDisableUndo disableUndo(textEditor);
             if (selection) {
               // Since we don't use undo transaction, we don't need to store
               // selection state.  SetText will set selection to tail.
               // Note that textEditor will collapse selection to the end.
@@ -2585,34 +2597,48 @@ nsTextEditorState::SetValue(const nsAStr
           props.SetEnd(std::min(props.GetEnd(), newValue.Length()));
         }
       }
 
       // 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.
+      if (aFlags & eSetValue_BySetUserInput) {
+        nsCOMPtr<Element> element = do_QueryInterface(textControlElement);
+        MOZ_ASSERT(element);
+        RefPtr<TextEditor> textEditor;
+        DebugOnly<nsresult> rvIgnored =
+          nsContentUtils::DispatchInputEvent(element, 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.
       if (IsSelectionCached()) {
         SelectionProperties& props = GetSelectionProperties();
         props.SetIsDirty();
       }
     }
 
     // If we've reached the point where the root node has been created, we
     // can assume that it's safe to notify.
     ValueWasChanged(!!mBoundFrame);
   }
 
-  mTextCtrlElement->OnValueChanged(/* aNotify = */ !!mBoundFrame,
-                                   /* aWasInteractiveUserChange = */ false);
+  // XXX Should we stop notifying "value changed" if mTextCtrlElement has
+  //     been cleared?
+  textControlElement->OnValueChanged(/* aNotify = */ !!mBoundFrame,
+                                     /* aWasInteractiveUserChange = */ false);
 
   return true;
 }
 
 bool
 nsTextEditorState::HasNonEmptyValue()
 {
   if (mTextEditor && mBoundFrame && mEditorInitialized &&
--- a/dom/html/nsTextEditorState.h
+++ b/dom/html/nsTextEditorState.h
@@ -151,17 +151,19 @@ public:
     mTextCtrlElement = nullptr;
   }
 
   mozilla::TextEditor* GetTextEditor();
   mozilla::TextEditor* GetTextEditorWithoutCreation();
   nsISelectionController* GetSelectionController() const;
   nsFrameSelection* GetConstFrameSelection();
   nsresult BindToFrame(nsTextControlFrame* aFrame);
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   void UnbindFromFrame(nsTextControlFrame* aFrame);
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   nsresult PrepareEditor(const nsAString *aValue = nullptr);
   void InitializeKeyboardEventListeners();
 
   enum SetValueFlags
   {
     // The call is for internal processing.
     eSetValue_Internal              = 0,
     // The value is changed by a call of setUserInput() from chrome.
@@ -177,19 +179,21 @@ public:
     // be within the length of the new value.  In either case, if the value has
     // not changed the cursor won't move.
     eSetValue_MoveCursorToEndIfValueChanged = 1 << 3,
     // The value is changed for a XUL text control as opposed to for an HTML
     // text control.  Such value changes are different in that they preserve the
     // undo history.
     eSetValue_ForXUL                = 1 << 4,
   };
+  MOZ_CAN_RUN_SCRIPT
   MOZ_MUST_USE bool SetValue(const nsAString& aValue,
                              const nsAString* aOldValue,
                              uint32_t aFlags);
+  MOZ_CAN_RUN_SCRIPT
   MOZ_MUST_USE bool SetValue(const nsAString& aValue,
                              uint32_t aFlags)
   {
     return SetValue(aValue, nullptr, aFlags);
   }
   void GetValue(nsAString& aValue, bool aIgnoreWrap) const;
   bool HasNonEmptyValue();
   // The following methods are for textarea element to use whether default
@@ -299,16 +303,17 @@ public:
   bool IsSelectionCached() const;
   SelectionProperties& GetSelectionProperties();
   void SetSelectionProperties(SelectionProperties& aProps);
   void WillInitEagerly() { mSelectionRestoreEagerInit = true; }
   bool HasNeverInitializedBefore() const { return !mEverInited; }
   // Sync up our selection properties with our editor prior to being destroyed.
   // This will invoke UnbindFromFrame() to ensure that we grab whatever
   // selection state may be at the moment.
+  MOZ_CAN_RUN_SCRIPT
   void SyncUpSelectionPropertiesBeforeDestruction();
 
   // Get the selection range start and end points in our text.
   void GetSelectionRange(uint32_t* aSelectionStart, uint32_t* aSelectionEnd,
                          mozilla::ErrorResult& aRv);
 
   // Get the selection direction
   nsITextControlFrame::SelectionDirection
--- a/dom/html/test/forms/test_MozEditableElement_setUserInput.html
+++ b/dom/html/test/forms/test_MozEditableElement_setUserInput.html
@@ -122,25 +122,18 @@ SimpleTest.waitForFocus(() => {
     // Before setting focus, editor of the element may have not been created yet.
     let previousValue = target.value;
     SpecialPowers.wrap(target).setUserInput(test.input.before);
     if (target.value == previousValue && test.result.before != previousValue) {
       todo_is(target.value, test.result.before, `setUserInput("${test.input.before}") before ${tag} gets focus should set its value to "${test.result.before}"`);
     } else {
       is(target.value, test.result.before, `setUserInput("${test.input.before}") before ${tag} gets focus should set its value to "${test.result.before}"`);
     }
-    if (test.element === "textarea") {
-      todo_is(inputEvents.length, 1,
-              `Only one "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
-    } else if (target.value == previousValue) {
-      if (test.type === "date" ||
-          test.type === "month" ||
-          test.type === "week" ||
-          test.type === "time" ||
-          test.type === "datetime-local") {
+    if (target.value == previousValue) {
+      if (test.type === "date" || test.type === "time") {
         todo_is(inputEvents.length, 0,
                 `No "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
       } else {
         is(inputEvents.length, 0,
            `No "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
       }
     } else if (!test.result.fireInputEvent) {
       // HTML spec defines that "input" elements whose type are "hidden",
@@ -185,25 +178,18 @@ SimpleTest.waitForFocus(() => {
     target.focus();
     previousValue = target.value;
     SpecialPowers.wrap(target).setUserInput(test.input.after);
     if (target.value == previousValue && test.result.after != previousValue) {
       todo_is(target.value, test.result.after, `setUserInput("${test.input.after}") after ${tag} gets focus should set its value to "${test.result.after}"`);
     } else {
       is(target.value, test.result.after, `setUserInput("${test.input.after}") after ${tag} gets focus should set its value to "${test.result.after}"`);
     }
-    if (test.element === "textarea") {
-      todo_is(inputEvents.length, 1,
-              `Only one "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
-    } else if (target.value == previousValue) {
-      if (test.type === "date" ||
-          test.type === "month" ||
-          test.type === "week" ||
-          test.type === "time" ||
-          test.type === "datetime-local") {
+    if (target.value == previousValue) {
+      if (test.type === "date" || test.type === "time") {
         todo_is(inputEvents.length, 0,
                 `No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
       } else {
         is(inputEvents.length, 0,
            `No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
       }
     } else if (!test.result.fireInputEvent) {
       // HTML spec defines that "input" elements whose type are "hidden",
@@ -216,16 +202,19 @@ SimpleTest.waitForFocus(() => {
           test.type === "reset" ||
           test.type === "button") {
         todo_is(inputEvents.length, 0,
                 `No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
       } else {
         is(inputEvents.length, 0,
            `No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
       }
+    } else if (test.type === "password") {
+      is(inputEvents.length, 0,
+         `Only one "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) {
         if (test.type === "number" || test.type === "time") {
           todo(inputEvents[0] instanceof InputEvent,
--- a/toolkit/content/tests/chrome/file_editor_with_autocomplete.js
+++ b/toolkit/content/tests/chrome/file_editor_with_autocomplete.js
@@ -282,17 +282,16 @@ nsDoTestsForEditorWithAutoComplete.proto
         if (aTarget.tagName === "textbox") {
           return false;
         }
         synthesizeKey("KEY_ArrowDown", {}, aWindow);
         synthesizeKey("KEY_Enter", {}, aWindow);
         return true;
       }, popup: false, value: "Mozilla", searchString: "Mozilla",
       inputEvents: [
-        {inputType: "insertReplacementText"}, // TODO: We don't need to dispatch "input" event int this case because of not changing the value.
       ],
     },
     { description: "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): undo the word, but typed text shouldn't be canceled",
       completeDefaultIndex: true,
       execute(aWindow, aTarget) {
         // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
         if (aTarget.tagName === "textbox") {
           return false;