Bug 903746 - part 1: Add TextEvent r=smaug
Unfortunately, we returned a `CompositionEvent` for
`Document.createEvent("textevent")` because we had a text event which we stopped
exposing to the web and was replaced with `eCompositionChange` event.
Therefore, this change could potentially have a compatibility risk.
Differential Revision: https://phabricator.services.mozilla.com/D200120
This commit is contained in:
@@ -47,6 +47,7 @@
|
||||
#include "mozilla/dom/SimpleGestureEvent.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "mozilla/dom/StorageEvent.h"
|
||||
#include "mozilla/dom/TextEvent.h"
|
||||
#include "mozilla/dom/TimeEvent.h"
|
||||
#include "mozilla/dom/TouchEvent.h"
|
||||
#include "mozilla/dom/TransitionEvent.h"
|
||||
@@ -1404,6 +1405,9 @@ nsresult EventDispatcher::DispatchDOMEvent(EventTarget* aTarget,
|
||||
case eEditorInputEventClass:
|
||||
return NS_NewDOMInputEvent(aOwner, aPresContext,
|
||||
aEvent->AsEditorInputEvent());
|
||||
case eLegacyTextEventClass:
|
||||
return NS_NewDOMTextEvent(aOwner, aPresContext,
|
||||
aEvent->AsLegacyTextEvent());
|
||||
case eDragEventClass:
|
||||
return NS_NewDOMDragEvent(aOwner, aPresContext, aEvent->AsDragEvent());
|
||||
case eClipboardEventClass:
|
||||
@@ -1448,10 +1452,15 @@ nsresult EventDispatcher::DispatchDOMEvent(EventTarget* aTarget,
|
||||
if (aEventType.LowerCaseEqualsLiteral("keyboardevent")) {
|
||||
return NS_NewDOMKeyboardEvent(aOwner, aPresContext, nullptr);
|
||||
}
|
||||
if (aEventType.LowerCaseEqualsLiteral("compositionevent") ||
|
||||
aEventType.LowerCaseEqualsLiteral("textevent")) {
|
||||
if (aEventType.LowerCaseEqualsLiteral("compositionevent")) {
|
||||
return NS_NewDOMCompositionEvent(aOwner, aPresContext, nullptr);
|
||||
}
|
||||
if (aEventType.LowerCaseEqualsLiteral("textevent")) {
|
||||
if (!StaticPrefs::dom_events_textevent_enabled()) {
|
||||
return NS_NewDOMCompositionEvent(aOwner, aPresContext, nullptr);
|
||||
}
|
||||
return NS_NewDOMTextEvent(aOwner, aPresContext, nullptr);
|
||||
}
|
||||
if (aEventType.LowerCaseEqualsLiteral("mutationevent") ||
|
||||
aEventType.LowerCaseEqualsLiteral("mutationevents")) {
|
||||
return NS_NewDOMMutationEvent(aOwner, aPresContext, nullptr);
|
||||
|
||||
51
dom/events/TextEvent.cpp
Normal file
51
dom/events/TextEvent.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/TextEvents.h"
|
||||
#include "mozilla/dom/TextEvent.h"
|
||||
#include "nsGlobalWindowInner.h"
|
||||
#include "nsPresContext.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
TextEvent::TextEvent(EventTarget* aOwner, nsPresContext* aPresContext,
|
||||
InternalLegacyTextEvent* aEvent)
|
||||
: UIEvent(aOwner, aPresContext,
|
||||
aEvent
|
||||
? aEvent
|
||||
: new InternalLegacyTextEvent(false, eVoidEvent, nullptr)) {
|
||||
NS_ASSERTION(mEvent->mClass == eLegacyTextEventClass, "event type mismatch");
|
||||
mEventIsInternal = !aEvent;
|
||||
}
|
||||
|
||||
void TextEvent::InitTextEvent(const nsAString& typeArg, bool canBubbleArg,
|
||||
bool cancelableArg, nsGlobalWindowInner* viewArg,
|
||||
const nsAString& dataArg) {
|
||||
if (NS_WARN_IF(mEvent->mFlags.mIsBeingDispatched)) {
|
||||
return;
|
||||
}
|
||||
|
||||
UIEvent::InitUIEvent(typeArg, canBubbleArg, cancelableArg, viewArg, 0);
|
||||
|
||||
static_cast<InternalLegacyTextEvent*>(mEvent)->mData = dataArg;
|
||||
}
|
||||
|
||||
void TextEvent::GetData(nsAString& aData) const {
|
||||
// TODO: Add a security check like InputEvent::GetData().
|
||||
aData = static_cast<InternalLegacyTextEvent*>(mEvent)->mData;
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
||||
already_AddRefed<TextEvent> NS_NewDOMTextEvent(
|
||||
EventTarget* aOwner, nsPresContext* aPresContext,
|
||||
InternalLegacyTextEvent* aEvent) {
|
||||
RefPtr<TextEvent> it = new TextEvent(aOwner, aPresContext, aEvent);
|
||||
return it.forget();
|
||||
}
|
||||
44
dom/events/TextEvent.h
Normal file
44
dom/events/TextEvent.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_dom_TextEvent_h
|
||||
#define mozilla_dom_TextEvent_h
|
||||
|
||||
#include "mozilla/dom/UIEvent.h"
|
||||
|
||||
#include "mozilla/dom/TextEventBinding.h"
|
||||
#include "mozilla/EventForwards.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
class TextEvent : public UIEvent {
|
||||
public:
|
||||
TextEvent(EventTarget* aOwner, nsPresContext* aPresContext,
|
||||
InternalLegacyTextEvent* aEvent);
|
||||
|
||||
NS_INLINE_DECL_REFCOUNTING_INHERITED(TextEvent, UIEvent)
|
||||
|
||||
virtual JSObject* WrapObjectInternal(
|
||||
JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override {
|
||||
return TextEvent_Binding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
void GetData(nsAString& aData) const;
|
||||
void InitTextEvent(const nsAString& typeArg, bool canBubbleArg,
|
||||
bool cancelableArg, nsGlobalWindowInner* viewArg,
|
||||
const nsAString& dataArg);
|
||||
|
||||
protected:
|
||||
~TextEvent() = default;
|
||||
};
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
already_AddRefed<mozilla::dom::TextEvent> NS_NewDOMTextEvent(
|
||||
mozilla::dom::EventTarget* aOwner, nsPresContext* aPresContext,
|
||||
mozilla::InternalLegacyTextEvent* aEvent);
|
||||
|
||||
#endif // mozilla_dom_InputEvent_h_
|
||||
@@ -94,6 +94,7 @@ EXPORTS.mozilla.dom += [
|
||||
"SimpleGestureEvent.h",
|
||||
"StorageEvent.h",
|
||||
"TextClause.h",
|
||||
"TextEvent.h",
|
||||
"Touch.h",
|
||||
"TouchEvent.h",
|
||||
"TransitionEvent.h",
|
||||
@@ -154,6 +155,7 @@ UNIFIED_SOURCES += [
|
||||
"StorageEvent.cpp",
|
||||
"TextClause.cpp",
|
||||
"TextComposition.cpp",
|
||||
"TextEvent.cpp",
|
||||
"Touch.cpp",
|
||||
"TouchEvent.cpp",
|
||||
"TransitionEvent.cpp",
|
||||
|
||||
@@ -376,6 +376,13 @@ const kEventConstructors = {
|
||||
return new TCPServerSocketEvent(aName, aProps);
|
||||
},
|
||||
},
|
||||
TextEvent : { create (aName, aProps) {
|
||||
var e = document.createEvent("textevent");
|
||||
e.initTextEvent("textInput", aProps.bubbles, aProps.cancelable,
|
||||
aProps.view, aProps.data);
|
||||
return e;
|
||||
},
|
||||
},
|
||||
TimeEvent: { create: null
|
||||
// Cannot create untrusted event from JS
|
||||
},
|
||||
@@ -477,7 +484,8 @@ function test() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv(
|
||||
{"set": [["dom.w3c_touch_events.legacy_apis.enabled", true],
|
||||
["layout.css.content-visibility.enabled", true]]},
|
||||
["layout.css.content-visibility.enabled", true],
|
||||
["dom.events.textevent.enabled", true]]},
|
||||
function() {
|
||||
test();
|
||||
SimpleTest.finish();
|
||||
|
||||
@@ -924,6 +924,28 @@ is(e.dataTransfer, null, "InputEvent.dataTransfer should be null in default");
|
||||
is(e.inputType, "", "InputEvent.inputType should be empty string in default");
|
||||
is(e.isComposing, false, "InputEvent.isComposing should be false in default");
|
||||
|
||||
// TextEvent
|
||||
if (SpecialPowers.getBoolPref("dom.events.textevent.enabled")) {
|
||||
try {
|
||||
e = new TextEvent();
|
||||
ok(false, "TextEvent should not have constructor");
|
||||
} catch (exp) {
|
||||
ok(true, "TextEvent does not have a constructor");
|
||||
}
|
||||
try {
|
||||
e = new TextEvent("foo");
|
||||
ok(false, "TextEvent should not have constructor");
|
||||
} catch (exp) {
|
||||
ok(true, "TextEvent does not have a constructor taking a event type");
|
||||
}
|
||||
try {
|
||||
e = new TextEvent("foo", {});
|
||||
ok(false, "TextEvent should not have constructor");
|
||||
} catch (exp) {
|
||||
ok(true, "TextEvent does not have a constructor taking event type and a dictionary");
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
||||
@@ -1376,6 +1376,12 @@ let interfaceNamesInGlobalScope = [
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "TextEncoderStream", insecureContext: true },
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{
|
||||
name: "TextEvent",
|
||||
insecureContext: true,
|
||||
disabled: !SpecialPowers.getBoolPref("dom.events.textevent.enabled"),
|
||||
},
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "TextMetrics", insecureContext: true },
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "TextTrack", insecureContext: true },
|
||||
|
||||
20
dom/webidl/TextEvent.webidl
Normal file
20
dom/webidl/TextEvent.webidl
Normal file
@@ -0,0 +1,20 @@
|
||||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* The origin of this IDL file is
|
||||
* https://w3c.github.io/uievents/#textevent
|
||||
*/
|
||||
|
||||
[Pref="dom.events.textevent.enabled", Exposed=Window]
|
||||
interface TextEvent : UIEvent
|
||||
{
|
||||
readonly attribute DOMString data;
|
||||
|
||||
undefined initTextEvent(DOMString type,
|
||||
optional boolean bubbles = false,
|
||||
optional boolean cancelable = false,
|
||||
optional Window? view = null,
|
||||
optional DOMString data = "undefined");
|
||||
};
|
||||
@@ -325,6 +325,9 @@ with Files("SubtleCrypto.webidl"):
|
||||
with Files("TCP*"):
|
||||
BUG_COMPONENT = ("Core", "DOM: Networking")
|
||||
|
||||
with Files("TextEvent.webidl"):
|
||||
BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
|
||||
|
||||
with Files("TextTrack*"):
|
||||
BUG_COMPONENT = ("Core", "Audio/Video")
|
||||
|
||||
@@ -969,6 +972,7 @@ WEBIDL_FILES = [
|
||||
"TextDecoderStream.webidl",
|
||||
"TextEncoder.webidl",
|
||||
"TextEncoderStream.webidl",
|
||||
"TextEvent.webidl",
|
||||
"TextTrack.webidl",
|
||||
"TextTrackCue.webidl",
|
||||
"TextTrackCueList.webidl",
|
||||
|
||||
@@ -2610,6 +2610,13 @@
|
||||
value: true
|
||||
mirror: always
|
||||
|
||||
# Expose Window.TextEvent and make the builtin editors dispatch `textInput`
|
||||
# event as a default action of `beforeinput`.
|
||||
- name: dom.events.textevent.enabled
|
||||
type: bool
|
||||
value: false
|
||||
mirror: always
|
||||
|
||||
# Whether to expose test interfaces of various sorts
|
||||
- name: dom.expose_test_interfaces
|
||||
type: bool
|
||||
|
||||
@@ -440,6 +440,10 @@ class WidgetEvent : public WidgetEventTime {
|
||||
mFlags.mCancelable = false;
|
||||
mFlags.mBubbles = mFlags.mIsTrusted;
|
||||
break;
|
||||
case eLegacyTextEventClass:
|
||||
mFlags.mCancelable = mFlags.mIsTrusted && mMessage == eLegacyTextInput;
|
||||
mFlags.mBubbles = mFlags.mIsTrusted && mMessage == eLegacyTextInput;
|
||||
break;
|
||||
case eMouseEventClass:
|
||||
mFlags.mCancelable =
|
||||
(mMessage != eMouseEnter && mMessage != eMouseLeave);
|
||||
|
||||
@@ -26,6 +26,7 @@ NS_EVENT_CLASS(Widget, CompositionEvent)
|
||||
NS_EVENT_CLASS(Widget, QueryContentEvent)
|
||||
NS_EVENT_CLASS(Widget, SelectionEvent)
|
||||
NS_EVENT_CLASS(Internal, EditorInputEvent)
|
||||
NS_EVENT_CLASS(Internal, LegacyTextEvent)
|
||||
|
||||
// MouseEvents.h
|
||||
NS_EVENT_CLASS(Widget, MouseEventBase)
|
||||
|
||||
@@ -432,6 +432,9 @@ NS_EVENT_MESSAGE_FIRST_LAST(eGamepadEvent, eGamepadButtonDown,
|
||||
NS_EVENT_MESSAGE(eEditorInput)
|
||||
NS_EVENT_MESSAGE(eEditorBeforeInput)
|
||||
|
||||
// textInput event which is a default action of beforeinput
|
||||
NS_EVENT_MESSAGE(eLegacyTextInput)
|
||||
|
||||
// selection events
|
||||
NS_EVENT_MESSAGE(eSelectStart)
|
||||
NS_EVENT_MESSAGE(eSelectionChange)
|
||||
|
||||
@@ -1442,13 +1442,8 @@ class WidgetSelectionEvent : public WidgetGUIEvent {
|
||||
******************************************************************************/
|
||||
|
||||
class InternalEditorInputEvent : public InternalUIEvent {
|
||||
private:
|
||||
InternalEditorInputEvent()
|
||||
: mData(VoidString()),
|
||||
mInputType(EditorInputType::eUnknown),
|
||||
mIsComposing(false) {}
|
||||
|
||||
public:
|
||||
InternalEditorInputEvent() = delete;
|
||||
virtual InternalEditorInputEvent* AsEditorInputEvent() override {
|
||||
return this;
|
||||
}
|
||||
@@ -1457,9 +1452,7 @@ class InternalEditorInputEvent : public InternalUIEvent {
|
||||
nsIWidget* aWidget = nullptr,
|
||||
const WidgetEventTime* aTime = nullptr)
|
||||
: InternalUIEvent(aIsTrusted, aMessage, aWidget, eEditorInputEventClass,
|
||||
aTime),
|
||||
mData(VoidString()),
|
||||
mInputType(EditorInputType::eUnknown) {}
|
||||
aTime) {}
|
||||
|
||||
virtual WidgetEvent* Duplicate() const override {
|
||||
MOZ_ASSERT(mClass == eEditorInputEventClass,
|
||||
@@ -1472,13 +1465,13 @@ class InternalEditorInputEvent : public InternalUIEvent {
|
||||
return result;
|
||||
}
|
||||
|
||||
nsString mData;
|
||||
nsString mData = VoidString();
|
||||
RefPtr<dom::DataTransfer> mDataTransfer;
|
||||
OwningNonNullStaticRangeArray mTargetRanges;
|
||||
|
||||
EditorInputType mInputType;
|
||||
EditorInputType mInputType = EditorInputType::eUnknown;
|
||||
|
||||
bool mIsComposing;
|
||||
bool mIsComposing = false;
|
||||
|
||||
void AssignEditorInputEventData(const InternalEditorInputEvent& aEvent,
|
||||
bool aCopyTargets) {
|
||||
@@ -1502,10 +1495,47 @@ class InternalEditorInputEvent : public InternalUIEvent {
|
||||
|
||||
private:
|
||||
static const char16_t* const kInputTypeNames[];
|
||||
typedef nsTHashMap<nsStringHashKey, EditorInputType> InputTypeHashtable;
|
||||
using InputTypeHashtable = nsTHashMap<nsStringHashKey, EditorInputType>;
|
||||
static InputTypeHashtable* sInputTypeHashtable;
|
||||
};
|
||||
|
||||
/******************************************************************************
|
||||
* mozilla::InternalLegacyTextEvent
|
||||
******************************************************************************/
|
||||
|
||||
class InternalLegacyTextEvent : public InternalUIEvent {
|
||||
public:
|
||||
InternalLegacyTextEvent() = delete;
|
||||
|
||||
virtual InternalLegacyTextEvent* AsLegacyTextEvent() override { return this; }
|
||||
|
||||
InternalLegacyTextEvent(bool aIsTrusted, EventMessage aMessage,
|
||||
nsIWidget* aWidget = nullptr,
|
||||
const WidgetEventTime* aTime = nullptr)
|
||||
: InternalUIEvent(aIsTrusted, aMessage, aWidget, eLegacyTextEventClass,
|
||||
aTime) {}
|
||||
|
||||
virtual WidgetEvent* Duplicate() const override {
|
||||
MOZ_ASSERT(mClass == eLegacyTextEventClass,
|
||||
"Duplicate() must be overridden by sub class");
|
||||
// Not copying widget, it is a weak reference.
|
||||
InternalLegacyTextEvent* result =
|
||||
new InternalLegacyTextEvent(false, mMessage, nullptr, this);
|
||||
result->AssignLegacyTextEventData(*this, true);
|
||||
result->mFlags = mFlags;
|
||||
return result;
|
||||
}
|
||||
|
||||
nsString mData;
|
||||
|
||||
void AssignLegacyTextEventData(const InternalLegacyTextEvent& aEvent,
|
||||
bool aCopyTargets) {
|
||||
AssignUIEventData(aEvent, aCopyTargets);
|
||||
|
||||
mData = aEvent.mData;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_TextEvents_h__
|
||||
|
||||
Reference in New Issue
Block a user