/* -*- 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 "mozilla/ClearOnShutdown.h" namespace mozilla { namespace 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; typedef ListenerCollection> JSListeners; typedef ListenerCollection> WeakJSListeners; typedef ListenerCollection> WeakNativeListeners; static bool gCallingListeners = false; 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, FlaggedArray& aListeners, const Sequence>& aEvents, const std::function& aUnwrapListener, const std::function>&)>& aCallListener) { for (uint32_t i = 0; i < aListeners.Length(); i++) { Flagged& l = aListeners[i]; TUnwrapped unwrapped = aUnwrapListener(l.value); if (!unwrapped) { aListeners.RemoveElementAt(i); i--; continue; } if ((l.flags & aEventFlags) == aEventFlags) { aCallListener(unwrapped, aEvents); } else if (l.flags & aEventFlags) { Sequence> filtered; for (const OwningNonNull& event : aEvents) { if (l.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 (gCallingListeners) { 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 (gCallingListeners) { 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 (gCallingListeners) { 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; } } } void PlacesObservers::NotifyListeners( GlobalObject& aGlobal, const Sequence>& aEvents, ErrorResult& rv) { NotifyListeners(aEvents); } void PlacesObservers::NotifyListeners( const Sequence>& aEvents) { MOZ_RELEASE_ASSERT(!gCallingListeners); gCallingListeners = true; uint32_t flags = GetFlagsForEvents(aEvents); CallListeners, RefPtr>( flags, *JSListeners::GetListeners(), aEvents, [](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(aEvents); }); CallListeners, RefPtr>( flags, *WeakNativeListeners::GetListeners(), aEvents, [](auto& cb) { return cb.get(); }, [&](auto& cb, const Sequence>& events) { cb->HandlePlacesEvent(events); }); CallListeners, RefPtr>( flags, *WeakJSListeners::GetListeners(), aEvents, [](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(aEvents); }); auto& listenersToRemove = *JSListeners::GetListenersToRemove(); if (listenersToRemove.Length() > 0) { for (auto& listener : listenersToRemove) { RemoveListener(listener.flags, *listener.value); } } listenersToRemove.Clear(); auto& weakListenersToRemove = *WeakJSListeners::GetListenersToRemove(); if (weakListenersToRemove.Length() > 0) { for (auto& listener : weakListenersToRemove) { RemoveListener(listener.flags, *listener.value.get()); } } weakListenersToRemove.Clear(); auto& nativeListenersToRemove = *WeakNativeListeners::GetListenersToRemove(); if (nativeListenersToRemove.Length() > 0) { for (auto& listener : nativeListenersToRemove) { RemoveListener(listener.flags, listener.value.get()); } } nativeListenersToRemove.Clear(); gCallingListeners = false; } } // namespace dom } // namespace mozilla