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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Masayuki Nakano 2018-11-21 03:59:02 +00:00
Родитель 388e64d857
Коммит abe138f771
30 изменённых файлов: 304 добавлений и 173 удалений

Просмотреть файл

@ -115,16 +115,14 @@ function triggerAutofillAndCheckProfile(profile) {
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();

Просмотреть файл

@ -68,10 +68,10 @@ 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();

Просмотреть файл

@ -50,16 +50,14 @@ function checkElementFilled(element, expectedvalue) {
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();

Просмотреть файл

@ -57,6 +57,7 @@
#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"
@ -85,6 +86,7 @@
#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"
@ -4367,6 +4369,8 @@ nsContentUtils::DispatchTrustedEvent(nsIDocument* aDoc, nsISupports* aTarget,
Composed aComposed,
bool* aDefaultAction)
{
MOZ_ASSERT(!aEventName.EqualsLiteral("input"),
"Use DispatchInputEvent() instead");
return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable,
aComposed, Trusted::eYes, aDefaultAction);
}
@ -4450,6 +4454,110 @@ nsContentUtils::DispatchEvent(nsIDocument* aDoc, nsISupports* aTarget,
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,

Просмотреть файл

@ -123,6 +123,7 @@ class Dispatcher;
class ErrorResult;
class EventListenerManager;
class HTMLEditor;
class TextEditor;
namespace dom {
class ContentFrameMessageManager;
@ -1377,9 +1378,12 @@ public:
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.
@ -1438,6 +1442,26 @@ public:
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

Просмотреть файл

@ -251,16 +251,12 @@ public:
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()),
@ -595,7 +591,9 @@ public:
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:
@ -604,6 +602,7 @@ private:
* 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;
@ -634,15 +633,14 @@ nsColorPickerShownCallback::UpdateInternal(const nsAString& aColor,
}
}
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;
}
@ -2353,13 +2351,9 @@ HTMLInputElement::SetUserInput(const nsAString& aValue,
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.
@ -2390,6 +2384,16 @@ HTMLInputElement::GetTextEditor()
return GetTextEditorFromState();
}
NS_IMETHODIMP_(TextEditor*)
HTMLInputElement::GetTextEditorWithoutCreation()
{
nsTextEditorState* state = GetEditorState();
if (!state) {
return nullptr;
}
return state->GetTextEditorWithoutCreation();
}
NS_IMETHODIMP_(nsISelectionController*)
HTMLInputElement::GetSelectionController()
{
@ -2735,7 +2739,7 @@ HTMLInputElement::SetFiles(FileList* 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()");
@ -3750,12 +3754,9 @@ HTMLInputElement::CancelRangeThumbDrag(bool aIsForUserEvent)
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");
}
}
@ -3778,11 +3779,9 @@ HTMLInputElement::SetValueOfRangeForUserEvent(Decimal aValue)
}
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");
}
}
@ -3877,11 +3876,9 @@ HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection)
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
@ -4091,9 +4088,9 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
}
} 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),

Просмотреть файл

