/* -*- 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 "PlacesObservers.h" #include "PlacesWeakCallbackWrapper.h" #include "nsIWeakReferenceUtils.h" #include "nsIXPConnect.h" #include "mozilla/ClearOnShutdown.h" namespace mozilla::dom { template struct Flagged { Flagged(uint32_t aFlags, T&& aValue) : flags(aFlags), value(std::forward(aValue)) {} Flagged(Flagged&& aOther) : Flagged(std::move(aOther.flags), std::move(aOther.value)) {} Flagged(const Flagged& aOther) = default; ~Flagged() = default; uint32_t flags; T value; }; template using FlaggedArray = nsTArray>; template struct ListenerCollection { static StaticAutoPtr> gListeners; static StaticAutoPtr> gListenersToRemove; static FlaggedArray* GetListeners(bool aDoNotInit = false) { MOZ_ASSERT(NS_IsMainThread()); if (!gListeners && !aDoNotInit) { gListeners = new FlaggedArray(); ClearOnShutdown(&gListeners); } return gListeners; } static FlaggedArray* GetListenersToRemove(bool aDoNotInit = false) { MOZ_ASSERT(NS_IsMainThread()); if (!gListenersToRemove && !aDoNotInit) { gListenersToRemove = new FlaggedArray(); ClearOnShutdown(&gListenersToRemove); } return gListenersToRemove; } }; template StaticAutoPtr> ListenerCollection::gListeners; template StaticAutoPtr> ListenerCollection::gListenersToRemove; using JSListeners = ListenerCollection>; using WeakJSListeners = ListenerCollection>; using WeakNativeListeners = ListenerCollection>; // Even if NotifyListeners is called any timing, we mange the notifications with // adding to this queue, then sending in sequence. This avoids sending nested // notifications while previous ones are still being sent. static nsTArray>> gNotificationQueue; uint32_t GetEventTypeFlag(PlacesEventType aEventType) { if (aEventType == PlacesEventType::None) { return 0; } return 1 << ((uint32_t)aEventType - 1); } uint32_t GetFlagsForEventTypes(const nsTArray& aEventTypes) { uint32_t flags = 0; for (PlacesEventType eventType : aEventTypes) { flags |= GetEventTypeFlag(eventType); } return flags; } uint32_t GetFlagsForEvents( const nsTArray>& aEvents) { uint32_t flags = 0; for (const PlacesEvent& event : aEvents) { flags |= GetEventTypeFlag(event.Type()); } return flags; } template MOZ_CAN_RUN_SCRIPT void CallListeners( uint32_t aEventFlags, const Sequence>& aEvents, unsigned long aListenersLengthToCall, const std::function& aUnwrapListener, const std::function>&)>& aCallListener) { auto& listeners = *TListenerCollection::GetListeners(); for (uint32_t i = 0; i < aListenersLengthToCall; i++) { Flagged& listener = listeners[i]; TUnwrapped unwrapped = aUnwrapListener(listener.value); if (!unwrapped) { continue; } if ((listener.flags & aEventFlags) == aEventFlags) { aCallListener(unwrapped, aEvents); } else if (listener.flags & aEventFlags) { Sequence> filtered; for (const OwningNonNull& event : aEvents) { if (listener.flags & GetEventTypeFlag(event->Type())) { bool success = !!filtered.AppendElement(event, fallible); MOZ_RELEASE_ASSERT(success); } } aCallListener(unwrapped, filtered); } } } void PlacesObservers::AddListener(GlobalObject& aGlobal, const nsTArray& aEventTypes, PlacesEventCallback& aCallback, ErrorResult& rv) { uint32_t flags = GetFlagsForEventTypes(aEventTypes); FlaggedArray>* listeners = JSListeners::GetListeners(); Flagged> pair(flags, &aCallback); listeners->AppendElement(pair); } void PlacesObservers::AddListener(GlobalObject& aGlobal, const nsTArray& aEventTypes, PlacesWeakCallbackWrapper& aCallback, ErrorResult& rv) { uint32_t flags = GetFlagsForEventTypes(aEventTypes); FlaggedArray>* listeners = WeakJSListeners::GetListeners(); WeakPtr weakCb(&aCallback); MOZ_ASSERT(weakCb.get()); Flagged> flagged(flags, std::move(weakCb)); listeners->AppendElement(flagged); } void PlacesObservers::AddListener( const nsTArray& aEventTypes, places::INativePlacesEventCallback* aCallback) { uint32_t flags = GetFlagsForEventTypes(aEventTypes); FlaggedArray>* listeners = WeakNativeListeners::GetListeners(); Flagged> pair(flags, aCallback); listeners->AppendElement(pair); } void PlacesObservers::RemoveListener( GlobalObject& aGlobal, const nsTArray& aEventTypes, PlacesEventCallback& aCallback, ErrorResult& rv) { uint32_t flags = GetFlagsForEventTypes(aEventTypes); if (!gNotificationQueue.IsEmpty()) { FlaggedArray>* listeners = JSListeners::GetListenersToRemove(); Flagged> pair(flags, &aCallback); listeners->AppendElement(pair); } else { RemoveListener(flags, aCallback); } } void PlacesObservers::RemoveListener( GlobalObject& aGlobal, const nsTArray& aEventTypes, PlacesWeakCallbackWrapper& aCallback, ErrorResult& rv) { uint32_t flags = GetFlagsForEventTypes(aEventTypes); if (!gNotificationQueue.IsEmpty()) { FlaggedArray>* listeners = WeakJSListeners::GetListenersToRemove(); WeakPtr weakCb(&aCallback); MOZ_ASSERT(weakCb.get()); Flagged> flagged(flags, std::move(weakCb)); listeners->AppendElement(flagged); } else { RemoveListener(flags, aCallback); } } void PlacesObservers::RemoveListener( const nsTArray& aEventTypes, places::INativePlacesEventCallback* aCallback) { uint32_t flags = GetFlagsForEventTypes(aEventTypes); if (!gNotificationQueue.IsEmpty()) { FlaggedArray>* listeners = WeakNativeListeners::GetListenersToRemove(); Flagged> pair(flags, aCallback); listeners->AppendElement(pair); } else { RemoveListener(flags, aCallback); } } void PlacesObservers::RemoveListener(uint32_t aFlags, PlacesEventCallback& aCallback) { FlaggedArray>* listeners = JSListeners::GetListeners(/* aDoNotInit: */ true); if (!listeners) { return; } for (uint32_t i = 0; i < listeners->Length(); i++) { Flagged>& l = listeners->ElementAt(i); if (!(*l.value == aCallback)) { continue; } if (l.flags == (aFlags & l.flags)) { listeners->RemoveElementAt(i); i--; } else { l.flags &= ~aFlags; } } } void PlacesObservers::RemoveListener(uint32_t aFlags, PlacesWeakCallbackWrapper& aCallback) { FlaggedArray>* listeners = WeakJSListeners::GetListeners(/* aDoNotInit: */ true); if (!listeners) { return; } for (uint32_t i = 0; i < listeners->Length(); i++) { Flagged>& l = listeners->ElementAt(i); RefPtr unwrapped = l.value.get(); if (unwrapped != &aCallback) { continue; } if (l.flags == (aFlags & l.flags)) { listeners->RemoveElementAt(i); i--; } else { l.flags &= ~aFlags; } } } void PlacesObservers::RemoveListener( uint32_t aFlags, places::INativePlacesEventCallback* aCallback) { FlaggedArray>* listeners = WeakNativeListeners::GetListeners(/* aDoNotInit: */ true); if (!listeners) { return; } for (uint32_t i = 0; i < listeners->Length(); i++) { Flagged>& l = listeners->ElementAt(i); RefPtr unwrapped = l.value.get(); if (unwrapped != aCallback) { continue; } if (l.flags == (aFlags & l.flags)) { listeners->RemoveElementAt(i); i--; } else { l.flags &= ~aFlags; } } } template void CleanupListeners( const std::function& aUnwrapListener, const std::function&)>& aRemoveListener) { auto& listeners = *TListenerCollection::GetListeners(); for (uint32_t i = 0; i < listeners.Length(); i++) { Flagged& listener = listeners[i]; TUnwrapped unwrapped = aUnwrapListener(listener.value); if (!unwrapped) { listeners.RemoveElementAt(i); i--; } } auto& listenersToRemove = *TListenerCollection::GetListenersToRemove(); for (auto& listener : listenersToRemove) { aRemoveListener(listener); } listenersToRemove.Clear(); } void PlacesObservers::NotifyListeners( GlobalObject& aGlobal, const Sequence>& aEvents, ErrorResult& rv) { NotifyListeners(aEvents); } void PlacesObservers::NotifyListeners( const Sequence>& aEvents) { MOZ_ASSERT(aEvents.Length() > 0, "Must pass a populated array of events"); if (aEvents.Length() == 0) { return; } #ifdef DEBUG if (!gNotificationQueue.IsEmpty()) { NS_WARNING( "Avoid nested Places notifications if possible, the order of events " "cannot be guaranteed"); nsCOMPtr xpc = nsIXPConnect::XPConnect(); Unused << xpc->DebugDumpJSStack(false, false, false); } #endif gNotificationQueue.AppendElement(aEvents); // If gNotificationQueue has only the events we added now, start to notify. // Otherwise, as it already started the notification processing, // rely on the processing. if (gNotificationQueue.Length() == 1) { NotifyNext(); } } void PlacesObservers::NotifyNext() { auto events = gNotificationQueue[0]; uint32_t flags = GetFlagsForEvents(events); // Send up to the number of current listeners, to avoid handling listeners // added during this notification. unsigned long jsListenersLength = JSListeners::GetListeners()->Length(); unsigned long weakNativeListenersLength = WeakNativeListeners::GetListeners()->Length(); unsigned long weakJSListenersLength = WeakJSListeners::GetListeners()->Length(); CallListeners, RefPtr, JSListeners>( flags, events, jsListenersLength, [](auto& cb) { return cb; }, // MOZ_CAN_RUN_SCRIPT_BOUNDARY because on Windows this gets called from // some internals of the std::function implementation that we can't // annotate. We handle this by annotating CallListeners and making sure // it holds a strong ref to the callback. [&](auto& cb, const auto& events) MOZ_CAN_RUN_SCRIPT_BOUNDARY { MOZ_KnownLive(cb)->Call(events); }); CallListeners, RefPtr, WeakNativeListeners>( flags, events, weakNativeListenersLength, [](auto& cb) { return cb.get(); }, [&](auto& cb, const Sequence>& events) { cb->HandlePlacesEvent(events); }); CallListeners, RefPtr, WeakJSListeners>( flags, events, weakJSListenersLength, [](auto& cb) { return cb.get(); }, // MOZ_CAN_RUN_SCRIPT_BOUNDARY because on Windows this gets called from // some internals of the std::function implementation that we can't // annotate. We handle this by annotating CallListeners and making sure // it holds a strong ref to the callback. [&](auto& cb, const auto& events) MOZ_CAN_RUN_SCRIPT_BOUNDARY { RefPtr callback(cb->mCallback); callback->Call(events); }); gNotificationQueue.RemoveElementAt(0); CleanupListeners, RefPtr, JSListeners>( [](auto& cb) { return cb; }, [&](auto& cb) { RemoveListener(cb.flags, *cb.value); }); CleanupListeners, RefPtr, WeakJSListeners>( [](auto& cb) { return cb.get(); }, [&](auto& cb) { RemoveListener(cb.flags, *cb.value.get()); }); CleanupListeners, RefPtr, WeakNativeListeners>( [](auto& cb) { return cb.get(); }, [&](auto& cb) { RemoveListener(cb.flags, cb.value.get()); }); if (!gNotificationQueue.IsEmpty()) { NotifyNext(); } } } // namespace mozilla::dom