/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/. */ #include "EventListenerService.h" #include "mozilla/BasicEvents.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventListenerManager.h" #include "mozilla/JSEventHandler.h" #include "mozilla/Maybe.h" #include "mozilla/dom/EventListenerBinding.h" #include "mozilla/dom/ScriptSettings.h" #include "nsArrayUtils.h" #include "nsCOMArray.h" #include "nsIXPConnect.h" #include "nsJSUtils.h" #include "nsMemory.h" #include "nsServiceManagerUtils.h" #include "nsArray.h" #include "nsThreadUtils.h" namespace mozilla { using namespace dom; /****************************************************************************** * mozilla::EventListenerChange ******************************************************************************/ NS_IMPL_ISUPPORTS(EventListenerChange, nsIEventListenerChange) EventListenerChange::~EventListenerChange() { } EventListenerChange::EventListenerChange(EventTarget* aTarget) : mTarget(aTarget) { } void EventListenerChange::AddChangedListenerName(nsAtom* aEventName) { mChangedListenerNames.AppendElement(aEventName); } NS_IMETHODIMP EventListenerChange::GetTarget(EventTarget** aTarget) { NS_ENSURE_ARG_POINTER(aTarget); NS_ADDREF(*aTarget = mTarget); return NS_OK; } NS_IMETHODIMP EventListenerChange::GetCountOfEventListenerChangesAffectingAccessibility( uint32_t* aCount) { *aCount = 0; size_t length = mChangedListenerNames.Length(); for (size_t i = 0; i < length; i++) { RefPtr listenerName = mChangedListenerNames[i]; // These are the event listener changes which may make an element // accessible or inaccessible. if (listenerName == nsGkAtoms::onclick || listenerName == nsGkAtoms::onmousedown || listenerName == nsGkAtoms::onmouseup) { *aCount += 1; } } return NS_OK; } /****************************************************************************** * mozilla::EventListenerInfo ******************************************************************************/ EventListenerInfo::EventListenerInfo(const nsAString& aType, JS::Handle aScriptedListener, bool aCapturing, bool aAllowsUntrusted, bool aInSystemEventGroup) : mType(aType) , mScriptedListener(aScriptedListener) , mCapturing(aCapturing) , mAllowsUntrusted(aAllowsUntrusted) , mInSystemEventGroup(aInSystemEventGroup) { HoldJSObjects(this); } EventListenerInfo::~EventListenerInfo() { DropJSObjects(this); } NS_IMPL_CYCLE_COLLECTION_CLASS(EventListenerInfo) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(EventListenerInfo) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EventListenerInfo) tmp->mScriptedListener = nullptr; NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(EventListenerInfo) NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScriptedListener) NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EventListenerInfo) NS_INTERFACE_MAP_ENTRY(nsIEventListenerInfo) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(EventListenerInfo) NS_IMPL_CYCLE_COLLECTING_RELEASE(EventListenerInfo) NS_IMETHODIMP EventListenerInfo::GetType(nsAString& aType) { aType = mType; return NS_OK; } NS_IMETHODIMP EventListenerInfo::GetCapturing(bool* aCapturing) { *aCapturing = mCapturing; return NS_OK; } NS_IMETHODIMP EventListenerInfo::GetAllowsUntrusted(bool* aAllowsUntrusted) { *aAllowsUntrusted = mAllowsUntrusted; return NS_OK; } NS_IMETHODIMP EventListenerInfo::GetInSystemEventGroup(bool* aInSystemEventGroup) { *aInSystemEventGroup = mInSystemEventGroup; return NS_OK; } NS_IMETHODIMP EventListenerInfo::GetListenerObject(JSContext* aCx, JS::MutableHandle aObject) { Maybe ac; GetJSVal(aCx, ac, aObject); return NS_OK; } /****************************************************************************** * mozilla::EventListenerService ******************************************************************************/ NS_IMPL_ISUPPORTS(EventListenerService, nsIEventListenerService) bool EventListenerInfo::GetJSVal(JSContext* aCx, Maybe& aAc, JS::MutableHandle aJSVal) { if (mScriptedListener) { aJSVal.setObject(*mScriptedListener); aAc.emplace(aCx, mScriptedListener); return true; } aJSVal.setNull(); return false; } NS_IMETHODIMP EventListenerInfo::ToSource(nsAString& aResult) { aResult.SetIsVoid(true); AutoSafeJSContext cx; Maybe ac; JS::Rooted v(cx); if (GetJSVal(cx, ac, &v)) { JSString* str = JS_ValueToSource(cx, v); if (str) { nsAutoJSString autoStr; if (autoStr.init(cx, str)) { aResult.Assign(autoStr); } } } return NS_OK; } EventListenerService* EventListenerService::sInstance = nullptr; EventListenerService::EventListenerService() { MOZ_ASSERT(!sInstance); sInstance = this; } EventListenerService::~EventListenerService() { MOZ_ASSERT(sInstance == this); sInstance = nullptr; } NS_IMETHODIMP EventListenerService::GetListenerInfoFor(EventTarget* aEventTarget, uint32_t* aCount, nsIEventListenerInfo*** aOutArray) { NS_ENSURE_ARG_POINTER(aEventTarget); *aCount = 0; *aOutArray = nullptr; nsCOMArray listenerInfos; EventListenerManager* elm = aEventTarget->GetExistingListenerManager(); if (elm) { elm->GetListenerInfo(&listenerInfos); } int32_t count = listenerInfos.Count(); if (count == 0) { return NS_OK; } listenerInfos.Forget(aOutArray); *aCount = count; return NS_OK; } NS_IMETHODIMP EventListenerService::GetEventTargetChainFor(EventTarget* aEventTarget, bool aComposed, uint32_t* aCount, EventTarget*** aOutArray) { *aCount = 0; *aOutArray = nullptr; NS_ENSURE_ARG(aEventTarget); WidgetEvent event(true, eVoidEvent); event.SetComposed(aComposed); nsTArray targets; nsresult rv = EventDispatcher::Dispatch(aEventTarget, nullptr, &event, nullptr, nullptr, nullptr, &targets); NS_ENSURE_SUCCESS(rv, rv); int32_t count = targets.Length(); if (count == 0) { return NS_OK; } *aOutArray = static_cast( moz_xmalloc(sizeof(EventTarget*) * count)); NS_ENSURE_TRUE(*aOutArray, NS_ERROR_OUT_OF_MEMORY); for (int32_t i = 0; i < count; ++i) { NS_ADDREF((*aOutArray)[i] = targets[i]); } *aCount = count; return NS_OK; } NS_IMETHODIMP EventListenerService::HasListenersFor(EventTarget* aEventTarget, const nsAString& aType, bool* aRetVal) { NS_ENSURE_TRUE(aEventTarget, NS_ERROR_UNEXPECTED); EventListenerManager* elm = aEventTarget->GetExistingListenerManager(); *aRetVal = elm && elm->HasListenersFor(aType); return NS_OK; } static already_AddRefed ToEventListener(JSContext* aCx, JS::Handle aValue) { if (NS_WARN_IF(!aValue.isObject())) { return nullptr; } JS::Rooted obj(aCx, &aValue.toObject()); RefPtr listener = new EventListener(aCx, obj, GetIncumbentGlobal()); return listener.forget(); } NS_IMETHODIMP EventListenerService::AddSystemEventListener(EventTarget *aTarget, const nsAString& aType, JS::Handle aListener, bool aUseCapture, JSContext* aCx) { MOZ_ASSERT(aTarget, "Missing target"); NS_ENSURE_TRUE(aTarget, NS_ERROR_UNEXPECTED); RefPtr listener = ToEventListener(aCx, aListener); if (!listener) { return NS_ERROR_UNEXPECTED; } EventListenerManager* manager = aTarget->GetOrCreateListenerManager(); NS_ENSURE_STATE(manager); EventListenerFlags flags = aUseCapture ? TrustedEventsAtSystemGroupCapture() : TrustedEventsAtSystemGroupBubble(); manager->AddEventListenerByType(listener, aType, flags); return NS_OK; } NS_IMETHODIMP EventListenerService::RemoveSystemEventListener(EventTarget *aTarget, const nsAString& aType, JS::Handle aListener, bool aUseCapture, JSContext* aCx) { MOZ_ASSERT(aTarget, "Missing target"); NS_ENSURE_TRUE(aTarget, NS_ERROR_UNEXPECTED); RefPtr listener = ToEventListener(aCx, aListener); if (!listener) { return NS_ERROR_UNEXPECTED; } EventListenerManager* manager = aTarget->GetExistingListenerManager(); if (manager) { EventListenerFlags flags = aUseCapture ? TrustedEventsAtSystemGroupCapture() : TrustedEventsAtSystemGroupBubble(); manager->RemoveEventListenerByType(listener, aType, flags); } return NS_OK; } NS_IMETHODIMP EventListenerService::AddListenerForAllEvents(EventTarget* aTarget, JS::Handle aListener, bool aUseCapture, bool aWantsUntrusted, bool aSystemEventGroup, JSContext* aCx) { NS_ENSURE_STATE(aTarget); RefPtr listener = ToEventListener(aCx, aListener); if (!listener) { return NS_ERROR_UNEXPECTED; } EventListenerManager* manager = aTarget->GetOrCreateListenerManager(); NS_ENSURE_STATE(manager); manager->AddListenerForAllEvents(listener, aUseCapture, aWantsUntrusted, aSystemEventGroup); return NS_OK; } NS_IMETHODIMP EventListenerService::RemoveListenerForAllEvents(EventTarget* aTarget, JS::Handle aListener, bool aUseCapture, bool aSystemEventGroup, JSContext* aCx) { NS_ENSURE_STATE(aTarget); RefPtr listener = ToEventListener(aCx, aListener); if (!listener) { return NS_ERROR_UNEXPECTED; } EventListenerManager* manager = aTarget->GetExistingListenerManager(); if (manager) { manager->RemoveListenerForAllEvents(listener, aUseCapture, aSystemEventGroup); } return NS_OK; } NS_IMETHODIMP EventListenerService::AddListenerChangeListener(nsIListenerChangeListener* aListener) { if (!mChangeListeners.Contains(aListener)) { mChangeListeners.AppendElement(aListener); } return NS_OK; }; NS_IMETHODIMP EventListenerService::RemoveListenerChangeListener(nsIListenerChangeListener* aListener) { mChangeListeners.RemoveElement(aListener); return NS_OK; }; void EventListenerService::NotifyAboutMainThreadListenerChangeInternal(dom::EventTarget* aTarget, nsAtom* aName) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aTarget); if (mChangeListeners.IsEmpty()) { return; } if (!mPendingListenerChanges) { mPendingListenerChanges = nsArrayBase::Create(); nsCOMPtr runnable = NewRunnableMethod("EventListenerService::NotifyPendingChanges", this, &EventListenerService::NotifyPendingChanges); if (nsCOMPtr global = aTarget->GetOwnerGlobal()) { global->Dispatch(TaskCategory::Other, runnable.forget()); } else if (nsCOMPtr node = do_QueryInterface(aTarget)) { node->OwnerDoc()->Dispatch(TaskCategory::Other, runnable.forget()); } else { NS_DispatchToCurrentThread(runnable); } } RefPtr changes = mPendingListenerChangesSet.LookupForAdd(aTarget).OrInsert( [this, aTarget] () { EventListenerChange* c = new EventListenerChange(aTarget); mPendingListenerChanges->AppendElement(c); return c; }); changes->AddChangedListenerName(aName); } void EventListenerService::NotifyPendingChanges() { nsCOMPtr changes; mPendingListenerChanges.swap(changes); mPendingListenerChangesSet.Clear(); nsTObserverArray>::EndLimitedIterator iter(mChangeListeners); while (iter.HasMore()) { nsCOMPtr listener = iter.GetNext(); listener->ListenersChanged(changes); } } } // namespace mozilla nsresult NS_NewEventListenerService(nsIEventListenerService** aResult) { *aResult = new mozilla::EventListenerService(); NS_ADDREF(*aResult); return NS_OK; }