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:
Masayuki Nakano
2024-04-08 12:29:59 +00:00
parent 1322539649
commit e5d2cc1126
14 changed files with 227 additions and 16 deletions

View File

@@ -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
View 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
View 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_

View File

@@ -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",

View File

@@ -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();

View File

@@ -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>

View File

@@ -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 },

View 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");
};

View File

@@ -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",

View File

@@ -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

View File

@@ -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);

View File

@@ -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)

View File

@@ -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)

View File

@@ -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__