diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 4a3e4b82a072..9c690ee4dac8 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -956,7 +956,8 @@ bool nsContentUtils::IsExternalProtocol(nsIURI* aURI) { return NS_SUCCEEDED(rv) && doesNotReturnData; } -static nsAtom* GetEventTypeFromMessage(EventMessage aEventMessage) { +/* static */ +nsAtom* nsContentUtils::GetEventTypeFromMessage(EventMessage aEventMessage) { switch (aEventMessage) { #define MESSAGE_TO_EVENT(name_, message_, type_, struct_) \ case message_: \ diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 07e4cbb6510a..043786a843a5 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -1692,6 +1692,11 @@ class nsContentUtils { */ static EventMessage GetEventMessage(nsAtom* aName); + /** + * Return the event type atom for a given event message. + */ + static nsAtom* GetEventTypeFromMessage(EventMessage aEventMessage); + /** * Returns the EventMessage and nsAtom to be used for event listener * registration. diff --git a/dom/events/EventListenerManager.cpp b/dom/events/EventListenerManager.cpp index c9aa9490b25b..e5b9ccf00f00 100644 --- a/dom/events/EventListenerManager.cpp +++ b/dom/events/EventListenerManager.cpp @@ -9,6 +9,7 @@ #include "js/loader/LoadedScript.h" #include "mozilla/BasicEvents.h" +#include "mozilla/BinarySearch.h" #include "mozilla/CycleCollectedJSRuntime.h" #include "mozilla/DOMEventTargetHelper.h" #include "mozilla/EventDispatcher.h" @@ -63,11 +64,6 @@ namespace mozilla { using namespace dom; using namespace hal; -#define EVENT_TYPE_EQUALS(ls, message, userType, allEvents) \ - ((ls->mEventMessage == message && \ - (ls->mEventMessage != eUnidentifiedEvent || ls->mTypeAtom == userType)) || \ - (allEvents && ls->mAllEvents)) - static const uint32_t kAllMutationBits = NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED | NS_EVENT_BITS_MUTATION_NODEINSERTED | NS_EVENT_BITS_MUTATION_NODEREMOVED | @@ -98,6 +94,29 @@ static uint32_t MutationBitForEventType(EventMessage aEventType) { return 0; } +class ListenerMapEntryComparator { + public: + explicit ListenerMapEntryComparator(nsAtom* aTarget) + : mAddressOfEventType(reinterpret_cast(aTarget)) {} + + int operator()( + const EventListenerManager::EventListenerMapEntry& aEntry) const { + uintptr_t value = reinterpret_cast(aEntry.mTypeAtom.get()); + if (mAddressOfEventType == value) { + return 0; + } + + if (mAddressOfEventType < value) { + return -1; + } + + return 1; + } + + private: + const uintptr_t mAddressOfEventType; // the address of the atom, can be 0 +}; + uint32_t EventListenerManager::sMainThreadCreatedCount = 0; EventListenerManagerBase::EventListenerManagerBase() @@ -147,10 +166,19 @@ void EventListenerManager::RemoveAllListenersSilently() { return; } mClearingListeners = true; - mListeners.Clear(); + mListenerMap.Clear(); mClearingListeners = false; } +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + EventListenerManager::EventListenerMap& aField, const char* aName, + uint32_t aFlags = 0) { + for (const auto& entry : aField.mEntries) { + ImplCycleCollectionTraverse(aCallback, *entry.mListeners, aName); + } +} + inline void ImplCycleCollectionTraverse( nsCycleCollectionTraversalCallback& aCallback, EventListenerManager::Listener& aField, const char* aName, @@ -179,7 +207,7 @@ inline void ImplCycleCollectionTraverse( NS_IMPL_CYCLE_COLLECTION_CLASS(EventListenerManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(EventListenerManager) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListeners) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerMap); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EventListenerManager) @@ -210,6 +238,9 @@ void EventListenerManager::AddEventListenerInternal( bool aAllEvents, AbortSignal* aSignal) { MOZ_ASSERT((aEventMessage && aTypeAtom) || aAllEvents, // all-events listener "Missing type"); + MOZ_ASSERT_IF( + aEventMessage != eUnidentifiedEvent && !aAllEvents, + aTypeAtom == nsContentUtils::GetEventTypeFromMessage(aEventMessage)); if (!aListenerHolder || mClearingListeners) { return; @@ -223,15 +254,15 @@ void EventListenerManager::AddEventListenerInternal( // know that there's an EventListenerHolder on the stack holding a strong ref // to the listener. - Listener* listener; - uint32_t count = mListeners.Length(); - for (uint32_t i = 0; i < count; i++) { - listener = &mListeners.ElementAt(i); + RefPtr listeners = + aAllEvents ? mListenerMap.GetOrCreateListenersForAllEvents() + : mListenerMap.GetOrCreateListenersForType(aTypeAtom); + + for (const Listener& listener : listeners->NonObservingRange()) { // mListener == aListenerHolder is the last one, since it can be a bit slow. - if (listener->mListenerIsHandler == aHandler && - listener->mFlags.EqualsForAddition(aFlags) && - EVENT_TYPE_EQUALS(listener, aEventMessage, aTypeAtom, aAllEvents) && - listener->mListener == aListenerHolder) { + if (listener.mListenerIsHandler == aHandler && + listener.mFlags.EqualsForAddition(aFlags) && + listener.mListener == aListenerHolder) { return; } } @@ -239,8 +270,7 @@ void EventListenerManager::AddEventListenerInternal( ClearNoListenersForEvents(); mNoListenerForEventAtom = nullptr; - listener = - aAllEvents ? mListeners.InsertElementAt(0) : mListeners.AppendElement(); + Listener* listener = listeners->AppendElement(); listener->mEventMessage = aEventMessage; listener->mTypeAtom = aTypeAtom; listener->mFlags = aFlags; @@ -771,48 +801,41 @@ void EventListenerManager::RemoveEventListenerInternal( return; } - Listener* listener; + Maybe entryIndex = aAllEvents + ? mListenerMap.EntryIndexForAllEvents() + : mListenerMap.EntryIndexForType(aUserType); + if (!entryIndex) { + return; + } - uint32_t count = mListeners.Length(); - bool deviceType = IsDeviceType(aEventMessage); + ListenerArray& listenerArray = *mListenerMap.mEntries[*entryIndex].mListeners; - RefPtr kungFuDeathGrip(this); - - for (uint32_t i = 0; i < count; ++i) { - listener = &mListeners.ElementAt(i); - if (EVENT_TYPE_EQUALS(listener, aEventMessage, aUserType, aAllEvents)) { + Maybe listenerIndex = [&]() -> Maybe { + uint32_t count = listenerArray.Length(); + for (uint32_t i = 0; i < count; ++i) { + Listener* listener = &listenerArray.ElementAt(i); if (listener->mListener == aListenerHolder && listener->mFlags.EqualsForRemoval(aFlags)) { - mListeners.RemoveElementAt(i); - NotifyEventListenerRemoved(aUserType); - if (!aAllEvents && deviceType) { - DisableDevice(aEventMessage); - } - return; + return Some(i); } } - } -} + return Nothing(); + }(); -bool EventListenerManager::Listener::MatchesEventMessage( - const WidgetEvent* aEvent, EventMessage aEventMessage) const { - MOZ_ASSERT(aEventMessage == aEvent->mMessage || - aEventMessage == EventListenerManager::GetLegacyEventMessage( - aEvent->mMessage), - "aEvent and aEventMessage should agree, modulo legacyness"); - - if (MOZ_UNLIKELY(mAllEvents)) { - return true; + if (!listenerIndex) { + return; } - // This is slightly different from EVENT_TYPE_EQUALS in that it returns - // true even when aEvent->mMessage == eUnidentifiedEvent and - // mEventMessage != eUnidentifiedEvent as long as the atoms are - // the same. - if (aEvent->mMessage == eUnidentifiedEvent) { - return mTypeAtom == aEvent->mSpecifiedEventType; + listenerArray.RemoveElementAt(*listenerIndex); + if (listenerArray.IsEmpty()) { + mListenerMap.mEntries.RemoveElementAt(*entryIndex); + } + + RefPtr kungFuDeathGrip(this); + NotifyEventListenerRemoved(aUserType); + if (!aAllEvents && IsDeviceType(aEventMessage)) { + DisableDevice(aEventMessage); } - return mEventMessage == aEventMessage; } static bool IsDefaultPassiveWhenOnRoot(EventMessage aMessage) { @@ -885,15 +908,18 @@ void EventListenerManager::RemoveEventListenerByType( } EventListenerManager::Listener* EventListenerManager::FindEventHandler( - EventMessage aEventMessage, nsAtom* aTypeAtom) { + nsAtom* aTypeAtom) { // Run through the listeners for this type and see if a script // listener is registered - Listener* listener; - uint32_t count = mListeners.Length(); + RefPtr listeners = mListenerMap.GetListenersForType(aTypeAtom); + if (!listeners) { + return nullptr; + } + + uint32_t count = listeners->Length(); for (uint32_t i = 0; i < count; ++i) { - listener = &mListeners.ElementAt(i); - if (listener->mListenerIsHandler && - EVENT_TYPE_EQUALS(listener, aEventMessage, aTypeAtom, false)) { + Listener* listener = &listeners->ElementAt(i); + if (listener->mListenerIsHandler) { return listener; } } @@ -906,7 +932,7 @@ EventListenerManager::Listener* EventListenerManager::SetEventHandlerInternal( MOZ_ASSERT(aName); EventMessage eventMessage = GetEventMessage(aName); - Listener* listener = FindEventHandler(eventMessage, aName); + Listener* listener = FindEventHandler(aName); if (!listener) { // If we didn't find a script listener or no listeners existed @@ -921,7 +947,7 @@ EventListenerManager::Listener* EventListenerManager::SetEventHandlerInternal( AddEventListenerInternal(EventListenerHolder(jsEventHandler), eventMessage, aName, flags, true); - listener = FindEventHandler(eventMessage, aName); + listener = FindEventHandler(aName); } else { JSEventHandler* jsEventHandler = listener->GetJSEventHandler(); MOZ_ASSERT(jsEventHandler, @@ -1033,15 +1059,38 @@ void EventListenerManager::RemoveEventHandler(nsAtom* aName) { return; } - EventMessage eventMessage = GetEventMessage(aName); - Listener* listener = FindEventHandler(eventMessage, aName); + Maybe entryIndex = mListenerMap.EntryIndexForType(aName); + if (!entryIndex) { + return; + } - if (listener) { - mListeners.RemoveElementAt(uint32_t(listener - &mListeners.ElementAt(0))); - NotifyEventListenerRemoved(aName); - if (IsDeviceType(eventMessage)) { - DisableDevice(eventMessage); + ListenerArray& listenerArray = *mListenerMap.mEntries[*entryIndex].mListeners; + + Maybe listenerIndex = [&]() -> Maybe { + uint32_t count = listenerArray.Length(); + for (uint32_t i = 0; i < count; ++i) { + Listener* listener = &listenerArray.ElementAt(i); + if (listener->mListenerIsHandler) { + return Some(i); + } } + return Nothing(); + }(); + + if (!listenerIndex) { + return; + } + + listenerArray.RemoveElementAt(*listenerIndex); + if (listenerArray.IsEmpty()) { + mListenerMap.mEntries.RemoveElementAt(*entryIndex); + } + + RefPtr kungFuDeathGrip(this); + NotifyEventListenerRemoved(aName); + EventMessage eventMessage = GetEventMessage(aName); + if (IsDeviceType(eventMessage)) { + DisableDevice(eventMessage); } } @@ -1366,6 +1415,66 @@ already_AddRefed EventListenerManager::WindowFromListener( return innerWindow.forget(); } +Maybe EventListenerManager::EventListenerMap::EntryIndexForType( + nsAtom* aTypeAtom) const { + MOZ_ASSERT(aTypeAtom); + + size_t matchIndexOrInsertionPoint = 0; + bool foundMatch = BinarySearchIf(mEntries, 0, mEntries.Length(), + ListenerMapEntryComparator(aTypeAtom), + &matchIndexOrInsertionPoint); + return foundMatch ? Some(matchIndexOrInsertionPoint) : Nothing(); +} + +Maybe EventListenerManager::EventListenerMap::EntryIndexForAllEvents() + const { + // If we have an entry for "all events listeners", it'll be at the beginning + // of the list and its type atom will be null. + return !mEntries.IsEmpty() && mEntries[0].mTypeAtom == nullptr ? Some(0) + : Nothing(); +} + +RefPtr +EventListenerManager::EventListenerMap::GetListenersForType( + nsAtom* aTypeAtom) const { + Maybe index = EntryIndexForType(aTypeAtom); + return index ? mEntries[*index].mListeners : nullptr; +} + +RefPtr +EventListenerManager::EventListenerMap::GetListenersForAllEvents() const { + Maybe index = EntryIndexForAllEvents(); + return index ? mEntries[*index].mListeners : nullptr; +} + +RefPtr +EventListenerManager::EventListenerMap::GetOrCreateListenersForType( + nsAtom* aTypeAtom) { + MOZ_ASSERT(aTypeAtom); + size_t matchIndexOrInsertionPoint = 0; + bool foundMatch = BinarySearchIf(mEntries, 0, mEntries.Length(), + ListenerMapEntryComparator(aTypeAtom), + &matchIndexOrInsertionPoint); + if (foundMatch) { + return mEntries[matchIndexOrInsertionPoint].mListeners; + } + RefPtr listeners = MakeRefPtr(); + mEntries.InsertElementAt(matchIndexOrInsertionPoint, + EventListenerMapEntry{aTypeAtom, listeners}); + + return listeners; +} + +RefPtr +EventListenerManager::EventListenerMap::GetOrCreateListenersForAllEvents() { + RefPtr listeners = GetListenersForAllEvents(); + if (!listeners) { + listeners = MakeRefPtr(); + mEntries.InsertElementAt(0, EventListenerMapEntry{nullptr, listeners}); + } + return listeners; +} + void EventListenerManager::HandleEventInternal(nsPresContext* aPresContext, WidgetEvent* aEvent, Event** aDOMEvent, @@ -1383,6 +1492,93 @@ void EventListenerManager::HandleEventInternal(nsPresContext* aPresContext, aEvent->PreventDefault(); } + if (aEvent->mFlags.mImmediatePropagationStopped) { + return; + } + + Maybe userInputStatePusher; + Maybe popupStatePusher; + if (mIsMainThreadELM) { + userInputStatePusher.emplace(UserActivation::IsUserInteractionEvent(aEvent), + aEvent); + popupStatePusher.emplace( + PopupBlocker::GetEventPopupControlState(aEvent, *aDOMEvent)); + } + + EventMessage eventMessage = aEvent->mMessage; + RefPtr typeAtom = + eventMessage == eUnidentifiedEvent + ? aEvent->mSpecifiedEventType.get() + : nsContentUtils::GetEventTypeFromMessage(eventMessage); + if (!typeAtom) { + // Some messages don't have a corresponding type atom, e.g. + // eMouseEnterIntoWidget. These events can't have a listener, so we + // can stop here. + return; + } + + bool hasAnyListenerForEventType = false; + + // First, notify any "all events" listeners. + if (RefPtr listenersForAllEvents = + mListenerMap.GetListenersForAllEvents()) { + HandleEventWithListenerArray(listenersForAllEvents, typeAtom, eventMessage, + aPresContext, aEvent, aDOMEvent, + aCurrentTarget, aItemInShadowTree); + hasAnyListenerForEventType = true; + } + + // Now look for listeners for typeAtom, and call them if we have any. + bool hasAnyListenerMatchingGroup = false; + if (RefPtr listeners = + mListenerMap.GetListenersForType(typeAtom)) { + hasAnyListenerMatchingGroup = HandleEventWithListenerArray( + listeners, typeAtom, eventMessage, aPresContext, aEvent, aDOMEvent, + aCurrentTarget, aItemInShadowTree); + hasAnyListenerForEventType = true; + } + + if (!hasAnyListenerMatchingGroup && aEvent->IsTrusted()) { + // If we didn't find any matching listeners, and our event has a legacy + // version, check the listeners for the legacy version. + EventMessage legacyEventMessage = GetLegacyEventMessage(eventMessage); + if (legacyEventMessage != eventMessage) { + MOZ_ASSERT( + GetLegacyEventMessage(legacyEventMessage) == legacyEventMessage, + "Legacy event messages should not themselves have legacy versions"); + RefPtr legacyTypeAtom = + nsContentUtils::GetEventTypeFromMessage(legacyEventMessage); + if (RefPtr legacyListeners = + mListenerMap.GetListenersForType(legacyTypeAtom)) { + HandleEventWithListenerArray( + legacyListeners, legacyTypeAtom, legacyEventMessage, aPresContext, + aEvent, aDOMEvent, aCurrentTarget, aItemInShadowTree); + hasAnyListenerForEventType = true; + } + } + } + + aEvent->mCurrentTarget = nullptr; + + if (mIsMainThreadELM && !hasAnyListenerForEventType) { + if (aEvent->mMessage != eUnidentifiedEvent) { + mNoListenerForEvents[2] = mNoListenerForEvents[1]; + mNoListenerForEvents[1] = mNoListenerForEvents[0]; + mNoListenerForEvents[0] = aEvent->mMessage; + } else { + mNoListenerForEventAtom = aEvent->mSpecifiedEventType; + } + } + + if (aEvent->DefaultPrevented()) { + *aEventStatus = nsEventStatus_eConsumeNoDefault; + } +} + +bool EventListenerManager::HandleEventWithListenerArray( + ListenerArray* aListeners, nsAtom* aTypeAtom, EventMessage aEventMessage, + nsPresContext* aPresContext, WidgetEvent* aEvent, Event** aDOMEvent, + EventTarget* aCurrentTarget, bool aItemInShadowTree) { auto ensureDOMEvent = [&]() { if (!*aDOMEvent) { // Lazily create the DOM event. @@ -1396,136 +1592,88 @@ void EventListenerManager::HandleEventInternal(nsPresContext* aPresContext, return *aDOMEvent != nullptr; }; - Maybe userInputStatePusher; - Maybe popupStatePusher; - if (mIsMainThreadELM) { - userInputStatePusher.emplace(UserActivation::IsUserInteractionEvent(aEvent), - aEvent); - popupStatePusher.emplace( - PopupBlocker::GetEventPopupControlState(aEvent, *aDOMEvent)); + Maybe eventMessageAutoOverride; + bool isOverridingEventMessage = aEvent->mMessage != aEventMessage; + bool hasAnyListenerMatchingGroup = false; + bool didReplaceOnceListener = false; + + for (Listener& listenerRef : aListeners->EndLimitedRange()) { + Listener* listener = &listenerRef; + if (listener->mListenerType == Listener::eNoListener) { + // The listener is a placeholder value of a removed "once" listener. + continue; + } + if (!listener->mEnabled) { + // The listener has been disabled, for example by devtools. + continue; + } + if (!listener->MatchesEventGroup(aEvent)) { + continue; + } + hasAnyListenerMatchingGroup = true; + + // Check that the phase is same in event and event listener. Also check + // that the event is trusted or that the listener allows untrusted events. + if (!listener->MatchesEventPhase(aEvent) || + !listener->AllowsEventTrustedness(aEvent)) { + continue; + } + + Maybe listenerHolder; + if (listener->mFlags.mOnce) { + // Move the listener to the stack before handling the event. + // The order is important, otherwise the listener could be + // called again inside the listener. + listenerHolder.emplace(std::move(*listener)); + listener = listenerHolder.ptr(); + didReplaceOnceListener = true; + } + if (ensureDOMEvent()) { + if (isOverridingEventMessage && !eventMessageAutoOverride) { + // Override the domEvent's event-message (its .type) until we + // finish traversing listeners (when eventMessageAutoOverride + // destructs). + eventMessageAutoOverride.emplace(*aDOMEvent, aEventMessage); + } + if (!HandleEventSingleListener(listener, aEvent, *aDOMEvent, + aCurrentTarget, aItemInShadowTree)) { + break; + } + } } - bool hasListener = false; - bool usingLegacyMessage = false; - bool hasRemovedListener = false; - EventMessage eventMessage = aEvent->mMessage; - - while (!aEvent->mFlags.mImmediatePropagationStopped) { - Maybe legacyAutoOverride; - bool hasListenerForCurrentGroup = false; - for (Listener& listenerRef : mListeners.EndLimitedRange()) { - Listener* listener = &listenerRef; - if (!listener->MatchesEventMessage(aEvent, eventMessage)) { - continue; - } - if (listener->mListenerType == Listener::eNoListener) { - // The listener has been removed, it cannot handle anything. - continue; - } - if (!listener->mEnabled) { - // The listener has been disabled, for example by devtools. - continue; - } - hasListener = true; - if (!listener->MatchesEventGroup(aEvent)) { - continue; - } - hasListenerForCurrentGroup = true; - - // Check that the phase is same in event and event listener. Also check - // that the event is trusted or that the listener allows untrusted events. - if (!listener->MatchesEventPhase(aEvent) || - !listener->AllowsEventTrustedness(aEvent)) { - continue; - } - - Maybe listenerHolder; - if (listener->mFlags.mOnce) { - // Move the listener to the stack before handling the event. - // The order is important, otherwise the listener could be - // called again inside the listener. - listenerHolder.emplace(std::move(*listener)); - listener = listenerHolder.ptr(); - hasRemovedListener = true; - } - if (ensureDOMEvent()) { - if (usingLegacyMessage && !legacyAutoOverride) { - // Override the domEvent's event-message (its .type) until we - // finish traversing listeners (when legacyAutoOverride - // destructs) - legacyAutoOverride.emplace(*aDOMEvent, eventMessage); - } - if (!HandleEventSingleListener(listener, aEvent, *aDOMEvent, - aCurrentTarget, aItemInShadowTree)) { - break; - } - } - } - - // If we didn't find any matching listeners, and our event has a legacy - // version, we'll now switch to looking for that legacy version and we'll - // recheck our listeners. - if (hasListenerForCurrentGroup || usingLegacyMessage || - !aEvent->IsTrusted()) { - // No need to recheck listeners, because we already found a match, we - // already rechecked them, or it is not a trusted event. - break; - } - EventMessage legacyEventMessage = GetLegacyEventMessage(eventMessage); - if (legacyEventMessage == eventMessage) { - break; // There's no legacy version of our event; no need to recheck. - } - MOZ_ASSERT( - GetLegacyEventMessage(legacyEventMessage) == legacyEventMessage, - "Legacy event messages should not themselves have legacy versions"); - - // Recheck our listeners, using the legacy event message we just looked up: - eventMessage = legacyEventMessage; - usingLegacyMessage = true; - } - - aEvent->mCurrentTarget = nullptr; - - if (hasRemovedListener) { - // If there are any once listeners replaced with a placeholder in - // the loop above, we need to clean up them here. Note that, this - // could clear once listeners handled in some outer level as well, - // but that should not affect the result. - mListeners.NonObservingRemoveElementsBy([](const Listener& aListener) { + if (didReplaceOnceListener) { + // If there are any once listeners replaced with a placeholder during the + // loop above, we need to clean up them here. Note that this could clear + // once listeners handled in some outer level as well, but that should not + // affect the result. + size_t oldLength = aListeners->Length(); + aListeners->NonObservingRemoveElementsBy([](const Listener& aListener) { return aListener.mListenerType == Listener::eNoListener; }); - NotifyEventListenerRemoved(aEvent->mSpecifiedEventType); - if (IsDeviceType(aEvent->mMessage)) { - // This is a device-type event, we need to check whether we can - // disable device after removing the once listeners. - const auto [begin, end] = mListeners.NonObservingRange(); - const bool hasAnyListener = - std::any_of(begin, end, [aEvent](const Listener& listenerRef) { - const Listener* listener = &listenerRef; - return EVENT_TYPE_EQUALS(listener, aEvent->mMessage, - aEvent->mSpecifiedEventType, - /* all events */ false); - }); - - if (!hasAnyListener) { - DisableDevice(aEvent->mMessage); + size_t newLength = aListeners->Length(); + if (newLength == 0) { + // Remove the entry that has now become empty. + mListenerMap.mEntries.RemoveElementsBy([](EventListenerMapEntry& entry) { + return entry.mListeners->IsEmpty(); + }); + } + if (newLength < oldLength) { + // Call NotifyEventListenerRemoved once for every removed listener. + size_t removedCount = oldLength - newLength; + for (size_t i = 0; i < removedCount; i++) { + NotifyEventListenerRemoved(aTypeAtom); + } + if (IsDeviceType(aEventMessage)) { + // Call DisableDevice once for every removed listener. + for (size_t i = 0; i < removedCount; i++) { + DisableDevice(aEventMessage); + } } } } - if (mIsMainThreadELM && !hasListener) { - if (aEvent->mMessage != eUnidentifiedEvent) { - mNoListenerForEvents[2] = mNoListenerForEvents[1]; - mNoListenerForEvents[1] = mNoListenerForEvents[0]; - mNoListenerForEvents[0] = aEvent->mMessage; - } else { - mNoListenerForEventAtom = aEvent->mSpecifiedEventType; - } - } - - if (aEvent->DefaultPrevented()) { - *aEventStatus = nsEventStatus_eConsumeNoDefault; - } + return hasAnyListenerMatchingGroup; } void EventListenerManager::Disconnect() { @@ -1616,11 +1764,10 @@ void EventListenerManager::RemoveListenerForAllEvents( bool EventListenerManager::HasMutationListeners() { if (mMayHaveMutationListeners) { - uint32_t count = mListeners.Length(); - for (uint32_t i = 0; i < count; ++i) { - Listener* listener = &mListeners.ElementAt(i); - if (listener->mEventMessage >= eLegacyMutationEventFirst && - listener->mEventMessage <= eLegacyMutationEventLast) { + for (const auto& entry : mListenerMap.mEntries) { + EventMessage message = GetEventMessage(entry.mTypeAtom); + if (message >= eLegacyMutationEventFirst && + message <= eLegacyMutationEventLast) { return true; } } @@ -1632,15 +1779,14 @@ bool EventListenerManager::HasMutationListeners() { uint32_t EventListenerManager::MutationListenerBits() { uint32_t bits = 0; if (mMayHaveMutationListeners) { - uint32_t count = mListeners.Length(); - for (uint32_t i = 0; i < count; ++i) { - Listener* listener = &mListeners.ElementAt(i); - if (listener->mEventMessage >= eLegacyMutationEventFirst && - listener->mEventMessage <= eLegacyMutationEventLast) { - if (listener->mEventMessage == eLegacySubtreeModified) { + for (const auto& entry : mListenerMap.mEntries) { + EventMessage message = GetEventMessage(entry.mTypeAtom); + if (message >= eLegacyMutationEventFirst && + message <= eLegacyMutationEventLast) { + if (message == eLegacySubtreeModified) { return kAllMutationBits; } - bits |= MutationBitForEventType(listener->mEventMessage); + bits |= MutationBitForEventType(message); } } } @@ -1669,21 +1815,30 @@ bool EventListenerManager::HasListenersForInternal( #endif NS_ASSERTION(StringBeginsWith(name, u"on"_ns), "Event name does not start with 'on'"); - uint32_t count = mListeners.Length(); - for (uint32_t i = 0; i < count; ++i) { - const Listener* listener = &mListeners.ElementAt(i); - if (listener->mTypeAtom == aEventNameWithOn) { - if (aIgnoreSystemGroup && listener->mFlags.mInSystemGroup) { - continue; - } + RefPtr listeners = + mListenerMap.GetListenersForType(aEventNameWithOn); + if (!listeners) { + return false; + } + + MOZ_ASSERT(!listeners->IsEmpty()); + + if (!aIgnoreSystemGroup) { + return true; + } + + // Check if any non-system-group listeners exist in `listeners`. + for (const auto& listener : listeners->NonObservingRange()) { + if (!listener.mFlags.mInSystemGroup) { return true; } } + return false; } bool EventListenerManager::HasListeners() const { - return !mListeners.IsEmpty(); + return !mListenerMap.IsEmpty(); } nsresult EventListenerManager::GetListenerInfo( @@ -1691,52 +1846,55 @@ nsresult EventListenerManager::GetListenerInfo( nsCOMPtr target = mTarget; NS_ENSURE_STATE(target); aList.Clear(); - for (const Listener& listener : mListeners.ForwardRange()) { - // If this is a script handler and we haven't yet - // compiled the event handler itself go ahead and compile it - if (listener.mListenerType == Listener::eJSEventListener && - listener.mHandlerIsString) { - CompileEventHandlerInternal(const_cast(&listener), nullptr, - nullptr); - } - nsAutoString eventType; - if (listener.mAllEvents) { - eventType.SetIsVoid(true); - } else if (listener.mListenerType == Listener::eNoListener) { - continue; - } else { - eventType.Assign(Substring(nsDependentAtomString(listener.mTypeAtom), 2)); - } + for (const auto& entry : mListenerMap.mEntries) { + for (const Listener& listener : entry.mListeners->ForwardRange()) { + // If this is a script handler and we haven't yet + // compiled the event handler itself go ahead and compile it + if (listener.mListenerType == Listener::eJSEventListener && + listener.mHandlerIsString) { + CompileEventHandlerInternal(const_cast(&listener), nullptr, + nullptr); + } + nsAutoString eventType; + if (listener.mAllEvents) { + eventType.SetIsVoid(true); + } else if (listener.mListenerType == Listener::eNoListener) { + continue; + } else { + eventType.Assign(Substring(nsDependentAtomString(entry.mTypeAtom), 2)); + } - JS::Rooted callback(RootingCx()); - JS::Rooted callbackGlobal(RootingCx()); - if (JSEventHandler* handler = listener.GetJSEventHandler()) { - if (handler->GetTypedEventHandler().HasEventHandler()) { - CallbackFunction* callbackFun = handler->GetTypedEventHandler().Ptr(); - callback = callbackFun->CallableOrNull(); - callbackGlobal = callbackFun->CallbackGlobalOrNull(); + JS::Rooted callback(RootingCx()); + JS::Rooted callbackGlobal(RootingCx()); + if (JSEventHandler* handler = listener.GetJSEventHandler()) { + if (handler->GetTypedEventHandler().HasEventHandler()) { + CallbackFunction* callbackFun = handler->GetTypedEventHandler().Ptr(); + callback = callbackFun->CallableOrNull(); + callbackGlobal = callbackFun->CallbackGlobalOrNull(); + if (!callback) { + // This will be null for cross-compartment event listeners + // which have been destroyed. + continue; + } + } + } else if (listener.mListenerType == Listener::eWebIDLListener) { + EventListener* listenerCallback = + listener.mListener.GetWebIDLCallback(); + callback = listenerCallback->CallbackOrNull(); + callbackGlobal = listenerCallback->CallbackGlobalOrNull(); if (!callback) { // This will be null for cross-compartment event listeners // which have been destroyed. continue; } } - } else if (listener.mListenerType == Listener::eWebIDLListener) { - EventListener* listenerCallback = listener.mListener.GetWebIDLCallback(); - callback = listenerCallback->CallbackOrNull(); - callbackGlobal = listenerCallback->CallbackGlobalOrNull(); - if (!callback) { - // This will be null for cross-compartment event listeners - // which have been destroyed. - continue; - } - } - RefPtr info = new EventListenerInfo( - this, eventType, callback, callbackGlobal, listener.mFlags.mCapture, - listener.mFlags.mAllowUntrustedEvents, listener.mFlags.mInSystemGroup, - listener.mListenerIsHandler); - aList.AppendElement(info.forget()); + RefPtr info = new EventListenerInfo( + this, eventType, callback, callbackGlobal, listener.mFlags.mCapture, + listener.mFlags.mAllowUntrustedEvents, listener.mFlags.mInSystemGroup, + listener.mListenerIsHandler); + aList.AppendElement(info.forget()); + } } return NS_OK; } @@ -1746,11 +1904,25 @@ EventListenerManager::Listener* EventListenerManager::GetListenerFor( bool aAllowsUntrusted, bool aInSystemEventGroup, bool aIsHandler) { NS_ENSURE_TRUE(aListener, nullptr); - for (Listener& listener : mListeners.ForwardRange()) { - if ((aType.IsVoid() && !listener.mAllEvents) || - !Substring(nsDependentAtomString(listener.mTypeAtom), 2) - .Equals(aType) || - listener.mListenerType == Listener::eNoListener) { + RefPtr listeners; + if (aType.IsVoid()) { + listeners = mListenerMap.GetListenersForAllEvents(); + } else { + for (auto& mapEntry : mListenerMap.mEntries) { + if (RefPtr typeAtom = mapEntry.mTypeAtom) { + if (Substring(nsDependentAtomString(typeAtom), 2).Equals(aType)) { + listeners = mapEntry.mListeners; + break; + } + } + } + } + if (!listeners) { + return nullptr; + } + + for (Listener& listener : listeners->ForwardRange()) { + if (listener.mListenerType == Listener::eNoListener) { continue; } @@ -1809,25 +1981,11 @@ nsresult EventListenerManager::SetListenerEnabled( } bool EventListenerManager::HasUnloadListeners() { - uint32_t count = mListeners.Length(); - for (uint32_t i = 0; i < count; ++i) { - Listener* listener = &mListeners.ElementAt(i); - if (listener->mEventMessage == eUnload) { - return true; - } - } - return false; + return mListenerMap.GetListenersForType(nsGkAtoms::onunload) != nullptr; } bool EventListenerManager::HasBeforeUnloadListeners() { - uint32_t count = mListeners.Length(); - for (uint32_t i = 0; i < count; ++i) { - Listener* listener = &mListeners.ElementAt(i); - if (listener->mEventMessage == eBeforeUnload) { - return true; - } - } - return false; + return mListenerMap.GetListenersForType(nsGkAtoms::onbeforeunload) != nullptr; } void EventListenerManager::SetEventHandler(nsAtom* aEventName, @@ -1875,8 +2033,7 @@ void EventListenerManager::SetEventHandler( const TypedEventHandler* EventListenerManager::GetTypedEventHandler( nsAtom* aEventName) { - EventMessage eventMessage = GetEventMessage(aEventName); - Listener* listener = FindEventHandler(eventMessage, aEventName); + Listener* listener = FindEventHandler(aEventName); if (!listener) { return nullptr; @@ -1895,12 +2052,29 @@ const TypedEventHandler* EventListenerManager::GetTypedEventHandler( size_t EventListenerManager::SizeOfIncludingThis( MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + mListenerMap.SizeOfExcludingThis(aMallocSizeOf); +} + +size_t EventListenerManager::EventListenerMap::SizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const { + size_t n = mEntries.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& entry : mEntries) { + n += entry.SizeOfExcludingThis(aMallocSizeOf); + } + return n; +} + +size_t EventListenerManager::EventListenerMapEntry::SizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const { + return mListeners->SizeOfIncludingThis(aMallocSizeOf); +} + +size_t EventListenerManager::ListenerArray::SizeOfIncludingThis( + MallocSizeOf aMallocSizeOf) const { size_t n = aMallocSizeOf(this); - n += mListeners.ShallowSizeOfExcludingThis(aMallocSizeOf); - uint32_t count = mListeners.Length(); - for (uint32_t i = 0; i < count; ++i) { - JSEventHandler* jsEventHandler = - mListeners.ElementAt(i).GetJSEventHandler(); + n += ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& listener : NonObservingRange()) { + JSEventHandler* jsEventHandler = listener.GetJSEventHandler(); if (jsEventHandler) { n += jsEventHandler->SizeOfIncludingThis(aMallocSizeOf); } @@ -1908,19 +2082,27 @@ size_t EventListenerManager::SizeOfIncludingThis( return n; } +uint32_t EventListenerManager::ListenerCount() const { + uint32_t count = 0; + for (const auto& entry : mListenerMap.mEntries) { + count += entry.mListeners->Length(); + } + return count; +} + void EventListenerManager::MarkForCC() { - uint32_t count = mListeners.Length(); - for (uint32_t i = 0; i < count; ++i) { - const Listener& listener = mListeners.ElementAt(i); - JSEventHandler* jsEventHandler = listener.GetJSEventHandler(); - if (jsEventHandler) { - const TypedEventHandler& typedHandler = - jsEventHandler->GetTypedEventHandler(); - if (typedHandler.HasEventHandler()) { - typedHandler.Ptr()->MarkForCC(); + for (const auto& entry : mListenerMap.mEntries) { + for (const auto& listener : entry.mListeners->NonObservingRange()) { + JSEventHandler* jsEventHandler = listener.GetJSEventHandler(); + if (jsEventHandler) { + const TypedEventHandler& typedHandler = + jsEventHandler->GetTypedEventHandler(); + if (typedHandler.HasEventHandler()) { + typedHandler.Ptr()->MarkForCC(); + } + } else if (listener.mListenerType == Listener::eWebIDLListener) { + listener.mListener.GetWebIDLCallback()->MarkForCC(); } - } else if (listener.mListenerType == Listener::eWebIDLListener) { - listener.mListener.GetWebIDLCallback()->MarkForCC(); } } if (mRefCnt.IsPurple()) { @@ -1929,34 +2111,37 @@ void EventListenerManager::MarkForCC() { } void EventListenerManager::TraceListeners(JSTracer* aTrc) { - uint32_t count = mListeners.Length(); - for (uint32_t i = 0; i < count; ++i) { - const Listener& listener = mListeners.ElementAt(i); - JSEventHandler* jsEventHandler = listener.GetJSEventHandler(); - if (jsEventHandler) { - const TypedEventHandler& typedHandler = - jsEventHandler->GetTypedEventHandler(); - if (typedHandler.HasEventHandler()) { - mozilla::TraceScriptHolder(typedHandler.Ptr(), aTrc); + for (const auto& entry : mListenerMap.mEntries) { + for (const auto& listener : entry.mListeners->NonObservingRange()) { + JSEventHandler* jsEventHandler = listener.GetJSEventHandler(); + if (jsEventHandler) { + const TypedEventHandler& typedHandler = + jsEventHandler->GetTypedEventHandler(); + if (typedHandler.HasEventHandler()) { + mozilla::TraceScriptHolder(typedHandler.Ptr(), aTrc); + } + } else if (listener.mListenerType == Listener::eWebIDLListener) { + mozilla::TraceScriptHolder(listener.mListener.GetWebIDLCallback(), + aTrc); } - } else if (listener.mListenerType == Listener::eWebIDLListener) { - mozilla::TraceScriptHolder(listener.mListener.GetWebIDLCallback(), aTrc); + // We might have eWrappedJSListener, but that is the legacy type for + // JS implemented event listeners, and trickier to handle here. } - // We might have eWrappedJSListener, but that is the legacy type for - // JS implemented event listeners, and trickier to handle here. } } bool EventListenerManager::HasNonSystemGroupListenersForUntrustedKeyEvents() { - uint32_t count = mListeners.Length(); - for (uint32_t i = 0; i < count; ++i) { - Listener* listener = &mListeners.ElementAt(i); - if (!listener->mFlags.mInSystemGroup && - listener->mFlags.mAllowUntrustedEvents && - (listener->mTypeAtom == nsGkAtoms::onkeydown || - listener->mTypeAtom == nsGkAtoms::onkeypress || - listener->mTypeAtom == nsGkAtoms::onkeyup)) { - return true; + for (const auto& entry : mListenerMap.mEntries) { + if (entry.mTypeAtom != nsGkAtoms::onkeydown && + entry.mTypeAtom != nsGkAtoms::onkeypress && + entry.mTypeAtom != nsGkAtoms::onkeyup) { + continue; + } + for (const auto& listener : entry.mListeners->NonObservingRange()) { + if (!listener.mFlags.mInSystemGroup && + listener.mFlags.mAllowUntrustedEvents) { + return true; + } } } return false; @@ -1964,26 +2149,35 @@ bool EventListenerManager::HasNonSystemGroupListenersForUntrustedKeyEvents() { bool EventListenerManager:: HasNonPassiveNonSystemGroupListenersForUntrustedKeyEvents() { - uint32_t count = mListeners.Length(); - for (uint32_t i = 0; i < count; ++i) { - Listener* listener = &mListeners.ElementAt(i); - if (!listener->mFlags.mPassive && !listener->mFlags.mInSystemGroup && - listener->mFlags.mAllowUntrustedEvents && - (listener->mTypeAtom == nsGkAtoms::onkeydown || - listener->mTypeAtom == nsGkAtoms::onkeypress || - listener->mTypeAtom == nsGkAtoms::onkeyup)) { - return true; + for (const auto& entry : mListenerMap.mEntries) { + if (entry.mTypeAtom != nsGkAtoms::onkeydown && + entry.mTypeAtom != nsGkAtoms::onkeypress && + entry.mTypeAtom != nsGkAtoms::onkeyup) { + continue; + } + for (const auto& listener : entry.mListeners->NonObservingRange()) { + if (!listener.mFlags.mPassive && !listener.mFlags.mInSystemGroup && + listener.mFlags.mAllowUntrustedEvents) { + return true; + } } } return false; } bool EventListenerManager::HasApzAwareListeners() { - uint32_t count = mListeners.Length(); - for (uint32_t i = 0; i < count; ++i) { - Listener* listener = &mListeners.ElementAt(i); - if (IsApzAwareListener(listener)) { - return true; + if (!mIsMainThreadELM) { + return false; + } + + for (const auto& entry : mListenerMap.mEntries) { + if (!IsApzAwareEvent(entry.mTypeAtom)) { + continue; + } + for (const auto& listener : entry.mListeners->NonObservingRange()) { + if (!listener.mFlags.mPassive) { + return true; + } } } return false; @@ -2022,27 +2216,34 @@ bool EventListenerManager::IsApzAwareEvent(nsAtom* aEvent) { bool EventListenerManager::HasNonPassiveWheelListener() { MOZ_ASSERT(NS_IsMainThread()); - uint32_t count = mListeners.Length(); - for (uint32_t i = 0; i < count; ++i) { - Listener* listener = &mListeners.ElementAt(i); - if (!listener->mFlags.mPassive && IsWheelEventType(listener->mTypeAtom)) { - return true; + for (const auto& entry : mListenerMap.mEntries) { + if (!IsWheelEventType(entry.mTypeAtom)) { + continue; + } + for (const auto& listener : entry.mListeners->NonObservingRange()) { + if (!listener.mFlags.mPassive) { + return true; + } } } return false; } void EventListenerManager::RemoveAllListeners() { - while (!mListeners.IsEmpty()) { - size_t idx = mListeners.Length() - 1; - RefPtr type = mListeners.ElementAt(idx).mTypeAtom; - EventMessage message = mListeners.ElementAt(idx).mEventMessage; - mListeners.RemoveElementAt(idx); - NotifyEventListenerRemoved(type); - if (IsDeviceType(message)) { - DisableDevice(message); + for (auto& entry : mListenerMap.mEntries) { + RefPtr type = entry.mTypeAtom; + ListenerArray& listeners = *entry.mListeners; + while (!listeners.IsEmpty()) { + size_t idx = listeners.Length() - 1; + EventMessage message = listeners.ElementAt(idx).mEventMessage; + listeners.RemoveElementAt(idx); + NotifyEventListenerRemoved(type); + if (IsDeviceType(message)) { + DisableDevice(message); + } } } + mListenerMap.Clear(); } already_AddRefed diff --git a/dom/events/EventListenerManager.h b/dom/events/EventListenerManager.h index 9bab53f5ac23..46260a4bd4fd 100644 --- a/dom/events/EventListenerManager.h +++ b/dom/events/EventListenerManager.h @@ -271,9 +271,6 @@ class EventListenerManager final : public EventListenerManagerBase { } } - MOZ_ALWAYS_INLINE bool MatchesEventMessage( - const WidgetEvent* aEvent, EventMessage aEventMessage) const; - MOZ_ALWAYS_INLINE bool MatchesEventGroup(const WidgetEvent* aEvent) const { return mFlags.mInSystemGroup == aEvent->mFlags.mInSystemGroup; } @@ -291,6 +288,66 @@ class EventListenerManager final : public EventListenerManagerBase { } }; + /** + * A reference counted subclass of a listener observer array. + */ + struct ListenerArray final : public nsAutoTObserverArray { + NS_INLINE_DECL_REFCOUNTING(EventListenerManager::ListenerArray); + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; + + protected: + ~ListenerArray() = default; + }; + + /** + * An entry in the event listener map for a certain event type, carrying the + * array of listeners for that type. + */ + struct EventListenerMapEntry { + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const; + + // The event type. Null if this entry is for "all events" listeners. + RefPtr mTypeAtom; + // The array of listeners. New listeners are always added at the end. + // This is a RefPtr rather than an inline member for two reasons: + // - It needs to be a separate heap allocation so that, if the array of + // entries is mutated during iteration, the ListenerArray remains in a + // stable place. + // - It's a RefPtr rather than a UniquePtr so that iteration can share + // ownership of it and make sure that the listener array remains alive + // even if the entry is removed during iteration. + RefPtr mListeners; + }; + + /** + * The map of event listeners, keyed by event type atom. + */ + struct EventListenerMap { + bool IsEmpty() const { return mEntries.IsEmpty(); } + void Clear() { mEntries.Clear(); } + + Maybe EntryIndexForType(nsAtom* aTypeAtom) const; + Maybe EntryIndexForAllEvents() const; + + // Returns null if no entry is present for the given type. + RefPtr GetListenersForType(nsAtom* aTypeAtom) const; + RefPtr GetListenersForAllEvents() const; + + // Never returns null, creates a new empty entry if needed. + RefPtr GetOrCreateListenersForType(nsAtom* aTypeAtom); + RefPtr GetOrCreateListenersForAllEvents(); + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const; + + // The array of entries, ordered by event type atom (specifically by the + // nsAtom* address). If mEntries contains an entry for "all events" + // listeners, that entry will be the first entry, because its atom will be + // null so it will be ordered to the front. + // All entries have non-empty listener arrays. If a non-empty listener + // entry becomes empty, it is removed immediately. + AutoTArray mEntries; + }; + explicit EventListenerManager(dom::EventTarget* aTarget); NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(EventListenerManager) @@ -405,7 +462,7 @@ class EventListenerManager final : public EventListenerManagerBase { return; } - if (mListeners.IsEmpty() || aEvent->PropagationStopped()) { + if (mListenerMap.IsEmpty() || aEvent->PropagationStopped()) { return; } @@ -513,7 +570,7 @@ class EventListenerManager final : public EventListenerManagerBase { size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; - uint32_t ListenerCount() const { return mListeners.Length(); } + uint32_t ListenerCount() const; void MarkForCC(); @@ -543,6 +600,17 @@ class EventListenerManager final : public EventListenerManagerBase { dom::EventTarget* aCurrentTarget, nsEventStatus* aEventStatus, bool aItemInShadowTree); + /** + * Iterate the listener array and calls the matching listeners. + * + * Returns true if any listener matching the event group was found. + */ + MOZ_CAN_RUN_SCRIPT + bool HandleEventWithListenerArray( + ListenerArray* aListeners, nsAtom* aTypeAtom, EventMessage aEventMessage, + nsPresContext* aPresContext, WidgetEvent* aEvent, dom::Event** aDOMEvent, + dom::EventTarget* aCurrentTarget, bool aItemInShadowTree); + /** * Call the listener. * @@ -588,7 +656,7 @@ class EventListenerManager final : public EventListenerManagerBase { /** * Find the Listener for the "inline" event listener for aTypeAtom. */ - Listener* FindEventHandler(EventMessage aEventMessage, nsAtom* aTypeAtom); + Listener* FindEventHandler(nsAtom* aTypeAtom); /** * Set the "inline" event listener for aName to aHandler. aHandler may be @@ -691,7 +759,7 @@ class EventListenerManager final : public EventListenerManagerBase { // BE AWARE, a lot of instances of EventListenerManager will be created. // Therefor, we need to keep this class compact. When you add integer - // members, please add them to EventListemerManagerBase and check the size + // members, please add them to EventListenerManagerBase and check the size // at build time. already_AddRefed GetScriptGlobalAndDocument( @@ -699,7 +767,7 @@ class EventListenerManager final : public EventListenerManagerBase { void MaybeMarkPassive(EventMessage aMessage, EventListenerFlags& aFlags); - nsAutoTObserverArray mListeners; + EventListenerMap mListenerMap; dom::EventTarget* MOZ_NON_OWNING_REF mTarget; RefPtr mNoListenerForEventAtom; diff --git a/dom/events/test/test_bug448602.html b/dom/events/test/test_bug448602.html index 5c076894277f..18b4cb2d2fa7 100644 --- a/dom/events/test/test_bug448602.html +++ b/dom/events/test/test_bug448602.html @@ -78,7 +78,16 @@ function runTests() { root.addEventListener("click", bubblingListener); root.addEventListener("fooevent", capturingListener, true); root.addEventListener("fooevent", bubblingListener); - infos = els.getListenerInfoFor(root); + + // We now have both "click" and "fooevent" listeners. + // Get the new set of listener infos, because we'll want to flip certain + // "click" event listeners on and off in the tests below. + // The order of event types is not guaranteed by getListenerInfoFor; but the + // order of listeners for a single event is guaranteed. So we filter the infos + // by event type. + const combinedListenerInfos = [...els.getListenerInfoFor(root)]; + const clickInfos = combinedListenerInfos.filter((info) => info.type == "click"); + // Use a child node to dispatch events so that both capturing and bubbling // listeners get called. l2 = document.getElementById("testlevel2"); @@ -88,29 +97,29 @@ function runTests() { ok(bubblingListenerCalled); clearListenerStates(); - infos[0].enabled = false; + clickInfos[0].enabled = false; l2.click(); ok(!handlerCalled); ok(capturingListenerCalled); ok(bubblingListenerCalled); clearListenerStates(); - infos[0].enabled = true; + clickInfos[0].enabled = true; - infos[1].enabled = false; + clickInfos[1].enabled = false; l2.click(); ok(handlerCalled); ok(!capturingListenerCalled); ok(bubblingListenerCalled); clearListenerStates(); - infos[1].enabled = true; + clickInfos[1].enabled = true; - infos[2].enabled = false; + clickInfos[2].enabled = false; l2.click(); ok(handlerCalled); ok(capturingListenerCalled); ok(!bubblingListenerCalled); clearListenerStates(); - infos[2].enabled = true; + clickInfos[2].enabled = true; root.removeEventListener("click", capturingListener, true); root.removeEventListener("click", bubblingListener); diff --git a/testing/web-platform/tests/dom/events/remove-all-listeners.html b/testing/web-platform/tests/dom/events/remove-all-listeners.html new file mode 100644 index 000000000000..3a2a751a146b --- /dev/null +++ b/testing/web-platform/tests/dom/events/remove-all-listeners.html @@ -0,0 +1,95 @@ + +Various edge cases where listeners are removed during iteration + + +
+