tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

PlacesObservers.cpp (14738B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "PlacesObservers.h"
      8 
      9 #include "PlacesWeakCallbackWrapper.h"
     10 #include "mozilla/ClearOnShutdown.h"
     11 #include "nsIWeakReferenceUtils.h"
     12 #include "nsIXPConnect.h"
     13 
     14 namespace mozilla::dom {
     15 
     16 template <class T>
     17 struct Flagged {
     18  Flagged(uint32_t aFlags, T&& aValue)
     19      : flags(aFlags), value(std::forward<T>(aValue)) {}
     20  Flagged(Flagged&& aOther)
     21      : Flagged(std::move(aOther.flags), std::move(aOther.value)) {}
     22  Flagged(const Flagged& aOther) = default;
     23  ~Flagged() = default;
     24 
     25  uint32_t flags = 0;
     26  T value;
     27 };
     28 
     29 template <class T>
     30 using FlaggedArray = nsTArray<Flagged<T>>;
     31 
     32 template <class T>
     33 struct ListenerCollection {
     34  static StaticAutoPtr<FlaggedArray<T>> gListeners;
     35  static StaticAutoPtr<FlaggedArray<T>> gListenersToRemove;
     36 
     37  static FlaggedArray<T>* GetListeners(bool aDoNotInit = false) {
     38    MOZ_ASSERT(NS_IsMainThread());
     39    if (!gListeners && !aDoNotInit) {
     40      gListeners = new FlaggedArray<T>();
     41      ClearOnShutdown(&gListeners);
     42    }
     43    return gListeners;
     44  }
     45 
     46  static FlaggedArray<T>* GetListenersToRemove(bool aDoNotInit = false) {
     47    MOZ_ASSERT(NS_IsMainThread());
     48    if (!gListenersToRemove && !aDoNotInit) {
     49      gListenersToRemove = new FlaggedArray<T>();
     50      ClearOnShutdown(&gListenersToRemove);
     51    }
     52    return gListenersToRemove;
     53  }
     54 };
     55 
     56 template <class T>
     57 MOZ_GLOBINIT StaticAutoPtr<FlaggedArray<T>> ListenerCollection<T>::gListeners;
     58 template <class T>
     59 MOZ_GLOBINIT StaticAutoPtr<FlaggedArray<T>>
     60    ListenerCollection<T>::gListenersToRemove;
     61 
     62 using JSListeners = ListenerCollection<RefPtr<PlacesEventCallback>>;
     63 using WeakJSListeners = ListenerCollection<WeakPtr<PlacesWeakCallbackWrapper>>;
     64 using WeakNativeListeners =
     65    ListenerCollection<WeakPtr<places::INativePlacesEventCallback>>;
     66 
     67 // Even if NotifyListeners is called any timing, we mange the notifications with
     68 // adding to this queue, then sending in sequence. This avoids sending nested
     69 // notifications while previous ones are still being sent.
     70 constinit static nsTArray<Sequence<OwningNonNull<PlacesEvent>>>
     71    gNotificationQueue;
     72 
     73 uint32_t GetEventTypeFlag(PlacesEventType aEventType) {
     74  if (aEventType == PlacesEventType::None) {
     75    return 0;
     76  }
     77  return 1 << ((uint32_t)aEventType - 1);
     78 }
     79 
     80 uint32_t GetFlagsForEventTypes(const nsTArray<PlacesEventType>& aEventTypes) {
     81  uint32_t flags = 0;
     82  for (PlacesEventType eventType : aEventTypes) {
     83    flags |= GetEventTypeFlag(eventType);
     84  }
     85  return flags;
     86 }
     87 
     88 uint32_t GetFlagsForEvents(
     89    const nsTArray<OwningNonNull<PlacesEvent>>& aEvents) {
     90  uint32_t flags = 0;
     91  for (const PlacesEvent& event : aEvents) {
     92    flags |= GetEventTypeFlag(event.Type());
     93  }
     94  return flags;
     95 }
     96 
     97 template <class TWrapped, class TUnwrapped, class TListenerCollection>
     98 MOZ_CAN_RUN_SCRIPT void CallListeners(
     99    uint32_t aEventFlags, const Sequence<OwningNonNull<PlacesEvent>>& aEvents,
    100    unsigned long aListenersLengthToCall,
    101    const std::function<TUnwrapped(TWrapped&)>& aUnwrapListener,
    102    const std::function<void(TUnwrapped&,
    103                             const Sequence<OwningNonNull<PlacesEvent>>&)>&
    104        aCallListener) {
    105  auto& listeners = *TListenerCollection::GetListeners();
    106  for (uint32_t i = 0; i < aListenersLengthToCall; i++) {
    107    Flagged<TWrapped>& listener = listeners[i];
    108    TUnwrapped unwrapped = aUnwrapListener(listener.value);
    109    if (!unwrapped) {
    110      continue;
    111    }
    112 
    113    if ((listener.flags & aEventFlags) == aEventFlags) {
    114      aCallListener(unwrapped, aEvents);
    115    } else if (listener.flags & aEventFlags) {
    116      Sequence<OwningNonNull<PlacesEvent>> filtered;
    117      for (const OwningNonNull<PlacesEvent>& event : aEvents) {
    118        if (listener.flags & GetEventTypeFlag(event->Type())) {
    119          bool success = !!filtered.AppendElement(event, fallible);
    120          MOZ_RELEASE_ASSERT(success);
    121        }
    122      }
    123      aCallListener(unwrapped, filtered);
    124    }
    125  }
    126 }
    127 
    128 StaticRefPtr<PlacesEventCounts> PlacesObservers::sCounts;
    129 static void EnsureCountsInitialized() {
    130  if (!PlacesObservers::sCounts) {
    131    PlacesObservers::sCounts = new PlacesEventCounts();
    132    ClearOnShutdown(&PlacesObservers::sCounts);
    133  }
    134 }
    135 
    136 void PlacesObservers::AddListener(GlobalObject& aGlobal,
    137                                  const nsTArray<PlacesEventType>& aEventTypes,
    138                                  PlacesEventCallback& aCallback,
    139                                  ErrorResult& rv) {
    140  uint32_t flags = GetFlagsForEventTypes(aEventTypes);
    141 
    142  FlaggedArray<RefPtr<PlacesEventCallback>>* listeners =
    143      JSListeners::GetListeners();
    144  Flagged<RefPtr<PlacesEventCallback>> pair(flags, &aCallback);
    145  listeners->AppendElement(pair);
    146 }
    147 
    148 void PlacesObservers::AddListener(GlobalObject& aGlobal,
    149                                  const nsTArray<PlacesEventType>& aEventTypes,
    150                                  PlacesWeakCallbackWrapper& aCallback,
    151                                  ErrorResult& rv) {
    152  uint32_t flags = GetFlagsForEventTypes(aEventTypes);
    153 
    154  FlaggedArray<WeakPtr<PlacesWeakCallbackWrapper>>* listeners =
    155      WeakJSListeners::GetListeners();
    156  WeakPtr<PlacesWeakCallbackWrapper> weakCb(&aCallback);
    157  MOZ_ASSERT(weakCb.get());
    158  Flagged<WeakPtr<PlacesWeakCallbackWrapper>> flagged(flags, std::move(weakCb));
    159  listeners->AppendElement(flagged);
    160 }
    161 
    162 void PlacesObservers::AddListener(
    163    const nsTArray<PlacesEventType>& aEventTypes,
    164    places::INativePlacesEventCallback* aCallback) {
    165  uint32_t flags = GetFlagsForEventTypes(aEventTypes);
    166 
    167  FlaggedArray<WeakPtr<places::INativePlacesEventCallback>>* listeners =
    168      WeakNativeListeners::GetListeners();
    169  Flagged<WeakPtr<places::INativePlacesEventCallback>> pair(flags, aCallback);
    170  listeners->AppendElement(pair);
    171 }
    172 
    173 void PlacesObservers::RemoveListener(
    174    GlobalObject& aGlobal, const nsTArray<PlacesEventType>& aEventTypes,
    175    PlacesEventCallback& aCallback, ErrorResult& rv) {
    176  uint32_t flags = GetFlagsForEventTypes(aEventTypes);
    177  if (!gNotificationQueue.IsEmpty()) {
    178    FlaggedArray<RefPtr<PlacesEventCallback>>* listeners =
    179        JSListeners::GetListenersToRemove();
    180    Flagged<RefPtr<PlacesEventCallback>> pair(flags, &aCallback);
    181    listeners->AppendElement(pair);
    182  } else {
    183    RemoveListener(flags, aCallback);
    184  }
    185 }
    186 
    187 void PlacesObservers::RemoveListener(
    188    GlobalObject& aGlobal, const nsTArray<PlacesEventType>& aEventTypes,
    189    PlacesWeakCallbackWrapper& aCallback, ErrorResult& rv) {
    190  uint32_t flags = GetFlagsForEventTypes(aEventTypes);
    191  if (!gNotificationQueue.IsEmpty()) {
    192    FlaggedArray<WeakPtr<PlacesWeakCallbackWrapper>>* listeners =
    193        WeakJSListeners::GetListenersToRemove();
    194    WeakPtr<PlacesWeakCallbackWrapper> weakCb(&aCallback);
    195    MOZ_ASSERT(weakCb.get());
    196    Flagged<WeakPtr<PlacesWeakCallbackWrapper>> flagged(flags,
    197                                                        std::move(weakCb));
    198    listeners->AppendElement(flagged);
    199  } else {
    200    RemoveListener(flags, aCallback);
    201  }
    202 }
    203 
    204 void PlacesObservers::RemoveListener(
    205    const nsTArray<PlacesEventType>& aEventTypes,
    206    places::INativePlacesEventCallback* aCallback) {
    207  uint32_t flags = GetFlagsForEventTypes(aEventTypes);
    208  if (!gNotificationQueue.IsEmpty()) {
    209    FlaggedArray<WeakPtr<places::INativePlacesEventCallback>>* listeners =
    210        WeakNativeListeners::GetListenersToRemove();
    211    Flagged<WeakPtr<places::INativePlacesEventCallback>> pair(flags, aCallback);
    212    listeners->AppendElement(pair);
    213  } else {
    214    RemoveListener(flags, aCallback);
    215  }
    216 }
    217 
    218 void PlacesObservers::RemoveListener(uint32_t aFlags,
    219                                     PlacesEventCallback& aCallback) {
    220  FlaggedArray<RefPtr<PlacesEventCallback>>* listeners =
    221      JSListeners::GetListeners(/* aDoNotInit: */ true);
    222  if (!listeners) {
    223    return;
    224  }
    225  for (uint32_t i = 0; i < listeners->Length(); i++) {
    226    Flagged<RefPtr<PlacesEventCallback>>& l = listeners->ElementAt(i);
    227    if (!(*l.value == aCallback)) {
    228      continue;
    229    }
    230    if (l.flags == (aFlags & l.flags)) {
    231      listeners->RemoveElementAt(i);
    232      i--;
    233    } else {
    234      l.flags &= ~aFlags;
    235    }
    236  }
    237 }
    238 
    239 void PlacesObservers::RemoveListener(uint32_t aFlags,
    240                                     PlacesWeakCallbackWrapper& aCallback) {
    241  FlaggedArray<WeakPtr<PlacesWeakCallbackWrapper>>* listeners =
    242      WeakJSListeners::GetListeners(/* aDoNotInit: */ true);
    243  if (!listeners) {
    244    return;
    245  }
    246  for (uint32_t i = 0; i < listeners->Length(); i++) {
    247    Flagged<WeakPtr<PlacesWeakCallbackWrapper>>& l = listeners->ElementAt(i);
    248    RefPtr<PlacesWeakCallbackWrapper> unwrapped = l.value.get();
    249    if (unwrapped != &aCallback) {
    250      continue;
    251    }
    252    if (l.flags == (aFlags & l.flags)) {
    253      listeners->RemoveElementAt(i);
    254      i--;
    255    } else {
    256      l.flags &= ~aFlags;
    257    }
    258  }
    259 }
    260 
    261 void PlacesObservers::RemoveListener(
    262    uint32_t aFlags, places::INativePlacesEventCallback* aCallback) {
    263  FlaggedArray<WeakPtr<places::INativePlacesEventCallback>>* listeners =
    264      WeakNativeListeners::GetListeners(/* aDoNotInit: */ true);
    265  if (!listeners) {
    266    return;
    267  }
    268  for (uint32_t i = 0; i < listeners->Length(); i++) {
    269    Flagged<WeakPtr<places::INativePlacesEventCallback>>& l =
    270        listeners->ElementAt(i);
    271    RefPtr<places::INativePlacesEventCallback> unwrapped = l.value.get();
    272    if (unwrapped != aCallback) {
    273      continue;
    274    }
    275    if (l.flags == (aFlags & l.flags)) {
    276      listeners->RemoveElementAt(i);
    277      i--;
    278    } else {
    279      l.flags &= ~aFlags;
    280    }
    281  }
    282 }
    283 
    284 template <class TWrapped, class TUnwrapped, class TListenerCollection>
    285 void CleanupListeners(
    286    const std::function<TUnwrapped(TWrapped&)>& aUnwrapListener,
    287    const std::function<void(Flagged<TWrapped>&)>& aRemoveListener) {
    288  auto& listeners = *TListenerCollection::GetListeners();
    289  for (uint32_t i = 0; i < listeners.Length(); i++) {
    290    Flagged<TWrapped>& listener = listeners[i];
    291    TUnwrapped unwrapped = aUnwrapListener(listener.value);
    292    if (!unwrapped) {
    293      listeners.RemoveElementAt(i);
    294      i--;
    295    }
    296  }
    297 
    298  auto& listenersToRemove = *TListenerCollection::GetListenersToRemove();
    299  for (auto& listener : listenersToRemove) {
    300    aRemoveListener(listener);
    301  }
    302  listenersToRemove.Clear();
    303 }
    304 
    305 void PlacesObservers::NotifyListeners(
    306    GlobalObject& aGlobal, const Sequence<OwningNonNull<PlacesEvent>>& aEvents,
    307    ErrorResult& rv) {
    308  NotifyListeners(aEvents);
    309 }
    310 
    311 void PlacesObservers::NotifyListeners(
    312    const Sequence<OwningNonNull<PlacesEvent>>& aEvents) {
    313  MOZ_ASSERT(aEvents.Length() > 0, "Must pass a populated array of events");
    314  if (aEvents.Length() == 0) {
    315    return;
    316  }
    317  EnsureCountsInitialized();
    318  for (const auto& event : aEvents) {
    319    DebugOnly<nsresult> rv = sCounts->Increment(event->Type());
    320    MOZ_ASSERT(NS_SUCCEEDED(rv));
    321  }
    322 #ifdef DEBUG
    323  if (!gNotificationQueue.IsEmpty()) {
    324    NS_WARNING(
    325        "Avoid nested Places notifications if possible, the order of events "
    326        "cannot be guaranteed");
    327    nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
    328    (void)xpc->DebugDumpJSStack(false, false, false);
    329  }
    330 #endif
    331 
    332  gNotificationQueue.AppendElement(aEvents);
    333 
    334  // If gNotificationQueue has only the events we added now, start to notify.
    335  // Otherwise, as it already started the notification processing,
    336  // rely on the processing.
    337  if (gNotificationQueue.Length() == 1) {
    338    NotifyNext();
    339  }
    340 }
    341 
    342 void PlacesObservers::NotifyNext() {
    343  auto events = gNotificationQueue[0];
    344  uint32_t flags = GetFlagsForEvents(events);
    345 
    346  // Send up to the number of current listeners, to avoid handling listeners
    347  // added during this notification.
    348  unsigned long jsListenersLength = JSListeners::GetListeners()->Length();
    349  unsigned long weakNativeListenersLength =
    350      WeakNativeListeners::GetListeners()->Length();
    351  unsigned long weakJSListenersLength =
    352      WeakJSListeners::GetListeners()->Length();
    353 
    354  CallListeners<RefPtr<PlacesEventCallback>, RefPtr<PlacesEventCallback>,
    355                JSListeners>(
    356      flags, events, jsListenersLength, [](auto& cb) { return cb; },
    357      // MOZ_CAN_RUN_SCRIPT_BOUNDARY because on Windows this gets called from
    358      // some internals of the std::function implementation that we can't
    359      // annotate.  We handle this by annotating CallListeners and making sure
    360      // it holds a strong ref to the callback.
    361      [&](auto& cb, const auto& events)
    362          MOZ_CAN_RUN_SCRIPT_BOUNDARY { MOZ_KnownLive(cb)->Call(events); });
    363 
    364  CallListeners<WeakPtr<places::INativePlacesEventCallback>,
    365                RefPtr<places::INativePlacesEventCallback>,
    366                WeakNativeListeners>(
    367      flags, events, weakNativeListenersLength,
    368      [](auto& cb) { return cb.get(); },
    369      [&](auto& cb, const Sequence<OwningNonNull<PlacesEvent>>& events) {
    370        cb->HandlePlacesEvent(events);
    371      });
    372 
    373  CallListeners<WeakPtr<PlacesWeakCallbackWrapper>,
    374                RefPtr<PlacesWeakCallbackWrapper>, WeakJSListeners>(
    375      flags, events, weakJSListenersLength, [](auto& cb) { return cb.get(); },
    376      // MOZ_CAN_RUN_SCRIPT_BOUNDARY because on Windows this gets called from
    377      // some internals of the std::function implementation that we can't
    378      // annotate.  We handle this by annotating CallListeners and making sure
    379      // it holds a strong ref to the callback.
    380      [&](auto& cb, const auto& events) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
    381        RefPtr<PlacesEventCallback> callback(cb->mCallback);
    382        callback->Call(events);
    383      });
    384 
    385  gNotificationQueue.RemoveElementAt(0);
    386 
    387  CleanupListeners<RefPtr<PlacesEventCallback>, RefPtr<PlacesEventCallback>,
    388                   JSListeners>(
    389      [](auto& cb) { return cb; },
    390      [&](auto& cb) { RemoveListener(cb.flags, *cb.value); });
    391  CleanupListeners<WeakPtr<PlacesWeakCallbackWrapper>,
    392                   RefPtr<PlacesWeakCallbackWrapper>, WeakJSListeners>(
    393      [](auto& cb) { return cb.get(); },
    394      [&](auto& cb) { RemoveListener(cb.flags, *cb.value.get()); });
    395  CleanupListeners<WeakPtr<places::INativePlacesEventCallback>,
    396                   RefPtr<places::INativePlacesEventCallback>,
    397                   WeakNativeListeners>(
    398      [](auto& cb) { return cb.get(); },
    399      [&](auto& cb) { RemoveListener(cb.flags, cb.value.get()); });
    400 
    401  if (!gNotificationQueue.IsEmpty()) {
    402    NotifyNext();
    403  }
    404 }
    405 
    406 already_AddRefed<PlacesEventCounts> PlacesObservers::Counts(
    407    const GlobalObject& global) {
    408  EnsureCountsInitialized();
    409  return do_AddRef(sCounts);
    410 };
    411 
    412 }  // namespace mozilla::dom