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/. */
7 #include "PlacesObservers.h"
9 #include "PlacesWeakCallbackWrapper.h"
10 #include "nsIWeakReferenceUtils.h"
11 #include "nsIXPConnect.h"
12 #include "mozilla/ClearOnShutdown.h"
14 namespace mozilla::dom
{
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;
30 using FlaggedArray
= nsTArray
<Flagged
<T
>>;
33 struct ListenerCollection
{
34 static StaticAutoPtr
<FlaggedArray
<T
>> gListeners
;
35 static StaticAutoPtr
<FlaggedArray
<T
>> gListenersToRemove
;
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
);
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
);
52 return gListenersToRemove
;
57 StaticAutoPtr
<FlaggedArray
<T
>> ListenerCollection
<T
>::gListeners
;
59 StaticAutoPtr
<FlaggedArray
<T
>> ListenerCollection
<T
>::gListenersToRemove
;
61 using JSListeners
= ListenerCollection
<RefPtr
<PlacesEventCallback
>>;
62 using WeakJSListeners
= ListenerCollection
<WeakPtr
<PlacesWeakCallbackWrapper
>>;
63 using WeakNativeListeners
=
64 ListenerCollection
<WeakPtr
<places::INativePlacesEventCallback
>>;
66 // Even if NotifyListeners is called any timing, we mange the notifications with
67 // adding to this queue, then sending in sequence. This avoids sending nested
68 // notifications while previous ones are still being sent.
69 static nsTArray
<Sequence
<OwningNonNull
<PlacesEvent
>>> gNotificationQueue
;
71 uint32_t GetEventTypeFlag(PlacesEventType aEventType
) {
72 if (aEventType
== PlacesEventType::None
) {
75 return 1 << ((uint32_t)aEventType
- 1);
78 uint32_t GetFlagsForEventTypes(const nsTArray
<PlacesEventType
>& aEventTypes
) {
80 for (PlacesEventType eventType
: aEventTypes
) {
81 flags
|= GetEventTypeFlag(eventType
);
86 uint32_t GetFlagsForEvents(
87 const nsTArray
<OwningNonNull
<PlacesEvent
>>& aEvents
) {
89 for (const PlacesEvent
& event
: aEvents
) {
90 flags
|= GetEventTypeFlag(event
.Type());
95 template <class TWrapped
, class TUnwrapped
, class TListenerCollection
>
96 MOZ_CAN_RUN_SCRIPT
void CallListeners(
97 uint32_t aEventFlags
, const Sequence
<OwningNonNull
<PlacesEvent
>>& aEvents
,
98 unsigned long aListenersLengthToCall
,
99 const std::function
<TUnwrapped(TWrapped
&)>& aUnwrapListener
,
100 const std::function
<void(TUnwrapped
&,
101 const Sequence
<OwningNonNull
<PlacesEvent
>>&)>&
103 auto& listeners
= *TListenerCollection::GetListeners();
104 for (uint32_t i
= 0; i
< aListenersLengthToCall
; i
++) {
105 Flagged
<TWrapped
>& listener
= listeners
[i
];
106 TUnwrapped unwrapped
= aUnwrapListener(listener
.value
);
111 if ((listener
.flags
& aEventFlags
) == aEventFlags
) {
112 aCallListener(unwrapped
, aEvents
);
113 } else if (listener
.flags
& aEventFlags
) {
114 Sequence
<OwningNonNull
<PlacesEvent
>> filtered
;
115 for (const OwningNonNull
<PlacesEvent
>& event
: aEvents
) {
116 if (listener
.flags
& GetEventTypeFlag(event
->Type())) {
117 bool success
= !!filtered
.AppendElement(event
, fallible
);
118 MOZ_RELEASE_ASSERT(success
);
121 aCallListener(unwrapped
, filtered
);
126 StaticRefPtr
<PlacesEventCounts
> PlacesObservers::sCounts
;
127 static void EnsureCountsInitialized() {
128 if (!PlacesObservers::sCounts
) {
129 PlacesObservers::sCounts
= new PlacesEventCounts();
130 ClearOnShutdown(&PlacesObservers::sCounts
);
134 void PlacesObservers::AddListener(GlobalObject
& aGlobal
,
135 const nsTArray
<PlacesEventType
>& aEventTypes
,
136 PlacesEventCallback
& aCallback
,
138 uint32_t flags
= GetFlagsForEventTypes(aEventTypes
);
140 FlaggedArray
<RefPtr
<PlacesEventCallback
>>* listeners
=
141 JSListeners::GetListeners();
142 Flagged
<RefPtr
<PlacesEventCallback
>> pair(flags
, &aCallback
);
143 listeners
->AppendElement(pair
);
146 void PlacesObservers::AddListener(GlobalObject
& aGlobal
,
147 const nsTArray
<PlacesEventType
>& aEventTypes
,
148 PlacesWeakCallbackWrapper
& aCallback
,
150 uint32_t flags
= GetFlagsForEventTypes(aEventTypes
);
152 FlaggedArray
<WeakPtr
<PlacesWeakCallbackWrapper
>>* listeners
=
153 WeakJSListeners::GetListeners();
154 WeakPtr
<PlacesWeakCallbackWrapper
> weakCb(&aCallback
);
155 MOZ_ASSERT(weakCb
.get());
156 Flagged
<WeakPtr
<PlacesWeakCallbackWrapper
>> flagged(flags
, std::move(weakCb
));
157 listeners
->AppendElement(flagged
);
160 void PlacesObservers::AddListener(
161 const nsTArray
<PlacesEventType
>& aEventTypes
,
162 places::INativePlacesEventCallback
* aCallback
) {
163 uint32_t flags
= GetFlagsForEventTypes(aEventTypes
);
165 FlaggedArray
<WeakPtr
<places::INativePlacesEventCallback
>>* listeners
=
166 WeakNativeListeners::GetListeners();
167 Flagged
<WeakPtr
<places::INativePlacesEventCallback
>> pair(flags
, aCallback
);
168 listeners
->AppendElement(pair
);
171 void PlacesObservers::RemoveListener(
172 GlobalObject
& aGlobal
, const nsTArray
<PlacesEventType
>& aEventTypes
,
173 PlacesEventCallback
& aCallback
, ErrorResult
& rv
) {
174 uint32_t flags
= GetFlagsForEventTypes(aEventTypes
);
175 if (!gNotificationQueue
.IsEmpty()) {
176 FlaggedArray
<RefPtr
<PlacesEventCallback
>>* listeners
=
177 JSListeners::GetListenersToRemove();
178 Flagged
<RefPtr
<PlacesEventCallback
>> pair(flags
, &aCallback
);
179 listeners
->AppendElement(pair
);
181 RemoveListener(flags
, aCallback
);
185 void PlacesObservers::RemoveListener(
186 GlobalObject
& aGlobal
, const nsTArray
<PlacesEventType
>& aEventTypes
,
187 PlacesWeakCallbackWrapper
& aCallback
, ErrorResult
& rv
) {
188 uint32_t flags
= GetFlagsForEventTypes(aEventTypes
);
189 if (!gNotificationQueue
.IsEmpty()) {
190 FlaggedArray
<WeakPtr
<PlacesWeakCallbackWrapper
>>* listeners
=
191 WeakJSListeners::GetListenersToRemove();
192 WeakPtr
<PlacesWeakCallbackWrapper
> weakCb(&aCallback
);
193 MOZ_ASSERT(weakCb
.get());
194 Flagged
<WeakPtr
<PlacesWeakCallbackWrapper
>> flagged(flags
,
196 listeners
->AppendElement(flagged
);
198 RemoveListener(flags
, aCallback
);
202 void PlacesObservers::RemoveListener(
203 const nsTArray
<PlacesEventType
>& aEventTypes
,
204 places::INativePlacesEventCallback
* aCallback
) {
205 uint32_t flags
= GetFlagsForEventTypes(aEventTypes
);
206 if (!gNotificationQueue
.IsEmpty()) {
207 FlaggedArray
<WeakPtr
<places::INativePlacesEventCallback
>>* listeners
=
208 WeakNativeListeners::GetListenersToRemove();
209 Flagged
<WeakPtr
<places::INativePlacesEventCallback
>> pair(flags
, aCallback
);
210 listeners
->AppendElement(pair
);
212 RemoveListener(flags
, aCallback
);
216 void PlacesObservers::RemoveListener(uint32_t aFlags
,
217 PlacesEventCallback
& aCallback
) {
218 FlaggedArray
<RefPtr
<PlacesEventCallback
>>* listeners
=
219 JSListeners::GetListeners(/* aDoNotInit: */ true);
223 for (uint32_t i
= 0; i
< listeners
->Length(); i
++) {
224 Flagged
<RefPtr
<PlacesEventCallback
>>& l
= listeners
->ElementAt(i
);
225 if (!(*l
.value
== aCallback
)) {
228 if (l
.flags
== (aFlags
& l
.flags
)) {
229 listeners
->RemoveElementAt(i
);
237 void PlacesObservers::RemoveListener(uint32_t aFlags
,
238 PlacesWeakCallbackWrapper
& aCallback
) {
239 FlaggedArray
<WeakPtr
<PlacesWeakCallbackWrapper
>>* listeners
=
240 WeakJSListeners::GetListeners(/* aDoNotInit: */ true);
244 for (uint32_t i
= 0; i
< listeners
->Length(); i
++) {
245 Flagged
<WeakPtr
<PlacesWeakCallbackWrapper
>>& l
= listeners
->ElementAt(i
);
246 RefPtr
<PlacesWeakCallbackWrapper
> unwrapped
= l
.value
.get();
247 if (unwrapped
!= &aCallback
) {
250 if (l
.flags
== (aFlags
& l
.flags
)) {
251 listeners
->RemoveElementAt(i
);
259 void PlacesObservers::RemoveListener(
260 uint32_t aFlags
, places::INativePlacesEventCallback
* aCallback
) {
261 FlaggedArray
<WeakPtr
<places::INativePlacesEventCallback
>>* listeners
=
262 WeakNativeListeners::GetListeners(/* aDoNotInit: */ true);
266 for (uint32_t i
= 0; i
< listeners
->Length(); i
++) {
267 Flagged
<WeakPtr
<places::INativePlacesEventCallback
>>& l
=
268 listeners
->ElementAt(i
);
269 RefPtr
<places::INativePlacesEventCallback
> unwrapped
= l
.value
.get();
270 if (unwrapped
!= aCallback
) {
273 if (l
.flags
== (aFlags
& l
.flags
)) {
274 listeners
->RemoveElementAt(i
);
282 template <class TWrapped
, class TUnwrapped
, class TListenerCollection
>
283 void CleanupListeners(
284 const std::function
<TUnwrapped(TWrapped
&)>& aUnwrapListener
,
285 const std::function
<void(Flagged
<TWrapped
>&)>& aRemoveListener
) {
286 auto& listeners
= *TListenerCollection::GetListeners();
287 for (uint32_t i
= 0; i
< listeners
.Length(); i
++) {
288 Flagged
<TWrapped
>& listener
= listeners
[i
];
289 TUnwrapped unwrapped
= aUnwrapListener(listener
.value
);
291 listeners
.RemoveElementAt(i
);
296 auto& listenersToRemove
= *TListenerCollection::GetListenersToRemove();
297 for (auto& listener
: listenersToRemove
) {
298 aRemoveListener(listener
);
300 listenersToRemove
.Clear();
303 void PlacesObservers::NotifyListeners(
304 GlobalObject
& aGlobal
, const Sequence
<OwningNonNull
<PlacesEvent
>>& aEvents
,
306 NotifyListeners(aEvents
);
309 void PlacesObservers::NotifyListeners(
310 const Sequence
<OwningNonNull
<PlacesEvent
>>& aEvents
) {
311 MOZ_ASSERT(aEvents
.Length() > 0, "Must pass a populated array of events");
312 if (aEvents
.Length() == 0) {
315 EnsureCountsInitialized();
316 for (const auto& event
: aEvents
) {
317 DebugOnly
<nsresult
> rv
= sCounts
->Increment(event
->Type());
318 MOZ_ASSERT(NS_SUCCEEDED(rv
));
321 if (!gNotificationQueue
.IsEmpty()) {
323 "Avoid nested Places notifications if possible, the order of events "
324 "cannot be guaranteed");
325 nsCOMPtr
<nsIXPConnect
> xpc
= nsIXPConnect::XPConnect();
326 Unused
<< xpc
->DebugDumpJSStack(false, false, false);
330 gNotificationQueue
.AppendElement(aEvents
);
332 // If gNotificationQueue has only the events we added now, start to notify.
333 // Otherwise, as it already started the notification processing,
334 // rely on the processing.
335 if (gNotificationQueue
.Length() == 1) {
340 void PlacesObservers::NotifyNext() {
341 auto events
= gNotificationQueue
[0];
342 uint32_t flags
= GetFlagsForEvents(events
);
344 // Send up to the number of current listeners, to avoid handling listeners
345 // added during this notification.
346 unsigned long jsListenersLength
= JSListeners::GetListeners()->Length();
347 unsigned long weakNativeListenersLength
=
348 WeakNativeListeners::GetListeners()->Length();
349 unsigned long weakJSListenersLength
=
350 WeakJSListeners::GetListeners()->Length();
352 CallListeners
<RefPtr
<PlacesEventCallback
>, RefPtr
<PlacesEventCallback
>,
354 flags
, events
, jsListenersLength
, [](auto& cb
) { return cb
; },
355 // MOZ_CAN_RUN_SCRIPT_BOUNDARY because on Windows this gets called from
356 // some internals of the std::function implementation that we can't
357 // annotate. We handle this by annotating CallListeners and making sure
358 // it holds a strong ref to the callback.
359 [&](auto& cb
, const auto& events
)
360 MOZ_CAN_RUN_SCRIPT_BOUNDARY
{ MOZ_KnownLive(cb
)->Call(events
); });
362 CallListeners
<WeakPtr
<places::INativePlacesEventCallback
>,
363 RefPtr
<places::INativePlacesEventCallback
>,
364 WeakNativeListeners
>(
365 flags
, events
, weakNativeListenersLength
,
366 [](auto& cb
) { return cb
.get(); },
367 [&](auto& cb
, const Sequence
<OwningNonNull
<PlacesEvent
>>& events
) {
368 cb
->HandlePlacesEvent(events
);
371 CallListeners
<WeakPtr
<PlacesWeakCallbackWrapper
>,
372 RefPtr
<PlacesWeakCallbackWrapper
>, WeakJSListeners
>(
373 flags
, events
, weakJSListenersLength
, [](auto& cb
) { return cb
.get(); },
374 // MOZ_CAN_RUN_SCRIPT_BOUNDARY because on Windows this gets called from
375 // some internals of the std::function implementation that we can't
376 // annotate. We handle this by annotating CallListeners and making sure
377 // it holds a strong ref to the callback.
378 [&](auto& cb
, const auto& events
) MOZ_CAN_RUN_SCRIPT_BOUNDARY
{
379 RefPtr
<PlacesEventCallback
> callback(cb
->mCallback
);
380 callback
->Call(events
);
383 gNotificationQueue
.RemoveElementAt(0);
385 CleanupListeners
<RefPtr
<PlacesEventCallback
>, RefPtr
<PlacesEventCallback
>,
387 [](auto& cb
) { return cb
; },
388 [&](auto& cb
) { RemoveListener(cb
.flags
, *cb
.value
); });
389 CleanupListeners
<WeakPtr
<PlacesWeakCallbackWrapper
>,
390 RefPtr
<PlacesWeakCallbackWrapper
>, WeakJSListeners
>(
391 [](auto& cb
) { return cb
.get(); },
392 [&](auto& cb
) { RemoveListener(cb
.flags
, *cb
.value
.get()); });
393 CleanupListeners
<WeakPtr
<places::INativePlacesEventCallback
>,
394 RefPtr
<places::INativePlacesEventCallback
>,
395 WeakNativeListeners
>(
396 [](auto& cb
) { return cb
.get(); },
397 [&](auto& cb
) { RemoveListener(cb
.flags
, cb
.value
.get()); });
399 if (!gNotificationQueue
.IsEmpty()) {
404 already_AddRefed
<PlacesEventCounts
> PlacesObservers::Counts(
405 const GlobalObject
& global
) {
406 EnsureCountsInitialized();
407 return do_AddRef(sCounts
);
410 } // namespace mozilla::dom