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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Masayuki Nakano 2018-11-20 22:06:37 +00:00
parent abe138f771
commit cc3adb8c9e
10 changed files with 84 additions and 31 deletions

View File

@ -2344,6 +2344,10 @@ HTMLInputElement::SetUserInput(const nsAString& aValue,
return;
}
bool isInputEventDispatchedByTextEditorState =
GetValueMode() == VALUE_MODE_VALUE &&
IsSingleLineTextControl(false);
nsresult rv =
SetValueInternal(aValue,
nsTextEditorState::eSetValue_BySetUserInput |
@ -2351,9 +2355,11 @@ HTMLInputElement::SetUserInput(const nsAString& aValue,
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.
@ -2810,6 +2816,11 @@ HTMLInputElement::SetValueInternal(const nsAString& aValue,
}
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;
}

View File

@ -163,9 +163,11 @@ public:
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;
@ -186,6 +188,7 @@ public:
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(
@ -206,6 +209,7 @@ public:
virtual void UnbindFromTree(bool aDeep = true,
bool aNullParent = true) override;
MOZ_CAN_RUN_SCRIPT_BOUNDARY
virtual void DoneCreatingElement() override;
virtual EventStates IntrinsicState() const override;
@ -233,7 +237,9 @@ public:
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;
@ -245,6 +251,7 @@ public:
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;
@ -284,6 +291,7 @@ public:
*/
HTMLInputElement* GetSelectedRadioButton() const;
MOZ_CAN_RUN_SCRIPT_BOUNDARY
virtual nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLInputElement,
@ -724,6 +732,7 @@ public:
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);
@ -1019,10 +1028,12 @@ protected:
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)
{
@ -1210,6 +1221,7 @@ protected:
* @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);

View File

@ -61,6 +61,7 @@ public:
}
// nsIFormControl
MOZ_CAN_RUN_SCRIPT_BOUNDARY
NS_IMETHOD Reset() override;
NS_IMETHOD SubmitNamesValues(HTMLFormSubmission* aFormSubmission) override;
NS_IMETHOD SaveState() override;
@ -87,7 +88,9 @@ public:
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;
@ -99,6 +102,7 @@ public:
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;
@ -128,6 +132,7 @@ public:
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);
@ -278,6 +283,7 @@ public:
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();
@ -303,6 +309,7 @@ public:
return mState.GetTextEditor();
}
MOZ_CAN_RUN_SCRIPT_BOUNDARY
void SetUserInput(const nsAString& aValue,
nsIPrincipal& aSubjectPrincipal);
@ -359,6 +366,7 @@ protected:
* @param aValue String to set.
* @param aFlags See nsTextEditorState::SetValueFlags.
*/
MOZ_CAN_RUN_SCRIPT
nsresult SetValueInternal(const nsAString& aValue, uint32_t aFlags);
/**

View File

@ -126,7 +126,8 @@ InputType::GetNonFileValueInternal(nsAString& aValue) const
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

View File

@ -130,6 +130,7 @@ protected:
* @param aValue String to set.
* @param aFlags See nsTextEditorState::SetValueFlags.
*/
MOZ_CAN_RUN_SCRIPT
nsresult SetValueInternal(const nsAString& aValue, uint32_t aFlags);
/**

View File

@ -67,6 +67,7 @@ public:
return new (aMemory) RangeInputType(aInputElement);
}
MOZ_CAN_RUN_SCRIPT
nsresult MinMaxStepAttrChanged() override;
private:

View File

@ -61,10 +61,10 @@ SetEditorFlagsIfNecessary(EditorBase& aEditorBase, uint32_t aFlags)
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,
@ -73,7 +73,7 @@ public:
{
MOZ_ASSERT(aTextEditor);
}
~ValueSetter()
~AutoInputEventSuppresser()
{
mTextEditor->SuppressDispatchingInputEvent(mOuterTransaction);
}
@ -2400,6 +2400,10 @@ nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue,
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
@ -2431,7 +2435,7 @@ nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue,
// 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)) {
@ -2454,8 +2458,6 @@ nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue,
return true;
}
valueSetter.Init();
// get the flags, remove readonly, disabled and max-length,
// set the value, restore flags
{
@ -2470,10 +2472,16 @@ nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue,
// 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?
@ -2511,6 +2519,10 @@ nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue,
"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.
@ -2590,6 +2602,18 @@ nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue,
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.
@ -2606,8 +2630,10 @@ nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue,
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;
}

View File

@ -156,7 +156,9 @@ public:
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();
@ -182,9 +184,11 @@ public:
// 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)
{
@ -304,6 +308,7 @@ public:
// 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.

View File

@ -127,15 +127,8 @@ SimpleTest.waitForFocus(() => {
} 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 {
@ -190,15 +183,8 @@ SimpleTest.waitForFocus(() => {
} 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 {
@ -221,6 +207,9 @@ SimpleTest.waitForFocus(() => {
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`);

View File

@ -287,7 +287,6 @@ nsDoTestsForEditorWithAutoComplete.prototype = {
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",