mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-21 17:25:36 +00:00
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:
parent
abe138f771
commit
cc3adb8c9e
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
/**
|
||||
|
@ -67,6 +67,7 @@ public:
|
||||
return new (aMemory) RangeInputType(aInputElement);
|
||||
}
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
nsresult MinMaxStepAttrChanged() override;
|
||||
|
||||
private:
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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`);
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user