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 void PlacesObservers::AddListener(GlobalObject
& aGlobal
,
127 const nsTArray
<PlacesEventType
>& aEventTypes
,
128 PlacesEventCallback
& aCallback
,
130 uint32_t flags
= GetFlagsForEventTypes(aEventTypes
);
132 FlaggedArray
<RefPtr
<PlacesEventCallback
>>* listeners
=
133 JSListeners::GetListeners();
134 Flagged
<RefPtr
<PlacesEventCallback
>> pair(flags
, &aCallback
);
135 listeners
->AppendElement(pair
);
138 void PlacesObservers::AddListener(GlobalObject
& aGlobal
,
139 const nsTArray
<PlacesEventType
>& aEventTypes
,
140 PlacesWeakCallbackWrapper
& aCallback
,
142 uint32_t flags
= GetFlagsForEventTypes(aEventTypes
);
144 FlaggedArray
<WeakPtr
<PlacesWeakCallbackWrapper
>>* listeners
=
145 WeakJSListeners::GetListeners();
146 WeakPtr
<PlacesWeakCallbackWrapper
> weakCb(&aCallback
);
147 MOZ_ASSERT(weakCb
.get());
148 Flagged
<WeakPtr
<PlacesWeakCallbackWrapper
>> flagged(flags
, std::move(weakCb
));
149 listeners
->AppendElement(flagged
);
152 void PlacesObservers::AddListener(
153 const nsTArray
<PlacesEventType
>& aEventTypes
,
154 places::INativePlacesEventCallback
* aCallback
) {
155 uint32_t flags
= GetFlagsForEventTypes(aEventTypes
);
157 FlaggedArray
<WeakPtr
<places::INativePlacesEventCallback
>>* listeners
=
158 WeakNativeListeners::GetListeners();
159 Flagged
<WeakPtr
<places::INativePlacesEventCallback
>> pair(flags
, aCallback
);
160 listeners
->AppendElement(pair
);
163 void PlacesObservers::RemoveListener(
164 GlobalObject
& aGlobal
, const nsTArray
<PlacesEventType
>& aEventTypes
,
165 PlacesEventCallback
& aCallback
, ErrorResult
& rv
) {
166 uint32_t flags
= GetFlagsForEventTypes(aEventTypes
);
167 if (!gNotificationQueue
.IsEmpty()) {
168 FlaggedArray
<RefPtr
<PlacesEventCallback
>>* listeners
=
169 JSListeners::GetListenersToRemove();
170 Flagged
<RefPtr
<PlacesEventCallback
>> pair(flags
, &aCallback
);
171 listeners
->AppendElement(pair
);
173 RemoveListener(flags
, aCallback
);
177 void PlacesObservers::RemoveListener(
178 GlobalObject
& aGlobal
, const nsTArray
<PlacesEventType
>& aEventTypes
,
179 PlacesWeakCallbackWrapper
& aCallback
, ErrorResult
& rv
) {
180 uint32_t flags
= GetFlagsForEventTypes(aEventTypes
);
181 if (!gNotificationQueue
.IsEmpty()) {
182 FlaggedArray
<WeakPtr
<PlacesWeakCallbackWrapper
>>* listeners
=
183 WeakJSListeners::GetListenersToRemove();
184 WeakPtr
<PlacesWeakCallbackWrapper
> weakCb(&aCallback
);
185 MOZ_ASSERT(weakCb
.get());
186 Flagged
<WeakPtr
<PlacesWeakCallbackWrapper
>> flagged(flags
,
188 listeners
->AppendElement(flagged
);
190 RemoveListener(flags
, aCallback
);
194 void PlacesObservers::RemoveListener(
195 const nsTArray
<PlacesEventType
>& aEventTypes
,
196 places::INativePlacesEventCallback
* aCallback
) {
197 uint32_t flags
= GetFlagsForEventTypes(aEventTypes
);
198 if (!gNotificationQueue
.IsEmpty()) {
199 FlaggedArray
<WeakPtr
<places::INativePlacesEventCallback
>>* listeners
=
200 WeakNativeListeners::GetListenersToRemove();
201 Flagged
<WeakPtr
<places::INativePlacesEventCallback
>> pair(flags
, aCallback
);
202 listeners
->AppendElement(pair
);
204 RemoveListener(flags
, aCallback
);
208 void PlacesObservers::RemoveListener(uint32_t aFlags
,
209 PlacesEventCallback
& aCallback
) {
210 FlaggedArray
<RefPtr
<PlacesEventCallback
>>* listeners
=
211 JSListeners::GetListeners(/* aDoNotInit: */ true);
215 for (uint32_t i
= 0; i
< listeners
->Length(); i
++) {
216 Flagged
<RefPtr
<PlacesEventCallback
>>& l
= listeners
->ElementAt(i
);
217 if (!(*l
.value
== aCallback
)) {
220 if (l
.flags
== (aFlags
& l
.flags
)) {
221 listeners
->RemoveElementAt(i
);
229 void PlacesObservers::RemoveListener(uint32_t aFlags
,
230 PlacesWeakCallbackWrapper
& aCallback
) {
231 FlaggedArray
<WeakPtr
<PlacesWeakCallbackWrapper
>>* listeners
=
232 WeakJSListeners::GetListeners(/* aDoNotInit: */ true);
236 for (uint32_t i
= 0; i
< listeners
->Length(); i
++) {
237 Flagged
<WeakPtr
<PlacesWeakCallbackWrapper
>>& l
= listeners
->ElementAt(i
);
238 RefPtr
<PlacesWeakCallbackWrapper
> unwrapped
= l
.value
.get();
239 if (unwrapped
!= &aCallback
) {
242 if (l
.flags
== (aFlags
& l
.flags
)) {
243 listeners
->RemoveElementAt(i
);
251 void PlacesObservers::RemoveListener(
252 uint32_t aFlags
, places::INativePlacesEventCallback
* aCallback
) {
253 FlaggedArray
<WeakPtr
<places::INativePlacesEventCallback
>>* listeners
=
254 WeakNativeListeners::GetListeners(/* aDoNotInit: */ true);
258 for (uint32_t i
= 0; i
< listeners
->Length(); i
++) {
259 Flagged
<WeakPtr
<places::INativePlacesEventCallback
>>& l
=
260 listeners
->ElementAt(i
);
261 RefPtr
<places::INativePlacesEventCallback
> unwrapped
= l
.value
.get();
262 if (unwrapped
!= aCallback
) {
265 if (l
.flags
== (aFlags
& l
.flags
)) {
266 listeners
->RemoveElementAt(i
);
274 template <class TWrapped
, class TUnwrapped
, class TListenerCollection
>
275 void CleanupListeners(
276 const std::function
<TUnwrapped(TWrapped
&)>& aUnwrapListener
,
277 const std::function
<void(Flagged
<TWrapped
>&)>& aRemoveListener
) {
278 auto& listeners
= *TListenerCollection::GetListeners();
279 for (uint32_t i
= 0; i
< listeners
.Length(); i
++) {
280 Flagged
<TWrapped
>& listener
= listeners
[i
];
281 TUnwrapped unwrapped
= aUnwrapListener(listener
.value
);
283 listeners
.RemoveElementAt(i
);
288 auto& listenersToRemove
= *TListenerCollection::GetListenersToRemove();
289 for (auto& listener
: listenersToRemove
) {
290 aRemoveListener(listener
);
292 listenersToRemove
.Clear();
295 void PlacesObservers::NotifyListeners(
296 GlobalObject
& aGlobal
, const Sequence
<OwningNonNull
<PlacesEvent
>>& aEvents
,
298 NotifyListeners(aEvents
);
301 void PlacesObservers::NotifyListeners(
302 const Sequence
<OwningNonNull
<PlacesEvent
>>& aEvents
) {
303 MOZ_ASSERT(aEvents
.Length() > 0, "Must pass a populated array of events");
304 if (aEvents
.Length() == 0) {
309 if (!gNotificationQueue
.IsEmpty()) {
311 "Avoid nested Places notifications if possible, the order of events "
312 "cannot be guaranteed");
313 nsCOMPtr
<nsIXPConnect
> xpc
= nsIXPConnect::XPConnect();
314 Unused
<< xpc
->DebugDumpJSStack(false, false, false);
318 gNotificationQueue
.AppendElement(aEvents
);
320 // If gNotificationQueue has only the events we added now, start to notify.
321 // Otherwise, as it already started the notification processing,
322 // rely on the processing.
323 if (gNotificationQueue
.Length() == 1) {
328 void PlacesObservers::NotifyNext() {
329 auto events
= gNotificationQueue
[0];
330 uint32_t flags
= GetFlagsForEvents(events
);
332 // Send up to the number of current listeners, to avoid handling listeners
333 // added during this notification.
334 unsigned long jsListenersLength
= JSListeners::GetListeners()->Length();
335 unsigned long weakNativeListenersLength
=
336 WeakNativeListeners::GetListeners()->Length();
337 unsigned long weakJSListenersLength
=
338 WeakJSListeners::GetListeners()->Length();
340 CallListeners
<RefPtr
<PlacesEventCallback
>, RefPtr
<PlacesEventCallback
>,
342 flags
, events
, jsListenersLength
, [](auto& cb
) { return cb
; },
343 // MOZ_CAN_RUN_SCRIPT_BOUNDARY because on Windows this gets called from
344 // some internals of the std::function implementation that we can't
345 // annotate. We handle this by annotating CallListeners and making sure
346 // it holds a strong ref to the callback.
347 [&](auto& cb
, const auto& events
)
348 MOZ_CAN_RUN_SCRIPT_BOUNDARY
{ MOZ_KnownLive(cb
)->Call(events
); });
350 CallListeners
<WeakPtr
<places::INativePlacesEventCallback
>,
351 RefPtr
<places::INativePlacesEventCallback
>,
352 WeakNativeListeners
>(
353 flags
, events
, weakNativeListenersLength
,
354 [](auto& cb
) { return cb
.get(); },
355 [&](auto& cb
, const Sequence
<OwningNonNull
<PlacesEvent
>>& events
) {
356 cb
->HandlePlacesEvent(events
);
359 CallListeners
<WeakPtr
<PlacesWeakCallbackWrapper
>,
360 RefPtr
<PlacesWeakCallbackWrapper
>, WeakJSListeners
>(
361 flags
, events
, weakJSListenersLength
, [](auto& cb
) { return cb
.get(); },
362 // MOZ_CAN_RUN_SCRIPT_BOUNDARY because on Windows this gets called from
363 // some internals of the std::function implementation that we can't
364 // annotate. We handle this by annotating CallListeners and making sure
365 // it holds a strong ref to the callback.
366 [&](auto& cb
, const auto& events
) MOZ_CAN_RUN_SCRIPT_BOUNDARY
{
367 RefPtr
<PlacesEventCallback
> callback(cb
->mCallback
);
368 callback
->Call(events
);
371 gNotificationQueue
.RemoveElementAt(0);
373 CleanupListeners
<RefPtr
<PlacesEventCallback
>, RefPtr
<PlacesEventCallback
>,
375 [](auto& cb
) { return cb
; },
376 [&](auto& cb
) { RemoveListener(cb
.flags
, *cb
.value
); });
377 CleanupListeners
<WeakPtr
<PlacesWeakCallbackWrapper
>,
378 RefPtr
<PlacesWeakCallbackWrapper
>, WeakJSListeners
>(
379 [](auto& cb
) { return cb
.get(); },
380 [&](auto& cb
) { RemoveListener(cb
.flags
, *cb
.value
.get()); });
381 CleanupListeners
<WeakPtr
<places::INativePlacesEventCallback
>,
382 RefPtr
<places::INativePlacesEventCallback
>,
383 WeakNativeListeners
>(
384 [](auto& cb
) { return cb
.get(); },
385 [&](auto& cb
) { RemoveListener(cb
.flags
, cb
.value
.get()); });
387 if (!gNotificationQueue
.IsEmpty()) {
392 } // namespace mozilla::dom