@ -187,12 +187,18 @@ public:
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,
@ -223,6 +229,7 @@ public:
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;
@ -891,12 +898,14 @@ public:
};
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
@ -916,6 +925,7 @@ public:
*/
nsIEditor* GetEditor();
MOZ_CAN_RUN_SCRIPT_BOUNDARY
void SetUserInput(const nsAString& aInput,
nsIPrincipal& aSubjectPrincipal);
@ -1054,6 +1064,7 @@ protected:
/**
* 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,
@ -1180,6 +1191,7 @@ protected:
/**
* Manages the internal data storage across type changes.
*/
MOZ_CAN_RUN_SCRIPT
void HandleTypeChange(uint8_t aNewType, bool aNotify);
/**

Просмотреть файл

@ -224,6 +224,12 @@ HTMLTextAreaElement::GetTextEditor()
return mState.GetTextEditor();
}
NS_IMETHODIMP_(TextEditor*)
HTMLTextAreaElement::GetTextEditorWithoutCreation()
{
return mState.GetTextEditorWithoutCreation();
}
NS_IMETHODIMP_(nsISelectionController*)
HTMLTextAreaElement::GetSelectionController()
{

Просмотреть файл

@ -83,6 +83,7 @@ public:
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;

Просмотреть файл

@ -102,8 +102,12 @@ public:
* 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.

Просмотреть файл

@ -1223,6 +1223,12 @@ nsTextEditorState::GetTextEditor()
return mTextEditor;
}
TextEditor*
nsTextEditorState::GetTextEditorWithoutCreation()
{
return mTextEditor;
}
nsISelectionController*
nsTextEditorState::GetSelectionController() const
{

Просмотреть файл

@ -152,6 +152,7 @@ public:
}
mozilla::TextEditor* GetTextEditor();
mozilla::TextEditor* GetTextEditorWithoutCreation();
nsISelectionController* GetSelectionController() const;
nsFrameSelection* GetConstFrameSelection();
nsresult BindToFrame(nsTextControlFrame* aFrame);

Просмотреть файл

@ -164,14 +164,19 @@ SimpleTest.waitForFocus(() => {
}
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)`);
}
@ -222,14 +227,19 @@ SimpleTest.waitForFocus(() => {
}
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)`);
}

Просмотреть файл

@ -57,13 +57,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=851780
}
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,

Просмотреть файл

@ -2142,56 +2142,6 @@ EditorBase::NotifySelectionChanged(nsIDocument* aDocument,
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)
{
@ -2252,19 +2202,15 @@ EditorBase::NotifyEditorObservers(NotificationForEditorObservers aNotification)
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

Просмотреть файл

@ -356,6 +356,7 @@ public:
/**
* ToggleTextDirection() toggles text-direction of the root element.
*/
MOZ_CAN_RUN_SCRIPT_BOUNDARY
nsresult ToggleTextDirection();
/**
@ -367,6 +368,7 @@ public:
eLTR,
eRTL,
};
MOZ_CAN_RUN_SCRIPT
void SwitchTextDirectionTo(TextDirection aTextDirection);
/**
@ -1732,7 +1734,9 @@ protected: // Called by helper classes.
* 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();
@ -1768,6 +1772,8 @@ protected: // Shouldn't be used by friend classes
virtual nsresult SelectAllInternal();
nsresult DetermineCurrentDirection();
MOZ_CAN_RUN_SCRIPT
void FireInputEvent();
/**
@ -1888,7 +1894,7 @@ protected: // Shouldn't be used by friend classes
/**
* 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.
@ -1950,6 +1956,7 @@ protected: // Shouldn't be used by friend classes
eNotifyEditorObserversOfBefore,
eNotifyEditorObserversOfCancel
};
MOZ_CAN_RUN_SCRIPT
void NotifyEditorObservers(NotificationForEditorObservers aNotification);
private:

Просмотреть файл

@ -1031,7 +1031,7 @@ EditorEventListener::HandleChangeComposition(
return NS_OK;
}
TextEditor* textEditor = editorBase->AsTextEditor();
RefPtr<TextEditor> textEditor = editorBase->AsTextEditor();
return textEditor->OnCompositionChange(*aCompositionChangeEvent);
}
@ -1050,7 +1050,7 @@ EditorEventListener::HandleEndComposition(
MOZ_ASSERT(!aCompositionEndEvent->DefaultPrevented(),
"eCompositionEnd shouldn't be cancelable");
TextEditor* textEditor = editorBase->AsTextEditor();
RefPtr<TextEditor> textEditor = editorBase->AsTextEditor();
textEditor->OnCompositionEnd(*aCompositionEndEvent);
}

Просмотреть файл

@ -63,11 +63,14 @@ protected:
#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);

Просмотреть файл

@ -5454,10 +5454,10 @@ HTMLEditor::GetPreferredIMEState(IMEState* aState)
return NS_OK;
}
already_AddRefed<nsIContent>
HTMLEditor::GetInputEventTargetContent()
already_AddRefed<Element>
HTMLEditor::GetInputEventTargetElement()
{
nsCOMPtr<nsIContent> target = GetActiveEditingHost();
RefPtr<Element> target = GetActiveEditingHost();
return target.forget();
}

Просмотреть файл

@ -1778,7 +1778,7 @@ protected: // Shouldn't be used by friend classes
*/
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.

Просмотреть файл

@ -1460,10 +1460,10 @@ TextEditor::OnCompositionEnd(WidgetCompositionEvent& aCompositionEndEvent)
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();
}

Просмотреть файл

@ -64,7 +64,9 @@ public:
// 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;
@ -205,6 +207,7 @@ public:
* @param aCompositionChangeEvent eCompositionChange event which should
* be handled in this editor.
*/
MOZ_CAN_RUN_SCRIPT
nsresult
OnCompositionChange(WidgetCompositionEvent& aCompositionChangeEvent);
@ -213,6 +216,7 @@ public:
* event and it's followed by eCompositionEnd event and after
* OnCompositionChange() is called.
*/
MOZ_CAN_RUN_SCRIPT
void OnCompositionEnd(WidgetCompositionEvent& aCompositionEndEvent);
/**
@ -530,7 +534,7 @@ protected: // Shouldn't be used by friend classes
*/
bool EnsureComposition(WidgetCompositionEvent& aCompositionEvent);
virtual already_AddRefed<nsIContent> GetInputEventTargetContent() override;
virtual already_AddRefed<Element> GetInputEventTargetElement() override;
protected:
mutable nsCOMPtr<nsIDocumentEncoder> mCachedDocumentEncoder;

Просмотреть файл

@ -128,6 +128,7 @@ public:
* @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; }

Просмотреть файл

@ -459,11 +459,11 @@ nsFileControlFrame::DnDListener::HandleEvent(Event* aEvent)
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"),

Просмотреть файл

@ -123,7 +123,9 @@ protected:
: 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);

Просмотреть файл

@ -70,7 +70,10 @@ public:
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() {}
@ -1390,14 +1393,17 @@ nsListControlFrame::FireOnInputAndOnChange()
}
}
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);

Просмотреть файл

@ -134,6 +134,7 @@ public:
* 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();
/**
@ -160,10 +161,13 @@ public:
* @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);
/**
@ -257,6 +261,7 @@ protected:
* @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();
/**
@ -271,6 +276,7 @@ protected:
* 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);
/**
@ -378,6 +384,7 @@ protected:
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);

Просмотреть файл

@ -35,10 +35,10 @@ function handleSubmit() { // eslint-disable-line no-unused-vars
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);

Просмотреть файл

@ -95,19 +95,12 @@ nsDoTestsForEditorWithAutoComplete.prototype = {
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');
}

Просмотреть файл

@ -510,7 +510,8 @@ private:
mFlags.mBubbles = true;
break;
default:
if (mMessage == eResize) {
if (mMessage == eResize ||
mMessage == eEditorInput) {
mFlags.mCancelable = false;
} else {
mFlags.mCancelable = true;
@ -614,7 +615,8 @@ public:
// 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,