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