Bug 1845381 - For begin/repeat/end SMIL animation events, only support "beginEvent"/"repeatEvent"/"endEvent" in addEventListener. r=dholbert,smaug
Before this patch, we would have called listeners added with both
addEventListener("end", ...) and addEventListener("endEvent", ...).
Now we will just call listeners added with the latter and ignore the
former.
This change only affects listeners added with addEventListener.
For attribute handlers and IDL property listeners, we still use
onbegin/onrepeat/onend as before.
The new behavior matches Blink, simplifies the code, and will let us
improve performance by storing event listeners in a map keyed by event
type atom (bug 1834370).
Differential Revision: https://phabricator.services.mozilla.com/D184532
This commit is contained in:
@@ -976,20 +976,13 @@ static nsAtom* GetEventTypeFromMessage(EventMessage aEventMessage) {
|
||||
}
|
||||
}
|
||||
|
||||
// Because of SVG/SMIL we have several atoms mapped to the same
|
||||
// id, but we can rely on MESSAGE_TO_EVENT to map id to only one atom.
|
||||
static bool ShouldAddEventToStringEventTable(const EventNameMapping& aMapping) {
|
||||
MOZ_ASSERT(aMapping.mAtom);
|
||||
return GetEventTypeFromMessage(aMapping.mMessage) == aMapping.mAtom;
|
||||
}
|
||||
|
||||
bool nsContentUtils::InitializeEventTable() {
|
||||
NS_ASSERTION(!sAtomEventTable, "EventTable already initialized!");
|
||||
NS_ASSERTION(!sStringEventTable, "EventTable already initialized!");
|
||||
|
||||
static const EventNameMapping eventArray[] = {
|
||||
#define EVENT(name_, _message, _type, _class) \
|
||||
{nsGkAtoms::on##name_, _type, _message, _class, false},
|
||||
{nsGkAtoms::on##name_, _type, _message, _class},
|
||||
#define WINDOW_ONLY_EVENT EVENT
|
||||
#define DOCUMENT_ONLY_EVENT EVENT
|
||||
#define NON_IDL_EVENT EVENT
|
||||
@@ -1010,12 +1003,10 @@ bool nsContentUtils::InitializeEventTable() {
|
||||
MOZ_ASSERT(!sAtomEventTable->Contains(eventArray[i].mAtom),
|
||||
"Double-defining event name; fix your EventNameList.h");
|
||||
sAtomEventTable->InsertOrUpdate(eventArray[i].mAtom, eventArray[i]);
|
||||
if (ShouldAddEventToStringEventTable(eventArray[i])) {
|
||||
sStringEventTable->InsertOrUpdate(
|
||||
Substring(nsDependentAtomString(eventArray[i].mAtom), 2),
|
||||
eventArray[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -4565,13 +4556,6 @@ nsAtom* nsContentUtils::GetEventMessageAndAtom(
|
||||
mapping.mMessage = eUnidentifiedEvent;
|
||||
mapping.mType = EventNameType_None;
|
||||
mapping.mEventClassID = eBasicEventClass;
|
||||
// This is a slow hashtable call, but at least we cache the result for the
|
||||
// following calls. Because GetEventMessageAndAtomForListener utilizes
|
||||
// sStringEventTable, it needs to know in which cases sStringEventTable
|
||||
// doesn't contain the information it needs so that it can use
|
||||
// sAtomEventTable instead.
|
||||
mapping.mMaybeSpecialSVGorSMILEvent =
|
||||
GetEventMessage(atom) != eUnidentifiedEvent;
|
||||
sStringEventTable->InsertOrUpdate(aName, mapping);
|
||||
return mapping.mAtom;
|
||||
}
|
||||
@@ -4581,35 +4565,24 @@ EventMessage nsContentUtils::GetEventMessageAndAtomForListener(
|
||||
const nsAString& aName, nsAtom** aOnName) {
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Our hashtables are not threadsafe");
|
||||
|
||||
// Because of SVG/SMIL sStringEventTable contains a subset of the event names
|
||||
// comparing to the sAtomEventTable. However, usually sStringEventTable
|
||||
// contains the information we need, so in order to reduce hashtable
|
||||
// lookups, start from it.
|
||||
// Check sStringEventTable for a matching entry. This will only fail for
|
||||
// user-defined event types.
|
||||
EventNameMapping mapping;
|
||||
EventMessage msg = eUnidentifiedEvent;
|
||||
RefPtr<nsAtom> atom;
|
||||
if (sStringEventTable->Get(aName, &mapping)) {
|
||||
if (mapping.mMaybeSpecialSVGorSMILEvent) {
|
||||
// Try the atom version so that we should get the right message for
|
||||
// SVG/SMIL.
|
||||
atom = NS_AtomizeMainThread(u"on"_ns + aName);
|
||||
msg = GetEventMessage(atom);
|
||||
} else {
|
||||
atom = mapping.mAtom;
|
||||
msg = mapping.mMessage;
|
||||
RefPtr<nsAtom> atom = mapping.mAtom;
|
||||
atom.forget(aOnName);
|
||||
return mapping.mMessage;
|
||||
}
|
||||
|
||||
// sStringEventTable did not contain an entry for this event type string.
|
||||
// Call GetEventMessageAndAtom, which will create an event type atom and
|
||||
// cache it in sStringEventTable for future calls.
|
||||
EventMessage msg = eUnidentifiedEvent;
|
||||
RefPtr<nsAtom> atom = GetEventMessageAndAtom(aName, eBasicEventClass, &msg);
|
||||
atom.forget(aOnName);
|
||||
return msg;
|
||||
}
|
||||
|
||||
// GetEventMessageAndAtom will cache the event type for the future usage...
|
||||
GetEventMessageAndAtom(aName, eBasicEventClass, &msg);
|
||||
|
||||
// ...and then call this method recursively to get the message and atom from
|
||||
// now updated sStringEventTable.
|
||||
return GetEventMessageAndAtomForListener(aName, aOnName);
|
||||
}
|
||||
|
||||
static nsresult GetEventAndTarget(Document* aDoc, nsISupports* aTarget,
|
||||
const nsAString& aEventName,
|
||||
CanBubble aCanBubble, Cancelable aCancelable,
|
||||
|
||||
@@ -242,9 +242,6 @@ struct EventNameMapping {
|
||||
int32_t mType;
|
||||
mozilla::EventMessage mMessage;
|
||||
mozilla::EventClassID mEventClassID;
|
||||
// True if mAtom is possibly used by special SVG/SMIL events, but
|
||||
// mMessage is eUnidentifiedEvent. See EventNameList.h
|
||||
bool mMaybeSpecialSVGorSMILEvent;
|
||||
};
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@@ -435,24 +435,31 @@ NON_IDL_EVENT(systemstatusbarclick, eXULSystemStatusBarClick, EventNameType_XUL,
|
||||
NON_IDL_EVENT(SVGLoad, eSVGLoad, EventNameType_None, eBasicEventClass)
|
||||
NON_IDL_EVENT(SVGScroll, eSVGScroll, EventNameType_None, eBasicEventClass)
|
||||
|
||||
// Only map the ID to the real event name when MESSAGE_TO_EVENT is defined.
|
||||
#ifndef MESSAGE_TO_EVENT
|
||||
EVENT(begin, eSMILBeginEvent, EventNameType_SMIL, eBasicEventClass)
|
||||
#endif
|
||||
// The three SMIL animation events. We mark these as NON_IDL_EVENT even though
|
||||
// there exist IDL properties for them, because the IDL properties have
|
||||
// different names (onbegin/onend/onrepeat rather than
|
||||
// onbeginEvent/onendEvent/onrepeatEvent).
|
||||
// And we use EventNameType_None because we don't want IsEventAttributeName to
|
||||
// return true for onbeginEvent etc.
|
||||
NON_IDL_EVENT(beginEvent, eSMILBeginEvent, EventNameType_None,
|
||||
eSMILTimeEventClass)
|
||||
// Only map the ID to the real event name when MESSAGE_TO_EVENT is defined.
|
||||
#ifndef MESSAGE_TO_EVENT
|
||||
EVENT(end, eSMILEndEvent, EventNameType_SMIL, eBasicEventClass)
|
||||
#endif
|
||||
NON_IDL_EVENT(endEvent, eSMILEndEvent, EventNameType_None, eSMILTimeEventClass)
|
||||
// Only map the ID to the real event name when MESSAGE_TO_EVENT is defined.
|
||||
#ifndef MESSAGE_TO_EVENT
|
||||
EVENT(repeat, eSMILRepeatEvent, EventNameType_SMIL, eBasicEventClass)
|
||||
#endif
|
||||
NON_IDL_EVENT(repeatEvent, eSMILRepeatEvent, EventNameType_None,
|
||||
eSMILTimeEventClass)
|
||||
|
||||
#ifndef MESSAGE_TO_EVENT
|
||||
// Repeat the SMIL animation events once more without the Event suffix,
|
||||
// so that IsEventAttributeName() will return the right thing for these events.
|
||||
// We use eUnidentifiedEvent here so that we don't accidentally treat these
|
||||
// as alternate event names for the actual events.
|
||||
// See bug 1845422 for cleaning this up.
|
||||
NON_IDL_EVENT(begin, eUnidentifiedEvent, EventNameType_SMIL,
|
||||
eSMILTimeEventClass)
|
||||
NON_IDL_EVENT(end, eUnidentifiedEvent, EventNameType_SMIL, eSMILTimeEventClass)
|
||||
NON_IDL_EVENT(repeat, eUnidentifiedEvent, EventNameType_SMIL,
|
||||
eSMILTimeEventClass)
|
||||
#endif
|
||||
|
||||
NON_IDL_EVENT(MozAfterPaint, eAfterPaint, EventNameType_None, eBasicEventClass)
|
||||
|
||||
NON_IDL_EVENT(MozScrolledAreaChanged, eScrolledAreaChanged, EventNameType_None,
|
||||
@@ -530,7 +537,7 @@ EVENT(webkitTransitionEnd, eWebkitTransitionEnd, EventNameType_All,
|
||||
// These are only here so that IsEventAttributeName() will return the right
|
||||
// thing for these events. We could probably remove them if we used
|
||||
// Element::GetEventNameForAttr on the input to IsEventAttributeName before
|
||||
// looking it up in the hashtable...
|
||||
// looking it up in the hashtable... see bug 1845422.
|
||||
EVENT(webkitanimationend, eUnidentifiedEvent, EventNameType_All,
|
||||
eAnimationEventClass)
|
||||
EVENT(webkitanimationiteration, eUnidentifiedEvent, EventNameType_All,
|
||||
|
||||
@@ -71,6 +71,33 @@ class SVGAnimationElement : public SVGAnimationElementBase, public SVGTests {
|
||||
void EndElement(ErrorResult& rv) { EndElementAt(0.f, rv); }
|
||||
void EndElementAt(float offset, ErrorResult& rv);
|
||||
|
||||
// Manually implement onbegin/onrepeat/onend IDL property getters/setters.
|
||||
// We don't autogenerate these methods because the property name differs
|
||||
// from the event type atom - the event type atom has an extra 'Event' tacked
|
||||
// on at the end. (i.e. 'onbegin' corresponds to an event whose name is
|
||||
// literally 'beginEvent' rather than 'begin')
|
||||
|
||||
EventHandlerNonNull* GetOnbegin() {
|
||||
return GetEventHandler(nsGkAtoms::onbeginEvent);
|
||||
}
|
||||
void SetOnbegin(EventHandlerNonNull* handler) {
|
||||
EventTarget::SetEventHandler(nsGkAtoms::onbeginEvent, handler);
|
||||
}
|
||||
|
||||
EventHandlerNonNull* GetOnrepeat() {
|
||||
return GetEventHandler(nsGkAtoms::onrepeatEvent);
|
||||
}
|
||||
void SetOnrepeat(EventHandlerNonNull* handler) {
|
||||
EventTarget::SetEventHandler(nsGkAtoms::onrepeatEvent, handler);
|
||||
}
|
||||
|
||||
EventHandlerNonNull* GetOnend() {
|
||||
return GetEventHandler(nsGkAtoms::onendEvent);
|
||||
}
|
||||
void SetOnend(EventHandlerNonNull* handler) {
|
||||
EventTarget::SetEventHandler(nsGkAtoms::onendEvent, handler);
|
||||
}
|
||||
|
||||
// SVGTests
|
||||
SVGElement* AsSVGElement() final { return this; }
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
// In the pass case, we'll get an end event almost immediately.
|
||||
// In the failure case, wait 30s before giving up.
|
||||
timeoutID = window.setTimeout(giveUp, 30000);
|
||||
anim.addEventListener('end', finish, true);
|
||||
anim.addEventListener('endEvent', finish, true);
|
||||
}
|
||||
|
||||
function giveUp() {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
// In the pass case, we'll get an end event almost immediately.
|
||||
// In the failure case, wait 30s before giving up.
|
||||
timeoutID = window.setTimeout(giveUp, 30000);
|
||||
anim.addEventListener('end', finish, true);
|
||||
anim.addEventListener('endEvent', finish, true);
|
||||
}
|
||||
|
||||
function giveUp() {
|
||||
|
||||
75
testing/web-platform/tests/svg/animations/custom-events.html
Normal file
75
testing/web-platform/tests/svg/animations/custom-events.html
Normal file
@@ -0,0 +1,75 @@
|
||||
<!DOCTYPE html>
|
||||
<title>Custom events with the names "end" and "endEvent" and their effects on various types of event listeners</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<svg height="0">
|
||||
<rect width="100" height="100" fill="blue">
|
||||
<animate attributeName="x" begin="0s" from="0" to="100"
|
||||
id="targetWithAttributeHandlers"
|
||||
onend="gOnEndHandlerCallCount++"
|
||||
onendEvent="gNonexistentOnEndEventHandlerCallCount++"/>
|
||||
<animate attributeName="y" begin="0s" from="0" to="100"
|
||||
id="targetWithIDLListeners"/>
|
||||
<animate attributeName="width" begin="0s" from="100" to="120"
|
||||
id="targetWithRegularListeners"/>
|
||||
</rect>
|
||||
</svg>
|
||||
<script>
|
||||
// This test checks how various types of event handlers / listeners react to custom
|
||||
// events with the names "end" and "endEvent".
|
||||
// The SVG spec does not define an event called "end" - the animation event is called "endEvent".
|
||||
// The SVG spec does not define an IDL property called "onendEvent", only one called "onend".
|
||||
// The SVG spec does not define an attribute called "onendEvent", only one called "onend".
|
||||
|
||||
// Incremented in the "onend" attribute event handler.
|
||||
gOnEndHandlerCallCount = 0;
|
||||
// "onendEvent" is an invalid attribute name so this should never be incremented.
|
||||
gNonexistentOnEndEventHandlerCallCount = 0;
|
||||
// Incremented in the "onend" IDL property event listener.
|
||||
gOnEndListenerCallCount = 0;
|
||||
// "onendEvent" is an unrecognized property name so this should never be incremented.
|
||||
gNonexistentOnEndEventListenerCallCount = 0;
|
||||
// Incremented in the "endEvent" event listener.
|
||||
gEndEventListenerCallCount = 0;
|
||||
// Incremented in the "end" event listener. This should only happen for manually-created events with the name "end".
|
||||
gEndListenerCallCount = 0;
|
||||
|
||||
let targetWithAttributeHandlers = document.getElementById("targetWithAttributeHandlers");
|
||||
let targetWithIDLListeners = document.getElementById("targetWithIDLListeners");
|
||||
targetWithIDLListeners.onend = () => { gOnEndListenerCallCount++; };
|
||||
targetWithIDLListeners.onendEvent = () => { gNonexistentOnEndEventListenerCallCount++; };
|
||||
let targetWithRegularListeners = document.getElementById("targetWithRegularListeners");
|
||||
targetWithRegularListeners.addEventListener("endEvent", () => { gEndEventListenerCallCount++; });
|
||||
targetWithRegularListeners.addEventListener("end", () => { gEndListenerCallCount++; });
|
||||
|
||||
test(t => {
|
||||
targetWithAttributeHandlers.dispatchEvent(new Event("end"));
|
||||
assert_equals(gOnEndHandlerCallCount, 0);
|
||||
assert_equals(gNonexistentOnEndEventHandlerCallCount, 0);
|
||||
targetWithIDLListeners.dispatchEvent(new Event("end"));
|
||||
assert_equals(gOnEndListenerCallCount, 0);
|
||||
assert_equals(gNonexistentOnEndEventListenerCallCount, 0);
|
||||
targetWithRegularListeners.dispatchEvent(new Event("end"));
|
||||
assert_equals(gEndEventListenerCallCount, 0);
|
||||
assert_equals(gEndListenerCallCount, 1);
|
||||
}, "custom events with the name 'end' should only call the event listener for the event 'end' and no attribute handlers or IDL listeners");
|
||||
|
||||
test(t => {
|
||||
gOnEndHandlerCallCount = 0;
|
||||
gNonexistentOnEndEventHandlerCallCount = 0;
|
||||
gOnEndListenerCallCount = 0;
|
||||
gNonexistentOnEndEventListenerCallCount = 0;
|
||||
gEndEventListenerCallCount = 0;
|
||||
gEndListenerCallCount = 0;
|
||||
targetWithAttributeHandlers.dispatchEvent(new Event("endEvent"));
|
||||
assert_equals(gOnEndHandlerCallCount, 1);
|
||||
assert_equals(gNonexistentOnEndEventHandlerCallCount, 0);
|
||||
targetWithIDLListeners.dispatchEvent(new Event("endEvent"));
|
||||
assert_equals(gOnEndListenerCallCount, 1);
|
||||
assert_equals(gNonexistentOnEndEventListenerCallCount, 0);
|
||||
targetWithRegularListeners.dispatchEvent(new Event("endEvent"));
|
||||
assert_equals(gEndEventListenerCallCount, 1);
|
||||
assert_equals(gEndListenerCallCount, 0);
|
||||
}, "custom events with the name 'endEvent' should call 'onend' attribute handlers and IDL property listeners, and 'endEvent' listeners");
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<title>Event handling of endEvent with various types of event listeners</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<svg height="0">
|
||||
<rect width="100" height="100" fill="blue">
|
||||
<animate attributeName="x" begin="0s" from="0" to="100" dur="5ms" end="5ms"
|
||||
id="targetWithAttributeHandlers"
|
||||
onend="gOnEndHandlerCallCount++"
|
||||
onendEvent="gNonexistentOnEndEventHandlerCallCount++"/>
|
||||
<animate attributeName="y" begin="0s" from="0" to="100" dur="5ms" end="5ms"
|
||||
id="targetWithIDLListeners"/>
|
||||
<animate attributeName="width" begin="0s" from="100" to="120" dur="5ms" end="5ms"
|
||||
id="targetWithRegularListeners"/>
|
||||
<set attributeName="visibility" begin="0s" end="10ms" from="visible" to="visible"
|
||||
id="timekeeper"/>
|
||||
</rect>
|
||||
</svg>
|
||||
<script>
|
||||
// This test checks how various types of event handlers / listeners react to an
|
||||
// animation end event.
|
||||
// The SVG spec does not define an event called "end" - the animation event is called "endEvent".
|
||||
// The SVG spec does not define an IDL property called "onendEvent", only one called "onend".
|
||||
// The SVG spec does not define an attribute called "onendEvent", only one called "onend".
|
||||
|
||||
// Incremented in the "onend" attribute event handler.
|
||||
gOnEndHandlerCallCount = 0;
|
||||
// "onendEvent" is an invalid attribute name so this should never be incremented.
|
||||
gNonexistentOnEndEventHandlerCallCount = 0;
|
||||
// Incremented in the "onend" IDL property event listener.
|
||||
gOnEndListenerCallCount = 0;
|
||||
// "onendEvent" is an unrecognized property name so this should never be incremented.
|
||||
gNonexistentOnEndEventListenerCallCount = 0;
|
||||
// Incremented in the "endEvent" event listener.
|
||||
gEndEventListenerCallCount = 0;
|
||||
// Incremented in the "end" event listener. This should only happen for custom
|
||||
// events with the name "end" (which are not used in this test).
|
||||
gEndListenerCallCount = 0;
|
||||
|
||||
let targetWithAttributeHandlers = document.getElementById("targetWithAttributeHandlers");
|
||||
let targetWithIDLListeners = document.getElementById("targetWithIDLListeners");
|
||||
targetWithIDLListeners.onend = () => { gOnEndListenerCallCount++; };
|
||||
targetWithIDLListeners.onendEvent = () => { gNonexistentOnEndEventListenerCallCount++; };
|
||||
let targetWithRegularListeners = document.getElementById("targetWithRegularListeners");
|
||||
targetWithRegularListeners.addEventListener("endEvent", () => { gEndEventListenerCallCount++; });
|
||||
targetWithRegularListeners.addEventListener("end", () => { gEndListenerCallCount++; });
|
||||
|
||||
async_test(t => {
|
||||
let timekeeper = document.getElementById("timekeeper");
|
||||
timekeeper.addEventListener("endEvent", t.step_func(() => {
|
||||
requestAnimationFrame(t.step_func_done(() => {
|
||||
assert_equals(gOnEndHandlerCallCount, 1);
|
||||
assert_equals(gNonexistentOnEndEventHandlerCallCount, 0);
|
||||
assert_equals(gOnEndListenerCallCount, 1);
|
||||
assert_equals(gNonexistentOnEndEventListenerCallCount, 0);
|
||||
assert_equals(gEndEventListenerCallCount, 1);
|
||||
assert_equals(gEndListenerCallCount, 0);
|
||||
}));
|
||||
}));
|
||||
}, "When the animation ends, only the 'onend' attribute + IDL listeners and the 'endEvent' listener should be called");
|
||||
|
||||
</script>
|
||||
Reference in New Issue
Block a user