From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- dom/base/PlacesObservers.cpp | 392 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 392 insertions(+) create mode 100644 dom/base/PlacesObservers.cpp (limited to 'dom/base/PlacesObservers.cpp') diff --git a/dom/base/PlacesObservers.cpp b/dom/base/PlacesObservers.cpp new file mode 100644 index 0000000000..bb92e4a072 --- /dev/null +++ b/dom/base/PlacesObservers.cpp @@ -0,0 +1,392 @@ +/* -*- 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 -- cgit v1.2.3