From bbdaa0ddbd8e419a0bcc9226016694b2883a7073 Mon Sep 17 00:00:00 2001 From: Daniel Holbert Date: Tue, 2 Feb 2016 10:03:49 -0800 Subject: [PATCH] Bug 1236979 part 3: If there are no listeners for a transition or animation event, check listeners again using a webkit-prefixed event name. r=smaug --- dom/events/EventListenerManager.cpp | 191 +++++++++++++++++++--------- dom/events/EventListenerManager.h | 11 +- 2 files changed, 144 insertions(+), 58 deletions(-) diff --git a/dom/events/EventListenerManager.cpp b/dom/events/EventListenerManager.cpp index 3ddb7dd9e486..bb4e09079b4d 100644 --- a/dom/events/EventListenerManager.cpp +++ b/dom/events/EventListenerManager.cpp @@ -19,7 +19,9 @@ #include "mozilla/HalSensor.h" #include "mozilla/InternalMutationEvent.h" #include "mozilla/JSEventHandler.h" +#include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" +#include "mozilla/Preferences.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/Event.h" @@ -95,6 +97,21 @@ MutationBitForEventType(EventMessage aEventType) uint32_t EventListenerManager::sMainThreadCreatedCount = 0; +static bool +IsWebkitPrefixSupportEnabled() +{ + static bool sIsWebkitPrefixSupportEnabled; + static bool sIsPrefCached = false; + + if (!sIsPrefCached) { + sIsPrefCached = true; + Preferences::AddBoolVarCache(&sIsWebkitPrefixSupportEnabled, + "layout.css.prefixes.webkit"); + } + + return sIsWebkitPrefixSupportEnabled; +} + EventListenerManagerBase::EventListenerManagerBase() : mNoListenerForEvent(eVoidEvent) , mMayHavePaintEventListener(false) @@ -603,9 +620,15 @@ EventListenerManager::RemoveEventListenerInternal( } bool -EventListenerManager::ListenerCanHandle(Listener* aListener, - WidgetEvent* aEvent) +EventListenerManager::ListenerCanHandle(const Listener* aListener, + const WidgetEvent* aEvent, + EventMessage aEventMessage) const + { + MOZ_ASSERT(aEventMessage == aEvent->mMessage || + aEventMessage == GetLegacyEventMessage(aEvent->mMessage), + "aEvent and aEventMessage should agree, modulo legacyness"); + // This is slightly different from EVENT_TYPE_EQUALS in that it returns // true even when aEvent->mMessage == eUnidentifiedEvent and // aListener=>mEventMessage != eUnidentifiedEvent as long as the atoms are @@ -620,7 +643,7 @@ EventListenerManager::ListenerCanHandle(Listener* aListener, return aListener->mTypeString.Equals(aEvent->typeString); } MOZ_ASSERT(mIsMainThreadELM); - return aListener->mEventMessage == aEvent->mMessage; + return aListener->mEventMessage == aEventMessage; } void @@ -1048,6 +1071,30 @@ EventListenerManager::HandleEventSubType(Listener* aListener, return result; } +EventMessage +EventListenerManager::GetLegacyEventMessage(EventMessage aEventMessage) const +{ + // (If we're off-main-thread, we can't check the pref; so we just behave as + // if it's disabled.) + if (mIsMainThreadELM && IsWebkitPrefixSupportEnabled()) { + // webkit-prefixed legacy events: + if (aEventMessage == eTransitionEnd) { + return eWebkitTransitionEnd; + } + if (aEventMessage == eAnimationStart) { + return eWebkitAnimationStart; + } + if (aEventMessage == eAnimationEnd) { + return eWebkitAnimationEnd; + } + if (aEventMessage == eAnimationIteration) { + return eWebkitAnimationIteration; + } + } + + return aEventMessage; +} + nsIDocShell* EventListenerManager::GetDocShellForTarget() { @@ -1104,77 +1151,107 @@ EventListenerManager::HandleEventInternal(nsPresContext* aPresContext, aEvent->mFlags.mDefaultPrevented = true; } - nsAutoTObserverArray::EndLimitedIterator iter(mListeners); Maybe popupStatePusher; if (mIsMainThreadELM) { popupStatePusher.emplace(Event::GetEventPopupControlState(aEvent, *aDOMEvent)); } bool hasListener = false; - while (iter.HasMore()) { - if (aEvent->mFlags.mImmediatePropagationStopped) { - break; - } - Listener* listener = &iter.GetNext(); - // Check that the phase is same in event and event listener. - // Handle only trusted events, except when listener permits untrusted events. - if (ListenerCanHandle(listener, aEvent)) { - hasListener = true; - if (listener->IsListening(aEvent) && - (aEvent->mFlags.mIsTrusted || - listener->mFlags.mAllowUntrustedEvents)) { - if (!*aDOMEvent) { - // This is tiny bit slow, but happens only once per event. - nsCOMPtr et = - do_QueryInterface(aEvent->originalTarget); - RefPtr event = EventDispatcher::CreateEvent(et, aPresContext, + bool usingLegacyMessage = false; + EventMessage eventMessage = aEvent->mMessage; + + while (true) { + nsAutoTObserverArray::EndLimitedIterator iter(mListeners); + Maybe legacyAutoOverride; + while (iter.HasMore()) { + if (aEvent->mFlags.mImmediatePropagationStopped) { + break; + } + Listener* listener = &iter.GetNext(); + // Check that the phase is same in event and event listener. + // Handle only trusted events, except when listener permits untrusted events. + if (ListenerCanHandle(listener, aEvent, eventMessage)) { + hasListener = true; + if (listener->IsListening(aEvent) && + (aEvent->mFlags.mIsTrusted || + listener->mFlags.mAllowUntrustedEvents)) { + if (!*aDOMEvent) { + // This is tiny bit slow, but happens only once per event. + nsCOMPtr et = + do_QueryInterface(aEvent->originalTarget); + RefPtr event = EventDispatcher::CreateEvent(et, aPresContext, aEvent, EmptyString()); - event.forget(aDOMEvent); - } - if (*aDOMEvent) { - if (!aEvent->currentTarget) { - aEvent->currentTarget = aCurrentTarget->GetTargetForDOMEvent(); - if (!aEvent->currentTarget) { - break; - } + event.forget(aDOMEvent); } - - // Maybe add a marker to the docshell's timeline, but only - // bother with all the logic if some docshell is recording. - nsDocShell* docShell; - RefPtr timelines = TimelineConsumers::Get(); - bool needsEndEventMarker = false; - - if (mIsMainThreadELM && - listener->mListenerType != Listener::eNativeListener) { - nsCOMPtr docShellComPtr = GetDocShellForTarget(); - if (docShellComPtr) { - docShell = static_cast(docShellComPtr.get()); - if (timelines && timelines->HasConsumer(docShell)) { - needsEndEventMarker = true; - nsAutoString typeStr; - (*aDOMEvent)->GetType(typeStr); - uint16_t phase; - (*aDOMEvent)->GetEventPhase(&phase); - timelines->AddMarkerForDocShell(docShell, Move( - MakeUnique( - typeStr, phase, MarkerTracingType::START))); + if (*aDOMEvent) { + if (!aEvent->currentTarget) { + aEvent->currentTarget = aCurrentTarget->GetTargetForDOMEvent(); + if (!aEvent->currentTarget) { + break; } } - } + if (usingLegacyMessage && !legacyAutoOverride) { + // Override the aDOMEvent's event-message (its .type) until we + // finish traversing listeners (when legacyAutoOverride destructs) + legacyAutoOverride.emplace(*aDOMEvent, eventMessage); + } - if (NS_FAILED(HandleEventSubType(listener, *aDOMEvent, aCurrentTarget))) { - aEvent->mFlags.mExceptionHasBeenRisen = true; - } + // Maybe add a marker to the docshell's timeline, but only + // bother with all the logic if some docshell is recording. + nsDocShell* docShell; + RefPtr timelines = TimelineConsumers::Get(); + bool needsEndEventMarker = false; - if (needsEndEventMarker) { - timelines->AddMarkerForDocShell( - docShell, "DOMEvent", MarkerTracingType::END); + if (mIsMainThreadELM && + listener->mListenerType != Listener::eNativeListener) { + nsCOMPtr docShellComPtr = GetDocShellForTarget(); + if (docShellComPtr) { + docShell = static_cast(docShellComPtr.get()); + if (timelines && timelines->HasConsumer(docShell)) { + needsEndEventMarker = true; + nsAutoString typeStr; + (*aDOMEvent)->GetType(typeStr); + uint16_t phase; + (*aDOMEvent)->GetEventPhase(&phase); + timelines->AddMarkerForDocShell(docShell, Move( + MakeUnique( + typeStr, phase, MarkerTracingType::START))); + } + } + } + + if (NS_FAILED(HandleEventSubType(listener, *aDOMEvent, aCurrentTarget))) { + aEvent->mFlags.mExceptionHasBeenRisen = true; + } + + if (needsEndEventMarker) { + timelines->AddMarkerForDocShell( + docShell, "DOMEvent", MarkerTracingType::END); + } } } } } + + // 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 (hasListener || usingLegacyMessage) { + // (No need to recheck listeners, because we already found a match, or we + // already rechecked them.) + 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->currentTarget = nullptr; diff --git a/dom/events/EventListenerManager.h b/dom/events/EventListenerManager.h index 85660e77b37a..57095bfb1d5d 100644 --- a/dom/events/EventListenerManager.h +++ b/dom/events/EventListenerManager.h @@ -458,6 +458,13 @@ protected: nsIDOMEvent* aDOMEvent, dom::EventTarget* aCurrentTarget); + /** + * If the given EventMessage has a legacy version that we support, then this + * function returns that legacy version. Otherwise, this function simply + * returns the passed-in EventMessage. + */ + EventMessage GetLegacyEventMessage(EventMessage aEventMessage) const; + nsIDocShell* GetDocShellForTarget(); /** @@ -571,7 +578,9 @@ protected: nsPIDOMWindowInner* GetInnerWindowForTarget(); already_AddRefed GetTargetAsInnerWindow() const; - bool ListenerCanHandle(Listener* aListener, WidgetEvent* aEvent); + bool ListenerCanHandle(const Listener* aListener, + const WidgetEvent* aEvent, + EventMessage aEventMessage) const; // BE AWARE, a lot of instances of EventListenerManager will be created. // Therefor, we need to keep this class compact. When you add integer