Bug 970802 - part 3: Implement beforeinput event dispatcher and add onbeforeinput event handler attribute r=smaug
This patch makes `nsContentUtils::DispatchInputEvent()` dispatch `beforeinput` event too. And also adds `onbeforeinput` event handler which is really important for feature detection (although Chrome has not implemented this attribute yet: https://bugs.chromium.org/p/chromium/issues/detail?id=947408). However, we don't implement `InputEvent.getTargetRanges()` in this bug and implementing `beforeinput` event may hit bugs of some web apps. Therefore, this patch disables `beforeinput` event by default even in Nightly channel. Differential Revision: https://phabricator.services.mozilla.com/D58125
This commit is contained in:
@@ -4059,7 +4059,8 @@ nsresult nsContentUtils::DispatchTrustedEvent(
|
||||
Document* aDoc, nsISupports* aTarget, const nsAString& aEventName,
|
||||
CanBubble aCanBubble, Cancelable aCancelable, Composed aComposed,
|
||||
bool* aDefaultAction) {
|
||||
MOZ_ASSERT(!aEventName.EqualsLiteral("input"),
|
||||
MOZ_ASSERT(!aEventName.EqualsLiteral("input") &&
|
||||
!aEventName.EqualsLiteral("beforeinput"),
|
||||
"Use DispatchInputEvent() instead");
|
||||
return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable,
|
||||
aComposed, Trusted::eYes, aDefaultAction);
|
||||
@@ -4141,10 +4142,14 @@ nsContentUtils::InputEventOptions::InputEventOptions(
|
||||
}
|
||||
|
||||
// static
|
||||
nsresult nsContentUtils::DispatchInputEvent(Element* aEventTargetElement,
|
||||
EditorInputType aEditorInputType,
|
||||
TextEditor* aTextEditor,
|
||||
const InputEventOptions& aOptions) {
|
||||
nsresult nsContentUtils::DispatchInputEvent(
|
||||
Element* aEventTargetElement, EventMessage aEventMessage,
|
||||
EditorInputType aEditorInputType, TextEditor* aTextEditor,
|
||||
const InputEventOptions& aOptions,
|
||||
nsEventStatus* aEventStatus /* = nullptr */) {
|
||||
MOZ_ASSERT(aEventMessage == eEditorInput ||
|
||||
aEventMessage == eEditorBeforeInput);
|
||||
|
||||
if (NS_WARN_IF(!aEventTargetElement)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
@@ -4152,7 +4157,8 @@ nsresult nsContentUtils::DispatchInputEvent(Element* aEventTargetElement,
|
||||
// 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.
|
||||
// dispatch "beforeinput" nor "input" event for HTMLEditor except HTMLEditor
|
||||
// itself.
|
||||
bool useInputEvent = false;
|
||||
if (aTextEditor) {
|
||||
useInputEvent = true;
|
||||
@@ -4177,15 +4183,18 @@ nsresult nsContentUtils::DispatchInputEvent(Element* aEventTargetElement,
|
||||
// If the event target is an <input> element, we need to update
|
||||
// validationMessage value before dispatching "input" event because
|
||||
// "input" event listener may need to check it.
|
||||
HTMLInputElement* inputElement =
|
||||
HTMLInputElement::FromNode(aEventTargetElement);
|
||||
if (inputElement) {
|
||||
MOZ_KnownLive(inputElement)->MaybeUpdateAllValidityStates(true);
|
||||
// XXX Should we stop dispatching "input" event if the target is removed
|
||||
// from the DOM tree?
|
||||
if (aEventMessage == eEditorInput) {
|
||||
HTMLInputElement* inputElement =
|
||||
HTMLInputElement::FromNode(aEventTargetElement);
|
||||
if (inputElement) {
|
||||
MOZ_KnownLive(inputElement)->MaybeUpdateAllValidityStates(true);
|
||||
// XXX Should we stop dispatching "input" event if the target is removed
|
||||
// from the DOM tree?
|
||||
}
|
||||
}
|
||||
|
||||
if (!useInputEvent) {
|
||||
MOZ_ASSERT(aEventMessage == eEditorInput);
|
||||
MOZ_ASSERT(aEditorInputType == EditorInputType::eUnknown);
|
||||
// Dispatch "input" event with Event instance.
|
||||
WidgetEvent widgetEvent(true, eUnidentifiedEvent);
|
||||
@@ -4227,7 +4236,12 @@ nsresult nsContentUtils::DispatchInputEvent(Element* aEventTargetElement,
|
||||
}
|
||||
|
||||
// Dispatch "input" event with InputEvent instance.
|
||||
InternalEditorInputEvent inputEvent(true, eEditorInput, widget);
|
||||
InternalEditorInputEvent inputEvent(true, aEventMessage, widget);
|
||||
|
||||
inputEvent.mFlags.mCancelable =
|
||||
aEventMessage == eEditorBeforeInput &&
|
||||
IsCancelableBeforeInputEvent(aEditorInputType);
|
||||
MOZ_ASSERT(!inputEvent.mFlags.mCancelable || aEventStatus);
|
||||
|
||||
// Using same time as old event dispatcher in EditorBase for backward
|
||||
// compatibility.
|
||||
@@ -4239,8 +4253,7 @@ nsresult nsContentUtils::DispatchInputEvent(Element* aEventTargetElement,
|
||||
// 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;
|
||||
inputEvent.mIsComposing = aTextEditor && aTextEditor->GetComposition();
|
||||
|
||||
if (!aTextEditor || !aTextEditor->AsHTMLEditor()) {
|
||||
if (IsDataAvailableOnTextEditor(aEditorInputType)) {
|
||||
@@ -4279,9 +4292,27 @@ nsresult nsContentUtils::DispatchInputEvent(Element* aEventTargetElement,
|
||||
|
||||
inputEvent.mInputType = aEditorInputType;
|
||||
|
||||
(new AsyncEventDispatcher(aEventTargetElement, inputEvent))
|
||||
->RunDOMEventWhenSafe();
|
||||
return NS_OK;
|
||||
if (!IsSafeToRunScript()) {
|
||||
// If we cannot dispatch an event right now, we cannot make it cancelable.
|
||||
NS_ASSERTION(
|
||||
!inputEvent.mFlags.mCancelable,
|
||||
"Cancelable beforeinput event dispatcher should run when it's safe");
|
||||
inputEvent.mFlags.mCancelable = false;
|
||||
(new AsyncEventDispatcher(aEventTargetElement, inputEvent))
|
||||
->RunDOMEventWhenSafe();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
RefPtr<nsPresContext> presContext =
|
||||
aEventTargetElement->OwnerDoc()->GetPresContext();
|
||||
if (NS_WARN_IF(!presContext)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
nsresult rv = EventDispatcher::Dispatch(aEventTargetElement, presContext,
|
||||
&inputEvent, nullptr, aEventStatus);
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
||||
"Dispatching `beforeinput` or `input` event failed");
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult nsContentUtils::DispatchChromeEvent(
|
||||
|
||||
@@ -1458,15 +1458,19 @@ class nsContentUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method dispatches "input" event with proper event class. If it's
|
||||
* unsafe to dispatch, this put the event into the script runner queue.
|
||||
* This method dispatches "beforeinput" event with EditorInputEvent or
|
||||
* "input" event with proper event class. If it's unsafe to dispatch,
|
||||
* this put the event into the script runner queue. In such case, the
|
||||
* event becomes not cancelable even if it's defined as cancelable by
|
||||
* the spec.
|
||||
* 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 aEventTarget The event target element of the "beforeinput"
|
||||
* or "input" event. Must not be nullptr.
|
||||
* @param aEventMessage Muse be eEditorBeforeInput or eEditorInput.
|
||||
* @param aEditorInputType The inputType value of InputEvent.
|
||||
* If aEventTarget won't dispatch "input" event
|
||||
* with InputEvent, set EditorInputType::eUnknown.
|
||||
@@ -1476,11 +1480,18 @@ class nsContentUtils {
|
||||
* @param aOptions Optional. If aEditorInputType value requires
|
||||
* some additional data, they should be properly
|
||||
* set with this argument.
|
||||
* @param aEventStatus Returns nsEventStatus_eConsumeNoDefault if
|
||||
* the dispatching event is cancelable and the
|
||||
* event was canceled by script (including
|
||||
* chrome script). Otherwise, returns given
|
||||
* value. Note that this can be nullptr only
|
||||
* when the dispatching event is not cancelable.
|
||||
*/
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
static nsresult DispatchInputEvent(Element* aEventTarget) {
|
||||
return DispatchInputEvent(aEventTarget, mozilla::EditorInputType::eUnknown,
|
||||
nullptr, InputEventOptions());
|
||||
return DispatchInputEvent(aEventTarget, mozilla::eEditorInput,
|
||||
mozilla::EditorInputType::eUnknown, nullptr,
|
||||
InputEventOptions());
|
||||
}
|
||||
struct MOZ_STACK_CLASS InputEventOptions final {
|
||||
InputEventOptions() = default;
|
||||
@@ -1493,9 +1504,11 @@ class nsContentUtils {
|
||||
};
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
static nsresult DispatchInputEvent(Element* aEventTarget,
|
||||
mozilla::EventMessage aEventMessage,
|
||||
mozilla::EditorInputType aEditorInputType,
|
||||
mozilla::TextEditor* aTextEditor,
|
||||
const InputEventOptions& aOptions);
|
||||
const InputEventOptions& aOptions,
|
||||
nsEventStatus* aEventStatus = nullptr);
|
||||
|
||||
/**
|
||||
* This method creates and dispatches a untrusted event.
|
||||
|
||||
@@ -180,6 +180,8 @@ EVENT(finish, eMarqueeFinish, EventNameType_HTMLMarqueeOnly, eBasicEventClass)
|
||||
EVENT(formdata, eFormData, EventNameType_HTML, eBasicEventClass)
|
||||
EVENT(fullscreenchange, eFullscreenChange, EventNameType_HTML, eBasicEventClass)
|
||||
EVENT(fullscreenerror, eFullscreenError, EventNameType_HTML, eBasicEventClass)
|
||||
EVENT(beforeinput, eEditorBeforeInput, EventNameType_HTMLXUL,
|
||||
eEditorInputEventClass)
|
||||
EVENT(input, eEditorInput, EventNameType_HTMLXUL, eEditorInputEventClass)
|
||||
EVENT(invalid, eFormInvalid, EventNameType_HTMLXUL, eBasicEventClass)
|
||||
EVENT(keydown, eKeyDown, EventNameType_HTMLXUL, eKeyboardEventClass)
|
||||
|
||||
@@ -2957,12 +2957,15 @@ bool TextControlState::SetValueWithoutTextEditor(
|
||||
// event with "insertReplacementText" since web apps may want to know
|
||||
// the user operation which changes editor value with a built-in function
|
||||
// like autocomplete, password manager, session restore, etc.
|
||||
// XXX Should we stop dispatching `input` event if the text control
|
||||
// element has already removed from the DOM tree by a `beforeinput`
|
||||
// event listener?
|
||||
if (aHandlingSetValue.GetSetValueFlags() & eSetValue_BySetUserInput) {
|
||||
MOZ_ASSERT(aHandlingSetValue.GetTextControlElement());
|
||||
MOZ_ASSERT(!aHandlingSetValue.GetSettingValue().IsVoid());
|
||||
DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
|
||||
MOZ_KnownLive(aHandlingSetValue.GetTextControlElement()),
|
||||
EditorInputType::eInsertReplacementText, nullptr,
|
||||
eEditorInput, EditorInputType::eInsertReplacementText, nullptr,
|
||||
nsContentUtils::InputEventOptions(
|
||||
aHandlingSetValue.GetSettingValue()));
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
|
||||
|
||||
@@ -31,6 +31,8 @@ interface mixin GlobalEventHandlers {
|
||||
attribute EventHandler onfocus;
|
||||
//(Not implemented)attribute EventHandler oncancel;
|
||||
attribute EventHandler onauxclick;
|
||||
[Pref="dom.input_events.beforeinput.enabled"]
|
||||
attribute EventHandler onbeforeinput;
|
||||
attribute EventHandler oncanplay;
|
||||
attribute EventHandler oncanplaythrough;
|
||||
attribute EventHandler onchange;
|
||||
|
||||
@@ -2250,7 +2250,7 @@ void EditorBase::DispatchInputEvent(EditAction aEditAction,
|
||||
}
|
||||
RefPtr<TextEditor> textEditor = AsTextEditor();
|
||||
DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
|
||||
targetElement, ToInputType(aEditAction), textEditor,
|
||||
targetElement, eEditorInput, ToInputType(aEditAction), textEditor,
|
||||
aDataTransfer ? nsContentUtils::InputEventOptions(aDataTransfer)
|
||||
: nsContentUtils::InputEventOptions(aData));
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
|
||||
|
||||
@@ -1617,6 +1617,11 @@
|
||||
value: false
|
||||
mirror: always
|
||||
|
||||
- name: dom.input_events.beforeinput.enabled
|
||||
type: bool
|
||||
value: false
|
||||
mirror: always
|
||||
|
||||
# Whether we conform to Input Events Level 1 or Input Events Level 2.
|
||||
# true: conforming to Level 1
|
||||
# false: conforming to Level 2
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "mozilla/dom/EventTarget.h"
|
||||
#include "mozilla/layers/LayersTypes.h"
|
||||
#include "mozilla/EventForwards.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/dom/EventTarget.h"
|
||||
#include "mozilla/layers/LayersTypes.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsAtom.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
@@ -855,7 +855,8 @@ class WidgetEvent : public WidgetEventTime {
|
||||
mMessage == eDragStart || mMessage == eDrop;
|
||||
break;
|
||||
case eEditorInputEventClass:
|
||||
mFlags.mComposed = mMessage == eEditorInput;
|
||||
mFlags.mComposed =
|
||||
mMessage == eEditorInput || mMessage == eEditorBeforeInput;
|
||||
break;
|
||||
case eFocusEventClass:
|
||||
mFlags.mComposed = mMessage == eBlur || mMessage == eFocus ||
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
#include "nsStringFwd.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
#ifdef DEBUG
|
||||
# include "mozilla/StaticPrefs_dom.h"
|
||||
#endif // #ifdef DEBUG
|
||||
|
||||
class nsCommandParams;
|
||||
|
||||
/**
|
||||
@@ -204,6 +208,130 @@ inline bool IsDataTransferAvailableOnHTMLEditor(EditorInputType aInputType) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* IsCancelableBeforeInputEvent() returns true if `beforeinput` event for
|
||||
* aInputType should be cancelable.
|
||||
*
|
||||
* Input Events Level 1:
|
||||
* https://rawgit.com/w3c/input-events/v1/index.html#x5-1-2-attributes
|
||||
* Input Events Level 2:
|
||||
* https://w3c.github.io/input-events/#interface-InputEvent-Attributes
|
||||
*/
|
||||
inline bool IsCancelableBeforeInputEvent(EditorInputType aInputType) {
|
||||
switch (aInputType) {
|
||||
case EditorInputType::eInsertText:
|
||||
return true; // In Level 1, undefined.
|
||||
case EditorInputType::eInsertReplacementText:
|
||||
return true; // In Level 1, undefined.
|
||||
case EditorInputType::eInsertLineBreak:
|
||||
return true; // In Level 1, undefined.
|
||||
case EditorInputType::eInsertParagraph:
|
||||
return true; // In Level 1, undefined.
|
||||
case EditorInputType::eInsertOrderedList:
|
||||
return true;
|
||||
case EditorInputType::eInsertUnorderedList:
|
||||
return true;
|
||||
case EditorInputType::eInsertHorizontalRule:
|
||||
return true;
|
||||
case EditorInputType::eInsertFromYank:
|
||||
return true;
|
||||
case EditorInputType::eInsertFromDrop:
|
||||
return true;
|
||||
case EditorInputType::eInsertFromPaste:
|
||||
return true;
|
||||
case EditorInputType::eInsertFromPasteAsQuotation:
|
||||
return true;
|
||||
case EditorInputType::eInsertTranspose:
|
||||
return true;
|
||||
case EditorInputType::eInsertCompositionText:
|
||||
return false;
|
||||
case EditorInputType::eInsertFromComposition:
|
||||
MOZ_ASSERT(!StaticPrefs::dom_input_events_conform_to_level_1());
|
||||
return true;
|
||||
case EditorInputType::eInsertLink:
|
||||
return true;
|
||||
case EditorInputType::eDeleteByComposition:
|
||||
MOZ_ASSERT(!StaticPrefs::dom_input_events_conform_to_level_1());
|
||||
return true;
|
||||
case EditorInputType::eDeleteCompositionText:
|
||||
MOZ_ASSERT(!StaticPrefs::dom_input_events_conform_to_level_1());
|
||||
return false;
|
||||
case EditorInputType::eDeleteWordBackward:
|
||||
return true; // In Level 1, undefined.
|
||||
case EditorInputType::eDeleteWordForward:
|
||||
return true; // In Level 1, undefined.
|
||||
case EditorInputType::eDeleteSoftLineBackward:
|
||||
return true; // In Level 1, undefined.
|
||||
case EditorInputType::eDeleteSoftLineForward:
|
||||
return true; // In Level 1, undefined.
|
||||
case EditorInputType::eDeleteEntireSoftLine:
|
||||
return true; // In Level 1, undefined.
|
||||
case EditorInputType::eDeleteHardLineBackward:
|
||||
return true; // In Level 1, undefined.
|
||||
case EditorInputType::eDeleteHardLineForward:
|
||||
return true; // In Level 1, undefined.
|
||||
case EditorInputType::eDeleteByDrag:
|
||||
return true;
|
||||
case EditorInputType::eDeleteByCut:
|
||||
return true;
|
||||
case EditorInputType::eDeleteContent:
|
||||
return true; // In Level 1, undefined.
|
||||
case EditorInputType::eDeleteContentBackward:
|
||||
return true; // In Level 1, undefined.
|
||||
case EditorInputType::eDeleteContentForward:
|
||||
return true; // In Level 1, undefined.
|
||||
case EditorInputType::eHistoryUndo:
|
||||
return true;
|
||||
case EditorInputType::eHistoryRedo:
|
||||
return true;
|
||||
case EditorInputType::eFormatBold:
|
||||
return true;
|
||||
case EditorInputType::eFormatItalic:
|
||||
return true;
|
||||
case EditorInputType::eFormatUnderline:
|
||||
return true;
|
||||
case EditorInputType::eFormatStrikeThrough:
|
||||
return true;
|
||||
case EditorInputType::eFormatSuperscript:
|
||||
return true;
|
||||
case EditorInputType::eFormatSubscript:
|
||||
return true;
|
||||
case EditorInputType::eFormatJustifyFull:
|
||||
return true;
|
||||
case EditorInputType::eFormatJustifyCenter:
|
||||
return true;
|
||||
case EditorInputType::eFormatJustifyRight:
|
||||
return true;
|
||||
case EditorInputType::eFormatJustifyLeft:
|
||||
return true;
|
||||
case EditorInputType::eFormatIndent:
|
||||
return true;
|
||||
case EditorInputType::eFormatOutdent:
|
||||
return true;
|
||||
case EditorInputType::eFormatRemove:
|
||||
return true;
|
||||
case EditorInputType::eFormatSetBlockTextDirection:
|
||||
return true;
|
||||
case EditorInputType::eFormatSetInlineTextDirection:
|
||||
return true;
|
||||
case EditorInputType::eFormatBackColor:
|
||||
return true;
|
||||
case EditorInputType::eFormatFontColor:
|
||||
return true;
|
||||
case EditorInputType::eFormatFontName:
|
||||
return true;
|
||||
case EditorInputType::eUnknown:
|
||||
// This is not declared by Input Events, but it does not make sense to
|
||||
// allow web apps to cancel default action without inputType value check.
|
||||
// If some our specific edit actions should be cancelable, new inputType
|
||||
// value for them should be declared by the spec.
|
||||
return false;
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("The new input type is not handled");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#define NS_DEFINE_COMMAND(aName, aCommandStr) , aName
|
||||
#define NS_DEFINE_COMMAND_WITH_PARAM(aName, aCommandStr, aParam) , aName
|
||||
#define NS_DEFINE_COMMAND_NO_EXEC_COMMAND(aName) , aName
|
||||
|
||||
@@ -458,6 +458,7 @@ NS_EVENT_MESSAGE_FIRST_LAST(eGamepadEvent, eGamepadButtonDown,
|
||||
|
||||
// input and beforeinput events.
|
||||
NS_EVENT_MESSAGE(eEditorInput)
|
||||
NS_EVENT_MESSAGE(eEditorBeforeInput)
|
||||
|
||||
// selection events
|
||||
NS_EVENT_MESSAGE(eSelectStart)
|
||||
|
||||
@@ -738,6 +738,7 @@ STATIC_ATOMS = [
|
||||
Atom("onauxclick", "onauxclick"),
|
||||
Atom("onbeforecopy", "onbeforecopy"),
|
||||
Atom("onbeforecut", "onbeforecut"),
|
||||
Atom("onbeforeinput", "onbeforeinput"),
|
||||
Atom("onbeforepaste", "onbeforepaste"),
|
||||
Atom("onbeforeprint", "onbeforeprint"),
|
||||
Atom("onbeforescriptexecute", "onbeforescriptexecute"),
|
||||
|
||||
Reference in New Issue
Block a user