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