diff -r 000000000000 -r 6474c204b198 dom/events/EventListenerManager.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dom/events/EventListenerManager.h Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,579 @@ +/* -*- Mode: C++; 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/. */ + +#ifndef mozilla_EventListenerManager_h_ +#define mozilla_EventListenerManager_h_ + +#include "mozilla/BasicEvents.h" +#include "mozilla/dom/EventListenerBinding.h" +#include "mozilla/JSEventHandler.h" +#include "mozilla/MemoryReporting.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsGkAtoms.h" +#include "nsIDOMEventListener.h" +#include "nsTObserverArray.h" + +class nsIDOMEvent; +class nsIEventListenerInfo; +class nsIScriptContext; +class nsPIDOMWindow; + +struct EventTypeData; + +template class nsCOMArray; + +namespace mozilla { + +class ELMCreationDetector; +class EventListenerManager; + +namespace dom { +class EventTarget; +class Element; +} // namespace dom + +typedef dom::CallbackObjectHolder EventListenerHolder; + +struct EventListenerFlags +{ + friend class EventListenerManager; +private: + // If mListenerIsJSListener is true, the listener is implemented by JS. + // Otherwise, it's implemented by native code or JS but it's wrapped. + bool mListenerIsJSListener : 1; + +public: + // If mCapture is true, it means the listener captures the event. Otherwise, + // it's listening at bubbling phase. + bool mCapture : 1; + // If mInSystemGroup is true, the listener is listening to the events in the + // system group. + bool mInSystemGroup : 1; + // If mAllowUntrustedEvents is true, the listener is listening to the + // untrusted events too. + bool mAllowUntrustedEvents : 1; + + EventListenerFlags() : + mListenerIsJSListener(false), + mCapture(false), mInSystemGroup(false), mAllowUntrustedEvents(false) + { + } + + bool Equals(const EventListenerFlags& aOther) const + { + return (mCapture == aOther.mCapture && + mInSystemGroup == aOther.mInSystemGroup && + mListenerIsJSListener == aOther.mListenerIsJSListener && + mAllowUntrustedEvents == aOther.mAllowUntrustedEvents); + } + + bool EqualsIgnoringTrustness(const EventListenerFlags& aOther) const + { + return (mCapture == aOther.mCapture && + mInSystemGroup == aOther.mInSystemGroup && + mListenerIsJSListener == aOther.mListenerIsJSListener); + } + + bool operator==(const EventListenerFlags& aOther) const + { + return Equals(aOther); + } +}; + +inline EventListenerFlags TrustedEventsAtBubble() +{ + EventListenerFlags flags; + return flags; +} + +inline EventListenerFlags TrustedEventsAtCapture() +{ + EventListenerFlags flags; + flags.mCapture = true; + return flags; +} + +inline EventListenerFlags AllEventsAtBubbe() +{ + EventListenerFlags flags; + flags.mAllowUntrustedEvents = true; + return flags; +} + +inline EventListenerFlags AllEventsAtCapture() +{ + EventListenerFlags flags; + flags.mCapture = true; + flags.mAllowUntrustedEvents = true; + return flags; +} + +inline EventListenerFlags TrustedEventsAtSystemGroupBubble() +{ + EventListenerFlags flags; + flags.mInSystemGroup = true; + return flags; +} + +inline EventListenerFlags TrustedEventsAtSystemGroupCapture() +{ + EventListenerFlags flags; + flags.mCapture = true; + flags.mInSystemGroup = true; + return flags; +} + +inline EventListenerFlags AllEventsAtSystemGroupBubble() +{ + EventListenerFlags flags; + flags.mInSystemGroup = true; + flags.mAllowUntrustedEvents = true; + return flags; +} + +inline EventListenerFlags AllEventsAtSystemGroupCapture() +{ + EventListenerFlags flags; + flags.mCapture = true; + flags.mInSystemGroup = true; + flags.mAllowUntrustedEvents = true; + return flags; +} + +/* + * Event listener manager + */ + +class EventListenerManager MOZ_FINAL +{ +public: + struct Listener + { + EventListenerHolder mListener; + nsCOMPtr mTypeAtom; // for the main thread + nsString mTypeString; // for non-main-threads + uint16_t mEventType; + + enum ListenerType MOZ_ENUM_TYPE(uint8_t) + { + eNativeListener = 0, + eJSEventListener, + eWrappedJSListener, + eWebIDLListener, + eListenerTypeCount + }; + uint8_t mListenerType; + + bool mListenerIsHandler : 1; + bool mHandlerIsString : 1; + bool mAllEvents : 1; + + EventListenerFlags mFlags; + + JSEventHandler* GetJSEventHandler() const + { + return (mListenerType == eJSEventListener) ? + static_cast(mListener.GetXPCOMCallback()) : + nullptr; + } + + Listener() + { + MOZ_ASSERT(sizeof(mListenerType) == 1); + MOZ_ASSERT(eListenerTypeCount < 255); + } + + ~Listener() + { + if ((mListenerType == eJSEventListener) && mListener) { + static_cast( + mListener.GetXPCOMCallback())->Disconnect(); + } + } + + MOZ_ALWAYS_INLINE bool IsListening(const WidgetEvent* aEvent) const + { + if (mFlags.mInSystemGroup != aEvent->mFlags.mInSystemGroup) { + return false; + } + // FIXME Should check !mFlags.mCapture when the event is in target + // phase because capture phase event listeners should not be fired. + // But it breaks at least 's buttons. Bug 235441. + return ((mFlags.mCapture && aEvent->mFlags.mInCapturePhase) || + (!mFlags.mCapture && aEvent->mFlags.mInBubblingPhase)); + } + }; + + EventListenerManager(dom::EventTarget* aTarget); + virtual ~EventListenerManager(); + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(EventListenerManager) + + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(EventListenerManager) + + void AddEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + bool aUseCapture, + bool aWantsUntrusted) + { + EventListenerHolder holder(aListener); + AddEventListener(aType, holder, aUseCapture, aWantsUntrusted); + } + void AddEventListener(const nsAString& aType, + dom::EventListener* aListener, + bool aUseCapture, + bool aWantsUntrusted) + { + EventListenerHolder holder(aListener); + AddEventListener(aType, holder, aUseCapture, aWantsUntrusted); + } + void RemoveEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + bool aUseCapture) + { + EventListenerHolder holder(aListener); + RemoveEventListener(aType, holder, aUseCapture); + } + void RemoveEventListener(const nsAString& aType, + dom::EventListener* aListener, + bool aUseCapture) + { + EventListenerHolder holder(aListener); + RemoveEventListener(aType, holder, aUseCapture); + } + + void AddListenerForAllEvents(nsIDOMEventListener* aListener, + bool aUseCapture, + bool aWantsUntrusted, + bool aSystemEventGroup); + void RemoveListenerForAllEvents(nsIDOMEventListener* aListener, + bool aUseCapture, + bool aSystemEventGroup); + + /** + * Sets events listeners of all types. + * @param an event listener + */ + void AddEventListenerByType(nsIDOMEventListener *aListener, + const nsAString& type, + const EventListenerFlags& aFlags) + { + EventListenerHolder holder(aListener); + AddEventListenerByType(holder, type, aFlags); + } + void AddEventListenerByType(const EventListenerHolder& aListener, + const nsAString& type, + const EventListenerFlags& aFlags); + void RemoveEventListenerByType(nsIDOMEventListener *aListener, + const nsAString& type, + const EventListenerFlags& aFlags) + { + EventListenerHolder holder(aListener); + RemoveEventListenerByType(holder, type, aFlags); + } + void RemoveEventListenerByType(const EventListenerHolder& aListener, + const nsAString& type, + const EventListenerFlags& aFlags); + + /** + * Sets the current "inline" event listener for aName to be a + * function compiled from aFunc if !aDeferCompilation. If + * aDeferCompilation, then we assume that we can get the string from + * mTarget later and compile lazily. + * + * aElement, if not null, is the element the string is associated with. + */ + // XXXbz does that play correctly with nodes being adopted across + // documents? Need to double-check the spec here. + nsresult SetEventHandler(nsIAtom *aName, + const nsAString& aFunc, + uint32_t aLanguage, + bool aDeferCompilation, + bool aPermitUntrustedEvents, + dom::Element* aElement); + /** + * Remove the current "inline" event listener for aName. + */ + void RemoveEventHandler(nsIAtom *aName, const nsAString& aTypeString); + + void HandleEvent(nsPresContext* aPresContext, + WidgetEvent* aEvent, + nsIDOMEvent** aDOMEvent, + dom::EventTarget* aCurrentTarget, + nsEventStatus* aEventStatus) + { + if (mListeners.IsEmpty() || aEvent->mFlags.mPropagationStopped) { + return; + } + + if (!mMayHaveCapturingListeners && !aEvent->mFlags.mInBubblingPhase) { + return; + } + + if (!mMayHaveSystemGroupListeners && aEvent->mFlags.mInSystemGroup) { + return; + } + + // Check if we already know that there is no event listener for the event. + if (mNoListenerForEvent == aEvent->message && + (mNoListenerForEvent != NS_USER_DEFINED_EVENT || + mNoListenerForEventAtom == aEvent->userType)) { + return; + } + HandleEventInternal(aPresContext, aEvent, aDOMEvent, aCurrentTarget, + aEventStatus); + } + + /** + * Tells the event listener manager that its target (which owns it) is + * no longer using it (and could go away). + */ + void Disconnect(); + + /** + * Allows us to quickly determine if we have mutation listeners registered. + */ + bool HasMutationListeners(); + + /** + * Allows us to quickly determine whether we have unload or beforeunload + * listeners registered. + */ + bool HasUnloadListeners(); + + /** + * Returns the mutation bits depending on which mutation listeners are + * registered to this listener manager. + * @note If a listener is an nsIDOMMutationListener, all possible mutation + * event bits are returned. All bits are also returned if one of the + * event listeners is registered to handle DOMSubtreeModified events. + */ + uint32_t MutationListenerBits(); + + /** + * Returns true if there is at least one event listener for aEventName. + */ + bool HasListenersFor(const nsAString& aEventName); + + /** + * Returns true if there is at least one event listener for aEventNameWithOn. + * Note that aEventNameWithOn must start with "on"! + */ + bool HasListenersFor(nsIAtom* aEventNameWithOn); + + /** + * Returns true if there is at least one event listener. + */ + bool HasListeners(); + + /** + * Sets aList to the list of nsIEventListenerInfo objects representing the + * listeners managed by this listener manager. + */ + nsresult GetListenerInfo(nsCOMArray* aList); + + uint32_t GetIdentifierForEvent(nsIAtom* aEvent); + + static void Shutdown(); + + /** + * Returns true if there may be a paint event listener registered, + * false if there definitely isn't. + */ + bool MayHavePaintEventListener() { return mMayHavePaintEventListener; } + + /** + * Returns true if there may be a touch event listener registered, + * false if there definitely isn't. + */ + bool MayHaveTouchEventListener() { return mMayHaveTouchEventListener; } + + bool MayHaveMouseEnterLeaveEventListener() { return mMayHaveMouseEnterLeaveEventListener; } + bool MayHavePointerEnterLeaveEventListener() { return mMayHavePointerEnterLeaveEventListener; } + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; + + uint32_t ListenerCount() const + { + return mListeners.Length(); + } + + void MarkForCC(); + + dom::EventTarget* GetTarget() { return mTarget; } + +protected: + void HandleEventInternal(nsPresContext* aPresContext, + WidgetEvent* aEvent, + nsIDOMEvent** aDOMEvent, + dom::EventTarget* aCurrentTarget, + nsEventStatus* aEventStatus); + + nsresult HandleEventSubType(Listener* aListener, + nsIDOMEvent* aDOMEvent, + dom::EventTarget* aCurrentTarget); + + /** + * Compile the "inline" event listener for aListener. The + * body of the listener can be provided in aBody; if this is null we + * will look for it on mTarget. If aBody is provided, aElement should be + * as well; otherwise it will also be inferred from mTarget. + */ + nsresult CompileEventHandlerInternal(Listener* aListener, + const nsAString* aBody, + dom::Element* aElement); + + /** + * Find the Listener for the "inline" event listener for aTypeAtom. + */ + Listener* FindEventHandler(uint32_t aEventType, + nsIAtom* aTypeAtom, + const nsAString& aTypeString); + + /** + * Set the "inline" event listener for aName to aHandler. aHandler may be + * have no actual handler set to indicate that we should lazily get and + * compile the string for this listener, but in that case aContext and + * aScopeGlobal must be non-null. Otherwise, aContext and aScopeGlobal are + * allowed to be null. + */ + Listener* SetEventHandlerInternal(nsIAtom* aName, + const nsAString& aTypeString, + const TypedEventHandler& aHandler, + bool aPermitUntrustedEvents); + + bool IsDeviceType(uint32_t aType); + void EnableDevice(uint32_t aType); + void DisableDevice(uint32_t aType); + +public: + /** + * Set the "inline" event listener for aEventName to aHandler. If + * aHandler is null, this will actually remove the event listener + */ + void SetEventHandler(nsIAtom* aEventName, + const nsAString& aTypeString, + dom::EventHandlerNonNull* aHandler); + void SetEventHandler(dom::OnErrorEventHandlerNonNull* aHandler); + void SetEventHandler(dom::OnBeforeUnloadEventHandlerNonNull* aHandler); + + /** + * Get the value of the "inline" event listener for aEventName. + * This may cause lazy compilation if the listener is uncompiled. + * + * Note: It's the caller's responsibility to make sure to call the right one + * of these methods. In particular, "onerror" events use + * OnErrorEventHandlerNonNull for some event targets and EventHandlerNonNull + * for others. + */ + dom::EventHandlerNonNull* GetEventHandler(nsIAtom* aEventName, + const nsAString& aTypeString) + { + const TypedEventHandler* typedHandler = + GetTypedEventHandler(aEventName, aTypeString); + return typedHandler ? typedHandler->NormalEventHandler() : nullptr; + } + + dom::OnErrorEventHandlerNonNull* GetOnErrorEventHandler() + { + const TypedEventHandler* typedHandler = mIsMainThreadELM ? + GetTypedEventHandler(nsGkAtoms::onerror, EmptyString()) : + GetTypedEventHandler(nullptr, NS_LITERAL_STRING("error")); + return typedHandler ? typedHandler->OnErrorEventHandler() : nullptr; + } + + dom::OnBeforeUnloadEventHandlerNonNull* GetOnBeforeUnloadEventHandler() + { + const TypedEventHandler* typedHandler = + GetTypedEventHandler(nsGkAtoms::onbeforeunload, EmptyString()); + return typedHandler ? typedHandler->OnBeforeUnloadEventHandler() : nullptr; + } + +protected: + /** + * Helper method for implementing the various Get*EventHandler above. Will + * return null if we don't have an event handler for this event name. + */ + const TypedEventHandler* GetTypedEventHandler(nsIAtom* aEventName, + const nsAString& aTypeString); + + void AddEventListener(const nsAString& aType, + const EventListenerHolder& aListener, + bool aUseCapture, + bool aWantsUntrusted); + void RemoveEventListener(const nsAString& aType, + const EventListenerHolder& aListener, + bool aUseCapture); + + void AddEventListenerInternal(const EventListenerHolder& aListener, + uint32_t aType, + nsIAtom* aTypeAtom, + const nsAString& aTypeString, + const EventListenerFlags& aFlags, + bool aHandler = false, + bool aAllEvents = false); + void RemoveEventListenerInternal(const EventListenerHolder& aListener, + uint32_t aType, + nsIAtom* aUserType, + const nsAString& aTypeString, + const EventListenerFlags& aFlags, + bool aAllEvents = false); + void RemoveAllListeners(); + const EventTypeData* GetTypeDataForIID(const nsIID& aIID); + const EventTypeData* GetTypeDataForEventName(nsIAtom* aName); + nsPIDOMWindow* GetInnerWindowForTarget(); + already_AddRefed GetTargetAsInnerWindow() const; + + bool ListenerCanHandle(Listener* aListener, WidgetEvent* aEvent); + + already_AddRefed + GetScriptGlobalAndDocument(nsIDocument** aDoc); + + uint32_t mMayHavePaintEventListener : 1; + uint32_t mMayHaveMutationListeners : 1; + uint32_t mMayHaveCapturingListeners : 1; + uint32_t mMayHaveSystemGroupListeners : 1; + uint32_t mMayHaveTouchEventListener : 1; + uint32_t mMayHaveMouseEnterLeaveEventListener : 1; + uint32_t mMayHavePointerEnterLeaveEventListener : 1; + uint32_t mClearingListeners : 1; + uint32_t mIsMainThreadELM : 1; + uint32_t mNoListenerForEvent : 23; + + nsAutoTObserverArray mListeners; + dom::EventTarget* mTarget; // WEAK + nsCOMPtr mNoListenerForEventAtom; + + friend class ELMCreationDetector; + static uint32_t sMainThreadCreatedCount; +}; + +} // namespace mozilla + +/** + * NS_AddSystemEventListener() is a helper function for implementing + * EventTarget::AddSystemEventListener(). + */ +inline nsresult +NS_AddSystemEventListener(mozilla::dom::EventTarget* aTarget, + const nsAString& aType, + nsIDOMEventListener *aListener, + bool aUseCapture, + bool aWantsUntrusted) +{ + mozilla::EventListenerManager* listenerManager = + aTarget->GetOrCreateListenerManager(); + NS_ENSURE_STATE(listenerManager); + mozilla::EventListenerFlags flags; + flags.mInSystemGroup = true; + flags.mCapture = aUseCapture; + flags.mAllowUntrustedEvents = aWantsUntrusted; + listenerManager->AddEventListenerByType(aListener, aType, flags); + return NS_OK; +} + +#endif // mozilla_EventListenerManager_h_