summaryrefslogtreecommitdiffstats
path: root/dom/base/PlacesObservers.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/PlacesObservers.cpp')
-rw-r--r--dom/base/PlacesObservers.cpp392
1 files changed, 392 insertions, 0 deletions
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 <class T>
+struct Flagged {
+ Flagged(uint32_t aFlags, T&& aValue)
+ : flags(aFlags), value(std::forward<T>(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 <class T>
+using FlaggedArray = nsTArray<Flagged<T>>;
+
+template <class T>
+struct ListenerCollection {
+ static StaticAutoPtr<FlaggedArray<T>> gListeners;
+ static StaticAutoPtr<FlaggedArray<T>> gListenersToRemove;
+
+ static FlaggedArray<T>* GetListeners(bool aDoNotInit = false) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!gListeners && !aDoNotInit) {
+ gListeners = new FlaggedArray<T>();
+ ClearOnShutdown(&gListeners);
+ }
+ return gListeners;
+ }
+
+ static FlaggedArray<T>* GetListenersToRemove(bool aDoNotInit = false) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!gListenersToRemove && !aDoNotInit) {
+ gListenersToRemove = new FlaggedArray<T>();
+ ClearOnShutdown(&gListenersToRemove);
+ }
+ return gListenersToRemove;
+ }
+};
+
+template <class T>
+StaticAutoPtr<FlaggedArray<T>> ListenerCollection<T>::gListeners;
+template <class T>
+StaticAutoPtr<FlaggedArray<T>> ListenerCollection<T>::gListenersToRemove;
+
+using JSListeners = ListenerCollection<RefPtr<PlacesEventCallback>>;
+using WeakJSListeners = ListenerCollection<WeakPtr<PlacesWeakCallbackWrapper>>;
+using WeakNativeListeners =
+ ListenerCollection<WeakPtr<places::INativePlacesEventCallback>>;
+
+// 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<Sequence<OwningNonNull<PlacesEvent>>> gNotificationQueue;
+
+uint32_t GetEventTypeFlag(PlacesEventType aEventType) {
+ if (aEventType == PlacesEventType::None) {
+ return 0;
+ }
+ return 1 << ((uint32_t)aEventType - 1);
+}
+
+uint32_t GetFlagsForEventTypes(const nsTArray<PlacesEventType>& aEventTypes) {
+ uint32_t flags = 0;
+ for (PlacesEventType eventType : aEventTypes) {
+ flags |= GetEventTypeFlag(eventType);
+ }
+ return flags;
+}
+
+uint32_t GetFlagsForEvents(
+ const nsTArray<OwningNonNull<PlacesEvent>>& aEvents) {
+ uint32_t flags = 0;
+ for (const PlacesEvent& event : aEvents) {
+ flags |= GetEventTypeFlag(event.Type());
+ }
+ return flags;
+}
+
+template <class TWrapped, class TUnwrapped, class TListenerCollection>
+MOZ_CAN_RUN_SCRIPT void CallListeners(
+ uint32_t aEventFlags, const Sequence<OwningNonNull<PlacesEvent>>& aEvents,
+ unsigned long aListenersLengthToCall,
+ const std::function<TUnwrapped(TWrapped&)>& aUnwrapListener,
+ const std::function<void(TUnwrapped&,
+ const Sequence<OwningNonNull<PlacesEvent>>&)>&
+ aCallListener) {
+ auto& listeners = *TListenerCollection::GetListeners();
+ for (uint32_t i = 0; i < aListenersLengthToCall; i++) {
+ Flagged<TWrapped>& 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<OwningNonNull<PlacesEvent>> filtered;
+ for (const OwningNonNull<PlacesEvent>& 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<PlacesEventType>& aEventTypes,
+ PlacesEventCallback& aCallback,
+ ErrorResult& rv) {
+ uint32_t flags = GetFlagsForEventTypes(aEventTypes);
+
+ FlaggedArray<RefPtr<PlacesEventCallback>>* listeners =
+ JSListeners::GetListeners();
+ Flagged<RefPtr<PlacesEventCallback>> pair(flags, &aCallback);
+ listeners->AppendElement(pair);
+}
+
+void PlacesObservers::AddListener(GlobalObject& aGlobal,
+ const nsTArray<PlacesEventType>& aEventTypes,
+ PlacesWeakCallbackWrapper& aCallback,
+ ErrorResult& rv) {
+ uint32_t flags = GetFlagsForEventTypes(aEventTypes);
+
+ FlaggedArray<WeakPtr<PlacesWeakCallbackWrapper>>* listeners =
+ WeakJSListeners::GetListeners();
+ WeakPtr<PlacesWeakCallbackWrapper> weakCb(&aCallback);
+ MOZ_ASSERT(weakCb.get());
+ Flagged<WeakPtr<PlacesWeakCallbackWrapper>> flagged(flags, std::move(weakCb));
+ listeners->AppendElement(flagged);
+}
+
+void PlacesObservers::AddListener(
+ const nsTArray<PlacesEventType>& aEventTypes,
+ places::INativePlacesEventCallback* aCallback) {
+ uint32_t flags = GetFlagsForEventTypes(aEventTypes);
+
+ FlaggedArray<WeakPtr<places::INativePlacesEventCallback>>* listeners =
+ WeakNativeListeners::GetListeners();
+ Flagged<WeakPtr<places::INativePlacesEventCallback>> pair(flags, aCallback);
+ listeners->AppendElement(pair);
+}
+
+void PlacesObservers::RemoveListener(
+ GlobalObject& aGlobal, const nsTArray<PlacesEventType>& aEventTypes,
+ PlacesEventCallback& aCallback, ErrorResult& rv) {
+ uint32_t flags = GetFlagsForEventTypes(aEventTypes);
+ if (!gNotificationQueue.IsEmpty()) {
+ FlaggedArray<RefPtr<PlacesEventCallback>>* listeners =
+ JSListeners::GetListenersToRemove();
+ Flagged<RefPtr<PlacesEventCallback>> pair(flags, &aCallback);
+ listeners->AppendElement(pair);
+ } else {
+ RemoveListener(flags, aCallback);
+ }
+}
+
+void PlacesObservers::RemoveListener(
+ GlobalObject& aGlobal, const nsTArray<PlacesEventType>& aEventTypes,
+ PlacesWeakCallbackWrapper& aCallback, ErrorResult& rv) {
+ uint32_t flags = GetFlagsForEventTypes(aEventTypes);
+ if (!gNotificationQueue.IsEmpty()) {
+ FlaggedArray<WeakPtr<PlacesWeakCallbackWrapper>>* listeners =
+ WeakJSListeners::GetListenersToRemove();
+ WeakPtr<PlacesWeakCallbackWrapper> weakCb(&aCallback);
+ MOZ_ASSERT(weakCb.get());
+ Flagged<WeakPtr<PlacesWeakCallbackWrapper>> flagged(flags,
+ std::move(weakCb));
+ listeners->AppendElement(flagged);
+ } else {
+ RemoveListener(flags, aCallback);
+ }
+}
+
+void PlacesObservers::RemoveListener(
+ const nsTArray<PlacesEventType>& aEventTypes,
+ places::INativePlacesEventCallback* aCallback) {
+ uint32_t flags = GetFlagsForEventTypes(aEventTypes);
+ if (!gNotificationQueue.IsEmpty()) {
+ FlaggedArray<WeakPtr<places::INativePlacesEventCallback>>* listeners =
+ WeakNativeListeners::GetListenersToRemove();
+ Flagged<WeakPtr<places::INativePlacesEventCallback>> pair(flags, aCallback);
+ listeners->AppendElement(pair);
+ } else {
+ RemoveListener(flags, aCallback);
+ }
+}
+
+void PlacesObservers::RemoveListener(uint32_t aFlags,
+ PlacesEventCallback& aCallback) {
+ FlaggedArray<RefPtr<PlacesEventCallback>>* listeners =
+ JSListeners::GetListeners(/* aDoNotInit: */ true);
+ if (!listeners) {
+ return;
+ }
+ for (uint32_t i = 0; i < listeners->Length(); i++) {
+ Flagged<RefPtr<PlacesEventCallback>>& 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<WeakPtr<PlacesWeakCallbackWrapper>>* listeners =
+ WeakJSListeners::GetListeners(/* aDoNotInit: */ true);
+ if (!listeners) {
+ return;
+ }
+ for (uint32_t i = 0; i < listeners->Length(); i++) {
+ Flagged<WeakPtr<PlacesWeakCallbackWrapper>>& l = listeners->ElementAt(i);
+ RefPtr<PlacesWeakCallbackWrapper> 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<WeakPtr<places::INativePlacesEventCallback>>* listeners =
+ WeakNativeListeners::GetListeners(/* aDoNotInit: */ true);
+ if (!listeners) {
+ return;
+ }
+ for (uint32_t i = 0; i < listeners->Length(); i++) {
+ Flagged<WeakPtr<places::INativePlacesEventCallback>>& l =
+ listeners->ElementAt(i);
+ RefPtr<places::INativePlacesEventCallback> unwrapped = l.value.get();
+ if (unwrapped != aCallback) {
+ continue;
+ }
+ if (l.flags == (aFlags & l.flags)) {
+ listeners->RemoveElementAt(i);
+ i--;
+ } else {
+ l.flags &= ~aFlags;
+ }
+ }
+}
+
+template <class TWrapped, class TUnwrapped, class TListenerCollection>
+void CleanupListeners(
+ const std::function<TUnwrapped(TWrapped&)>& aUnwrapListener,
+ const std::function<void(Flagged<TWrapped>&)>& aRemoveListener) {
+ auto& listeners = *TListenerCollection::GetListeners();
+ for (uint32_t i = 0; i < listeners.Length(); i++) {
+ Flagged<TWrapped>& 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<OwningNonNull<PlacesEvent>>& aEvents,
+ ErrorResult& rv) {
+ NotifyListeners(aEvents);
+}
+
+void PlacesObservers::NotifyListeners(
+ const Sequence<OwningNonNull<PlacesEvent>>& 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<nsIXPConnect> 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<PlacesEventCallback>, RefPtr<PlacesEventCallback>,
+ 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<WeakPtr<places::INativePlacesEventCallback>,
+ RefPtr<places::INativePlacesEventCallback>,
+ WeakNativeListeners>(
+ flags, events, weakNativeListenersLength,
+ [](auto& cb) { return cb.get(); },
+ [&](auto& cb, const Sequence<OwningNonNull<PlacesEvent>>& events) {
+ cb->HandlePlacesEvent(events);
+ });
+
+ CallListeners<WeakPtr<PlacesWeakCallbackWrapper>,
+ RefPtr<PlacesWeakCallbackWrapper>, 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<PlacesEventCallback> callback(cb->mCallback);
+ callback->Call(events);
+ });
+
+ gNotificationQueue.RemoveElementAt(0);
+
+ CleanupListeners<RefPtr<PlacesEventCallback>, RefPtr<PlacesEventCallback>,
+ JSListeners>(
+ [](auto& cb) { return cb; },
+ [&](auto& cb) { RemoveListener(cb.flags, *cb.value); });
+ CleanupListeners<WeakPtr<PlacesWeakCallbackWrapper>,
+ RefPtr<PlacesWeakCallbackWrapper>, WeakJSListeners>(
+ [](auto& cb) { return cb.get(); },
+ [&](auto& cb) { RemoveListener(cb.flags, *cb.value.get()); });
+ CleanupListeners<WeakPtr<places::INativePlacesEventCallback>,
+ RefPtr<places::INativePlacesEventCallback>,
+ WeakNativeListeners>(
+ [](auto& cb) { return cb.get(); },
+ [&](auto& cb) { RemoveListener(cb.flags, cb.value.get()); });
+
+ if (!gNotificationQueue.IsEmpty()) {
+ NotifyNext();
+ }
+}
+
+} // namespace mozilla::dom